简体   繁体   English

.NET中WeekNumber计算中的错误?

[英]Bug in WeekNumber calculation .NET?

I have a rather weird problem. 我有一个很奇怪的问题。 I live in denmark and here the first week (Week 1) of 2013 starts the 31th of december 2012 and lasts for 7 days - as weeks normally do :) 我住在丹麦,在这里,2013年的第一周(第1周)从2012年12月31日开始,持续7天-就像通常的几周一样:)

According to .NET however the 30th of december is Week 52, the 31th is Week 53 and the 1st of January is Week 1. 根据.NET,但是12月30日为第52周,31日为第53周,1月1日为第1周。

Week 53 lasts for only one day, and Week 1 for 6 days. 第53周仅持续1天,而第1周仅持续6天。 Clearly this must be wrong (a week consisting of less than 7 days) and certainly is wrong in danish context. 显然这一定是错误的(一周少于7天),并且在丹麦语环境中当然是错误的。 Where the 31th of december is Week 1, NOT Week 53. 12月31日是第1周,而不是第53周。

The following code illustrates the problem (CurrentCulture is "da-DK") 以下代码说明了该问题(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();
    }

Have I done something wrong or is this a bug in .NET ? 我做错了什么还是这是.NET中的错误? If the latter - do you have suggestions for calculating weeknumbers correctly ? 如果是后者-您对正确计算周数有建议吗?

The problem is that the GetWeekOfYear method does not respect ISO 8601, which is what you expect, but it doesn't. 问题在于GetWeekOfYear方法不符合您期望的ISO 8601,但不符合。

Note that while you are using FirstFourDayWeek , the documentation says: 请注意,当您使用FirstFourDayWeek文档显示

The first week based on the FirstFourDayWeek value can have four to seven days. 基于FirstFourDayWeek值的第一周可以有四到七天。

which is a violation of the ISO 8601 rule that all weeks have to have seven days. 这违反了ISO 8601规则,即所有星期必须有7天。

Also: 也:

在此处输入图片说明


You can use the following method to obtain the correct week number according to ISO 8601: 您可以使用以下方法根据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;
    }
}

Source (there are also plenty other functions out there...) 来源 (那里还有很多其他功能...)


So, changing your loop to 因此,将循环更改为

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

will result in: 将导致:

Date: 30.12.2012 WeekNumber: 52: CorrectWeekNumber: 52 日期:30.12.2012星期数:52:正确星期数:52
Date: 31.12.2012 WeekNumber: 53: CorrectWeekNumber: 1 日期:31.12.2012星期数:53:正确星期数:1
Date: 01.01.2013 WeekNumber: 1: CorrectWeekNumber: 1 日期:01.01.2013星期数:1:正确的星期数:1
Date: 02.01.2013 WeekNumber: 1: CorrectWeekNumber: 1 日期:02.01.2013星期数:1:正确的星期数:1
Date: 03.01.2013 WeekNumber: 1: CorrectWeekNumber: 1 日期:03.01.2013星期数:1:正确的星期数:1
Date: 04.01.2013 WeekNumber: 1: CorrectWeekNumber: 1 日期:04.01.2013星期数:1:正确的星期数:1
Date: 05.01.2013 WeekNumber: 1: CorrectWeekNumber: 1 日期:05.01.2013星期数:1:正确的星期数:1
Date: 06.01.2013 WeekNumber: 1: CorrectWeekNumber: 1 日期:06.01.2013星期数:1:正确的星期数:1
Date: 07.01.2013 WeekNumber: 2: CorrectWeekNumber: 2 日期:07.01.2013星期数:2:正确的星期数:2
Date: 08.01.2013 WeekNumber: 2: CorrectWeekNumber: 2 日期:08.01.2013星期数:2:正确的星期数:2
Date: 09.01.2013 WeekNumber: 2: CorrectWeekNumber: 2 日期:09.01.2013星期数:2:正确的星期数:2

Thanks for all the answers. 感谢所有的答案。 I also searched some more and finally created two C# methods to achieve what I wanted: 我还进行了更多搜索,最终创建了两个C#方法来实现我想要的功能:

First a concise one found in one of the comments at: http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx 首先,在以下网址的评论之一中找到一个简洁的网址http : //blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft- net.aspx

Which Jon Senchyna also pointed to: 乔恩·森奇纳(Jon Senchyna)也指出:

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

And also one at: http://www.tondering.dk/claus/cal/week.php#calcweekno 还有一个网址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;
    }

Both gave me what I wanted. 两者都给了我我想要的东西。

I also wrote a small unittest that proves that they return the same weeknumbers for the first 3000 years of the calendar. 我还写了一个小单元测试,证明他们在日历的前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);
    }

1.1.2013 is tuesday, and starts week #1. 2013年1月1日是星期二,从第1周开始。 31.12.2012 is monday, belongs to year 2012 and thus is week #53. 2012年12月31日是星期一,属于2012年,因此是第53周。 Naturally, you can't have week 1 on December as that would mean there would two week 1's in a year, and thus would cause all sorts of nasty problems with code. 自然地,您不能在12月拥有第1周,因为这意味着一年中将有两个第1周,因此将导致各种代码问题。

There is no rule that a week must be exactly 7 days long. 没有规则,一周必须恰好是7天。

I don't know anything about the Danish calendar, but the rules for .NET are clear, and your code is corresponding to those exactly. 我对丹麦历法一无所知,但.NET的规则很明确,您的代码与之完全对应。 MSDN documentation for the CalendarWeekRule enumeration is here , for reference. CalendarWeekRule枚举的MSDN文档在此处 ,以供参考。 In summary: 综上所述:

.NET will always treat 31 December as being in "the last week of the year." .NET始终将12月31日视为“一年的最后一周”。

You say you're getting a value of FirstFourDayWeek for the week rule. 您说周规则获得了FirstFourDayWeek的值。 Since 1 Jan 2013 is a Tuesday, the remainder of that week has six days (the next week starts on the following Monday). 由于2013年1月1日为星期二,因此该周的剩余时间为六天(下周从下周一开始)。 Therefore, 1-6 Jan 2013 is considered "Week 1." 因此,2013年1月1-6日被视为“第1周”。 If you had FirstFullWeek for that rule, week 1 would start on Monday 7th instead. 如果您有FirstFullWeek于该规则的FirstFullWeek ,则第1周将从7月星期一开始。

As for "Clearly this must be wrong:" clearly it's right, according to its own spec. 至于“显然这一定是错误的:”显然是正确的,根据它自己的规范。 There is never a requirement for any programming language or API to conform to a particular expectation; 从未要求任何编程语言或API都必须符合特定的期望。 each project defines its own requirements. 每个项目定义自己的需求。 .NET is following rules that we may deem unexpected, but they are documented, and it's consistent to its documentation. .NET遵循了我们认为可能出乎意料的规则,但已对其进行了记录,并且与它的文档一致。

Whether that's useful or not is another question... 这是否有用是另一个问题...

The Calendar.GetWeekOfYear doesn't' support the ISO8601 specification, see also ISO 8601 Week of Year format in Microsoft .Net . Calendar.GetWeekOfYear不支持ISO8601规范,另请参见Microsoft .Net中的ISO 8601年度周格式

You can use the Week class of the Time Period Library for .NET : 您可以使用.NET时间周期库Week类:

// ----------------------------------------------------------------------
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

You can use the following code in order to calculate the week number for a given date: 您可以使用以下代码来计算给定日期的星期数:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM