Oblicz datę na podstawie numeru tygodnia

Odpowiedzi:

251

Miałem problemy z rozwiązaniem @HenkHolterman, nawet z poprawką @RobinAndersson.

Czytanie o standardzie ISO 8601 ładnie rozwiązuje problem. Jako cel użyj pierwszego czwartku, a nie poniedziałku. Poniższy kod będzie działał również przez 53 tydzień 2009 roku.

public static DateTime FirstDateOfWeekISO8601(int year, int weekOfYear)
{
    DateTime jan1 = new DateTime(year, 1, 1);
    int daysOffset = DayOfWeek.Thursday - jan1.DayOfWeek;

    // Use first Thursday in January to get first week of the year as
    // it will never be in Week 52/53
    DateTime firstThursday = jan1.AddDays(daysOffset);
    var cal = CultureInfo.CurrentCulture.Calendar;
    int firstWeek = cal.GetWeekOfYear(firstThursday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

    var weekNum = weekOfYear;
    // As we're adding days to a date in Week 1,
    // we need to subtract 1 in order to get the right date for week #1
    if (firstWeek == 1)
    {
        weekNum -= 1;
    }

    // Using the first Thursday as starting week ensures that we are starting in the right year
    // then we add number of weeks multiplied with days
    var result = firstThursday.AddDays(weekNum * 7);

    // Subtract 3 days from Thursday to get Monday, which is the first weekday in ISO8601
    return result.AddDays(-3);
}       
Mikael Svenson
źródło
3
Wypróbowałem prawie wszystkie podane tutaj rozwiązania, to jest jedyne, które teraz działa poprawnie. Obecnie jest to 7 lutego 2012. Tydzień nr to # 6. Ten kod poprawnie podaje 6 lutego jako datę rozpoczęcia tygodnia. Wszystkie inne rozwiązania dały mi 13 lutego, który jest właściwie datą rozpoczęcia tygodnia # 7.
HaukurHaf,
1
Działa jak urok ... przetestowałem kilka danych: pastebin.com/mfx8s1vq Wszystko działa bez zarzutu! Dzięki Mikael!
Mittchel
1
@ RobinWassén-Andersson Dobrze, że wróciłeś do tego pytania: D 6 więcej głosów, a połączę się z „nie” poprawną odpowiedzią hehe.
Mikael Svenson
2
Sugestia: niech „ISO8601” będzie odpowiadać nazwie metody, ponieważ po prostu nie ma uniwersalnej „poprawnej” odpowiedzi.
Henk Holterman
1
@Muflix Nie wiem wystarczająco dużo o SQL i datach / kalendarzach, żeby wiedzieć - ale CLR zadziała.
Mikael Svenson
36

Podoba mi się rozwiązanie dostarczone przez Henka Holtermana. Ale aby być trochę bardziej niezależnym od kultury, musisz wybrać pierwszy dzień tygodnia dla aktualnej kultury (nie zawsze jest to poniedziałek):

using System.Globalization;

static DateTime FirstDateOfWeek(int year, int weekOfYear)
{
  DateTime jan1 = new DateTime(year, 1, 1);

  int daysOffset = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek - (int)jan1.DayOfWeek;

  DateTime firstMonday = jan1.AddDays(daysOffset);

  int firstWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(jan1, CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule, CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek);

  if (firstWeek <= 1)
  {
    weekOfYear -= 1;
  }

  return firstMonday.AddDays(weekOfYear * 7);
}
Tomasz
źródło
Dodałem to do Mannex, jako metodę rozszerzającą do kalendarza i DateTimeFormatInfo. Odwołałem się również do tej odpowiedzi w sprawie kredytu.
Atif Aziz
1
Nie działa poprawnie na moim komputerze. Pokazuje datę z 0010 zamiast 2010. Nie wiem, czy jest to problem we frameworku .net czy w tej funkcji. W każdym razie niezła próba ...
Eduardo Xavier
6
firstMondayto zła nazwa zmiennej dla czegoś, co może nie być poniedziałkiem. =)
mflodin
11

AKTUALIZACJA : .NET Core 3.0 i .NET Standard 2.1 są dostarczane z tym typem.

Dobre wieści! Prośba przyciąganie dodanie System.Globalization.ISOWeekdo .NET Rdzenia właśnie połączyły się i jest obecnie planowana na wydaniu 3.0. Miejmy nadzieję, że rozprzestrzeni się na inne platformy .NET w niezbyt odległej przyszłości.

