Obtenga el número de semana correcto de una fecha determinada

222

Busqué en Google mucho y encontré muchas soluciones, pero ninguna de ellas me dio el número de semana correcto para el 2012-12-31. Incluso el ejemplo en MSDN ( enlace ) falla.

2012-12-31 es lunes, por lo tanto, debería ser la semana 1, pero cada método que probé me da 53. Estos son algunos de los métodos que he probado:

De la Biblioteca MDSN:

DateTimeFormatInfo dfi = DateTimeFormatInfo.CurrentInfo;
Calendar cal = dfi.Calendar;

return cal.GetWeekOfYear(date, dfi.CalendarWeekRule, dfi.FirstDayOfWeek);

Solución 2:

return new GregorianCalendar(GregorianCalendarTypes.Localized).GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

Solución 3:

CultureInfo ciCurr = CultureInfo.CurrentCulture;
int weekNum = ciCurr.Calendar.GetWeekOfYear(dtPassed, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
return weekNum;

Actualizar

El siguiente método en realidad devuelve 1 cuando la fecha es 2012-12-31. En otras palabras, mi problema era que mis métodos no seguían el estándar ISO-8601.

// This presumes that weeks start with Monday.
// Week 1 is the 1st week of the year with a Thursday in it.
public static int GetIso8601WeekOfYear(DateTime time)
{
    // Seriously cheat.  If its Monday, Tuesday or Wednesday, then it'll 
    // be the same week# as whatever Thursday, Friday or Saturday are,
    // and we always get those right
    DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
    if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
    {
        time = time.AddDays(3);
    }

    // Return the week of our adjusted day
    return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}
Lámparas de color ambar
fuente
44
¿Cómo es la semana 1 al final del año? Quiero decir, veo de dónde lo sacas. Pero 53 tiene sentido para mí.
benjer3
1
En mis fragmentos de código obtengo el CultureInfo y esas cosas. Pensé que mi programa ya sabía qué calendario estoy usando. (Aquí en Alemania el 31 de diciembre de 2012 es en la semana 1 de 2013)
Amberlamps
Este código no funciona del todo, ya que debería probar las fechas el 31 de diciembre de 2016, por ejemplo, o el 1 de enero de 2016
cavej03
@ cavej03 31-dic-2016 es la semana 52 y GetIso8601WeekOfYear devuelve 52, así que supongo que funciona correctamente.
Muflix

Respuestas:

311

Como se señaló en esta página de MSDN, hay una ligera diferencia entre la numeración de la semana ISO8601 y .Net.

Puede consultar este artículo en el blog de MSDN para obtener una mejor explicación: " Formato ISO 8601 Semana del año en Microsoft .Net "

En pocas palabras, .Net permite que las semanas se dividan en años, mientras que el estándar ISO no. En el artículo también hay una función simple para obtener el número de semana ISO 8601 correcto para la última semana del año.

Actualización El siguiente método en realidad devuelve 1 para el 2012-12-31cual es correcto en ISO 8601 (por ejemplo, Alemania).

// This presumes that weeks start with Monday.
// Week 1 is the 1st week of the year with a Thursday in it.
public static int GetIso8601WeekOfYear(DateTime time)
{
    // Seriously cheat.  If its Monday, Tuesday or Wednesday, then it'll 
    // be the same week# as whatever Thursday, Friday or Saturday are,
    // and we always get those right
    DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
    if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
    {
        time = time.AddDays(3);
    }

    // Return the week of our adjusted day
    return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
} 
il_guru
fuente
1
@il_guru ¿Qué pasa si deseo que la semana comience con el domingo? ¿Reemplazo todo "lunes" con "domingo"?
Usuario2012384
2
@ User2012384 para su información, si desea seguir el estándar ISO8601, el primer día de la semana siempre debe ser el lunes.
Starceaker
1
@il_guru Ni siquiera necesitas este "truco" si, ¿verdad? Hasta donde puedo decir "GetWeekOfYear (time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday)"; funciona bien
Starceaker
2
Si busca el segundo enlace (formato ISO 8601 Semana del año en Microsoft .Net), puede encontrar la respuesta a su pregunta directamente desde Microsoft. Un ejemplo es el lunes 31/12/2007: con la función .Net devolverá la semana número 53 de 2007, mientras que para el estándar ISO es la semana número 1 de 2008
il_guru
3
He verificado manualmente que esto sea correcto para los años 2012 a 2018 (suponiendo que epochconverter.com sea correcto). ¡Todos deberíamos alegrarnos este año porque la semana 1 de 2018 en realidad comenzó el primero de enero por una vez!
Aidan
32

Puede haber más de 52 semanas en un año. Cada año tiene 52 semanas completas + 1 o +2 (año bisiesto) días adicionales. Componen una semana 53.

  • 52 semanas * 7 días = 364 días.

Entonces, por cada año, tiene al menos uno por día adicional. Dos por años bisiestos. ¿Se cuentan estos días adicionales como semanas separadas?

Cuántas semanas hay realmente depende del día de inicio de su semana. Consideremos esto para el 2012.

  • EE. UU. (Domingo -> sábado): 52 semanas + una semana corta de 2 días para el 30/12/2012 y el 31/12/2012. Esto da como resultado un total de 53 semanas. Los últimos dos días de este año (domingo + lunes) conforman su propia semana corta.

Verifique la configuración actual de su Cultura para ver qué usa como primer día de la semana.

Como puede ver, es normal obtener 53 como resultado.

  • Europa (lunes -> domingo): el 2 de enero (2012-1-2) es el primer lunes, así que este es el primer día de la primera semana. Pregunte el número de la semana para el 1 de enero y obtendrá 52, ya que se considera parte de 2011 la semana pasada.

Incluso es posible tener una semana 54. Ocurre cada 28 años cuando el 1 de enero y el 31 de diciembre se tratan como semanas separadas. Debe ser un año bisiesto también.

Por ejemplo, el año 2000 tuvo 54 semanas. El 1 de enero (sábado) fue el primer día de una semana, y el 31 de diciembre (sol) fue el segundo día de una semana.

var d = new DateTime(2012, 12, 31);
CultureInfo cul = CultureInfo.CurrentCulture;

var firstDayWeek = cul.Calendar.GetWeekOfYear(
    d,
    CalendarWeekRule.FirstDay,
    DayOfWeek.Monday);

int weekNum = cul.Calendar.GetWeekOfYear(
    d,
    CalendarWeekRule.FirstDay,
    DayOfWeek.Monday);

int year = weekNum == 52 && d.Month == 1 ? d.Year - 1 : d.Year;
Console.WriteLine("Year: {0} Week: {1}", year, weekNum);

Imprime: Año: 2012 Semana: 54

Cambie CalendarWeekRule en el ejemplo anterior a FirstFullWeek o FirstFourDayWeek y obtendrá 53. Guardemos el día de inicio el lunes ya que estamos tratando con Alemania.

Entonces, la semana 53 comienza el lunes 31/12/2012, dura un día y luego se detiene.

53 es la respuesta correcta. Cambia la Cultura a Alemania si quieres probarlo.

CultureInfo cul = CultureInfo.GetCultureInfo("de-DE");
Christophe Geers
fuente
Sé que es posible que haya 53 semanas. Pero cuando tiene domingo (30/12) y lunes (31/12), ¿no cuenta el martes (01/01) también como parte de la semana 53 en los Estados Unidos?
Amberlamps
¡Nunca he visto una semana 54!
Amberlamps
Ciertos paquetes de software fiscal los tienen.
Christophe Geers
De acuerdo, descubrí que es posible en los calendarios de EE. UU., Pero no sucederá en los calendarios alemanes.
Amberlamps
1
+1 por sus profundos pensamientos sobre el tema, pero el fragmento de código en la entrada del blog proporcionada en una respuesta diferente en realidad resolvió mi problema.
Amberlamps
22

Esta es la forma:

public int GetWeekNumber()
{
    CultureInfo ciCurr = CultureInfo.CurrentCulture;
    int weekNum = ciCurr.Calendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
    return weekNum;
}

Lo más importante para es el CalendarWeekRuleparámetro.

Ver aquí: https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=IT-IT&k=k(System.Globalization.CalendarWeekRule);k(TargetFrameworkMoniker-.NETFramework

daniele3004
fuente
3
"Varias personas han notado que Calendar.GetWeekOfYear () es casi como la semana ISO 8601 cuando pasó CalendarWeekRule.FirstFourDayWeek y DayOfWeek.Monday, sin embargo, es un poco diferente. Específicamente ISO 8601 siempre tiene 7 días de semana" . Blogs.msdn. microsoft.com/shawnste/2006/01/24/…
Juha Palomäki
1
Su enlace está en italiano
radbyx
para el nuevo DateTime (2000,12,31) devuelve solo 52?
Leszek P
18

¡Buenas noticias! Se acaba de fusionar una solicitud de extracción que se agrega System.Globalization.ISOWeeka .NET Core y actualmente está programada para la versión 3.0. Con suerte, se propagará a las otras plataformas .NET en un futuro no muy lejano.

El tipo tiene la siguiente firma, que debería cubrir la mayoría de las necesidades de la semana ISO :

namespace System.Globalization
{
    public static class ISOWeek
    {
        public static int GetWeekOfYear(DateTime date);
        public static int GetWeeksInYear(int year);
        public static int GetYear(DateTime date);
        public static DateTime GetYearEnd(int year);
        public static DateTime GetYearStart(int year);
        public static DateTime ToDateTime(int year, int week, DayOfWeek dayOfWeek);
    }
}

Puedes encontrar el código fuente aquí .

ACTUALIZACIÓN : Estas API también se han incluido en la versión 2.1 de .NET Standard .

khellang
fuente
¿Alguna idea de cuándo se lanzará v 3.0?
Jacques
"Estamos planeando lanzar una primera vista previa de .NET Core 3 a finales de este año y la versión final en 2019". (a partir de la entrada en el blog hoja de ruta )
khellang
@khellang, ¿cómo podría obtener la semana ordinal en un mes según la fecha y hora? En lugar de recibir la semana 33, me gustaría ver la semana 2 (porque la semana 33 es la semana 2 en agosto). stackoverflow.com/questions/58039103/…
Roxy'Pro
11

Como no parece haber una cultura .Net que produzca el número de semana ISO-8601 correcto, prefiero omitir por completo la determinación de semana incorporada y hacer el cálculo manualmente, en lugar de intentar corregir una corrección parcialmente correcta resultado.

Lo que terminé con es el siguiente método de extensión:

/// <summary>
/// Converts a date to a week number.
/// ISO 8601 week 1 is the week that contains the first Thursday that year.
/// </summary>
public static int ToIso8601Weeknumber(this DateTime date)
{
    var thursday = date.AddDays(3 - date.DayOfWeek.DayOffset());
    return (thursday.DayOfYear - 1) / 7 + 1;
}

/// <summary>
/// Converts a week number to a date.
/// Note: Week 1 of a year may start in the previous year.
/// ISO 8601 week 1 is the week that contains the first Thursday that year, so
/// if December 28 is a Monday, December 31 is a Thursday,
/// and week 1 starts January 4.
/// If December 28 is a later day in the week, week 1 starts earlier.
/// If December 28 is a Sunday, it is in the same week as Thursday January 1.
/// </summary>
public static DateTime FromIso8601Weeknumber(int weekNumber, int? year = null, DayOfWeek day = DayOfWeek.Monday)
{
    var dec28 = new DateTime((year ?? DateTime.Today.Year) - 1, 12, 28);
    var monday = dec28.AddDays(7 * weekNumber - dec28.DayOfWeek.DayOffset());
    return monday.AddDays(day.DayOffset());
}

/// <summary>
/// Iso8601 weeks start on Monday. This returns 0 for Monday.
/// </summary>
private static int DayOffset(this DayOfWeek weekDay)
{
    return ((int)weekDay + 6) % 7;
}

En primer lugar, ((int)date.DayOfWeek + 6) % 7)determina el número del día de la semana, 0 = lunes, 6 = domingo.

date.AddDays(-((int)date.DayOfWeek + 6) % 7) determina la fecha del lunes anterior al número de semana solicitado.

Tres días después es el jueves objetivo, que determina en qué año está la semana.

Si divide el número de día (basado en cero) dentro del año por siete (redondeando hacia abajo), obtendrá el número de semana (basado en cero) en el año.

En c #, los resultados de cálculo de enteros se redondean hacia abajo implícitamente.

realbart
fuente
1
Esta es la mejor solución con diferencia, pero señalaré una forma ligeramente diferente de hacerlo: DateTime se basa en el calendario gregoriano proleptico, es decir, extiende el calendario gregoriano hasta el año 1. El 1 de enero de 0001 es un Lunes. Tickses el número de incrementos de 100 nanosegundos desde el 1 de enero de 0001. Dado que hay 86,400 segundos en un día, podemos escribir:var thursday = date.AddDays(3 - date.Ticks / 86400 / 10_000_000 % 7); return (thursday.DayOfYear - 1) / 7 + 1;
Adrian S
5

En .NET 3.0 y versiones posteriores, puede usar el ISOWeek.GetWeekOfDatemétodo .

Tenga en cuenta que el año en el formato de número de año + semana puede diferir del año DateTimedebido a las semanas que cruzan el límite del año.

johh
fuente
3

C # al puerto Powershell desde el código anterior de il_guru :

function GetWeekOfYear([datetime] $inputDate)
{
   $day = [System.Globalization.CultureInfo]::InvariantCulture.Calendar.GetDayOfWeek($inputDate)
   if (($day -ge [System.DayOfWeek]::Monday) -and ($day -le [System.DayOfWeek]::Wednesday))
   {
      $inputDate = $inputDate.AddDays(3)
   }

   # Return the week of our adjusted day
   $weekofYear = [System.Globalization.CultureInfo]::InvariantCulture.Calendar.GetWeekOfYear($inputDate, [System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [System.DayOfWeek]::Monday)
   return $weekofYear
}
Rainer
fuente
1
¿Podrías explicar tu código? Las respuestas de solo código generalmente no son aceptables en StackOverflow y, como tales, es probable que se eliminen.
Wai Ha Lee
@Wai Ha Lee, el código ya está explicado. Ver publicación anterior de il_guru. Porté su código a powershell, para que otras personas puedan usarlo en powershell porque hasta ahora no hay una buena solución en powershell.
Rainer
Si eso es lo que hiciste, debes acreditar al autor original en la respuesta misma. La respuesta que portó tiene mejores comentarios que presumiblemente ha eliminado. Además, esta no es una respuesta a la pregunta, ya que la pregunta no mencionaba PowerShell.
Wai Ha Lee
1
Lo hice ahora @Wai Ha Lee. La pregunta ya está marcada como respondida, por lo que esta sería la misma solución escrita en otro idioma. Hice un favor a las personas que buscan una solución en el lenguaje Powershell (estaba buscando una solución en Powershell, pero lo que encontré fue una solución en C #). La idea sigue siendo la misma y el autor obtiene el crédito por ello.
Rainer
2

La forma más fácil de determinar el estilo de número de semana ISO 8601 usando c # y la clase DateTime.

Pregunte esto: el jueves cuántos años es el jueves de esta semana. La respuesta es igual al número de semana deseado.

var dayOfWeek = (int)moment.DayOfWeek;
// Make monday the first day of the week
if (--dayOfWeek < 0)
    dayOfWeek = 6;
// The whole nr of weeks before this thursday plus one is the week number
var weekNumber = (moment.AddDays(3 - dayOfWeek).DayOfYear - 1) / 7 + 1;
usuario6887101
fuente
1
var cultureInfo = CultureInfo.CurrentCulture;
var calendar = cultureInfo.Calendar;

var calendarWeekRule = cultureInfo.DateTimeFormat.CalendarWeekRule;
var firstDayOfWeek = cultureInfo.DateTimeFormat.FirstDayOfWeek;
var lastDayOfWeek = cultureInfo.LCID == 1033 //En-us
                    ? DayOfWeek.Saturday
                    : DayOfWeek.Sunday;

var lastDayOfYear = new DateTime(date.Year, 12, 31);

var weekNumber = calendar.GetWeekOfYear(date, calendarWeekRule, firstDayOfWeek);

 //Check if this is the last week in the year and it doesn`t occupy the whole week
return weekNumber == 53 && lastDayOfYear.DayOfWeek != lastDayOfWeek 
       ? 1  
       : weekNumber;

Funciona bien tanto para las culturas estadounidense como rusa. ISO 8601 también será correcta, porque la semana rusa comienza el lunes.

Donskikh Andrei
fuente
1

Aquí hay una versión de extensión y una versión anulable de la respuesta de il_guru .

Extensión:

public static int GetIso8601WeekOfYear(this DateTime time)
{
    var day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
    if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
    {
        time = time.AddDays(3);
    }

    return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}

Anulable:

public static int? GetIso8601WeekOfYear(this DateTime? time)
{
    return time?.GetIso8601WeekOfYear();
}

Usos:

new DateTime(2019, 03, 15).GetIso8601WeekOfYear(); //returns 11
((DateTime?) new DateTime(2019, 03, 15)).GetIso8601WeekOfYear(); //returns 11
((DateTime?) null).GetIso8601WeekOfYear(); //returns null
Trotar
fuente
0

La pregunta es: ¿Cómo define si una semana es en 2012 o en 2013? Supongo que su suposición es que, dado que 6 días de la semana son en 2013, esta semana debe marcarse como la primera semana de 2013.

No estoy seguro si este es el camino correcto. Esa semana comenzó en 2012 (el lunes 31 de diciembre), por lo que debe marcarse como la última semana de 2012, por lo tanto, debe ser el 53 de 2012. La primera semana de 2013 debe comenzar el lunes 7.

Ahora, puede manejar el caso particular de semanas límite (primera y última semana del año) utilizando la información del día de la semana. Todo depende de tu lógica.

Samy Arous
fuente
De acuerdo, puedo ver tu punto. La cosa es que cuando uso 2013/01/07 en cualquiera de mis métodos, dice la semana 2. Entonces, 2012/12/31 es la semana 53 y 2013/01/07 es la semana 2. Se podría pensar que no hay semana 1 de 2013. Pero cuando intento 01/01/2013 dice la semana 1.
Amberlamps
0
  DateTimeFormatInfo dfi = DateTimeFormatInfo.CurrentInfo;
  DateTime date1 = new DateTime(2011, 1, 1);
  Calendar cal = dfi.Calendar;

  Console.WriteLine("{0:d}: Week {1} ({2})", date1, 
                    cal.GetWeekOfYear(date1, dfi.CalendarWeekRule, 
                                      dfi.FirstDayOfWeek),
                    cal.ToString().Substring(cal.ToString().LastIndexOf(".") + 1));      
Imran
fuente
0

Basado en la respuesta de il_guru, creé esta versión para mis propias necesidades que también devuelve el componente del año.

    /// <summary>
    /// This presumes that weeks start with Monday.
    /// Week 1 is the 1st week of the year with a Thursday in it.
    /// </summary>
    /// <param name="time">The date to calculate the weeknumber for.</param>
    /// <returns>The year and weeknumber</returns>
    /// <remarks>
    /// Based on Stack Overflow Answer: https://stackoverflow.com/a/11155102
    /// </remarks>
    public static (short year, byte week) GetIso8601WeekOfYear(DateTime time)
    {
        // Seriously cheat.  If its Monday, Tuesday or Wednesday, then it'll
        // be the same week# as whatever Thursday, Friday or Saturday are,
        // and we always get those right
        DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
        if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
        {
            time = time.AddDays(3);
        }
        // Return the week of our adjusted day
        var week = (byte)CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
        return ((short)(week >= 52 & time.Month == 1 ? time.Year - 1 : time.Year), week);
    }
NKCSS
fuente
0

Estos dos métodos ayudarán, suponiendo que nuestra semana comience el lunes

/// <summary>
    /// Returns the weekId
    /// </summary>
    /// <param name="DateTimeReference"></param>
    /// <returns>Returns the current week id</returns>
    public static DateTime GetDateFromWeek(int WeekReference)
    {
        //365 leap
        int DaysOffset = 0;
        if (WeekReference > 1)
        {
            DaysOffset = 7;
            WeekReference = WeekReference - 1;
        }
        DateTime DT = new DateTime(DateTime.Now.Year, 1, 1);
        int CurrentYear = DT.Year;
        DateTime SelectedDateTime = DateTime.MinValue;

        while (CurrentYear == DT.Year)
        {
            int TheWeek = WeekReportData.GetWeekId(DT);
            if (TheWeek == WeekReference)
            {
                SelectedDateTime = DT;
                break;
            }
            DT = DT.AddDays(1.0D);
        }

        if (SelectedDateTime == DateTime.MinValue)
        {
            throw new Exception("Please check week");
        }

        return SelectedDateTime.AddDays(DaysOffset);
    }
/// <summary>
    /// Returns the weekId
    /// </summary>
    /// <param name="DateTimeReference"></param>
    /// <returns>Returns the current week id</returns>
    public static int GetWeekId(DateTime DateTimeReference)
    {
        CultureInfo ciCurr = CultureInfo.InvariantCulture;
        int weekNum = ciCurr.Calendar.GetWeekOfYear(DateTimeReference,
        CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday);
        return weekNum;
    }
Mnyikka
fuente
-1

Un año tiene 52 semanas y 1 día o 2 en caso de un año de vuelta (52 x 7 = 364). 2012-12-31 sería la semana 53, una semana que solo tendría 2 días porque 2012 es un año de vuelta.

CarlosJ
fuente
Esto es incorrecto. El primer día de la semana del año puede caer en cualquier día de la semana, dependiendo de cómo cuente las semanas, el año puede tener una semana 54.
krowe2
-2
public int GetWeekNumber()
{
   CultureInfo ciCurr = CultureInfo.CurrentCulture;
   int weekNum = ciCurr.Calendar.GetWeekOfYear(DateTime.Now, 
   CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
   return weekNum;
}
Meghnath Das
fuente