Ошибка в расчете 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? Если последний - есть ли у вас предложения по правильному подсчету недельных номеров?
Ответы
Ответ 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;
}
}