cpython/Modules/_zoneinfo.c

#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)
{}