Calcular data a partir do número da semana

Respostas:

251

Eu tive problemas com a solução de @HenkHolterman, mesmo com a correção de @RobinAndersson.

A leitura da norma ISO 8601 resolve bem o problema. Use a primeira quinta-feira como alvo e não segunda-feira. O código abaixo também funcionará para a 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);
}       
Mikael Svenson
fonte
3
Eu tentei quase todas as soluções fornecidas aqui, essa é a única que funciona corretamente para mim agora. Atualmente, é o dia 7 de fevereiro de 2012. A semana nº é # 6. Esse código me dá corretamente o dia 6 de fevereiro como a data de início da semana. Todas as outras soluções me deram o dia 13 de fevereiro, que é realmente a data de início da semana # 7.
HaukurHaf
1
Funciona como um encanto .. testou alguns dados: pastebin.com/mfx8s1vq Todo o trabalho é impecável! Obrigado Mikael!
Mittchel
1
@ RobinWassén-Andersson Ainda bem que você revisou essa pergunta: D 6 votos a mais e eu vou amarrar com a resposta "não" correta hehe.
Mikael Svenson
2
Sugestão: faça com que "ISO8601" pertença ao nome do método, pois simplesmente não existe uma resposta universal 'correta'.
Henk Holterman
1
@Uflix Não sei o suficiente sobre SQL e datas / calendários para saber - mas fazer CLR funcionará.
Mikael Svenson
36

Eu gosto da solução fornecida por Henk Holterman. Mas, para ser um pouco mais independente da cultura, você precisa obter o primeiro dia da semana para a cultura atual (nem sempre é segunda-feira):

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);
}
Thomas
fonte
Adicionei isso ao Mannex, como um método de extensão para Calendar e DateTimeFormatInfo. Também fiz referência cruzada a esta resposta para crédito.
Atif Aziz
1
Não funciona corretamente na minha máquina. Ele mostra uma data com 0010 em vez de 2010. Não sei se é um problema na estrutura .net ou nessa função. Boa tentativa, enfim ...
Eduardo Xavier
6
firstMondayé um nome de variável inválido para algo que pode não ser uma segunda-feira. =)
mflodin 12/11/2012
11

ATUALIZAR : O .NET Core 3.0 e o .NET Standard 2.1 foram fornecidos com esse tipo.

Boas notícias! Uma solicitação pull adicionandoSystem.Globalization.ISOWeek ao .NET Core foi mesclada e está programada para a versão 3.0. Espero que ele se propague para as outras plataformas .NET em um futuro não muito distante.

Você deve poder usar o ISOWeek.ToDateTime(int year, int week, DayOfWeek dayOfWeek) método para calcular isso.

Você pode encontrar o código fonte aqui .

Khellang
fonte
9

A maneira mais fácil é provavelmente encontrar a primeira segunda-feira do ano e adicionar o número relevante de semanas. Aqui está um código de exemplo. Ele assume um número de semana começando em 1, a propósito:

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);
    }
}
Jon Skeet
fonte
4
Sim, mas a primeira segunda-feira do ano poderia pertencer à semana 52 | 53 do ano anterior.
Henk Holterman
1
Depende de como você deseja definir as coisas. Infelizmente, não temos muitas informações aqui ... Espero que isso seja útil.
31909 Jon Skeet
3

Pessoalmente, eu aproveitaria as informações da cultura para obter o dia da semana e passar para o primeiro dia da semana da cultura. Não tenho certeza se estou explicando corretamente, aqui está um exemplo:

    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;
    }
Alexander Kahoun
fonte
3

usando o 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);
Simon
fonte
2

De acordo com a ISO 8601: 1988, usada na Suécia, a primeira semana do ano é a primeira semana que tem pelo menos quatro dias no ano novo.

Portanto, se sua semana começa em uma segunda-feira, a primeira quinta-feira de cada ano é dentro da primeira semana. Você pode DateAdd ou DateDiff a partir disso.

idstam
fonte
2

Supondo que o número da semana comece em 1

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

