Calcular fecha a partir del número de semana


Respuestas:


251

Tuve problemas con la solución de @HenkHolterman incluso con la solución de @RobinAndersson.

Leer sobre el estándar ISO 8601 resuelve muy bien el problema. Use el primer jueves como objetivo y no el lunes. El siguiente código funcionará también para la semana 53 de 2009.

public static DateTime FirstDateOfWeekISO8601(int year, int weekOfYear)
{
    DateTime jan1 = new DateTime(year, 1, 1);
    int daysOffset = DayOfWeek.Thursday - jan1.DayOfWeek;

    // Use first Thursday in January to get first week of the year as
    // it will never be in Week 52/53
    DateTime firstThursday = jan1.AddDays(daysOffset);
    var cal = CultureInfo.CurrentCulture.Calendar;
    int firstWeek = cal.GetWeekOfYear(firstThursday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

    var weekNum = weekOfYear;
    // As we're adding days to a date in Week 1,
    // we need to subtract 1 in order to get the right date for week #1
    if (firstWeek == 1)
    {
        weekNum -= 1;
    }

    // Using the first Thursday as starting week ensures that we are starting in the right year
    // then we add number of weeks multiplied with days
    var result = firstThursday.AddDays(weekNum * 7);

    // Subtract 3 days from Thursday to get Monday, which is the first weekday in ISO8601
    return result.AddDays(-3);
}       

3
He probado casi todas las soluciones dadas aquí, esta es la única que funciona correctamente para mí en este momento. Actualmente, es el 7 de febrero de 2012. La semana nr es el # 6. Este código me da correctamente el 6 de febrero como la fecha de inicio de la semana. Todas las otras soluciones me dieron el 13 de febrero, que en realidad es la fecha de inicio de la semana # 7.
HaukurHaf

1
Funciona como un encanto ... probó algunos datos: pastebin.com/mfx8s1vq ¡ Todo funciona perfectamente! Gracias Mikael!
Mittchel

1
@ RobinWassén-Andersson Lo bueno es que volviste a esta pregunta: D 6 votos más y lo vincularé con la respuesta "no" correcta, jeje.
Mikael Svenson

2
Sugerencia: Haga "ISO8601" pert del nombre del método, ya que simplemente no hay una respuesta "correcta" universal.
Henk Holterman

1
@Muflix No sé lo suficiente sobre SQL y fechas / calendarios para saber, pero hacer CLR funcionará.
Mikael Svenson

36

Me gusta la solución provista por Henk Holterman. Pero para ser un poco más independiente de la cultura, debes obtener el primer día de la semana para la cultura actual (no siempre es lunes):

using System.Globalization;

static DateTime FirstDateOfWeek(int year, int weekOfYear)
{
  DateTime jan1 = new DateTime(year, 1, 1);

  int daysOffset = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek - (int)jan1.DayOfWeek;

  DateTime firstMonday = jan1.AddDays(daysOffset);

  int firstWeek = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(jan1, CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule, CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek);

  if (firstWeek <= 1)
  {
    weekOfYear -= 1;
  }

  return firstMonday.AddDays(weekOfYear * 7);
}

He agregado esto a Mannex, como un método de extensión para Calendar y DateTimeFormatInfo. También hice referencia cruzada a esta respuesta para obtener crédito.
Atif Aziz

1
No funciona correctamente en mi máquina. Muestra una fecha con 0010 en lugar de 2010. No sé si es un problema en .net framework o en esta función. Buen intento, de todos modos ...
Eduardo Xavier

66
firstMondayes un nombre de variable incorrecto para algo que podría no ser un lunes. =)
mflodin

11

ACTUALIZAR : .NET Core 3.0 y .NET Standard 2.1 se han enviado con este tipo.

¡Buenas noticias! Una solicitud de extracción agregandoSystem.Globalization.ISOWeek acaba de fusionar a .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.

Deberías poder usar el ISOWeek.ToDateTime(int year, int week, DayOfWeek dayOfWeek) método para calcular esto.

Puedes encontrar el código fuente aquí .


9

Probablemente la forma más fácil es encontrar el primer lunes del año y luego agregar el número relevante de semanas. Aquí hay un código de muestra. Asume un número de semana que comienza en 1, por cierto:

using System;

class Test
{
    static void Main()
    {
        // Show the third Tuesday in 2009. Should be January 20th
        Console.WriteLine(YearWeekDayToDateTime(2009, DayOfWeek.Tuesday, 3));
    }

