Obter o número da semana correto de uma determinada data

222

Pesquisei bastante no Google e encontrei muitas soluções, mas nenhuma delas me deu o número de semana correto para o 31/12/2012. Até o exemplo no MSDN ( link ) falha.

31/12/2012 é segunda-feira, portanto deve ser a Semana 1, mas todos os métodos que tentei me deram 53. Aqui estão alguns dos métodos que tentei:

Na biblioteca MDSN:

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

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

Solução 2:

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

Solução 3:

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

Atualizar

O método a seguir retorna 1 quando a data é 31/12/2012. Em outras palavras, meu problema era que meus métodos não estavam seguindo o padrão 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);
}
Amberlamps
fonte
4
Como está a semana 1 no final do ano? Quero dizer, eu vejo onde você conseguiu. Mas 53 faz sentido para mim.
benjer3
1
Nos meus trechos de código, recebo o CultureInfo e outras coisas. Eu pensei que meu programa já sabia então qual calendário eu estou usando. (Aqui na Alemanha 31 de Dezembro de 2012 é na semana 1 de 2013)
Amberlamps
Este código não funciona muito como deveria tentar datas 31-Dez-2016, por exemplo, ou 1-Jan-2016
cavej03
@ cavej03 31-dec-2016 é a semana 52 e o GetIso8601WeekOfYear retorna 52, então eu acho que funciona corretamente.
Muflix

Respostas:

311

Conforme observado nesta página do MSDN, há uma pequena diferença entre a ISO8601 e a numeração da semana .Net.

Você pode consultar este artigo no Blog do MSDN para obter uma explicação melhor: " Formato da semana do ano ISO 8601 no Microsoft .Net "

Simplificando, o .Net permite que as semanas sejam divididas por anos, enquanto o padrão ISO não. No artigo, há também uma função simples para obter o número de semana ISO 8601 correto para a última semana do ano.

Atualização O método a seguir realmente retorna 1 para o 2012-12-31que está correto na ISO 8601 (por exemplo, Alemanha).

// 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
fonte
1
@il_guru E se eu desejar que a semana comece com domingo? Eu substituo todos os "Monday" por "Sunday" ??
User2012384
2
@ User2012384 fyi, se você deseja seguir o padrão ISO8601, o firstDayOfWeek deve sempre ser segunda-feira.
Starceaker
1
@il_guru Você nem precisa dessa "trapaça" se precisar? Tanto quanto eu posso dizer "GetWeekOfYear (time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);" funciona muito bem.
Starceaker
2
Se você procurar o segundo link (formato da semana do ano ISO 8601 no Microsoft .Net), poderá encontrar a resposta para sua pergunta diretamente da Microsoft. Um exemplo é segunda-feira 31/12/2007: com a função Net ele retornará semana número 53 de 2007, enquanto para o padrão ISO é semana número 1 de 2008
il_guru
3
Eu verifiquei manualmente que isso está correto nos anos de 2012 a 2018 (assumindo que epochconverter.com esteja correto). Todos devemos nos alegrar este ano, que a semana 1 de 2018 realmente começou no primeiro de janeiro de uma vez!
Aidan
32

Pode haver mais de 52 semanas em um ano. Cada ano tem 52 semanas completas + 1 ou +2 (ano bissexto) dias extras. Eles compensam a 53ª semana.

  • 52 semanas * 7 dias = 364 dias.

Portanto, para cada ano você tem pelo menos um por dia extra. Dois por anos bissextos. Esses dias extras são contados como semanas separadas?

Quantas semanas existem realmente depende do dia de início da sua semana. Vamos considerar isso para 2012.

  • EUA (domingo -> sábado): 52 semanas + uma semana curta de 2 dias para 2012-12-30 e 2012-12-31. Isso resulta em um total de 53 semanas. Os últimos dois dias deste ano (domingo + segunda-feira) compõem sua própria semana curta.

Verifique as configurações da sua cultura atual para ver o que ela usa como o primeiro dia da semana.

Como você vê, é normal obter 53 como resultado.

  • Europa (segunda-feira -> domingo): 2 de janeiro (2012-1-2) é a primeira segunda-feira, portanto, este é o primeiro dia da primeira semana. Peça o número da semana para 1º de janeiro e você retornará 52, pois é considerado parte da semana de 2011 da semana passada.