Powinieneś być w stanie użyć ISOWeek.ToDateTime(int year, int week, DayOfWeek dayOfWeek)metody, aby to obliczyć.

Możesz znaleźć kod źródłowy tutaj .

khellang
źródło
9

Najprościej jest prawdopodobnie znaleźć pierwszy poniedziałek roku, a następnie dodać odpowiednią liczbę tygodni. Oto przykładowy kod. Przy okazji zakłada numer tygodnia zaczynający się od 1:

using System;

class Test
{
    static void Main()
    {
        // Show the third Tuesday in 2009. Should be January 20th
        Console.WriteLine(YearWeekDayToDateTime(2009, DayOfWeek.Tuesday, 3));
    }

    static DateTime YearWeekDayToDateTime(int year, DayOfWeek day, int week)
    {
        DateTime startOfYear = new DateTime (year, 1, 1);

        // The +7 and %7 stuff is to avoid negative numbers etc.
        int daysToFirstCorrectDay = (((int)day - (int)startOfYear.DayOfWeek) + 7) % 7;

        return startOfYear.AddDays(7 * (week-1) + daysToFirstCorrectDay);
    }
}
Jon Skeet
źródło
4
Tak, ale pierwszy poniedziałek roku może przypadać na 52 | 53 tydzień poprzedniego roku.
Henk Holterman
1
To zależy, jak chcesz zdefiniować rzeczy. Niestety nie mamy tu zbyt wielu informacji ... Mam nadzieję, że jest to przydatne.
Jon Skeet,
3

Osobiście skorzystałbym z informacji o kulturze, aby poznać dzień tygodnia i przejść do pierwszego dnia tygodnia kultury. Nie jestem pewien, czy dobrze to wyjaśniam, oto przykład:

    public DateTime GetFirstDayOfWeek(int year, int weekNumber)
    {
        return GetFirstDayOfWeek(year, weekNumber, Application.CurrentCulture);
    }

    public DateTime GetFirstDayOfWeek(int year, int weekNumber,
        System.Globalization.CultureInfo culture)
    {
        System.Globalization.Calendar calendar = culture.Calendar;
        DateTime firstOfYear = new DateTime(year, 1, 1, calendar);
        DateTime targetDay = calendar.AddWeeks(firstOfYear, weekNumber);
        DayOfWeek firstDayOfWeek = culture.DateTimeFormat.FirstDayOfWeek;

        while (targetDay.DayOfWeek != firstDayOfWeek)
        {
            targetDay = targetDay.AddDays(-1);
        }

        return targetDay;
    }
Alexander Kahoun
źródło
3

używając Fluent DateTime http://fluentdatetime.codeplex.com/

        var year = 2009;
        var firstDayOfYear = new DateTime(year, 1, 1);
        var firstMonday = firstDayOfYear.Next(DayOfWeek.Monday);
        var weeksDateTime = 12.Weeks().Since(firstMonday);
Szymon
źródło
2

Zgodnie z normą ISO 8601: 1988 , używaną w Szwecji, pierwszy tydzień roku jest pierwszym tygodniem, który ma co najmniej cztery dni w nowym roku.

Więc jeśli Twój tydzień zaczyna się w poniedziałek, pierwszy czwartek dowolnego roku przypada w ciągu pierwszego tygodnia. Możesz od tego DateAdd lub DateDiff.

idstam
źródło
2

Zakładając, że numer tygodnia zaczyna się od 1

DateTime dt =  new DateTime(YearNumber, 1, 1).AddDays((WeekNumber - 1) * 7 - (WeekNumber == 1 ? 0 : 1));
return dt.AddDays(-(int)dt.DayOfWeek);

To powinno dać ci pierwszy dzień w danym tygodniu. Nie przeprowadzałem wielu testów, ale wygląda na to, że działa. To mniejsze rozwiązanie niż większość innych, które znalazłem w Internecie, więc chciałem się nim podzielić.

QuinnG
źródło
zła odpowiedź. Korzystanie z DateTime.Parse nie jest wymagane, ponieważ DateTime ma konstruktora, który przyjmuje rok, miesiąc i dzień. new DateTime(1,1,YearNumber)
Jamiec
Zaktualizowano kod, aby utworzyć nowy DateTime zamiast używania analizy. Było późno. ;)
QuinnG
2

