c# - the - week number excel
Error en el cálculo de WeekNumber.NET? (6)
Tengo un problema bastante raro. Vivo en Dinamarca y aquí la primera semana (Semana 1) de 2013 comienza el 31 de diciembre de 2012 y dura 7 días, como suelen hacerlo las semanas :)
Según .NET, sin embargo, el 30 de diciembre es la semana 52, el 31 es la semana 53 y el 1 de enero es la semana 1.
La semana 53 dura solo un día, y la semana 1 dura 6 días. Claramente, esto debe ser incorrecto (una semana de menos de 7 días) y ciertamente es incorrecto en el contexto danés. Donde el 31 de diciembre es la semana 1, NO la semana 53.
El siguiente código ilustra el problema (CurrentCulture es "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();
}
¿He hecho algo mal o es un error en .NET? Si es lo último, ¿tiene sugerencias para calcular los números de semana correctamente?
1.1.2013 es martes, y comienza la semana # 1. 31.12.2012 es lunes, pertenece al año 2012 y, por lo tanto, es la semana # 53. Naturalmente, no puede tener la semana 1 en diciembre, ya que eso significaría que habría dos semanas 1 en un año, y por lo tanto causaría todo tipo de problemas desagradables con el código.
No hay ninguna regla que una semana debe ser exactamente de 7 días.
Calendar.GetWeekOfYear no es compatible con la especificación ISO8601, consulte también el formato de la Semana del Año ISO 8601 en Microsoft .Net .
Puede utilizar la clase Semana de la biblioteca de períodos de tiempo para .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
El problema es que el método GetWeekOfYear
no respeta ISO 8601, que es lo que usted espera, pero no lo hace.
Tenga en cuenta que mientras usa FirstFourDayWeek
, la msdn.microsoft.com/en-us/library/… dice:
La primera semana basada en el valor de FirstFourDayWeek puede tener de cuatro a siete días.
que es una violación de la norma ISO 8601 de que todas las semanas deben tener siete días.
blogs.msdn.com/b/shawnste/archive/2006/01/24/…
Puede utilizar el siguiente método para obtener el número de semana correcto de acuerdo con 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 (también hay muchas otras funciones por ahí ...)
Así, cambiando su bucle a
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));
}
resultará en:
Fecha: 30.12.2012 WeekNumber: 52: CorrectWeekNumber: 52
Fecha: 31.12.2012 WeekNumber: 53: CorrectWeekNumber: 1
Fecha: 01.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Fecha: 02.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Fecha: 03.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Fecha: 04.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Fecha: 05.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Fecha: 06.01.2013 WeekNumber: 1: CorrectWeekNumber: 1
Fecha: 07.01.2013 WeekNumber: 2: CorrectWeekNumber: 2
Fecha: 08.01.2013 WeekNumber: 2: CorrectWeekNumber: 2
Fecha: 09.01.2013 WeekNumber: 2: CorrectWeekNumber: 2
Gracias por todas las respuestas. También busqué un poco más y finalmente creé dos métodos de C # para lograr lo que quería:
Primero, un resumen que se encuentra en uno de los comentarios en: blogs.msdn.com/b/shawnste/archive/2006/01/24/…
Que Jon Senchyna también señaló:
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);
}
Y también uno en: 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;
}
Ambos me dieron lo que yo quería.
También escribí una pequeña prueba de unidad que prueba que devuelven los mismos números de semana durante los primeros 3000 años del calendario.
[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);
}
No sé nada sobre el calendario danés, pero las reglas para .NET son claras y su código se corresponde exactamente con eso. La documentación de MSDN para la enumeración CalendarWeekRule
está msdn.microsoft.com/en-us/library/… , para referencia. En resumen:
.NET siempre considerará el 31 de diciembre como "la última semana del año".
Usted dice que está obteniendo un valor de la regla FirstFourDayWeek
para la semana. Como el 1 de enero de 2013 es un martes, el resto de esa semana tiene seis días (la próxima semana comienza el lunes siguiente). Por lo tanto, 1-6 de enero de 2013 se considera "Semana 1". Si tuviera FirstFullWeek
para esa regla, la semana 1 comenzaría el lunes 7 en su lugar.
En cuanto a "Claramente, esto debe estar equivocado:" claramente está bien, según su propia especificación. Nunca existe un requisito para que cualquier lenguaje de programación o API se ajuste a una expectativa particular; Cada proyecto define sus propios requisitos. .NET sigue las reglas que podemos considerar inesperadas, pero están documentadas y son coherentes con su documentación.
Si eso es útil o no es otra pregunta ...
Puede usar el siguiente código para calcular el número de semana para una fecha determinada:
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;
}
}