Isso deve lhe dar o primeiro dia em qualquer semana. Não testei muito, mas parece que funciona. É uma solução menor do que a maioria das outras que encontrei na Web, então queria compartilhar.

QuinnG
fonte
resposta ruim. O uso do DateTime.Parse não é necessário, pois o DateTime possui um construtor que leva ano, mês e dia. new DateTime(1,1,YearNumber)
Jamiec
Atualizado o código para criar um novo DateTime em vez de usar a análise. Era tarde. ;)
QuinnG
2

A Biblioteca de Período de Tempo gratuita para .NET inclui a semana de classe de conformidade ISO 8601 :

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

fonte
2

Este funcionou para mim, também tem a vantagem de esperar uma informação de cultura como parâmetro para testar a fórmula com diferentes culturas. Se vazio, ele obtém as informações atuais da cultura ... os valores válidos são: "it", "en-us", "fr", ... e assim por diante. O truque é subtrair o número da semana do primeiro dia do ano, que pode ser 1 para indicar que o primeiro dia está dentro da primeira semana. Espero que isto ajude.

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
Paolo Marani
fonte
2

Aqui está um método que é compatível com os números das semanas do Google Analytics e também com o mesmo esquema de numeração que usamos internamente na Intel, e que, com certeza, também é usado em muitos outros 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);
}
Samer Adra
fonte
2

Eu tentei alguns códigos acima e alguns têm pequenos erros, quando você tenta anos diferentes com diferentes dias da semana, você os vê, eu peguei o código de Jon Skeet, consertei e funcionou, código muito simples.

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
GEORGE BAXTER
fonte
1

Mudou levemente o código de Mikael Svenson. Encontrei a semana da primeira segunda-feira e alterei adequadamente o número da 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;
    }
Vitalij Roscinski
fonte
0

A semana 1 é definida como a semana que começa na segunda-feira e contém a primeira quinta-feira do ano.

amaca
fonte
1
Essa é uma definição, existem outras.
Henk Holterman
1
O padrão ISO define a semana 1 como a semana com a primeira quinta-feira do ano.
RickardN
0

Melhorei um pouco a solução de Thomas com uma substituição:

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

Caso contrário, a data era anterior a uma semana.

Obrigado Thomas, grande ajuda.

pasx
fonte
0

Usei uma das soluções, mas ela me deu resultados errados, simplesmente porque conta o domingo como o primeiro dia da semana.

Eu mudei:

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

para:

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

e agora está funcionando como um encanto.

user1564287
fonte
1
Mas isso responde à pergunta do OP? É 1/1 mais um número fixo de dias. Nenhum primeiro dia da semana conceito.
Gert Arnold
0

A solução proposta não está completa - ela funciona apenas para CalendarWeekRule.FirstFullWeek. Outros tipos de regras da semana não funcionam. Isso pode ser visto usando este caso de teste:

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);
    }
}
Colin Breame
fonte
0

Fiz uma versão refinada da solução proposta que é mais simples e parametriza o firstDayOfWeek:

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;
}
Colin Breame
fonte
0

esta é a minha solução quando queremos calcular uma data, ano, número da semana e dia da 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);
}
José Ignacio Becerra Becerra
fonte
0

Simplifiquei o código que Mikael Svensson forneceu, o que é correto para muitos países da 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));        
}
Oskar Sjöberg
fonte
0

Eu escrevi e testei o código a seguir e está funcionando perfeitamente para mim. Informe-me se alguém tiver problemas com isso. Também postei uma pergunta para obter a melhor resposta possível. Alguém pode achar ú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;
        }
Aprendiz
fonte
Isso está errado - pelo menos para muitos países europeus. GetFirstDateOfWeekByWeekNumber(2020, 29)para a semana 29 de 2020, isso retorna 07/20/2020. Mas o primeiro dia da 29ª semana foi07/13/2020
Matthias Burger
0

Atualmente, não há classe C # que lide corretamente com os números ISO 8601 semanas. Mesmo que você possa instanciar uma cultura, procurar a coisa mais próxima e corrigi-la, acho melhor fazer o cálculo completo:

    /// <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;
    }
realbart
fonte