.NET дает мне неправильный номер недели для 29 декабря 2008?

Согласно по официальному (григорианскому) календарю номер недели на 29/12/2008 равен 1, потому что после последнего дня 52 недели (т.е. 28/12) в году осталось три или меньше дней. Немного странно, но ладно, правила есть правила.

Итак, согласно этому календарю, у нас есть эти граничные значения на 2008/2009 год.

  • 28.12 это 52 неделя.
  • 29.12 - первая неделя.
  • 1/1 это неделя 1
  • 8/1 это вторая неделя

C# предлагает класс GregorianCalendar с функцией GetWeekOfYear(date, rule, firstDayOfWeek).

Параметр rule представляет собой перечисление с 3 возможными значениями: FirstDay, FirstFourWeekDay, FirstFullWeek. Из того, что я понял, я должен использовать правило FirstFourWeekDay, но на всякий случай я попробовал их все.

Последний параметр сообщает, какой день недели следует считать первым днем ​​недели, в соответствии с этим календарем это понедельник, поэтому понедельник.

Поэтому я запустил быстрое и грязное консольное приложение, чтобы проверить это:

using System;
using System.Globalization;

namespace CalendarTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var cal = new GregorianCalendar();
            var firstWeekDay = DayOfWeek.Monday;
            var twentyEighth = new DateTime(2008, 12, 28);
            var twentyNinth = new DateTime(2008, 12, 29);
            var firstJan = new DateTime(2009, 1, 1);
            var eightJan = new DateTime(2009, 1, 8);
            PrintWeekDays(cal, twentyEighth, firstWeekDay);
            PrintWeekDays(cal, twentyNinth, firstWeekDay);
            PrintWeekDays(cal, firstJan, firstWeekDay);
            PrintWeekDays(cal, eightJan, firstWeekDay);
            Console.ReadKey();
        }

        private static void PrintWeekDays(Calendar cal, DateTime dt, DayOfWeek firstWeekDay)
        {
            Console.WriteLine("Testing for " + dt.ToShortDateString());
            Console.WriteLine("--------------------------------------------");
            Console.Write(CalendarWeekRule.FirstDay.ToString() + "\t\t");
            Console.WriteLine(cal.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, firstWeekDay));
            Console.Write(CalendarWeekRule.FirstFourDayWeek.ToString() + "\t");
            Console.WriteLine(cal.GetWeekOfYear(dt, CalendarWeekRule.FirstFourDayWeek, firstWeekDay));
            Console.Write(CalendarWeekRule.FirstFullWeek.ToString() + "\t\t");
            Console.WriteLine(cal.GetWeekOfYear(dt, CalendarWeekRule.FirstFullWeek, firstWeekDay));
            Console.WriteLine("--------------------------------------------");
        }
    }
}

... и это то, что я получаю

Testing for 28.12.2008
--------------------------------------------
FirstDay                52
FirstFourDayWeek        52
FirstFullWeek           51
--------------------------------------------
Testing for 29.12.2008
--------------------------------------------
FirstDay                53
FirstFourDayWeek        53
FirstFullWeek           52
--------------------------------------------
Testing for 01.01.2009
--------------------------------------------
FirstDay                1
FirstFourDayWeek        1
FirstFullWeek           52
--------------------------------------------
Testing for 08.01.2009
--------------------------------------------
FirstDay                2
FirstFourDayWeek        2
FirstFullWeek           1
--------------------------------------------

Итак, как мы видим, ни одна из приведенных выше комбинаций не соответствует официальному календарю (если вы спешите, просто обратите внимание, что 29/12 никогда не получает неделю №1).

Что я здесь не так? Может быть, есть что-то вопиющее, что я упускаю? (Здесь, в Бельгии, пятница и поздний рабочий день, потерпите меня ;))

Редактировать: Может быть, мне следует объяснить: мне нужна функция, которая работает для любого года, возвращая те же результаты, что и григорианский календарь, который я связал. Так что никаких специальных обходных путей для 2008 года.


