#ifndef Py_BUILD_CORE_BUILTIN #define Py_BUILD_CORE_MODULE … #endif #include "Python.h" #include "pycore_critical_section.h" // _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED() #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "datetime.h" // PyDateTime_TZInfo #include <stddef.h> // offsetof() #include <stdint.h> #include "clinic/_zoneinfo.c.h" /*[clinic input] module zoneinfo class zoneinfo.ZoneInfo "PyObject *" "PyTypeObject *" [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=d12c73c0eef36df8]*/ TransitionRuleType; StrongCacheNode; _ttinfo; _tzrule; PyZoneInfo_ZoneInfo; struct TransitionRuleType { … }; CalendarRule; DayRule; struct StrongCacheNode { … }; zoneinfo_state; // Constants static const int EPOCHORDINAL = …; static int DAYS_IN_MONTH[] = …; static int DAYS_BEFORE_MONTH[] = …; static const int SOURCE_NOCACHE = …; static const int SOURCE_CACHE = …; static const int SOURCE_FILE = …; static const size_t ZONEINFO_STRONG_CACHE_MAX_SIZE = …; // Forward declarations static int load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj); static void utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs, unsigned char *isdsts, size_t num_transitions, size_t num_ttinfos); static int ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff, int64_t *trans_local[2], size_t num_ttinfos, size_t num_transitions); static int parse_tz_str(zoneinfo_state *state, PyObject *tz_str_obj, _tzrule *out); static int parse_abbr(const char **p, PyObject **abbr); static int parse_tz_delta(const char **p, long *total_seconds); static int parse_transition_time(const char **p, int *hour, int *minute, int *second); static int parse_transition_rule(const char **p, TransitionRuleType **out); static _ttinfo * find_tzrule_ttinfo(_tzrule *rule, int64_t ts, unsigned char fold, int year); static _ttinfo * find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year, unsigned char *fold); static int build_ttinfo(zoneinfo_state *state, long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out); static void xdecref_ttinfo(_ttinfo *ttinfo); static int ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1); static int build_tzrule(zoneinfo_state *state, PyObject *std_abbr, PyObject *dst_abbr, long std_offset, long dst_offset, TransitionRuleType *start, TransitionRuleType *end, _tzrule *out); static void free_tzrule(_tzrule *tzrule); static PyObject * load_timedelta(zoneinfo_state *state, long seconds); static int get_local_timestamp(PyObject *dt, int64_t *local_ts); static _ttinfo * find_ttinfo(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *dt); static int ymd_to_ord(int y, int m, int d); static int is_leap_year(int year); static size_t _bisect(const int64_t value, const int64_t *arr, size_t size); static int eject_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *key); static void clear_strong_cache(zoneinfo_state *state, const PyTypeObject *const type); static void update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *key, PyObject *zone); static PyObject * zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *const key); static inline zoneinfo_state * zoneinfo_get_state(PyObject *mod) { … } static inline zoneinfo_state * zoneinfo_get_state_by_cls(PyTypeObject *cls) { … } static struct PyModuleDef zoneinfomodule; static inline zoneinfo_state * zoneinfo_get_state_by_self(PyTypeObject *self) { … } static PyObject * zoneinfo_new_instance(zoneinfo_state *state, PyTypeObject *type, PyObject *key) { … } static PyObject * get_weak_cache(zoneinfo_state *state, PyTypeObject *type) { … } /*[clinic input] @critical_section @classmethod zoneinfo.ZoneInfo.__new__ key: object Create a new ZoneInfo instance. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) /*[clinic end generated code: output=95e61dab86bb95c3 input=ef73d7a83bf8790e]*/ { … } static int zoneinfo_traverse(PyZoneInfo_ZoneInfo *self, visitproc visit, void *arg) { … } static int zoneinfo_clear(PyZoneInfo_ZoneInfo *self) { … } static void zoneinfo_dealloc(PyObject *obj_self) { … } /*[clinic input] @classmethod zoneinfo.ZoneInfo.from_file cls: defining_class file_obj: object / key: object = None Create a ZoneInfo file from a file object. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_from_file_impl(PyTypeObject *type, PyTypeObject *cls, PyObject *file_obj, PyObject *key) /*[clinic end generated code: output=77887d1d56a48324 input=d26111f29eed6863]*/ { … } /*[clinic input] @classmethod zoneinfo.ZoneInfo.no_cache cls: defining_class / key: object Get a new instance of ZoneInfo, bypassing the cache. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_no_cache_impl(PyTypeObject *type, PyTypeObject *cls, PyObject *key) /*[clinic end generated code: output=b0b09b3344c171b7 input=0238f3d56b1ea3f1]*/ { … } /*[clinic input] @critical_section @classmethod zoneinfo.ZoneInfo.clear_cache cls: defining_class / * only_keys: object = None Clear the ZoneInfo cache. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, PyObject *only_keys) /*[clinic end generated code: output=114d9b7c8a22e660 input=35944715df26d24e]*/ { … } /*[clinic input] zoneinfo.ZoneInfo.utcoffset cls: defining_class dt: object / Retrieve a timedelta representing the UTC offset in a zone at the given datetime. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_utcoffset_impl(PyObject *self, PyTypeObject *cls, PyObject *dt) /*[clinic end generated code: output=b71016c319ba1f91 input=2bb6c5364938f19c]*/ { … } /*[clinic input] zoneinfo.ZoneInfo.dst cls: defining_class dt: object / Retrieve a timedelta representing the amount of DST applied in a zone at the given datetime. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_dst_impl(PyObject *self, PyTypeObject *cls, PyObject *dt) /*[clinic end generated code: output=cb6168d7723a6ae6 input=2167fb80cf8645c6]*/ { … } /*[clinic input] zoneinfo.ZoneInfo.tzname cls: defining_class dt: object / Retrieve a string containing the abbreviation for the time zone that applies in a zone at a given datetime. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo_tzname_impl(PyObject *self, PyTypeObject *cls, PyObject *dt) /*[clinic end generated code: output=3b6ae6c3053ea75a input=15a59a4f92ed1f1f]*/ { … } #define GET_DT_TZINFO … static PyObject * zoneinfo_fromutc(PyObject *obj_self, PyObject *dt) { … } static PyObject * zoneinfo_repr(PyZoneInfo_ZoneInfo *self) { … } static PyObject * zoneinfo_str(PyZoneInfo_ZoneInfo *self) { … } /* Pickles the ZoneInfo object by key and source. * * ZoneInfo objects are pickled by reference to the TZif file that they came * from, which means that the exact transitions may be different or the file * may not un-pickle if the data has changed on disk in the interim. * * It is necessary to include a bit indicating whether or not the object * was constructed from the cache, because from-cache objects will hit the * unpickling process's cache, whereas no-cache objects will bypass it. * * Objects constructed from ZoneInfo.from_file cannot be pickled. */ static PyObject * zoneinfo_reduce(PyObject *obj_self, PyObject *unused) { … } /*[clinic input] @classmethod zoneinfo.ZoneInfo._unpickle cls: defining_class key: object from_cache: unsigned_char(bitwise=True) / Private method used in unpickling. [clinic start generated code]*/ static PyObject * zoneinfo_ZoneInfo__unpickle_impl(PyTypeObject *type, PyTypeObject *cls, PyObject *key, unsigned char from_cache) /*[clinic end generated code: output=556712fc709deecb input=6ac8c73eed3de316]*/ { … } /* It is relatively expensive to construct new timedelta objects, and in most * cases we're looking at a relatively small number of timedeltas, such as * integer number of hours, etc. We will keep a cache so that we construct * a minimal number of these. * * Possibly this should be replaced with an LRU cache so that it's not possible * for the memory usage to explode from this, but in order for this to be a * serious problem, one would need to deliberately craft a malicious time zone * file with many distinct offsets. As of tzdb 2019c, loading every single zone * fills the cache with ~450 timedeltas for a total size of ~12kB. * * This returns a new reference to the timedelta. */ static PyObject * load_timedelta(zoneinfo_state *state, long seconds) { … } /* Constructor for _ttinfo object - this starts by initializing the _ttinfo * to { NULL, NULL, NULL }, so that Py_XDECREF will work on partially * initialized _ttinfo objects. */ static int build_ttinfo(zoneinfo_state *state, long utcoffset, long dstoffset, PyObject *tzname, _ttinfo *out) { … } /* Decrease reference count on any non-NULL members of a _ttinfo */ static void xdecref_ttinfo(_ttinfo *ttinfo) { … } /* Equality function for _ttinfo. */ static int ttinfo_eq(const _ttinfo *const tti0, const _ttinfo *const tti1) { … } /* Given a file-like object, this populates a ZoneInfo object * * The current version calls into a Python function to read the data from * file into Python objects, and this translates those Python objects into * C values and calculates derived values (e.g. dstoff) in C. * * This returns 0 on success and -1 on failure. * * The function will never return while `self` is partially initialized — * the object only needs to be freed / deallocated if this succeeds. */ static int load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj) { … } /* Function to calculate the local timestamp of a transition from the year. */ int64_t calendarrule_year_to_timestamp(TransitionRuleType *base_self, int year) { … } /* Constructor for CalendarRule. */ int calendarrule_new(int month, int week, int day, int hour, int minute, int second, CalendarRule *out) { … } /* Function to calculate the local timestamp of a transition from the year. * * This translates the day of the year into a local timestamp — either a * 1-based Julian day, not including leap days, or the 0-based year-day, * including leap days. * */ int64_t dayrule_year_to_timestamp(TransitionRuleType *base_self, int year) { … } /* Constructor for DayRule. */ static int dayrule_new(int julian, int day, int hour, int minute, int second, DayRule *out) { … } /* Calculate the start and end rules for a _tzrule in the given year. */ static void tzrule_transitions(_tzrule *rule, int year, int64_t *start, int64_t *end) { … } /* Calculate the _ttinfo that applies at a given local time from a _tzrule. * * This takes a local timestamp and fold for disambiguation purposes; the year * could technically be calculated from the timestamp, but given that the * callers of this function already have the year information accessible from * the datetime struct, it is taken as an additional parameter to reduce * unnecessary calculation. * */ static _ttinfo * find_tzrule_ttinfo(_tzrule *rule, int64_t ts, unsigned char fold, int year) { … } /* Calculate the ttinfo and fold that applies for a _tzrule at an epoch time. * * This function can determine the _ttinfo that applies at a given epoch time, * (analogous to trans_list_utc), and whether or not the datetime is in a fold. * This is to be used in the .fromutc() function. * * The year is technically a redundant parameter, because it can be calculated * from the timestamp, but all callers of this function should have the year * in the datetime struct anyway, so taking it as a parameter saves unnecessary * calculation. **/ static _ttinfo * find_tzrule_ttinfo_fromutc(_tzrule *rule, int64_t ts, int year, unsigned char *fold) { … } /* Parse a TZ string in the format specified by the POSIX standard: * * std offset[dst[offset],start[/time],end[/time]] * * std and dst must be 3 or more characters long and must not contain a * leading colon, embedded digits, commas, nor a plus or minus signs; The * spaces between "std" and "offset" are only for display and are not actually * present in the string. * * The format of the offset is ``[+|-]hh[:mm[:ss]]`` * * See the POSIX.1 spec: IEE Std 1003.1-2018 §8.3: * * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html */ static int parse_tz_str(zoneinfo_state *state, PyObject *tz_str_obj, _tzrule *out) { … } static int parse_digits(const char **p, int min, int max, int *value) { … } /* Parse the STD and DST abbreviations from a TZ string. */ static int parse_abbr(const char **p, PyObject **abbr) { … } /* Parse a UTC offset from a TZ str. */ static int parse_tz_delta(const char **p, long *total_seconds) { … } /* Parse the date portion of a transition rule. */ static int parse_transition_rule(const char **p, TransitionRuleType **out) { … } /* Parse the time portion of a transition rule (e.g. following an /) */ static int parse_transition_time(const char **p, int *hour, int *minute, int *second) { … } /* Constructor for a _tzrule. * * If `dst_abbr` is NULL, this will construct an "STD-only" _tzrule, in which * case `dst_offset` will be ignored and `start` and `end` are expected to be * NULL as well. * * Returns 0 on success. */ static int build_tzrule(zoneinfo_state *state, PyObject *std_abbr, PyObject *dst_abbr, long std_offset, long dst_offset, TransitionRuleType *start, TransitionRuleType *end, _tzrule *out) { … } /* Destructor for _tzrule. */ static void free_tzrule(_tzrule *tzrule) { … } /* Calculate DST offsets from transitions and UTC offsets * * This is necessary because each C `ttinfo` only contains the UTC offset, * time zone abbreviation and an isdst boolean - it does not include the * amount of the DST offset, but we need the amount for the dst() function. * * Thus function uses heuristics to infer what the offset should be, so it * is not guaranteed that this will work for all zones. If we cannot assign * a value for a given DST offset, we'll assume it's 1H rather than 0H, so * bool(dt.dst()) will always match ttinfo.isdst. */ static void utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs, unsigned char *isdsts, size_t num_transitions, size_t num_ttinfos) { … } #define _swap(x, y, buffer) … /* Calculate transitions in local time from UTC time and offsets. * * We want to know when each transition occurs, denominated in the number of * nominal wall-time seconds between 1970-01-01T00:00:00 and the transition in * *local time* (note: this is *not* equivalent to the output of * datetime.timestamp, which is the total number of seconds actual elapsed * since 1970-01-01T00:00:00Z in UTC). * * This is an ambiguous question because "local time" can be ambiguous — but it * is disambiguated by the `fold` parameter, so we allocate two arrays: * * trans_local[0]: The wall-time transitions for fold=0 * trans_local[1]: The wall-time transitions for fold=1 * * This returns 0 on success and a negative number of failure. The trans_local * arrays must be freed if they are not NULL. */ static int ts_to_local(size_t *trans_idx, int64_t *trans_utc, long *utcoff, int64_t *trans_local[2], size_t num_ttinfos, size_t num_transitions) { … } /* Simple bisect_right binary search implementation */ static size_t _bisect(const int64_t value, const int64_t *arr, size_t size) { … } /* Find the ttinfo rules that apply at a given local datetime. */ static _ttinfo * find_ttinfo(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *dt) { … } static int is_leap_year(int year) { … } /* Calculates ordinal datetime from year, month and day. */ static int ymd_to_ord(int y, int m, int d) { … } /* Calculate the number of seconds since 1970-01-01 in local time. * * This gets a datetime in the same "units" as self->trans_list_wall so that we * can easily determine which transitions a datetime falls between. See the * comment above ts_to_local for more information. * */ static int get_local_timestamp(PyObject *dt, int64_t *local_ts) { … } ///// // Functions for cache handling /* Constructor for StrongCacheNode * * This function doesn't set MemoryError if PyMem_Malloc fails, * as the cache intentionally doesn't propagate exceptions * and fails silently if error occurs. */ static StrongCacheNode * strong_cache_node_new(PyObject *key, PyObject *zone) { … } /* Destructor for StrongCacheNode */ void strong_cache_node_free(StrongCacheNode *node) { … } /* Frees all nodes at or after a specified root in the strong cache. * * This can be used on the root node to free the entire cache or it can be used * to clear all nodes that have been expired (which, if everything is going * right, will actually only be 1 node at a time). */ void strong_cache_free(StrongCacheNode *root) { … } /* Removes a node from the cache and update its neighbors. * * This is used both when ejecting a node from the cache and when moving it to * the front of the cache. */ static void remove_from_strong_cache(zoneinfo_state *state, StrongCacheNode *node) { … } /* Retrieves the node associated with a key, if it exists. * * This traverses the strong cache until it finds a matching key and returns a * pointer to the relevant node if found. Returns NULL if no node is found. * * root may be NULL, indicating an empty cache. */ static StrongCacheNode * find_in_strong_cache(const StrongCacheNode *const root, PyObject *const key) { … } /* Ejects a given key from the class's strong cache, if applicable. * * This function is used to enable the per-key functionality in clear_cache. */ static int eject_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *key) { … } /* Moves a node to the front of the LRU cache. * * The strong cache is an LRU cache, so whenever a given node is accessed, if * it is not at the front of the cache, it needs to be moved there. */ static void move_strong_cache_node_to_front(zoneinfo_state *state, StrongCacheNode **root, StrongCacheNode *node) { … } /* Retrieves a ZoneInfo from the strong cache if it's present. * * This function finds the ZoneInfo by key and if found will move the node to * the front of the LRU cache and return a new reference to it. It returns NULL * if the key is not in the cache. * * The strong cache is currently only implemented for the base class, so this * always returns a cache miss for subclasses. */ static PyObject * zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *const key) { … } /* Inserts a new key into the strong LRU cache. * * This function is only to be used after a cache miss — it creates a new node * at the front of the cache and ejects any stale entries (keeping the size of * the cache to at most ZONEINFO_STRONG_CACHE_MAX_SIZE). */ static void update_strong_cache(zoneinfo_state *state, const PyTypeObject *const type, PyObject *key, PyObject *zone) { … } /* Clears all entries into a type's strong cache. * * Because the strong cache is not implemented for subclasses, this is a no-op * for everything except the base class. */ void clear_strong_cache(zoneinfo_state *state, const PyTypeObject *const type) { … } static PyObject * new_weak_cache(void) { … } // This function is not idempotent and must be called on a new module object. static int initialize_caches(zoneinfo_state *state) { … } static PyObject * zoneinfo_init_subclass(PyTypeObject *cls, PyObject *args, PyObject **kwargs) { … } ///// // Specify the ZoneInfo type static PyMethodDef zoneinfo_methods[] = …; static PyMemberDef zoneinfo_members[] = …; static PyType_Slot zoneinfo_slots[] = …; static PyType_Spec zoneinfo_spec = …; ///// // Specify the _zoneinfo module static PyMethodDef module_methods[] = …; static int module_traverse(PyObject *mod, visitproc visit, void *arg) { … } static int module_clear(PyObject *mod) { … } static void module_free(void *mod) { … } static int zoneinfomodule_exec(PyObject *m) { … } static PyModuleDef_Slot zoneinfomodule_slots[] = …; static struct PyModuleDef zoneinfomodule = …; PyMODINIT_FUNC PyInit__zoneinfo(void) { … }