LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-timezone.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 81.7 % 218 178
Test Date: 2026-11-02 18:19:43 Functions: 95.7 % 23 22
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************\
       2              :  * gnc-timezone.cpp - Retrieve timezone information from OS.        *
       3              :  * Copyright 2014 John Ralls <jralls@ceridwen.us>                   *
       4              :  * Based on work done with Arnel Borja for GLib's gtimezone in 2012.*
       5              :  * This program is free software; you can redistribute it and/or    *
       6              :  * modify it under the terms of the GNU General Public License as   *
       7              :  * published by the Free Software Foundation; either version 2 of   *
       8              :  * the License, or (at your option) any later version.              *
       9              :  *                                                                  *
      10              :  * This program is distributed in the hope that it will be useful,  *
      11              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      12              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      13              :  * GNU General Public License for more details.                     *
      14              :  *                                                                  *
      15              :  * You should have received a copy of the GNU General Public License*
      16              :  * along with this program; if not, contact:                        *
      17              :  *                                                                  *
      18              :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      19              :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      20              :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      21              : \********************************************************************/
      22              : 
      23              : #include "gnc-timezone.hpp"
      24              : 
      25              : #include <string>
      26              : #include <cstdint>
      27              : #include <iostream>
      28              : #include <algorithm>
      29              : #include <boost/date_time/gregorian/gregorian.hpp>
      30              : #if PLATFORM(WINDOWS)
      31              : //We'd prefer to use std::codecvt, but it's not supported by gcc until 5.0.
      32              : #include <boost/locale/encoding_utf.hpp>
      33              : #endif
      34              : #include "qoflog.h"
      35              : static const QofLogModule log_module = "gnc-timezone";
      36              : 
      37              : using namespace gnc::date;
      38              : 
      39              : using duration = boost::posix_time::time_duration;
      40              : using time_zone = boost::local_time::custom_time_zone;
      41              : using dst_offsets = boost::local_time::dst_adjustment_offsets;
      42              : using calc_rule_ptr = boost::local_time::dst_calc_rule_ptr;
      43              : using PTZ = boost::local_time::posix_time_zone;
      44              : 
      45              : const unsigned int TimeZoneProvider::min_year = 1400;
      46              : const unsigned int TimeZoneProvider::max_year = 9999;
      47              : 
      48              : template<typename T>
      49              : T*
      50        23718 : endian_swap(T* t)
      51              : {
      52              : #ifndef WORDS_BIGENDIAN
      53        23718 :     auto memp = reinterpret_cast<unsigned char*>(t);
      54        23718 :     std::reverse(memp, memp + sizeof(T));
      55              : #endif
      56        23718 :     return t;
      57              : }
      58              : 
      59              : #if PLATFORM(WINDOWS)
      60              : /* libstdc++ to_string is broken on MinGW with no real interest in fixing it.
      61              :  * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52015
      62              :  */
      63              : #if ! COMPILER(MINGW)
      64              : using std::to_string;
      65              : #else
      66              : template<typename T> inline std::string to_string(T num);
      67              : 
      68              : template<>
      69              : inline std::string
      70              : to_string<unsigned int>(unsigned int num)
      71              : {
      72              :     constexpr unsigned int numchars = sizeof num * 3 + 1;
      73              :     char buf [numchars] {};
      74              :     snprintf (buf, numchars, "%u", num);
      75              :     return std::string(buf);
      76              : }
      77              : 
      78              : template<>
      79              : inline std::string
      80              : to_string<int>(int num)
      81              : {
      82              :     constexpr unsigned int numchars = sizeof num * 3 + 1;
      83              :     char buf [numchars];
      84              :     snprintf (buf, numchars, "%d", num);
      85              :     return std::string(buf);
      86              : }
      87              : #endif
      88              : 
      89              : static std::string
      90              : windows_default_tzname (void)
      91              : {
      92              :   const char *subkey =
      93              :     "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
      94              :   constexpr size_t keysize {128};
      95              :   HKEY key;
      96              :   char key_name[keysize] {};
      97              :   unsigned long tz_keysize = keysize;
      98              :   if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
      99              :                      KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
     100              :     {
     101              :         if (RegQueryValueExA (key, "TimeZoneKeyName", nullptr, nullptr,
     102              :                                 (LPBYTE)key_name, &tz_keysize) != ERROR_SUCCESS)
     103              :         {
     104              :             memset (key_name, 0, tz_keysize);
     105              :         }
     106              :       RegCloseKey (key);
     107              :     }
     108              :   return std::string(key_name);
     109              : }
     110              : 
     111              : typedef   struct
     112              : {
     113              :   LONG Bias;
     114              :   LONG StandardBias;
     115              :   LONG DaylightBias;
     116              :   SYSTEMTIME StandardDate;
     117              :   SYSTEMTIME DaylightDate;
     118              : } RegTZI;
     119              : 
     120              : static time_zone_names
     121              : windows_tz_names (HKEY key)
     122              : {
     123              :     /* The weird sizeof arg is because C++ won't find a type's
     124              :      * element, just an object's.
     125              :      */
     126              :     constexpr auto s_size = sizeof (((TIME_ZONE_INFORMATION*)0)->StandardName);
     127              :     char std_name[s_size];
     128              :     unsigned long size = s_size;
     129              :     if (RegQueryValueExA (key, "Std", NULL, NULL,
     130              :                           (LPBYTE)&(std_name), &size) != ERROR_SUCCESS)
     131              :         throw std::invalid_argument ("Registry contains no standard name.");
     132              : 
     133              :     constexpr auto d_size = sizeof (((TIME_ZONE_INFORMATION*)0)->DaylightName);
     134              :     char dlt_name[d_size];
     135              :     size = d_size;
     136              :     if (RegQueryValueExA (key, "Dlt", NULL, NULL,
     137              :                           (LPBYTE)&(dlt_name), &size) != ERROR_SUCCESS)
     138              :         throw std::invalid_argument ("Registry contains no daylight name.");
     139              : 
     140              :     return time_zone_names (std_name, std_name, dlt_name, dlt_name);
     141              : }
     142              : 
     143              : #define make_week_num(x)  static_cast<boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num>(x)
     144              : 
     145              : static TZ_Ptr
     146              : zone_from_regtzi (const RegTZI& regtzi, time_zone_names names)
     147              : {
     148              :     using ndate = boost::gregorian::nth_day_of_the_week_in_month;
     149              :     using nth_day_rule = boost::local_time::nth_day_of_the_week_in_month_dst_rule;
     150              :     /* Note that Windows runs its biases backwards from POSIX and
     151              :      * boost::date_time: It's the value added to the local time to get
     152              :      * GMT rather than the value added to GMT to get local time; for
     153              :      * the same reason the DaylightBias is negative as one generally
     154              :      * adds an hour less to the local time to get GMT. Biases are in
     155              :      * minutes.
     156              :      */
     157              :     duration std_off (0, regtzi.StandardBias - regtzi.Bias, 0);
     158              :     duration dlt_off (0, -regtzi.DaylightBias, 0);
     159              :     duration start_time (regtzi.StandardDate.wHour, regtzi.StandardDate.wMinute,
     160              :                          regtzi.StandardDate.wSecond);
     161              :     duration end_time (regtzi.DaylightDate.wHour, regtzi.DaylightDate.wMinute,
     162              :                        regtzi.DaylightDate.wSecond);
     163              :     dst_offsets offsets (dlt_off, start_time, end_time);
     164              :     auto std_week_num = make_week_num(regtzi.StandardDate.wDay);
     165              :     auto dlt_week_num = make_week_num(regtzi.DaylightDate.wDay);
     166              :     calc_rule_ptr dates;
     167              :     if (regtzi.StandardDate.wMonth != 0)
     168              :     {
     169              :         try
     170              :         {
     171              :             ndate start (dlt_week_num, regtzi.DaylightDate.wDayOfWeek,
     172              :                          regtzi.DaylightDate.wMonth);
     173              :             ndate end(std_week_num, regtzi.StandardDate.wDayOfWeek,
     174              :                       regtzi.StandardDate.wMonth);
     175              :             dates.reset(new nth_day_rule (start, end));
     176              :         }
     177              :         catch (boost::gregorian::bad_month& err)
     178              :         {
     179              :             PWARN("Caught Bad Month Exception. Daylight Bias: %ld  "
     180              :                   "Standard Month : %d  Daylight Month: %d",
     181              :                   regtzi.DaylightBias, regtzi.StandardDate.wMonth,
     182              :                   regtzi.DaylightDate.wMonth);
     183              :         }
     184              :     }
     185              :     return TZ_Ptr(new time_zone(names, std_off, offsets, dates));
     186              : }
     187              : 
     188              : void
     189              : TimeZoneProvider::load_windows_dynamic_tz (HKEY key, time_zone_names names)
     190              : {
     191              :     DWORD first, last;
     192              : 
     193              :     try
     194              :     {
     195              :         unsigned long size = sizeof first;
     196              :         if (RegQueryValueExA (key, "FirstEntry", NULL, NULL,
     197              :                               (LPBYTE) &first, &size) != ERROR_SUCCESS)
     198              :             throw std::invalid_argument ("No first entry.");
     199              : 
     200              :         size = sizeof last;
     201              :         if (RegQueryValueExA (key, "LastEntry", NULL, NULL,
     202              :                               (LPBYTE) &last, &size) != ERROR_SUCCESS)
     203              :             throw std::invalid_argument ("No last entry.");
     204              : 
     205              :         TZ_Ptr tz {};
     206              :         for (unsigned int year = first; year <= last; year++)
     207              :         {
     208              :             auto s = to_string(year);
     209              :             auto ystr = s.c_str();
     210              :             RegTZI regtzi {};
     211              :             size = sizeof regtzi;
     212              :             auto err_val = RegQueryValueExA (key, ystr, NULL, NULL,
     213              :                                              (LPBYTE) &regtzi, &size);
     214              :             if (err_val != ERROR_SUCCESS)
     215              :             {
     216              :                 break;
     217              :             }
     218              :             tz = zone_from_regtzi (regtzi, names);
     219              :             if (year == first)
     220              :                 m_zone_vector.push_back (std::make_pair(0, tz));
     221              :             m_zone_vector.push_back (std::make_pair(year, tz));
     222              :         }
     223              :         m_zone_vector.push_back (std::make_pair(max_year, tz));
     224              :    }
     225              :     catch (const std::invalid_argument& err)
     226              :     {
     227              :         RegCloseKey (key);
     228              :         throw;
     229              :     }
     230              :     catch (const std::bad_alloc& err)
     231              :     {
     232              :         RegCloseKey (key);
     233              :         throw;
     234              :     }
     235              :     RegCloseKey (key);
     236              : }
     237              : 
     238              : void
     239              : TimeZoneProvider::load_windows_classic_tz (HKEY key, time_zone_names names)
     240              : {
     241              :     RegTZI regtzi {};
     242              :     unsigned long size = sizeof regtzi;
     243              :     try
     244              :     {
     245              :         if (RegQueryValueExA (key, "TZI", NULL, NULL,
     246              :                               (LPBYTE) &regtzi, &size) == ERROR_SUCCESS)
     247              :         {
     248              :             m_zone_vector.push_back(
     249              :                 std::make_pair(max_year, zone_from_regtzi (regtzi, names)));
     250              :         }
     251              :     }
     252              :     catch (const std::bad_alloc& err)
     253              :     {
     254              :         RegCloseKey (key);
     255              :         throw;
     256              :     }
     257              :     RegCloseKey (key);
     258              : }
     259              : 
     260              : void
     261              : TimeZoneProvider::load_windows_default_tz()
     262              : {
     263              :     TIME_ZONE_INFORMATION tzi {};
     264              :     GetTimeZoneInformation (&tzi);
     265              :     RegTZI regtzi { tzi.Bias, tzi.StandardBias, tzi.DaylightBias,
     266              :             tzi.StandardDate, tzi.DaylightDate };
     267              :     using boost::locale::conv::utf_to_utf;
     268              :     auto std_name = utf_to_utf<char>(tzi.StandardName,
     269              :                                 tzi.StandardName + sizeof(tzi.StandardName));
     270              :     auto dlt_name = utf_to_utf<char>(tzi.DaylightName,
     271              :                                 tzi.DaylightName + sizeof(tzi.DaylightName));
     272              :     time_zone_names names (std_name, std_name, dlt_name, dlt_name);
     273              :     m_zone_vector.push_back(std::make_pair(max_year, zone_from_regtzi(regtzi, names)));
     274              : }
     275              : 
     276              : TimeZoneProvider::TimeZoneProvider (const std::string& identifier) :
     277              :     m_zone_vector ()
     278              : {
     279              :     HKEY key;
     280              :     const std::string reg_key =
     281              :         "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
     282              : 
     283              :     auto key_name = (identifier.empty() ? windows_default_tzname () :
     284              :                      identifier);
     285              : 
     286              :     if (key_name.empty())
     287              :     {
     288              :         load_windows_default_tz();
     289              :         return;
     290              :     }
     291              :     std::string subkey = reg_key + key_name;
     292              :     if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey.c_str(), 0,
     293              :                        KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
     294              :         throw std::invalid_argument ("No TZ in registry named " + key_name);
     295              : 
     296              :     time_zone_names names {windows_tz_names (key)};
     297              :     RegCloseKey (key);
     298              : 
     299              :     std::string subkey_dynamic = subkey + "\\Dynamic DST";
     300              :     if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey_dynamic.c_str(), 0,
     301              :                        KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
     302              :         this->load_windows_dynamic_tz (key, names);
     303              :     else if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey.c_str(), 0,
     304              :                             KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
     305              :         this->load_windows_classic_tz (key, names);
     306              :     else
     307              :         throw std::invalid_argument ("No data for TZ " + key_name);
     308              : }
     309              : #elif PLATFORM(POSIX)
     310              : using std::to_string;
     311              : #include <istream>
     312              : #include <cstdlib>
     313              : 
     314              : using boost::posix_time::ptime;
     315              : //To enable using Transition with different meanings for IANA files
     316              : //and for DSTRules.
     317              : namespace IANAParser
     318              : {
     319              :     struct TZHead
     320              :     {
     321              :         char magic[4];
     322              :         char version;
     323              :         uint8_t reserved[15];
     324              :         uint8_t ttisgmtcnt[4];
     325              :         uint8_t ttisstdcnt[4];
     326              :         uint8_t leapcnt[4];
     327              :         uint8_t timecnt[4];
     328              :         uint8_t typecnt[4];
     329              :         uint8_t charcnt[4];
     330              :     };
     331              : 
     332              :     struct TTInfo
     333              :     {
     334              :         int32_t gmtoff;
     335              :         uint8_t isdst;
     336              :         uint8_t abbrind;
     337              :     };
     338              : 
     339              :     struct TZInfo
     340              :     {
     341              :         TTInfo info;
     342              :         std::string name;
     343              :         bool isstd;
     344              :         bool isgmt;
     345              :     };
     346              : 
     347              :     struct Transition
     348              :     {
     349              :         int64_t timestamp;
     350              :         uint8_t index;
     351              :     };
     352              : 
     353              :     static std::unique_ptr<char[]>
     354          118 :     find_tz_file(const std::string& name)
     355              :     {
     356          118 :         std::ifstream ifs;
     357          118 :         auto tzname = name;
     358          118 :         if (tzname.empty())
     359          118 :             if (auto tzenv = std::getenv("TZ"))
     360          236 :                 tzname = std::string(tzenv);
     361              :         //std::cout << "Testing tzname " << tzname << "\n";
     362          118 :         if (!tzname.empty())
     363              :         {
     364              : //POSIX specifies that that identifier should begin with ':', but we
     365              : //should be liberal. If it's there, it's not part of the filename.
     366          118 :             if (tzname[0] == ':')
     367            0 :                 tzname.erase(tzname.begin());
     368          118 :             if (tzname[0] == '/') //Absolute filename
     369              :             {
     370            0 :                 ifs.open(tzname, std::ios::in|std::ios::binary|std::ios::ate);
     371              :             }
     372              :             else
     373              :             {
     374          118 :                 const char* tzdir_c = std::getenv("TZDIR");
     375          118 :                 std::string tzdir = tzdir_c ? tzdir_c : "/usr/share/zoneinfo";
     376              : //Note that we're not checking the filename.
     377          236 :                 ifs.open(std::move(tzdir + "/" + tzname),
     378              :                          std::ios::in|std::ios::binary|std::ios::ate);
     379          118 :             }
     380              :         }
     381              : 
     382          118 :         if (! ifs.is_open())
     383            0 :             throw std::invalid_argument("The timezone string failed to resolve to a valid filename");
     384          118 :         std::streampos filesize = ifs.tellg();
     385          118 :         std::unique_ptr<char[]>fileblock(new char[filesize]);
     386          118 :         ifs.seekg(0, std::ios::beg);
     387          118 :         ifs.read(fileblock.get(), filesize);
     388          118 :         ifs.close();
     389          118 :         return fileblock;
     390          118 :     }
     391              : 
     392              :     using TZInfoVec = std::vector<TZInfo>;
     393              :     using TZInfoIter = TZInfoVec::iterator;
     394              : 
     395              :     struct IANAParser
     396              :     {
     397          118 :         IANAParser(const std::string& name) : IANAParser(find_tz_file(name)) {}
     398              :         IANAParser(std::unique_ptr<char[]>);
     399              :         std::vector<Transition>transitions;
     400              :         TZInfoVec tzinfo;
     401              :         int last_year;
     402              :     };
     403              : 
     404          118 :     IANAParser::IANAParser(std::unique_ptr<char[]>fileblock)
     405              :     {
     406          118 :         unsigned int fb_index = 0;
     407          118 :         TZHead tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
     408              :         static constexpr int ttinfo_size = 6; //struct TTInfo gets padded
     409          118 :         last_year = 2037; //Constrained by 32-bit time_t.
     410          118 :         int transition_size = 4; // length of a transition time in the file
     411              : 
     412          118 :         auto time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
     413          118 :         auto type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
     414          118 :         auto char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
     415          118 :         auto isgmt_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisgmtcnt)));
     416          118 :         auto isstd_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisstdcnt)));
     417          118 :         auto leap_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.leapcnt)));
     418          118 :         if ((tzh.version == '2' || tzh.version == '3'))
     419              :         {
     420          118 :             fb_index = (sizeof(tzh) +
     421          118 :                         (sizeof(uint32_t) + sizeof(uint8_t)) * time_count +
     422          118 :                         ttinfo_size * type_count +
     423          118 :                         sizeof(char) * char_count +
     424          118 :                         sizeof(uint8_t) * isgmt_count +
     425          118 :                         sizeof(uint8_t) * isstd_count +
     426          118 :                         2 * sizeof(uint32_t) * leap_count);
     427              : 
     428              :             //This might change at some point in the probably very
     429              :             //distant future.
     430          118 :             tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
     431          118 :             last_year = 2499;
     432          118 :             time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
     433          118 :             type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
     434          118 :             char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
     435          118 :             transition_size = 8;
     436              :         }
     437          118 :         fb_index += sizeof(tzh);
     438          118 :         auto start_index = fb_index;
     439          118 :         auto info_index_zero = start_index + time_count * transition_size;
     440        22066 :         for(uint32_t index = 0; index < time_count; ++index)
     441              :         {
     442        21948 :             fb_index = start_index + index * transition_size;
     443        21948 :             auto info_index = info_index_zero + index;
     444        21948 :             if (transition_size  == 4)
     445              :             {
     446              :                 int32_t transition_time;
     447              :                 // Ensure correct alignment for ARM.
     448            0 :                 memcpy(&transition_time,
     449            0 :                        endian_swap(reinterpret_cast<int32_t*>(&fileblock[fb_index])),
     450              :                        sizeof(int32_t));
     451            0 :                 auto info = static_cast<uint8_t>(fileblock[info_index]);
     452            0 :                 transitions.push_back({transition_time, info});
     453              :             }
     454              :             else
     455              :             {
     456              :                 int64_t transition_time;
     457              :                 // Ensure correct alignment for ARM.
     458        21948 :                 memcpy(&transition_time, &fileblock[fb_index],
     459              :                        sizeof(int64_t));
     460        21948 :                 transition_time = *(endian_swap(&transition_time));
     461        21948 :                 auto info = static_cast<uint8_t>(fileblock[info_index]);
     462        21948 :                 transitions.push_back({transition_time, info});
     463              :             }
     464              :         }
     465              : 
     466              :         //Add in the tzinfo indexes consumed in the previous loop
     467          118 :         start_index = info_index_zero + time_count;
     468          118 :         auto abbrev = start_index + type_count * ttinfo_size;
     469          118 :         auto std_dist = abbrev + char_count;
     470          118 :         auto gmt_dist = std_dist + type_count;
     471          826 :         for(uint32_t index = 0; index < type_count; ++index)
     472              :         {
     473          708 :             fb_index = start_index + index * ttinfo_size;
     474              :             /* Use memcpy instead of static_cast to avoid memory alignment issues with chars */
     475          708 :             TTInfo info{};
     476          708 :             memcpy(&info, &fileblock[fb_index], ttinfo_size);
     477          708 :             endian_swap(&info.gmtoff);
     478          708 :             tzinfo.push_back(
     479         1416 :                 {info, &fileblock[abbrev + info.abbrind],
     480          708 :          (index < isstd_count ? fileblock[std_dist + index] != '\0' : true),
     481          708 :          (index < isgmt_count ? fileblock[gmt_dist + index] != '\0' : false)});
     482              :         }
     483              : 
     484         3658 :     }
     485              : }
     486              : 
     487              : namespace DSTRule
     488              : {
     489              :     using gregorian_date = boost::gregorian::date;
     490              :     using IANAParser::TZInfoIter;
     491              :     using ndate = boost::gregorian::nth_day_of_the_week_in_month;
     492              :     using week_num =
     493              :         boost::date_time::nth_kday_of_month<boost::gregorian::date>::week_num;
     494              : 
     495              :     struct Transition
     496              :     {
     497          236 :         Transition() : month(1), dow(0), week(static_cast<week_num>(0)) {}
     498              :         Transition(gregorian_date date);
     499              :         bool operator==(const Transition& rhs) const noexcept;
     500              :         ndate get();
     501              :         boost::gregorian::greg_month month;
     502              :         boost::gregorian::greg_weekday dow;
     503              :         week_num week;
     504              :     };
     505              : 
     506        21712 :     Transition::Transition(gregorian_date date) :
     507        21712 :         month(date.month()), dow(date.day_of_week()),
     508        21712 :         week(static_cast<week_num>((6 + date.day() - date.day_of_week()) / 7))
     509        21712 :     {}
     510              : 
     511              :     bool
     512        18762 :     Transition::operator==(const Transition& rhs) const noexcept
     513              :     {
     514        18762 :         return (month == rhs.month && dow == rhs.dow && week == rhs.week);
     515              :     }
     516              : 
     517              :     ndate
     518         8496 :     Transition::get()
     519              :     {
     520         8496 :         return ndate(week, dow, month);
     521              :     }
     522              : 
     523              :     struct DSTRule
     524              :     {
     525              :         DSTRule();
     526              :         DSTRule(TZInfoIter info1, TZInfoIter info2,
     527              :                 ptime date1, ptime date2);
     528              :         bool operator==(const DSTRule& rhs) const noexcept;
     529              :         bool operator!=(const DSTRule& rhs) const noexcept;
     530              :         Transition to_std;
     531              :         Transition to_dst;
     532              :         duration to_std_time;
     533              :         duration to_dst_time;
     534              :         TZInfoIter std_info;
     535              :         TZInfoIter dst_info;
     536              :     };
     537              : 
     538          118 :     DSTRule::DSTRule() : to_std(), to_dst(), to_std_time {}, to_dst_time {},
     539          118 :         std_info (), dst_info () {};
     540              : 
     541        10856 :     DSTRule::DSTRule (TZInfoIter info1, TZInfoIter info2,
     542        10856 :                       ptime date1, ptime date2) :
     543        10856 :         to_std(date1.date()), to_dst(date2.date()),
     544        10856 :         to_std_time(date1.time_of_day()), to_dst_time(date2.time_of_day()),
     545        10856 :         std_info(info1), dst_info(info2)
     546              :     {
     547        21712 :         if (info1->info.isdst == info2->info.isdst)
     548            0 :             throw(std::invalid_argument("Both infos have the same dst value."));
     549        21712 :         if (info1->info.isdst && !info2->info.isdst)
     550              :         {
     551        10856 :             std::swap(to_std, to_dst);
     552        10856 :             std::swap(to_std_time, to_dst_time);
     553        10856 :             std::swap(std_info, dst_info);
     554              :         }
     555              : 
     556              :         /* Documentation notwithstanding, the date-time rules are
     557              :          * looking for local time (wall clock to use the RFC 8538
     558              :          * definition) values.
     559              :          *
     560              :          * The TZ Info contains two fields, isstd and isgmt (renamed
     561              :          * to isut in newer versions of tzinfo). In theory if both are
     562              :          * 0 the transition times represent wall-clock times,
     563              :          * i.e. time stamps in the respective time zone's local time
     564              :          * at the moment of the transition. If isstd is 1 then the
     565              :          * representation is always in standard time instead of
     566              :          * daylight time; this is significant for dst->std
     567              :          * transitions. If isgmt/isut is one then isstd must also be
     568              :          * set and the transition time is in UTC.
     569              :          *
     570              :          * In practice it seems that the timestamps are always in UTC
     571              :          * so the isgmt/isut flag isn't meaningful. The times always
     572              :          * need to have the utc offset added to them to make the
     573              :          * transition occur at the right time; the isstd flag
     574              :          * determines whether that should be the standard offset or
     575              :          * the daylight offset for the daylight->standard transition.
     576              :          */
     577              : 
     578        21712 :         to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
     579        21712 :         if (std_info->isstd) //if isstd always use standard time
     580            0 :             to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
     581              :         else
     582        21712 :             to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
     583              : 
     584        10856 :     }
     585              : 
     586              :     bool
     587        10856 :     DSTRule::operator==(const DSTRule& rhs) const noexcept
     588              :     {
     589        18762 :         return (to_std == rhs.to_std &&
     590         7906 :                 to_dst == rhs.to_dst &&
     591         6608 :                 to_std_time == rhs.to_std_time &&
     592         6608 :                 to_dst_time == rhs.to_dst_time &&
     593        31978 :                 std_info == rhs.std_info &&
     594        24072 :                 dst_info == rhs.dst_info);
     595              :     }
     596              : 
     597              :     bool
     598        10856 :     DSTRule::operator!=(const DSTRule& rhs) const noexcept
     599              :     {
     600        10856 :         return ! operator==(rhs);
     601              :     }
     602              : }
     603              : 
     604              : static TZ_Entry
     605          944 : zone_no_dst(int year, IANAParser::TZInfoIter std_info)
     606              : {
     607         5664 :     time_zone_names names(std_info->name, std_info->name, "", "");
     608          944 :     duration std_off(0, 0, std_info->info.gmtoff);
     609          944 :     dst_offsets offsets({0, 0, 0}, {0, 0, 0}, {0, 0, 0});
     610          944 :     boost::local_time::dst_calc_rule_ptr calc_rule(nullptr);
     611          944 :     TZ_Ptr tz(new time_zone(names, std_off, offsets, calc_rule));
     612         1888 :     return std::make_pair(year, tz);
     613          944 : }
     614              : 
     615              : static TZ_Entry
     616         4248 : zone_from_rule(int year, DSTRule::DSTRule rule)
     617              : {
     618              :     using boost::gregorian::partial_date;
     619              :     using boost::local_time::partial_date_dst_rule;
     620              :     using nth_day_rule =
     621              :         boost::local_time::nth_day_of_the_week_in_month_dst_rule;
     622              : 
     623         4248 :     time_zone_names names(rule.std_info->name, rule.std_info->name,
     624        12744 :                           rule.dst_info->name, rule.dst_info->name);
     625         4248 :     duration std_off(0, 0, rule.std_info->info.gmtoff);
     626              :     duration dlt_off(0, 0,
     627         8496 :                      rule.dst_info->info.gmtoff - rule.std_info->info.gmtoff);
     628         4248 :     dst_offsets offsets(dlt_off, rule.to_dst_time, rule.to_std_time);
     629         4248 :     calc_rule_ptr dates(new nth_day_rule(rule.to_dst.get(), rule.to_std.get()));
     630         4248 :     TZ_Ptr tz(new time_zone(names, std_off, offsets, dates));
     631         8496 :     return std::make_pair(year, tz);
     632         4248 : }
     633              : 
     634              : void
     635          118 : TimeZoneProvider::parse_file(const std::string& tzname)
     636              : {
     637          118 :     IANAParser::IANAParser parser(tzname);
     638              :     using boost::posix_time::hours;
     639          118 :     const auto one_year = hours(366 * 24); //Might be a leap year.
     640          118 :     auto last_info = std::find_if(parser.tzinfo.begin(), parser.tzinfo.end(),
     641          118 :                                   [](IANAParser::TZInfo tz)
     642          118 :                                   {return !tz.info.isdst;});
     643          118 :     auto last_time = ptime();
     644          118 :     DSTRule::DSTRule last_rule;
     645              :     using boost::gregorian::date;
     646              :     using boost::posix_time::ptime;
     647              :     using boost::posix_time::time_duration;
     648          118 :     for (auto txi = parser.transitions.begin();
     649        44132 :          txi != parser.transitions.end(); ++txi)
     650              :     {
     651        43896 :         auto this_info = parser.tzinfo.begin() + txi->index;
     652              : //Can't use boost::posix_date::from_time_t() constructor because it
     653              : //silently casts the time_t to an int32_t.
     654        21948 :         auto this_time = ptime(date(1970, 1, 1),
     655        21948 :                                time_duration(txi->timestamp / 3600, 0,
     656        65844 :                                              txi->timestamp % 3600));
     657              :         /* Note: The "get" function retrieves the last zone with a
     658              :          * year *earlier* than the requested year: Zone periods run
     659              :          * from the saved year to the beginning year of the next zone.
     660              :          */
     661              :         try
     662              :         {
     663        21948 :             auto this_year = this_time.date().year();
     664              :             //Initial case
     665        21948 :             if (last_time.is_not_a_date_time())
     666              :             {
     667          118 :                 m_zone_vector.push_back(zone_no_dst(this_year - 1, last_info));
     668          118 :                 m_zone_vector.push_back(zone_no_dst(this_year, this_info));
     669              :             }
     670              :             // No change in is_dst means a permanent zone change.
     671        43660 :             else if (last_info->info.isdst == this_info->info.isdst)
     672              :             {
     673          118 :                 m_zone_vector.push_back(zone_no_dst(this_year, this_info));
     674              :             }
     675              :             /* If there have been no transitions in at least a year
     676              :              * then we need to create a no-DST rule with last_info to
     677              :              * reflect the frozen timezone.
     678              :              */
     679        21712 :             else if (this_time - last_time > one_year)
     680              :             {
     681          472 :                 auto year = last_time.date().year();
     682          472 :                 if (m_zone_vector.back().first == year)
     683          236 :                     year = year + 1; // no operator ++ or +=, sigh.
     684          472 :                 m_zone_vector.push_back(zone_no_dst(year, last_info));
     685              :             }
     686              :             /* It's been less than a year, so it's probably a DST
     687              :              * cycle. This consumes two transitions so we want only
     688              :              * the return-to-standard-time one to make a DST rule.
     689              :              */
     690        21240 :             else if (!this_info->info.isdst)
     691              :             {
     692              :                 DSTRule::DSTRule new_rule(last_info, this_info,
     693        10856 :                                           last_time, this_time);
     694        10856 :                 if (new_rule != last_rule)
     695              :                 {
     696         4248 :                     last_rule = new_rule;
     697         4248 :                     auto year = last_time.date().year();
     698         4248 :                     m_zone_vector.push_back(zone_from_rule(year, new_rule));
     699              :                 }
     700              :             }
     701              :         }
     702            0 :         catch(const boost::gregorian::bad_year& err)
     703              :         {
     704            0 :             continue;
     705            0 :         } catch (std::out_of_range &)
     706              :         {
     707            0 :             continue;
     708            0 :         }
     709        21948 :         last_time = this_time;
     710        21948 :         last_info = this_info;
     711              :     }
     712              : /* if the transitions end before the end of the zoneinfo coverage
     713              :  * period then the zone rescinded DST and we need a final no-dstzone.
     714              :  */
     715          118 :     if (last_time.is_not_a_date_time())
     716            0 :         m_zone_vector.push_back(zone_no_dst(max_year, last_info));
     717          118 :     else if (last_time.date().year() < parser.last_year)
     718          118 :         m_zone_vector.push_back(zone_no_dst(last_time.date().year(), last_info));
     719          118 : }
     720              : 
     721              : bool
     722          118 : TimeZoneProvider::construct(const std::string& tzname)
     723              : {
     724              :     try
     725              :     {
     726          118 :         parse_file(tzname);
     727              :     }
     728            0 :     catch(const std::invalid_argument& err)
     729              :     {
     730              :         try
     731              :         {
     732            0 :             TZ_Ptr zone(new PTZ(tzname));
     733            0 :             m_zone_vector.push_back(std::make_pair(max_year, zone));
     734            0 :         }
     735            0 :         catch(std::exception& err)
     736              :         {
     737            0 :             return false;
     738            0 :         }
     739            0 :     }
     740          118 :     return true;
     741              : }
     742              : 
     743          118 : TimeZoneProvider::TimeZoneProvider(const std::string& tzname) :  m_zone_vector {}
     744              : {
     745          118 :     if(construct(tzname))
     746          118 :         return;
     747            0 :     DEBUG("%s invalid, trying TZ environment variable.\n", tzname.c_str());
     748            0 :     const char* tz_env = getenv("TZ");
     749            0 :     if(tz_env && construct(tz_env))
     750            0 :         return;
     751            0 :     DEBUG("No valid $TZ, resorting to /etc/localtime.\n");
     752              :     try
     753              :     {
     754            0 :         parse_file("/etc/localtime");
     755              :     }
     756            0 :     catch(const std::invalid_argument& env)
     757              :     {
     758            0 :         DEBUG("/etc/localtime invalid, resorting to GMT.");
     759            0 :         TZ_Ptr zone(new PTZ("UTC0"));
     760            0 :         m_zone_vector.push_back(std::make_pair(max_year, zone));
     761            0 :     }
     762            0 : }
     763              : #endif
     764              : 
     765              : 
     766              : TZ_Ptr
     767        78046 : TimeZoneProvider::get(int year) const noexcept
     768              : {
     769        78046 :     if (m_zone_vector.empty())
     770            0 :         return TZ_Ptr(new PTZ("UTC0"));
     771        78046 :     auto iter = find_if(m_zone_vector.rbegin(), m_zone_vector.rend(),
     772       728445 :                         [=](TZ_Entry e) { return e.first <= year; });
     773        78046 :     if (iter == m_zone_vector.rend())
     774            3 :             return m_zone_vector.front().second;
     775        78043 :     return iter->second;
     776              : }
     777              : 
     778              : void
     779            0 : TimeZoneProvider::dump() const noexcept
     780              : {
     781            0 :     for (const auto& zone : m_zone_vector)
     782            0 :         std::cout << zone.first << ": " << zone.second->to_posix_string() << "\n";
     783            0 : }
        

Generated by: LCOV version 2.0-1