Assume I have a unix timetsamp
int timestamp=1594386202;
I want to write a function
int floor_to_month_utc(int timestamp);
that floors a timestamp it to the beginning of the the corresponding month (in UTC). In this example the function shall satisfy 1593561600 == floor_to_month_utc(1594386202)
because 1594386202
corresponds to 2020-07-10T13:03:22+00:00
UTC time and 1593561600
is the beginning of the corresponding month 2020-07-01T00:00:00+00:00
.
How can I do this in C++? (Actually I also need the ceil of the current timestamp to the next month timestamp but I think that I can write this myself if you give me a hint how to implement the floor function)
As Alan Birtles points out in the comments, this can easily be accomplished in C++20:
#include <chrono>
int
floor_to_month_utc(int timestamp)
{
using namespace std::chrono;
sys_seconds tp{seconds{timestamp}};
year_month_day ymd = floor<days>(tp);
sys_seconds result = sys_days{ymd.year()/ymd.month()/1};
return result.time_since_epoch().count();
}
Explanation:
The first step is to put the input into the <chrono>
type system. It is understood that timestamp
is a count of seconds since 1970-01-01 00:00:00 UTC (excluding leap seconds). This is also known as Unix Time.
std::chrono::sys_seconds
is the C++20 type that corresponds to these semantics. So sys_seconds tp{seconds{timestamp}};
first converts timestamp
to the duration seconds
, and then to the time_point
sys_seconds
.
Next one must realize that this is a calendrical computation, not a chronological computation. And the calendar being used is the civil calendar, which is modeled in C++20 <chrono>
. So the next step is to convert the time_point
tp
to a day in the civil calendar:
year_month_day ymd = floor<days>(tp);
floor<days>
simply truncates the precision to tp
to days
precision (making it a count of days since 1970-01-01 00:00:00 UTC).
ymd
is a {year, month, day}
data structure, converted from the days
-precision time_point
and has type std::chrono::year_month_day
. Both ymd
and floor<days>(tp)
contain exactly the same information. But the information is stored in two different data structures: {year, month, day}
vs {count of days}
.
The next step is to find the first day of the year and month referred to by ymd
. This is simply the expression ymd.year()/ymd.month()/1
.
This can be converted back to the {count of days}
data structure with:
sys_days{ymd.year()/ymd.month()/1}
std::chrono::sys_days
is simply a type alias for the type of the expression floor<days>(tp)
, which has type:
time_point<system_clock, days>
Next the sys_days
data structure is implicitly converted to seconds
-precision, and the integral amount is extracted and returned from that data structure.
A preview of the C++20 <chrono>
library is here and can be used with C++11/14/17. To port the above function to this preview library simply add "date/date.h"
and using namespace date;
. "date/date.h"
is a header-only library and so requires no installation.
This can be exercised like so:
std::cout << floor_to_month_utc(1594386202) << '\n';
which outputs:
1593561600
It is instructive to compare the above code to this answer which gives a different result:
1593626076
The reason the result is different goes back to the distinction between calendrical computations, and chronological computations. Calendrical computations are done with respect to some calendar (civil, Chinese, Julian, whatever). Whereas chronological computations operate only on fixed (regular) units of time. Calendrical months and years don't have regular lengths of time.
The C++20 <chrono>
library can perform chronological computations with months
and years
though. This is sometimes the right answer when dealing with physical or biological processes that stretch on for months or years. Such processes don't care about human made calendars. So in this case it makes sense to deal with the average length of months and years.
This statement:
floor<months>(sys_seconds{seconds{timestamp}})
simply divides the time since the Unix Time epoch into regular, even months, with each month being exactly 2,629,746 seconds long (the average length of the civil month). So this answer is correct if one desired a chronological computation with hypothetical uniform months (an entirely different calendar if you will).