Ошибка в расчете WeekNumber.NET? - программирование

Ошибка в расчете WeekNumber.NET?

У меня довольно странная проблема. Я живу в дании, и здесь первая неделя (неделя 1) 2013 года начинается 31 декабря 2012 года и длится 7 дней - как обычно делают недели:)

Согласно .NET, однако 30 декабря - неделя 52, 31-я неделя 53, а 1 января - 1-я неделя.

Неделя 53 длится всего один день, а неделя 1 - 6 дней. Очевидно, что это должно быть неправильно (неделя, состоящая менее чем за 7 дней) и, конечно же, неверна в датском контексте. Где 31 декабря - неделя 1, НЕ неделя 53.

Следующий код иллюстрирует проблему (CurrentCulture - "da-DK" )

    static void Main(string[] args)
    {
        //Here I get Monday
        DayOfWeek firstDayOfWeek = DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek;             
        //Here I get FirstFourDayWeek
        CalendarWeekRule weekRule = DateTimeFormatInfo.CurrentInfo.CalendarWeekRule; 

        DateTime date = new DateTime(2012,12,30);

        for (int i = 0; i <= 10; i++)
        {
            DateTime currentDate = date.AddDays(i);
            Console.WriteLine("Date: {0} WeekNumber: {1}",
                currentDate.ToShortDateString(),
                CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(currentDate, weekRule, firstDayOfWeek));
        }
        Console.ReadLine();
    }

Я сделал что-то неправильно или это ошибка в .NET? Если последний - есть ли у вас предложения по правильному подсчету недельных номеров?

4b9b3361

Ответ 1

Проблема заключается в том, что метод GetWeekOfYear не соответствует ISO 8601, чего вы ожидаете, но это не так.

Обратите внимание, что, пока вы используете FirstFourDayWeek, документация говорит:

Первая неделя, основанная на значении FirstFourDayWeek, может иметь от четырех до семи дней.

что является нарушением правила ISO 8601, что все недели должны иметь семь дней.

Также:

enter image description here


Вы можете использовать следующий метод для получения правильного номера недели в соответствии с ISO 8601:

int weekNumber(DateTime fromDate)
{
    // Get jan 1st of the year
    DateTime startOfYear = fromDate.AddDays(- fromDate.Day + 1).AddMonths(- fromDate.Month +1);
    // Get dec 31st of the year
    DateTime endOfYear = startOfYear.AddYears(1).AddDays(-1);
    // ISO 8601 weeks start with Monday 
    // The first week of a year includes the first Thursday 
    // DayOfWeek returns 0 for sunday up to 6 for saterday
    int[] iso8601Correction = {6,7,8,9,10,4,5};
    int nds = fromDate.Subtract(startOfYear).Days  + iso8601Correction[(int)startOfYear.DayOfWeek];
    int wk = nds / 7;
    switch(wk)
    {
        case 0 : 
            // Return weeknumber of dec 31st of the previous year
            return weekNumber(startOfYear.AddDays(-1));
        case 53 : 
            // If dec 31st falls before thursday it is week 01 of next year
            if (endOfYear.DayOfWeek < DayOfWeek.Thursday)
                return 1;
            else
                return wk;
        default : return wk;
    }
}

Источник (там также есть много других функций...)


Итак, изменив ваш цикл на

for (int i = 0; i <= 10; i++)
{
    DateTime currentDate = date.AddDays(i);
    Console.WriteLine("Date: {0} WeekNumber: {1}: CorrectWeekNumber: {2}",
        currentDate.ToShortDateString(),
        CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(currentDate, weekRule, firstDayOfWeek),
        weekNumber(currentDate));
}

приведет к:

Дата: 30.12.2012 WeekNumber: 52: CorrectWeekNumber: 52
Дата: 31.12.2012 WeekNumber: 53: CorrectWeekNumber: 1
Дата: 01.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Дата: 02.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Дата: 03.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Дата: 04.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Дата: 05.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Дата: 06.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Дата: 07.01.2013 WeekNumber: 2: CorrectWeekNumber: 2
Дата: 08.01.2013 WeekNumber: 2: CorrectWeekNumber: 2
Дата: 09.01.2013 WeekNumber: 2: CorrectWeekNumber: 2

Ответ 2

Спасибо за все ответы. Я также искал еще несколько и, наконец, создал два метода С# для достижения того, что я хотел:

Сначала краткий, найденный в одном из комментариев: http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx

Какой Джон Сенчина также указал на:

     public static int WeekNumber(this DateTime date)
    {
        Calendar cal = CultureInfo.InvariantCulture.Calendar;
        DayOfWeek day = cal.GetDayOfWeek(date);
        date = date.AddDays(4 - ((int)day == 0 ? 7 : (int)day));
        return cal.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
    }

И еще один: http://www.tondering.dk/claus/cal/week.php#calcweekno

    public static int WeekNumber2(this DateTime date)
    {
        int a;
        int b;
        int c;
        int s;
        int e;
        int f;

        if (date.Month <= 2)
        {
            a = date.Year - 1;
            b = a / 4 - a / 100 + a / 400;
            c = (a - 1) / 4 - (a - 1) / 100 + (a - 1) / 400;
            s = b - c;
            e = 0;
            f = date.Day - 1 + 31 * (date.Month - 1);
        }
        else
        {
            a = date.Year;
            b = a / 4 - a / 100 + a / 400;
            c = (a - 1) / 4 - (a - 1) / 100 + (a - 1) / 400;
            s = b - c;
            e = s + 1;
            f = date.Day + ((153 * (date.Month - 3) + 2) / 5) + 58 + s;
        }

        int g = (a + b) % 7;
        int d = (f + g - e) % 7;
        int n = f + 3 - d;

        if (n < 0)
            return 53 - ((g - s) / 5);
        if (n > (364 + s))
            return 1;
        return n / 7 + 1;
    }

