8

I need to return year and week of a given date. Sounds simple. But to be right 2012-01-01 have to return 2011-52, because week 1 in 2012 starts January 2th.

To find the week, I use:

GregorianCalendar calw = new GregorianCalendar(GregorianCalendarTypes.Localized);
return calw.GetWeekOfYear(DateTime.Parse("2012-01-01"), CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday).ToString();

this return 52. (correct)

But how do I get the Year?

edit:

With the help from here: http://codebetter.com/petervanooijen/2005/09/26/iso-weeknumbers-of-a-date-a-c-implementation/

This seems to work:

 private int weekYear(DateTime fromDate)
    {
        GregorianCalendar cal = new GregorianCalendar(GregorianCalendarTypes.Localized);
        int week = weekNumber(fromDate);
        int month = cal.GetMonth(fromDate);
        int year = cal.GetYear(fromDate);

        //week starts after 31st december
        if (week > 50 && month == 1)
            year = year - 1;
        //week starts before 1st January
        if (week < 5 && month == 12)
            year = year + 1;

        return year;
    }
private int weekNumber(DateTime fromDate)
{
    // Get jan 1st of the year
    DateTime startOfYear = fromDate.AddDays(-fromDate.Day + 1).AddMonths(-fromDate.Month + 1);
    // Get dec 31st of the year
    DateTime endOfYear = startOfYear.AddYears(1).AddDays(-1);
    // ISO 8601 weeks start with Monday 
    // The first week of a year includes the first Thursday 
    // DayOfWeek returns 0 for sunday up to 6 for saterday
    int[] iso8601Correction = { 6, 7, 8, 9, 10, 4, 5 };
    int nds = fromDate.Subtract(startOfYear).Days + iso8601Correction[(int)startOfYear.DayOfWeek];
    int wk = nds / 7;
    switch (wk)
    {
        case 0:
            // Return weeknumber of dec 31st of the previous year
            return weekNumber(startOfYear.AddDays(-1));
        case 53:
            // If dec 31st falls before thursday it is week 01 of next year
            if (endOfYear.DayOfWeek < DayOfWeek.Thursday)
                return 1;
            else
                return wk;
        default: return wk;
    }
}
user408698
  • 141
  • 1
  • 2
  • 7

5 Answers5

11

Noda Time handles this for you very easily:

Noda Time v1.x

using System;
using NodaTime;

public class Test
{
    static void Main()
    {
        LocalDate date = new LocalDate(2012, 1, 1);
        Console.WriteLine($"WeekYear: {date.WeekYear}");             // 2011
        Console.WriteLine($"WeekOfWeekYear: {date.WeekOfWeekYear}"); // 52
    }
}

Noda Time v2.x

using System;
using NodaTime;
using NodaTime.Calendars;

public class Test
{
    static void Main()
    {
        LocalDate date = new LocalDate(2012, 1, 1);
        IWeekYearRule rule = WeekYearRules.Iso;
        Console.WriteLine($"WeekYear: {rule.GetWeekYear(date)}"); // 2011
        Console.WriteLine($"WeekOfWeekYear: {rule.GetWeekOfWeekYear(date)}"); // 52
    }
}

That's using the ISO calendar system where the week year starts in the first week with at least 4 days in that year. (Like CalendarWeekRule.FirstFourDayWeek.) If you want a different calendar system, specify it in the LocalDate constructor. Week year rules are handled slightly differently between 1.x and 2.x.

EDIT: Note that this gives the right value for both this situation (where the week-year is less than the calendar year) and the situation at the other end of the year, where the week-year can be more than the calendar year. For example, December 31st 2012 is in week 1 of week-year 2013.

That's the beauty of having a library do this for you: its job is to understand this sort of thing. Your code shouldn't have to worry about it. You should just be able to ask for what you want.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Hi Jon. Some years(ex: 2009, 2015, 2020, 2026...) have 53 weeks. Will "2015-12-30" give WeekOfWeekYear as 53? – Haritha Nov 29 '13 at 11:32
  • Yes. I tested. And it works for these special years too. I searched a lot in internet and this is the only correct answer I got. – Haritha Dec 02 '13 at 05:40
  • @Haritha: Sorry not to have replied before - I didn't see your comment for some reason. Glad it works as intended though :) – Jon Skeet Dec 02 '13 at 06:42
  • Thanks. But I have another problem. As the definition of `ISO8601`, the first week of the year is the week with the year's first Thursday in it. `WeekOfWeekYear` gives that. But I need the first week of the year as the week with the year's first **Wednesday** in it. Can I do it with NodaTime? – Haritha Dec 03 '13 at 11:22
  • @Haritha: Yes - you can call `CalendarSystem.GetGregorianCalendar(5)` to say that you want to have at least 5 days in the week rather than 4. – Jon Skeet Dec 03 '13 at 11:29
  • @JonSkeet Great library, thanks; I've been searching for something like that. But in my case it was out of the question to include any libriaries, as I had to deliver plain-text .ps1 script, so I just had to come up with my own solution and make it MY job to understand this :). – AdamL Jan 08 '15 at 20:24
  • In 2.0.0 version API has changed: it's now `IWeekYearRule.GetWeekYear` / `GetWeekOfWeekYear`. Standard rules are available in `WeekYearRules` – arghtype Apr 20 '17 at 18:30
