Performing date time arithmetic in custom date time class - c++

I have a very naive struct representing date time which I would like to perform arithmetic on:
struct MyDateTime
{
MyDateTime(int year, int month, int day, uint64_t nanos);
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
I'd like to be able to add/subtract MyDateTime from another MyDateTime.
My idea was to make my struct a wrapper and use Boost internally.
I looked at Boost Posix Time:
https://www.boost.org/doc/libs/1_55_0/doc/html/date_time/examples.html#date_time.examples.time_math
But this seems to only be doing time math (not accounting for the date component).
I looked at Boost Gregorian Date but I couldn't see any time argument in the constructors.
What is the simplest way to use Boost, so I can perform datetime arithmetic?

As you may have realized by now, dates cannot be added.
Dates and timestamps are mathematically akin to tensors, in that their difference type is in a different domain.
When you commented that time_duration doesn't include a date, you still had a point though.
Because the time_duration might be the time-domain difference type (the difference type ptime) but we need an analog for the date-part of ptime, which is boost::gregorian::date.
Boost Gregorian dates are basically blessed tuples of (yyyy,mm,dd).So a natural difference type would just be a signed integral number of days. And that's exactly* what boost::gregorian::date_duration is:
boost::gregorian::date_duration x = date{} - date{};
boost::posix_time::time_duration y = ptime{} - ptime{};
Because that type is implemented in the Gregorian module you will get correct differences, even with special cases like leap days and other anomalies: https://www.calendar.com/blog/gregorian-calendar-facts/
So, you could in fact use that type as a difference type, just for the ymd part.
Simplify
The good news is, you don't have to bother: boost::posix_time::ptime encapsulates a full boost::gregorian::date, hence when you get a boost::posix_time::time_duration from subtracting ptimes, you will already get the number of days ciphered in:
#include <boost/date_time.hpp>
int main() {
auto now = boost::posix_time::microsec_clock::local_time();
auto later = now + boost::posix_time::hours(3);
auto tomorrow = later + boost::gregorian::days(1);
auto ereweek = later - boost::gregorian::weeks(1);
std::cout << later << " is " << (later - now) << " later than " << now
<< std::endl;
std::cout << tomorrow << " is " << (tomorrow - later) << " later than " << later
<< std::endl;
std::cout << ereweek << " is " << (ereweek - now) << " later than " << now
<< std::endl;
}
Starting from the current time we add 3 hours, 1 day and then subtract a week. It prints: Live On Coliru:
2021-Mar-28 01:50:45.095670 is 03:00:00 later than 2021-Mar-27 22:50:45.095670
2021-Mar-29 01:50:45.095670 is 24:00:00 later than 2021-Mar-28 01:50:45.095670
2021-Mar-21 01:50:45.095670 is -165:00:00 later than 2021-Mar-27 22:50:45.095670
Note that 24h is 1 day, and -165h is (7*24 - 3) hours ago.
There's loads of smarts in the Gregorian calendar module:
std::cout << date{2021, 2, 1} - date{2020, 2, 1} << std::endl; // 366
std::cout << date{2020, 2, 1} - date{2019, 2, 1} << std::endl; // 365
Taking into account leap days. But also knowing the varying lengths of a calendar month in context:
auto term = boost::gregorian::months(1);
for (date origin : {date{2021, 2, 17}, date{2021, 3, 17}}) {
std::cout << ((origin + term) - origin) << std::endl;
};
Prints 28 and 31 respectively.
Applying It To Your Type
I'd suggest keeping with the library difference type, as clearly you had not previously given it any thought that you needed one. By simply creating some interconversions you can have your cake and eat it too:
struct MyDateTime {
MyDateTime(int year = 1970, int month = 1, int day = 1, uint64_t nanos = 0)
: year(year),
month(month),
day(day),
nanosSinceMidnight(nanos) {}
operator ptime() const {
return {date(year, month, day),
microseconds(nanosSinceMidnight / 1'000)};
}
explicit MyDateTime(ptime const& from)
: year(from.date().year()),
month(from.date().month()),
day(from.date().day()),
nanosSinceMidnight(from.time_of_day().total_milliseconds() * 1'000) {}
private:
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
Now, I would question the usefulness of keeping your MyDateTime type, but I realize legacy code exists, and sometimes you require a longer time period while moving away from it.
Nanoseconds
Nanosecond precision is not enabled by default. You need to [opt in to use that](https://www.boost.org/doc/libs/1_58_0/doc/html/date_time/details.html#boost-common-heading-doc-spacer:~:text=To%20use%20the%20alternate%20resolution%20(96,the%20variable%20BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG%20must%20be%20defined).
In the sample below I do.
Be careful that al the translation units in your project use the define, or you will cause ODR violations.
Live Demo
Adding some convenience operator<< as well:
Live On Coliru
#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
#include <boost/date_time.hpp>
#include <vector>
using boost::posix_time::ptime;
using boost::gregorian::date;
using boost::posix_time::nanoseconds;
struct MyDateTime {
MyDateTime(MyDateTime const&) = default;
MyDateTime& operator=(MyDateTime const&) = default;
MyDateTime(int year = 1970, int month = 1, int day = 1, uint64_t nanos = 0)
: year(year),
month(month),
day(day),
nanosSinceMidnight(nanos) {}
operator ptime() const {
return {date(year, month, day), nanoseconds(nanosSinceMidnight)};
}
/*explicit*/ MyDateTime(ptime const& from)
: year(from.date().year()),
month(from.date().month()),
day(from.date().day()),
nanosSinceMidnight(from.time_of_day().total_nanoseconds()) {}
private:
friend std::ostream& operator<<(std::ostream& os, MyDateTime const& dt) {
auto save = os.rdstate();
os << std::dec << std::setfill('0') << std::setw(4) << dt.year << "/"
<< std::setw(2) << dt.month << "/" << std::setw(2) << dt.day << " +"
<< dt.nanosSinceMidnight;
os.setstate(save);
return os;
}
int year;
int month;
int day;
uint64_t nanosSinceMidnight;
};
int main() {
namespace g = boost::gregorian;
namespace p = boost::posix_time;
using p::time_duration;
std::vector<time_duration> terms{p::seconds(30), p::hours(-168),
p::minutes(-15),
p::nanoseconds(60'000'000'000 * 60 * 24)};
for (auto mydt : {MyDateTime{2021, 2, 17}, MyDateTime{2021, 3, 17}}) {
std::cout << "---- Origin: " << mydt << "\n";
for (time_duration term : terms) {
mydt = ptime(mydt) + term;
std::cout << "Result: " << mydt << "\n";
}
};
}
Prints
---- Origin: 2021/02/17 +0
Result: 2021/02/17 +30000000000
Result: 2021/02/10 +30000000000
Result: 2021/02/09 +85530000000000
Result: 2021/02/10 +85530000000000
---- Origin: 2021/03/17 +0
Result: 2021/03/17 +30000000000
Result: 2021/03/10 +30000000000
Result: 2021/03/09 +85530000000000
Result: 2021/03/10 +85530000000000

Related

Get year/month/day h/m/s/ns from Boost local_date_time object?

I have converted a timestamp (UTC) since Epoch to a boost::posix_time::ptime and applied a timezone, resulting in a boost::local_time::local_date_time object. However, I cannot retrieve the year, month, day, hh/mm/ss/ns or nanoseconds since midnight for the adjusted date time.
It appears the local_date_time object doesn't provide getters for these.
I can't get these from the boost::posix_time::ptime because it hasn't been shifted for timezone.
What's the best approach?
using namespace boost::gregorian;
using namespace boost::local_time;
using namespace boost::posix_time;
// Create timezone
tz_database tz_db;
tz_db.load_from_file("libs/date_time/data/date_time_zonespec.csv");
time_zone_ptr chicago_tz = tz_db.time_zone_from_region("America/Chicago");
// Create Epoch offset (seconds)
std::time_t btime_ = nanosSinceEpochUTC / 1E9;
ptime dateTime = boost::posix_time::from_time_t(btime_);
// Create local Chicago time at Epoch offset
const local_date_time chicago(dateTime, chicago_tz);
// I need to retrieve the year/month/day/hh/mm/ss etc from the adjusted time, not the ptime.
The time properties are in the time_of_day() sub object. The representation type of that subobject is time_duration and it has all the accessors you want:
Live On Coliru
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/tz_db_base.hpp>
int main() {
// Create timezone
boost::local_time::tz_database tz_db;
{
std::istringstream iss(R"("America/Chicago","CST","Central Standard Time","CDT","Central Daylight Time","-06:00:00","+01:00:00","2;0;3","+02:00:00","1;0;11","+02:00:00")");
tz_db.load_from_stream(iss);
}
auto chicago_tz = tz_db.time_zone_from_region("America/Chicago");
// Create Epoch offset (seconds)
std::time_t btime_ = 1596241091; // nanosSinceEpochUTC / 1E9;
auto dateTime = boost::posix_time::from_time_t(btime_);
std::cout << "ptime: " << dateTime << "\n";
// Create local Chicago time at Epoch offset
const boost::local_time::local_date_time chicago(dateTime, chicago_tz);
std::cout << "local_date_time: " << chicago << "\n";
// I need to retrieve the year/month/day/hh/mm/ss etc from the adjusted
// time, not the ptime.
std::cout << "year/m/d: " << chicago.local_time().date() << "\n";
auto tod = chicago.local_time().time_of_day();
std::cout << "time_of_day: " << tod << "\n";
std::cout << "hh, mm, ss: " <<
tod.hours() << ", " <<
tod.minutes() << ", " <<
tod.seconds() << "\n";
}
As you can see, it's important to access through the local_time() accessor. Note how all the items (day, month, hours) wrapped back correctly according to the time zone.
Prints:
ptime: 2020-Aug-01 00:18:11
local_date_time: 2020-Jul-31 19:18:11 CDT
year/m/d: 2020-Jul-31
time_of_day: 19:18:11
hh, mm, ss: 19, 18, 11
What I do is as follows: I am naming one class after the name of a time zone. In my case gmt and fetch everything via instances of my very own time as well as date class.
class gmt {
string get(string& s) {
this->dt= this->d.getDay();
this->tm= this->d.getTime();
s= "expires="+this->d.getName(this->d.getDayName())
+", " +this->dt.substr( 0, 2) +"-" +this->d.getMonthName() +"-" +this->dt.substr( 8, 2)
+" " +this->tm +" GMT";
return s;
}
friend class cookie;
string dt, tm;
MyDate d;
};
Okay, it isn't "boost" but you can solve it via its own std and isn't that contrary.

time_t to boost date conversion giving incorrect result

I have a collection of unix timestamps I am converting to boost (1.65.1) dates but the conversions seem to break down when they get too far in the future. Anything around 2040 and beyond seems to be wrapping in some way back to post 1900.
Given the following code...
{
std::time_t t = 1558220400;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
{
std::time_t t = 2145500000;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
{
std::time_t t = 2500000000;
boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
std::cout << "Date: " << date << std::endl;
}
... I get the following output...
Date: 2019-May-18
Date: 2037-Dec-27
Date: 1913-Feb-13
... however I am expecting the following output...
Expected output:
Date: 2019-May-18
Date: 2037-Dec-27
Date: 2049-Mar-22
Is there something I am doing wrong here?
It appears that you're experiencing the Year 2038 problem.
The largest number representable by 32 bit signed integer is 2'147'483'647. 2'147'483'647 seconds since 00:00:00 UTC on 1st of January 1970 (the UNIX epoch) is 03:14:07 UTC on 19th of January 2038. Any UNIX time after that is unrepresentable using a 32 bit signed integer.
Either std::time_t on the system is 32 bits, or it is converted into 32 bits inside the boost library. You can see from the source that boost converts the input into long using static_cast (and still does in version 1.70). long is 32 bits for example on windows, even on 64 bit architectures. It is 64 bits on many other systems such as 64 bit Linux.
In C++20 this can now look like:
#include <chrono>
#include <iostream>
int
main()
{
using namespace std::chrono;
{
std::time_t t = 1558220400;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
{
std::time_t t = 2145500000;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
{
std::time_t t = 2500000000;
auto date = floor<days>(system_clock::from_time_t(t));
std::cout << "Date: " << date << '\n';
}
}
Output:
Date: 2019-05-18
Date: 2037-12-27
Date: 2049-03-22
If your time_t is 32 bits, then the above isn't quite sufficient to fix the problem. In that case, you must avoid the C API completely. This looks like:
{
auto t = 1558220400;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
{
auto t = 2145500000;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
{
auto t = 2500000000;
auto date = floor<days>(sys_seconds{seconds{t}});
std::cout << "Date: " << date << '\n';
}
If your vendor isn't shipping this part of C++20 yet, a free, open-source preview that works with C++11/14/17 is available.1
Just add:
#include "date/date.h"
...
using namespace date;
1 Full disclosure: I am the lead author of this library. I am not pursuing any financial gain from this effort. But sometimes people get grumpy if I don't fully disclose this information.
As noted by eerorika this is caused by integer overflow since the boost::posix_time::from_time_t is casting the 64bit time_t value to a 32 bit long (on Windows).
If you are in a pinch and find yourself in the same position then you can use the following function to perform the conversion:
boost::gregorian::datetimet_to_date(time_t t)
{
auto time = boost::posix_time::ptime(boost::gregorian::date(1970,1,1));
int64_t current_t = t;
long long_max = std::numeric_limits<long>::max();
while(current_t > 0)
{
long seconds_to_add = 0;
if(current_t >= long_max)
seconds_to_add = long_max;
else
seconds_to_add = static_cast<long>(current_t);
current_t -= seconds_to_add;
time += boost::posix_time::seconds(seconds_to_add);
}
return time.date();
}

add_month removed from boost::gregorian?

I'm using boost::gregorian to perform date calculations. I would like to use add_month as per the example (current as of 1.63 http://www.boost.org/doc/libs/1_63_0/doc/html/date_time/examples.html)
/* Simple program that uses the gregorian calendar to progress by exactly
* one month, irregardless of how many days are in that month.
*
* This method can be used as an alternative to iterators
*/
#include "boost/date_time/gregorian/gregorian.hpp"
#include <iostream>
int main()
{
using namespace boost::gregorian;
date d = day_clock::local_day();
add_month mf(1);
date d2 = d + mf.get_offset(d);
std::cout << "Today is: " << to_simple_string(d) << ".\n"
<< "One month from today will be: " << to_simple_string(d2)
<< std::endl;
return 0;
}
However, this gives the error message
month.cpp: In function `int main()':
month.cpp:33:5: error: `add_month' was not declared in this scope
add_month mf(1);
^
month.cpp:35:19: error: `mf' was not declared in this scope
date d2 = d + mf.get_offset(d);
^
Indeed. The example is outdated. In fact I don't remember seeing this feature so it might be long out-of-date.
I recommend the following approach instead:
/* Simple program that uses the gregorian calendar to progress by exactly
* one month, irregardless of how many days are in that month.
*
* This method can be used as an alternative to iterators
*/
#include "boost/date_time/gregorian/gregorian.hpp"
#include "boost/date_time/gregorian/gregorian.hpp"
#include <iostream>
int main()
{
using namespace boost::gregorian;
date d = day_clock::local_day(),
prev = d - months(1),
next = d + months(1);
std::cout << "Today is: " << to_simple_string(d) << ".\n"
<< "One month before today was: " << to_simple_string(prev) << "\n"
<< "One month from today will be: " << to_simple_string(next) << "\n";
}
Which printed (for me):
Today is: 2017-Mar-23.
One month before today was: 2017-Feb-23
One month from today will be: 2017-Apr-23

Convert boost::posix_time::ptime to NTP datestamp

I need to convert a boost::posix_time::ptime into a NTP Datestamp according to
RFC 5905 represented by the following structure:
struct NtpDatestamp {
std::int32_t era_number;
std::uint32_t seconds_since_era_epoch;
std::uint64_t fraction_of_second;
};
RFC 5905 states the following:
To convert system time in any format to NTP date and timestamp formats
requires that the number of seconds s from the prime epoch to the system
time be determined. To determine the integer era and timestamp given s,
era = s / 2^(32) and timestamp = s - era * 2^(32),
which works for positive and negative dates. To determine s given the era
and timestamp,
s = era * 2^(32) + timestamp.
Therefore I've tried the following:
const auto system_time = boost::posix_time::time_from_string("1899-12-31 00:00:00.000");
const boost::posix_time::ptime prime_epoch{boost::gregorian::date{1900, 1, 1}};
// Calculate the number of seconds from the prime epoch to the system time.
const boost::posix_time::time_duration time_duration{system_time - prime_epoch};
const std::int64_t s{time_duration.total_seconds()};
const std::int32_t era_number{static_cast<std::int32_t>(s / std::pow(2, 32))};
const std::uint64_t seconds_since_era_epoch{static_cast<std::uint64_t>(s - s / std::pow(2, 32) * std::pow(2, 32))};
// The fraction of a NTP Datestamp is measured in Attoseconds.
const std::uint64_t fraction_of_second{static_cast<std::uint64_t>(time_duration.total_microseconds() * 1e12)};
But that gives incorrect results.
I am completely stumped with this (actually simple) problem at the moment.
Can someone guide me into the correct direction? How can I obtain the era number, era offset and fraction of a NTP datestamp from a boost::posix_time::ptime?
Edit: Either the calculations in RFC 5905 are not accurate enough or I do misinterpret them. Thanks to the comments I've changed the calculation to the following (this time a complete example):
#include <cmath>
#include <cstdint>
#include <iostream>
#include <boost/date_time.hpp>
int main() {
const auto system_time =
boost::posix_time::time_from_string("1899-12-31 00:00:00.000");
const boost::posix_time::ptime prime_epoch{
boost::gregorian::date{1900, 1, 1}};
// Calculate the number of seconds from the prime epoch to the system time.
const boost::posix_time::time_duration time_duration{prime_epoch -
system_time};
// s is correctly determined now.
std::int64_t s{time_duration.total_seconds()};
if (prime_epoch > system_time) {
// boost::posix_time::time_duration does not take the sign into account.
s *= -1;
}
// TODO(wolters): The following calculations do not return the correct
// results, but the RFC 5905 states them
const std::int32_t era{static_cast<std::int32_t>(s / std::pow(2, 32))};
const std::uint64_t timestamp{
static_cast<std::uint64_t>(s - era * std::pow(2, 32))};
// The fraction of a NTP Datestamp is measured in Attoseconds.
// TODO(wolters): `boost::posix_time::ptime` does NOT resolve to attoseconds,
// but doesn't the target format expect the value to be specified as
// attoseconds? Doesn't the following depend on Boost compile options?
const std::uint64_t fraction{
static_cast<std::uint64_t>(time_duration.fractional_seconds())};
std::cout << "s = " << std::dec << s << '\n';
// TODO(wolters): This does still not match the expected results; taken from
// Figure 4 of https://www.ietf.org/rfc/rfc5905.txt
std::cout << "Era (expected: -1) = " << std::dec << era << '\n';
std::cout << "Timestamp (expected: 4294880896) = " << std::dec << timestamp
<< '\n';
std::cout << "Fraction (expected: 0) = " << std::dec << fraction << '\n';
}
s is calculated correctly now, but the other calculations are wrong. I think I do miss something important completely...
It seems that I've figured out the missing pieces by myself. I've implemented the following algorithm in a reusable class ntp::Datestamp and unit tested it with the reference dates of RFC 5905. All tests finally are green. Here is the solution:
#include <cmath>
#include <cstdint>
#include <ctime>
#include <iostream>
#include <boost/date_time.hpp>
static std::time_t to_time(const boost::posix_time::ptime& time) {
static const boost::posix_time::ptime epoch_time{
boost::gregorian::date{1970, 1, 1}};
const boost::posix_time::time_duration diff{time - epoch_time};
return (diff.ticks() / diff.ticks_per_second());
}
int main() {
const auto system_time =
boost::posix_time::time_from_string("1899-12-31 00:00:00.123");
const boost::posix_time::ptime prime_epoch{
boost::gregorian::date{1900, 1, 1}};
// Calculate the number of seconds from the prime epoch to the system time.
std::time_t s{to_time(system_time) - to_time(prime_epoch)};
const std::int32_t era{static_cast<std::int32_t>(std::floor(s / std::pow(2, 32)))};
const std::uint32_t timestamp{
static_cast<std::uint32_t>(s - era * std::pow(2, 32))};
const std::uint64_t fraction{static_cast<std::uint64_t>(
system_time.time_of_day().fractional_seconds())};
std::cout << "s = " << std::dec << s << '\n';
std::cout << "Era (expected: -1) = " << std::dec << era << '\n';
std::cout << "Timestamp (expected: 4294880896) = " << std::dec << timestamp
<< '\n';
std::cout << "Fraction (expected: 123000) = " << std::dec << fraction << '\n';
}

c++ get years between date chosen by user and actual date(counting days,months,years)

i tried doing this:
struct Den_t
{
int day, month, year;
};
int main()
{
struct Den_t* Datum = new struct Den_t;
struct Den_t* Dnes = new struct Den_t;
time_t theTime = time(NULL);
struct tm aTime;
localtime_s(&aTime, &theTime);
Dnes->day = aTime.tm_mday;
Dnes->month = aTime.tm_mon + 1;
Dnes->year = aTime.tm_yday + 1900;
cin >> Datum->day >> Datum->month >> Datum->year;
if (Dnes->year - Datum->year >= 18 )
cout << "full aged " << endl;
else
cout << "not full aged " << endl;
system("PAUSE");
return 0;
}
but i somehow cant understand what should i even compare and decrement,could someone explain me
what else i need to do to tell people's date for example in float by
comparing year,month and day of actual time and date user inputs in
the program?
You have an issue with your code logic here.
For example:
Datum is 31/12/1982
Dnes is 01/01/2000
The year difference is 18 but the age is 17 and 2 days.
Consider using standard library functions instead of reinventing the wheel.
difftime could be useful, for example
This is a very dirty example, but it would do the work:
time_t dnes;
time(&dnes);
// Set datum here ...
cin >> Datum->day >> Datum->month >> Datum->year;
datum.tm_mday = Datum->day;
datum.tm_mon = Datum->month - 1;
datum.tm_yday = Datum->year - 1900;
datum->tm_yday+=18;
if (difftime(dnes, mktime(&datum)) <0 )
cout << "not full aged " << endl;
else
cout << "full aged " << endl;
Using these libraries:
http://howardhinnant.github.io/date/date.html
http://howardhinnant.github.io/date/tz.html
This is how I would tackle the problem. First the code, then the explanation:
#include "tz.h"
#include "date.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
std::cout << "Enter birthday [day month year]:";
int di, mi, yi;
std::cin >> di >> mi >> yi;
if (std::cin.fail())
{
std::cout << "Invalid date\n";
return 1;
}
auto y = year{yi};
if (!y.ok())
{
std::cout << "Invalid year\n";
return 1;
}
auto m = month(mi);
if (!m.ok())
{
std::cout << "Invalid month\n";
return 1;
}
auto d = day(di);
if (!d.ok())
{
std::cout << "Invalid day\n";
return 1;
}
auto birthday = y/m/d;
if (!birthday.ok())
{
std::cout << "Invalid birthday\n";
return 1;
}
auto local_time = current_zone()->to_local(system_clock::now());
auto today = year_month_day{floor<days>(local_time)};
auto age = today.year() - birthday.year();
if (birthday + age > today)
--age;
if (age >= years{18})
std::cout << "full aged at " << age.count() << "\n";
else
std::cout << "not full aged at " << age.count() << "\n";
}
I would first go to some trouble to check the validity of the user input. What I have below seems like a minimum:
It must be integral input.
Each integral input must have a reasonable value (e.g. month must be in the range [1, 12].
The combination y/m/d must be a valid date.
A more robust program might give the user some feedback on what he input, and give him another chance to correct his mistake.
Once assured we have a valid birthday, we need to get the current date in the local timezone. This:
auto local_time = current_zone()->to_local(system_clock::now());
gets the local time.
This local time can be converted to a local year, month and day with:
auto today = year_month_day{floor<days>(local_time)};
This computation follows the custom that your birthday begins at the local midnight, regardless of what time of day (and where on the planet) you were actually born. In other words, once the local year/month/day is established, this problem is independent of the local time zone, and even the local time of day.
Next, the current age is computed:
auto age = today.year() - birthday.year();
if (birthday + age > today)
--age;
The difference between the years of today and the birthday is a first approximation to the age. This approximation is refined by computing the date on which your birthday falls this year. If this year's birthday is still in the future, then by custom we count that as one year younger. If we were doing something that leaned less towards a legal system, and more towards scientific work, we might well compute in other ways, such as rounding to the nearest year (also easy to do with this library).
If the birthday is on Feb 29, the above code still works: birthday + age will result (75% chance) in an invalid date, e.g.: feb/29/2015. However this invalid date will correctly compare greater than feb/28/2015 and less than mar/1/2015, exactly as we need it to! Invalid dates are your friend, not your enemy -- you just have to know they exist and what to do about them.
Now it is a simple matter to report your findings:
if (age >= years{18})
std::cout << "full aged at " << age.count() << "\n";
else
std::cout << "not full aged at " << age.count() << "\n";