person rodbv    schedule 09.01.2009    source источник


Ответы (7)


Этот статья более подробно рассматривает проблему и возможные обходные пути. Суть дела в том, что реализация календаря .NET, похоже, не соответствует стандарту ISO.

person Rad    schedule 09.01.2009
comment
хм... MS не подчиняется стандартам? Я в шоке. - person rmeador; 09.01.2009
comment
Выглядит многообещающе :) К сожалению, я не могу проверить это сейчас, но я сделаю это завтра. - person rodbv; 09.01.2009
comment
Я протестировал код rodbv с решением в статье, и он возвращает правильные результаты. - person Jesper Palm; 09.01.2009
comment
Это также застало меня врасплох в проекте, который я выполнял для клиента, который работает с номерами недель. Статья немного помогла. Вы можете прочитать эту статью для получения дополнительной информации en.wikipedia.org/wiki/ISO_week_date - person Rad; 10.01.2009
comment
Меня сбивает с толку тот факт, что Microsoft по-прежнему отказывается внедрять стандарт ISO, используемый в большинстве стран Европы. Все правительства, школы и больницы (которые часто используют номера недель) теперь обязаны закодировать обходной путь для этого простого расчета. - person Sire; 30.08.2011

@Конрад прав. Реализация .NET DateTime и GregorianCalendar не реализует/не соответствует полной спецификации ISO 8601. При этом их спецификация чрезвычайно подробна и нетривиальна для полной реализации, по крайней мере, для синтаксического анализа.

Дополнительная информация доступна на следующих сайтах:

Проще говоря:

Неделя определяется своим номером в данном году и начинается с понедельника. Первая неделя года — это та, которая включает первый четверг или, что то же самое, та, которая включает 4 января.

Вот часть кода, который я использую для правильной обработки дат ISO 8601:

    #region FirstWeekOfYear
    /// <summary>
    /// Gets the first week of the year.
    /// </summary>
    /// <param name="year">The year to retrieve the first week of.</param>
    /// <returns>A <see cref="DateTime"/>representing the start of the first
    /// week of the year.</returns>
    /// <remarks>
    /// Week 01 of a year is per definition the first week that has the Thursday 
    /// in this year, which is equivalent to the week that contains the fourth
    /// day of January. In other words, the first week of a new year is the week
    /// that has the majority of its days in the new year. Week 01 might also 
    /// contain days from the previous year and the week before week 01 of a year
    /// is the last week (52 or 53) of the previous year even if it contains days 
    /// from the new year.
    /// A week starts with Monday (day 1) and ends with Sunday (day 7). 
    /// </remarks>
    private static DateTime FirstWeekOfYear(int year)
    {
        int dayNumber;

        // Get the date that represents the fourth day of January for the given year.
        DateTime date = new DateTime(year, 1, 4, 0, 0, 0, DateTimeKind.Utc);

        // A week starts with Monday (day 1) and ends with Sunday (day 7).
        // Since DayOfWeek.Sunday = 0, translate it to 7. All of the other values
        // are correct since DayOfWeek.Monday = 1.
        if (date.DayOfWeek == DayOfWeek.Sunday)
        {
            dayNumber = 7;
        }
        else
        {
            dayNumber = (int)date.DayOfWeek;
        }

        // Since the week starts with Monday, figure out what day that 
        // Monday falls on.
        return date.AddDays(1 - dayNumber);
    }

    #endregion

    #region GetIsoDate
    /// <summary>
    /// Gets the ISO date for the specified <see cref="DateTime"/>.
    /// </summary>
    /// <param name="date">The <see cref="DateTime"/> for which the ISO date
    /// should be calculated.</param>
    /// <returns>An <see cref="Int32"/> representing the ISO date.</returns>
    private static int GetIsoDate(DateTime date)
    {
        DateTime firstWeek;
        int year = date.Year;

        // If we are near the end of the year, then we need to calculate
        // what next year's first week should be.
        if (date >= new DateTime(year, 12, 29))
        {
            if (date == DateTime.MaxValue)
            {
                firstWeek = FirstWeekOfYear(year);
            }
            else
            {
                firstWeek = FirstWeekOfYear(year + 1);
            }

            // If the current date is less than next years first week, then
            // we are still in the last month of the current year; otherwise
            // change to next year.
            if (date < firstWeek)
            {
                firstWeek = FirstWeekOfYear(year);
            }
            else
            {
                year++;
            }
        }
        else
        {
            // We aren't near the end of the year, so make sure
            // we're not near the beginning.
            firstWeek = FirstWeekOfYear(year);

            // If the current date is less than the current years
            // first week, then we are in the last month of the
            // previous year.
            if (date < firstWeek)
            {
                if (date == DateTime.MinValue)
                {
                    firstWeek = FirstWeekOfYear(year);
                }
                else
                {
                    firstWeek = FirstWeekOfYear(--year);
                }
            }
        }

        // return the ISO date as a numeric value, so it makes it
        // easier to get the year and the week.
        return (year * 100) + ((date - firstWeek).Days / 7 + 1);
    }

    #endregion

    #region Week
    /// <summary>
    /// Gets the week component of the date represented by this instance.
    /// </summary>
    /// <value>The week, between 1 and 53.</value>
    public int Week
    {
        get
        {
            return this.isoDate % 100;
        }
    }
    #endregion

    #region Year
    /// <summary>
    /// Gets the year component of the date represented by this instance.
    /// </summary>
    /// <value>The year, between 1 and 9999.</value>
    public int Year
    {
        get
        {
            return this.isoDate / 100;
        }
    }
    #endregion