Оба дали мне то, что я хотел.

Я также написал небольшой unittest, который доказывает, что они возвращают одни и те же самые числа в первые 3000 лет календаря.

    [TestMethod]
    public void WeekNumbers_CorrectFor_3000Years()
    {
        var weekNumbersMethod1 = WeekNumbers3000Years(DateManipulation.WeekNumber).ToList();
        var weekNumbersMethod2 = WeekNumbers3000Years(DateManipulation.WeekNumber2).ToList();
        CollectionAssert.AreEqual(weekNumbersMethod1, weekNumbersMethod2);
    }

    private IEnumerable<int> WeekNumbers3000Years(Func<DateTime, int> weekNumberCalculator)
    {
        var startDate = new DateTime(1,1,1);
        var endDate = new DateTime(3000, 12, 31);
        for(DateTime date = startDate; date < endDate; date = date.AddDays(1))
            yield return weekNumberCalculator(date);
    }

Ответ 3

1.1.2013 - вторник, и начинается неделя №1. 31.12.2012 - понедельник, относится к 2012 году и, таким образом, является неделей 53. Естественно, у вас не может быть недели 1 в декабре, поскольку это будет означать, что будет две недели 1 в год, и, таким образом, вызовет всевозможные неприятные проблемы с кодом.

Нет правила, что неделя должна быть ровно 7 дней.

Ответ 4

Я ничего не знаю о датском календаре, но правила для .NET ясны, и ваш код точно соответствует этим. Документация MSDN для перечисления CalendarWeekRule здесь, для справки. Вкратце:

.NET всегда будет относиться к 31 декабря как к "последней неделе года".

Вы говорите, что для правила недели вы получаете значение FirstFourDayWeek. С 1 января 2013 года во вторник, оставшаяся часть этой недели составляет шесть дней (следующая неделя начинается в следующий понедельник). Поэтому 1-6 января 2013 года считается "Неделя 1". Если у вас было FirstFullWeek для этого правила, неделя 1 начнется в понедельник 7-го вместо.

Что касается "Очевидно, что это должно быть неправильно:" ясно, что это правильно, согласно его собственной спецификации. Никогда не требуется, чтобы какой-либо язык программирования или API соответствовали определенному ожиданию; каждый проект определяет свои собственные требования..NET - это правила, которые мы можем считать неожиданными, но они документированы и соответствуют его документации.

Является ли это полезным или нет другим вопросом...

Ответ 5

Calendar.GetWeekOfYear не поддерживает спецификацию ISO8601, см. также Формат ISO 8601 Week of Year в Microsoft.Net.

Вы можете использовать класс Неделя Библиотека периода времени для .NET:

// ----------------------------------------------------------------------
public void CalendarWeekSample()
{
  DateTime testDate = new DateTime( 2007, 12, 31 );

  // .NET calendar week
  TimeCalendar calendar = new TimeCalendar();
  Console.WriteLine( "Calendar Week of {0}: {1}", testDate.ToShortDateString(),
                     new Week( testDate, calendar ).WeekOfYear );
  // > Calendar Week of 31.12.2007: 53

  // ISO 8601 calendar week
  TimeCalendar calendarIso8601 = new TimeCalendar(
    new TimeCalendarConfig { YearWeekType = YearWeekType.Iso8601 } );
  Console.WriteLine( "ISO 8601 Week of {0}: {1}", testDate.ToShortDateString(),
                     new Week( testDate, calendarIso8601 ).WeekOfYear );
  // > ISO 8601 Week of 31.12.2007: 1
} // CalendarWeekSample

Ответ 6

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

    public static int GetWeekNumber(DateTime date)
    {
        var firstDayOfYear = new DateTime(date.Year, 1, 1);
        var lastDayOfYear = new DateTime(date.Year, 12, 31);
        var lastDayOfPreviousYear = new DateTime(date.Year - 1, 12, 31);

        var weekDayOfFirstDayOfYear = (int)firstDayOfYear.DayOfWeek + 1;
        var weekDayOfLastDayOfYear = (int)lastDayOfYear.DayOfWeek + 1;
        var days = (date - firstDayOfYear).Days;

        if (days <= 7 - weekDayOfFirstDayOfYear)  // My day fall in 1'st week of the year
        {
            if (weekDayOfFirstDayOfYear > 5)
                return GetWeekNumber(lastDayOfPreviousYear);
            return 1;
        }
        else // My day fall not on 1'st week of the year
        {
            // Number of weeks that pass from 1'st Sunday of 2'nd week of the year
            var weekNo = ((days - (8 - weekDayOfFirstDayOfYear)) / 7) + 1;

            if (weekDayOfFirstDayOfYear < 6)  // if Year start at Sun...Thursday the first week is added.
                weekNo++;

            // Check if Last week of the year belong to next year
            if (weekDayOfLastDayOfYear < 5) // if the year end in Sunday to Wednesday then it might belong to the 1'st week of the next year
            {
                if ((lastDayOfYear - date).Days < weekDayOfLastDayOfYear)
                {
                    return 1;
                }
            }

            return weekNo;
        }
    }