Calculate week numbers with year starting in April

I am trying to calculate week numbers depending on specific rules. The example of the rule I am looking for is:

Week 1 = first week of the year which containers 1st April.

Example data:

Week 1 March 27, 2017 April 2, 2017

...

Week 23 September 4, 2017 September 10, 2017

I've found a similar question - Getting Week number of year starting from April - and using this as a guide I have come up with the following which works on specific date in the future. However a date time before the cut off, but in the current week, causes problems.

LocalDate firstOne = LocalDate.FromDateTime(dateTime);

int targetYear = firstOne.Month > 4 || firstOne.Month == 4 && firstOne.Day >= 1 ? firstOne.Year : firstOne.Year - 1;

LocalDate start = new LocalDate(targetYear, 4, 1);

int days = Period.Between(start, firstOne, PeriodUnits.Days).Days;
int targetWeek = (days / 7) + 1;

if (targetWeek >= 52)
{
    // if we are greater than 52 then we are technically in week 1 of the following year; eg 31st March 2017.
    // Remove the "year" from the week count and move the year counter back to the correct year.
    targetWeek -= 52;
    targetYear++;
}

return (targetYear, targetWeek);   

The current issue is if I pass in 27/3/2017, which is within 7 days of 1st, it still gives previous year and week 52. I'm guessing I need to take into account week start days somehow(?!).

Jon Skeet
people
quotationmark

How I'd approach this would depend on exactly what you need to do. In Noda Time 2.0 (and backported to 1.4) there's an IWeekYearRule interface you could implement, if you want to do this thoroughly.

If you just need to compute the year and week number, and don't need to go in the opposite direction, I'd do something like this:

  • Find the start of the week-year corresponding to the given calendar-year
  • Check whether the date passed in is on-or-after that
    • If so, do the calculation using that start-of-week-year
    • Otherwise, compute the start of the previous week-year, and use that

Finding the start of a week-year is really easy with DateAdjusters.PreviousOrSame, as you can just say "It's the Monday on or immediately before April 1st" (adjusting the day of the week as you need).

Here's a complete example, with sample results:

using System;
using NodaTime;

class Test
{
    static void Main()
    {
        ShowDate(2017, 1, 1);
        ShowDate(2017, 3, 27);
        ShowDate(2017, 9, 7);
    }

    private static void ShowDate(int year, int month, int day)
    {
        var date = new LocalDate(year, month, day);
        var result = GetWeekYearAndWeek(date);
        Console.WriteLine($"{date:uuuu-MM-dd} => {result}");
    }

    private static (int weekYear, int week) GetWeekYearAndWeek(LocalDate date)
    {
        // Initial guess...
        int weekYear = date.Year;
        var startOfWeekYear = GetStartOfWeekYear(weekYear);
        if (date < startOfWeekYear)
        {
            weekYear--;
            startOfWeekYear = GetStartOfWeekYear(weekYear);
        }

        int days = Period.Between(startOfWeekYear, date, PeriodUnits.Days).Days;
        int week = (days / 7) + 1;

        return (weekYear, week);
    }

    private const IsoDayOfWeek StartOfWeek = IsoDayOfWeek.Monday;

    private static LocalDate GetStartOfWeekYear(int weekYear) =>
        new LocalDate(weekYear, 4, 1).With(DateAdjusters.PreviousOrSame(StartOfWeek));
}

Output:

2017-01-01 => (2016, 40)
2017-03-27 => (2017, 1)
2017-09-07 => (2017, 24)

people

See more on this question at Stackoverflow