    static DateTime YearWeekDayToDateTime(int year, DayOfWeek day, int week)
    {
        DateTime startOfYear = new DateTime (year, 1, 1);

        // The +7 and %7 stuff is to avoid negative numbers etc.
        int daysToFirstCorrectDay = (((int)day - (int)startOfYear.DayOfWeek) + 7) % 7;

        return startOfYear.AddDays(7 * (week-1) + daysToFirstCorrectDay);
    }
}

44
Sí, pero el primer lunes del año podría pertenecer a la semana 52 | 53 del año anterior.
Henk Holterman

1
Depende de cómo quieras definir las cosas. Desafortunadamente no tenemos mucha información para continuar aquí ... Espero que esto sea útil.
Jon Skeet

3

Personalmente, aprovecharía la información cultural para obtener el día de la semana y pasar al primer día de la semana de la cultura. No estoy seguro si lo estoy explicando correctamente, aquí hay un ejemplo:

    public DateTime GetFirstDayOfWeek(int year, int weekNumber)
    {
        return GetFirstDayOfWeek(year, weekNumber, Application.CurrentCulture);
    }

    public DateTime GetFirstDayOfWeek(int year, int weekNumber,
        System.Globalization.CultureInfo culture)
    {
        System.Globalization.Calendar calendar = culture.Calendar;
        DateTime firstOfYear = new DateTime(year, 1, 1, calendar);
        DateTime targetDay = calendar.AddWeeks(firstOfYear, weekNumber);
        DayOfWeek firstDayOfWeek = culture.DateTimeFormat.FirstDayOfWeek;

        while (targetDay.DayOfWeek != firstDayOfWeek)
        {
            targetDay = targetDay.AddDays(-1);
        }

        return targetDay;
    }

3

usando Fluent DateTime http://fluentdatetime.codeplex.com/

        var year = 2009;
        var firstDayOfYear = new DateTime(year, 1, 1);
        var firstMonday = firstDayOfYear.Next(DayOfWeek.Monday);
        var weeksDateTime = 12.Weeks().Since(firstMonday);

2

De acuerdo con ISO 8601: 1988 que se usa en Suecia, la primera semana del año es la primera semana que tiene al menos cuatro días dentro del nuevo año.

Entonces, si su semana comienza un lunes, el primer jueves de cualquier año es dentro de la primera semana. Puede DateAdd o DateDiff a partir de eso.


2

Suponiendo que el número de la semana comienza en 1

DateTime dt =  new DateTime(YearNumber, 1, 1).AddDays((WeekNumber - 1) * 7 - (WeekNumber == 1 ? 0 : 1));
return dt.AddDays(-(int)dt.DayOfWeek);

Esto debería darte el primer día de cualquier semana. No he hecho muchas pruebas al respecto, pero parece que funciona. Es una solución más pequeña que la mayoría de las otras que encontré en la web, por lo que quería compartir.


mala respuesta. No se requiere el uso de DateTime.Parse ya que DateTime tiene un constructor que lleva año, mes y día. new DateTime(1,1,YearNumber)
Jamiec

Se actualizó el código para crear un nuevo DateTime en lugar de usar parse. Era tarde. ;)
QuinnG

2

La biblioteca gratuita de períodos de tiempo para .NET incluye la clase conforme ISO 8601 Semana :

// ----------------------------------------------------------------------
public static DateTime GetFirstDayOfWeek( int year, int weekOfYear )
{
  return new Week( year, weekOfYear ).FirstDayStart;
} // GetFirstDayOfWeek

2

Este funcionó para mí, también tiene la ventaja de esperar una información cultural como parámetro para probar la fórmula con diferentes culturas. Si está vacío, obtiene la información cultural actual ... los valores válidos son: "it", "en-us", "fr", ... y así sucesivamente. El truco consiste en restar el número de semana del primer día del año, que puede ser 1 para indicar que el primer día está dentro de la primera semana. Espero que esto ayude.

Public Shared Function FirstDayOfWeek(ByVal year As Integer, ByVal weekNumber As Integer, ByVal culture As String) As Date
    Dim cInfo As System.Globalization.CultureInfo
    If culture = "" Then
        cInfo = System.Globalization.CultureInfo.CurrentCulture
    Else
        cInfo = System.Globalization.CultureInfo.CreateSpecificCulture(culture)
    End If
    Dim calendar As System.Globalization.Calendar = cInfo.Calendar
    Dim firstOfYear As DateTime = New DateTime(year, 1, 1, calendar)
    Dim firstDayWeek As Integer = calendar.GetWeekOfYear(firstOfYear, cInfo.DateTimeFormat.CalendarWeekRule, cInfo.DateTimeFormat.FirstDayOfWeek)
    weekNumber -= firstDayWeek
    Dim targetDay As DateTime = calendar.AddWeeks(firstOfYear, weekNumber)
    Dim fDayOfWeek As DayOfWeek = cInfo.DateTimeFormat.FirstDayOfWeek

    While (targetDay.DayOfWeek <> fDayOfWeek)
        targetDay = targetDay.AddDays(-1)
    End While
    Return targetDay
