Обчисліть дату від номера тижня


135

Будь-хто знає простий спосіб отримати дату першого дня тижня (понеділок тут, у Європі). Я знаю номер року та тижня? Я збираюся це зробити в C #.


Сильно пов’язаний, можливо, дублікат: stackoverflow.com/questions/659183/…
Джон Раш

Відповіді:


251

У мене виникли проблеми з рішенням від @HenkHolterman, навіть із виправленням @RobinAndersson.

Читання стандарту ISO 8601 вирішує проблему непогано. Використовуйте перший четвер як ціль, а не понеділок. Код нижче буде також працювати на 53 тижні 2009 року.

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);
}       

3
Я пробував майже кожне рішення, що подається тут, це єдине, яке зараз правильно працює для мене. Наразі це 7 лютого 2012. Тиждень №6. Цей код правильно дає мені 6 лютого як дату початку тижня. Інші рішення мені дали 13 лютого, що насправді є початковою датою тижня №7.
HaukurHaf

1
Працює як шарм .. перевірено кілька даних: pastebin.com/mfx8s1vq Всі роботи бездоганно! Дякую Мікаелю!
Мітчел

1
@ RobinWassén-Andersson Добре, що ви переглянули це питання тоді: D Ще 6 голосів, і я зв’яжу правильну відповідь "не".
Мікаел Свенсон

2
Пропозиція: Зробіть "ISO8601" pert назви методу, оскільки просто немає універсальної "правильної" відповіді.
Хенк Холтерман

1
@Muflix Я не знаю достатньо про SQL та дати / календарі, щоб знати, але робота з CLR буде працювати.
Мікаель Свенсон

36

Мені подобається рішення, яке надає Хенк Холтерман. Але щоб бути трохи більш незалежною від культури, ви повинні отримати перший день тижня для поточної культури (це не завжди понеділок):

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);
}

Я додав це до Mannex, як метод розширення до календаря та DateTimeFormatInfo. Я також перехресно посилався на цю відповідь як кредит.
Атіф Азіз

1
Він не працює належним чином на моїй машині. Він показує дату з 0010 замість 2010 року. Я не знаю, чи це проблема в .net Framework або в цій функції. Приємно спробувати, все одно ...
Едуардо Ксав'є

6
firstMondayце погана назва змінної для чогось, що може бути не понеділком. =)
mflodin

11

ОНОВЛЕННЯ : .NET Core 3.0 і .NET Standard 2.1 поставляються з цим типом.

Гарні новини! Запит тягнути додавши System.Globalization.ISOWeekдо .NET Ядра просто об'єднані і в даний час планується до випуску 3.0. Сподіваємось, він буде розповсюджуватися на інші платформи .NET у не надто віддаленому майбутньому.

Ви повинні мати можливість використовувати ISOWeek.ToDateTime(int year, int week, DayOfWeek dayOfWeek)метод для обчислення цього.

Вихідний код ви можете знайти тут .


9

Найпростіший спосіб - це, мабуть, знайти перший понеділок року, а потім додати відповідну кількість тижнів. Ось приклад коду. Це передбачає число тижня, починаючи з 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);
    }
}

4
Так, але перший понеділок року може належати 52 | 53 тижня попереднього року.
Хенк Холтерман

1
Це залежить від того, як ви хочете визначити речі. На жаль, у нас не так багато інформації, щоб продовжувати тут ... Я сподіваюся, що це корисно.
Джон Скіт

3

Особисто я скористався інформацією про культуру, щоб отримати день тижня і перейти до першого дня тижня. Я не впевнений, чи правильно пояснюю це, ось приклад:

    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;
    }


2

Відповідно до ISO 8601: 1988, який використовується у Швеції перший тиждень року, це перший тиждень, який має принаймні чотири дні протягом нового року.

Отже, якщо ваш тиждень розпочинається в понеділок, перший четвер, будь-який рік, буде протягом першого тижня. Ви можете DateAdd або DateDiff від цього.


