strftime - convert week number ISO8601 to Gregorian standard C++

Viewed 315

I get week number in ISO 8601 format (%V) using strftime and like to convert to week number shown in my outlook (guess it is gregorian).

Outlook calendar setting is:

  • First Day of the week (Sunday)
  • First week of the year starts 1st Jan

For certain years, strftime matches with weeknumber shown in outlook, but for some it doesn't.

A simple code check:

#include <stdio.h>
#include <time.h>

int main ()
{
  time_t rawtime;
  struct tm *tmDate;
  char buffer [80];

    time (&rawtime);
    tmDate = localtime(&rawtime);
    tmDate->tm_hour  = 12;
    tmDate->tm_mday  = 1; //Day of the Month
    tmDate->tm_mon   = 8; //Month (0=Jan...8=Sep)
    tmDate->tm_year  = 2018-1900; //Year
    mktime(tmDate);
        
  strftime (buffer,80,"%Y-W%V",tmDate);
  puts (buffer);
  
  return 0;
}

For above code with it's input, the output will be 2018-W35, which matches with my outlook calendar(https://www.calendar-365.com/2018-calendar.html).

Whereas, if you change year to 2019, the output will be 2019-W35, but in my outlook it falls on 2019-W36 (https://www.calendar-365.com/2019-calendar.html).

Is it possible to map ISO8601 week number to gregorian style?

Any suggestions or code sample will be helpful!

Thank you!

1 Answers

The website you reference seems to use a very unique week numbering system. Its definition of week number appears to mirror the ISO definition with the exception that the first day of the week is Sunday instead of Monday. This means that the first day of the year is the Sunday prior to the first Thursday of the year.

There is no strftime flag to give a week number with this definition. But you can easily compute it with the C++20 <chrono> tools. Unfortunately they aren't shipping yet, but you can use this free, open-source C++20 chrono preview library which works with C++11/14/17.

In addition to computing week number, you'll also need to compute the year, as sometimes the gregorian year does not match the year associated with the week. For example according to https://www.calendar-365.com/2019-calendar.html, December 31, 2018 falls on week 1 of 2019.

So here is a function that computes both the year and the week number, given a date:

#include "date/date.h"
#include <chrono>
#include <iostream>
#include <utility>

// {year, week number}
std::pair<int, int>
outlook_weeknum(date::sys_days sd)
{
    using namespace date;
    auto y = year_month_day{sd + (Thursday - Sunday)}.year();
    auto year_start = sys_days{Thursday[1]/January/y} - (Thursday - Sunday);
    if (sd < year_start)
    {
        --y;
        year_start = sys_days{Thursday[1]/January/y} - (Thursday - Sunday);
    }
    return {int{y}, (sd - year_start)/weeks{1} + 1};
}

The logic is a little tricky. The hard part is finding the first day of the year of the date, which is the Sunday prior to the first Thursday of the year. This is nominally:

auto year_start = sys_days{Thursday[1]/January/y} - (Thursday - Sunday);

where y is the year in the week numbering system (usually, but not always the same as the gregorian year). When the date is very late in the year i.e. December 28 - 31, it may fall in the first week of the next year. To catch that possibility, first bump the date by 4 days (the difference between Sunday and Thursday), and then compute the current year.

         S  M  T  W  T  F  S
y-1 WL  21 22 23 24 25 26 27
y   W1  28 29 30 31  1  2  3

After doing this, compute the start of the year. And if the start of the year happens to be after the date, then you are in the situation that your date belongs in the previous year. In this case, the week number year may be one less than the gregorian year. This can happen when Jan 1 is Friday or Saturday.

         S  M  T  W  T  F  S
y-1 WL  27 28 29 30 31  1  2
y   W1   3  4  5  6  7  8  9

In summary, the dates 12/28 - 12/31 can have a week year number either equal to their gregorian year, or one greater. And the dates 01/01 and 01/02 can have a week year number either equal to their gregorian year, or one lesser. -- All depending on what day of the month the first Thursday of January falls on [1 - 7].

Once the week number year (y) is figured out, then the week number is simply the difference between the date and the first of the year divided by 7 days (1 week), plus one to bias the first week to 1 instead of 0.

This can be exercised like this:

int
main()
{
    using namespace date;

    auto [i, w] = outlook_weeknum(2019_y/9/1);
    std::cout << i << "-W" << w << '\n';
}

which outputs:

2019-W36

To port this code to C++20:

  1. Drop the #include "date/date.h"
  2. Change namespace date to namespace std::chrono
  3. Change 2019_y to 2019y
Related