Bezpłatna biblioteka okresów dla platformy .NET obejmuje tydzień klasy zgodności z normą ISO 8601 :

// ----------------------------------------------------------------------
public static DateTime GetFirstDayOfWeek( int year, int weekOfYear )
{
  return new Week( year, weekOfYear ).FirstDayStart;
} // GetFirstDayOfWeek

źródło
2

Ten zadziałał dla mnie, ma również tę zaletę, że oczekuje informacji o kulturze jako parametru do testowania formuły w różnych kulturach. Jeśli jest pusty, pobiera bieżące informacje o kulturze ... prawidłowe wartości to: „it”, „en-us”, „fr”, ... i tak dalej. Sztuczka polega na odjęciu numeru tygodnia pierwszego dnia roku, który może wynosić 1, aby wskazać, że pierwszy dzień przypada w pierwszym tygodniu. Mam nadzieję że to pomoże.

Public Shared Function FirstDayOfWeek(ByVal year As Integer, ByVal weekNumber As Integer, ByVal culture As String) As Date
    Dim cInfo As System.Globalization.CultureInfo
    If culture = "" Then
        cInfo = System.Globalization.CultureInfo.CurrentCulture
    Else
        cInfo = System.Globalization.CultureInfo.CreateSpecificCulture(culture)
    End If
    Dim calendar As System.Globalization.Calendar = cInfo.Calendar
    Dim firstOfYear As DateTime = New DateTime(year, 1, 1, calendar)
    Dim firstDayWeek As Integer = calendar.GetWeekOfYear(firstOfYear, cInfo.DateTimeFormat.CalendarWeekRule, cInfo.DateTimeFormat.FirstDayOfWeek)
    weekNumber -= firstDayWeek
    Dim targetDay As DateTime = calendar.AddWeeks(firstOfYear, weekNumber)
    Dim fDayOfWeek As DayOfWeek = cInfo.DateTimeFormat.FirstDayOfWeek

    While (targetDay.DayOfWeek <> fDayOfWeek)
        targetDay = targetDay.AddDays(-1)
    End While
    Return targetDay
End Function
Paolo Marani
źródło
2

Oto metoda, która jest zgodna z numerami tygodni używanymi w Google Analytics, a także z tym samym schematem numeracji, którego używaliśmy wewnętrznie w firmie Intel, i jestem pewien, że jest również używany w wielu innych kontekstach.

// Google Analytics does not follow ISO standards for date.
// It numbers week 1 starting on Jan. 1, regardless what day of week it starts on.
// It treats Sunday as the first day of the week.
// The first and last weeks of a year are usually not complete weeks.
public static DateTime GetStartDateTimeFromWeekNumberInYear(int year, uint weekOfYear)
{
  if (weekOfYear == 0 || weekOfYear > 54) throw new ArgumentException("Week number must be between 1 and 54! (Yes, 54... Year 2000 had Jan. 1 on a Saturday plus 53 Sundays.)");

  // January 1 -- first week.
  DateTime firstDayInWeek = new DateTime(year, 1, 1);
  if (weekOfYear == 1) return firstDayInWeek;

  // Get second week, starting on the following Sunday.      
  do
  {
    firstDayInWeek = firstDayInWeek.AddDays(1);
  } while (firstDayInWeek.DayOfWeek != DayOfWeek.Sunday);

  if (weekOfYear == 2) return firstDayInWeek;

  // Now get the Sunday of whichever week we're looking for.
  return firstDayInWeek.AddDays((weekOfYear - 2)*7);
}
Samer Adra
źródło
2

Wypróbowałem kilka kodów powyżej i niektóre mają małe błędy, kiedy spróbujesz różnych lat z różnymi początkowymi dniami tygodnia, zobaczysz je, wziąłem kod Jona Skeeta, naprawiłem i działa, bardzo prosty kod.

Public Function YearWeekDayToDateTime(ByVal year As Integer, ByVal weekDay As Integer, ByVal week As Integer) As DateTime
   ' weekDay, day you want
    Dim startOfYear As New DateTime(year, 1, 1)
    Dim startOfYearFixDay As Integer

    If startOfYear.DayOfWeek <> DayOfWeek.Sunday Then
        startOfYearFixDay = startOfYear.DayOfWeek
    Else
        startOfYearFixDay = 7
    End If

    Return startOfYear.AddDays((7 * (week)) - startOfYearFixDay + weekDay)
End Function
GEORGE BAXTER
źródło
1