É até possível ter uma 54ª semana. Acontece a cada 28 anos, quando 1 de janeiro e 31 de dezembro são tratados como semanas separadas. Também deve ser um ano bissexto.

Por exemplo, o ano de 2000 teve 54 semanas. 1º de janeiro (sábado) foi o primeiro dia da semana e 31 de dezembro (sol) foi o segundo dia da 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: Ano: 2012 Semana: 54

Altere CalendarWeekRule no exemplo acima para FirstFullWeek ou FirstFourDayWeek e você receberá 53. Vamos manter o dia de início na segunda-feira, pois estamos lidando com a Alemanha.

Assim, a semana 53 começa na segunda-feira 2012-12-31, dura um dia e depois para.

53 é a resposta correta. Mude a cultura para alemanha, se quiser tentar.

CultureInfo cul = CultureInfo.GetCultureInfo("de-DE");
Christophe Geers
fonte
Eu sei que é possível que haja 53 semanas. Mas quando você tem domingo (30/12) e segunda-feira (31/12), você não conta a terça-feira (01/01) também como parte da 53ª semana nos EUA?
Amberlamps
Eu nunca vi uma 54ª semana!
Amberlamps
Certos pacotes de software fiscal os possuem.
Christophe Geers
Ok, descobri que isso é possível em calandras americanas, mas isso não acontecerá em calandras alemãs.
Amberlamps
1
+1 por seus pensamentos profundos sobre o problema, mas o snippet de código na entrada do blog fornecido em uma resposta diferente realmente resolveu meu problema.
Amberlamps
22

Este é o caminho:

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

O mais importante para é o CalendarWeekRuleparâmetro

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

daniele3004
fonte
3
"Várias pessoas notaram que Calendar.GetWeekOfYear () é quase como a semana ISO 8601, quando passou no CalendarWeekRule.FirstFourDayWeek e DayOfWeek.Monday, no entanto, é um pouco diferente. Especificamente, o ISO 8601 sempre tem 7 semanas por dia." Blogs.msdn. microsoft.com/shawnste/2006/01/24/…
Juha Palomäki 31/01
1
O seu link está em italiano
radbyx 26/06/19
para o novo DateTime (2000, 12, 31) ele retorna apenas 52?
Leszek P
18

Boas notícias! A solicitação de recebimento adicionando System.Globalization.ISOWeekpara .NET Núcleo foi apenas fundiram e está previsto para a versão 3.0. Espero que ele se propague para as outras plataformas .NET em um futuro não muito distante.

O tipo tem a seguinte assinatura, que deve cobrir a maioria das necessidades da 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);
    }
}

Você pode encontrar o código fonte aqui .

ATUALIZAÇÃO : essas APIs também foram incluídas na versão 2.1 do .NET Standard .

Khellang
fonte
Alguma idéia de quando a versão 3.0 será lançada?
Jacques
"Estamos planejando lançar uma primeira prévia do .NET Core 3 ainda este ano e a versão final em 2019". (da postagem do blog de roadmap )
khellang
@khellang como eu poderia ter uma semana ordinal em um mês com base na data e hora? Em vez de receber a semana 33, eu gostaria de ver a semana 2 (porque a semana 33 é a semana 2 em agosto). stackoverflow.com/questions/58039103/…
Roxy'Pro
11

Como não parece haver uma cultura .Net que produz o número de semana ISO-8601 correto, prefiro ignorar completamente a determinação da semana interna e fazer o cálculo manualmente, em vez de tentar corrigir uma correção parcialmente correta. resultado.

Acabei com o seguinte método de extensão:

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

Primeiro, ((int)date.DayOfWeek + 6) % 7)determina o número do dia da semana, 0 = segunda-feira, 6 = domingo.

date.AddDays(-((int)date.DayOfWeek + 6) % 7) determina a data da segunda-feira antes do número da semana solicitado.

Três dias depois é a meta na quinta-feira, que determina em que ano a semana está.

Se você dividir o número do dia (com base em zero) no ano por sete (arredondar para baixo), obterá o número da semana (com base em zero) no ano.

Em c #, os resultados do cálculo inteiro são arredondados para baixo implicitamente.