person Scott Dorman    schedule 12.01.2009

Номера недель различаются от страны к стране и должны зависеть от ваших региональных/региональных настроек, если я не ошибаюсь.

Редактировать: Википедия поддерживает мое смутное воспоминание о том, что эти числа различаются в зависимости от страны: http://en.wikipedia.org/wiki/Week_number#Week_number

Я ожидаю, что респектабельная структура будет подчиняться СТРАНЕ, выбранной в вашей локальной среде выполнения.

person krosenvold    schedule 09.01.2009
comment
Хм, может быть. Я посмотрю, правильно ли я все сделаю, если исправлю это на en-US Cultureinfo. - person rodbv; 09.01.2009
comment
Провел некоторые тесты и немного прочитал MSDN: культура используется для информирования 2-го и 3-го параметров GetWeekOfYear, правила и первого дня недели. Но поскольку я их предоставляю, культура становится неактуальной. - person rodbv; 09.01.2009

По моему опыту, продемонстрированное поведение является типичным поведением, относящимся к частичной последней неделе как неделе 53. Это может быть связано с тем, что все значительные воздействия, которые у меня были с номерами недель, были связаны с учетом конца календарного года для целей отчетности. , и IRS (или налоговая служба по вашему выбору) считает, что календарный год заканчивается 31 декабря, а не последней полной неделей года.

person Greg D    schedule 09.01.2009

Я знаю, что это старый пост, но в любом случае noda time, похоже, дает правильный результат.

person Peter    schedule 03.12.2012

В качестве обходного пути вы можете сказать, что номер недели — WeekNumber mod 52. Я думаю, что это сработает для случаев, которые вы описываете.

person Kibbee    schedule 09.01.2009
comment
кроме того, есть годы с 53 неделями, например. 2009 г. - person rodbv; 09.01.2009

В качестве обходного пути, почему бы не использовать FirstFourDayWeek, но добавить:

  if ( weekNumber > 52 )
    weekNumber = 1;
person Mike Scott    schedule 09.01.2009
comment
Мне нужен общий алгоритм... бывают случаи, что в году официально 53 недели (2009 год такой, IIRC) - person rodbv; 09.01.2009