Lekko zmieniony kod Mikaela Svensona. Znalazłem tydzień pierwszego poniedziałku i odpowiednio zmieniłem numer tygodnia.

 DateTime GetFirstWeekDay(int year, int weekNum)
    {
        Calendar calendar = CultureInfo.CurrentCulture.Calendar;

        DateTime jan1 = new DateTime(year, 1, 1);

        int daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
        DateTime firstMonday = jan1.AddDays(daysOffset);
        int firstMondayWeekNum = calendar.GetWeekOfYear(firstMonday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

        DateTime firstWeekDay = firstMonday.AddDays((weekNum-firstMondayWeekNum) * 7);

        return firstWeekDay;
    }
Vitalij Roscinski
źródło
0

Tydzień 1 definiuje się jako tydzień rozpoczynający się w poniedziałek i zawierający pierwszy czwartek roku.

amaca
źródło
1
To jedna definicja, są inne.
Henk Holterman
1
Standard ISO definiuje tydzień 1 jako tydzień zawierający pierwszy czwartek roku.
RickardN
0

Poprawiłem trochę rozwiązanie Thomasa z zastąpieniem:

   public static DateTime FirstDateOfWeek(int year, int weekOfYear)
    {
      return Timer.FirstDateOfWeekOfMonth(year, 1, weekOfYear);
    }

    public static DateTime FirstDateOfWeekOfMonth(int year, int month, 
    int weekOfYear)
    {
      DateTime dtFirstDayOfMonth = new DateTime(year, month, 1);

       //I also commented out this part:
      /*
      if (firstWeek <= 1)
      {
        weekOfYear -= 1;
      }
      */

W przeciwnym razie data poprzedzała tydzień.

Dziękuję Thomas, świetna pomoc.

pasx
źródło
0

Użyłem jednego z rozwiązań, ale dało mi to błędne wyniki, po prostu dlatego, że niedziela liczy się jako pierwszy dzień tygodnia.

Zmieniłam:

var firstDay = new DateTime(DateTime.Now.Year, 1, 1).AddDays((weekNumber - 1) * 7);
var lastDay = firstDay.AddDays(6);

do:

var lastDay = new DateTime(DateTime.Now.Year, 1, 1).AddDays((weekNumber) * 7);
var firstDay = lastDay.AddDays(-6);

a teraz działa jak urok.

user1564287
źródło
1
Ale czy to odpowiada na pytanie OP? To 1/1 plus stała liczba dni. Brak koncepcji pierwszego dnia tygodnia.
Gert Arnold
0

Proponowane rozwiązanie nie jest kompletne - działa tylko dla CalendarWeekRule.FirstFullWeek. Inne typy reguł tygodniowych nie działają. Można to zobaczyć za pomocą tego przypadku testowego:

foreach (CalendarWeekRule rule in Enum.GetValues(typeof(CalendarWeekRule)))
{
    for (int year = 1900; year < 2000; year++)
    {
        DateTime date = FirstDateOfWeek(year, 1, rule);
        Assert(CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date, rule, DayOfWeek.Monday) == 1);
        Assert(CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date.AddDays(-1), rule, DayOfWeek.Monday) != 1);
    }
}
Colin Breame
źródło
0

Stworzyłem dopracowaną wersję proponowanego rozwiązania, która jest prostsza i parametryzuje firstDayOfWeek:

public static DateTime GetFirstDayOfWeek(int year, int week, DayOfWeek firstDayOfWeek)
{
    return GetWeek1Day1(year, firstDayOfWeek).AddDays(7 * (week - 1));
}

public static DateTime GetWeek1Day1(int year, DayOfWeek firstDayOfWeek)
{
    DateTime date = new DateTime(year, 1, 1);

    // Move towards firstDayOfWeek
    date = date.AddDays(firstDayOfWeek - date.DayOfWeek);

    // Either 1 or 52 or 53
    int weekOfYear = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFullWeek, firstDayOfWeek);

    // Move forwards 1 week if week is 52 or 53
    date = date.AddDays(7 * System.Math.Sign(weekOfYear - 1));

    return date;
}
Colin Breame
źródło
0

to moje rozwiązanie, gdy chcemy obliczyć datę podając rok, numer tygodnia i dzień tygodnia.

int Year = 2014;
int Week = 48;
int DayOfWeek = 4;

