Получить правильный номер недели на указанную дату

222

Я много гуглил и нашел много решений, но ни одно из них не дает мне правильный номер недели на 2012-12-31. Даже пример на MSDN ( ссылка ) терпит неудачу.

2012-12-31 - понедельник, поэтому это должна быть неделя 1, но каждый метод, который я пробовал, дает мне 53. Вот некоторые из методов, которые я пробовал:

Из библиотеки MDSN:

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

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

Решение 2:

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

Решение 3:

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

Обновить

Следующий метод фактически возвращает 1, когда дата 2012-12-31. Другими словами, моя проблема заключалась в том, что мои методы не соответствовали стандарту 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
источник
4
Как проходит первая неделя в конце года? Я имею в виду, я вижу, где вы это получите. Но 53 имеет смысл для меня.
benjer3
1
В моих фрагментах кода я получаю CultureInfo и прочее. Я думал, что моя программа к тому времени знает, какой календарь я использую. (Здесь, в Германии, 31 декабря 2012 года
первая
Этот код работает не совсем так, как он должен попробовать даты 31-го-2016, например, или 1-го
января
@ cavej03 31 декабря 2016 года - 52-я неделя, а GetIso8601WeekOfYear возвращает 52, поэтому я думаю, что она работает правильно.
Muflix

Ответы:

311

Как отмечено на этой странице MSDN, существует небольшая разница между неделей нумерации ISO8601 и неделей .Net.

Вы можете обратиться к этой статье в блоге MSDN для лучшего объяснения: « Формат недели года ISO 8601 в Microsoft .Net »

Проще говоря, .Net позволяет разделять недели по годам, а стандарт ISO - нет. В статье также есть простая функция для получения правильного номера недели ISO 8601 для последней недели года.

Обновление Следующий метод на самом деле возвращает 1, 2012-12-31что является правильным в 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);
} 
il_guru
источник
1
@il_guru Что если я захочу, чтобы неделя началась с воскресенья ?? Я заменяю все "понедельник" на "воскресенье" ??
User2012384
2
@ User2012384 Кстати, если вы хотите следовать стандарту ISO8601, firstDayOfWeek всегда должен быть понедельником.
Starceaker
1
@il_guru Тебе даже не нужен этот "обман", если, правда? Насколько я могу сказать, "GetWeekOfYear (time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);" работает просто отлично.
Starceaker
2
Если вы посмотрите вторую ссылку (формат ISO 8601 «Неделя года» в Microsoft .Net), вы можете найти ответ на свой вопрос непосредственно в Microsoft. Пример - понедельник, 31.12.2007: с помощью функции .Net он вернет номер 53 недели 2007 года, а для стандарта ISO - неделю 1 2008 года
il_guru
3
Я вручную проверил, чтобы это было правильно в 2012–2018 годах (при условии, что epochconverter.com верен). Мы все должны радоваться в этом году, что первая неделя 2018 года фактически началась с первого января!
Эйдан
32

Может быть более 52 недель в году. Каждый год имеет 52 полных недели + 1 или +2 (високосный год) дополнительных дней. Они компенсируют 53-ю неделю.

  • 52 недели * 7 дней = 364 дня.

Таким образом, на каждый год у вас есть хотя бы один дополнительный день. Два за високосные годы. Эти дополнительные дни считаются отдельными неделями?

Сколько недель действительно зависит от начального дня вашей недели. Давайте рассмотрим это на 2012 год.

  • США (воскресенье -> суббота): 52 недели + одна короткая 2-дневная неделя на 2012-12-30 и 2012-12-31. В результате всего 53 недели. Последние два дня этого года (воскресенье + понедельник) составляют собственную короткую неделю.

Проверьте текущие настройки культуры, чтобы увидеть, что она использует в качестве первого дня недели.

Как видите, нормально получить 53 в результате.

  • Европа (понедельник -> воскресенье): 2 января (2012-1-2) - первый понедельник, поэтому это первый день первой недели. Спросите номер недели на 1 января, и вы получите 52, так как это считается частью прошлой недели 2011 года.

Можно даже провести 54-ю неделю. Бывает каждые 28 лет, когда 1 января и 31 декабря рассматриваются как отдельные недели. Это должен быть високосный год тоже.

Например, в 2000 году было 54 недели. 1 января (суббота) был первым днем ​​недели, а 31 декабря (солнце) - вторым днем ​​недели.

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

