I just ran your program on macOS with the latest IANA timezone database and got identical results:
libc++abi: terminating due to uncaught exception of type date::nonexistent_local_time: 2023-03-12 02:00:00 is in a gap between
2023-03-12 02:00:00 EST and
2023-03-12 03:00:00 EDT which are both equivalent to
2023-03-12 07:00:00 UTC
This exception is propagating out of the zonedTime
constructor which has type date::zoned_time
.
It turns out that the timezone "America/New_York" changes from "standard time" to "daylight saving time" on 2023-03-12 02:00:00 EST by increasing its UTC offset from -5h to -4h. As it does this the local time skips from 2am to 3am.
2023-03-12 02:00:00 local time does not exist in this timezone.
You've done nothing wrong, and your program is correct. It is simply that you've hit an exceptional condition.
How you want to handle this exceptional condition is up to you, and you've got options:
- You could reject the input local time 2023-03-12 02:00:00 since it doesn't exist, and move on, say to the next input local time.
- You could create an artificial mapping of local time to UTC such that the entire hour [2am, 3am) maps to 2023-03-12 07:00:00 UTC / 2023-03-12 03:00:00 EDT.
To choose the latter change the zonedTime
construction to:
auto zonedTime = date::make_zoned(timezone, localTime, date::choose::earliest);
Now the exception is suppressed, and the output is:
Original broken-down time: 2023-3-12 2:0
Converted time with DST: 2023-03-12 03:00:00 EDT
With this option, the converted time will be same for all values of the input time from 2:00:00 to 2:59:59.999... .
You could also use date::choose::latest
with the same effects. The choice makes no difference in this example. But when going the other way: from daylight saving to standard, there will be a difference between earliest
and latest
. In this case there will be two mappings from local time to UTC, instead of 0 mappings (the local time happens twice). You can choose to map either to the earliest local time, or the latest local time (ordered by their UTC equivalent).
Bonus Question 1:
Are there any api/function which will tell us if given date time is in
dst or not. Like 2023-03-12 04:00:00 is in dst but Nov 07, 2021 03:00
is not in dst?
Yes. Given a sys_time
or a local_time
, paired with a time_zone
, there is a sys_info
object containing everything there is to know about that instant in time in that timezone.
sys_info
documentation
struct sys_info
{
sys_seconds begin;
sys_seconds end;
std::chrono::seconds offset;
std::chrono::minutes save;
std::string abbrev;
};
If the save
member is 0min
then the associated time_point
/time_zone
is not in daylight saving, else it is.
Example code to extract this information:
#include "date/tz.h"
#include <chrono>
#include <iostream>
template <class Duration>
bool
is_dst(date::zoned_time<Duration> const& zt)
{
using namespace std::literals;
auto info = zt.get_info();
return info.save != 0min;
}
int
main()
{
using namespace date;
using namespace std;
using namespace chrono;
auto tz = locate_zone("America/New_York");
cout << is_dst(zoned_time{tz, local_days{2023_y/3/12} + 4h}) << '\n';
cout << is_dst(zoned_time{tz, local_days{November/7/2021} + 3h}) << '\n';
}
Output:
1
0
Bonus Question 2:
Is there any api which will take input as 2023-03-12 04:00:00 EST but
will return 2023-03-12 05:00:00 EDT?
Sort of. You're not going to like it. For the specific example of EST to EDT, yes. But in general, no.
The reason it works for EST to EDT is that there exists an IANA timezone that goes by the name of EST that is always offset by -5h. But the same is not true of most other timezone abbreviations including EDT.
I can make this specific example work by creating a zoned_time
in the EST time_zone
, and then finding the equivalent time in America/New_York.
#include "date/tz.h"
#include <chrono>
#include <iostream>
int
main()
{
using namespace date;
using namespace std;
using namespace chrono;
zoned_time zt1{"EST", local_days{2023_y/03/12} + 4h};
zoned_time zt2{"America/New_York", zt1};
cout << zt1 << '\n';
cout << zt2 << '\n';
}
Output:
2023-03-12 04:00:00 EST
2023-03-12 05:00:00 EDT
However, in general there is no easy way to convert a "daylight saving" time_point to a "non daylight saving" time_point, or vice-versa.
I say "easy" because there are always low-level hacks that one can do with this library. But there lies the rabbit hole. You'll get into situations that work only sometimes. Maybe even most of the time if you're really unlucky. There's no sure-fire technique.
Bonus Question 3:
How to convert zt2 into broken time (year, month, days, hour, min and
sec)?
// break zt2 into local field values
// get local time_point which is a "datetime"
auto tpl = zt2.get_local_time();
// get date as count of days
auto tpd = floor<days>(tpl);
// get time of day: datetime - date
auto tod = tpl - tpd;
// convert date from {count} data structure to {year, month, day}
year_month_day ymd{tpd};
// convert duration since midnight into {hours, minutes, seconds}
hh_mm_ss hms{tod};
// ymd and hms together have all of the fields.
// There is a getter for each field to get a type safe field
year y = ymd.year();
month m = ymd.month();
day d = ymd.day();
hours h = hms.hours();
minutes M = hms.minutes();
seconds s = hms.seconds();
// Each field has a way to convert to integral types
// This step is considered unsafe. It is better to
// stay within the chrono type system!
int yi{y};
unsigned mi{m};
unsigned di{d};
int64_t hi = h.count();
int64_t Mi = M.count();
int64_t si = s.count();
To break down the sys_time
from zt2
do the same thing, except start with zt2.get_sys_time()
instead of zt2.get_local_time()
.
To go the other way, from type safe fields into a local time_point
:
tpl = local_days{y/m/d} + h + M + s;
Use sys_days
in place of local_days
if you are reversing a sys_time
breakdown.
If you are starting with integral types, each integral type will first have to be explicitly converted into a type safe field, for example:
year{yi} // etc.