Calculate date from week number

Viewed 165144

Anyone know an easy way to get the date of the first day in the week (monday here in Europe). I know the year and the week number? I'm going to do this in C#.

26 Answers

Note

The below answer uses the .NET Calendar rules. It does not promise ISO8601 conformance. See some of the other answers here when you need that. Week numbering is a mess, always try to find out what rules you need to follow first.


The code below correctly puts the start of week 1, 2009 at 29-12-2008. The CalendarWeekRule probably should be a parameter.

Note that the weekNum should be >= 1

static DateTime FirstDateOfWeek(int year, int weekNum, CalendarWeekRule rule)
{
    Debug.Assert(weekNum >= 1);

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

    int daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
    DateTime firstMonday = jan1.AddDays(daysOffset);
    Debug.Assert(firstMonday.DayOfWeek == DayOfWeek.Monday);

    var cal = CultureInfo.CurrentCulture.Calendar;
    int firstWeek = cal.GetWeekOfYear(firstMonday, rule, DayOfWeek.Monday);

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

    DateTime result = firstMonday.AddDays(weekNum * 7);

    return result;
}

I like the solution provided by Henk Holterman. But to be a little more culture independent, you have to get the first day of the week for the current culture ( it's not always monday ):

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

UPDATE: .NET Core 3.0 and .NET Standard 2.1 has shipped with this type.

Good news! A pull request adding System.Globalization.ISOWeek to .NET Core was just merged and is currently slated for the 3.0 release. Hopefully it will propagate to the other .NET platforms in a not-too-distant future.

You should be able to use the ISOWeek.ToDateTime(int year, int week, DayOfWeek dayOfWeek) method to calculate this.

You can find the source code here.

The easiest way is probably to find the first Monday of the year, and then add the relevant number of weeks. Here's some sample code. It assumes a week number starting at 1, by the way:

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

Personally I'd take advantage of the culture info to get the day of the week and loop down to the culture's first day of the week. I'm not sure if I'm explaining it properly, here's an example:

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

According to ISO 8601:1988 that is used in Sweden the first week of the year is the first week that has at least four days within the new year.

So if your week starts on a Monday the first Thursday any year is within the first week. You can DateAdd or DateDiff from that.

As suggested by khellang (sorry but I cannot add comments), with .NET Core is quite simple. This is my piece of code:

var today = DateTime.Today;
CultureInfo myCI = new CultureInfo("it-IT");
Calendar myCal = myCI.Calendar;
var weekNumber = myCal.GetWeekOfYear(today, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday);
var firstWeekDay = ISOWeek.ToDateTime(today.Year, weekNumber, DayOfWeek.Monday);

Week 1 is defined as being the week that starts on a Monday and contains the first Thursday of the year.

One of the biggest problems I found was to convert from weeks to dates, and then from dates to weeks.

The main problem is when trying to get the correct week year from a date that belongs to a week of the previous year. Luckily System.Globalization.ISOWeek.GetYear handles this.

Here is my solution:

public class WeekOfYear
{
    public static (int Year, int Week) DateToWeekOfYear(DateTime date) =>
        (ISOWeek.GetYear(date), ISOWeek.GetWeekOfYear(date));

    public static bool ValidYearAndWeek(int year, int week) =>
           year >= 1 && year <= 9999 && week >= 1 && week <= 53 // bounds of year/week
        && !(year <= 1 && week <= 1) && !(year >= 9999 && week >= 53); // bounds of DateTime

    public int Year { get; }
    public int Week { get; }
    public virtual DateTime StartOfWeek { get; protected set; }
    public virtual DateTime EndOfWeek { get; protected set; }

    public virtual IEnumerable<DateTime> DaysInWeek =>
        Enumerable.Range(1, 10).Select(i => StartOfWeek.AddDays(i));

    public WeekOfYear(int year, int week)
    {
        if (!ValidYearAndWeek(year, week))
            throw new ArgumentException($"DateTime can't represent {week} of year {year}.");

        Year = year;
        Week = week;
        StartOfWeek = ISOWeek.ToDateTime(year, week, DayOfWeek.Monday);
        EndOfWeek = ISOWeek.ToDateTime(year, week, DayOfWeek.Sunday).AddDays(1).AddTicks(-1);
    }

    public WeekOfYear((int Year, int Week) week) : this(week.Year, week.Week) { }
    public WeekOfYear(DateTime date) : this(DateToWeekOfYear(date)) { }
}

The second biggest problem was the preference for weeks starting on Sundays in the US.

The solution I cam up with subclasses WeekOfYear from above, and manages the offset of the in the constructor (which converts week to dates) and DateToWeekOfYear (which converts from date to week).

public class UsWeekOfYear : WeekOfYear
{
    public static new (int Year, int Week) DateToWeekOfYear(DateTime date)
    {
        // if date is a sunday, return the next week
        if (date.DayOfWeek == DayOfWeek.Sunday) date = date.AddDays(1);
        return WeekOfYear.DateToWeekOfYear(date);
    }

    public UsWeekOfYear(int year, int week) : base(year, week)
    {
        StartOfWeek = ISOWeek.ToDateTime(year, week, DayOfWeek.Monday).AddDays(-1);
        EndOfWeek = ISOWeek.ToDateTime(year, week, DayOfWeek.Sunday).AddTicks(-1);
    }

    public UsWeekOfYear((int Year, int Week) week) : this(week.Year, week.Week) { }
    public UsWeekOfYear(DateTime date) : this(DateToWeekOfYear(date)) { }
}

Here is some test code:

public static void Main(string[] args)
{
    Console.WriteLine("== Last Week / First Week");
    Log(new WeekOfYear(2020, 53));
    Log(new UsWeekOfYear(2020, 53));

    Log(new WeekOfYear(2021, 1));
    Log(new UsWeekOfYear(2021, 1));

    Console.WriteLine("\n== Year Crossover (iso)");
    var start = new DateTime(2020, 12, 26);
    var i = 0;
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2020-12-26 - Sat
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2020-12-27 - Sun
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2020-12-28 - Mon
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2020-12-29 - Tue
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2020-12-30 - Wed
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2020-12-30 - Thu
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2021-01-01 - Fri
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2021-01-02 - Sat
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2021-01-03 - Sun
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2021-01-04 - Mon
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2021-01-05 - Tue
    Log(start.AddDays(i), new WeekOfYear(start.AddDays(i++))); // 2021-01-06 - Wed

    Console.WriteLine("\n== Year Crossover (us)");
    i = 0;
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2020-12-26 - Sat
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2020-12-27 - Sun
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2020-12-28 - Mon
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2020-12-29 - Tue
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2020-12-30 - Wed
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2020-12-30 - Thu
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2021-01-01 - Fri
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2021-01-02 - Sat
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2021-01-03 - Sun
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2021-01-04 - Mon
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2021-01-05 - Tue
    Log(start.AddDays(i), new UsWeekOfYear(start.AddDays(i++))); // 2021-01-06 - Wed

    var x = new UsWeekOfYear(2020, 53) as WeekOfYear;
}

public static void Log(WeekOfYear week)
{
    Console.WriteLine($"{week} - {week.StartOfWeek:yyyy-MM-dd} ({week.StartOfWeek:ddd}) - {week.EndOfWeek:yyyy-MM-dd} ({week.EndOfWeek:ddd})");
}

public static void Log(DateTime date, WeekOfYear week)
{
    Console.WriteLine($"{date:yyyy-MM-dd (ddd)} - {week} - {week.StartOfWeek:yyyy-MM-dd (ddd)} - {week.EndOfWeek:yyyy-MM-dd (ddd)}");
}
Related