/* C implementation of the datetime module */ /* bpo-35081: Defining this prevents including the C API capsule; * internal versions of the Py*_Check macros which do not require * the capsule are defined below */ #define _PY_DATETIME_IMPL #ifndef Py_BUILD_CORE_BUILTIN #define Py_BUILD_CORE_MODULE … #endif #include "Python.h" #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_object.h" // _PyObject_Init() #include "pycore_time.h" // _PyTime_ObjectToTime_t() #include "datetime.h" #include <time.h> #ifdef MS_WINDOWS # include <winsock2.h> /* struct timeval */ #endif /* forward declarations */ static PyTypeObject PyDateTime_DateType; static PyTypeObject PyDateTime_DateTimeType; static PyTypeObject PyDateTime_TimeType; static PyTypeObject PyDateTime_DeltaType; static PyTypeObject PyDateTime_TZInfoType; static PyTypeObject PyDateTime_TimeZoneType; datetime_state; /* The module has a fixed number of static objects, due to being exposed * through the datetime C-API. There are five types exposed directly, * one type exposed indirectly, and one singleton constant (UTC). * * Each of these objects is hidden behind a macro in the same way as * the per-module objects stored in module state. The macros for the * static objects don't need to be passed a state, but the consistency * of doing so is more clear. We use a dedicated noop macro, NO_STATE, * to make the special case obvious. */ #define NO_STATE … #define DATE_TYPE(st) … #define DATETIME_TYPE(st) … #define TIME_TYPE(st) … #define DELTA_TYPE(st) … #define TZINFO_TYPE(st) … #define TIMEZONE_TYPE(st) … #define ISOCALENDAR_DATE_TYPE(st) … #define PyDate_Check(op) … #define PyDate_CheckExact(op) … #define PyDateTime_Check(op) … #define PyDateTime_CheckExact(op) … #define PyTime_Check(op) … #define PyTime_CheckExact(op) … #define PyDelta_Check(op) … #define PyDelta_CheckExact(op) … #define PyTZInfo_Check(op) … #define PyTZInfo_CheckExact(op) … #define PyTimezone_Check(op) … #define CONST_US_PER_MS(st) … #define CONST_US_PER_SECOND(st) … #define CONST_US_PER_MINUTE(st) … #define CONST_US_PER_HOUR(st) … #define CONST_US_PER_DAY(st) … #define CONST_US_PER_WEEK(st) … #define CONST_SEC_PER_DAY(st) … #define CONST_EPOCH(st) … #define CONST_UTC(st) … static datetime_state * get_module_state(PyObject *module) { … } #define INTERP_KEY … static PyObject * get_current_module(PyInterpreterState *interp, int *p_reloading) { … } static PyModuleDef datetimemodule; static datetime_state * _get_current_state(PyObject **p_mod) { … } #define GET_CURRENT_STATE(MOD_VAR) … #define RELEASE_CURRENT_STATE(ST_VAR, MOD_VAR) … static int set_current_module(PyInterpreterState *interp, PyObject *mod) { … } static void clear_current_module(PyInterpreterState *interp, PyObject *expected) { … } /* We require that C int be at least 32 bits, and use int virtually * everywhere. In just a few cases we use a temp long, where a Python * API returns a C long. In such cases, we have to ensure that the * final result fits in a C int (this can be an issue on 64-bit boxes). */ #if SIZEOF_INT < 4 # error "_datetime.c requires that C int have at least 32 bits" #endif #define MINYEAR … #define MAXYEAR … #define MAXORDINAL … /* Nine decimal digits is easy to communicate, and leaves enough room * so that two delta days can be added w/o fear of overflowing a signed * 32-bit int, and with plenty of room left over to absorb any possible * carries from adding seconds. */ #define MAX_DELTA_DAYS … /* Rename the long macros in datetime.h to more reasonable short names. */ #define GET_YEAR … #define GET_MONTH … #define GET_DAY … #define DATE_GET_HOUR … #define DATE_GET_MINUTE … #define DATE_GET_SECOND … #define DATE_GET_MICROSECOND … #define DATE_GET_FOLD … /* Date accessors for date and datetime. */ #define SET_YEAR(o, v) … #define SET_MONTH(o, v) … #define SET_DAY(o, v) … /* Date/Time accessors for datetime. */ #define DATE_SET_HOUR(o, v) … #define DATE_SET_MINUTE(o, v) … #define DATE_SET_SECOND(o, v) … #define DATE_SET_MICROSECOND(o, v) … #define DATE_SET_FOLD(o, v) … /* Time accessors for time. */ #define TIME_GET_HOUR … #define TIME_GET_MINUTE … #define TIME_GET_SECOND … #define TIME_GET_MICROSECOND … #define TIME_GET_FOLD … #define TIME_SET_HOUR(o, v) … #define TIME_SET_MINUTE(o, v) … #define TIME_SET_SECOND(o, v) … #define TIME_SET_MICROSECOND(o, v) … #define TIME_SET_FOLD(o, v) … /* Delta accessors for timedelta. */ #define GET_TD_DAYS(o) … #define GET_TD_SECONDS(o) … #define GET_TD_MICROSECONDS(o) … #define SET_TD_DAYS(o, v) … #define SET_TD_SECONDS(o, v) … #define SET_TD_MICROSECONDS(o, v) … #define HASTZINFO … #define GET_TIME_TZINFO … #define GET_DT_TZINFO … /* M is a char or int claiming to be a valid month. The macro is equivalent * to the two-sided Python test * 1 <= M <= 12 */ #define MONTH_IS_SANE(M) … static int check_tzinfo_subclass(PyObject *p); /*[clinic input] module datetime class datetime.datetime "PyDateTime_DateTime *" "get_datetime_state()->datetime_type" class datetime.date "PyDateTime_Date *" "get_datetime_state()->date_type" class datetime.time "PyDateTime_Time *" "get_datetime_state()->time_type" class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "get_datetime_state()->isocalendar_date_type" [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=c8f3d834a860d50a]*/ #include "clinic/_datetimemodule.c.h" /* --------------------------------------------------------------------------- * Math utilities. */ /* k = i+j overflows iff k differs in sign from both inputs, * iff k^i has sign bit set and k^j has sign bit set, * iff (k^i)&(k^j) has sign bit set. */ #define SIGNED_ADD_OVERFLOWED(RESULT, I, J) … /* Compute Python divmod(x, y), returning the quotient and storing the * remainder into *r. The quotient is the floor of x/y, and that's * the real point of this. C will probably truncate instead (C99 * requires truncation; C89 left it implementation-defined). * Simplification: we *require* that y > 0 here. That's appropriate * for all the uses made of it. This simplifies the code and makes * the overflow case impossible (divmod(LONG_MIN, -1) is the only * overflow case). */ static int divmod(int x, int y, int *r) { … } /* Nearest integer to m / n for integers m and n. Half-integer results * are rounded to even. */ static PyObject * divide_nearest(PyObject *m, PyObject *n) { … } /* --------------------------------------------------------------------------- * General calendrical helper functions */ /* For each month ordinal in 1..12, the number of days in that month, * and the number of days before that month in the same year. These * are correct for non-leap years only. */ static const int _days_in_month[] = …; static const int _days_before_month[] = …; /* year -> 1 if leap year, else 0. */ static int is_leap(int year) { … } /* year, month -> number of days in that month in that year */ static int days_in_month(int year, int month) { … } /* year, month -> number of days in year preceding first day of month */ static int days_before_month(int year, int month) { … } /* year -> number of days before January 1st of year. Remember that we * start with year 1, so days_before_year(1) == 0. */ static int days_before_year(int year) { … } /* Number of days in 4, 100, and 400 year cycles. That these have * the correct values is asserted in the module init function. */ #define DI4Y … #define DI100Y … #define DI400Y … /* ordinal -> year, month, day, considering 01-Jan-0001 as day 1. */ static void ord_to_ymd(int ordinal, int *year, int *month, int *day) { … } /* year, month, day -> ordinal, considering 01-Jan-0001 as day 1. */ static int ymd_to_ord(int year, int month, int day) { … } /* Day of week, where Monday==0, ..., Sunday==6. 1/1/1 was a Monday. */ static int weekday(int year, int month, int day) { … } /* Ordinal of the Monday starting week 1 of the ISO year. Week 1 is the * first calendar week containing a Thursday. */ static int iso_week1_monday(int year) { … } static int iso_to_ymd(const int iso_year, const int iso_week, const int iso_day, int *year, int *month, int *day) { … } /* --------------------------------------------------------------------------- * Range checkers. */ /* Check that -MAX_DELTA_DAYS <= days <= MAX_DELTA_DAYS. If so, return 0. * If not, raise OverflowError and return -1. */ static int check_delta_day_range(int days) { … } /* Check that date arguments are in range. Return 0 if they are. If they * aren't, raise ValueError and return -1. */ static int check_date_args(int year, int month, int day) { … } /* Check that time arguments are in range. Return 0 if they are. If they * aren't, raise ValueError and return -1. */ static int check_time_args(int h, int m, int s, int us, int fold) { … } /* --------------------------------------------------------------------------- * Normalization utilities. */ /* One step of a mixed-radix conversion. A "hi" unit is equivalent to * factor "lo" units. factor must be > 0. If *lo is less than 0, or * at least factor, enough of *lo is converted into "hi" units so that * 0 <= *lo < factor. The input values must be such that int overflow * is impossible. */ static void normalize_pair(int *hi, int *lo, int factor) { … } /* Fiddle days (d), seconds (s), and microseconds (us) so that * 0 <= *s < 24*3600 * 0 <= *us < 1000000 * The input values must be such that the internals don't overflow. * The way this routine is used, we don't get close. */ static void normalize_d_s_us(int *d, int *s, int *us) { … } /* Fiddle years (y), months (m), and days (d) so that * 1 <= *m <= 12 * 1 <= *d <= days_in_month(*y, *m) * The input values must be such that the internals don't overflow. * The way this routine is used, we don't get close. */ static int normalize_y_m_d(int *y, int *m, int *d) { … } /* Fiddle out-of-bounds months and days so that the result makes some kind * of sense. The parameters are both inputs and outputs. Returns < 0 on * failure, where failure means the adjusted year is out of bounds. */ static int normalize_date(int *year, int *month, int *day) { … } /* Force all the datetime fields into range. The parameters are both * inputs and outputs. Returns < 0 on error. */ static int normalize_datetime(int *year, int *month, int *day, int *hour, int *minute, int *second, int *microsecond) { … } /* --------------------------------------------------------------------------- * Basic object allocation: tp_alloc implementations. These allocate * Python objects of the right size and type, and do the Python object- * initialization bit. If there's not enough memory, they return NULL after * setting MemoryError. All data members remain uninitialized trash. * * We abuse the tp_alloc "nitems" argument to communicate whether a tzinfo * member is needed. This is ugly, imprecise, and possibly insecure. * tp_basicsize for the time and datetime types is set to the size of the * struct that has room for the tzinfo member, so subclasses in Python will * allocate enough space for a tzinfo member whether or not one is actually * needed. That's the "ugly and imprecise" parts. The "possibly insecure" * part is that PyType_GenericAlloc() (which subclasses in Python end up * using) just happens today to effectively ignore the nitems argument * when tp_itemsize is 0, which it is for these type objects. If that * changes, perhaps the callers of tp_alloc slots in this file should * be changed to force a 0 nitems argument unless the type being allocated * is a base type implemented in this file (so that tp_alloc is time_alloc * or datetime_alloc below, which know about the nitems abuse). */ static PyObject * time_alloc(PyTypeObject *type, Py_ssize_t aware) { … } static PyObject * datetime_alloc(PyTypeObject *type, Py_ssize_t aware) { … } /* --------------------------------------------------------------------------- * Helpers for setting object fields. These work on pointers to the * appropriate base class. */ /* For date and datetime. */ static void set_date_fields(PyDateTime_Date *self, int y, int m, int d) { … } /* --------------------------------------------------------------------------- * String parsing utilities and helper functions */ static unsigned char is_digit(const char c) { … } static const char * parse_digits(const char *ptr, int *var, size_t num_digits) { … } static int parse_isoformat_date(const char *dtstr, const size_t len, int *year, int *month, int *day) { … } static int parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, int *minute, int *second, int *microsecond) { … } static int parse_isoformat_time(const char *dtstr, size_t dtlen, int *hour, int *minute, int *second, int *microsecond, int *tzoffset, int *tzmicrosecond) { … } /* --------------------------------------------------------------------------- * Create various objects, mostly without range checking. */ /* Create a date instance with no range checking. */ static PyObject * new_date_ex(int year, int month, int day, PyTypeObject *type) { … } #define new_date(year, month, day) … // Forward declaration static PyObject * new_datetime_ex(int, int, int, int, int, int, int, PyObject *, PyTypeObject *); /* Create date instance with no range checking, or call subclass constructor */ static PyObject * new_date_subclass_ex(int year, int month, int day, PyObject *cls) { … } /* Create a datetime instance with no range checking. */ static PyObject * new_datetime_ex2(int year, int month, int day, int hour, int minute, int second, int usecond, PyObject *tzinfo, int fold, PyTypeObject *type) { … } static PyObject * new_datetime_ex(int year, int month, int day, int hour, int minute, int second, int usecond, PyObject *tzinfo, PyTypeObject *type) { … } #define new_datetime(y, m, d, hh, mm, ss, us, tzinfo, fold) … static PyObject * call_subclass_fold(PyObject *cls, int fold, const char *format, ...) { … } static PyObject * new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute, int second, int usecond, PyObject *tzinfo, int fold, PyObject *cls) { … } static PyObject * new_datetime_subclass_ex(int year, int month, int day, int hour, int minute, int second, int usecond, PyObject *tzinfo, PyObject *cls) { … } /* Create a time instance with no range checking. */ static PyObject * new_time_ex2(int hour, int minute, int second, int usecond, PyObject *tzinfo, int fold, PyTypeObject *type) { … } static PyObject * new_time_ex(int hour, int minute, int second, int usecond, PyObject *tzinfo, PyTypeObject *type) { … } #define new_time(hh, mm, ss, us, tzinfo, fold) … static PyObject * new_time_subclass_fold_ex(int hour, int minute, int second, int usecond, PyObject *tzinfo, int fold, PyObject *cls) { … } static PyDateTime_Delta * look_up_delta(int, int, int, PyTypeObject *); /* Create a timedelta instance. Normalize the members iff normalize is * true. Passing false is a speed optimization, if you know for sure * that seconds and microseconds are already in their proper ranges. In any * case, raises OverflowError and returns NULL if the normalized days is out * of range. */ static PyObject * new_delta_ex(int days, int seconds, int microseconds, int normalize, PyTypeObject *type) { … } #define new_delta(d, s, us, normalize) … PyDateTime_TimeZone; static PyDateTime_TimeZone * look_up_timezone(PyObject *offset, PyObject *name); /* Create new timezone instance checking offset range. This function does not check the name argument. Caller must assure that offset is a timedelta instance and name is either NULL or a unicode object. */ static PyObject * create_timezone(PyObject *offset, PyObject *name) { … } static int delta_bool(PyDateTime_Delta *self); static PyDateTime_TimeZone utc_timezone; static PyObject * new_timezone(PyObject *offset, PyObject *name) { … } /* --------------------------------------------------------------------------- * tzinfo helpers. */ /* Ensure that p is None or of a tzinfo subclass. Return 0 if OK; if not * raise TypeError and return -1. */ static int check_tzinfo_subclass(PyObject *p) { … } /* If self has a tzinfo member, return a BORROWED reference to it. Else * return NULL, which is NOT AN ERROR. There are no error returns here, * and the caller must not decref the result. */ static PyObject * get_tzinfo_member(PyObject *self) { … } /* Call getattr(tzinfo, name)(tzinfoarg), and check the result. tzinfo must * be an instance of the tzinfo class. If the method returns None, this * returns None. If the method doesn't return None or timedelta, TypeError is * raised and this returns NULL. If it returns a timedelta and the value is * out of range or isn't a whole number of minutes, ValueError is raised and * this returns NULL. Else result is returned. */ static PyObject * call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg) { … } /* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the * result. tzinfo must be an instance of the tzinfo class. If utcoffset() * returns None, call_utcoffset returns 0 and sets *none to 1. If uctoffset() * doesn't return None or timedelta, TypeError is raised and this returns -1. * If utcoffset() returns an out of range timedelta, * ValueError is raised and this returns -1. Else *none is * set to 0 and the offset is returned (as timedelta, positive east of UTC). */ static PyObject * call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg) { … } /* Call tzinfo.dst(tzinfoarg), and extract an integer from the * result. tzinfo must be an instance of the tzinfo class. If dst() * returns None, call_dst returns 0 and sets *none to 1. If dst() * doesn't return None or timedelta, TypeError is raised and this * returns -1. If dst() returns an invalid timedelta for a UTC offset, * ValueError is raised and this returns -1. Else *none is set to 0 and * the offset is returned (as timedelta, positive east of UTC). */ static PyObject * call_dst(PyObject *tzinfo, PyObject *tzinfoarg) { … } /* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be * an instance of the tzinfo class or None. If tzinfo isn't None, and * tzname() doesn't return None or a string, TypeError is raised and this * returns NULL. If the result is a string, we ensure it is a Unicode * string. */ static PyObject * call_tzname(PyObject *tzinfo, PyObject *tzinfoarg) { … } /* repr is like "someclass(arg1, arg2)". If tzinfo isn't None, * stuff * ", tzinfo=" + repr(tzinfo) * before the closing ")". */ static PyObject * append_keyword_tzinfo(PyObject *repr, PyObject *tzinfo) { … } /* repr is like "someclass(arg1, arg2)". If fold isn't 0, * stuff * ", fold=" + repr(tzinfo) * before the closing ")". */ static PyObject * append_keyword_fold(PyObject *repr, int fold) { … } static inline PyObject * tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds) { … } /* --------------------------------------------------------------------------- * String format helpers. */ static PyObject * format_ctime(PyDateTime_Date *date, int hours, int minutes, int seconds) { … } static PyObject *delta_negative(PyDateTime_Delta *self); /* Add formatted UTC offset string to buf. buf has no more than * buflen bytes remaining. The UTC offset is gotten by calling * tzinfo.uctoffset(tzinfoarg). If that returns None, \0 is stored into * *buf, and that's all. Else the returned value is checked for sanity (an * integer in range), and if that's OK it's converted to an hours & minutes * string of the form * sign HH sep MM [sep SS [. UUUUUU]] * Returns 0 if everything is OK. If the return value from utcoffset() is * bogus, an appropriate exception is set and -1 is returned. */ static int format_utcoffset(char *buf, size_t buflen, const char *sep, PyObject *tzinfo, PyObject *tzinfoarg) { … } static PyObject * make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) { … } static PyObject * make_Zreplacement(PyObject *object, PyObject *tzinfoarg) { … } static PyObject * make_freplacement(PyObject *object) { … } /* I sure don't want to reproduce the strftime code from the time module, * so this imports the module and calls it. All the hair is due to * giving special meanings to the %z, %:z, %Z and %f format codes via a * preprocessing step on the format string. * tzinfoarg is the argument to pass to the object's tzinfo method, if * needed. */ static PyObject * wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, PyObject *tzinfoarg) { … } /* --------------------------------------------------------------------------- * Wrap functions from the time module. These aren't directly available * from C. Perhaps they should be. */ /* Call time.time() and return its result (a Python float). */ static PyObject * time_time(void) { … } /* Build a time.struct_time. The weekday and day number are automatically * computed from the y,m,d args. */ static PyObject * build_struct_time(int y, int m, int d, int hh, int mm, int ss, int dstflag) { … } /* --------------------------------------------------------------------------- * Miscellaneous helpers. */ /* The comparisons here all most naturally compute a cmp()-like result. * This little helper turns that into a bool result for rich comparisons. */ static PyObject * diff_to_bool(int diff, int op) { … } /* --------------------------------------------------------------------------- * Class implementations. */ /* * PyDateTime_Delta implementation. */ /* Convert a timedelta to a number of us, * (24*3600*self.days + self.seconds)*1000000 + self.microseconds * as a Python int. * Doing mixed-radix arithmetic by hand instead is excruciating in C, * due to ubiquitous overflow possibilities. */ static PyObject * delta_to_microseconds(PyDateTime_Delta *self) { … } static PyObject * checked_divmod(PyObject *a, PyObject *b) { … } /* Convert a number of us (as a Python int) to a timedelta. */ static PyObject * microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type) { … } #define microseconds_to_delta(pymicros) … static PyObject * multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta) { … } static PyObject * get_float_as_integer_ratio(PyObject *floatobj) { … } /* op is 0 for multiplication, 1 for division */ static PyObject * multiply_truedivide_timedelta_float(PyDateTime_Delta *delta, PyObject *floatobj, int op) { … } static PyObject * divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj) { … } static PyObject * divide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) { … } static PyObject * truedivide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) { … } static PyObject * truedivide_timedelta_int(PyDateTime_Delta *delta, PyObject *i) { … } static PyObject * delta_add(PyObject *left, PyObject *right) { … } static PyObject * delta_negative(PyDateTime_Delta *self) { … } static PyObject * delta_positive(PyDateTime_Delta *self) { … } static PyObject * delta_abs(PyDateTime_Delta *self) { … } static PyObject * delta_subtract(PyObject *left, PyObject *right) { … } static int delta_cmp(PyObject *self, PyObject *other) { … } static PyObject * delta_richcompare(PyObject *self, PyObject *other, int op) { … } static PyObject *delta_getstate(PyDateTime_Delta *self); static Py_hash_t delta_hash(PyDateTime_Delta *self) { … } static PyObject * delta_multiply(PyObject *left, PyObject *right) { … } static PyObject * delta_divide(PyObject *left, PyObject *right) { … } static PyObject * delta_truedivide(PyObject *left, PyObject *right) { … } static PyObject * delta_remainder(PyObject *left, PyObject *right) { … } static PyObject * delta_divmod(PyObject *left, PyObject *right) { … } /* Fold in the value of the tag ("seconds", "weeks", etc) component of a * timedelta constructor. sofar is the # of microseconds accounted for * so far, and there are factor microseconds per current unit, the number * of which is given by num. num * factor is added to sofar in a * numerically careful way, and that's the result. Any fractional * microseconds left over (this can happen if num is a float type) are * added into *leftover. * Note that there are many ways this can give an error (NULL) return. */ static PyObject * accum(const char* tag, PyObject *sofar, PyObject *num, PyObject *factor, double *leftover) { … } static PyObject * delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) { … } static int delta_bool(PyDateTime_Delta *self) { … } static PyObject * delta_repr(PyDateTime_Delta *self) { … } static PyObject * delta_str(PyDateTime_Delta *self) { … } /* Pickle support, a simple use of __reduce__. */ /* __getstate__ isn't exposed */ static PyObject * delta_getstate(PyDateTime_Delta *self) { … } static PyObject * delta_total_seconds(PyObject *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * delta_reduce(PyDateTime_Delta* self, PyObject *Py_UNUSED(ignored)) { … } #define OFFSET(field) … static PyMemberDef delta_members[] = …; static PyMethodDef delta_methods[] = …; static const char delta_doc[] = …); static PyNumberMethods delta_as_number = …; static PyTypeObject PyDateTime_DeltaType = …; // XXX Can we make this const? static PyDateTime_Delta zero_delta = …; static PyDateTime_Delta * look_up_delta(int days, int seconds, int microseconds, PyTypeObject *type) { … } /* * PyDateTime_Date implementation. */ /* Accessor properties. */ static PyObject * date_year(PyDateTime_Date *self, void *unused) { … } static PyObject * date_month(PyDateTime_Date *self, void *unused) { … } static PyObject * date_day(PyDateTime_Date *self, void *unused) { … } static PyGetSetDef date_getset[] = …; /* Constructors. */ static char *date_kws[] = …; static PyObject * date_from_pickle(PyTypeObject *type, PyObject *state) { … } static PyObject * date_new(PyTypeObject *type, PyObject *args, PyObject *kw) { … } static PyObject * date_fromtimestamp(PyObject *cls, PyObject *obj) { … } /* Return new date from current time. * We say this is equivalent to fromtimestamp(time.time()), and the * only way to be sure of that is to *call* time.time(). That's not * generally the same as calling C's time. */ static PyObject * date_today(PyObject *cls, PyObject *dummy) { … } /*[clinic input] @classmethod datetime.date.fromtimestamp timestamp: object / Create a date from a POSIX timestamp. The timestamp is a number, e.g. created via time.time(), that is interpreted as local time. [clinic start generated code]*/ static PyObject * datetime_date_fromtimestamp(PyTypeObject *type, PyObject *timestamp) /*[clinic end generated code: output=fd045fda58168869 input=eabb3fe7f40491fe]*/ { … } /* bpo-36025: This is a wrapper for API compatibility with the public C API, * which expects a function that takes an *args tuple, whereas the argument * clinic generates code that takes METH_O. */ static PyObject * datetime_date_fromtimestamp_capi(PyObject *cls, PyObject *args) { … } /* Return new date from proleptic Gregorian ordinal. Raises ValueError if * the ordinal is out of range. */ static PyObject * date_fromordinal(PyObject *cls, PyObject *args) { … } /* Return the new date from a string as generated by date.isoformat() */ static PyObject * date_fromisoformat(PyObject *cls, PyObject *dtstr) { … } static PyObject * date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw) { … } /* Return new date from _strptime.strptime_datetime_date(). */ static PyObject * date_strptime(PyObject *cls, PyObject *args) { … } /* * Date arithmetic. */ /* date + timedelta -> date. If arg negate is true, subtract the timedelta * instead. */ static PyObject * add_date_timedelta(PyDateTime_Date *date, PyDateTime_Delta *delta, int negate) { … } static PyObject * date_add(PyObject *left, PyObject *right) { … } static PyObject * date_subtract(PyObject *left, PyObject *right) { … } /* Various ways to turn a date into a string. */ static PyObject * date_repr(PyDateTime_Date *self) { … } static PyObject * date_isoformat(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } /* str() calls the appropriate isoformat() method. */ static PyObject * date_str(PyDateTime_Date *self) { … } static PyObject * date_ctime(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * date_strftime(PyDateTime_Date *self, PyObject *args, PyObject *kw) { … } static PyObject * date_format(PyDateTime_Date *self, PyObject *args) { … } /* ISO methods. */ static PyObject * date_isoweekday(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } PyDoc_STRVAR(iso_calendar_date__doc__, "The result of date.isocalendar() or datetime.isocalendar()\n\n\ This object may be accessed either as a tuple of\n\ ((year, week, weekday)\n\ or via the object attributes as named in the above tuple."); PyDateTime_IsoCalendarDate; static PyObject * iso_calendar_date_repr(PyDateTime_IsoCalendarDate *self) { … } static PyObject * iso_calendar_date_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * iso_calendar_date_year(PyDateTime_IsoCalendarDate *self, void *unused) { … } static PyObject * iso_calendar_date_week(PyDateTime_IsoCalendarDate *self, void *unused) { … } static PyObject * iso_calendar_date_weekday(PyDateTime_IsoCalendarDate *self, void *unused) { … } static PyGetSetDef iso_calendar_date_getset[] = …; static PyMethodDef iso_calendar_date_methods[] = …; static int iso_calendar_date_traverse(PyDateTime_IsoCalendarDate *self, visitproc visit, void *arg) { … } static void iso_calendar_date_dealloc(PyDateTime_IsoCalendarDate *self) { … } static PyType_Slot isocal_slots[] = …; static PyType_Spec isocal_spec = …; /*[clinic input] @classmethod datetime.IsoCalendarDate.__new__ as iso_calendar_date_new year: int week: int weekday: int [clinic start generated code]*/ static PyObject * iso_calendar_date_new_impl(PyTypeObject *type, int year, int week, int weekday) /*[clinic end generated code: output=383d33d8dc7183a2 input=4f2c663c9d19c4ee]*/ { … } static PyObject * date_isocalendar(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } /* Miscellaneous methods. */ static PyObject * date_richcompare(PyObject *self, PyObject *other, int op) { … } static PyObject * date_timetuple(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } /*[clinic input] datetime.date.replace year: int(c_default="GET_YEAR(self)") = unchanged month: int(c_default="GET_MONTH(self)") = unchanged day: int(c_default="GET_DAY(self)") = unchanged Return date with new specified fields. [clinic start generated code]*/ static PyObject * datetime_date_replace_impl(PyDateTime_Date *self, int year, int month, int day) /*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/ { … } static Py_hash_t generic_hash(unsigned char *data, int len) { … } static PyObject *date_getstate(PyDateTime_Date *self); static Py_hash_t date_hash(PyDateTime_Date *self) { … } static PyObject * date_toordinal(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * date_weekday(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) { … } /* Pickle support, a simple use of __reduce__. */ /* __getstate__ isn't exposed */ static PyObject * date_getstate(PyDateTime_Date *self) { … } static PyObject * date_reduce(PyDateTime_Date *self, PyObject *arg) { … } static PyMethodDef date_methods[] = …; static const char date_doc[] = …); static PyNumberMethods date_as_number = …; static PyTypeObject PyDateTime_DateType = …; /* * PyDateTime_TZInfo implementation. */ /* This is a pure abstract base class, so doesn't do anything beyond * raising NotImplemented exceptions. Real tzinfo classes need * to derive from this. This is mostly for clarity, and for efficiency in * datetime and time constructors (their tzinfo arguments need to * be subclasses of this tzinfo class, which is easy and quick to check). * * Note: For reasons having to do with pickling of subclasses, we have * to allow tzinfo objects to be instantiated. This wasn't an issue * in the Python implementation (__init__() could raise NotImplementedError * there without ill effect), but doing so in the C implementation hit a * brick wall. */ static PyObject * tzinfo_nogo(const char* methodname) { … } /* Methods. A subclass must implement these. */ static PyObject * tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt) { … } static PyObject * tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt) { … } static PyObject * tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt) { … } static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta, int factor); static PyObject *datetime_utcoffset(PyObject *self, PyObject *); static PyObject *datetime_dst(PyObject *self, PyObject *); static PyObject * tzinfo_fromutc(PyDateTime_TZInfo *self, PyObject *dt) { … } /* * Pickle support. This is solely so that tzinfo subclasses can use * pickling -- tzinfo itself is supposed to be uninstantiable. */ static PyObject * tzinfo_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { … } static PyMethodDef tzinfo_methods[] = …; static const char tzinfo_doc[] = …); static PyTypeObject PyDateTime_TZInfoType = …; static char *timezone_kws[] = …; static PyObject * timezone_new(PyTypeObject *type, PyObject *args, PyObject *kw) { … } static void timezone_dealloc(PyDateTime_TimeZone *self) { … } static PyObject * timezone_richcompare(PyDateTime_TimeZone *self, PyDateTime_TimeZone *other, int op) { … } static Py_hash_t timezone_hash(PyDateTime_TimeZone *self) { … } /* Check argument type passed to tzname, utcoffset, or dst methods. Returns 0 for good argument. Returns -1 and sets exception info otherwise. */ static int _timezone_check_argument(PyObject *dt, const char *meth) { … } static PyObject * timezone_repr(PyDateTime_TimeZone *self) { … } static PyObject * timezone_str(PyDateTime_TimeZone *self) { … } static PyObject * timezone_tzname(PyDateTime_TimeZone *self, PyObject *dt) { … } static PyObject * timezone_utcoffset(PyDateTime_TimeZone *self, PyObject *dt) { … } static PyObject * timezone_dst(PyObject *self, PyObject *dt) { … } static PyObject * timezone_fromutc(PyDateTime_TimeZone *self, PyDateTime_DateTime *dt) { … } static PyObject * timezone_getinitargs(PyDateTime_TimeZone *self, PyObject *Py_UNUSED(ignored)) { … } static PyMethodDef timezone_methods[] = …; static const char timezone_doc[] = …); static PyTypeObject PyDateTime_TimeZoneType = …; // XXX Can we make this const? static PyDateTime_TimeZone utc_timezone = …; static PyDateTime_TimeZone * look_up_timezone(PyObject *offset, PyObject *name) { … } /* * PyDateTime_Time implementation. */ /* Accessor properties. */ static PyObject * time_hour(PyDateTime_Time *self, void *unused) { … } static PyObject * time_minute(PyDateTime_Time *self, void *unused) { … } /* The name time_second conflicted with some platform header file. */ static PyObject * py_time_second(PyDateTime_Time *self, void *unused) { … } static PyObject * time_microsecond(PyDateTime_Time *self, void *unused) { … } static PyObject * time_tzinfo(PyDateTime_Time *self, void *unused) { … } static PyObject * time_fold(PyDateTime_Time *self, void *unused) { … } static PyGetSetDef time_getset[] = …; /* * Constructors. */ static char *time_kws[] = …; static PyObject * time_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) { … } static PyObject * time_new(PyTypeObject *type, PyObject *args, PyObject *kw) { … } /* Return new time from _strptime.strptime_datetime_time(). */ static PyObject * time_strptime(PyObject *cls, PyObject *args) { … } /* * Destructor. */ static void time_dealloc(PyDateTime_Time *self) { … } /* * Indirect access to tzinfo methods. */ /* These are all METH_NOARGS, so don't need to check the arglist. */ static PyObject * time_utcoffset(PyObject *self, PyObject *unused) { … } static PyObject * time_dst(PyObject *self, PyObject *unused) { … } static PyObject * time_tzname(PyDateTime_Time *self, PyObject *unused) { … } /* * Various ways to turn a time into a string. */ static PyObject * time_repr(PyDateTime_Time *self) { … } static PyObject * time_str(PyDateTime_Time *self) { … } static PyObject * time_isoformat(PyDateTime_Time *self, PyObject *args, PyObject *kw) { … } static PyObject * time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw) { … } /* * Miscellaneous methods. */ static PyObject * time_richcompare(PyObject *self, PyObject *other, int op) { … } static Py_hash_t time_hash(PyDateTime_Time *self) { … } /*[clinic input] datetime.time.replace hour: int(c_default="TIME_GET_HOUR(self)") = unchanged minute: int(c_default="TIME_GET_MINUTE(self)") = unchanged second: int(c_default="TIME_GET_SECOND(self)") = unchanged microsecond: int(c_default="TIME_GET_MICROSECOND(self)") = unchanged tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged * fold: int(c_default="TIME_GET_FOLD(self)") = unchanged Return time with new specified fields. [clinic start generated code]*/ static PyObject * datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute, int second, int microsecond, PyObject *tzinfo, int fold) /*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/ { … } static PyObject * time_fromisoformat(PyObject *cls, PyObject *tstr) { … } /* Pickle support, a simple use of __reduce__. */ /* Let basestate be the non-tzinfo data string. * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). * So it's a tuple in any (non-error) case. * __getstate__ isn't exposed. */ static PyObject * time_getstate(PyDateTime_Time *self, int proto) { … } static PyObject * time_reduce_ex(PyDateTime_Time *self, PyObject *args) { … } static PyObject * time_reduce(PyDateTime_Time *self, PyObject *arg) { … } static PyMethodDef time_methods[] = …; static const char time_doc[] = …); static PyTypeObject PyDateTime_TimeType = …; /* * PyDateTime_DateTime implementation. */ /* Accessor properties. Properties for day, month, and year are inherited * from date. */ static PyObject * datetime_hour(PyDateTime_DateTime *self, void *unused) { … } static PyObject * datetime_minute(PyDateTime_DateTime *self, void *unused) { … } static PyObject * datetime_second(PyDateTime_DateTime *self, void *unused) { … } static PyObject * datetime_microsecond(PyDateTime_DateTime *self, void *unused) { … } static PyObject * datetime_tzinfo(PyDateTime_DateTime *self, void *unused) { … } static PyObject * datetime_fold(PyDateTime_DateTime *self, void *unused) { … } static PyGetSetDef datetime_getset[] = …; /* * Constructors. */ static char *datetime_kws[] = …; static PyObject * datetime_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) { … } static PyObject * datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) { … } /* TM_FUNC is the shared type of _PyTime_localtime() and * _PyTime_gmtime(). */ TM_FUNC; /* As of version 2015f max fold in IANA database is * 23 hours at 1969-09-30 13:00:00 in Kwajalein. */ static long long max_fold_seconds = …; /* NB: date(1970,1,1).toordinal() == 719163 */ static long long epoch = …; static long long utc_to_seconds(int year, int month, int day, int hour, int minute, int second) { … } static long long local(long long u) { … } /* Internal helper. * Build datetime from a time_t and a distinct count of microseconds. * Pass localtime or gmtime for f, to control the interpretation of timet. */ static PyObject * datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, PyObject *tzinfo) { … } /* Internal helper. * Build datetime from a Python timestamp. Pass localtime or gmtime for f, * to control the interpretation of the timestamp. Since a double doesn't * have enough bits to cover a datetime's full range of precision, it's * better to call datetime_from_timet_and_us provided you have a way * to get that much precision (e.g., C time() isn't good enough). */ static PyObject * datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, PyObject *tzinfo) { … } /* Internal helper. * Build most accurate possible datetime for current time. Pass localtime or * gmtime for f as appropriate. */ static PyObject * datetime_best_possible(PyObject *cls, TM_FUNC f, PyObject *tzinfo) { … } /*[clinic input] @classmethod datetime.datetime.now tz: object = None Timezone object. Returns new datetime object representing current time local to tz. If no tz is specified, uses local timezone. [clinic start generated code]*/ static PyObject * datetime_datetime_now_impl(PyTypeObject *type, PyObject *tz) /*[clinic end generated code: output=b3386e5345e2b47a input=80d09869c5267d00]*/ { … } /* Return best possible UTC time -- this isn't constrained by the * precision of a timestamp. */ static PyObject * datetime_utcnow(PyObject *cls, PyObject *dummy) { … } /* Return new local datetime from timestamp (Python timestamp -- a double). */ static PyObject * datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) { … } /* Return new UTC datetime from timestamp (Python timestamp -- a double). */ static PyObject * datetime_utcfromtimestamp(PyObject *cls, PyObject *args) { … } /* Return new datetime from _strptime.strptime_datetime_datetime(). */ static PyObject * datetime_strptime(PyObject *cls, PyObject *args) { … } /* Return new datetime from date/datetime and time arguments. */ static PyObject * datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) { … } static PyObject * _sanitize_isoformat_str(PyObject *dtstr) { … } static Py_ssize_t _find_isoformat_datetime_separator(const char *dtstr, Py_ssize_t len) { … } static PyObject * datetime_fromisoformat(PyObject *cls, PyObject *dtstr) { … } /* * Destructor. */ static void datetime_dealloc(PyDateTime_DateTime *self) { … } /* * Indirect access to tzinfo methods. */ /* These are all METH_NOARGS, so don't need to check the arglist. */ static PyObject * datetime_utcoffset(PyObject *self, PyObject *unused) { … } static PyObject * datetime_dst(PyObject *self, PyObject *unused) { … } static PyObject * datetime_tzname(PyObject *self, PyObject *unused) { … } /* * datetime arithmetic. */ /* factor must be 1 (to add) or -1 (to subtract). The result inherits * the tzinfo state of date. */ static PyObject * add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta, int factor) { … } static PyObject * datetime_add(PyObject *left, PyObject *right) { … } static PyObject * datetime_subtract(PyObject *left, PyObject *right) { … } /* Various ways to turn a datetime into a string. */ static PyObject * datetime_repr(PyDateTime_DateTime *self) { … } static PyObject * datetime_str(PyDateTime_DateTime *self) { … } static PyObject * datetime_isoformat(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) { … } static PyObject * datetime_ctime(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } /* Miscellaneous methods. */ static PyObject * flip_fold(PyObject *dt) { … } static PyObject * get_flip_fold_offset(PyObject *dt) { … } /* PEP 495 exception: Whenever one or both of the operands in * inter-zone comparison is such that its utcoffset() depends * on the value of its fold attribute, the result is False. * * Return 1 if exception applies, 0 if not, and -1 on error. */ static int pep495_eq_exception(PyObject *self, PyObject *other, PyObject *offset_self, PyObject *offset_other) { … } static PyObject * datetime_richcompare(PyObject *self, PyObject *other, int op) { … } static Py_hash_t datetime_hash(PyDateTime_DateTime *self) { … } /*[clinic input] datetime.datetime.replace year: int(c_default="GET_YEAR(self)") = unchanged month: int(c_default="GET_MONTH(self)") = unchanged day: int(c_default="GET_DAY(self)") = unchanged hour: int(c_default="DATE_GET_HOUR(self)") = unchanged minute: int(c_default="DATE_GET_MINUTE(self)") = unchanged second: int(c_default="DATE_GET_SECOND(self)") = unchanged microsecond: int(c_default="DATE_GET_MICROSECOND(self)") = unchanged tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged * fold: int(c_default="DATE_GET_FOLD(self)") = unchanged Return datetime with new specified fields. [clinic start generated code]*/ static PyObject * datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year, int month, int day, int hour, int minute, int second, int microsecond, PyObject *tzinfo, int fold) /*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/ { … } static PyObject * local_timezone_from_timestamp(time_t timestamp) { … } static PyObject * local_timezone(PyDateTime_DateTime *utc_time) { … } static long long local_to_seconds(int year, int month, int day, int hour, int minute, int second, int fold); static PyObject * local_timezone_from_local(PyDateTime_DateTime *local_dt) { … } static PyDateTime_DateTime * datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) { … } static PyObject * datetime_timetuple(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } static long long local_to_seconds(int year, int month, int day, int hour, int minute, int second, int fold) { … } /* date(1970,1,1).toordinal() == 719163 */ #define EPOCH_SECONDS … static PyObject * datetime_timestamp(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * datetime_getdate(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * datetime_gettime(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * datetime_gettimetz(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } static PyObject * datetime_utctimetuple(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored)) { … } /* Pickle support, a simple use of __reduce__. */ /* Let basestate be the non-tzinfo data string. * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). * So it's a tuple in any (non-error) case. * __getstate__ isn't exposed. */ static PyObject * datetime_getstate(PyDateTime_DateTime *self, int proto) { … } static PyObject * datetime_reduce_ex(PyDateTime_DateTime *self, PyObject *args) { … } static PyObject * datetime_reduce(PyDateTime_DateTime *self, PyObject *arg) { … } static PyMethodDef datetime_methods[] = …; static const char datetime_doc[] = …); static PyNumberMethods datetime_as_number = …; static PyTypeObject PyDateTime_DateTimeType = …; /* --------------------------------------------------------------------------- * datetime C-API. */ static PyTypeObject * const capi_types[] = …; /* The C-API is process-global. This violates interpreter isolation * due to the objects stored here. Thus each of those objects must * be managed carefully. */ // XXX Can we make this const? static PyDateTime_CAPI capi = …; /* Get a new C API by calling this function. * Clients get at C API via PyDateTime_IMPORT, defined in datetime.h. */ static inline PyDateTime_CAPI * get_datetime_capi(void) { … } static PyObject * create_timezone_from_delta(int days, int sec, int ms, int normalize) { … } /* --------------------------------------------------------------------------- * Module state lifecycle. */ static int init_state(datetime_state *st, PyObject *module, PyObject *old_module) { … } static int traverse_state(datetime_state *st, visitproc visit, void *arg) { … } static int clear_state(datetime_state *st) { … } static int init_static_types(PyInterpreterState *interp, int reloading) { … } /* --------------------------------------------------------------------------- * Module methods and initialization. */ static PyMethodDef module_methods[] = …; static int _datetime_exec(PyObject *module) { … } static PyModuleDef_Slot module_slots[] = …; static int module_traverse(PyObject *mod, visitproc visit, void *arg) { … } static int module_clear(PyObject *mod) { … } static void module_free(void *mod) { … } static PyModuleDef datetimemodule = …; PyMODINIT_FUNC PyInit__datetime(void) { … } /* --------------------------------------------------------------------------- Some time zone algebra. For a datetime x, let x.n = x stripped of its timezone -- its naive time. x.o = x.utcoffset(), and assuming that doesn't raise an exception or return None x.d = x.dst(), and assuming that doesn't raise an exception or return None x.s = x's standard offset, x.o - x.d Now some derived rules, where k is a duration (timedelta). 1. x.o = x.s + x.d This follows from the definition of x.s. 2. If x and y have the same tzinfo member, x.s = y.s. This is actually a requirement, an assumption we need to make about sane tzinfo classes. 3. The naive UTC time corresponding to x is x.n - x.o. This is again a requirement for a sane tzinfo class. 4. (x+k).s = x.s This follows from #2, and that datimetimetz+timedelta preserves tzinfo. 5. (x+k).n = x.n + k Again follows from how arithmetic is defined. Now we can explain tz.fromutc(x). Let's assume it's an interesting case (meaning that the various tzinfo methods exist, and don't blow up or return None when called). The function wants to return a datetime y with timezone tz, equivalent to x. x is already in UTC. By #3, we want y.n - y.o = x.n [1] The algorithm starts by attaching tz to x.n, and calling that y. So x.n = y.n at the start. Then it wants to add a duration k to y, so that [1] becomes true; in effect, we want to solve [2] for k: (y+k).n - (y+k).o = x.n [2] By #1, this is the same as (y+k).n - ((y+k).s + (y+k).d) = x.n [3] By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start. Substituting that into [3], x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving k - (y+k).s - (y+k).d = 0; rearranging, k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so k = y.s - (y+k).d On the RHS, (y+k).d can't be computed directly, but y.s can be, and we approximate k by ignoring the (y+k).d term at first. Note that k can't be very large, since all offset-returning methods return a duration of magnitude less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must be 0, so ignoring it has no consequence then. In any case, the new value is z = y + y.s [4] It's helpful to step back at look at [4] from a higher level: it's simply mapping from UTC to tz's standard time. At this point, if z.n - z.o = x.n [5] we have an equivalent time, and are almost done. The insecurity here is at the start of daylight time. Picture US Eastern for concreteness. The wall time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good sense then. The docs ask that an Eastern tzinfo class consider such a time to be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST on the day DST starts. We want to return the 1:MM EST spelling because that's the only spelling that makes sense on the local wall clock. In fact, if [5] holds at this point, we do have the standard-time spelling, but that takes a bit of proof. We first prove a stronger result. What's the difference between the LHS and RHS of [5]? Let diff = x.n - (z.n - z.o) [6] Now z.n = by [4] (y + y.s).n = by #5 y.n + y.s = since y.n = x.n x.n + y.s = since z and y are have the same tzinfo member, y.s = z.s by #2 x.n + z.s Plugging that back into [6] gives diff = x.n - ((x.n + z.s) - z.o) = expanding x.n - x.n - z.s + z.o = cancelling - z.s + z.o = by #2 z.d So diff = z.d. If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time spelling we wanted in the endcase described above. We're done. Contrarily, if z.d = 0, then we have a UTC equivalent, and are also done. If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to add to z (in effect, z is in tz's standard time, and we need to shift the local clock into tz's daylight time). Let z' = z + z.d = z + diff [7] and we can again ask whether z'.n - z'.o = x.n [8] If so, we're done. If not, the tzinfo class is insane, according to the assumptions we've made. This also requires a bit of proof. As before, let's compute the difference between the LHS and RHS of [8] (and skipping some of the justifications for the kinds of substitutions we've done several times already): diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7] x.n - (z.n + diff - z'.o) = replacing diff via [6] x.n - (z.n + x.n - (z.n - z.o) - z'.o) = x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n - z.n + z.n - z.o + z'.o = cancel z.n - z.o + z'.o = #1 twice -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo z'.d - z.d So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal, we've found the UTC-equivalent so are done. In fact, we stop with [7] and return z', not bothering to compute z'.d. How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by a dst() offset, and starting *from* a time already in DST (we know z.d != 0), would have to change the result dst() returns: we start in DST, and moving a little further into it takes us out of DST. There isn't a sane case where this can happen. The closest it gets is at the end of DST, where there's an hour in UTC with no spelling in a hybrid tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM UTC) because the docs insist on that, but 0:MM is taken as being in daylight time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in standard time. Since that's what the local clock *does*, we want to map both UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous in local time, but so it goes -- it's the way the local clock works. When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0, so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going. z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8] (correctly) concludes that z' is not UTC-equivalent to x. Because we know z.d said z was in daylight time (else [5] would have held and we would have stopped then), and we know z.d != z'.d (else [8] would have held and we would have stopped then), and there are only 2 possible values dst() can return in Eastern, it follows that z'.d must be 0 (which it is in the example, but the reasoning doesn't depend on the example -- it depends on there being two possible dst() outcomes, one zero and the other non-zero). Therefore z' must be in standard time, and is the spelling we want in this case. Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is concerned (because it takes z' as being in standard time rather than the daylight time we intend here), but returning it gives the real-life "local clock repeats an hour" behavior when mapping the "unspellable" UTC hour into tz. When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with the 1:MM standard time spelling we want. So how can this break? One of the assumptions must be violated. Two possibilities: 1) [2] effectively says that y.s is invariant across all y belong to a given time zone. This isn't true if, for political reasons or continental drift, a region decides to change its base offset from UTC. 2) There may be versions of "double daylight" time where the tail end of the analysis gives up a step too early. I haven't thought about that enough to say. In any case, it's clear that the default fromutc() is strong enough to handle "almost all" time zones: so long as the standard offset is invariant, it doesn't matter if daylight time transition points change from year to year, or if daylight time is skipped in some years; it doesn't matter how large or small dst() may get within its bounds; and it doesn't even matter if some perverse time zone returns a negative dst()). So a breaking case must be pretty bizarre, and a tzinfo subclass can override fromutc() if it is. --------------------------------------------------------------------------- */