Home > Blockchain >  c adding years and days using date.h
c adding years and days using date.h

Time:01-17

Working on calendar duration arithmetic using date.h and std::chrono, but getting an unexpected result.

Sample code is:

#include "date.h"

#include <string>
#include <chrono>
#include <iostream>

int main() {
    date::sys_seconds calendarDate = {};
    calendarDate = std::chrono::years(30)   date::sys_seconds(std::chrono::days(10));
    std::string stringDate = date::format("%Y-%m-%d %H:%M:%S", calendarDate);
    std::cout << "{}   30 years   10 days = " << stringDate << "\n";
    return 0;
}

Actual Output: {} 30 years 10 days = 2000-01-11 06:36:00

Expected Output: {} 30 years 10 days = 2000-01-11 00:00:00

Using Ubuntu 22.04; g 11.3.0

Compiled with: gcc -g -std=c 20 main.cpp -lstdc

Using date.h fromm here: https://raw.githubusercontent.com/HowardHinnant/date/master/include/date/date.h

Any insight into what's adding in the extra 6hours and 36minutes?

CodePudding user response:

It is because of leap time. For the Gregorian calendar, the average length of the calendar year (the mean year) across the complete leap cycle of 400 years is 365.2425 days (97 out of 400 years are leap years).

This is taken into account in the definition of std::chrono::years which is defined as a duration type with Period std::ratio<31556952> (60 * 60 * 24 * 365.2425 = 31556952).

However, if you compare this to the non-leap-aware duration of a year (60 * 60 * 24 * 365 = 31536000), then you get a difference of 20952 seconds or 5 hours 49 minutes and 12 seconds. If you multiply that difference by 30 it grows to:

7 days 6 hours 36 minutes and 0 seconds - and at least the latter part should look familiar to you.

But what about the 7 days? Well, it turns out that between 1970 and 2000: 1972, 1976, 1980, 1984, 1988, 1992, and 1996 were leap years - if you count that is 7, and that is where the days went.

You can check this by yourself by changing your code and looking at the output:

calendarDate = std::chrono::seconds(60 * 60 * 24 * 365 * 30); - "1999-12-25 00:00:00"

calendarDate = std::chrono::seconds(60 * 60 * 24 * 365 * 30) day(7); - "2000-01-01 00:00:00"

By the way, the problem is that std::chrono::years gives you the length of an average year, while the date.h library knows (and cares about) the specific years: It knows that 1970 was not a leap year (so 60 * 60 * 24 * 365 = 31536000 seconds), but 1972 was a leap year (so 60 * 60 * 24 * 366 = 31622400 seconds).

If you had added 31 years instead, then you would have added 5:49:12 more skew from the average year, but then removed 1 more day since 2000 was also a leap year - so the time it would print would still be in 2000, but ~12 hours before new-years 2001.

CodePudding user response:

Linux stores dates as UTC. My guess is that your machine is located at Central Time zone (CT) which is 6 hours from UTC.

CodePudding user response:

In addition to Frodyne's good answer (which I've upvoted), I wanted to add that you can do either chronological arithmetic or calendrical arithmetic with date.h.

You've done chronological arithmetic, where years is considered to be a uniform unit, and is equal to the average civil year.

Calendrical arithmetic follows the irregularity of calendars, and is what you expected. This is how you would accomplish that:

#include "date/date.h"

#include <string>
#include <chrono>
#include <iostream>

int main() {
    auto chronologicalDateTime = date::sys_seconds(date::days(10));
    auto chronologicalDate     = date::floor<date::days>( chronologicalDateTime);
    date::year_month_day calendarDate =  chronologicalDate;
    auto timeOfDay =  chronologicalDateTime -  chronologicalDate;
    calendarDate  = date::years{30};
    chronologicalDateTime = date::sys_days{calendarDate}   timeOfDay;

    std::string stringDate = date::format("%Y-%m-%d %H:%M:%S",  chronologicalDateTime);
    std::cout << "{}   30 years   10 days = " << stringDate << "\n";
    return 0;
}

You first convert the chronological date/time to a calendrical date, and add the years to the calendrical date. Then convert that back into a chronological date/time.

{}   30 years   10 days = 2000-01-11 00:00:00

Here's another SO answer that goes over the same principle, except using months: https://stackoverflow.com/a/43018120/576911

  • Related