Выходит: Год: 2012 Неделя: 54

Измените CalendarWeekRule в приведенном выше примере на FirstFullWeek или FirstFourDayWeek, и вы вернетесь 53. Давайте оставим стартовый день в понедельник, так как мы имеем дело с Германией.

Таким образом, неделя 53 начинается в понедельник 2012-12-31, длится один день, а затем останавливается.

53 является правильным ответом. Измените Культуру на Германию, если хотите попробовать.

CultureInfo cul = CultureInfo.GetCultureInfo("de-DE");
Кристоф Гирс
источник
Я знаю, что возможно, что есть 53 недели. Но когда у вас есть воскресенье (12/30) и понедельник (12/31), разве вы не считаете четверг (01/01) также частью 53-й недели в США?
Amberlamps
Я никогда не видел 54 недели!
Amberlamps
Некоторые фискальные программные пакеты имеют их.
Кристоф Гирс
Хорошо, я узнал, что это возможно в американских каландрах, но это не произойдет в немецких каландрах.
Amberlamps
1
+1 за ваши глубокие мысли по этому вопросу, но фрагмент кода в записи блога, предоставленной в другом ответе, фактически решил мою проблему.
Amberlamps
22

Это способ:

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

Наиболее важным является CalendarWeekRuleпараметр.

Смотрите здесь: https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=IT-IT&k=k(System.Globalization.CalendarWeekRule);k(TargetFrameworkMoniker-.NETFramework

daniele3004
источник
3
«Некоторые люди заметили, что Calendar.GetWeekOfYear () почти аналогичен неделе ISO 8601, когда прошли CalendarWeekRule.FirstFourDayWeek и DayOfWeek.Monday, однако это немного отличается. В частности, ISO 8601 всегда имеет 7 дней недели». Blogs.msdn. microsoft.com/shawnste/2006/01/24/…
Юха Паломяки
1
Ваша ссылка на итальянском языке
Radbyx
для нового DateTime (2000,12,31) он возвращает только 52?
Leszek P
18

Хорошие новости! Добавление запроса на добавление System.Globalization.ISOWeekв .NET Core было только что объединено и в настоящее время запланировано на выпуск 3.0. Надеемся, что он распространится на другие платформы .NET в недалеком будущем.

Тип имеет следующую подпись, которая должна охватывать большинство потребностей недели 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);
    }
}

Вы можете найти исходный код здесь .

ОБНОВЛЕНИЕ : Эти API также были включены в версию 2.1 стандарта .NET .

khellang
источник
Есть идеи, когда выйдет v 3.0?
Жак
«Мы планируем выпустить первый превью .NET Core 3 в конце этого года, а финальную версию - в 2019 году». (из сообщения в блоге «Дорожная карта» )
khellang
@khellang, как я могу получить порядковую неделю в месяце на основе даты и времени? Вместо того, чтобы получить 33-ю неделю, я бы хотел увидеть 2-ю неделю (потому что 33-я неделя - 2-я неделя августа). stackoverflow.com/questions/58039103/…
Roxy'Pro
11

Поскольку, по-видимому, не существует .Net-культуры, которая выдает правильный номер недели ISO-8601, я бы предпочел полностью обойти определение встроенной недели и выполнить расчет вручную, вместо того, чтобы пытаться исправить частично правильное значение. результат.

В итоге я выбрал следующий метод расширения:

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

Прежде всего, ((int)date.DayOfWeek + 6) % 7)определяет номер дня недели, 0 = понедельник, 6 = воскресенье.

date.AddDays(-((int)date.DayOfWeek + 6) % 7) определяет дату понедельника, предшествующего запрошенному номеру недели.

Три дня спустя - целевой четверг, который определяет год недели.

Если вы поделите (на основе нуля) число дней в году на семь (округление вниз), вы получите номер недели (на основе нуля) в году.

В c # результаты вычисления целых чисел неявно округляются.

realbart
источник
1
На данный момент это лучшее решение, но я укажу немного другой способ сделать это: DateTime основан на проалептическом григорианском календаре - то есть он расширяет григорианский календарь до года 1. 1 января 0001 года оказывается Понедельник. Ticksэто число приращений в 100 наносекунд с 1 января 0001 года. Учитывая, что в дне 86 400 секунд, мы можем написать:var thursday = date.AddDays(3 - date.Ticks / 86400 / 10_000_000 % 7); return (thursday.DayOfYear - 1) / 7 + 1;
Адриан С
5