realbart
fonte
1
Esta é a melhor solução, de longe, mas vou apontar uma maneira ligeiramente diferente de fazê-lo: o DateTime é baseado no calendário gregoriano pró-séptico - ou seja, estende o calendário gregoriano de volta ao ano 1. O 1º de janeiro de 0001 é um Segunda-feira. Ticksé o número de incrementos de 100 nanossegundos desde 1º de janeiro de 0001. Dado que existem 86.400 segundos em um dia, podemos escrever:var thursday = date.AddDays(3 - date.Ticks / 86400 / 10_000_000 % 7); return (thursday.DayOfYear - 1) / 7 + 1;
Adrian S
5

No .NET 3.0 e posterior, você pode usar o ISOWeek.GetWeekOfDate-Method .

Observe que o ano no formato de número ano + semana pode ser diferente do ano DateTimedevido às semanas que cruzam o limite do ano.

johh
fonte
3

C # para a porta do PowerShell do código acima 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
fonte
1
Você poderia explicar seu código? As respostas somente de código geralmente não são aceitáveis ​​no StackOverflow e, como tal, podem ser excluídas.
Wai Ha Lee
@Wai Ha Lee, o código já está explicado. Veja post acima de il_guru. Eu carreguei o código dele para o PowerShell, para que outras pessoas possam usá-lo no PowerShell porque até agora não há uma boa solução no PowerShell.
Rainer
Se foi o que você fez, credite o autor original na resposta. A resposta que você portou tem melhores comentários que você provavelmente removeu. Além disso, essa não é uma resposta para a pergunta, pois ela não mencionou o PowerShell.
Wai Ha Lee
1
Eu fiz agora @Wai Ha Lee. A pergunta já está marcada como respondida, portanto, essa seria a mesma solução escrita em outro idioma. Fiz um favor às pessoas que procuram uma solução na linguagem Powershell (estava procurando uma solução em Powershell, mas o que encontrei foi uma solução em C #). A idéia permanece a mesma e o autor recebe o crédito por ela.
Rainer
2

A maneira mais fácil de determinar o estilo ISO 8601 do número da semana usando c # e a classe DateTime.

Pergunte o seguinte: a quinta-feira do ano é a quinta-feira desta semana. A resposta é igual ao número da semana desejado.

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;
user6887101
fonte
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 bem para as culturas dos EUA e da Rússia. A ISO 8601 também estará correta, porque a semana russa começa na segunda-feira.

Donskikh Andrei
fonte
1

Aqui está uma versão de extensão e uma versão anulável da resposta de il_guru .

Extensão:

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);
}

Anulável:

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
Jogge
fonte
0

A pergunta é: como você define se uma semana é em 2012 ou em 2013? Sua suposição, eu acho, é que, como 6 dias da semana são em 2013, essa semana deve ser marcada como a primeira semana de 2013.

Não tenho certeza se este é o caminho certo a seguir. A semana começou em 2012 (segunda-feira, 31 de dezembro), portanto deve ser marcada como a última semana de 2012, portanto deve ser a 53ª de 2012. A primeira semana de 2013 deve começar na segunda-feira, dia 7.

Agora, você pode lidar com o caso específico de semanas de ponta (primeira e última semana do ano) usando as informações do dia da semana. Tudo depende da sua lógica.

Samy Arous
fonte
Ok, eu posso entender o seu ponto. O problema é que quando eu uso 2013/01/07 em qualquer um dos meus métodos, é a semana 2. Então, 31/12/2012 é a semana 53 e 07/07/2013 é a semana 2. Você pode pensar que não há uma semana 1 de 2013. Mas quando eu tento 2013/01/01 diz 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
fonte
0

Com base na resposta de il_guru, criei esta versão para minhas próprias necessidades, que também retorna o componente do ano.

    /// <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
fonte
0

Esses dois métodos ajudarão, supondo que nossa semana comece na segunda-feira

/// <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
fonte
-1

Um ano tem 52 semanas e 1 dia ou 2 no caso de um ano colo (52 x 7 = 364). 31/12/2012 seria a semana 53, uma semana que teria apenas dois dias, porque 2012 é um ano de volta.

CarlosJ
fonte
Isto está incorreto. O primeiro dia da semana do ano pode cair em qualquer dia da semana, dependendo de como você conta as semanas, o ano pode ter uma 54ª semana.
krowe2
-2
public int GetWeekNumber()
{
   CultureInfo ciCurr = CultureInfo.CurrentCulture;
   int weekNum = ciCurr.Calendar.GetWeekOfYear(DateTime.Now, 
   CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
   return weekNum;
}
Meghnath Das
fonte