【问题标题】:Bug in WeekNumber calculation .NET?WeekNumber 计算 .NET 中的错误?
【发布时间】:2012-08-25 04:07:29
【问题描述】:

我有一个很奇怪的问题。我住在丹麦,2013 年的第一周(第 1 周)从 2012 年 12 月 31 日开始,持续 7 天 - 就像通常的几周一样 :)

然而,根据 .NET,12 月 30 日是第 52 周,31 日是第 53 周,1 月 1 日是第 1 周。

第 53 周仅持续 1 天,第 1 周持续 6 天。显然,这一定是错误的(一周少于 7 天),在丹麦语境中肯定是错误的。 12 月 31 日是第 1 周,而不是第 53 周。

以下代码说明了问题(CurrentCulture is "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 中的错误?如果是后者 - 你有正确计算周数的建议吗?

【问题讨论】:

标签: c# .net date-arithmetic


【解决方案1】:

问题在于 GetWeekOfYear 方法不遵守 ISO 8601,这是您所期望的,但事实并非如此。

请注意,当您使用 FirstFourDayWeek 时,documentation 表示:

基于 FirstFourDayWeek 值的第一周可以有 4 到 7 天。

这违反了 ISO 8601 规则,即所有周必须有 天。

Also:


您可以使用以下方法根据 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(还有很多其他功能...)


因此,将循环更改为

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

将导致:

日期:2012 年 12 月 30 日周数:52:正确周数:52
日期:2012 年 12 月 31 日周数:53:正确周数:1
日期:01.01.2013 周数:1:正确周数:1
日期:02.01.2013 周数:1:正确周数:1
日期:03.01.2013 周数:1:正确周数:1
日期:04.01.2013 周数:1:正确周数:1
日期:05.01.2013 周数:1:正确周数:1
日期:06.01.2013 周数:1:正确周数:1
日期:07.01.2013 周数:2:正确周数:2
日期:08.01.2013 周数:2:正确周数:2
日期:09.01.2013 周数:2:正确周数:2

【讨论】:

【解决方案2】:

感谢所有答案。我也搜索了一些,最后创建了两个 C# 方法来实现我想要的:

首先在以下 cmets 中找到一个简洁的: http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx

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

还有一个在: 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;
    }

两者都给了我想要的东西。

我还编写了一个小型单元测试,证明它们在日历的前 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 周。自然,您不能在 12 月有第 1 周,因为这意味着一年中会有两个第 1 周,因此会导致代码出现各种令人讨厌的问题。

    没有规定一周必须正好是 7 天。

    【讨论】:

    • 错了。 12 月 29 日、30 日、31 日可以属于下一年的第一周,1 月 1 日、2 日、3 日可以属于上一年。这取决于你住在哪里。
    • 没有规定一周必须正好是 7 天。:有一条规定就是这样:ISO 8601
    • @BigYellowCactus:没有任何迹象表明 .NET 的日期处理符合 ISO8601。
    • @DanPuzey 我知道,这正是问题所在。这就是为什么我在第一条评论中说这取决于你是否还活着。看我的回答。
    • 但它没有,因为它不是这样实现的。在现实生活中,是的,在 .NET 中,不是。在这种情况下,您的评论无效。
    【解决方案4】:

    Calendar.GetWeekOfYear 不支持 ISO8601 规范,另请参阅 ISO 8601 Week of Year format in Microsoft .Net

    你可以使用Time Period Library for .NETWeek类:

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

    【讨论】:

      【解决方案5】:

      您可以使用以下代码来计算给定日期的周数:

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

      【讨论】:

        【解决方案6】:

        我对丹麦日历一无所知,但 .NET 的规则很明确,并且您的代码与这些规则完全对应。 CalendarWeekRule 枚举的 MSDN 文档为 here,供参考。总结:

        .NET 将始终将 12 月 31 日视为“一年中的最后一周”。

        你说你得到了 FirstFourDayWeek 的周规则值。由于 2013 年 1 月 1 日是星期二,因此该周的剩余时间为六天(下周从下周一开始)。因此,2013 年 1 月 1 日至 6 日被视为“第 1 周”。如果您为该规则设置了 FirstFullWeek,则第 1 周将从 7 号星期一开始。

        至于“显然这一定是错的:”显然它是正确的,根据它自己的规范。从来没有要求任何编程语言或 API 符合特定的期望;每个项目都定义了自己的要求。 .NET 遵循我们可能认为出乎意料的规则,但它们记录在案,并且与其文档一致。

        这是否有用是另一个问题......

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-12
          • 1970-01-01
          • 1970-01-01
          • 2020-03-20
          • 2017-10-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多