В .NET 3.0 и более поздних версиях вы можете использовать ISOWeek.GetWeekOfDate-Method .

Обратите внимание, что год в формате числа год + неделя может отличаться от года из- DateTimeза недель, пересекающих границу года.

johh
источник
3

C # в порт Powershell из приведенного выше кода из 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
источник
1
Не могли бы вы объяснить свой код? Ответы только на код, как правило, неприемлемы в StackOverflow, и поэтому могут быть удалены.
Вай Ха Ли
@ Вай Ха Ли код уже объяснен. Смотрите пост выше от il_guru. Я перенес его код в powershell, чтобы другие люди могли использовать его в powershell, потому что пока нет хорошего решения в powershell.
Райнер
Если это то, что вы сделали, вы должны указать автора в самом ответе. Ответ, который вы перенесли, содержит лучшие комментарии, которые вы, вероятно, удалили. Кроме того, это не ответ на вопрос, поскольку в вопросе не упоминалась PowerShell.
Вай Ха Ли
1
Я сделал теперь @ Вай Ха Ли. Вопрос уже помечен как ответ, так что это будет то же решение, написанное на другом языке. Я сделал одолжение людям, которые ищут решение на языке Powershell (я искал решение для Powershell, но нашел решение в C #). Идея остается прежней, и автор получает кредит за это.
Райнер
2

Самый простой способ определить стиль номера недели ISO 8601 с помощью c # и класса DateTime.

Спросите: четверг года - это четверг этой недели. Ответ равен номеру требуемой недели.

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
источник
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;

Это хорошо работает как для культур США и России. ISO 8601 также будет правильным, потому что русская неделя начинается в понедельник.

Донских андрей
источник
1

Вот расширенная версия и обнуляемая версия ответа il_guru .

Расширение:

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

Nullable:

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

Обычаи:

new DateTime(2019, 03, 15).GetIso8601WeekOfYear(); //returns 11
((DateTime?) new DateTime(2019, 03, 15)).GetIso8601WeekOfYear(); //returns 11
((DateTime?) null).GetIso8601WeekOfYear(); //returns null
Jogge
источник
0

Вопрос в том, как определить, будет ли неделя в 2012 году или в 2013 году. Я полагаю, что вы предполагаете, что, поскольку в 2013 году 6 дней недели, эта неделя должна быть отмечена как первая неделя 2013 года.

Не уверен, что это правильный путь. Эта неделя началась в 2012 году (в понедельник 31 декабря), поэтому она должна быть помечена как последняя неделя 2012 года, следовательно, она должна быть 53-й 2012 года. Первая неделя 2013 года должна начаться в понедельник 7-го.

Теперь вы можете обрабатывать конкретный случай крайних недель (первой и последней недели года), используя информацию о дне недели. Все зависит от вашей логики.

Сами Арус
источник
Хорошо, я понимаю вашу точку зрения. Дело в том, что когда я использую 2013/01/07 в любом из своих методов, это говорит о неделе 2. Итак, 2012/12/31 - это неделя 53, а 2013/01/07 - это неделя 2. Вы можете подумать, что недели нет 1 2013. Но когда я пробую 2013/01/01, это говорит о первой неделе.
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));      
Имран
источник
0

Основываясь на ответе il_guru, я создал эту версию для своих нужд, которая также возвращает компонент year.

    /// <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
источник
0

Эти два метода помогут, предполагая, что наша неделя начинается в понедельник

/// <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
источник
-1

Год имеет 52 недели и 1 день или 2 в случае коленного года (52 x 7 = 364). 2012-12-31 - это 53-я неделя, неделя, на которую уйдет всего 2 дня, потому что 2012 год - это круговой год.

CarlosJ
источник
Это неверно Первый день недели года может приходиться на любой день недели, в зависимости от того, как вы считаете недели, в году может быть 54-я неделя.
krowe2
-2
public int GetWeekNumber()
{
   CultureInfo ciCurr = CultureInfo.CurrentCulture;
   int weekNum = ciCurr.Calendar.GetWeekOfYear(DateTime.Now, 
   CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
   return weekNum;
}
Мегнатх Дас
источник