DateTime FecIni = new DateTime(Year, 1, 1);
FecIni = FecIni.AddDays(7 * (Week - 1));
if ((int)FecIni.DayOfWeek > DayOfWeek)
{
    while ((int)FecIni.DayOfWeek != DayOfWeek) FecIni = FecIni.AddDays(-1);
}
else
{
    while ((int)FecIni.DayOfWeek != DayOfWeek) FecIni = FecIni.AddDays(1);
}
José Ignacio Becerra Becerra
źródło
0

Uprościłem kod podany przez Mikaela Svenssona, który jest poprawny dla wielu krajów w Europie.

public static DateTime FirstDateOfWeekIso8601(int year, int week)
{
        var firstThursdayOfYear = new DateTime(year, 1, 1);
        while (firstThursdayOfYear.DayOfWeek != DayOfWeek.Thursday)
        {
            firstThursdayOfYear = firstThursdayOfYear.AddDays(1);
        }

        var startDateOfWeekOne = firstThursdayOfYear.AddDays(-(DayOfWeek.Thursday - DayOfWeek.Monday));

        return startDateOfWeekOne.AddDays(7 * (week - 1));        
}
Oskar Sjöberg
źródło
0

Napisałem i przetestowałem następujący kod i dla mnie działa doskonale. Daj mi znać, jeśli ktoś będzie miał z tym problem, również wysłałem pytanie, aby uzyskać najlepszą możliwą odpowiedź. Ktoś może uznać to za przydatne.

public static DateTime GetFirstDateOfWeekByWeekNumber(int year, int weekNumber)
        {
            var date = new DateTime(year, 01, 01);
            var firstDayOfYear = date.DayOfWeek;
            var result = date.AddDays(weekNumber * 7);

            if (firstDayOfYear == DayOfWeek.Monday)
                return result.Date;
            if (firstDayOfYear == DayOfWeek.Tuesday)
                return result.AddDays(-1).Date;
            if (firstDayOfYear == DayOfWeek.Wednesday)
                return result.AddDays(-2).Date;
            if (firstDayOfYear == DayOfWeek.Thursday)
                return result.AddDays(-3).Date;
            if (firstDayOfYear == DayOfWeek.Friday)
                return result.AddDays(-4).Date;
            if (firstDayOfYear == DayOfWeek.Saturday)
                return result.AddDays(-5).Date;
            return result.AddDays(-6).Date;
        }
Uczeń
źródło
To jest złe - przynajmniej dla wielu krajów europejskich. GetFirstDateOfWeekByWeekNumber(2020, 29)w tygodniu 29 2020 to powraca 07/20/2020. Ale pierwszy dzień 29 tygodnia był07/13/2020
Matthias Burger
0

Obecnie nie ma klasy C #, która poprawnie obsługuje numery tygodni ISO 8601. Mimo że możesz utworzyć instancję kultury, poszukać najbliższej rzeczy i poprawić to, myślę, że lepiej jest wykonać pełne obliczenia samodzielnie:

    /// <summary>
    /// Converts a date to a week number.
    /// ISO 8601 week 1 is the week that contains the first Thursday that year.
    /// </summary>
    public static int ToIso8601Weeknumber(this DateTime date)
    {
        var thursday = date.AddDays(3 - date.DayOfWeek.DayOffset());
        return (thursday.DayOfYear - 1) / 7 + 1;
    }

    /// <summary>
    /// Converts a week number to a date.
    /// Note: Week 1 of a year may start in the previous year.
    /// ISO 8601 week 1 is the week that contains the first Thursday that year, so
    /// if December 28 is a Monday, December 31 is a Thursday,
    /// and week 1 starts January 4.
    /// If December 28 is a later day in the week, week 1 starts earlier.
    /// If December 28 is a Sunday, it is in the same week as Thursday January 1.
    /// </summary>
    public static DateTime FromIso8601Weeknumber(int weekNumber, int? year = null, DayOfWeek day = DayOfWeek.Monday)
    {
        var dec28 = new DateTime((year ?? DateTime.Today.Year) - 1, 12, 28);
        var monday = dec28.AddDays(7 * weekNumber - dec28.DayOfWeek.DayOffset());
        return monday.AddDays(day.DayOffset());
    }

    /// <summary>
    /// Iso8601 weeks start on Monday. This returns 0 for Monday.
    /// </summary>
    private static int DayOffset(this DayOfWeek weekDay)
    {
        return ((int)weekDay + 6) % 7;
    }
realbart
źródło