0

I have to show a date's calendar week using the same numbering used in Outlook.
The numbering has to follow the italian (it-IT) culture.
The Outlook calendar is configured using Monday as first day of week and "1st of January is always on Week 1".
An example of another calendar using the same numbering logic is here (maybe you need to turn week numbers on).

I've tried different methods but I don't get the correct week number with some dates.
In particular, I have tried using:

  • Calendar returned from CultureInfo.GetCulture("it-IT")
  • GregorianCalendar
  • ISOWeek

Some of the dates I tried are:

  • 31st December 200
  • 29th March 2021
  • 31 December 2012
  • 21 December 1992
  • 30 December 2019

The only reliable way I found to get the correct week number is to calculate it myself, but I don't really like the idea to have to depend on my date calculations code (I can't be 100% sure to have found the correct logic has I can't test every possible date).

There is a program below showing the various results for every method I tried and how I manually calculate week numbers.
You can also try it online here.

The output of the example (as an image to show the various colors):
Example program output

So the question is: is there a built-in way to get Week numbers from a date using the same numbering logic used by Outlook ?

EDIT:

I've already tried to use CalendarWeekRule.FirstFourDayWeek and CalendarWeekRule.FirstFullWeek

The example program I mentioned:

    public sealed class Program
    {
        private static Calendar italianCalendar = CultureInfo.GetCultureInfo("it-IT").Calendar;
        private static Calendar plainGregorianCalendar = new GregorianCalendar();

        public static void Main()
        {
            var _31stOfDecember2000 = new DateTime(2000, 12, 31);
            var _29thOfMarch2021 = new DateTime(2021, 3, 29);
            var _31stOfDecember2012 = new DateTime(2012, 12, 31);
            var _21stDecember1992 = new DateTime(1992, 12, 21);
            var _30thDecember2019 = new DateTime(2019, 12, 30);
            var _1stOfJanuary2013 = new DateTime(2013, 1, 1);

            PrintWeek(_31stOfDecember2000, 53);
            PrintWeek(_29thOfMarch2021, 14);
            PrintWeek(_31stOfDecember2012, 1);
            PrintWeek(_21stDecember1992, 52);
            PrintWeek(_30thDecember2019, 1);
            PrintWeek(_1stOfJanuary2013, 1);

            Console.ReadKey();
        }

        private static void PrintWeek(DateTime date, int expectedWeek)
        {
            var isoWeek = ISOWeek.GetWeekOfYear(date);
            var italianCalendarWeek = italianCalendar.GetWeekOfYear(date, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
            var gregorianCalendarWeek = plainGregorianCalendar.GetWeekOfYear(date, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
            var manuallyCalculatedWeek = CalculateWeek(date);

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Date: {0:yyyy MMMM dd} - Expected week {1}", date, expectedWeek);

            Console.ForegroundColor = isoWeek == expectedWeek ? ConsoleColor.Green : ConsoleColor.Red;
            Console.WriteLine("ISO Week: {0}", isoWeek);

            Console.ForegroundColor = italianCalendarWeek == expectedWeek ? ConsoleColor.Green : ConsoleColor.Red;
            Console.WriteLine("Italian calendar Week: {0}", italianCalendarWeek);

            Console.ForegroundColor = gregorianCalendarWeek == expectedWeek ? ConsoleColor.Green : ConsoleColor.Red;
            Console.WriteLine("Gregorian calendar Week: {0}", gregorianCalendarWeek);

            Console.ForegroundColor = manuallyCalculatedWeek == expectedWeek ? ConsoleColor.Green : ConsoleColor.Red;
            Console.WriteLine("Manually calculated Week: {0}", manuallyCalculatedWeek);

            Console.WriteLine();
        }

        public static int CalculateWeek(DateTime date)
        {
            var firstDayOfFirstWeekOfDateYear = StartOfWeekOfYear(date.Year, 1);
            var firstDayOfFirstWeekOfNextYear = StartOfWeekOfYear(date.Year + 1, 1);
            var lastDayOfLastWeekOfDateYear = firstDayOfFirstWeekOfNextYear.AddDays(-1);

            var timePassedSinceFirstWeek = date - firstDayOfFirstWeekOfDateYear;
            var daysPassedSinceFirstWeek = timePassedSinceFirstWeek.Days;

            var timePassedBetweenFirstAndEndOfYear = lastDayOfLastWeekOfDateYear - firstDayOfFirstWeekOfDateYear;
            var daysPassedBetweenFirstAndEndOfYear = timePassedBetweenFirstAndEndOfYear.Days;

            var weeksOfDateYear = (daysPassedBetweenFirstAndEndOfYear / 7) + 1;

            //If the week number surpasses the effective number of weeks of the year it is wrapped around the next year using modulo operator
            //Modulo will wrap the week number around the Year weeks leaving the reminder of the calculation as the Week number of the next year.
            var week = (daysPassedSinceFirstWeek / 7 % weeksOfDateYear) + 1;

            return week;
        }

        private static DateTime StartOfWeekOfYear(int year, int week)
        {
            var firstOfJanuary = new DateTime(year, 1, 1);

            var dayOfWeekFirstOfJanuary = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(firstOfJanuary);

            //Sunday is 0 instead of 7, screwing calculations over
            var dayOfWeekOffset = dayOfWeekFirstOfJanuary == DayOfWeek.Sunday ? -6 : DayOfWeek.Monday - dayOfWeekFirstOfJanuary;

            var daysOffset = (7 * (week - 1)) + dayOfWeekOffset;

            return firstOfJanuary.AddDays(daysOffset);
        }
Gwinn
  • 1,218
  • 1
  • 9
  • 19
  • Have you read the answer at https://stackoverflow.com/questions/11154673/get-the-correct-week-number-of-a-given-date? I think the output of the extension method on that page matches your requirements. – Pranav Negandhi Mar 14 '20 at 17:02
  • @PranavNegandhi It seems it's using ISO8601 week numbering which is implemented by the static class `ISOWeek` that I've tried to no avail. Additonally ISO week numbering does not allow to split weeks across a year which seems is what my Outlook calendar and the italian calendar is doing – Gwinn Mar 14 '20 at 17:15
  • Have you tried using `CalendarWeekRule.FirstFourDayWeek`? – AimusSage Mar 14 '20 at 17:21
  • @AimusSage Yes, again without much success. I've updated the question to add this detail – Gwinn Mar 14 '20 at 17:28

1 Answers1

1

I adapted the answer from the one linked above to suit your requirements. The results match with all the manual calculations you've provided above.

using System;
using System.Globalization;

public class Program
{
    public static void Main()
    {
        var today = new DateTime(2000, 12, 31);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", today, today.GetWeekOfYear("it-it"), today.DayOfWeek, today.DayOfYear));
        today = new DateTime(2021, 3, 29);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", today, today.GetWeekOfYear("it-it"), today.DayOfWeek, today.DayOfYear));
        today = new DateTime(2012, 12, 31);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", today, today.GetWeekOfYear("it-it"), today.DayOfWeek, today.DayOfYear));
        today = new DateTime(1992, 12, 21);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", today, today.GetWeekOfYear("it-it"), today.DayOfWeek, today.DayOfYear));
        today = new DateTime(2009, 12, 30);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", today, today.GetWeekOfYear("it-it"), today.DayOfWeek, today.DayOfYear));
        today = new DateTime(2013, 1, 1);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", today, today.GetWeekOfYear("it-it"), today.DayOfWeek, today.DayOfYear));
    }
}

static class DateTimeExtensions
{
    public static int GetWeekOfYear(this DateTime time, string languageTag)
    {
        // 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
        var culture = CultureInfo.GetCultureInfoByIetfLanguageTag(languageTag);
        var day = culture.Calendar.GetDayOfWeek(time);
        if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
        {
            time = time.AddDays(3);
        }

        // Return the week of our adjusted day
        return culture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
    }
}
Pranav Negandhi
  • 1,594
  • 1
  • 8
  • 17
  • What you did is basically calculate Thursday, Friday etc.. if the date is not on one of those days and the get the week number of the new date ? As far as you know, Is there a particular reason why this works and why the Calendar class does not do it already ? – Gwinn Mar 16 '20 at 08:07
  • I am not very clear about it myself as yet. This code is ripped off from the other answer, and all I changed was the parameter to GetWeekOfYear to CalendarWeekRule.FirstDay. – Pranav Negandhi Mar 16 '20 at 08:52
  • Marked as answer, for reference there is a blog post here that explains and shows the same code you provided here: https://learn.microsoft.com/en-us/archive/blogs/shawnste/iso-8601-week-of-year-format-in-microsoft-net – Gwinn Mar 18 '20 at 14:26