4

You can get the weeknumber according to the CalendarWeekRule in this way:

var d = new DateTime(2012, 01, 01);
System.Globalization.CultureInfo cul = System.Globalization.CultureInfo.CurrentCulture;
var firstDayWeek = cul.Calendar.GetWeekOfYear(
    d,
    System.Globalization.CalendarWeekRule.FirstDay,
    DayOfWeek.Monday);
int weekNum = cul.Calendar.GetWeekOfYear(
    d,
    System.Globalization.CalendarWeekRule.FirstFourDayWeek,
    DayOfWeek.Monday);
int year = weekNum >= 52 && d.Month == 1 ? d.Year - 1 : d.Year;

You probably want to compare CalendarWeekRule.FirstDay with CalendarWeekRule.FirstFourDayWeek. On this way you get the weeknumber and the year (DateTime.Year-1 if they differ).

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • 2
    This will fail for (say) December 31st 2012, where the week year is 2013. – Jon Skeet Apr 29 '12 at 12:05
  • No, that's still not going to give 2013 for December 31st 2012, is it? It's still *either* going to give `d.Year - 1` or `d.Year`. Sometimes it needs to give `d.Year + 1`. This sort of detail is why I'd prefer to trust a dedicated library :) – Jon Skeet Apr 29 '12 at 12:30
  • I think you need `(weekNum == 52 || weekNum == 53)` for the year calculation, because this will fail for `2016-01-01` since 2015 has in fact 53 weeks – Ocaso Protal Aug 14 '17 at 12:11
2

That is just an edge case which you will have to add special code for. Get the year from the date string and then if the week = 52 and the month = 1 then subtract one from the year.

John Koerner
  • 37,428
  • 8
  • 84
  • 134
  • Your right. Several thinks can go wrong. I would prefer not to add a library just for this. But unless someone else come up with something reliable, I add the Library you suggest. – user408698 Apr 29 '12 at 12:56
  • HI. What about years which have 53 years? – Haritha Nov 29 '13 at 10:53
1

I have solving similar problem where the result should be in "YYYYWW" format. I wanted avoid hardcoded dates and using 3rd party libraries.

My test case was date 1.1.2017 which should return week 201652 (Iso YearWeek)

To get week number I have used thread: Get the correct week number of a given date which returns week number without the year. Finally the correct year I got from Monday(first day of iso week) of required date:

    // returns only week number
    // from [Get the correct week number of a given date] thread
    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
        var week = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
        return week;
    }

    // returns int YearWeek in format "YYYYWW"
    public static int GetIso8601YearWeekOfYear(DateTime time)
    {
        var delta = (-((time.DayOfWeek - CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek + 7) % 7));
        var firstDayofWeek = time.AddDays(delta); // takeMonday
        var week = GetIso8601WeekOfYear(time);
        var yearWeek = (firstDayofWeek.Year * 100) + week;
        return yearWeek;
    }
Community
  • 1
  • 1
0

In my approach I'm taking advantage of the fact, that GetWeekOfYear() displays a correct ISO-8601 week number for days with the same year as Thursday of the same week. So I look for Thursday that belongs to the same week as a given date, and then call GetWeekOfYear() on it.

I can't do that trick to get a correct year, as there's no iso8601-compliant method for this, so I make a year adjustment if Thursday belongs to a different year than a given date.

The solution is basically a three-liner:

using System.Globalization;

namespace TESTS
{
    class Program
    {
        static void Main(string[] args)
        {

            //sample dates with correct week numbers in comments:
            string[] dats = new string[] { 
                 "2011-12-31","2012-01-01"  //1152
                ,"2012-12-31","2013-01-01"  //1301
                ,"2013-12-31","2014-01-01"  //1401
                ,"2014-12-31","2015-01-01"  //1501
                ,"2015-12-31", "2016-01-01" //1553
            };

            foreach (string str in dats)
            {
                Console.WriteLine("{0} {1}", str, GetCalendarWeek(DateTime.Parse(str)));
            }

            Console.ReadKey();
    }


       public static int GetCalendarWeek(DateTime dat)
        {
            CultureInfo cult = System.Globalization.CultureInfo.CurrentCulture;

            // thursday of the same week as dat.
            // value__ for Sunday is 0, so I need (true, not division remainder %) mod function to have values 0..6 for monday..sunday
            // If you don't like casting Days to int, use some other method of getting that thursday
            DateTime thursday = dat.AddDays(mod((int)DayOfWeek.Thursday-1,7) - mod((int)dat.DayOfWeek-1,7));
            //week number for thursday:
            int wk = cult.Calendar.GetWeekOfYear(thursday, cult.DateTimeFormat.CalendarWeekRule, cult.DateTimeFormat.FirstDayOfWeek);
            // year adjustment - if thursday is in different year than dat, there'll be -1 or +1:
            int yr = dat.AddYears(thursday.Year-dat.Year).Year;

            // return in yyww format:
            return 100 * (yr%100) + wk;
        }

        // true mod - helper function (-1%7=-1, I need -1 mod 7 = 6):
        public static int mod(int x, int m)
        {
            return (x % m + m) % m;
        }


}
AdamL
  • 12,421
  • 5
  • 50
  • 74