2

Припустимо, що число тижня починається з 1

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

Це має дати вам перший день у будь-який тиждень. Я не робив багато тестування на ньому, але схоже, що це працює. Це менше рішення, ніж більшість інших, які я знайшов в Інтернеті, тому хотів поділитися.


погана відповідь. Використання DateTime.Parse не потрібно, оскільки DateTime має конструктор, який займає рік, місяць та день. new DateTime(1,1,YearNumber)
Jamiec

Оновлено код, щоб створити новий DateTime замість використання синтаксичного аналізу. Було пізно. ;)
QuinnG

2

Бібліотека вільного періоду часу для .NET включає тиждень відповідного класу ISO 8601 :

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

2

Ця робота працювала для мене, але також є перевагою очікувати культурного інформування як параметра для тестування формули з різними культурами. Якщо порожній, він отримує поточну інформацію про культуру ... дійсні значення такі: "it", "en-us", "fr", ... ando так далі. Хитрість полягає в тому, щоб відняти номер тижня першого дня року, який може бути 1, щоб вказати, що перший день припадає на перший тиждень. Сподіваюся, це допомагає.

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

2

Ось метод, сумісний із номерами тижнів, які використовує Google Analytics, а також та сама схема нумерації, яку ми використовували внутрішньо в Intel, і яка, я впевнений, також використовується у багатьох інших контекстах.

// 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);
}

2

Я спробував деякі коди вище, а деякі мають невеликі помилки, коли ви намагаєтеся в різні роки з різними початковими днями тижня, ви їх побачите, я взяв код Джона Скіта, виправлю його і він працює, дуже простий код.

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

1

Легко змінили код Мікаеля Свенсона. Я знайшов тиждень першого понеділка і відповідно змінив номер тижня.

 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;
    }

0

Тиждень 1 визначається як тиждень, що починається в понеділок і містить перший четвер року.


1
Це одне визначення, є й інші.
Хенк Холтерман

1
Стандарт ISO визначає тиждень 1 як тиждень з першим четвергом року.
RickardN


0

Я трохи покращив рішення Томаса за допомогою перебору:

   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;
      }
      */

Інакше даті передував тиждень.

Дякую Тома, чудова допомога.


0

Я використовував одне з рішень, але це дало неправильні результати, просто тому, що він вважає неділю першим днем ​​тижня.

Я змінив:

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

до:

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

і зараз це працює як шарм.


1
Але чи відповідає це питання ОП? Це 1/1 плюс фіксовану кількість днів. Немає першого дня тижня концепції.
Герт Арнольд

0

Запропоноване рішення не є повним - воно працює лише для CalendarWeekRule.FirstFullWeek. Інші тижні правила тижня не працюють. Це можна побачити за допомогою цього тестового випадку:

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);
    }
}

0

Я створив доопрацьовану версію запропонованого рішення, яка є більш простою і параметризує першийDayOfWeek:

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;
}

0

це моє рішення, коли ми хочемо обчислити дату року, числа тижня та дня тижня.

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);
}

0

Я спростив код Мікаел Свенссон, який є правильним для багатьох країн Європи.

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));        
}

0

Я написав і протестував наступний код і працює на мене чудово. Будь ласка, дайте мені знати, якщо хтось із цим стикається з проблемою, я також розмістив питання, щоб отримати найкращу можливу відповідь. Хтось може вважати його корисним.

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;
        }

Це неправильно - принаймні для багатьох європейських країн. GetFirstDateOfWeekByWeekNumber(2020, 29)для 29 тижня 2020 року це повертається 07/20/2020. Але перший день 29-го тижня був07/13/2020
Маттіас Бургер

0

В даний час не існує класу C #, який би правильно обробляв номери ISO 8601. Незважаючи на те, що ви можете створити культуру, шукати найближче і виправити це, я вважаю, що краще зробити повний розрахунок самостійно:

    /// <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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.