LCOV - code coverage report
Current view: top level - Modules - _zoneinfo.c (source / functions) Hit Total Coverage
Test: CPython 3.12 LCOV report [commit acb105a7c1f] Lines: 970 1118 86.8 %
Date: 2022-07-20 13:12:14 Functions: 58 58 100.0 %
Branches: 512 668 76.6 %

           Branch data     Line data    Source code
       1                 :            : #ifndef Py_BUILD_CORE_BUILTIN
       2                 :            : #  define Py_BUILD_CORE_MODULE 1
       3                 :            : #endif
       4                 :            : 
       5                 :            : #include "Python.h"
       6                 :            : #include "pycore_long.h"          // _PyLong_GetOne()
       7                 :            : #include "structmember.h"
       8                 :            : 
       9                 :            : #include <ctype.h>
      10                 :            : #include <stddef.h>
      11                 :            : #include <stdint.h>
      12                 :            : 
      13                 :            : #include "datetime.h"
      14                 :            : 
      15                 :            : // Imports
      16                 :            : static PyObject *io_open = NULL;
      17                 :            : static PyObject *_tzpath_find_tzfile = NULL;
      18                 :            : static PyObject *_common_mod = NULL;
      19                 :            : 
      20                 :            : typedef struct TransitionRuleType TransitionRuleType;
      21                 :            : typedef struct StrongCacheNode StrongCacheNode;
      22                 :            : 
      23                 :            : typedef struct {
      24                 :            :     PyObject *utcoff;
      25                 :            :     PyObject *dstoff;
      26                 :            :     PyObject *tzname;
      27                 :            :     long utcoff_seconds;
      28                 :            : } _ttinfo;
      29                 :            : 
      30                 :            : typedef struct {
      31                 :            :     _ttinfo std;
      32                 :            :     _ttinfo dst;
      33                 :            :     int dst_diff;
      34                 :            :     TransitionRuleType *start;
      35                 :            :     TransitionRuleType *end;
      36                 :            :     unsigned char std_only;
      37                 :            : } _tzrule;
      38                 :            : 
      39                 :            : typedef struct {
      40                 :            :     PyDateTime_TZInfo base;
      41                 :            :     PyObject *key;
      42                 :            :     PyObject *file_repr;
      43                 :            :     PyObject *weakreflist;
      44                 :            :     size_t num_transitions;
      45                 :            :     size_t num_ttinfos;
      46                 :            :     int64_t *trans_list_utc;
      47                 :            :     int64_t *trans_list_wall[2];
      48                 :            :     _ttinfo **trans_ttinfos;  // References to the ttinfo for each transition
      49                 :            :     _ttinfo *ttinfo_before;
      50                 :            :     _tzrule tzrule_after;
      51                 :            :     _ttinfo *_ttinfos;  // Unique array of ttinfos for ease of deallocation
      52                 :            :     unsigned char fixed_offset;
      53                 :            :     unsigned char source;
      54                 :            : } PyZoneInfo_ZoneInfo;
      55                 :            : 
      56                 :            : struct TransitionRuleType {
      57                 :            :     int64_t (*year_to_timestamp)(TransitionRuleType *, int);
      58                 :            : };
      59                 :            : 
      60                 :            : typedef struct {
      61                 :            :     TransitionRuleType base;
      62                 :            :     uint8_t month;
      63                 :            :     uint8_t week;
      64                 :            :     uint8_t day;
      65                 :            :     int8_t hour;
      66                 :            :     int8_t minute;
      67                 :            :     int8_t second;
      68                 :            : } CalendarRule;
      69                 :            : 
      70                 :            : typedef struct {
      71                 :            :     TransitionRuleType base;
      72                 :            :     uint8_t julian;
      73                 :            :     unsigned int day;
      74                 :            :     int8_t hour;
      75                 :            :     int8_t minute;
      76                 :            :     int8_t second;
      77                 :            : } DayRule;
      78                 :            : 
      79                 :            : struct StrongCacheNode {
      80                 :            :     StrongCacheNode *next;
      81                 :            :     StrongCacheNode *prev;
      82                 :            :     PyObject *key;
      83                 :            :     PyObject *zone;
      84                 :            : };
      85                 :            : 
      86                 :            : static PyTypeObject PyZoneInfo_ZoneInfoType;
      87                 :            : 
      88                 :            : // Globals
      89                 :            : static PyObject *TIMEDELTA_CACHE = NULL;
      90                 :            : static PyObject *ZONEINFO_WEAK_CACHE = NULL;
      91                 :            : static StrongCacheNode *ZONEINFO_STRONG_CACHE = NULL;
      92                 :            : static size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = 8;
      93                 :            : 
      94                 :            : static _ttinfo NO_TTINFO = {NULL, NULL, NULL, 0};
      95                 :            : 
      96                 :            : // Constants
      97                 :            : static const int EPOCHORDINAL = 719163;
      98                 :            : static int DAYS_IN_MONTH[] = {
      99                 :            :     -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
     100                 :            : };
     101                 :            : 
     102                 :            : static int DAYS_BEFORE_MONTH[] = {
     103                 :            :     -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
     104                 :            : };
     105                 :            : 
     106                 :            : static const int SOURCE_NOCACHE = 0;
     107                 :            : static const int SOURCE_CACHE = 1;
     108                 :            : static const int SOURCE_FILE = 2;
     109                 :            : 
     110                 :            : // Forward declarations
     111                 :            : static int
     112                 :            : load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj);
     113                 :            : static void
     114                 :            : utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs,
     115                 :            :                  unsigned char *isdsts, size_t num_transitions,
     116                 :            :                  size_t num_ttinfos);
     117                 :            : static int
     118                 :            : ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff,
     119                 :            :             int64_t *trans_local[2], size_t num_ttinfos,
     120                 :            :             size_t num_transitions);
     121                 :            : 
     122                 :            : static int
     123                 :            : parse_tz_str(PyObject *tz_str_obj, _tzrule *out);
     124                 :            : 
     125                 :            : static Py_ssize_t
     126                 :            : parse_abbr(const char *const p, PyObject **abbr);
     127                 :            : static Py_ssize_t
     128                 :            : parse_tz_delta(const char *const p, long *total_seconds);
     129                 :            : static Py_ssize_t
     130                 :            : parse_transition_time(const char *const p, int8_t *hour, int8_t *minute,
     131                 :            :                       int8_t *second);
     132                 :            : static Py_ssize_t
     133                 :            : parse_transition_rule(const char *const p, TransitionRuleType **out);
     134                 :            : 
     135                 :            : static _ttinfo *
     136                 :            : find_tzrule_ttinfo(_tzrule *rule, int64_t ts, unsigned char fold, int year);
     137                 :            : static _ttinfo *
     138                 :            : find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year,
     139                 :            :                            unsigned char *fold);
     140                 :            : 
     141                 :            : static int
     142                 :            : build_ttinfo(long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out);
     143                 :            : static void
     144                 :            : xdecref_ttinfo(_ttinfo *ttinfo);
     145                 :            : static int
     146                 :            : ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1);
     147                 :            : 
     148                 :            : static int
     149                 :            : build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset,
     150                 :            :              long dst_offset, TransitionRuleType *start,
     151                 :            :              TransitionRuleType *end, _tzrule *out);
     152                 :            : static void
     153                 :            : free_tzrule(_tzrule *tzrule);
     154                 :            : 
     155                 :            : static PyObject *
     156                 :            : load_timedelta(long seconds);
     157                 :            : 
     158                 :            : static int
     159                 :            : get_local_timestamp(PyObject *dt, int64_t *local_ts);
     160                 :            : static _ttinfo *
     161                 :            : find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt);
     162                 :            : 
     163                 :            : static int
     164                 :            : ymd_to_ord(int y, int m, int d);
     165                 :            : static int
     166                 :            : is_leap_year(int year);
     167                 :            : 
     168                 :            : static size_t
     169                 :            : _bisect(const int64_t value, const int64_t *arr, size_t size);
     170                 :            : 
     171                 :            : static int
     172                 :            : eject_from_strong_cache(const PyTypeObject *const type, PyObject *key);
     173                 :            : static void
     174                 :            : clear_strong_cache(const PyTypeObject *const type);
     175                 :            : static void
     176                 :            : update_strong_cache(const PyTypeObject *const type, PyObject *key,
     177                 :            :                     PyObject *zone);
     178                 :            : static PyObject *
     179                 :            : zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key);
     180                 :            : 
     181                 :            : static PyObject *
     182                 :        311 : zoneinfo_new_instance(PyTypeObject *type, PyObject *key)
     183                 :            : {
     184                 :        311 :     PyObject *file_obj = NULL;
     185                 :        311 :     PyObject *file_path = NULL;
     186                 :            : 
     187                 :        311 :     file_path = PyObject_CallFunctionObjArgs(_tzpath_find_tzfile, key, NULL);
     188         [ +  + ]:        311 :     if (file_path == NULL) {
     189                 :         20 :         return NULL;
     190                 :            :     }
     191         [ +  + ]:        291 :     else if (file_path == Py_None) {
     192                 :         20 :         file_obj = PyObject_CallMethod(_common_mod, "load_tzdata", "O", key);
     193         [ +  - ]:         20 :         if (file_obj == NULL) {
     194                 :         20 :             Py_DECREF(file_path);
     195                 :         20 :             return NULL;
     196                 :            :         }
     197                 :            :     }
     198                 :            : 
     199                 :        271 :     PyObject *self = (PyObject *)(type->tp_alloc(type, 0));
     200         [ -  + ]:        271 :     if (self == NULL) {
     201                 :          0 :         goto error;
     202                 :            :     }
     203                 :            : 
     204         [ +  - ]:        271 :     if (file_obj == NULL) {
     205                 :        271 :         file_obj = PyObject_CallFunction(io_open, "Os", file_path, "rb");
     206         [ -  + ]:        271 :         if (file_obj == NULL) {
     207                 :          0 :             goto error;
     208                 :            :         }
     209                 :            :     }
     210                 :            : 
     211         [ -  + ]:        271 :     if (load_data((PyZoneInfo_ZoneInfo *)self, file_obj)) {
     212                 :          0 :         goto error;
     213                 :            :     }
     214                 :            : 
     215                 :        271 :     PyObject *rv = PyObject_CallMethod(file_obj, "close", NULL);
     216                 :        271 :     Py_DECREF(file_obj);
     217                 :        271 :     file_obj = NULL;
     218         [ -  + ]:        271 :     if (rv == NULL) {
     219                 :          0 :         goto error;
     220                 :            :     }
     221                 :        271 :     Py_DECREF(rv);
     222                 :            : 
     223                 :        271 :     ((PyZoneInfo_ZoneInfo *)self)->key = key;
     224                 :        271 :     Py_INCREF(key);
     225                 :            : 
     226                 :        271 :     goto cleanup;
     227                 :          0 : error:
     228                 :          0 :     Py_XDECREF(self);
     229                 :          0 :     self = NULL;
     230                 :        271 : cleanup:
     231         [ -  + ]:        271 :     if (file_obj != NULL) {
     232                 :            :         PyObject *exc, *val, *tb;
     233                 :          0 :         PyErr_Fetch(&exc, &val, &tb);
     234                 :          0 :         PyObject *tmp = PyObject_CallMethod(file_obj, "close", NULL);
     235                 :          0 :         _PyErr_ChainExceptions(exc, val, tb);
     236         [ #  # ]:          0 :         if (tmp == NULL) {
     237         [ #  # ]:          0 :             Py_CLEAR(self);
     238                 :            :         }
     239                 :          0 :         Py_XDECREF(tmp);
     240                 :          0 :         Py_DECREF(file_obj);
     241                 :            :     }
     242                 :        271 :     Py_DECREF(file_path);
     243                 :        271 :     return self;
     244                 :            : }
     245                 :            : 
     246                 :            : static PyObject *
     247                 :        478 : get_weak_cache(PyTypeObject *type)
     248                 :            : {
     249         [ +  + ]:        478 :     if (type == &PyZoneInfo_ZoneInfoType) {
     250                 :        284 :         return ZONEINFO_WEAK_CACHE;
     251                 :            :     }
     252                 :            :     else {
     253                 :            :         PyObject *cache =
     254                 :        194 :             PyObject_GetAttrString((PyObject *)type, "_weak_cache");
     255                 :            :         // We are assuming that the type lives at least as long as the function
     256                 :            :         // that calls get_weak_cache, and that it holds a reference to the
     257                 :            :         // cache, so we'll return a "borrowed reference".
     258                 :        194 :         Py_XDECREF(cache);
     259                 :        194 :         return cache;
     260                 :            :     }
     261                 :            : }
     262                 :            : 
     263                 :            : static PyObject *
     264                 :        773 : zoneinfo_new(PyTypeObject *type, PyObject *args, PyObject *kw)
     265                 :            : {
     266                 :        773 :     PyObject *key = NULL;
     267                 :            :     static char *kwlist[] = {"key", NULL};
     268         [ -  + ]:        773 :     if (PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &key) == 0) {
     269                 :          0 :         return NULL;
     270                 :            :     }
     271                 :            : 
     272                 :        773 :     PyObject *instance = zone_from_strong_cache(type, key);
     273   [ +  +  -  + ]:        773 :     if (instance != NULL || PyErr_Occurred()) {
     274                 :        371 :         return instance;
     275                 :            :     }
     276                 :            : 
     277                 :        402 :     PyObject *weak_cache = get_weak_cache(type);
     278                 :        402 :     instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None);
     279         [ -  + ]:        402 :     if (instance == NULL) {
     280                 :          0 :         return NULL;
     281                 :            :     }
     282                 :            : 
     283         [ +  + ]:        402 :     if (instance == Py_None) {
     284                 :        288 :         Py_DECREF(instance);
     285                 :        288 :         PyObject *tmp = zoneinfo_new_instance(type, key);
     286         [ +  + ]:        288 :         if (tmp == NULL) {
     287                 :         40 :             return NULL;
     288                 :            :         }
     289                 :            : 
     290                 :            :         instance =
     291                 :        248 :             PyObject_CallMethod(weak_cache, "setdefault", "OO", key, tmp);
     292                 :        248 :         Py_DECREF(tmp);
     293         [ -  + ]:        248 :         if (instance == NULL) {
     294                 :          0 :             return NULL;
     295                 :            :         }
     296                 :        248 :         ((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE;
     297                 :            :     }
     298                 :            : 
     299                 :        362 :     update_strong_cache(type, key, instance);
     300                 :        362 :     return instance;
     301                 :            : }
     302                 :            : 
     303                 :            : static void
     304                 :        358 : zoneinfo_dealloc(PyObject *obj_self)
     305                 :            : {
     306                 :        358 :     PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self;
     307                 :            : 
     308         [ +  + ]:        358 :     if (self->weakreflist != NULL) {
     309                 :         86 :         PyObject_ClearWeakRefs(obj_self);
     310                 :            :     }
     311                 :            : 
     312         [ +  + ]:        358 :     if (self->trans_list_utc != NULL) {
     313                 :        322 :         PyMem_Free(self->trans_list_utc);
     314                 :            :     }
     315                 :            : 
     316         [ +  + ]:       1074 :     for (size_t i = 0; i < 2; i++) {
     317         [ +  + ]:        716 :         if (self->trans_list_wall[i] != NULL) {
     318                 :        584 :             PyMem_Free(self->trans_list_wall[i]);
     319                 :            :         }
     320                 :            :     }
     321                 :            : 
     322         [ +  + ]:        358 :     if (self->_ttinfos != NULL) {
     323         [ +  + ]:       2076 :         for (size_t i = 0; i < self->num_ttinfos; ++i) {
     324                 :       1754 :             xdecref_ttinfo(&(self->_ttinfos[i]));
     325                 :            :         }
     326                 :        322 :         PyMem_Free(self->_ttinfos);
     327                 :            :     }
     328                 :            : 
     329         [ +  + ]:        358 :     if (self->trans_ttinfos != NULL) {
     330                 :        322 :         PyMem_Free(self->trans_ttinfos);
     331                 :            :     }
     332                 :            : 
     333                 :        358 :     free_tzrule(&(self->tzrule_after));
     334                 :            : 
     335                 :        358 :     Py_XDECREF(self->key);
     336                 :        358 :     Py_XDECREF(self->file_repr);
     337                 :            : 
     338                 :        358 :     Py_TYPE(self)->tp_free((PyObject *)self);
     339                 :        358 : }
     340                 :            : 
     341                 :            : static PyObject *
     342                 :         88 : zoneinfo_from_file(PyTypeObject *type, PyObject *args, PyObject *kwargs)
     343                 :            : {
     344                 :         88 :     PyObject *file_obj = NULL;
     345                 :         88 :     PyObject *file_repr = NULL;
     346                 :         88 :     PyObject *key = Py_None;
     347                 :         88 :     PyZoneInfo_ZoneInfo *self = NULL;
     348                 :            : 
     349                 :            :     static char *kwlist[] = {"", "key", NULL};
     350         [ +  + ]:         88 :     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &file_obj,
     351                 :            :                                      &key)) {
     352                 :          1 :         return NULL;
     353                 :            :     }
     354                 :            : 
     355                 :         87 :     PyObject *obj_self = (PyObject *)(type->tp_alloc(type, 0));
     356                 :         87 :     self = (PyZoneInfo_ZoneInfo *)obj_self;
     357         [ -  + ]:         87 :     if (self == NULL) {
     358                 :          0 :         return NULL;
     359                 :            :     }
     360                 :            : 
     361                 :         87 :     file_repr = PyUnicode_FromFormat("%R", file_obj);
     362         [ -  + ]:         87 :     if (file_repr == NULL) {
     363                 :          0 :         goto error;
     364                 :            :     }
     365                 :            : 
     366         [ +  + ]:         87 :     if (load_data(self, file_obj)) {
     367                 :         36 :         goto error;
     368                 :            :     }
     369                 :            : 
     370                 :         51 :     self->source = SOURCE_FILE;
     371                 :         51 :     self->file_repr = file_repr;
     372                 :         51 :     self->key = key;
     373                 :         51 :     Py_INCREF(key);
     374                 :            : 
     375                 :         51 :     return obj_self;
     376                 :         36 : error:
     377                 :         36 :     Py_XDECREF(file_repr);
     378                 :         36 :     Py_XDECREF(self);
     379                 :         36 :     return NULL;
     380                 :            : }
     381                 :            : 
     382                 :            : static PyObject *
     383                 :         11 : zoneinfo_no_cache(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
     384                 :            : {
     385                 :            :     static char *kwlist[] = {"key", NULL};
     386                 :         11 :     PyObject *key = NULL;
     387         [ -  + ]:         11 :     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &key)) {
     388                 :          0 :         return NULL;
     389                 :            :     }
     390                 :            : 
     391                 :         11 :     PyObject *out = zoneinfo_new_instance(cls, key);
     392         [ +  - ]:         11 :     if (out != NULL) {
     393                 :         11 :         ((PyZoneInfo_ZoneInfo *)out)->source = SOURCE_NOCACHE;
     394                 :            :     }
     395                 :            : 
     396                 :         11 :     return out;
     397                 :            : }
     398                 :            : 
     399                 :            : static PyObject *
     400                 :         77 : zoneinfo_clear_cache(PyObject *cls, PyObject *args, PyObject *kwargs)
     401                 :            : {
     402                 :         77 :     PyObject *only_keys = NULL;
     403                 :            :     static char *kwlist[] = {"only_keys", NULL};
     404                 :            : 
     405         [ +  + ]:         77 :     if (!(PyArg_ParseTupleAndKeywords(args, kwargs, "|$O", kwlist,
     406                 :            :                                       &only_keys))) {
     407                 :          1 :         return NULL;
     408                 :            :     }
     409                 :            : 
     410                 :         76 :     PyTypeObject *type = (PyTypeObject *)cls;
     411                 :         76 :     PyObject *weak_cache = get_weak_cache(type);
     412                 :            : 
     413   [ +  +  +  + ]:        150 :     if (only_keys == NULL || only_keys == Py_None) {
     414                 :         74 :         PyObject *rv = PyObject_CallMethod(weak_cache, "clear", NULL);
     415         [ +  - ]:         74 :         if (rv != NULL) {
     416                 :         74 :             Py_DECREF(rv);
     417                 :            :         }
     418                 :            : 
     419                 :         74 :         clear_strong_cache(type);
     420                 :            :     }
     421                 :            :     else {
     422                 :          2 :         PyObject *item = NULL;
     423                 :          2 :         PyObject *pop = PyUnicode_FromString("pop");
     424         [ -  + ]:          2 :         if (pop == NULL) {
     425                 :          0 :             return NULL;
     426                 :            :         }
     427                 :            : 
     428                 :          2 :         PyObject *iter = PyObject_GetIter(only_keys);
     429         [ -  + ]:          2 :         if (iter == NULL) {
     430                 :          0 :             Py_DECREF(pop);
     431                 :          0 :             return NULL;
     432                 :            :         }
     433                 :            : 
     434         [ +  + ]:          5 :         while ((item = PyIter_Next(iter))) {
     435                 :            :             // Remove from strong cache
     436         [ -  + ]:          3 :             if (eject_from_strong_cache(type, item) < 0) {
     437                 :          0 :                 Py_DECREF(item);
     438                 :          0 :                 break;
     439                 :            :             }
     440                 :            : 
     441                 :            :             // Remove from weak cache
     442                 :          3 :             PyObject *tmp = PyObject_CallMethodObjArgs(weak_cache, pop, item,
     443                 :            :                                                        Py_None, NULL);
     444                 :            : 
     445                 :          3 :             Py_DECREF(item);
     446         [ -  + ]:          3 :             if (tmp == NULL) {
     447                 :          0 :                 break;
     448                 :            :             }
     449                 :          3 :             Py_DECREF(tmp);
     450                 :            :         }
     451                 :          2 :         Py_DECREF(iter);
     452                 :          2 :         Py_DECREF(pop);
     453                 :            :     }
     454                 :            : 
     455         [ -  + ]:         76 :     if (PyErr_Occurred()) {
     456                 :          0 :         return NULL;
     457                 :            :     }
     458                 :            : 
     459                 :         76 :     Py_RETURN_NONE;
     460                 :            : }
     461                 :            : 
     462                 :            : static PyObject *
     463                 :       3025 : zoneinfo_utcoffset(PyObject *self, PyObject *dt)
     464                 :            : {
     465                 :       3025 :     _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt);
     466         [ -  + ]:       3025 :     if (tti == NULL) {
     467                 :          0 :         return NULL;
     468                 :            :     }
     469                 :       3025 :     Py_INCREF(tti->utcoff);
     470                 :       3025 :     return tti->utcoff;
     471                 :            : }
     472                 :            : 
     473                 :            : static PyObject *
     474                 :       2919 : zoneinfo_dst(PyObject *self, PyObject *dt)
     475                 :            : {
     476                 :       2919 :     _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt);
     477         [ -  + ]:       2919 :     if (tti == NULL) {
     478                 :          0 :         return NULL;
     479                 :            :     }
     480                 :       2919 :     Py_INCREF(tti->dstoff);
     481                 :       2919 :     return tti->dstoff;
     482                 :            : }
     483                 :            : 
     484                 :            : static PyObject *
     485                 :       2919 : zoneinfo_tzname(PyObject *self, PyObject *dt)
     486                 :            : {
     487                 :       2919 :     _ttinfo *tti = find_ttinfo((PyZoneInfo_ZoneInfo *)self, dt);
     488         [ -  + ]:       2919 :     if (tti == NULL) {
     489                 :          0 :         return NULL;
     490                 :            :     }
     491                 :       2919 :     Py_INCREF(tti->tzname);
     492                 :       2919 :     return tti->tzname;
     493                 :            : }
     494                 :            : 
     495                 :            : #define GET_DT_TZINFO PyDateTime_DATE_GET_TZINFO
     496                 :            : 
     497                 :            : static PyObject *
     498                 :        307 : zoneinfo_fromutc(PyObject *obj_self, PyObject *dt)
     499                 :            : {
     500         [ +  + ]:        307 :     if (!PyDateTime_Check(dt)) {
     501                 :         16 :         PyErr_SetString(PyExc_TypeError,
     502                 :            :                         "fromutc: argument must be a datetime");
     503                 :         16 :         return NULL;
     504                 :            :     }
     505   [ +  +  +  + ]:        291 :     if (GET_DT_TZINFO(dt) != obj_self) {
     506                 :          8 :         PyErr_SetString(PyExc_ValueError,
     507                 :            :                         "fromutc: dt.tzinfo "
     508                 :            :                         "is not self");
     509                 :          8 :         return NULL;
     510                 :            :     }
     511                 :            : 
     512                 :        283 :     PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self;
     513                 :            : 
     514                 :            :     int64_t timestamp;
     515         [ -  + ]:        283 :     if (get_local_timestamp(dt, &timestamp)) {
     516                 :          0 :         return NULL;
     517                 :            :     }
     518                 :        283 :     size_t num_trans = self->num_transitions;
     519                 :            : 
     520                 :        283 :     _ttinfo *tti = NULL;
     521                 :        283 :     unsigned char fold = 0;
     522                 :            : 
     523   [ +  +  +  + ]:        283 :     if (num_trans >= 1 && timestamp < self->trans_list_utc[0]) {
     524                 :         13 :         tti = self->ttinfo_before;
     525                 :            :     }
     526         [ +  + ]:        270 :     else if (num_trans == 0 ||
     527         [ +  + ]:        181 :              timestamp > self->trans_list_utc[num_trans - 1]) {
     528                 :        124 :         tti = find_tzrule_ttinfo_fromutc(&(self->tzrule_after), timestamp,
     529                 :        124 :                                          PyDateTime_GET_YEAR(dt), &fold);
     530                 :            : 
     531                 :            :         // Immediately after the last manual transition, the fold/gap is
     532                 :            :         // between self->trans_ttinfos[num_transitions - 1] and whatever
     533                 :            :         // ttinfo applies immediately after the last transition, not between
     534                 :            :         // the STD and DST rules in the tzrule_after, so we may need to
     535                 :            :         // adjust the fold value.
     536         [ +  + ]:        124 :         if (num_trans) {
     537                 :         35 :             _ttinfo *tti_prev = NULL;
     538         [ +  + ]:         35 :             if (num_trans == 1) {
     539                 :          1 :                 tti_prev = self->ttinfo_before;
     540                 :            :             }
     541                 :            :             else {
     542                 :         34 :                 tti_prev = self->trans_ttinfos[num_trans - 2];
     543                 :            :             }
     544                 :         35 :             int64_t diff = tti_prev->utcoff_seconds - tti->utcoff_seconds;
     545         [ +  + ]:         35 :             if (diff > 0 &&
     546         [ +  + ]:         16 :                 timestamp < (self->trans_list_utc[num_trans - 1] + diff)) {
     547                 :          4 :                 fold = 1;
     548                 :            :             }
     549                 :            :         }
     550                 :            :     }
     551                 :            :     else {
     552                 :        146 :         size_t idx = _bisect(timestamp, self->trans_list_utc, num_trans);
     553                 :        146 :         _ttinfo *tti_prev = NULL;
     554                 :            : 
     555         [ +  + ]:        146 :         if (idx >= 2) {
     556                 :        127 :             tti_prev = self->trans_ttinfos[idx - 2];
     557                 :        127 :             tti = self->trans_ttinfos[idx - 1];
     558                 :            :         }
     559                 :            :         else {
     560                 :         19 :             tti_prev = self->ttinfo_before;
     561                 :         19 :             tti = self->trans_ttinfos[0];
     562                 :            :         }
     563                 :            : 
     564                 :            :         // Detect fold
     565                 :        146 :         int64_t shift =
     566                 :        146 :             (int64_t)(tti_prev->utcoff_seconds - tti->utcoff_seconds);
     567         [ +  + ]:        146 :         if (shift > (timestamp - self->trans_list_utc[idx - 1])) {
     568                 :         76 :             fold = 1;
     569                 :            :         }
     570                 :            :     }
     571                 :            : 
     572                 :        283 :     PyObject *tmp = PyNumber_Add(dt, tti->utcoff);
     573         [ -  + ]:        283 :     if (tmp == NULL) {
     574                 :          0 :         return NULL;
     575                 :            :     }
     576                 :            : 
     577         [ +  + ]:        283 :     if (fold) {
     578         [ +  + ]:        104 :         if (PyDateTime_CheckExact(tmp)) {
     579                 :         76 :             ((PyDateTime_DateTime *)tmp)->fold = 1;
     580                 :         76 :             dt = tmp;
     581                 :            :         }
     582                 :            :         else {
     583                 :         28 :             PyObject *replace = PyObject_GetAttrString(tmp, "replace");
     584                 :         28 :             PyObject *args = PyTuple_New(0);
     585                 :         28 :             PyObject *kwargs = PyDict_New();
     586                 :            : 
     587                 :         28 :             Py_DECREF(tmp);
     588   [ +  -  +  -  :         28 :             if (args == NULL || kwargs == NULL || replace == NULL) {
                   -  + ]
     589                 :          0 :                 Py_XDECREF(args);
     590                 :          0 :                 Py_XDECREF(kwargs);
     591                 :          0 :                 Py_XDECREF(replace);
     592                 :          0 :                 return NULL;
     593                 :            :             }
     594                 :            : 
     595                 :         28 :             dt = NULL;
     596         [ +  - ]:         28 :             if (!PyDict_SetItemString(kwargs, "fold", _PyLong_GetOne())) {
     597                 :         28 :                 dt = PyObject_Call(replace, args, kwargs);
     598                 :            :             }
     599                 :            : 
     600                 :         28 :             Py_DECREF(args);
     601                 :         28 :             Py_DECREF(kwargs);
     602                 :         28 :             Py_DECREF(replace);
     603                 :            : 
     604         [ -  + ]:         28 :             if (dt == NULL) {
     605                 :          0 :                 return NULL;
     606                 :            :             }
     607                 :            :         }
     608                 :            :     }
     609                 :            :     else {
     610                 :        179 :         dt = tmp;
     611                 :            :     }
     612                 :        283 :     return dt;
     613                 :            : }
     614                 :            : 
     615                 :            : static PyObject *
     616                 :         20 : zoneinfo_repr(PyZoneInfo_ZoneInfo *self)
     617                 :            : {
     618                 :         20 :     PyObject *rv = NULL;
     619                 :         20 :     const char *type_name = Py_TYPE((PyObject *)self)->tp_name;
     620         [ +  + ]:         20 :     if (!(self->key == Py_None)) {
     621                 :          8 :         rv = PyUnicode_FromFormat("%s(key=%R)", type_name, self->key);
     622                 :            :     }
     623                 :            :     else {
     624                 :            :         assert(PyUnicode_Check(self->file_repr));
     625                 :         12 :         rv = PyUnicode_FromFormat("%s.from_file(%U)", type_name,
     626                 :            :                                   self->file_repr);
     627                 :            :     }
     628                 :            : 
     629                 :         20 :     return rv;
     630                 :            : }
     631                 :            : 
     632                 :            : static PyObject *
     633                 :         44 : zoneinfo_str(PyZoneInfo_ZoneInfo *self)
     634                 :            : {
     635         [ +  + ]:         44 :     if (!(self->key == Py_None)) {
     636                 :         40 :         Py_INCREF(self->key);
     637                 :         40 :         return self->key;
     638                 :            :     }
     639                 :            :     else {
     640                 :          4 :         return zoneinfo_repr(self);
     641                 :            :     }
     642                 :            : }
     643                 :            : 
     644                 :            : /* Pickles the ZoneInfo object by key and source.
     645                 :            :  *
     646                 :            :  * ZoneInfo objects are pickled by reference to the TZif file that they came
     647                 :            :  * from, which means that the exact transitions may be different or the file
     648                 :            :  * may not un-pickle if the data has changed on disk in the interim.
     649                 :            :  *
     650                 :            :  * It is necessary to include a bit indicating whether or not the object
     651                 :            :  * was constructed from the cache, because from-cache objects will hit the
     652                 :            :  * unpickling process's cache, whereas no-cache objects will bypass it.
     653                 :            :  *
     654                 :            :  * Objects constructed from ZoneInfo.from_file cannot be pickled.
     655                 :            :  */
     656                 :            : static PyObject *
     657                 :         54 : zoneinfo_reduce(PyObject *obj_self, PyObject *unused)
     658                 :            : {
     659                 :         54 :     PyZoneInfo_ZoneInfo *self = (PyZoneInfo_ZoneInfo *)obj_self;
     660         [ +  + ]:         54 :     if (self->source == SOURCE_FILE) {
     661                 :            :         // Objects constructed from files cannot be pickled.
     662                 :            :         PyObject *pickle_error =
     663                 :         18 :             _PyImport_GetModuleAttrString("pickle", "PicklingError");
     664         [ -  + ]:         18 :         if (pickle_error == NULL) {
     665                 :          0 :             return NULL;
     666                 :            :         }
     667                 :            : 
     668                 :         18 :         PyErr_Format(pickle_error,
     669                 :            :                      "Cannot pickle a ZoneInfo file from a file stream.");
     670                 :         18 :         Py_DECREF(pickle_error);
     671                 :         18 :         return NULL;
     672                 :            :     }
     673                 :            : 
     674                 :         36 :     unsigned char from_cache = self->source == SOURCE_CACHE ? 1 : 0;
     675                 :         36 :     PyObject *constructor = PyObject_GetAttrString(obj_self, "_unpickle");
     676                 :            : 
     677         [ -  + ]:         36 :     if (constructor == NULL) {
     678                 :          0 :         return NULL;
     679                 :            :     }
     680                 :            : 
     681                 :         36 :     PyObject *rv = Py_BuildValue("O(OB)", constructor, self->key, from_cache);
     682                 :         36 :     Py_DECREF(constructor);
     683                 :         36 :     return rv;
     684                 :            : }
     685                 :            : 
     686                 :            : static PyObject *
     687                 :         54 : zoneinfo__unpickle(PyTypeObject *cls, PyObject *args)
     688                 :            : {
     689                 :            :     PyObject *key;
     690                 :            :     unsigned char from_cache;
     691         [ -  + ]:         54 :     if (!PyArg_ParseTuple(args, "OB", &key, &from_cache)) {
     692                 :          0 :         return NULL;
     693                 :            :     }
     694                 :            : 
     695         [ +  + ]:         54 :     if (from_cache) {
     696                 :         42 :         PyObject *val_args = Py_BuildValue("(O)", key);
     697         [ -  + ]:         42 :         if (val_args == NULL) {
     698                 :          0 :             return NULL;
     699                 :            :         }
     700                 :            : 
     701                 :         42 :         PyObject *rv = zoneinfo_new(cls, val_args, NULL);
     702                 :            : 
     703                 :         42 :         Py_DECREF(val_args);
     704                 :         42 :         return rv;
     705                 :            :     }
     706                 :            :     else {
     707                 :         12 :         return zoneinfo_new_instance(cls, key);
     708                 :            :     }
     709                 :            : }
     710                 :            : 
     711                 :            : /* It is relatively expensive to construct new timedelta objects, and in most
     712                 :            :  * cases we're looking at a relatively small number of timedeltas, such as
     713                 :            :  * integer number of hours, etc. We will keep a cache so that we construct
     714                 :            :  * a minimal number of these.
     715                 :            :  *
     716                 :            :  * Possibly this should be replaced with an LRU cache so that it's not possible
     717                 :            :  * for the memory usage to explode from this, but in order for this to be a
     718                 :            :  * serious problem, one would need to deliberately craft a malicious time zone
     719                 :            :  * file with many distinct offsets. As of tzdb 2019c, loading every single zone
     720                 :            :  * fills the cache with ~450 timedeltas for a total size of ~12kB.
     721                 :            :  *
     722                 :            :  * This returns a new reference to the timedelta.
     723                 :            :  */
     724                 :            : static PyObject *
     725                 :       4478 : load_timedelta(long seconds)
     726                 :            : {
     727                 :            :     PyObject *rv;
     728                 :       4478 :     PyObject *pyoffset = PyLong_FromLong(seconds);
     729         [ -  + ]:       4478 :     if (pyoffset == NULL) {
     730                 :          0 :         return NULL;
     731                 :            :     }
     732                 :       4478 :     rv = PyDict_GetItemWithError(TIMEDELTA_CACHE, pyoffset);
     733         [ +  + ]:       4478 :     if (rv == NULL) {
     734         [ -  + ]:         31 :         if (PyErr_Occurred()) {
     735                 :          0 :             goto error;
     736                 :            :         }
     737                 :         31 :         PyObject *tmp = PyDateTimeAPI->Delta_FromDelta(
     738                 :         31 :             0, seconds, 0, 1, PyDateTimeAPI->DeltaType);
     739                 :            : 
     740         [ -  + ]:         31 :         if (tmp == NULL) {
     741                 :          0 :             goto error;
     742                 :            :         }
     743                 :            : 
     744                 :         31 :         rv = PyDict_SetDefault(TIMEDELTA_CACHE, pyoffset, tmp);
     745                 :         31 :         Py_DECREF(tmp);
     746                 :            :     }
     747                 :            : 
     748                 :       4478 :     Py_XINCREF(rv);
     749                 :       4478 :     Py_DECREF(pyoffset);
     750                 :       4478 :     return rv;
     751                 :          0 : error:
     752                 :          0 :     Py_DECREF(pyoffset);
     753                 :          0 :     return NULL;
     754                 :            : }
     755                 :            : 
     756                 :            : /* Constructor for _ttinfo object - this starts by initializing the _ttinfo
     757                 :            :  * to { NULL, NULL, NULL }, so that Py_XDECREF will work on partially
     758                 :            :  * initialized _ttinfo objects.
     759                 :            :  */
     760                 :            : static int
     761                 :       2239 : build_ttinfo(long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out)
     762                 :            : {
     763                 :       2239 :     out->utcoff = NULL;
     764                 :       2239 :     out->dstoff = NULL;
     765                 :       2239 :     out->tzname = NULL;
     766                 :            : 
     767                 :       2239 :     out->utcoff_seconds = utcoffset;
     768                 :       2239 :     out->utcoff = load_timedelta(utcoffset);
     769         [ -  + ]:       2239 :     if (out->utcoff == NULL) {
     770                 :          0 :         return -1;
     771                 :            :     }
     772                 :            : 
     773                 :       2239 :     out->dstoff = load_timedelta(dstoffset);
     774         [ -  + ]:       2239 :     if (out->dstoff == NULL) {
     775                 :          0 :         return -1;
     776                 :            :     }
     777                 :            : 
     778                 :       2239 :     out->tzname = tzname;
     779                 :       2239 :     Py_INCREF(tzname);
     780                 :            : 
     781                 :       2239 :     return 0;
     782                 :            : }
     783                 :            : 
     784                 :            : /* Decrease reference count on any non-NULL members of a _ttinfo  */
     785                 :            : static void
     786                 :       2314 : xdecref_ttinfo(_ttinfo *ttinfo)
     787                 :            : {
     788         [ +  - ]:       2314 :     if (ttinfo != NULL) {
     789                 :       2314 :         Py_XDECREF(ttinfo->utcoff);
     790                 :       2314 :         Py_XDECREF(ttinfo->dstoff);
     791                 :       2314 :         Py_XDECREF(ttinfo->tzname);
     792                 :            :     }
     793                 :       2314 : }
     794                 :            : 
     795                 :            : /* Equality function for _ttinfo. */
     796                 :            : static int
     797                 :         11 : ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1)
     798                 :            : {
     799                 :            :     int rv;
     800         [ -  + ]:         11 :     if ((rv = PyObject_RichCompareBool(tti0->utcoff, tti1->utcoff, Py_EQ)) <
     801                 :            :         1) {
     802                 :          0 :         goto end;
     803                 :            :     }
     804                 :            : 
     805         [ -  + ]:         11 :     if ((rv = PyObject_RichCompareBool(tti0->dstoff, tti1->dstoff, Py_EQ)) <
     806                 :            :         1) {
     807                 :          0 :         goto end;
     808                 :            :     }
     809                 :            : 
     810         [ +  - ]:         11 :     if ((rv = PyObject_RichCompareBool(tti0->tzname, tti1->tzname, Py_EQ)) <
     811                 :            :         1) {
     812                 :          0 :         goto end;
     813                 :            :     }
     814                 :         11 : end:
     815                 :         11 :     return rv;
     816                 :            : }
     817                 :            : 
     818                 :            : /* Given a file-like object, this populates a ZoneInfo object
     819                 :            :  *
     820                 :            :  * The current version calls into a Python function to read the data from
     821                 :            :  * file into Python objects, and this translates those Python objects into
     822                 :            :  * C values and calculates derived values (e.g. dstoff) in C.
     823                 :            :  *
     824                 :            :  * This returns 0 on success and -1 on failure.
     825                 :            :  *
     826                 :            :  * The function will never return while `self` is partially initialized —
     827                 :            :  * the object only needs to be freed / deallocated if this succeeds.
     828                 :            :  */
     829                 :            : static int
     830                 :        358 : load_data(PyZoneInfo_ZoneInfo *self, PyObject *file_obj)
     831                 :            : {
     832                 :        358 :     PyObject *data_tuple = NULL;
     833                 :            : 
     834                 :        358 :     long *utcoff = NULL;
     835                 :        358 :     long *dstoff = NULL;
     836                 :        358 :     size_t *trans_idx = NULL;
     837                 :        358 :     unsigned char *isdst = NULL;
     838                 :            : 
     839                 :        358 :     self->trans_list_utc = NULL;
     840                 :        358 :     self->trans_list_wall[0] = NULL;
     841                 :        358 :     self->trans_list_wall[1] = NULL;
     842                 :        358 :     self->trans_ttinfos = NULL;
     843                 :        358 :     self->_ttinfos = NULL;
     844                 :        358 :     self->file_repr = NULL;
     845                 :            : 
     846                 :        358 :     size_t ttinfos_allocated = 0;
     847                 :            : 
     848                 :        358 :     data_tuple = PyObject_CallMethod(_common_mod, "load_data", "O", file_obj);
     849                 :            : 
     850         [ +  + ]:        358 :     if (data_tuple == NULL) {
     851                 :          8 :         goto error;
     852                 :            :     }
     853                 :            : 
     854         [ -  + ]:        350 :     if (!PyTuple_CheckExact(data_tuple)) {
     855                 :          0 :         PyErr_Format(PyExc_TypeError, "Invalid data result type: %r",
     856                 :            :                      data_tuple);
     857                 :          0 :         goto error;
     858                 :            :     }
     859                 :            : 
     860                 :            :     // Unpack the data tuple
     861                 :        350 :     PyObject *trans_idx_list = PyTuple_GetItem(data_tuple, 0);
     862         [ -  + ]:        350 :     if (trans_idx_list == NULL) {
     863                 :          0 :         goto error;
     864                 :            :     }
     865                 :            : 
     866                 :        350 :     PyObject *trans_utc = PyTuple_GetItem(data_tuple, 1);
     867         [ -  + ]:        350 :     if (trans_utc == NULL) {
     868                 :          0 :         goto error;
     869                 :            :     }
     870                 :            : 
     871                 :        350 :     PyObject *utcoff_list = PyTuple_GetItem(data_tuple, 2);
     872         [ -  + ]:        350 :     if (utcoff_list == NULL) {
     873                 :          0 :         goto error;
     874                 :            :     }
     875                 :            : 
     876                 :        350 :     PyObject *isdst_list = PyTuple_GetItem(data_tuple, 3);
     877         [ -  + ]:        350 :     if (isdst_list == NULL) {
     878                 :          0 :         goto error;
     879                 :            :     }
     880                 :            : 
     881                 :        350 :     PyObject *abbr = PyTuple_GetItem(data_tuple, 4);
     882         [ -  + ]:        350 :     if (abbr == NULL) {
     883                 :          0 :         goto error;
     884                 :            :     }
     885                 :            : 
     886                 :        350 :     PyObject *tz_str = PyTuple_GetItem(data_tuple, 5);
     887         [ -  + ]:        350 :     if (tz_str == NULL) {
     888                 :          0 :         goto error;
     889                 :            :     }
     890                 :            : 
     891                 :            :     // Load the relevant sizes
     892                 :        350 :     Py_ssize_t num_transitions = PyTuple_Size(trans_utc);
     893         [ -  + ]:        350 :     if (num_transitions < 0) {
     894                 :          0 :         goto error;
     895                 :            :     }
     896                 :            : 
     897                 :        350 :     Py_ssize_t num_ttinfos = PyTuple_Size(utcoff_list);
     898         [ -  + ]:        350 :     if (num_ttinfos < 0) {
     899                 :          0 :         goto error;
     900                 :            :     }
     901                 :            : 
     902                 :        350 :     self->num_transitions = (size_t)num_transitions;
     903                 :        350 :     self->num_ttinfos = (size_t)num_ttinfos;
     904                 :            : 
     905                 :            :     // Load the transition indices and list
     906                 :        350 :     self->trans_list_utc =
     907                 :        350 :         PyMem_Malloc(self->num_transitions * sizeof(int64_t));
     908         [ -  + ]:        350 :     if (self->trans_list_utc == NULL) {
     909                 :          0 :         goto error;
     910                 :            :     }
     911                 :        350 :     trans_idx = PyMem_Malloc(self->num_transitions * sizeof(Py_ssize_t));
     912         [ -  + ]:        350 :     if (trans_idx == NULL) {
     913                 :          0 :         goto error;
     914                 :            :     }
     915                 :            : 
     916         [ +  + ]:      39649 :     for (size_t i = 0; i < self->num_transitions; ++i) {
     917                 :      39299 :         PyObject *num = PyTuple_GetItem(trans_utc, i);
     918         [ -  + ]:      39299 :         if (num == NULL) {
     919                 :          0 :             goto error;
     920                 :            :         }
     921                 :      39299 :         self->trans_list_utc[i] = PyLong_AsLongLong(num);
     922   [ -  +  -  - ]:      39299 :         if (self->trans_list_utc[i] == -1 && PyErr_Occurred()) {
     923                 :          0 :             goto error;
     924                 :            :         }
     925                 :            : 
     926                 :      39299 :         num = PyTuple_GetItem(trans_idx_list, i);
     927         [ -  + ]:      39299 :         if (num == NULL) {
     928                 :          0 :             goto error;
     929                 :            :         }
     930                 :            : 
     931                 :      39299 :         Py_ssize_t cur_trans_idx = PyLong_AsSsize_t(num);
     932         [ -  + ]:      39299 :         if (cur_trans_idx == -1) {
     933                 :          0 :             goto error;
     934                 :            :         }
     935                 :            : 
     936                 :      39299 :         trans_idx[i] = (size_t)cur_trans_idx;
     937         [ -  + ]:      39299 :         if (trans_idx[i] > self->num_ttinfos) {
     938                 :          0 :             PyErr_Format(
     939                 :            :                 PyExc_ValueError,
     940                 :            :                 "Invalid transition index found while reading TZif: %zd",
     941                 :            :                 cur_trans_idx);
     942                 :            : 
     943                 :          0 :             goto error;
     944                 :            :         }
     945                 :            :     }
     946                 :            : 
     947                 :            :     // Load UTC offsets and isdst (size num_ttinfos)
     948                 :        350 :     utcoff = PyMem_Malloc(self->num_ttinfos * sizeof(long));
     949                 :        350 :     isdst = PyMem_Malloc(self->num_ttinfos * sizeof(unsigned char));
     950                 :            : 
     951   [ +  -  -  + ]:        350 :     if (utcoff == NULL || isdst == NULL) {
     952                 :          0 :         goto error;
     953                 :            :     }
     954         [ +  + ]:       2104 :     for (size_t i = 0; i < self->num_ttinfos; ++i) {
     955                 :       1754 :         PyObject *num = PyTuple_GetItem(utcoff_list, i);
     956         [ -  + ]:       1754 :         if (num == NULL) {
     957                 :          0 :             goto error;
     958                 :            :         }
     959                 :            : 
     960                 :       1754 :         utcoff[i] = PyLong_AsLong(num);
     961   [ -  +  -  - ]:       1754 :         if (utcoff[i] == -1 && PyErr_Occurred()) {
     962                 :          0 :             goto error;
     963                 :            :         }
     964                 :            : 
     965                 :       1754 :         num = PyTuple_GetItem(isdst_list, i);
     966         [ -  + ]:       1754 :         if (num == NULL) {
     967                 :          0 :             goto error;
     968                 :            :         }
     969                 :            : 
     970                 :       1754 :         int isdst_with_error = PyObject_IsTrue(num);
     971         [ -  + ]:       1754 :         if (isdst_with_error == -1) {
     972                 :          0 :             goto error;
     973                 :            :         }
     974                 :            :         else {
     975                 :       1754 :             isdst[i] = (unsigned char)isdst_with_error;
     976                 :            :         }
     977                 :            :     }
     978                 :            : 
     979                 :        350 :     dstoff = PyMem_Calloc(self->num_ttinfos, sizeof(long));
     980         [ -  + ]:        350 :     if (dstoff == NULL) {
     981                 :          0 :         goto error;
     982                 :            :     }
     983                 :            : 
     984                 :            :     // Derive dstoff and trans_list_wall from the information we've loaded
     985                 :        350 :     utcoff_to_dstoff(trans_idx, utcoff, dstoff, isdst, self->num_transitions,
     986                 :            :                      self->num_ttinfos);
     987                 :            : 
     988         [ -  + ]:        350 :     if (ts_to_local(trans_idx, self->trans_list_utc, utcoff,
     989                 :        350 :                     self->trans_list_wall, self->num_ttinfos,
     990                 :            :                     self->num_transitions)) {
     991                 :          0 :         goto error;
     992                 :            :     }
     993                 :            : 
     994                 :            :     // Build _ttinfo objects from utcoff, dstoff and abbr
     995                 :        350 :     self->_ttinfos = PyMem_Malloc(self->num_ttinfos * sizeof(_ttinfo));
     996         [ -  + ]:        350 :     if (self->_ttinfos == NULL) {
     997                 :          0 :         goto error;
     998                 :            :     }
     999         [ +  + ]:       2104 :     for (size_t i = 0; i < self->num_ttinfos; ++i) {
    1000                 :       1754 :         PyObject *tzname = PyTuple_GetItem(abbr, i);
    1001         [ -  + ]:       1754 :         if (tzname == NULL) {
    1002                 :          0 :             goto error;
    1003                 :            :         }
    1004                 :            : 
    1005                 :       1754 :         ttinfos_allocated++;
    1006         [ -  + ]:       1754 :         if (build_ttinfo(utcoff[i], dstoff[i], tzname, &(self->_ttinfos[i]))) {
    1007                 :          0 :             goto error;
    1008                 :            :         }
    1009                 :            :     }
    1010                 :            : 
    1011                 :            :     // Build our mapping from transition to the ttinfo that applies
    1012                 :        350 :     self->trans_ttinfos =
    1013                 :        350 :         PyMem_Calloc(self->num_transitions, sizeof(_ttinfo *));
    1014         [ -  + ]:        350 :     if (self->trans_ttinfos == NULL) {
    1015                 :          0 :         goto error;
    1016                 :            :     }
    1017         [ +  + ]:      39649 :     for (size_t i = 0; i < self->num_transitions; ++i) {
    1018                 :      39299 :         size_t ttinfo_idx = trans_idx[i];
    1019                 :            :         assert(ttinfo_idx < self->num_ttinfos);
    1020                 :      39299 :         self->trans_ttinfos[i] = &(self->_ttinfos[ttinfo_idx]);
    1021                 :            :     }
    1022                 :            : 
    1023                 :            :     // Set ttinfo_before to the first non-DST transition
    1024         [ +  + ]:        352 :     for (size_t i = 0; i < self->num_ttinfos; ++i) {
    1025         [ +  + ]:        302 :         if (!isdst[i]) {
    1026                 :        300 :             self->ttinfo_before = &(self->_ttinfos[i]);
    1027                 :        300 :             break;
    1028                 :            :         }
    1029                 :            :     }
    1030                 :            : 
    1031                 :            :     // If there are only DST ttinfos, pick the first one, if there are no
    1032                 :            :     // ttinfos at all, set ttinfo_before to NULL
    1033   [ +  +  +  + ]:        350 :     if (self->ttinfo_before == NULL && self->num_ttinfos > 0) {
    1034                 :          2 :         self->ttinfo_before = &(self->_ttinfos[0]);
    1035                 :            :     }
    1036                 :            : 
    1037   [ +  +  +  + ]:        350 :     if (tz_str != Py_None && PyObject_IsTrue(tz_str)) {
    1038         [ +  + ]:        286 :         if (parse_tz_str(tz_str, &(self->tzrule_after))) {
    1039                 :         27 :             goto error;
    1040                 :            :         }
    1041                 :            :     }
    1042                 :            :     else {
    1043         [ +  + ]:         64 :         if (!self->num_ttinfos) {
    1044                 :          1 :             PyErr_Format(PyExc_ValueError, "No time zone information found.");
    1045                 :          1 :             goto error;
    1046                 :            :         }
    1047                 :            : 
    1048                 :            :         size_t idx;
    1049         [ +  + ]:         63 :         if (!self->num_transitions) {
    1050                 :          4 :             idx = self->num_ttinfos - 1;
    1051                 :            :         }
    1052                 :            :         else {
    1053                 :         59 :             idx = trans_idx[self->num_transitions - 1];
    1054                 :            :         }
    1055                 :            : 
    1056                 :         63 :         _ttinfo *tti = &(self->_ttinfos[idx]);
    1057                 :         63 :         build_tzrule(tti->tzname, NULL, tti->utcoff_seconds, 0, NULL, NULL,
    1058                 :            :                      &(self->tzrule_after));
    1059                 :            : 
    1060                 :            :         // We've abused the build_tzrule constructor to construct an STD-only
    1061                 :            :         // rule mimicking whatever ttinfo we've picked up, but it's possible
    1062                 :            :         // that the one we've picked up is a DST zone, so we need to make sure
    1063                 :            :         // that the dstoff is set correctly in that case.
    1064         [ +  + ]:         63 :         if (PyObject_IsTrue(tti->dstoff)) {
    1065                 :         16 :             _ttinfo *tti_after = &(self->tzrule_after.std);
    1066                 :         16 :             Py_DECREF(tti_after->dstoff);
    1067                 :         16 :             tti_after->dstoff = tti->dstoff;
    1068                 :         16 :             Py_INCREF(tti_after->dstoff);
    1069                 :            :         }
    1070                 :            :     }
    1071                 :            : 
    1072                 :            :     // Determine if this is a "fixed offset" zone, meaning that the output of
    1073                 :            :     // the utcoffset, dst and tzname functions does not depend on the specific
    1074                 :            :     // datetime passed.
    1075                 :            :     //
    1076                 :            :     // We make three simplifying assumptions here:
    1077                 :            :     //
    1078                 :            :     // 1. If tzrule_after is not std_only, it has transitions that might occur
    1079                 :            :     //    (it is possible to construct TZ strings that specify STD and DST but
    1080                 :            :     //    no transitions ever occur, such as AAA0BBB,0/0,J365/25).
    1081                 :            :     // 2. If self->_ttinfos contains more than one _ttinfo object, the objects
    1082                 :            :     //    represent different offsets.
    1083                 :            :     // 3. self->ttinfos contains no unused _ttinfos (in which case an otherwise
    1084                 :            :     //    fixed-offset zone with extra _ttinfos defined may appear to *not* be
    1085                 :            :     //    a fixed offset zone).
    1086                 :            :     //
    1087                 :            :     // Violations to these assumptions would be fairly exotic, and exotic
    1088                 :            :     // zones should almost certainly not be used with datetime.time (the
    1089                 :            :     // only thing that would be affected by this).
    1090   [ +  +  +  + ]:        322 :     if (self->num_ttinfos > 1 || !self->tzrule_after.std_only) {
    1091                 :        309 :         self->fixed_offset = 0;
    1092                 :            :     }
    1093         [ +  + ]:         13 :     else if (self->num_ttinfos == 0) {
    1094                 :          2 :         self->fixed_offset = 1;
    1095                 :            :     }
    1096                 :            :     else {
    1097                 :            :         int constant_offset =
    1098                 :         11 :             ttinfo_eq(&(self->_ttinfos[0]), &self->tzrule_after.std);
    1099         [ -  + ]:         11 :         if (constant_offset < 0) {
    1100                 :          0 :             goto error;
    1101                 :            :         }
    1102                 :            :         else {
    1103                 :         11 :             self->fixed_offset = constant_offset;
    1104                 :            :         }
    1105                 :            :     }
    1106                 :            : 
    1107                 :        322 :     int rv = 0;
    1108                 :        322 :     goto cleanup;
    1109                 :         36 : error:
    1110                 :            :     // These resources only need to be freed if we have failed, if we succeed
    1111                 :            :     // in initializing a PyZoneInfo_ZoneInfo object, we can rely on its dealloc
    1112                 :            :     // method to free the relevant resources.
    1113         [ +  + ]:         36 :     if (self->trans_list_utc != NULL) {
    1114                 :         28 :         PyMem_Free(self->trans_list_utc);
    1115                 :         28 :         self->trans_list_utc = NULL;
    1116                 :            :     }
    1117                 :            : 
    1118         [ +  + ]:        108 :     for (size_t i = 0; i < 2; ++i) {
    1119         [ -  + ]:         72 :         if (self->trans_list_wall[i] != NULL) {
    1120                 :          0 :             PyMem_Free(self->trans_list_wall[i]);
    1121                 :          0 :             self->trans_list_wall[i] = NULL;
    1122                 :            :         }
    1123                 :            :     }
    1124                 :            : 
    1125         [ +  + ]:         36 :     if (self->_ttinfos != NULL) {
    1126         [ -  + ]:         28 :         for (size_t i = 0; i < ttinfos_allocated; ++i) {
    1127                 :          0 :             xdecref_ttinfo(&(self->_ttinfos[i]));
    1128                 :            :         }
    1129                 :         28 :         PyMem_Free(self->_ttinfos);
    1130                 :         28 :         self->_ttinfos = NULL;
    1131                 :            :     }
    1132                 :            : 
    1133         [ +  + ]:         36 :     if (self->trans_ttinfos != NULL) {
    1134                 :         28 :         PyMem_Free(self->trans_ttinfos);
    1135                 :         28 :         self->trans_ttinfos = NULL;
    1136                 :            :     }
    1137                 :            : 
    1138                 :         36 :     rv = -1;
    1139                 :        358 : cleanup:
    1140                 :        358 :     Py_XDECREF(data_tuple);
    1141                 :            : 
    1142         [ +  + ]:        358 :     if (utcoff != NULL) {
    1143                 :        350 :         PyMem_Free(utcoff);
    1144                 :            :     }
    1145                 :            : 
    1146         [ +  + ]:        358 :     if (dstoff != NULL) {
    1147                 :        350 :         PyMem_Free(dstoff);
    1148                 :            :     }
    1149                 :            : 
    1150         [ +  + ]:        358 :     if (isdst != NULL) {
    1151                 :        350 :         PyMem_Free(isdst);
    1152                 :            :     }
    1153                 :            : 
    1154         [ +  + ]:        358 :     if (trans_idx != NULL) {
    1155                 :        350 :         PyMem_Free(trans_idx);
    1156                 :            :     }
    1157                 :            : 
    1158                 :        358 :     return rv;
    1159                 :            : }
    1160                 :            : 
    1161                 :            : /* Function to calculate the local timestamp of a transition from the year. */
    1162                 :            : int64_t
    1163                 :       3264 : calendarrule_year_to_timestamp(TransitionRuleType *base_self, int year)
    1164                 :            : {
    1165                 :       3264 :     CalendarRule *self = (CalendarRule *)base_self;
    1166                 :            : 
    1167                 :            :     // We want (year, month, day of month); we have year and month, but we
    1168                 :            :     // need to turn (week, day-of-week) into day-of-month
    1169                 :            :     //
    1170                 :            :     // Week 1 is the first week in which day `day` (where 0 = Sunday) appears.
    1171                 :            :     // Week 5 represents the last occurrence of day `day`, so we need to know
    1172                 :            :     // the first weekday of the month and the number of days in the month.
    1173                 :       3264 :     int8_t first_day = (ymd_to_ord(year, self->month, 1) + 6) % 7;
    1174                 :       3264 :     uint8_t days_in_month = DAYS_IN_MONTH[self->month];
    1175   [ -  +  -  - ]:       3264 :     if (self->month == 2 && is_leap_year(year)) {
    1176                 :          0 :         days_in_month += 1;
    1177                 :            :     }
    1178                 :            : 
    1179                 :            :     // This equation seems magical, so I'll break it down:
    1180                 :            :     // 1. calendar says 0 = Monday, POSIX says 0 = Sunday so we need first_day
    1181                 :            :     //    + 1 to get 1 = Monday -> 7 = Sunday, which is still equivalent
    1182                 :            :     //    because this math is mod 7
    1183                 :            :     // 2. Get first day - desired day mod 7 (adjusting by 7 for negative
    1184                 :            :     //    numbers so that -1 % 7 = 6).
    1185                 :            :     // 3. Add 1 because month days are a 1-based index.
    1186                 :       3264 :     int8_t month_day = ((int8_t)(self->day) - (first_day + 1)) % 7;
    1187         [ +  + ]:       3264 :     if (month_day < 0) {
    1188                 :       2327 :         month_day += 7;
    1189                 :            :     }
    1190                 :       3264 :     month_day += 1;
    1191                 :            : 
    1192                 :            :     // Now use a 0-based index version of `week` to calculate the w-th
    1193                 :            :     // occurrence of `day`
    1194                 :       3264 :     month_day += ((int8_t)(self->week) - 1) * 7;
    1195                 :            : 
    1196                 :            :     // month_day will only be > days_in_month if w was 5, and `w` means "last
    1197                 :            :     // occurrence of `d`", so now we just check if we over-shot the end of the
    1198                 :            :     // month and if so knock off 1 week.
    1199         [ +  + ]:       3264 :     if (month_day > days_in_month) {
    1200                 :        678 :         month_day -= 7;
    1201                 :            :     }
    1202                 :            : 
    1203                 :       3264 :     int64_t ordinal = ymd_to_ord(year, self->month, month_day) - EPOCHORDINAL;
    1204                 :       3264 :     return ((ordinal * 86400) + (int64_t)(self->hour * 3600) +
    1205                 :       3264 :             (int64_t)(self->minute * 60) + (int64_t)(self->second));
    1206                 :            : }
    1207                 :            : 
    1208                 :            : /* Constructor for CalendarRule. */
    1209                 :            : int
    1210                 :        330 : calendarrule_new(uint8_t month, uint8_t week, uint8_t day, int8_t hour,
    1211                 :            :                  int8_t minute, int8_t second, CalendarRule *out)
    1212                 :            : {
    1213                 :            :     // These bounds come from the POSIX standard, which describes an Mm.n.d
    1214                 :            :     // rule as:
    1215                 :            :     //
    1216                 :            :     //   The d'th day (0 <= d <= 6) of week n of month m of the year (1 <= n <=
    1217                 :            :     //   5, 1 <= m <= 12, where week 5 means "the last d day in month m" which
    1218                 :            :     //   may occur in either the fourth or the fifth week). Week 1 is the first
    1219                 :            :     //   week in which the d'th day occurs. Day zero is Sunday.
    1220   [ +  +  +  + ]:        330 :     if (month <= 0 || month > 12) {
    1221                 :          4 :         PyErr_Format(PyExc_ValueError, "Month must be in (0, 12]");
    1222                 :          4 :         return -1;
    1223                 :            :     }
    1224                 :            : 
    1225   [ +  -  +  + ]:        326 :     if (week <= 0 || week > 5) {
    1226                 :          2 :         PyErr_Format(PyExc_ValueError, "Week must be in (0, 5]");
    1227                 :          2 :         return -1;
    1228                 :            :     }
    1229                 :            : 
    1230                 :            :     // If the 'day' parameter type is changed to a signed type,
    1231                 :            :     // "day < 0" check must be added.
    1232         [ +  + ]:        324 :     if (/* day < 0 || */ day > 6) {
    1233                 :          2 :         PyErr_Format(PyExc_ValueError, "Day must be in [0, 6]");
    1234                 :          2 :         return -1;
    1235                 :            :     }
    1236                 :            : 
    1237                 :        322 :     TransitionRuleType base = {&calendarrule_year_to_timestamp};
    1238                 :            : 
    1239                 :        322 :     CalendarRule new_offset = {
    1240                 :            :         .base = base,
    1241                 :            :         .month = month,
    1242                 :            :         .week = week,
    1243                 :            :         .day = day,
    1244                 :            :         .hour = hour,
    1245                 :            :         .minute = minute,
    1246                 :            :         .second = second,
    1247                 :            :     };
    1248                 :            : 
    1249                 :        322 :     *out = new_offset;
    1250                 :        322 :     return 0;
    1251                 :            : }
    1252                 :            : 
    1253                 :            : /* Function to calculate the local timestamp of a transition from the year.
    1254                 :            :  *
    1255                 :            :  * This translates the day of the year into a local timestamp — either a
    1256                 :            :  * 1-based Julian day, not including leap days, or the 0-based year-day,
    1257                 :            :  * including leap days.
    1258                 :            :  * */
    1259                 :            : int64_t
    1260                 :        316 : dayrule_year_to_timestamp(TransitionRuleType *base_self, int year)
    1261                 :            : {
    1262                 :            :     // The function signature requires a TransitionRuleType pointer, but this
    1263                 :            :     // function is only applicable to DayRule* objects.
    1264                 :        316 :     DayRule *self = (DayRule *)base_self;
    1265                 :            : 
    1266                 :            :     // ymd_to_ord calculates the number of days since 0001-01-01, but we want
    1267                 :            :     // to know the number of days since 1970-01-01, so we must subtract off
    1268                 :            :     // the equivalent of ymd_to_ord(1970, 1, 1).
    1269                 :            :     //
    1270                 :            :     // We subtract off an additional 1 day to account for January 1st (we want
    1271                 :            :     // the number of full days *before* the date of the transition - partial
    1272                 :            :     // days are accounted for in the hour, minute and second portions.
    1273                 :        316 :     int64_t days_before_year = ymd_to_ord(year, 1, 1) - EPOCHORDINAL - 1;
    1274                 :            : 
    1275                 :            :     // The Julian day specification skips over February 29th in leap years,
    1276                 :            :     // from the POSIX standard:
    1277                 :            :     //
    1278                 :            :     //   Leap days shall not be counted. That is, in all years-including leap
    1279                 :            :     //   years-February 28 is day 59 and March 1 is day 60. It is impossible to
    1280                 :            :     //   refer explicitly to the occasional February 29.
    1281                 :            :     //
    1282                 :            :     // This is actually more useful than you'd think — if you want a rule that
    1283                 :            :     // always transitions on a given calendar day (other than February 29th),
    1284                 :            :     // you would use a Julian day, e.g. J91 always refers to April 1st and J365
    1285                 :            :     // always refers to December 31st.
    1286                 :        316 :     unsigned int day = self->day;
    1287   [ +  +  +  -  :        316 :     if (self->julian && day >= 59 && is_leap_year(year)) {
                   +  + ]
    1288                 :        139 :         day += 1;
    1289                 :            :     }
    1290                 :            : 
    1291                 :        316 :     return ((days_before_year + day) * 86400) + (self->hour * 3600) +
    1292                 :        316 :            (self->minute * 60) + self->second;
    1293                 :            : }
    1294                 :            : 
    1295                 :            : /* Constructor for DayRule. */
    1296                 :            : static int
    1297                 :         18 : dayrule_new(uint8_t julian, unsigned int day, int8_t hour, int8_t minute,
    1298                 :            :             int8_t second, DayRule *out)
    1299                 :            : {
    1300                 :            :     // The POSIX standard specifies that Julian days must be in the range (1 <=
    1301                 :            :     // n <= 365) and that non-Julian (they call it "0-based Julian") days must
    1302                 :            :     // be in the range (0 <= n <= 365).
    1303   [ +  +  +  + ]:         18 :     if (day < julian || day > 365) {
    1304                 :          4 :         PyErr_Format(PyExc_ValueError, "day must be in [%u, 365], not: %u",
    1305                 :            :                      julian, day);
    1306                 :          4 :         return -1;
    1307                 :            :     }
    1308                 :            : 
    1309                 :         14 :     TransitionRuleType base = {
    1310                 :            :         &dayrule_year_to_timestamp,
    1311                 :            :     };
    1312                 :            : 
    1313                 :         14 :     DayRule tmp = {
    1314                 :            :         .base = base,
    1315                 :            :         .julian = julian,
    1316                 :            :         .day = day,
    1317                 :            :         .hour = hour,
    1318                 :            :         .minute = minute,
    1319                 :            :         .second = second,
    1320                 :            :     };
    1321                 :            : 
    1322                 :         14 :     *out = tmp;
    1323                 :            : 
    1324                 :         14 :     return 0;
    1325                 :            : }
    1326                 :            : 
    1327                 :            : /* Calculate the start and end rules for a _tzrule in the given year. */
    1328                 :            : static void
    1329                 :       1790 : tzrule_transitions(_tzrule *rule, int year, int64_t *start, int64_t *end)
    1330                 :            : {
    1331                 :            :     assert(rule->start != NULL);
    1332                 :            :     assert(rule->end != NULL);
    1333                 :       1790 :     *start = rule->start->year_to_timestamp(rule->start, year);
    1334                 :       1790 :     *end = rule->end->year_to_timestamp(rule->end, year);
    1335                 :       1790 : }
    1336                 :            : 
    1337                 :            : /* Calculate the _ttinfo that applies at a given local time from a _tzrule.
    1338                 :            :  *
    1339                 :            :  * This takes a local timestamp and fold for disambiguation purposes; the year
    1340                 :            :  * could technically be calculated from the timestamp, but given that the
    1341                 :            :  * callers of this function already have the year information accessible from
    1342                 :            :  * the datetime struct, it is taken as an additional parameter to reduce
    1343                 :            :  * unnecessary calculation.
    1344                 :            :  * */
    1345                 :            : static _ttinfo *
    1346                 :       1860 : find_tzrule_ttinfo(_tzrule *rule, int64_t ts, unsigned char fold, int year)
    1347                 :            : {
    1348         [ +  + ]:       1860 :     if (rule->std_only) {
    1349                 :        188 :         return &(rule->std);
    1350                 :            :     }
    1351                 :            : 
    1352                 :            :     int64_t start, end;
    1353                 :            :     uint8_t isdst;
    1354                 :            : 
    1355                 :       1672 :     tzrule_transitions(rule, year, &start, &end);
    1356                 :            : 
    1357                 :            :     // With fold = 0, the period (denominated in local time) with the smaller
    1358                 :            :     // offset starts at the end of the gap and ends at the end of the fold;
    1359                 :            :     // with fold = 1, it runs from the start of the gap to the beginning of the
    1360                 :            :     // fold.
    1361                 :            :     //
    1362                 :            :     // So in order to determine the DST boundaries we need to know both the
    1363                 :            :     // fold and whether DST is positive or negative (rare), and it turns out
    1364                 :            :     // that this boils down to fold XOR is_positive.
    1365         [ +  + ]:       1672 :     if (fold == (rule->dst_diff >= 0)) {
    1366                 :        695 :         end -= rule->dst_diff;
    1367                 :            :     }
    1368                 :            :     else {
    1369                 :        977 :         start += rule->dst_diff;
    1370                 :            :     }
    1371                 :            : 
    1372         [ +  + ]:       1672 :     if (start < end) {
    1373   [ +  +  +  + ]:        802 :         isdst = (ts >= start) && (ts < end);
    1374                 :            :     }
    1375                 :            :     else {
    1376   [ +  +  +  + ]:        870 :         isdst = (ts < end) || (ts >= start);
    1377                 :            :     }
    1378                 :            : 
    1379         [ +  + ]:       1672 :     if (isdst) {
    1380                 :        847 :         return &(rule->dst);
    1381                 :            :     }
    1382                 :            :     else {
    1383                 :        825 :         return &(rule->std);
    1384                 :            :     }
    1385                 :            : }
    1386                 :            : 
    1387                 :            : /* Calculate the ttinfo and fold that applies for a _tzrule at an epoch time.
    1388                 :            :  *
    1389                 :            :  * This function can determine the _ttinfo that applies at a given epoch time,
    1390                 :            :  * (analogous to trans_list_utc), and whether or not the datetime is in a fold.
    1391                 :            :  * This is to be used in the .fromutc() function.
    1392                 :            :  *
    1393                 :            :  * The year is technically a redundant parameter, because it can be calculated
    1394                 :            :  * from the timestamp, but all callers of this function should have the year
    1395                 :            :  * in the datetime struct anyway, so taking it as a parameter saves unnecessary
    1396                 :            :  * calculation.
    1397                 :            :  **/
    1398                 :            : static _ttinfo *
    1399                 :        124 : find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year,
    1400                 :            :                            unsigned char *fold)
    1401                 :            : {
    1402         [ +  + ]:        124 :     if (rule->std_only) {
    1403                 :          6 :         *fold = 0;
    1404                 :          6 :         return &(rule->std);
    1405                 :            :     }
    1406                 :            : 
    1407                 :            :     int64_t start, end;
    1408                 :            :     uint8_t isdst;
    1409                 :        118 :     tzrule_transitions(rule, year, &start, &end);
    1410                 :        118 :     start -= rule->std.utcoff_seconds;
    1411                 :        118 :     end -= rule->dst.utcoff_seconds;
    1412                 :            : 
    1413         [ +  + ]:        118 :     if (start < end) {
    1414   [ +  +  +  + ]:         73 :         isdst = (ts >= start) && (ts < end);
    1415                 :            :     }
    1416                 :            :     else {
    1417   [ +  +  +  + ]:         45 :         isdst = (ts < end) || (ts >= start);
    1418                 :            :     }
    1419                 :            : 
    1420                 :            :     // For positive DST, the ambiguous period is one dst_diff after the end of
    1421                 :            :     // DST; for negative DST, the ambiguous period is one dst_diff before the
    1422                 :            :     // start of DST.
    1423                 :            :     int64_t ambig_start, ambig_end;
    1424         [ +  + ]:        118 :     if (rule->dst_diff > 0) {
    1425                 :         99 :         ambig_start = end;
    1426                 :         99 :         ambig_end = end + rule->dst_diff;
    1427                 :            :     }
    1428                 :            :     else {
    1429                 :         19 :         ambig_start = start;
    1430                 :         19 :         ambig_end = start - rule->dst_diff;
    1431                 :            :     }
    1432                 :            : 
    1433   [ +  +  +  + ]:        118 :     *fold = (ts >= ambig_start) && (ts < ambig_end);
    1434                 :            : 
    1435         [ +  + ]:        118 :     if (isdst) {
    1436                 :         61 :         return &(rule->dst);
    1437                 :            :     }
    1438                 :            :     else {
    1439                 :         57 :         return &(rule->std);
    1440                 :            :     }
    1441                 :            : }
    1442                 :            : 
    1443                 :            : /* Parse a TZ string in the format specified by the POSIX standard:
    1444                 :            :  *
    1445                 :            :  *  std offset[dst[offset],start[/time],end[/time]]
    1446                 :            :  *
    1447                 :            :  *  std and dst must be 3 or more characters long and must not contain a
    1448                 :            :  *  leading colon, embedded digits, commas, nor a plus or minus signs; The
    1449                 :            :  *  spaces between "std" and "offset" are only for display and are not actually
    1450                 :            :  *  present in the string.
    1451                 :            :  *
    1452                 :            :  *  The format of the offset is ``[+|-]hh[:mm[:ss]]``
    1453                 :            :  *
    1454                 :            :  * See the POSIX.1 spec: IEE Std 1003.1-2018 §8.3:
    1455                 :            :  *
    1456                 :            :  * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
    1457                 :            :  */
    1458                 :            : static int
    1459                 :        286 : parse_tz_str(PyObject *tz_str_obj, _tzrule *out)
    1460                 :            : {
    1461                 :        286 :     PyObject *std_abbr = NULL;
    1462                 :        286 :     PyObject *dst_abbr = NULL;
    1463                 :        286 :     TransitionRuleType *start = NULL;
    1464                 :        286 :     TransitionRuleType *end = NULL;
    1465                 :            :     // Initialize offsets to invalid value (> 24 hours)
    1466                 :        286 :     long std_offset = 1 << 20;
    1467                 :        286 :     long dst_offset = 1 << 20;
    1468                 :            : 
    1469                 :        286 :     const char *tz_str = PyBytes_AsString(tz_str_obj);
    1470         [ -  + ]:        286 :     if (tz_str == NULL) {
    1471                 :          0 :         return -1;
    1472                 :            :     }
    1473                 :        286 :     const char *p = tz_str;
    1474                 :            : 
    1475                 :            :     // Read the `std` abbreviation, which must be at least 3 characters long.
    1476                 :        286 :     Py_ssize_t num_chars = parse_abbr(p, &std_abbr);
    1477         [ +  + ]:        286 :     if (num_chars < 1) {
    1478                 :          1 :         PyErr_Format(PyExc_ValueError, "Invalid STD format in %R", tz_str_obj);
    1479                 :          1 :         goto error;
    1480                 :            :     }
    1481                 :            : 
    1482                 :        285 :     p += num_chars;
    1483                 :            : 
    1484                 :            :     // Now read the STD offset, which is required
    1485                 :        285 :     num_chars = parse_tz_delta(p, &std_offset);
    1486         [ +  + ]:        285 :     if (num_chars < 0) {
    1487                 :          5 :         PyErr_Format(PyExc_ValueError, "Invalid STD offset in %R", tz_str_obj);
    1488                 :          5 :         goto error;
    1489                 :            :     }
    1490                 :        280 :     p += num_chars;
    1491                 :            : 
    1492                 :            :     // If the string ends here, there is no DST, otherwise we must parse the
    1493                 :            :     // DST abbreviation and start and end dates and times.
    1494         [ +  + ]:        280 :     if (*p == '\0') {
    1495                 :         96 :         goto complete;
    1496                 :            :     }
    1497                 :            : 
    1498                 :        184 :     num_chars = parse_abbr(p, &dst_abbr);
    1499         [ +  + ]:        184 :     if (num_chars < 1) {
    1500                 :          1 :         PyErr_Format(PyExc_ValueError, "Invalid DST format in %R", tz_str_obj);
    1501                 :          1 :         goto error;
    1502                 :            :     }
    1503                 :        183 :     p += num_chars;
    1504                 :            : 
    1505         [ +  + ]:        183 :     if (*p == ',') {
    1506                 :            :         // From the POSIX standard:
    1507                 :            :         //
    1508                 :            :         // If no offset follows dst, the alternative time is assumed to be one
    1509                 :            :         // hour ahead of standard time.
    1510                 :        121 :         dst_offset = std_offset + 3600;
    1511                 :            :     }
    1512                 :            :     else {
    1513                 :         62 :         num_chars = parse_tz_delta(p, &dst_offset);
    1514         [ +  + ]:         62 :         if (num_chars < 0) {
    1515                 :          3 :             PyErr_Format(PyExc_ValueError, "Invalid DST offset in %R",
    1516                 :            :                          tz_str_obj);
    1517                 :          3 :             goto error;
    1518                 :            :         }
    1519                 :            : 
    1520                 :         59 :         p += num_chars;
    1521                 :            :     }
    1522                 :            : 
    1523                 :        180 :     TransitionRuleType **transitions[2] = {&start, &end};
    1524         [ +  + ]:        516 :     for (size_t i = 0; i < 2; ++i) {
    1525         [ +  + ]:        352 :         if (*p != ',') {
    1526                 :          1 :             PyErr_Format(PyExc_ValueError,
    1527                 :            :                          "Missing transition rules in TZ string: %R",
    1528                 :            :                          tz_str_obj);
    1529                 :          1 :             goto error;
    1530                 :            :         }
    1531                 :        351 :         p++;
    1532                 :            : 
    1533                 :        351 :         num_chars = parse_transition_rule(p, transitions[i]);
    1534         [ +  + ]:        351 :         if (num_chars < 0) {
    1535                 :         15 :             PyErr_Format(PyExc_ValueError,
    1536                 :            :                          "Malformed transition rule in TZ string: %R",
    1537                 :            :                          tz_str_obj);
    1538                 :         15 :             goto error;
    1539                 :            :         }
    1540                 :        336 :         p += num_chars;
    1541                 :            :     }
    1542                 :            : 
    1543         [ +  + ]:        164 :     if (*p != '\0') {
    1544                 :          1 :         PyErr_Format(PyExc_ValueError,
    1545                 :            :                      "Extraneous characters at end of TZ string: %R",
    1546                 :            :                      tz_str_obj);
    1547                 :          1 :         goto error;
    1548                 :            :     }
    1549                 :            : 
    1550                 :        163 : complete:
    1551                 :        259 :     build_tzrule(std_abbr, dst_abbr, std_offset, dst_offset, start, end, out);
    1552                 :        259 :     Py_DECREF(std_abbr);
    1553                 :        259 :     Py_XDECREF(dst_abbr);
    1554                 :            : 
    1555                 :        259 :     return 0;
    1556                 :         27 : error:
    1557                 :         27 :     Py_XDECREF(std_abbr);
    1558   [ +  +  +  - ]:         27 :     if (dst_abbr != NULL && dst_abbr != Py_None) {
    1559                 :         21 :         Py_DECREF(dst_abbr);
    1560                 :            :     }
    1561                 :            : 
    1562         [ +  + ]:         27 :     if (start != NULL) {
    1563                 :          9 :         PyMem_Free(start);
    1564                 :            :     }
    1565                 :            : 
    1566         [ +  + ]:         27 :     if (end != NULL) {
    1567                 :          1 :         PyMem_Free(end);
    1568                 :            :     }
    1569                 :            : 
    1570                 :         27 :     return -1;
    1571                 :            : }
    1572                 :            : 
    1573                 :            : static int
    1574                 :       1135 : parse_uint(const char *const p, uint8_t *value)
    1575                 :            : {
    1576         [ -  + ]:       1135 :     if (!isdigit(*p)) {
    1577                 :          0 :         return -1;
    1578                 :            :     }
    1579                 :            : 
    1580                 :       1135 :     *value = (*p) - '0';
    1581                 :       1135 :     return 0;
    1582                 :            : }
    1583                 :            : 
    1584                 :            : /* Parse the STD and DST abbreviations from a TZ string. */
    1585                 :            : static Py_ssize_t
    1586                 :        470 : parse_abbr(const char *const p, PyObject **abbr)
    1587                 :            : {
    1588                 :        470 :     const char *ptr = p;
    1589                 :        470 :     char buff = *ptr;
    1590                 :            :     const char *str_start;
    1591                 :            :     const char *str_end;
    1592                 :            : 
    1593         [ +  + ]:        470 :     if (*ptr == '<') {
    1594                 :         70 :         ptr++;
    1595                 :         70 :         str_start = ptr;
    1596         [ +  + ]:        280 :         while ((buff = *ptr) != '>') {
    1597                 :            :             // From the POSIX standard:
    1598                 :            :             //
    1599                 :            :             //   In the quoted form, the first character shall be the less-than
    1600                 :            :             //   ( '<' ) character and the last character shall be the
    1601                 :            :             //   greater-than ( '>' ) character. All characters between these
    1602                 :            :             //   quoting characters shall be alphanumeric characters from the
    1603                 :            :             //   portable character set in the current locale, the plus-sign (
    1604                 :            :             //   '+' ) character, or the minus-sign ( '-' ) character. The std
    1605                 :            :             //   and dst fields in this case shall not include the quoting
    1606                 :            :             //   characters.
    1607   [ +  -  +  +  :        210 :             if (!isalpha(buff) && !isdigit(buff) && buff != '+' &&
             +  +  -  + ]
    1608                 :            :                 buff != '-') {
    1609                 :          0 :                 return -1;
    1610                 :            :             }
    1611                 :        210 :             ptr++;
    1612                 :            :         }
    1613                 :         70 :         str_end = ptr;
    1614                 :         70 :         ptr++;
    1615                 :            :     }
    1616                 :            :     else {
    1617                 :        400 :         str_start = p;
    1618                 :            :         // From the POSIX standard:
    1619                 :            :         //
    1620                 :            :         //   In the unquoted form, all characters in these fields shall be
    1621                 :            :         //   alphabetic characters from the portable character set in the
    1622                 :            :         //   current locale.
    1623         [ +  + ]:       1647 :         while (isalpha(*ptr)) {
    1624                 :       1247 :             ptr++;
    1625                 :            :         }
    1626                 :        400 :         str_end = ptr;
    1627                 :            :     }
    1628                 :            : 
    1629                 :        470 :     *abbr = PyUnicode_FromStringAndSize(str_start, str_end - str_start);
    1630         [ -  + ]:        470 :     if (*abbr == NULL) {
    1631                 :          0 :         return -1;
    1632                 :            :     }
    1633                 :            : 
    1634                 :        470 :     return ptr - p;
    1635                 :            : }
    1636                 :            : 
    1637                 :            : /* Parse a UTC offset from a TZ str. */
    1638                 :            : static Py_ssize_t
    1639                 :        347 : parse_tz_delta(const char *const p, long *total_seconds)
    1640                 :            : {
    1641                 :            :     // From the POSIX spec:
    1642                 :            :     //
    1643                 :            :     //   Indicates the value added to the local time to arrive at Coordinated
    1644                 :            :     //   Universal Time. The offset has the form:
    1645                 :            :     //
    1646                 :            :     //   hh[:mm[:ss]]
    1647                 :            :     //
    1648                 :            :     //   One or more digits may be used; the value is always interpreted as a
    1649                 :            :     //   decimal number.
    1650                 :            :     //
    1651                 :            :     // The POSIX spec says that the values for `hour` must be between 0 and 24
    1652                 :            :     // hours, but RFC 8536 §3.3.1 specifies that the hours part of the
    1653                 :            :     // transition times may be signed and range from -167 to 167.
    1654                 :        347 :     long sign = -1;
    1655                 :        347 :     long hours = 0;
    1656                 :        347 :     long minutes = 0;
    1657                 :        347 :     long seconds = 0;
    1658                 :            : 
    1659                 :        347 :     const char *ptr = p;
    1660                 :        347 :     char buff = *ptr;
    1661   [ +  +  +  + ]:        347 :     if (buff == '-' || buff == '+') {
    1662                 :            :         // Negative numbers correspond to *positive* offsets, from the spec:
    1663                 :            :         //
    1664                 :            :         //   If preceded by a '-', the timezone shall be east of the Prime
    1665                 :            :         //   Meridian; otherwise, it shall be west (which may be indicated by
    1666                 :            :         //   an optional preceding '+' ).
    1667         [ +  + ]:        133 :         if (buff == '-') {
    1668                 :        128 :             sign = 1;
    1669                 :            :         }
    1670                 :            : 
    1671                 :        133 :         ptr++;
    1672                 :            :     }
    1673                 :            : 
    1674                 :            :     // The hour can be 1 or 2 numeric characters
    1675         [ +  + ]:        735 :     for (size_t i = 0; i < 2; ++i) {
    1676                 :        692 :         buff = *ptr;
    1677         [ +  + ]:        692 :         if (!isdigit(buff)) {
    1678         [ +  + ]:        304 :             if (i == 0) {
    1679                 :          2 :                 return -1;
    1680                 :            :             }
    1681                 :            :             else {
    1682                 :        302 :                 break;
    1683                 :            :             }
    1684                 :            :         }
    1685                 :            : 
    1686                 :        388 :         hours *= 10;
    1687                 :        388 :         hours += buff - '0';
    1688                 :        388 :         ptr++;
    1689                 :            :     }
    1690                 :            : 
    1691   [ +  +  -  + ]:        345 :     if (hours > 24 || hours < 0) {
    1692                 :          6 :         return -1;
    1693                 :            :     }
    1694                 :            : 
    1695                 :            :     // Minutes and seconds always of the format ":dd"
    1696                 :        339 :     long *outputs[2] = {&minutes, &seconds};
    1697         [ +  - ]:        339 :     for (size_t i = 0; i < 2; ++i) {
    1698         [ +  - ]:        339 :         if (*ptr != ':') {
    1699                 :        339 :             goto complete;
    1700                 :            :         }
    1701                 :          0 :         ptr++;
    1702                 :            : 
    1703         [ #  # ]:          0 :         for (size_t j = 0; j < 2; ++j) {
    1704                 :          0 :             buff = *ptr;
    1705         [ #  # ]:          0 :             if (!isdigit(buff)) {
    1706                 :          0 :                 return -1;
    1707                 :            :             }
    1708                 :          0 :             *(outputs[i]) *= 10;
    1709                 :          0 :             *(outputs[i]) += buff - '0';
    1710                 :          0 :             ptr++;
    1711                 :            :         }
    1712                 :            :     }
    1713                 :            : 
    1714                 :          0 : complete:
    1715                 :        339 :     *total_seconds = sign * ((hours * 3600) + (minutes * 60) + seconds);
    1716                 :            : 
    1717                 :        339 :     return ptr - p;
    1718                 :            : }
    1719                 :            : 
    1720                 :            : /* Parse the date portion of a transition rule. */
    1721                 :            : static Py_ssize_t
    1722                 :        351 : parse_transition_rule(const char *const p, TransitionRuleType **out)
    1723                 :            : {
    1724                 :            :     // The full transition rule indicates when to change back and forth between
    1725                 :            :     // STD and DST, and has the form:
    1726                 :            :     //
    1727                 :            :     //   date[/time],date[/time]
    1728                 :            :     //
    1729                 :            :     // This function parses an individual date[/time] section, and returns
    1730                 :            :     // the number of characters that contributed to the transition rule. This
    1731                 :            :     // does not include the ',' at the end of the first rule.
    1732                 :            :     //
    1733                 :            :     // The POSIX spec states that if *time* is not given, the default is 02:00.
    1734                 :        351 :     const char *ptr = p;
    1735                 :        351 :     int8_t hour = 2;
    1736                 :        351 :     int8_t minute = 0;
    1737                 :        351 :     int8_t second = 0;
    1738                 :            : 
    1739                 :            :     // Rules come in one of three flavors:
    1740                 :            :     //
    1741                 :            :     //   1. Jn: Julian day n, with no leap days.
    1742                 :            :     //   2. n: Day of year (0-based, with leap days)
    1743                 :            :     //   3. Mm.n.d: Specifying by month, week and day-of-week.
    1744                 :            : 
    1745         [ +  + ]:        351 :     if (*ptr == 'M') {
    1746                 :            :         uint8_t month, week, day;
    1747                 :        331 :         ptr++;
    1748         [ -  + ]:        331 :         if (parse_uint(ptr, &month)) {
    1749                 :          9 :             return -1;
    1750                 :            :         }
    1751                 :        331 :         ptr++;
    1752         [ +  + ]:        331 :         if (*ptr != '.') {
    1753                 :            :             uint8_t tmp;
    1754         [ -  + ]:        144 :             if (parse_uint(ptr, &tmp)) {
    1755                 :          0 :                 return -1;
    1756                 :            :             }
    1757                 :            : 
    1758                 :        144 :             month *= 10;
    1759                 :        144 :             month += tmp;
    1760                 :        144 :             ptr++;
    1761                 :            :         }
    1762                 :            : 
    1763                 :        331 :         uint8_t *values[2] = {&week, &day};
    1764         [ +  + ]:        991 :         for (size_t i = 0; i < 2; ++i) {
    1765         [ +  + ]:        661 :             if (*ptr != '.') {
    1766                 :          1 :                 return -1;
    1767                 :            :             }
    1768                 :        660 :             ptr++;
    1769                 :            : 
    1770         [ -  + ]:        660 :             if (parse_uint(ptr, values[i])) {
    1771                 :          0 :                 return -1;
    1772                 :            :             }
    1773                 :        660 :             ptr++;
    1774                 :            :         }
    1775                 :            : 
    1776         [ +  + ]:        330 :         if (*ptr == '/') {
    1777                 :        176 :             ptr++;
    1778                 :            :             Py_ssize_t num_chars =
    1779                 :        176 :                 parse_transition_time(ptr, &hour, &minute, &second);
    1780         [ -  + ]:        176 :             if (num_chars < 0) {
    1781                 :          0 :                 return -1;
    1782                 :            :             }
    1783                 :        176 :             ptr += num_chars;
    1784                 :            :         }
    1785                 :            : 
    1786                 :        330 :         CalendarRule *rv = PyMem_Calloc(1, sizeof(CalendarRule));
    1787         [ -  + ]:        330 :         if (rv == NULL) {
    1788                 :          0 :             return -1;
    1789                 :            :         }
    1790                 :            : 
    1791         [ +  + ]:        330 :         if (calendarrule_new(month, week, day, hour, minute, second, rv)) {
    1792                 :          8 :             PyMem_Free(rv);
    1793                 :          8 :             return -1;
    1794                 :            :         }
    1795                 :            : 
    1796                 :        322 :         *out = (TransitionRuleType *)rv;
    1797                 :            :     }
    1798                 :            :     else {
    1799                 :         20 :         uint8_t julian = 0;
    1800                 :         20 :         unsigned int day = 0;
    1801         [ +  + ]:         20 :         if (*ptr == 'J') {
    1802                 :         10 :             julian = 1;
    1803                 :         10 :             ptr++;
    1804                 :            :         }
    1805                 :            : 
    1806         [ +  + ]:         59 :         for (size_t i = 0; i < 3; ++i) {
    1807         [ +  + ]:         50 :             if (!isdigit(*ptr)) {
    1808         [ +  + ]:         11 :                 if (i == 0) {
    1809                 :          2 :                     return -1;
    1810                 :            :                 }
    1811                 :          9 :                 break;
    1812                 :            :             }
    1813                 :         39 :             day *= 10;
    1814                 :         39 :             day += (*ptr) - '0';
    1815                 :         39 :             ptr++;
    1816                 :            :         }
    1817                 :            : 
    1818         [ +  + ]:         18 :         if (*ptr == '/') {
    1819                 :         16 :             ptr++;
    1820                 :            :             Py_ssize_t num_chars =
    1821                 :         16 :                 parse_transition_time(ptr, &hour, &minute, &second);
    1822         [ -  + ]:         16 :             if (num_chars < 0) {
    1823                 :          0 :                 return -1;
    1824                 :            :             }
    1825                 :         16 :             ptr += num_chars;
    1826                 :            :         }
    1827                 :            : 
    1828                 :         18 :         DayRule *rv = PyMem_Calloc(1, sizeof(DayRule));
    1829         [ -  + ]:         18 :         if (rv == NULL) {
    1830                 :          0 :             return -1;
    1831                 :            :         }
    1832                 :            : 
    1833         [ +  + ]:         18 :         if (dayrule_new(julian, day, hour, minute, second, rv)) {
    1834                 :          4 :             PyMem_Free(rv);
    1835                 :          4 :             return -1;
    1836                 :            :         }
    1837                 :         14 :         *out = (TransitionRuleType *)rv;
    1838                 :            :     }
    1839                 :            : 
    1840                 :        336 :     return ptr - p;
    1841                 :            : }
    1842                 :            : 
    1843                 :            : /* Parse the time portion of a transition rule (e.g. following an /) */
    1844                 :            : static Py_ssize_t
    1845                 :        192 : parse_transition_time(const char *const p, int8_t *hour, int8_t *minute,
    1846                 :            :                       int8_t *second)
    1847                 :            : {
    1848                 :            :     // From the spec:
    1849                 :            :     //
    1850                 :            :     //   The time has the same format as offset except that no leading sign
    1851                 :            :     //   ( '-' or '+' ) is allowed.
    1852                 :            :     //
    1853                 :            :     // The format for the offset is:
    1854                 :            :     //
    1855                 :            :     //   h[h][:mm[:ss]]
    1856                 :            :     //
    1857                 :            :     // RFC 8536 also allows transition times to be signed and to range from
    1858                 :            :     // -167 to +167, but the current version only supports [0, 99].
    1859                 :            :     //
    1860                 :            :     // TODO: Support the full range of transition hours.
    1861                 :        192 :     int8_t *components[3] = {hour, minute, second};
    1862                 :        192 :     const char *ptr = p;
    1863                 :        192 :     int8_t sign = 1;
    1864                 :            : 
    1865   [ +  +  -  + ]:        192 :     if (*ptr == '-' || *ptr == '+') {
    1866         [ +  - ]:          4 :         if (*ptr == '-') {
    1867                 :          4 :             sign = -1;
    1868                 :            :         }
    1869                 :          4 :         ptr++;
    1870                 :            :     }
    1871                 :            : 
    1872         [ +  + ]:        398 :     for (size_t i = 0; i < 3; ++i) {
    1873         [ +  + ]:        396 :         if (i > 0) {
    1874         [ +  + ]:        204 :             if (*ptr != ':') {
    1875                 :        190 :                 break;
    1876                 :            :             }
    1877                 :         14 :             ptr++;
    1878                 :            :         }
    1879                 :            : 
    1880                 :        206 :         uint8_t buff = 0;
    1881         [ +  + ]:        471 :         for (size_t j = 0; j < 2; j++) {
    1882         [ +  + ]:        412 :             if (!isdigit(*ptr)) {
    1883   [ +  -  +  - ]:        147 :                 if (i == 0 && j > 0) {
    1884                 :        147 :                     break;
    1885                 :            :                 }
    1886                 :          0 :                 return -1;
    1887                 :            :             }
    1888                 :            : 
    1889                 :        265 :             buff *= 10;
    1890                 :        265 :             buff += (*ptr) - '0';
    1891                 :        265 :             ptr++;
    1892                 :            :         }
    1893                 :            : 
    1894                 :        206 :         *(components[i]) = sign * buff;
    1895                 :            :     }
    1896                 :            : 
    1897                 :        192 :     return ptr - p;
    1898                 :            : }
    1899                 :            : 
    1900                 :            : /* Constructor for a _tzrule.
    1901                 :            :  *
    1902                 :            :  * If `dst_abbr` is NULL, this will construct an "STD-only" _tzrule, in which
    1903                 :            :  * case `dst_offset` will be ignored and `start` and `end` are expected to be
    1904                 :            :  * NULL as well.
    1905                 :            :  *
    1906                 :            :  * Returns 0 on success.
    1907                 :            :  */
    1908                 :            : static int
    1909                 :        322 : build_tzrule(PyObject *std_abbr, PyObject *dst_abbr, long std_offset,
    1910                 :            :              long dst_offset, TransitionRuleType *start,
    1911                 :            :              TransitionRuleType *end, _tzrule *out)
    1912                 :            : {
    1913                 :        322 :     _tzrule rv = {{0}};
    1914                 :            : 
    1915                 :        322 :     rv.start = start;
    1916                 :        322 :     rv.end = end;
    1917                 :            : 
    1918         [ -  + ]:        322 :     if (build_ttinfo(std_offset, 0, std_abbr, &rv.std)) {
    1919                 :          0 :         goto error;
    1920                 :            :     }
    1921                 :            : 
    1922         [ +  + ]:        322 :     if (dst_abbr != NULL) {
    1923                 :        163 :         rv.dst_diff = dst_offset - std_offset;
    1924         [ -  + ]:        163 :         if (build_ttinfo(dst_offset, rv.dst_diff, dst_abbr, &rv.dst)) {
    1925                 :          0 :             goto error;
    1926                 :            :         }
    1927                 :            :     }
    1928                 :            :     else {
    1929                 :        159 :         rv.std_only = 1;
    1930                 :            :     }
    1931                 :            : 
    1932                 :        322 :     *out = rv;
    1933                 :            : 
    1934                 :        322 :     return 0;
    1935                 :          0 : error:
    1936                 :          0 :     xdecref_ttinfo(&rv.std);
    1937                 :          0 :     xdecref_ttinfo(&rv.dst);
    1938                 :          0 :     return -1;
    1939                 :            : }
    1940                 :            : 
    1941                 :            : /* Destructor for _tzrule. */
    1942                 :            : static void
    1943                 :        358 : free_tzrule(_tzrule *tzrule)
    1944                 :            : {
    1945                 :        358 :     xdecref_ttinfo(&(tzrule->std));
    1946         [ +  + ]:        358 :     if (!tzrule->std_only) {
    1947                 :        199 :         xdecref_ttinfo(&(tzrule->dst));
    1948                 :            :     }
    1949                 :            : 
    1950         [ +  + ]:        358 :     if (tzrule->start != NULL) {
    1951                 :        163 :         PyMem_Free(tzrule->start);
    1952                 :            :     }
    1953                 :            : 
    1954         [ +  + ]:        358 :     if (tzrule->end != NULL) {
    1955                 :        163 :         PyMem_Free(tzrule->end);
    1956                 :            :     }
    1957                 :        358 : }
    1958                 :            : 
    1959                 :            : /* Calculate DST offsets from transitions and UTC offsets
    1960                 :            :  *
    1961                 :            :  * This is necessary because each C `ttinfo` only contains the UTC offset,
    1962                 :            :  * time zone abbreviation and an isdst boolean - it does not include the
    1963                 :            :  * amount of the DST offset, but we need the amount for the dst() function.
    1964                 :            :  *
    1965                 :            :  * Thus function uses heuristics to infer what the offset should be, so it
    1966                 :            :  * is not guaranteed that this will work for all zones. If we cannot assign
    1967                 :            :  * a value for a given DST offset, we'll assume it's 1H rather than 0H, so
    1968                 :            :  * bool(dt.dst()) will always match ttinfo.isdst.
    1969                 :            :  */
    1970                 :            : static void
    1971                 :        350 : utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs,
    1972                 :            :                  unsigned char *isdsts, size_t num_transitions,
    1973                 :            :                  size_t num_ttinfos)
    1974                 :            : {
    1975                 :        350 :     size_t dst_count = 0;
    1976                 :        350 :     size_t dst_found = 0;
    1977         [ +  + ]:       2104 :     for (size_t i = 0; i < num_ttinfos; ++i) {
    1978                 :       1754 :         dst_count++;
    1979                 :            :     }
    1980                 :            : 
    1981         [ +  + ]:      39357 :     for (size_t i = 1; i < num_transitions; ++i) {
    1982         [ -  + ]:      39007 :         if (dst_count == dst_found) {
    1983                 :          0 :             break;
    1984                 :            :         }
    1985                 :            : 
    1986                 :      39007 :         size_t idx = trans_idx[i];
    1987                 :      39007 :         size_t comp_idx = trans_idx[i - 1];
    1988                 :            : 
    1989                 :            :         // Only look at DST offsets that have nto been assigned already
    1990   [ +  +  +  + ]:      39007 :         if (!isdsts[idx] || dstoffs[idx] != 0) {
    1991                 :      38155 :             continue;
    1992                 :            :         }
    1993                 :            : 
    1994                 :        852 :         long dstoff = 0;
    1995                 :        852 :         long utcoff = utcoffs[idx];
    1996                 :            : 
    1997         [ +  + ]:        852 :         if (!isdsts[comp_idx]) {
    1998                 :        592 :             dstoff = utcoff - utcoffs[comp_idx];
    1999                 :            :         }
    2000                 :            : 
    2001   [ +  +  +  + ]:        852 :         if (!dstoff && idx < (num_ttinfos - 1)) {
    2002                 :        254 :             comp_idx = trans_idx[i + 1];
    2003                 :            : 
    2004                 :            :             // If the following transition is also DST and we couldn't find
    2005                 :            :             // the DST offset by this point, we're going to have to skip it
    2006                 :            :             // and hope this transition gets assigned later
    2007         [ +  + ]:        254 :             if (isdsts[comp_idx]) {
    2008                 :        232 :                 continue;
    2009                 :            :             }
    2010                 :            : 
    2011                 :         22 :             dstoff = utcoff - utcoffs[comp_idx];
    2012                 :            :         }
    2013                 :            : 
    2014         [ +  + ]:        620 :         if (dstoff) {
    2015                 :        592 :             dst_found++;
    2016                 :        592 :             dstoffs[idx] = dstoff;
    2017                 :            :         }
    2018                 :            :     }
    2019                 :            : 
    2020         [ +  + ]:        350 :     if (dst_found < dst_count) {
    2021                 :            :         // If there are time zones we didn't find a value for, we'll end up
    2022                 :            :         // with dstoff = 0 for something where isdst=1. This is obviously
    2023                 :            :         // wrong — one hour will be a much better guess than 0.
    2024         [ +  + ]:       2056 :         for (size_t idx = 0; idx < num_ttinfos; ++idx) {
    2025   [ +  +  +  + ]:       1754 :             if (isdsts[idx] && !dstoffs[idx]) {
    2026                 :         76 :                 dstoffs[idx] = 3600;
    2027                 :            :             }
    2028                 :            :         }
    2029                 :            :     }
    2030                 :        350 : }
    2031                 :            : 
    2032                 :            : #define _swap(x, y, buffer) \
    2033                 :            :     buffer = x;             \
    2034                 :            :     x = y;                  \
    2035                 :            :     y = buffer;
    2036                 :            : 
    2037                 :            : /* Calculate transitions in local time from UTC time and offsets.
    2038                 :            :  *
    2039                 :            :  * We want to know when each transition occurs, denominated in the number of
    2040                 :            :  * nominal wall-time seconds between 1970-01-01T00:00:00 and the transition in
    2041                 :            :  * *local time* (note: this is *not* equivalent to the output of
    2042                 :            :  * datetime.timestamp, which is the total number of seconds actual elapsed
    2043                 :            :  * since 1970-01-01T00:00:00Z in UTC).
    2044                 :            :  *
    2045                 :            :  * This is an ambiguous question because "local time" can be ambiguous — but it
    2046                 :            :  * is disambiguated by the `fold` parameter, so we allocate two arrays:
    2047                 :            :  *
    2048                 :            :  *  trans_local[0]: The wall-time transitions for fold=0
    2049                 :            :  *  trans_local[1]: The wall-time transitions for fold=1
    2050                 :            :  *
    2051                 :            :  * This returns 0 on success and a negative number of failure. The trans_local
    2052                 :            :  * arrays must be freed if they are not NULL.
    2053                 :            :  */
    2054                 :            : static int
    2055                 :        350 : ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff,
    2056                 :            :             int64_t *trans_local[2], size_t num_ttinfos,
    2057                 :            :             size_t num_transitions)
    2058                 :            : {
    2059         [ +  + ]:        350 :     if (num_transitions == 0) {
    2060                 :         58 :         return 0;
    2061                 :            :     }
    2062                 :            : 
    2063                 :            :     // Copy the UTC transitions into each array to be modified in place later
    2064         [ +  + ]:        876 :     for (size_t i = 0; i < 2; ++i) {
    2065                 :        584 :         trans_local[i] = PyMem_Malloc(num_transitions * sizeof(int64_t));
    2066         [ -  + ]:        584 :         if (trans_local[i] == NULL) {
    2067                 :          0 :             return -1;
    2068                 :            :         }
    2069                 :            : 
    2070                 :        584 :         memcpy(trans_local[i], trans_utc, num_transitions * sizeof(int64_t));
    2071                 :            :     }
    2072                 :            : 
    2073                 :            :     int64_t offset_0, offset_1, buff;
    2074         [ +  + ]:        292 :     if (num_ttinfos > 1) {
    2075                 :        290 :         offset_0 = utcoff[0];
    2076                 :        290 :         offset_1 = utcoff[trans_idx[0]];
    2077                 :            : 
    2078         [ +  + ]:        290 :         if (offset_1 > offset_0) {
    2079                 :         98 :             _swap(offset_0, offset_1, buff);
    2080                 :            :         }
    2081                 :            :     }
    2082                 :            :     else {
    2083                 :          2 :         offset_0 = utcoff[0];
    2084                 :          2 :         offset_1 = utcoff[0];
    2085                 :            :     }
    2086                 :            : 
    2087                 :        292 :     trans_local[0][0] += offset_0;
    2088                 :        292 :     trans_local[1][0] += offset_1;
    2089                 :            : 
    2090         [ +  + ]:      39299 :     for (size_t i = 1; i < num_transitions; ++i) {
    2091                 :      39007 :         offset_0 = utcoff[trans_idx[i - 1]];
    2092                 :      39007 :         offset_1 = utcoff[trans_idx[i]];
    2093                 :            : 
    2094         [ +  + ]:      39007 :         if (offset_1 > offset_0) {
    2095                 :      19467 :             _swap(offset_1, offset_0, buff);
    2096                 :            :         }
    2097                 :            : 
    2098                 :      39007 :         trans_local[0][i] += offset_0;
    2099                 :      39007 :         trans_local[1][i] += offset_1;
    2100                 :            :     }
    2101                 :            : 
    2102                 :        292 :     return 0;
    2103                 :            : }
    2104                 :            : 
    2105                 :            : /* Simple bisect_right binary search implementation */
    2106                 :            : static size_t
    2107                 :       6647 : _bisect(const int64_t value, const int64_t *arr, size_t size)
    2108                 :            : {
    2109                 :       6647 :     size_t lo = 0;
    2110                 :       6647 :     size_t hi = size;
    2111                 :            :     size_t m;
    2112                 :            : 
    2113         [ +  + ]:      53087 :     while (lo < hi) {
    2114                 :      46440 :         m = (lo + hi) / 2;
    2115         [ +  + ]:      46440 :         if (arr[m] > value) {
    2116                 :      31414 :             hi = m;
    2117                 :            :         }
    2118                 :            :         else {
    2119                 :      15026 :             lo = m + 1;
    2120                 :            :         }
    2121                 :            :     }
    2122                 :            : 
    2123                 :       6647 :     return hi;
    2124                 :            : }
    2125                 :            : 
    2126                 :            : /* Find the ttinfo rules that apply at a given local datetime. */
    2127                 :            : static _ttinfo *
    2128                 :       8863 : find_ttinfo(PyZoneInfo_ZoneInfo *self, PyObject *dt)
    2129                 :            : {
    2130                 :            :     // datetime.time has a .tzinfo attribute that passes None as the dt
    2131                 :            :     // argument; it only really has meaning for fixed-offset zones.
    2132         [ +  + ]:       8863 :     if (dt == Py_None) {
    2133         [ +  + ]:        138 :         if (self->fixed_offset) {
    2134                 :         15 :             return &(self->tzrule_after.std);
    2135                 :            :         }
    2136                 :            :         else {
    2137                 :        123 :             return &NO_TTINFO;
    2138                 :            :         }
    2139                 :            :     }
    2140                 :            : 
    2141                 :            :     int64_t ts;
    2142         [ -  + ]:       8725 :     if (get_local_timestamp(dt, &ts)) {
    2143                 :          0 :         return NULL;
    2144                 :            :     }
    2145                 :            : 
    2146                 :       8725 :     unsigned char fold = PyDateTime_DATE_GET_FOLD(dt);
    2147                 :            :     assert(fold < 2);
    2148                 :       8725 :     int64_t *local_transitions = self->trans_list_wall[fold];
    2149                 :       8725 :     size_t num_trans = self->num_transitions;
    2150                 :            : 
    2151   [ +  +  +  + ]:       8725 :     if (num_trans && ts < local_transitions[0]) {
    2152                 :        364 :         return self->ttinfo_before;
    2153                 :            :     }
    2154   [ +  +  +  + ]:       8361 :     else if (!num_trans || ts > local_transitions[self->num_transitions - 1]) {
    2155                 :       1860 :         return find_tzrule_ttinfo(&(self->tzrule_after), ts, fold,
    2156                 :       1860 :                                   PyDateTime_GET_YEAR(dt));
    2157                 :            :     }
    2158                 :            :     else {
    2159                 :       6501 :         size_t idx = _bisect(ts, local_transitions, self->num_transitions) - 1;
    2160                 :            :         assert(idx < self->num_transitions);
    2161                 :       6501 :         return self->trans_ttinfos[idx];
    2162                 :            :     }
    2163                 :            : }
    2164                 :            : 
    2165                 :            : static int
    2166                 :      12775 : is_leap_year(int year)
    2167                 :            : {
    2168                 :      12775 :     const unsigned int ayear = (unsigned int)year;
    2169   [ +  +  +  +  :      12775 :     return ayear % 4 == 0 && (ayear % 100 != 0 || ayear % 400 == 0);
                   +  + ]
    2170                 :            : }
    2171                 :            : 
    2172                 :            : /* Calculates ordinal datetime from year, month and day. */
    2173                 :            : static int
    2174                 :      13585 : ymd_to_ord(int y, int m, int d)
    2175                 :            : {
    2176                 :      13585 :     y -= 1;
    2177                 :      13585 :     int days_before_year = (y * 365) + (y / 4) - (y / 100) + (y / 400);
    2178                 :      13585 :     int yearday = DAYS_BEFORE_MONTH[m];
    2179   [ +  +  +  + ]:      13585 :     if (m > 2 && is_leap_year(y + 1)) {
    2180                 :       5099 :         yearday += 1;
    2181                 :            :     }
    2182                 :            : 
    2183                 :      13585 :     return days_before_year + yearday + d;
    2184                 :            : }
    2185                 :            : 
    2186                 :            : /* Calculate the number of seconds since 1970-01-01 in local time.
    2187                 :            :  *
    2188                 :            :  * This gets a datetime in the same "units" as self->trans_list_wall so that we
    2189                 :            :  * can easily determine which transitions a datetime falls between. See the
    2190                 :            :  * comment above ts_to_local for more information.
    2191                 :            :  * */
    2192                 :            : static int
    2193                 :       9008 : get_local_timestamp(PyObject *dt, int64_t *local_ts)
    2194                 :            : {
    2195                 :            :     assert(local_ts != NULL);
    2196                 :            : 
    2197                 :            :     int hour, minute, second;
    2198                 :            :     int ord;
    2199         [ +  + ]:       9008 :     if (PyDateTime_CheckExact(dt)) {
    2200                 :       6741 :         int y = PyDateTime_GET_YEAR(dt);
    2201                 :       6741 :         int m = PyDateTime_GET_MONTH(dt);
    2202                 :       6741 :         int d = PyDateTime_GET_DAY(dt);
    2203                 :       6741 :         hour = PyDateTime_DATE_GET_HOUR(dt);
    2204                 :       6741 :         minute = PyDateTime_DATE_GET_MINUTE(dt);
    2205                 :       6741 :         second = PyDateTime_DATE_GET_SECOND(dt);
    2206                 :            : 
    2207                 :       6741 :         ord = ymd_to_ord(y, m, d);
    2208                 :            :     }
    2209                 :            :     else {
    2210                 :       2267 :         PyObject *num = PyObject_CallMethod(dt, "toordinal", NULL);
    2211         [ -  + ]:       2267 :         if (num == NULL) {
    2212                 :          0 :             return -1;
    2213                 :            :         }
    2214                 :            : 
    2215                 :       2267 :         ord = PyLong_AsLong(num);
    2216                 :       2267 :         Py_DECREF(num);
    2217   [ -  +  -  - ]:       2267 :         if (ord == -1 && PyErr_Occurred()) {
    2218                 :          0 :             return -1;
    2219                 :            :         }
    2220                 :            : 
    2221                 :       2267 :         num = PyObject_GetAttrString(dt, "hour");
    2222         [ -  + ]:       2267 :         if (num == NULL) {
    2223                 :          0 :             return -1;
    2224                 :            :         }
    2225                 :       2267 :         hour = PyLong_AsLong(num);
    2226                 :       2267 :         Py_DECREF(num);
    2227         [ -  + ]:       2267 :         if (hour == -1) {
    2228                 :          0 :             return -1;
    2229                 :            :         }
    2230                 :            : 
    2231                 :       2267 :         num = PyObject_GetAttrString(dt, "minute");
    2232         [ -  + ]:       2267 :         if (num == NULL) {
    2233                 :          0 :             return -1;
    2234                 :            :         }
    2235                 :       2267 :         minute = PyLong_AsLong(num);
    2236                 :       2267 :         Py_DECREF(num);
    2237         [ -  + ]:       2267 :         if (minute == -1) {
    2238                 :          0 :             return -1;
    2239                 :            :         }
    2240                 :            : 
    2241                 :       2267 :         num = PyObject_GetAttrString(dt, "second");
    2242         [ -  + ]:       2267 :         if (num == NULL) {
    2243                 :          0 :             return -1;
    2244                 :            :         }
    2245                 :       2267 :         second = PyLong_AsLong(num);
    2246                 :       2267 :         Py_DECREF(num);
    2247         [ -  + ]:       2267 :         if (second == -1) {
    2248                 :          0 :             return -1;
    2249                 :            :         }
    2250                 :            :     }
    2251                 :            : 
    2252                 :       9008 :     *local_ts = (int64_t)(ord - EPOCHORDINAL) * 86400 +
    2253                 :       9008 :                 (int64_t)(hour * 3600 + minute * 60 + second);
    2254                 :            : 
    2255                 :       9008 :     return 0;
    2256                 :            : }
    2257                 :            : 
    2258                 :            : /////
    2259                 :            : // Functions for cache handling
    2260                 :            : 
    2261                 :            : /* Constructor for StrongCacheNode */
    2262                 :            : static StrongCacheNode *
    2263                 :        192 : strong_cache_node_new(PyObject *key, PyObject *zone)
    2264                 :            : {
    2265                 :        192 :     StrongCacheNode *node = PyMem_Malloc(sizeof(StrongCacheNode));
    2266         [ -  + ]:        192 :     if (node == NULL) {
    2267                 :          0 :         return NULL;
    2268                 :            :     }
    2269                 :            : 
    2270                 :        192 :     Py_INCREF(key);
    2271                 :        192 :     Py_INCREF(zone);
    2272                 :            : 
    2273                 :        192 :     node->next = NULL;
    2274                 :        192 :     node->prev = NULL;
    2275                 :        192 :     node->key = key;
    2276                 :        192 :     node->zone = zone;
    2277                 :            : 
    2278                 :        192 :     return node;
    2279                 :            : }
    2280                 :            : 
    2281                 :            : /* Destructor for StrongCacheNode */
    2282                 :            : void
    2283                 :        192 : strong_cache_node_free(StrongCacheNode *node)
    2284                 :            : {
    2285                 :        192 :     Py_XDECREF(node->key);
    2286                 :        192 :     Py_XDECREF(node->zone);
    2287                 :            : 
    2288                 :        192 :     PyMem_Free(node);
    2289                 :        192 : }
    2290                 :            : 
    2291                 :            : /* Frees all nodes at or after a specified root in the strong cache.
    2292                 :            :  *
    2293                 :            :  * This can be used on the root node to free the entire cache or it can be used
    2294                 :            :  * to clear all nodes that have been expired (which, if everything is going
    2295                 :            :  * right, will actually only be 1 node at a time).
    2296                 :            :  */
    2297                 :            : void
    2298                 :         93 : strong_cache_free(StrongCacheNode *root)
    2299                 :            : {
    2300                 :         93 :     StrongCacheNode *node = root;
    2301                 :            :     StrongCacheNode *next_node;
    2302         [ +  + ]:        282 :     while (node != NULL) {
    2303                 :        189 :         next_node = node->next;
    2304                 :        189 :         strong_cache_node_free(node);
    2305                 :            : 
    2306                 :        189 :         node = next_node;
    2307                 :            :     }
    2308                 :         93 : }
    2309                 :            : 
    2310                 :            : /* Removes a node from the cache and update its neighbors.
    2311                 :            :  *
    2312                 :            :  * This is used both when ejecting a node from the cache and when moving it to
    2313                 :            :  * the front of the cache.
    2314                 :            :  */
    2315                 :            : static void
    2316                 :        197 : remove_from_strong_cache(StrongCacheNode *node)
    2317                 :            : {
    2318         [ -  + ]:        197 :     if (ZONEINFO_STRONG_CACHE == node) {
    2319                 :          0 :         ZONEINFO_STRONG_CACHE = node->next;
    2320                 :            :     }
    2321                 :            : 
    2322         [ +  + ]:        197 :     if (node->prev != NULL) {
    2323                 :          5 :         node->prev->next = node->next;
    2324                 :            :     }
    2325                 :            : 
    2326         [ -  + ]:        197 :     if (node->next != NULL) {
    2327                 :          0 :         node->next->prev = node->prev;
    2328                 :            :     }
    2329                 :            : 
    2330                 :        197 :     node->next = NULL;
    2331                 :        197 :     node->prev = NULL;
    2332                 :        197 : }
    2333                 :            : 
    2334                 :            : /* Retrieves the node associated with a key, if it exists.
    2335                 :            :  *
    2336                 :            :  * This traverses the strong cache until it finds a matching key and returns a
    2337                 :            :  * pointer to the relevant node if found. Returns NULL if no node is found.
    2338                 :            :  *
    2339                 :            :  * root may be NULL, indicating an empty cache.
    2340                 :            :  */
    2341                 :            : static StrongCacheNode *
    2342                 :        596 : find_in_strong_cache(const StrongCacheNode *const root, PyObject *const key)
    2343                 :            : {
    2344                 :        596 :     const StrongCacheNode *node = root;
    2345         [ +  + ]:       1272 :     while (node != NULL) {
    2346                 :       1050 :         int rv = PyObject_RichCompareBool(key, node->key, Py_EQ);
    2347         [ -  + ]:       1050 :         if (rv < 0) {
    2348                 :          0 :             return NULL;
    2349                 :            :         }
    2350         [ +  + ]:       1050 :         if (rv) {
    2351                 :        374 :             return (StrongCacheNode *)node;
    2352                 :            :         }
    2353                 :            : 
    2354                 :        676 :         node = node->next;
    2355                 :            :     }
    2356                 :            : 
    2357                 :        222 :     return NULL;
    2358                 :            : }
    2359                 :            : 
    2360                 :            : /* Ejects a given key from the class's strong cache, if applicable.
    2361                 :            :  *
    2362                 :            :  * This function is used to enable the per-key functionality in clear_cache.
    2363                 :            :  */
    2364                 :            : static int
    2365                 :          3 : eject_from_strong_cache(const PyTypeObject *const type, PyObject *key)
    2366                 :            : {
    2367         [ -  + ]:          3 :     if (type != &PyZoneInfo_ZoneInfoType) {
    2368                 :          0 :         return 0;
    2369                 :            :     }
    2370                 :            : 
    2371                 :          3 :     StrongCacheNode *node = find_in_strong_cache(ZONEINFO_STRONG_CACHE, key);
    2372         [ +  - ]:          3 :     if (node != NULL) {
    2373                 :          3 :         remove_from_strong_cache(node);
    2374                 :            : 
    2375                 :          3 :         strong_cache_node_free(node);
    2376                 :            :     }
    2377         [ #  # ]:          0 :     else if (PyErr_Occurred()) {
    2378                 :          0 :         return -1;
    2379                 :            :     }
    2380                 :          3 :     return 0;
    2381                 :            : }
    2382                 :            : 
    2383                 :            : /* Moves a node to the front of the LRU cache.
    2384                 :            :  *
    2385                 :            :  * The strong cache is an LRU cache, so whenever a given node is accessed, if
    2386                 :            :  * it is not at the front of the cache, it needs to be moved there.
    2387                 :            :  */
    2388                 :            : static void
    2389                 :        563 : move_strong_cache_node_to_front(StrongCacheNode **root, StrongCacheNode *node)
    2390                 :            : {
    2391                 :        563 :     StrongCacheNode *root_p = *root;
    2392         [ +  + ]:        563 :     if (root_p == node) {
    2393                 :        369 :         return;
    2394                 :            :     }
    2395                 :            : 
    2396                 :        194 :     remove_from_strong_cache(node);
    2397                 :            : 
    2398                 :        194 :     node->prev = NULL;
    2399                 :        194 :     node->next = root_p;
    2400                 :            : 
    2401         [ +  + ]:        194 :     if (root_p != NULL) {
    2402                 :        144 :         root_p->prev = node;
    2403                 :            :     }
    2404                 :            : 
    2405                 :        194 :     *root = node;
    2406                 :            : }
    2407                 :            : 
    2408                 :            : /* Retrieves a ZoneInfo from the strong cache if it's present.
    2409                 :            :  *
    2410                 :            :  * This function finds the ZoneInfo by key and if found will move the node to
    2411                 :            :  * the front of the LRU cache and return a new reference to it. It returns NULL
    2412                 :            :  * if the key is not in the cache.
    2413                 :            :  *
    2414                 :            :  * The strong cache is currently only implemented for the base class, so this
    2415                 :            :  * always returns a cache miss for subclasses.
    2416                 :            :  */
    2417                 :            : static PyObject *
    2418                 :        773 : zone_from_strong_cache(const PyTypeObject *const type, PyObject *const key)
    2419                 :            : {
    2420         [ +  + ]:        773 :     if (type != &PyZoneInfo_ZoneInfoType) {
    2421                 :        180 :         return NULL;  // Strong cache currently only implemented for base class
    2422                 :            :     }
    2423                 :            : 
    2424                 :        593 :     StrongCacheNode *node = find_in_strong_cache(ZONEINFO_STRONG_CACHE, key);
    2425                 :            : 
    2426         [ +  + ]:        593 :     if (node != NULL) {
    2427                 :        371 :         move_strong_cache_node_to_front(&ZONEINFO_STRONG_CACHE, node);
    2428                 :        371 :         Py_INCREF(node->zone);
    2429                 :        371 :         return node->zone;
    2430                 :            :     }
    2431                 :            : 
    2432                 :        222 :     return NULL;  // Cache miss
    2433                 :            : }
    2434                 :            : 
    2435                 :            : /* Inserts a new key into the strong LRU cache.
    2436                 :            :  *
    2437                 :            :  * This function is only to be used after a cache miss — it creates a new node
    2438                 :            :  * at the front of the cache and ejects any stale entries (keeping the size of
    2439                 :            :  * the cache to at most ZONEINFO_STRONG_CACHE_MAX_SIZE).
    2440                 :            :  */
    2441                 :            : static void
    2442                 :        362 : update_strong_cache(const PyTypeObject *const type, PyObject *key,
    2443                 :            :                     PyObject *zone)
    2444                 :            : {
    2445         [ +  + ]:        362 :     if (type != &PyZoneInfo_ZoneInfoType) {
    2446                 :        170 :         return;
    2447                 :            :     }
    2448                 :            : 
    2449                 :        192 :     StrongCacheNode *new_node = strong_cache_node_new(key, zone);
    2450                 :            : 
    2451                 :        192 :     move_strong_cache_node_to_front(&ZONEINFO_STRONG_CACHE, new_node);
    2452                 :            : 
    2453                 :        192 :     StrongCacheNode *node = new_node->next;
    2454         [ +  + ]:        831 :     for (size_t i = 1; i < ZONEINFO_STRONG_CACHE_MAX_SIZE; ++i) {
    2455         [ +  + ]:        786 :         if (node == NULL) {
    2456                 :        147 :             return;
    2457                 :            :         }
    2458                 :        639 :         node = node->next;
    2459                 :            :     }
    2460                 :            : 
    2461                 :            :     // Everything beyond this point needs to be freed
    2462         [ +  + ]:         45 :     if (node != NULL) {
    2463         [ +  - ]:         30 :         if (node->prev != NULL) {
    2464                 :         30 :             node->prev->next = NULL;
    2465                 :            :         }
    2466                 :         30 :         strong_cache_free(node);
    2467                 :            :     }
    2468                 :            : }
    2469                 :            : 
    2470                 :            : /* Clears all entries into a type's strong cache.
    2471                 :            :  *
    2472                 :            :  * Because the strong cache is not implemented for subclasses, this is a no-op
    2473                 :            :  * for everything except the base class.
    2474                 :            :  */
    2475                 :            : void
    2476                 :         77 : clear_strong_cache(const PyTypeObject *const type)
    2477                 :            : {
    2478         [ +  + ]:         77 :     if (type != &PyZoneInfo_ZoneInfoType) {
    2479                 :         14 :         return;
    2480                 :            :     }
    2481                 :            : 
    2482                 :         63 :     strong_cache_free(ZONEINFO_STRONG_CACHE);
    2483                 :         63 :     ZONEINFO_STRONG_CACHE = NULL;
    2484                 :            : }
    2485                 :            : 
    2486                 :            : static PyObject *
    2487                 :          4 : new_weak_cache(void)
    2488                 :            : {
    2489                 :            :     PyObject *WeakValueDictionary =
    2490                 :          4 :             _PyImport_GetModuleAttrString("weakref", "WeakValueDictionary");
    2491         [ -  + ]:          4 :     if (WeakValueDictionary == NULL) {
    2492                 :          0 :         return NULL;
    2493                 :            :     }
    2494                 :          4 :     PyObject *weak_cache = PyObject_CallNoArgs(WeakValueDictionary);
    2495                 :          4 :     Py_DECREF(WeakValueDictionary);
    2496                 :          4 :     return weak_cache;
    2497                 :            : }
    2498                 :            : 
    2499                 :            : static int
    2500                 :          3 : initialize_caches(void)
    2501                 :            : {
    2502                 :            :     // TODO: Move to a PyModule_GetState / PEP 573 based caching system.
    2503         [ +  - ]:          3 :     if (TIMEDELTA_CACHE == NULL) {
    2504                 :          3 :         TIMEDELTA_CACHE = PyDict_New();
    2505                 :            :     }
    2506                 :            :     else {
    2507                 :          0 :         Py_INCREF(TIMEDELTA_CACHE);
    2508                 :            :     }
    2509                 :            : 
    2510         [ -  + ]:          3 :     if (TIMEDELTA_CACHE == NULL) {
    2511                 :          0 :         return -1;
    2512                 :            :     }
    2513                 :            : 
    2514         [ +  - ]:          3 :     if (ZONEINFO_WEAK_CACHE == NULL) {
    2515                 :          3 :         ZONEINFO_WEAK_CACHE = new_weak_cache();
    2516                 :            :     }
    2517                 :            :     else {
    2518                 :          0 :         Py_INCREF(ZONEINFO_WEAK_CACHE);
    2519                 :            :     }
    2520                 :            : 
    2521         [ -  + ]:          3 :     if (ZONEINFO_WEAK_CACHE == NULL) {
    2522                 :          0 :         return -1;
    2523                 :            :     }
    2524                 :            : 
    2525                 :          3 :     return 0;
    2526                 :            : }
    2527                 :            : 
    2528                 :            : static PyObject *
    2529                 :          1 : zoneinfo_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs)
    2530                 :            : {
    2531                 :          1 :     PyObject *weak_cache = new_weak_cache();
    2532         [ -  + ]:          1 :     if (weak_cache == NULL) {
    2533                 :          0 :         return NULL;
    2534                 :            :     }
    2535                 :            : 
    2536         [ -  + ]:          1 :     if (PyObject_SetAttrString((PyObject *)cls, "_weak_cache",
    2537                 :            :                                weak_cache) < 0) {
    2538                 :          0 :         Py_DECREF(weak_cache);
    2539                 :          0 :         return NULL;
    2540                 :            :     }
    2541                 :          1 :     Py_DECREF(weak_cache);
    2542                 :          1 :     Py_RETURN_NONE;
    2543                 :            : }
    2544                 :            : 
    2545                 :            : /////
    2546                 :            : // Specify the ZoneInfo type
    2547                 :            : static PyMethodDef zoneinfo_methods[] = {
    2548                 :            :     {"clear_cache", (PyCFunction)(void (*)(void))zoneinfo_clear_cache,
    2549                 :            :      METH_VARARGS | METH_KEYWORDS | METH_CLASS,
    2550                 :            :      PyDoc_STR("Clear the ZoneInfo cache.")},
    2551                 :            :     {"no_cache", (PyCFunction)(void (*)(void))zoneinfo_no_cache,
    2552                 :            :      METH_VARARGS | METH_KEYWORDS | METH_CLASS,
    2553                 :            :      PyDoc_STR("Get a new instance of ZoneInfo, bypassing the cache.")},
    2554                 :            :     {"from_file", (PyCFunction)(void (*)(void))zoneinfo_from_file,
    2555                 :            :      METH_VARARGS | METH_KEYWORDS | METH_CLASS,
    2556                 :            :      PyDoc_STR("Create a ZoneInfo file from a file object.")},
    2557                 :            :     {"utcoffset", (PyCFunction)zoneinfo_utcoffset, METH_O,
    2558                 :            :      PyDoc_STR("Retrieve a timedelta representing the UTC offset in a zone at "
    2559                 :            :                "the given datetime.")},
    2560                 :            :     {"dst", (PyCFunction)zoneinfo_dst, METH_O,
    2561                 :            :      PyDoc_STR("Retrieve a timedelta representing the amount of DST applied "
    2562                 :            :                "in a zone at the given datetime.")},
    2563                 :            :     {"tzname", (PyCFunction)zoneinfo_tzname, METH_O,
    2564                 :            :      PyDoc_STR("Retrieve a string containing the abbreviation for the time "
    2565                 :            :                "zone that applies in a zone at a given datetime.")},
    2566                 :            :     {"fromutc", (PyCFunction)zoneinfo_fromutc, METH_O,
    2567                 :            :      PyDoc_STR("Given a datetime with local time in UTC, retrieve an adjusted "
    2568                 :            :                "datetime in local time.")},
    2569                 :            :     {"__reduce__", (PyCFunction)zoneinfo_reduce, METH_NOARGS,
    2570                 :            :      PyDoc_STR("Function for serialization with the pickle protocol.")},
    2571                 :            :     {"_unpickle", (PyCFunction)zoneinfo__unpickle, METH_VARARGS | METH_CLASS,
    2572                 :            :      PyDoc_STR("Private method used in unpickling.")},
    2573                 :            :     {"__init_subclass__", (PyCFunction)(void (*)(void))zoneinfo_init_subclass,
    2574                 :            :      METH_VARARGS | METH_KEYWORDS | METH_CLASS,
    2575                 :            :      PyDoc_STR("Function to initialize subclasses.")},
    2576                 :            :     {NULL} /* Sentinel */
    2577                 :            : };
    2578                 :            : 
    2579                 :            : static PyMemberDef zoneinfo_members[] = {
    2580                 :            :     {.name = "key",
    2581                 :            :      .offset = offsetof(PyZoneInfo_ZoneInfo, key),
    2582                 :            :      .type = T_OBJECT_EX,
    2583                 :            :      .flags = READONLY,
    2584                 :            :      .doc = NULL},
    2585                 :            :     {NULL}, /* Sentinel */
    2586                 :            : };
    2587                 :            : 
    2588                 :            : static PyTypeObject PyZoneInfo_ZoneInfoType = {
    2589                 :            :     PyVarObject_HEAD_INIT(NULL, 0)  //
    2590                 :            :         .tp_name = "zoneinfo.ZoneInfo",
    2591                 :            :     .tp_basicsize = sizeof(PyZoneInfo_ZoneInfo),
    2592                 :            :     .tp_weaklistoffset = offsetof(PyZoneInfo_ZoneInfo, weakreflist),
    2593                 :            :     .tp_repr = (reprfunc)zoneinfo_repr,
    2594                 :            :     .tp_str = (reprfunc)zoneinfo_str,
    2595                 :            :     .tp_getattro = PyObject_GenericGetAttr,
    2596                 :            :     .tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE),
    2597                 :            :     /* .tp_doc = zoneinfo_doc, */
    2598                 :            :     .tp_methods = zoneinfo_methods,
    2599                 :            :     .tp_members = zoneinfo_members,
    2600                 :            :     .tp_new = zoneinfo_new,
    2601                 :            :     .tp_dealloc = zoneinfo_dealloc,
    2602                 :            : };
    2603                 :            : 
    2604                 :            : /////
    2605                 :            : // Specify the _zoneinfo module
    2606                 :            : static PyMethodDef module_methods[] = {{NULL, NULL}};
    2607                 :            : static void
    2608                 :          3 : module_free(void *m)
    2609                 :            : {
    2610                 :          3 :     Py_XDECREF(_tzpath_find_tzfile);
    2611                 :          3 :     _tzpath_find_tzfile = NULL;
    2612                 :            : 
    2613                 :          3 :     Py_XDECREF(_common_mod);
    2614                 :          3 :     _common_mod = NULL;
    2615                 :            : 
    2616                 :          3 :     Py_XDECREF(io_open);
    2617                 :          3 :     io_open = NULL;
    2618                 :            : 
    2619                 :          3 :     xdecref_ttinfo(&NO_TTINFO);
    2620                 :            : 
    2621   [ +  -  -  + ]:          3 :     if (TIMEDELTA_CACHE != NULL && Py_REFCNT(TIMEDELTA_CACHE) > 1) {
    2622                 :          0 :         Py_DECREF(TIMEDELTA_CACHE);
    2623                 :            :     } else {
    2624         [ +  - ]:          3 :         Py_CLEAR(TIMEDELTA_CACHE);
    2625                 :            :     }
    2626                 :            : 
    2627   [ +  -  -  + ]:          3 :     if (ZONEINFO_WEAK_CACHE != NULL && Py_REFCNT(ZONEINFO_WEAK_CACHE) > 1) {
    2628                 :          0 :         Py_DECREF(ZONEINFO_WEAK_CACHE);
    2629                 :            :     } else {
    2630         [ +  - ]:          3 :         Py_CLEAR(ZONEINFO_WEAK_CACHE);
    2631                 :            :     }
    2632                 :            : 
    2633                 :          3 :     clear_strong_cache(&PyZoneInfo_ZoneInfoType);
    2634                 :          3 : }
    2635                 :            : 
    2636                 :            : static int
    2637                 :          3 : zoneinfomodule_exec(PyObject *m)
    2638                 :            : {
    2639                 :          3 :     PyDateTime_IMPORT;
    2640         [ -  + ]:          3 :     if (PyDateTimeAPI == NULL) {
    2641                 :          0 :         goto error;
    2642                 :            :     }
    2643                 :          3 :     PyZoneInfo_ZoneInfoType.tp_base = PyDateTimeAPI->TZInfoType;
    2644         [ -  + ]:          3 :     if (PyType_Ready(&PyZoneInfo_ZoneInfoType) < 0) {
    2645                 :          0 :         goto error;
    2646                 :            :     }
    2647                 :            : 
    2648                 :          3 :     Py_INCREF(&PyZoneInfo_ZoneInfoType);
    2649                 :          3 :     PyModule_AddObject(m, "ZoneInfo", (PyObject *)&PyZoneInfo_ZoneInfoType);
    2650                 :            : 
    2651                 :            :     /* Populate imports */
    2652                 :          3 :     _tzpath_find_tzfile =
    2653                 :          3 :         _PyImport_GetModuleAttrString("zoneinfo._tzpath", "find_tzfile");
    2654         [ -  + ]:          3 :     if (_tzpath_find_tzfile == NULL) {
    2655                 :          0 :         goto error;
    2656                 :            :     }
    2657                 :            : 
    2658                 :          3 :     io_open = _PyImport_GetModuleAttrString("io", "open");
    2659         [ -  + ]:          3 :     if (io_open == NULL) {
    2660                 :          0 :         goto error;
    2661                 :            :     }
    2662                 :            : 
    2663                 :          3 :     _common_mod = PyImport_ImportModule("zoneinfo._common");
    2664         [ -  + ]:          3 :     if (_common_mod == NULL) {
    2665                 :          0 :         goto error;
    2666                 :            :     }
    2667                 :            : 
    2668         [ +  - ]:          3 :     if (NO_TTINFO.utcoff == NULL) {
    2669                 :          3 :         NO_TTINFO.utcoff = Py_None;
    2670                 :          3 :         NO_TTINFO.dstoff = Py_None;
    2671                 :          3 :         NO_TTINFO.tzname = Py_None;
    2672                 :            : 
    2673         [ +  + ]:         12 :         for (size_t i = 0; i < 3; ++i) {
    2674                 :          9 :             Py_INCREF(Py_None);
    2675                 :            :         }
    2676                 :            :     }
    2677                 :            : 
    2678         [ -  + ]:          3 :     if (initialize_caches()) {
    2679                 :          0 :         goto error;
    2680                 :            :     }
    2681                 :            : 
    2682                 :          3 :     return 0;
    2683                 :            : 
    2684                 :          0 : error:
    2685                 :          0 :     return -1;
    2686                 :            : }
    2687                 :            : 
    2688                 :            : static PyModuleDef_Slot zoneinfomodule_slots[] = {
    2689                 :            :     {Py_mod_exec, zoneinfomodule_exec}, {0, NULL}};
    2690                 :            : 
    2691                 :            : static struct PyModuleDef zoneinfomodule = {
    2692                 :            :     PyModuleDef_HEAD_INIT,
    2693                 :            :     .m_name = "_zoneinfo",
    2694                 :            :     .m_doc = "C implementation of the zoneinfo module",
    2695                 :            :     .m_size = 0,
    2696                 :            :     .m_methods = module_methods,
    2697                 :            :     .m_slots = zoneinfomodule_slots,
    2698                 :            :     .m_free = (freefunc)module_free};
    2699                 :            : 
    2700                 :            : PyMODINIT_FUNC
    2701                 :          3 : PyInit__zoneinfo(void)
    2702                 :            : {
    2703                 :          3 :     return PyModuleDef_Init(&zoneinfomodule);
    2704                 :            : }

Generated by: LCOV version 1.14