End Function

2

Aquí hay un método que es compatible con los números semanales que Google Analytics, y también el mismo esquema de numeración que utilizamos internamente en Intel, y que estoy seguro de que también se usa en muchos otros contextos.

// Google Analytics does not follow ISO standards for date.
// It numbers week 1 starting on Jan. 1, regardless what day of week it starts on.
// It treats Sunday as the first day of the week.
// The first and last weeks of a year are usually not complete weeks.
public static DateTime GetStartDateTimeFromWeekNumberInYear(int year, uint weekOfYear)
{
  if (weekOfYear == 0 || weekOfYear > 54) throw new ArgumentException("Week number must be between 1 and 54! (Yes, 54... Year 2000 had Jan. 1 on a Saturday plus 53 Sundays.)");

  // January 1 -- first week.
  DateTime firstDayInWeek = new DateTime(year, 1, 1);
  if (weekOfYear == 1) return firstDayInWeek;

  // Get second week, starting on the following Sunday.      
  do
  {
    firstDayInWeek = firstDayInWeek.AddDays(1);
  } while (firstDayInWeek.DayOfWeek != DayOfWeek.Sunday);

  if (weekOfYear == 2) return firstDayInWeek;

  // Now get the Sunday of whichever week we're looking for.
  return firstDayInWeek.AddDays((weekOfYear - 2)*7);
}

2

Probé algunos códigos anteriores y algunos tienen pequeños errores, cuando intentas diferentes años con diferentes días de inicio de la semana los verás, tomé el código de Jon Skeet, lo arreglé y funciona, código muy simple.

Public Function YearWeekDayToDateTime(ByVal year As Integer, ByVal weekDay As Integer, ByVal week As Integer) As DateTime
   ' weekDay, day you want
    Dim startOfYear As New DateTime(year, 1, 1)
    Dim startOfYearFixDay As Integer

    If startOfYear.DayOfWeek <> DayOfWeek.Sunday Then
        startOfYearFixDay = startOfYear.DayOfWeek
    Else
        startOfYearFixDay = 7
    End If

    Return startOfYear.AddDays((7 * (week)) - startOfYearFixDay + weekDay)
End Function

1

Cambió ligeramente el código de Mikael Svenson. Encontré la semana del primer lunes y cambié el número de semana.

 DateTime GetFirstWeekDay(int year, int weekNum)
    {
        Calendar calendar = CultureInfo.CurrentCulture.Calendar;

        DateTime jan1 = new DateTime(year, 1, 1);

        int daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
        DateTime firstMonday = jan1.AddDays(daysOffset);
        int firstMondayWeekNum = calendar.GetWeekOfYear(firstMonday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

        DateTime firstWeekDay = firstMonday.AddDays((weekNum-firstMondayWeekNum) * 7);

        return firstWeekDay;
    }

0

La semana 1 se define como la semana que comienza un lunes y contiene el primer jueves del año.


1
Esa es una definición, hay otras.
Henk Holterman

1
El estándar ISO define la semana 1 como la semana con el primer jueves del año.
RickardN


0

Mejoré un poco la solución de Thomas con una anulación:

   public static DateTime FirstDateOfWeek(int year, int weekOfYear)
    {
      return Timer.FirstDateOfWeekOfMonth(year, 1, weekOfYear);
    }

    public static DateTime FirstDateOfWeekOfMonth(int year, int month, 
    int weekOfYear)
    {
      DateTime dtFirstDayOfMonth = new DateTime(year, month, 1);

       //I also commented out this part:
      /*
      if (firstWeek <= 1)
      {
        weekOfYear -= 1;
      }
      */

De lo contrario, la fecha era anterior a una semana.

Gracias Thomas, gran ayuda.


0

Utilicé una de las soluciones, pero me dio resultados incorrectos, simplemente porque cuenta el domingo como primer día de la semana.

Cambié:

var firstDay = new DateTime(DateTime.Now.Year, 1, 1).AddDays((weekNumber - 1) * 7);
var lastDay = firstDay.AddDays(6);

a:

var lastDay = new DateTime(DateTime.Now.Year, 1, 1).AddDays((weekNumber) * 7);
var firstDay = lastDay.AddDays(-6);

Y ahora está funcionando como un encanto.


1
Pero, ¿esto responde a la pregunta del OP? Es 1/1 más un número fijo de días. No hay concepto de primer día de la semana.
Gert Arnold

0

La solución propuesta no está completa, solo funciona para CalendarWeekRule.FirstFullWeek. Otros tipos de reglas semanales no funcionan. Esto se puede ver usando este caso de prueba:

foreach (CalendarWeekRule rule in Enum.GetValues(typeof(CalendarWeekRule)))
{
    for (int year = 1900; year < 2000; year++)
    {
        DateTime date = FirstDateOfWeek(year, 1, rule);
        Assert(CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date, rule, DayOfWeek.Monday) == 1);
        Assert(CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date.AddDays(-1), rule, DayOfWeek.Monday) != 1);
    }
}

