LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-timezone.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 82.4 % 216 178
Test Date: 2025-02-07 16:25:45 Functions: 95.7 % 23 22
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     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                 :       22914 : endian_swap(T* t)
      51                 :             : {
      52                 :             : #if ! WORDS_BIGENDIAN
      53                 :       22914 :     auto memp = reinterpret_cast<unsigned char*>(t);
      54                 :       22914 :     std::reverse(memp, memp + sizeof(T));
      55                 :             : #endif
      56                 :       22914 :     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 (std::invalid_argument)
     226                 :             :     {
     227                 :             :         RegCloseKey (key);
     228                 :             :         throw;
     229                 :             :     }
     230                 :             :     catch (std::bad_alloc)
     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 (std::bad_alloc)
     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                 :         114 :     find_tz_file(const std::string& name)
     355                 :             :     {
     356                 :         114 :         std::ifstream ifs;
     357                 :         114 :         auto tzname = name;
     358                 :         114 :         if (tzname.empty())
     359                 :         114 :             if (auto tzenv = std::getenv("TZ"))
     360                 :         228 :                 tzname = std::string(tzenv);
     361                 :             :         //std::cout << "Testing tzname " << tzname << "\n";
     362                 :         114 :         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                 :         114 :             if (tzname[0] == ':')
     367                 :           0 :                 tzname.erase(tzname.begin());
     368                 :         114 :             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                 :         114 :                 const char* tzdir_c = std::getenv("TZDIR");
     375                 :         114 :                 std::string tzdir = tzdir_c ? tzdir_c : "/usr/share/zoneinfo";
     376                 :             : //Note that we're not checking the filename.
     377                 :         114 :                 ifs.open(std::move(tzdir + "/" + tzname),
     378                 :             :                          std::ios::in|std::ios::binary|std::ios::ate);
     379                 :         114 :             }
     380                 :             :         }
     381                 :             : 
     382                 :         114 :         if (! ifs.is_open())
     383                 :           0 :             throw std::invalid_argument("The timezone string failed to resolve to a valid filename");
     384                 :         114 :         std::streampos filesize = ifs.tellg();
     385                 :         114 :         std::unique_ptr<char[]>fileblock(new char[filesize]);
     386                 :         114 :         ifs.seekg(0, std::ios::beg);
     387                 :         114 :         ifs.read(fileblock.get(), filesize);
     388                 :         114 :         ifs.close();
     389                 :         228 :         return fileblock;
     390                 :         114 :     }
     391                 :             : 
     392                 :             :     using TZInfoVec = std::vector<TZInfo>;
     393                 :             :     using TZInfoIter = TZInfoVec::iterator;
     394                 :             : 
     395                 :             :     struct IANAParser
     396                 :             :     {
     397                 :         114 :         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                 :         114 :     IANAParser::IANAParser(std::unique_ptr<char[]>fileblock)
     405                 :             :     {
     406                 :         114 :         unsigned int fb_index = 0;
     407                 :         114 :         TZHead tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
     408                 :             :         static constexpr int ttinfo_size = 6; //struct TTInfo gets padded
     409                 :         114 :         last_year = 2037; //Constrained by 32-bit time_t.
     410                 :         114 :         int transition_size = 4; // length of a transition time in the file
     411                 :             : 
     412                 :         114 :         auto time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
     413                 :         114 :         auto type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
     414                 :         114 :         auto char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
     415                 :         114 :         auto isgmt_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisgmtcnt)));
     416                 :         114 :         auto isstd_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.ttisstdcnt)));
     417                 :         114 :         auto leap_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.leapcnt)));
     418                 :         114 :         if ((tzh.version == '2' || tzh.version == '3'))
     419                 :             :         {
     420                 :         114 :             fb_index = (sizeof(tzh) +
     421                 :         114 :                         (sizeof(uint32_t) + sizeof(uint8_t)) * time_count +
     422                 :         114 :                         ttinfo_size * type_count +
     423                 :         114 :                         sizeof(char) * char_count +
     424                 :         114 :                         sizeof(uint8_t) * isgmt_count +
     425                 :         114 :                         sizeof(uint8_t) * isstd_count +
     426                 :         114 :                         2 * sizeof(uint32_t) * leap_count);
     427                 :             : 
     428                 :             :             //This might change at some point in the probably very
     429                 :             :             //distant future.
     430                 :         114 :             tzh = *reinterpret_cast<TZHead*>(&fileblock[fb_index]);
     431                 :         114 :             last_year = 2499;
     432                 :         114 :             time_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.timecnt)));
     433                 :         114 :             type_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.typecnt)));
     434                 :         114 :             char_count = *(endian_swap(reinterpret_cast<uint32_t*>(tzh.charcnt)));
     435                 :         114 :             transition_size = 8;
     436                 :             :         }
     437                 :         114 :         fb_index += sizeof(tzh);
     438                 :         114 :         auto start_index = fb_index;
     439                 :         114 :         auto info_index_zero = start_index + time_count * transition_size;
     440                 :       21318 :         for(uint32_t index = 0; index < time_count; ++index)
     441                 :             :         {
     442                 :       21204 :             fb_index = start_index + index * transition_size;
     443                 :       21204 :             auto info_index = info_index_zero + index;
     444                 :       21204 :             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                 :       21204 :                 memcpy(&transition_time,
     459                 :       21204 :                        endian_swap(reinterpret_cast<int64_t*>(&fileblock[fb_index])),
     460                 :             :                        sizeof(int64_t));
     461                 :       21204 :                 auto info = static_cast<uint8_t>(fileblock[info_index]);
     462                 :       21204 :                 transitions.push_back({transition_time, info});
     463                 :             :             }
     464                 :             :         }
     465                 :             : 
     466                 :             :         //Add in the tzinfo indexes consumed in the previous loop
     467                 :         114 :         start_index = info_index_zero + time_count;
     468                 :         114 :         auto abbrev = start_index + type_count * ttinfo_size;
     469                 :         114 :         auto std_dist = abbrev + char_count;
     470                 :         114 :         auto gmt_dist = std_dist + type_count;
     471                 :         798 :         for(uint32_t index = 0; index < type_count; ++index)
     472                 :             :         {
     473                 :         684 :             fb_index = start_index + index * ttinfo_size;
     474                 :             :             /* Use memcpy instead of static_cast to avoid memory alignment issues with chars */
     475                 :         684 :             TTInfo info{};
     476                 :         684 :             memcpy(&info, &fileblock[fb_index], ttinfo_size);
     477                 :         684 :             endian_swap(&info.gmtoff);
     478                 :         684 :             tzinfo.push_back(
     479                 :         684 :                 {info, &fileblock[abbrev + info.abbrind],
     480                 :         684 :          (index < isstd_count ? fileblock[std_dist + index] != '\0' : true),
     481                 :         684 :          (index < isgmt_count ? fileblock[gmt_dist + index] != '\0' : false)});
     482                 :             :         }
     483                 :             : 
     484                 :        3534 :     }
     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                 :         228 :         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                 :       20976 :     Transition::Transition(gregorian_date date) :
     507                 :       20976 :         month(date.month()), dow(date.day_of_week()),
     508                 :       20976 :         week(static_cast<week_num>((6 + date.day() - date.day_of_week()) / 7))
     509                 :       20976 :     {}
     510                 :             : 
     511                 :             :     bool
     512                 :       18126 :     Transition::operator==(const Transition& rhs) const noexcept
     513                 :             :     {
     514                 :       18126 :         return (month == rhs.month && dow == rhs.dow && week == rhs.week);
     515                 :             :     }
     516                 :             : 
     517                 :             :     ndate
     518                 :        8208 :     Transition::get()
     519                 :             :     {
     520                 :        8208 :         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                 :         114 :     DSTRule::DSTRule() : to_std(), to_dst(), to_std_time {}, to_dst_time {},
     539                 :         114 :         std_info (), dst_info () {};
     540                 :             : 
     541                 :       10488 :     DSTRule::DSTRule (TZInfoIter info1, TZInfoIter info2,
     542                 :       10488 :                       ptime date1, ptime date2) :
     543                 :       10488 :         to_std(date1.date()), to_dst(date2.date()),
     544                 :       10488 :         to_std_time(date1.time_of_day()), to_dst_time(date2.time_of_day()),
     545                 :       10488 :         std_info(info1), dst_info(info2)
     546                 :             :     {
     547                 :       10488 :         if (info1->info.isdst == info2->info.isdst)
     548                 :           0 :             throw(std::invalid_argument("Both infos have the same dst value."));
     549                 :       10488 :         if (info1->info.isdst && !info2->info.isdst)
     550                 :             :         {
     551                 :       10488 :             std::swap(to_std, to_dst);
     552                 :       10488 :             std::swap(to_std_time, to_dst_time);
     553                 :       10488 :             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                 :       10488 :         to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
     579                 :       10488 :         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                 :       10488 :             to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
     583                 :             : 
     584                 :       10488 :     }
     585                 :             : 
     586                 :             :     bool
     587                 :       10488 :     DSTRule::operator==(const DSTRule& rhs) const noexcept
     588                 :             :     {
     589                 :       18126 :         return (to_std == rhs.to_std &&
     590                 :        7638 :                 to_dst == rhs.to_dst &&
     591                 :        6384 :                 to_std_time == rhs.to_std_time &&
     592                 :       12768 :                 to_dst_time == rhs.to_dst_time &&
     593                 :       24510 :                 std_info == rhs.std_info &&
     594                 :       16872 :                 dst_info == rhs.dst_info);
     595                 :             :     }
     596                 :             : 
     597                 :             :     bool
     598                 :       10488 :     DSTRule::operator!=(const DSTRule& rhs) const noexcept
     599                 :             :     {
     600                 :       10488 :         return ! operator==(rhs);
     601                 :             :     }
     602                 :             : }
     603                 :             : 
     604                 :             : static TZ_Entry
     605                 :         912 : zone_no_dst(int year, IANAParser::TZInfoIter std_info)
     606                 :             : {
     607                 :        2736 :     time_zone_names names(std_info->name, std_info->name, "", "");
     608                 :         912 :     duration std_off(0, 0, std_info->info.gmtoff);
     609                 :         912 :     dst_offsets offsets({0, 0, 0}, {0, 0, 0}, {0, 0, 0});
     610                 :         912 :     boost::local_time::dst_calc_rule_ptr calc_rule(nullptr);
     611                 :         912 :     TZ_Ptr tz(new time_zone(names, std_off, offsets, calc_rule));
     612                 :        1824 :     return std::make_pair(year, tz);
     613                 :         912 : }
     614                 :             : 
     615                 :             : static TZ_Entry
     616                 :        4104 : 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                 :        4104 :     time_zone_names names(rule.std_info->name, rule.std_info->name,
     624                 :        8208 :                           rule.dst_info->name, rule.dst_info->name);
     625                 :        4104 :     duration std_off(0, 0, rule.std_info->info.gmtoff);
     626                 :             :     duration dlt_off(0, 0,
     627                 :        4104 :                      rule.dst_info->info.gmtoff - rule.std_info->info.gmtoff);
     628                 :        4104 :     dst_offsets offsets(dlt_off, rule.to_dst_time, rule.to_std_time);
     629                 :        4104 :     calc_rule_ptr dates(new nth_day_rule(rule.to_dst.get(), rule.to_std.get()));
     630                 :        4104 :     TZ_Ptr tz(new time_zone(names, std_off, offsets, dates));
     631                 :        8208 :     return std::make_pair(year, tz);
     632                 :        4104 : }
     633                 :             : 
     634                 :             : void
     635                 :         114 : TimeZoneProvider::parse_file(const std::string& tzname)
     636                 :             : {
     637                 :         114 :     IANAParser::IANAParser parser(tzname);
     638                 :             :     using boost::posix_time::hours;
     639                 :         114 :     const auto one_year = hours(366 * 24); //Might be a leap year.
     640                 :         114 :     auto last_info = std::find_if(parser.tzinfo.begin(), parser.tzinfo.end(),
     641                 :         114 :                                   [](IANAParser::TZInfo tz)
     642                 :         114 :                                   {return !tz.info.isdst;});
     643                 :         114 :     auto last_time = ptime();
     644                 :         114 :     DSTRule::DSTRule last_rule;
     645                 :             :     using boost::gregorian::date;
     646                 :             :     using boost::posix_time::ptime;
     647                 :             :     using boost::posix_time::time_duration;
     648                 :         114 :     for (auto txi = parser.transitions.begin();
     649                 :       21318 :          txi != parser.transitions.end(); ++txi)
     650                 :             :     {
     651                 :       21204 :         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                 :       21204 :         auto this_time = ptime(date(1970, 1, 1),
     655                 :       42408 :                                time_duration(txi->timestamp / 3600, 0,
     656                 :       42408 :                                              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                 :       21204 :             auto this_year = this_time.date().year();
     664                 :             :             //Initial case
     665                 :       21204 :             if (last_time.is_not_a_date_time())
     666                 :             :             {
     667                 :         114 :                 m_zone_vector.push_back(zone_no_dst(this_year - 1, last_info));
     668                 :         114 :                 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                 :       21090 :             else if (last_info->info.isdst == this_info->info.isdst)
     672                 :             :             {
     673                 :         114 :                 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                 :       20976 :             else if (this_time - last_time > one_year)
     680                 :             :             {
     681                 :         456 :                 auto year = last_time.date().year();
     682                 :         456 :                 if (m_zone_vector.back().first == year)
     683                 :         228 :                     year = year + 1; // no operator ++ or +=, sigh.
     684                 :         456 :                 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                 :       20520 :             else if (!this_info->info.isdst)
     691                 :             :             {
     692                 :             :                 DSTRule::DSTRule new_rule(last_info, this_info,
     693                 :       10488 :                                           last_time, this_time);
     694                 :       10488 :                 if (new_rule != last_rule)
     695                 :             :                 {
     696                 :        4104 :                     last_rule = new_rule;
     697                 :        4104 :                     auto year = last_time.date().year();
     698                 :        4104 :                     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 :         }
     706                 :       21204 :         last_time = this_time;
     707                 :       21204 :         last_info = this_info;
     708                 :             :     }
     709                 :             : /* if the transitions end before the end of the zoneinfo coverage
     710                 :             :  * period then the zone rescinded DST and we need a final no-dstzone.
     711                 :             :  */
     712                 :         114 :     if (last_time.is_not_a_date_time())
     713                 :           0 :         m_zone_vector.push_back(zone_no_dst(max_year, last_info));
     714                 :         114 :     else if (last_time.date().year() < parser.last_year)
     715                 :         114 :         m_zone_vector.push_back(zone_no_dst(last_time.date().year(), last_info));
     716                 :         114 : }
     717                 :             : 
     718                 :             : bool
     719                 :         114 : TimeZoneProvider::construct(const std::string& tzname)
     720                 :             : {
     721                 :             :     try
     722                 :             :     {
     723                 :         114 :         parse_file(tzname);
     724                 :             :     }
     725                 :           0 :     catch(const std::invalid_argument& err)
     726                 :             :     {
     727                 :             :         try
     728                 :             :         {
     729                 :           0 :             TZ_Ptr zone(new PTZ(tzname));
     730                 :           0 :             m_zone_vector.push_back(std::make_pair(max_year, zone));
     731                 :           0 :         }
     732                 :           0 :         catch(std::exception& err)
     733                 :             :         {
     734                 :           0 :             return false;
     735                 :           0 :         }
     736                 :           0 :     }
     737                 :         114 :     return true;
     738                 :             : }
     739                 :             : 
     740                 :         114 : TimeZoneProvider::TimeZoneProvider(const std::string& tzname) :  m_zone_vector {}
     741                 :             : {
     742                 :         114 :     if(construct(tzname))
     743                 :         114 :         return;
     744                 :           0 :     DEBUG("%s invalid, trying TZ environment variable.\n", tzname.c_str());
     745                 :           0 :     const char* tz_env = getenv("TZ");
     746                 :           0 :     if(tz_env && construct(tz_env))
     747                 :           0 :         return;
     748                 :           0 :     DEBUG("No valid $TZ, resorting to /etc/localtime.\n");
     749                 :             :     try
     750                 :             :     {
     751                 :           0 :         parse_file("/etc/localtime");
     752                 :             :     }
     753                 :           0 :     catch(const std::invalid_argument& env)
     754                 :             :     {
     755                 :           0 :         DEBUG("/etc/localtime invalid, resorting to GMT.");
     756                 :           0 :         TZ_Ptr zone(new PTZ("UTC0"));
     757                 :           0 :         m_zone_vector.push_back(std::make_pair(max_year, zone));
     758                 :           0 :     }
     759                 :           0 : }
     760                 :             : #endif
     761                 :             : 
     762                 :             : 
     763                 :             : TZ_Ptr
     764                 :      222204 : TimeZoneProvider::get(int year) const noexcept
     765                 :             : {
     766                 :      222204 :     if (m_zone_vector.empty())
     767                 :           0 :         return TZ_Ptr(new PTZ("UTC0"));
     768                 :      222204 :     auto iter = find_if(m_zone_vector.rbegin(), m_zone_vector.rend(),
     769                 :     4166438 :                         [=](TZ_Entry e) { return e.first <= year; });
     770                 :      222204 :     if (iter == m_zone_vector.rend())
     771                 :           3 :             return m_zone_vector.front().second;
     772                 :      222201 :     return iter->second;
     773                 :             : }
     774                 :             : 
     775                 :             : void
     776                 :           0 : TimeZoneProvider::dump() const noexcept
     777                 :             : {
     778                 :           0 :     for (const auto& zone : m_zone_vector)
     779                 :           0 :         std::cout << zone.first << ": " << zone.second->to_posix_string() << "\n";
     780                 :           0 : }
        

Generated by: LCOV version 2.0-1