-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Describe the bug
There are certain time zones that used to observe Daylight Savings Time sometime in the past but have since abolished the practice. When trying to convert the time on one of these dates from the past when DST was observed, STL produces incorrect output.
Command-line test case
C:\Temp>type repro.cpp
#include <iostream>
#include <chrono>
#include <format>
using namespace std;
using namespace std::chrono;
int main() {
// Brazil (America/Sao_Paulo): Observed DST from ~Oct/Nov to ~Feb each year.
// Last DST period: Nov 4, 2018 → Feb 17, 2019 (UTC-2 BRST).
auto sp = locate_zone("America/Sao_Paulo");
auto sp1 = local_days{year{2019}/month{1}/day{1}} + hours{13};
cout << "=== America/Sao_Paulo (abolished DST Feb 2019) ===" << endl;
cout << "2019-01-01 13:00 local -> " << format("{:%H:%M}", sp->to_sys(sp1)) << " UTC (expect 15:00, DST UTC-2)" << endl;
auto si = sp->get_info(sp->to_sys(sp1));
cout << " offset=" << si.offset.count() << "s save=" << si.save.count() << "s" << endl;
// Turkey: abolished DST in Sep 2016, permanent UTC+3
// Summer 2016 had DST (UTC+3), standard was UTC+2
auto istanbul = locate_zone("Europe/Istanbul");
auto tur1 = local_days{year{2016}/month{7}/day{1}} + hours{12};
auto tur2 = local_days{year{2015}/month{7}/day{1}} + hours{12};
cout << "\n=== Europe/Istanbul (abolished DST Sep 2016) ===" << endl;
cout << "2016-07-01 12:00 local -> " << format("{:%H:%M}", istanbul->to_sys(tur1)) << " UTC (expect 09:00, DST UTC+3)" << endl;
cout << "2015-07-01 12:00 local -> " << format("{:%H:%M}", istanbul->to_sys(tur2)) << " UTC (expect 09:00, DST UTC+3)" << endl;
auto ti = istanbul->get_info(istanbul->to_sys(tur1));
cout << " offset=" << ti.offset.count() << "s save=" << ti.save.count() << "s" << endl;
// Russia: Summer 2010 had DST UTC+4, standard UTC+3
auto moscow = locate_zone("Europe/Moscow");
auto rus1 = local_days{year{2010}/month{7}/day{1}} + hours{12};
cout << "\n=== Europe/Moscow (complex history) ===" << endl;
cout << "2010-07-01 12:00 local -> " << format("{:%H:%M}", moscow->to_sys(rus1)) << " UTC (expect 08:00, DST UTC+4)" << endl;
auto mi = moscow->get_info(moscow->to_sys(rus1));
cout << " offset=" << mi.offset.count() << "s save=" << mi.save.count() << "s" << endl;
// Morocco: was UTC+0 with DST to UTC+1, went permanent UTC+1 in Oct 2018
// DST was suspended during Ramadan each year, so pick dates outside Ramadan
auto casablanca = locate_zone("Africa/Casablanca");
auto mor1 = local_days{year{2017}/month{4}/day{15}} + hours{12};
auto mor2 = local_days{year{2018}/month{4}/day{15}} + hours{12};
cout << "\n=== Africa/Casablanca (permanent UTC+1 since Oct 2018) ===" << endl;
cout << "2017-04-15 12:00 local -> " << format("{:%H:%M}", casablanca->to_sys(mor1)) << " UTC (expect 11:00, DST UTC+1)" << endl;
cout << "2018-04-15 12:00 local -> " << format("{:%H:%M}", casablanca->to_sys(mor2)) << " UTC (expect 11:00, DST UTC+1)" << endl;
auto ci = casablanca->get_info(casablanca->to_sys(mor1));
cout << " offset=" << ci.offset.count() << "s save=" << ci.save.count() << "s" << endl;
// Control: New York (still has DST) - should be correct
auto ny = locate_zone("America/New_York");
auto ny1 = local_days{year{2020}/month{7}/day{1}} + hours{12};
cout << "\n=== America/New_York (control, still has DST) ===" << endl;
cout << "2020-07-01 12:00 local -> " << format("{:%H:%M}", ny->to_sys(ny1)) << " UTC (expect 16:00, EDT UTC-4)" << endl;
auto ni = ny->get_info(ny->to_sys(ny1));
cout << " offset=" << ni.offset.count() << "s save=" << ni.save.count() << "s" << endl;
return 0;
}
C:\Temp>cl /EHsc /W4 /WX /std:c++20 .\repro.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.36.32522 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
/std:c++20
repro.cpp
Microsoft (R) Incremental Linker Version 14.36.32522.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:repro.exe
repro.obj
C:\Temp>.\repro.exe
=== America/Sao_Paulo (abolished DST Feb 2019) ===
2019-01-01 13:00 local -> 16:00 UTC (expect 15:00, DST UTC-2)
offset=-10800s save=0s
=== Europe/Istanbul (abolished DST Sep 2016) ===
2016-07-01 12:00 local -> 10:00 UTC (expect 09:00, DST UTC+3)
2015-07-01 12:00 local -> 10:00 UTC (expect 09:00, DST UTC+3)
offset=7200s save=0s
=== Europe/Moscow (complex history) ===
2010-07-01 12:00 local -> 09:00 UTC (expect 08:00, DST UTC+4)
offset=10800s save=0s
=== Africa/Casablanca (permanent UTC+1 since Oct 2018) ===
2017-04-15 12:00 local -> 11:00 UTC (expect 11:00, DST UTC+1)
2018-04-15 12:00 local -> 11:00 UTC (expect 11:00, DST UTC+1)
offset=3600s save=60s
=== America/New_York (control, still has DST) ===
2020-07-01 12:00 local -> 16:00 UTC (expect 16:00, EDT UTC-4)
offset=-14400s save=60s
Expected behavior
Mentioned in the output above.
STL version
C:\Temp>cl.exe
Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35222 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
usage: cl [ option... ] filename... [ /link linkoption... ]
Additional context
The problem here is because of STL's misunderstanding of ucal_inDaylightTime API from ICU. According to the documentation, it is used to
Determine if a UCalendar is currently in daylight savings time.
But STL seems to be using it to check whether the calendar was observing DST in the past, which is wrong. Relevant code from tzdb.cpp:
const auto _Is_daylight = __icu_ucal_inDaylightTime(_Cal.get(), &_UErr);
if (U_FAILURE(_UErr)) {
return _Report_error(_Info, __std_tzdb_error::_Icu_error);
}
_Info->_Save = _Is_daylight ? __icu_ucal_get(_Cal.get(), UCalendarDateFields::UCAL_DST_OFFSET, &_UErr) : 0;
if (U_FAILURE(_UErr)) {
return _Report_error(_Info, __std_tzdb_error::_Icu_error);
}STL was probably trying to be defensive but ended up shooting itself in the foot with this one. The suggested fix would be to just not use that guard and directly obtain the DST offset all the time.