0

He hecho una versión refinada de la solución propuesta que es más simple y parametriza el primer día de la semana:

public static DateTime GetFirstDayOfWeek(int year, int week, DayOfWeek firstDayOfWeek)
{
    return GetWeek1Day1(year, firstDayOfWeek).AddDays(7 * (week - 1));
}

public static DateTime GetWeek1Day1(int year, DayOfWeek firstDayOfWeek)
{
    DateTime date = new DateTime(year, 1, 1);

    // Move towards firstDayOfWeek
    date = date.AddDays(firstDayOfWeek - date.DayOfWeek);

    // Either 1 or 52 or 53
    int weekOfYear = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFullWeek, firstDayOfWeek);

    // Move forwards 1 week if week is 52 or 53
    date = date.AddDays(7 * System.Math.Sign(weekOfYear - 1));

    return date;
}

0

Esta es mi solución cuando queremos calcular una fecha dada año, número de semana y día de la semana.

int Year = 2014;
int Week = 48;
int DayOfWeek = 4;

DateTime FecIni = new DateTime(Year, 1, 1);
FecIni = FecIni.AddDays(7 * (Week - 1));
if ((int)FecIni.DayOfWeek > DayOfWeek)
{
    while ((int)FecIni.DayOfWeek != DayOfWeek) FecIni = FecIni.AddDays(-1);
}
else
{
    while ((int)FecIni.DayOfWeek != DayOfWeek) FecIni = FecIni.AddDays(1);
}

0

Simplifiqué el código que proporcionó Mikael Svensson, que es correcto para muchos países de Europa.

public static DateTime FirstDateOfWeekIso8601(int year, int week)
{
        var firstThursdayOfYear = new DateTime(year, 1, 1);
        while (firstThursdayOfYear.DayOfWeek != DayOfWeek.Thursday)
        {
            firstThursdayOfYear = firstThursdayOfYear.AddDays(1);
        }

        var startDateOfWeekOne = firstThursdayOfYear.AddDays(-(DayOfWeek.Thursday - DayOfWeek.Monday));

        return startDateOfWeekOne.AddDays(7 * (week - 1));        
}

0

He escrito y probado el siguiente código y funciona perfectamente bien para mí. Avíseme si alguien tiene problemas con esto, también he publicado una pregunta para obtener la mejor respuesta posible. Alguien puede encontrarlo útil.

public static DateTime GetFirstDateOfWeekByWeekNumber(int year, int weekNumber)
        {
            var date = new DateTime(year, 01, 01);
            var firstDayOfYear = date.DayOfWeek;
            var result = date.AddDays(weekNumber * 7);

            if (firstDayOfYear == DayOfWeek.Monday)
                return result.Date;
            if (firstDayOfYear == DayOfWeek.Tuesday)
                return result.AddDays(-1).Date;
            if (firstDayOfYear == DayOfWeek.Wednesday)
                return result.AddDays(-2).Date;
            if (firstDayOfYear == DayOfWeek.Thursday)
                return result.AddDays(-3).Date;
            if (firstDayOfYear == DayOfWeek.Friday)
                return result.AddDays(-4).Date;
            if (firstDayOfYear == DayOfWeek.Saturday)
                return result.AddDays(-5).Date;
            return result.AddDays(-6).Date;
        }

Esto está mal, al menos para muchos países europeos. GetFirstDateOfWeekByWeekNumber(2020, 29)para la semana 29 de 2020 esto vuelve 07/20/2020. Pero el primer día de la semana 29 fue07/13/2020
Matthias Burger

0

Actualmente, no existe una clase C # que maneje correctamente los números semanales ISO 8601. Aunque puede crear una instancia de una cultura, buscar lo más cercano y corregirlo, creo que es mejor hacer el cálculo completo usted mismo:

    /// <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;
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.