https://www.timeanddate.com/date/weekday.html computes various facts about a day of the year, for example:
Given an arbitrary date, how can these numbers be computed with the C++20 chrono specification?
This is remarkably easy with the C++20 chrono specification. Below I show a function which inputs an arbitrary date, and prints this information to cout. Though at the time of this writing, the C++20 chrono specification isn't yet shipping, it is approximated by a free, open-source library. So you can experiment with it today, and even include it in shipping applications as long as you adopt C++11 or later.
This answer will take the form of a function:
void info(std::chrono::sys_days sd);
sys_days is a day-precision time_point in the system_clock family. That means it is simply a count of days since 1970-01-01 00:00:00 UTC. The type alias sys_days is new with C++20, but the underlying type has been available since C++11 (time_point<system_clock, duration<int, ratio<86400>>>). If you use the open-source C++20 preview library, sys_days is in namespace date.
The code below assumes function-local:
using namespace std;
using namespace std::chrono;
to reduce verbosity. If you are experimenting with the open-source C++20 preview library, also assume:
using namespace date;
Heading
To output the first two lines is simple:
cout << format("{:%d %B %Y is a %A}\n", sd)
<< "\nAdditional facts\n";
Just take the date sd and use format with the familiar strftime/put_time flags to print out the date and text. The open-source C++20 preview library hasn't yet integrated the fmt library, and so uses the slightly altered format string "%d %B %Y is a %A\n".
This will output (for example):
26 December 2019 is a Thursday
Additional facts
Common intermediate results computed once
This section of the function is written last, because one doesn't yet know what computations will be needed multiple times. But once you know, here is how to compute them:
year_month_day ymd = sd;
auto y = ymd.year();
auto m = ymd.month();
weekday wd{sd};
sys_days NewYears = y/1/1;
sys_days LastDayOfYear = y/12/31;
We will need the year and month fields of sd, and the weekday (day of the week). It is efficient to compute them once and for all in this manner. We will also need (multiple times) the first and last days of the current year. It is hard to tell at this point, but it is efficient to store these values as type sys_days as their subsequent use is only with day-oriented arithmetic which sys_days is very efficient at (sub-nanosecond speeds).
Fact 1: day number of year, and number of days left in year
auto dn = sd - NewYears + days{1};
auto dl = LastDayOfYear - sd;
cout << "* It is day number " << dn/days{1} << " of the year, "
<< dl/days{1} << " days left.\n";
This prints out the day number of the year, with January 1 being day 1, and then also prints out the number of days remaining in the year, not including sd. The computation to do this is trivial. Dividing each result by days{1} is a way to extract the number of days in dn and dl into an integral type for formatting purposes.
Fact 2: Number of this weekday and total number of weekdays in year
sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];
auto total_wd = (last_wd - first_wd)/weeks{1} + 1;
auto n_wd = (sd - first_wd)/weeks{1} + 1;
cout << format("* It is {:%A} number ", wd) << n_wd << " out of "
<< total_wd << format(" in {:%Y}.\n}", y);
wd is the day of the week (Monday thru Sunday) computed at the top of this article. To perform this computation we first need the dates of the first and last wd's in the year y. y/1/wd[1] is the first wd in January, and y/12/wd[last] is the last wd in December.
The total number of wds in the year is just the number of weeks between these two dates (plus 1). The sub-expression last_wd - first_wd is the number of days between the two dates. Dividing this result by 1 week results in an integral type holding the number of weeks between the two dates.
The week number is done the same way as the total number of weeks except one starts with the current day instead of the last wd of the year: sd - first_wd.
Fact 3: Number of this weekday and total number of weekdays in month
first_wd = y/m/wd[1];
last_wd = y/m/wd[last];
total_wd = (last_wd - first_wd)/weeks{1} + 1;
n_wd = (sd - first_wd)/weeks{1} + 1;
cout << format("* It is {:%A} number }", wd) << n_wd << " out of "
<< total_wd << format(" in {:%B %Y}.\n", y/m);
This works just like Fact 2, except we start with the first and last wds of the year-month pair y/m instead of the entire year.
Fact 4: Number of days in year
auto total_days = LastDayOfYear - NewYears + days{1};
cout << format("* Year {:%Y} has ", y) << total_days/days{1} << " days.\n";
The code pretty much speaks for itself.
Fact 5 Number of days in month
total_days = sys_days{y/m/last} - sys_days{y/m/1} + days{1};
cout << format("* {:%B %Y} has ", y/m) << total_days/days{1} << " days.\n";
The expression y/m/last is the last day of the year-month pair y/m, and of course y/m/1 is the first day of the month. Both are converted to sys_days so that they can be subtracted to get the number of days between them. Add 1 for the 1-based count.
Use
info can be used like this:
info(December/26/2019);
or like this:
info(floor<days>(system_clock::now()));
Here is example output:
26 December 2019 is a Thursday
Additional facts
* It is day number 360 of the year, 5 days left.
* It is Thursday number 52 out of 52 in 2019.
* It is Thursday number 4 out of 4 in December 2019.
* Year 2019 has 365 days.
* December 2019 has 31 days.
Edit
For those who are not fond of the "conventional syntax", there is a complete "constructor syntax" that can be used instead.
For example:
sys_days NewYears = y/1/1;
sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];
can be replaced by:
sys_days NewYears = year_month_day{y, month{1}, day{1}};
sys_days first_wd = year_month_weekday{y, month{1}, weekday_indexed{wd, 1}};
sys_days last_wd = year_month_weekday_last{y, month{12}, weekday_last{wd}};
Related
How will I get the expiration date of an item, which is based on week code? Whenever I run the code that I made, the program reads the current date and disregards the week code. For example:
Week Code: 2138 (2021 week 38)
Shelf_life : 6 months
CTime weekcode = CTime::GetCurrentTime();
CTimeSpan shelf_no = CTimeSpan(atoi(view->m_pODBCPartsNo->m_shelf_life), 0, 0, 0);
CTime expiration_date = weekcode = shelf_no;
Week code is a date code, for example 2138, year 2021(21) week 38(38). Week 38 is around September 19, 2021 to September 25, 2021.
Now the question is, how will i get the expiration date that is based on week code ? Will I still use "GetCurrentTime" ?
Here's how I would do it using Howard Hinnant's free, open-source, header-only date library:
#include "date/iso_week.h"
#include "date/date.h"
#include <chrono>
#include <iostream>
int
main()
{
using namespace date;
int weekcode = 2138;
int shelf_life = 6;
iso_week::year y{weekcode / 100 + 2000};
iso_week::weeknum wk(weekcode % 100);
auto prod_date = y/wk/iso_week::wed;
auto exp_date = floor<days>(sys_days{prod_date} + months{shelf_life});
std::cout << "Production date : " << prod_date << '\n';
std::cout << "Expiration date : " << exp_date << '\n';
}
This assumes that you are using ISO week date as the definition of "week number", which is an internationally accepted standard. The program begins by simply decoding weekcode into a year and weeknum. It then uses the iso_week library to create a production date with this year and weeknum. To complete this date, a weekday must be supplied. I've chosen Wednesday since this is the middle of the ISO work week.
auto prod_date = y/wk/iso_week::wed;
The expiration date can then be computed by simply adding 6 months to that date. Note that this addition is done using a chronological computation because product aging is a physical process that does not care that different months have different lengths. Using an "average month" length is adequate.
To do this chronological arithmetic, the production date must first be converted to a chronological date, sys_days. This is simply a count of days since the Unix Time epoch.
auto exp_date = floor<days>(sys_days{prod_date} + months{shelf_life});
The result has a precision finer than days since the average month duration is not an integral number of days. So it must be truncated back to the precision of days to create a date, as opposed to a date-time.
The above program prints out:
Production date : 2021-W38-Wed
Expiration date : 2022-03-23
I am collecting GPS time (in ns) from a sensor and I am looking for a way to convert that to a UTC time in C++.
I have a working code before in python.
time_gps = time_gps * 10**(-9) # Converts ns -> s
gps_epoch = pd.datetime(year=1980, month=1, day=6)
delta = pd.to_timedelta(time_gps, unit='s')
time = gps_epoch + delta - pd.to_timedelta(19, unit='s')
Using the link "Using std::chrono / date::gps_clock for converting a double gps timestamp to utc/tai" helped me figure out how to convert from GPS time to UTC.
uint64_t gps_input_ns = 1281798087485516800;
date::gps_time<std::chrono::nanoseconds> gt_nano{date::round<std::chrono::nanoseconds>(std::chrono::duration<uint64_t, std::nano>{gps_input_ns})};
auto utc_nano = date::clock_cast<date::utc_clock>(gt_nano);
std::cout << utc_nano << " UTC\n";
Output: 2020-08-18 15:01:09.485516800 UTC
My next question is, how can I extract the date and time from the variable "utc_nano"? I'm not very familiar with chrono or the date library and therefore having problems trying to separate the date and time. Any help would be much appreciated.
I'm assuming that leap seconds are important to you since you're dealing with gps time which represents the physical seconds that are labeled leap seconds by UTC. It is fairly tricky to manipulate date/times with leaps seconds, which is why Unix Time is so popular in computer systems.
In the C++20 chrono preview library, Unix Time is modeled by sys_time, whereas true UTC is modeled by utc_time. The only difference between these two models is that sys_time doesn't count leap seconds and utc_time does.
The advantage of sys_time is that there exists a fast and efficient algorithm for translating the time duration since 1970-01-01 00:00:00 into fields: year, month, day, hour, minute, second, subseconds. So if you want to break utc_time into these fields, the trick is to first turn utc_time into sys_time, while remembering whether or not your utc_time is referencing a leap second. Indeed, this is exactly what the streaming operator for utc_time does.
There exists a helper function get_leap_second_info to aid in doing this. This function takes a utc_time and returns a {is leap second, count of leap seconds} struct. The first member is true if the argument refers to a leap second, the second argument tells you how many leap seconds there have been between the argument and 1970. So the first step is to get this information for utc_nano:
auto info = get_leap_second_info(utc_nano);
Now you can create a sys_time with this information. Since sys_time is just like utc_time excluding leap seconds, you can just subtract off the number of leap seconds that have occurred:
sys_time<nanoseconds> sys_nano{utc_nano.time_since_epoch() - info.elapsed};
Now you have a count of nanoseconds in Unix Time. Truncating to days precision gives you a count of days in Unix Time:
auto sys_day = floor<days>(sys_nano);
sys_day is a date. The time of day is simply the difference between the nanoseconds-precision time_point and the days-precision time_point:
auto tod = sys_nano - sys_day;
tod is a time. It is the duration since midnight. It may be short by a second. That information is in info.is_leap_second.
If you want these types as "field types", you could convert sys_day to type year_month_day:
year_month_day ymd = sys_days;
year_month_day has getters for year, month and day.
You can convert tod into a {hours, minutes, seconds, nanoseconds} struct with:
hh_mm_ss hms{tod};
This has getters: hours(), minutes(), seconds(), and subseconds(). The above syntax assumes C++17. If in C++11 or 14, the syntax is:
hh_mm_ss<nanoseconds> hms{tod};
hh_mm_ss doesn't directly support a count of 60s, but that information is still in info.is_leap_second. E.g.
std::cout << hms.seconds().count() + info.is_leap_second << '\n';
That will output 60 if and only if info.is_leap_second is true.
You can even try this code which makes use of C time related functions
uint64_t ns = 1281798087485516800ULL + 315964800000000000ULL; // offset between gps epoch and unix epoch is 315964800 seconds
struct timespec ts;
ts.tv_sec = ns / 1000000000ULL;
ts.tv_nsec = ns % 1000000000ULL;
struct tm stm;
gmtime_r(&ts.tv_sec, &stm);
std::cout << stm.tm_year + 1900 << "-" << stm.tm_mon + 1 << "-" << stm.tm_mday << " " << stm.tm_hour << ":" << stm.tm_min << ":" << stm.tm_sec << std::endl;
I was trying to make a program that calculated the fee of a membership at a swimming pool. The user would enter a date (the last time a member renewed the membership), and the program would calculate if their membership was overdue or not, using the current date.
The membership is meant to be overdue a week (or another arbitrary time period) before the start of the month they joined in a year's time. For example, if I joined in February 2016, I would have to pay on January 24 2017 or before to make sure the membership is overdue. As soon as it gets to January 25, a month fee should be charged ($15) and as soon as it reaches February 25, two months fee should be charged etc.
However, I do not know how to charge for subsequent months after the first one. For example, paying on February 3 should result in one month overdue but paying on February 26 should be two months but I do not know how to do this.
How can I fix my function because it doesn't seem to work?
E.g. I entered November 15 2016 and it should return 15 since the membership was due on October 24 2017 but it returns 0.
int membershipFine(int joinDay, int joinMonth, int joinYear, int currentDay, int currentMonth, int currentYear)
{
int dueDay[12] = {25, 22, 25, 24, 25, 24, 25, 25, 24, 25, 24, 25}; // the week before the end of each month in days
int correspondingMonth = joinMonth - 2; // finds the element position that corresponds
if (correspondingMonth == -1) // if they joined in january, the array will go to december
{
correspondingMonth = 11;
}
int differenceInMonths = currentMonth - joinMonth + 12 * (currentYear - joinYear);
if (differenceInMonths < 11)
{
return 0;
}
else if ((differenceInMonths == 11) && (joinDay < dueDay[correspondingMonth]))
{
return 0;
}
else if (differenceInMonths == 11)
{
return 15;
}
if (differenceInMonths > 11 && joinDay < dueDay[correspondingMonth]) // not sure about this if and else statement
{
return (differenceInMonths - 11) * 15;
}
else return (differenceInMonths - 10) * 15;
}
The best way to deal with dates and times is to use a library that raises the level of abstraction about integers, to dates and times. Howard Hinnant's free, open-source, header-only library is such a tool.
It has a {year, month, day} class called date::year_month_day that lends itself to year and month arithmetic. One could use this to change the API of membershipFine from taking 6 type-unsafe parameters to just two type-safe parameters:
int
membershipFine(date::year_month_day joinDate, date::year_month_day currentDate);
Your description of the due date appears to say that it is independent of the day of the month of the join date, and that it is 1 year, less 1 week from the first of the month of the join date. If this is true, this can be easily computed like this:
using namespace date;
year_month_day dueDate{
local_days{(joinDate.year()/joinDate.month() + years{1})/1} - weeks{1}};
The expression joinDate.year()/joinDate.month() creates a year_month object, which is just a year and month, neglecting the day-of-the-month from joinDate. I add 1 year to that year_month, which results in another year_month, exactly 1 year later.
To that sum, /1 is appended. This creates a year_month_day corresponding to the first day of the month of the aforementioned year_month.
Now even though year_month_day is great for year and month oriented arithmetic, it is not so great for day and week-oriented arithmetic. The best data structure for that is a {count-of-days} from some epoch. This library has such a data structure called local_days. So I convert to that, subtract 1 week, and then convert back to year_month_day.
All of this (to compute the due date) happens in the lines of code above.
Now I need to compute the fine based on the relationship between currentDate and dueDate. The fine is $0 if currentDate < dueDate, and otherwise is a function of the number of whole months (plus 1) currentDate is beyond dueDate (as I understand your problem statement):
int fine = 0;
if (currentDate >= dueDate)
{
auto differenceInMonths = currentDate.year()/currentDate.month() -
dueDate.year()/dueDate.month();
if (currentDate.day() >= dueDate.day())
++differenceInMonths;
fine = differenceInMonths.count() * 15;
}
The difference in months, neglecting the day-of-the-month, can be computed by converting to year_month objects and subtracting. Now if currentDate.day() < dueDate.day(), this is the correct answer. For example if the difference in months is 1, but the day of the month in currentDate has not yet exceeded the day of the month in dueDate, then we don't want to charge for a second month, else we do. If we do, differenceInMonths is incremented.
Then the fine is simply the differenceInMonths, converted from months to integral, times 15.
<aside> If there are any <chrono> fans out there, the type of differenceInMonths is actually a std::chrono::duration with a period that is exactly the average month. Thus the .count() member function to access the underlying integral value.
I've added some print statements to the above code, and below I show the whole thing put together plus a driver with a few examples:
#include "date/date.h"
#include <iostream>
int
membershipFine(date::year_month_day joinDate, date::year_month_day currentDate)
{
using namespace date;
year_month_day dueDate{
local_days{(joinDate.year()/joinDate.month() + years{1})/1} - weeks{1}};
int fine = 0;
if (currentDate >= dueDate)
{
auto differenceInMonths = currentDate.year()/currentDate.month() -
dueDate.year()/dueDate.month();
if (currentDate.day() >= dueDate.day())
++differenceInMonths;
fine = differenceInMonths.count() * 15;
}
std::cout << "join Date is " << joinDate << '\n';
std::cout << "due Date is " << dueDate << '\n';
std::cout << "current Date is " << currentDate << '\n';
std::cout << "fine is $" << fine << '\n';
return fine;
}
int
main()
{
using namespace date::literals;
std::cout << membershipFine(feb/29/2016, jan/24/2017) << '\n';
std::cout << membershipFine(feb/29/2016, jan/25/2017) << '\n';
std::cout << membershipFine(feb/29/2016, feb/24/2017) << '\n';
std::cout << membershipFine(feb/29/2016, feb/25/2017) << '\n';
}
This outputs:
join Date is 2016-02-29
due Date is 2017-01-25
current Date is 2017-01-24
fine is $0
0
join Date is 2016-02-29
due Date is 2017-01-25
current Date is 2017-01-25
fine is $15
15
join Date is 2016-02-29
due Date is 2017-01-25
current Date is 2017-02-24
fine is $15
15
join Date is 2016-02-29
due Date is 2017-01-25
current Date is 2017-02-25
fine is $30
30
In summary, the use of a library such as this frees you from having to think in terms of ints so you can concentrate on the logic you have to implement in terms of dates and calendars. The result is compact and readable code that is far more likely to be correct.
Update
In the comments below the OP asks about how to parse a date from cin and how to get the current date. There are several options.
Here is how I recommend asking for a date:
date::year_month_day join;
while (true)
{
std::cout << "Enter join date as yyyy-mm-dd: ";
std::cin >> date::parse("%F", join);
if (!std::cin.fail())
break;
std::cin.clear();
std::string garbage;
std::getline(std::cin, garbage);
std::cout << "Please try again.\n";
}
If you prefer to ask for some other format, here is the complete list of parsing flags available for use.
And the OP asks how to get the current date. There are multiple answers. If you are content with the current date in UTC, that is the simplest:
using namespace std::chrono;
using namespace date;
year_month_day today = floor<days>(system_clock::now());
If you want the current date in your local time zone, you need to use "date/tz.h" (requires some installation) and this syntax:
year_month_day today{floor<days>(make_zoned(current_zone(),
system_clock::now()).get_local_time())};
If you want the current date in some time zone other than your current local time zone, that can be done with:
year_month_day today{floor<days>(make_zoned("America/Los_Angeles",
system_clock::now()).get_local_time())};
No matter how you parse your join and today, they can be used like this:
std::cout << membershipFine(join, today) << '\n';
In order that a device (with limited memory) is able to manage its own timezone and daylight savings, I'm trying to calculate daylight savings triggers for 85 time zones based on a simplified description of each timezone. I have access to minimal C and C++ libraries within the device. The format of the timezone (inc. DST) description for each time zone is as follows:
UTC - the base time and date from system clock
GMTOffsetMinutes - offset from GMT with DST inactive
DSTDeltaMinutes - modifier to above with DST active (as applicable to TZ)
DSTStartMonth - month in which DST becomes active
DSTStartNthOccurranceOfDay - the nth occurrence of the day name in month
DSTDayOfWeek - Sun = 0 through to Sat = 6
DSTStartHour - hour at which DST becomes active
DSTStartMinute - minute at which DST becomes active
and corresponding EndMonth, EndNth..., EndHour, EndMinute
I have found numerous examples going the other way, i.e. starting with the date, but they involve using the modulus, keeping the remainder and dropping the quotient hence I have been unable to transpose the formula to suit my needs.
I also tried to reuse the standard "Jan = 6, Feb = 2, Mar = 2, Apr = 5, May = 0, etc. modifier table and year modifiers from the "tell me what day the 25th of June, 2067 is?" party trick and developed the following algorithm.
Date = DayOfWeek + ((NthOccuranceOfDay - 1) x 7 ) - MonthCode - YearCode
This worked for the first 6 random test dates I selected but then I started to see dates for which it failed. Is it possible that the basic algorithm is sound but I'm missing a further modifier or maybe that I'm applying the modifiers incorrectly?
Is there another solution I could utilize?
Using this open source, cross platform date library, one can write:
#include "date.h"
#include <iostream>
int
main()
{
using namespace date;
year_month_day us_daylight_starts = sys_days(sun[2]/mar/2015);
year_month_day us_daylight_ends = sys_days(sun[1]/nov/2015);
std::cout << us_daylight_starts << '\n';
std::cout << us_daylight_ends << '\n';
}
which will output:
2015-03-08
2015-11-01
The formulas this library is based on are in the public domain and documented here.
The algorithms paper has very complete unit tests validating the date algorithms over a range of millions of years (a far larger range than is necessary).
Sometimes daylight savings rules are written in terms of the last weekday of a month. That is just as easily handled:
year_month_day ymd = sys_days(sun[last]/nov/2015);
std::cout << ymd << '\n'; // 2015-11-29
That formula will be off by one week (or even two weeks) if MonthCode + YearCode is greater than or equal to DayOfWeek, because in that case you will be counting NthOccurenceOfDay from a negative date.
As an alternative, with no tables, you can compute the day of week of the first of the month using, for example, Zeller's algorithm:
int NthOccurrence(int year, int month, int n, int dayOfWeek) {
// year is the current year (eg. 2015)
// month is the target month (January == 1...December == 12)
// Finds the date of the nth dayOfWeek (Sun == 0...Sat == 6)
// Adjust month and year
if (month < 3) { --year, month += 12; }
// The gregorian calendar is a 400-year cycle
year = year % 400;
// There are no leap years in years 100, 200 and 300 of the cycle.
int century = year / 100;
int leaps = year / 4 - century;
// A normal year is 52 weeks and 1 day, so the calendar advances one day.
// In a leap year, it advances two days.
int advances = year + leaps;
// This is either magic or carefully contrived,
// depending on how you look at it:
int month_offset = (13 * (month + 1)) / 5;
// From which, we can compute the day of week of the first of the month:
int first = (month_offset + advances) % 7;
// If the dayOfWeek we're looking for is at least the day we just
// computed, we just add the difference. Otherwise, we need to add 7.
// Then we just add the desired number of weeks.
int offset = dayOfWeek - first;
if (offset < 0) offset += 7;
return 1 + offset + (n - 1) * 7;
}
Is it possible to manually set the epoch date/time to the January 1, 0000, so I might use the std::chrono::time_point::time_since_epoch to calculate the difference between a given date and January 1, 0000?
I tried the following:
#include <iostream>
#include <chrono>
#include <ctime>
int main(int argc, char*argv[])
{
std::tm epochStart = {};
epochStart.tm_sec = 0;
epochStart.tm_min = 0;
epochStart.tm_hour = 0;
epochStart.tm_mday = 0;
epochStart.tm_mon = 0;
epochStart.tm_year = -1900;
epochStart.tm_wday = 0;
epochStart.tm_yday = 0;
epochStart.tm_isdst = -1;
std::time_t base = std::mktime(&epochStart);
std::chrono::system_clock::time_point baseTp=
std::chrono::system_clock::from_time_t(base);
std::time_t btp = std::chrono::system_clock::to_time_t(baseTp);
std::cout << "time: " << std::ctime(&btp);
}
but this gives me
time: Thu Jan 1 00:59:59 1970
I would avoid std::time_t altogether. Using days_from_civil from chrono-Compatible Low-Level Date Algorithms, you can immediately compute any difference between std::chrono::system_clock::time_point, and any date in the proleptic Gregorian calendar1.
In addition to days_from_civil which takes a year/month/day triple and converts it into a count of days before/since 1970-01-01 (a chrono-compatible epoch), it is also convenient to create a custom chrono::duration to represent 24 hours:
typedef std::chrono::duration
<
int,
std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
> days;
Now you can create any epoch you want with just:
constexpr days epoch = days(days_from_civil(0, 1, 1)); // 0000-01-01
In C++1y this is even a compile-time computation!
And you can subtract this std::chrono::duration from any other std::chrono::duration:
auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;
delta is now a std::chrono::duration representing the amount of time between now, and 0000-01-01. You can then print that out however you want, or otherwise manipulate it. For example here is an entire working demo:
#include "../date_performance/date_algorithms"
#include <iostream>
#include <chrono>
typedef std::chrono::duration
<
int,
std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
> days;
int
main()
{
constexpr days epoch = days(days_from_civil(0, 1, 1));
auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;
days d = std::chrono::duration_cast<days>(delta);
std::cout << "It has been " << d.count() << " days, ";
delta -= d;
auto h = std::chrono::duration_cast<std::chrono::hours>(delta);
std::cout << h.count() << " hours, ";
delta -= h;
auto m = std::chrono::duration_cast<std::chrono::minutes>(delta);
std::cout << m.count() << " minutes, ";
delta -= m;
auto s = std::chrono::duration_cast<std::chrono::seconds>(delta);
std::cout << s.count() << " seconds ";
std::cout << " since 0000-01-01\n";
}
Which for me output:
It has been 735602 days, 19 hours, 14 minutes, 32 seconds since 0000-01-01
A word of warning about overflow:
The std::chrono::system_clock::time_point::duration is not guaranteed to have a range large enough to do this. It turns out that on my system it does. It is microseconds in a signed long long which will span +/- 292,000 years. If you need to avoid an overflow problem, you could truncate your std::chrono::system_clock::time_point::duration to courser units (e.g. seconds or days) to extend the range prior to subtracting 0000-01-01.
I got to thinking
And that usually leads to a disaster. However in this case I decided I should add to this post anyway. This:
constexpr days epoch = days(days_from_civil(0, 1, 1));
has type days, which is a duration. But it really isn't a duration. It is a point in time. It is a date. It is a time_point with a coarse precision. By introducing a new typedef, the code in this post can be cleaned up just a little bit more:
typedef std::chrono::time_point<std::chrono::system_clock, days> date_point;
Now instead of writing:
constexpr days epoch = days(days_from_civil(0, 1, 1));
One can write:
constexpr date_point epoch{days(days_from_civil(0, 1, 1))};
But even more importantly, instead of:
auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;
we can now write:
auto delta = std::chrono::system_clock::now() - epoch;
This delta still has exactly the same type and value as it did previously, and everything else in the demo still proceeds as exactly as it did before.
This is both a small change, and a big change. By treating epoch as a time_point instead of a duration, the algebra of time_point's and duration's works for us, both simplifying and type-checking our expressions to help us write cleaner code with fewer mistakes.
For example one can add two duration's together. But it doesn't make any sense at all to:
epoch + epoch
By using time_point instead of duration for the type of epoch, the compiler catches such non-sensical expressions at compile time.
1The proleptic Gregorian calendar has a year 0. In the year 0 it is 2 days behind the Julian calendar. Using a year 0 is also consistent with ISO 8601. As long as all parties involved know what calendar you are using, then everything is fine. Conversion between non-positive years and "BC years" is trivial if desired.
It's possible, the code you've given (minus a small fix, tm_mday starts with 1) yields:
Sat Jan 1 00:00:00 0
Live example
The real problem is: Are you on 32-bit or 64-bit? With a 32-bit system, time_t is also only 32 bits and you are limited to 1970 +/- 68 years.
On a 64-bit system, the limits are given by std::mktime and std::strftime, in my own code I have unit test for those strings and the corresponding values:
"-2147481748-01-01 00:00:00" maps to -67768040609740800
"2147483647-12-31 23:59:59" maps to 67767976233532799
I should probably also mention that there are systems where the above does not work because the underlying OS functions are buggy. For the record: I'm on Linux.
No. mktime and friends are based on UNIX time, which starts on 1st January 1970.
There is in fact no such thing as 0th January, 0000, so it seems likely that you would be better off finding another way to solve whatever is your actual problem.