diff options
author | Tim Peters <tim.peters@gmail.com> | 2002-12-16 20:18:38 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2002-12-16 20:18:38 (GMT) |
commit | 2a799bf77a83adef010ff4751e5195702f159f39 (patch) | |
tree | 90b697e3caf531a7fad627dfaf931a42415df25a /Modules | |
parent | 786ddb29c9170a585ca1010bd392fb5a5ff4078c (diff) | |
download | cpython-2a799bf77a83adef010ff4751e5195702f159f39.zip cpython-2a799bf77a83adef010ff4751e5195702f159f39.tar.gz cpython-2a799bf77a83adef010ff4751e5195702f159f39.tar.bz2 |
datetime escapes the sandbox. The Windows build is all set. I leave it
to others to argue about how to build it on other platforms (on Windows
it's in its own DLL).
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/datetimemodule.c | 5075 |
1 files changed, 5075 insertions, 0 deletions
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c new file mode 100644 index 0000000..58af972 --- /dev/null +++ b/Modules/datetimemodule.c @@ -0,0 +1,5075 @@ +/* C implementation for the date/time type documented at + * http://www.zope.org/Members/fdrake/DateTimeWiki/FrontPage + */ + +#include "Python.h" +#include "modsupport.h" +#include "structmember.h" + +#include <time.h> + +#include "datetime.h" + +/* 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 1 +#define MAXYEAR 9999 + +/* 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 999999999 + +/* Rename the long macros in datetime.h to more reasonable short names. */ +#define GET_YEAR PyDateTime_GET_YEAR +#define GET_MONTH PyDateTime_GET_MONTH +#define GET_DAY PyDateTime_GET_DAY +#define DATE_GET_HOUR PyDateTime_DATE_GET_HOUR +#define DATE_GET_MINUTE PyDateTime_DATE_GET_MINUTE +#define DATE_GET_SECOND PyDateTime_DATE_GET_SECOND +#define DATE_GET_MICROSECOND PyDateTime_DATE_GET_MICROSECOND + +/* Date accessors for date and datetime. */ +#define SET_YEAR(o, v) (((o)->data[0] = ((v) & 0xff00) >> 8), \ + ((o)->data[1] = ((v) & 0x00ff))) +#define SET_MONTH(o, v) (PyDateTime_GET_MONTH(o) = (v)) +#define SET_DAY(o, v) (PyDateTime_GET_DAY(o) = (v)) + +/* Date/Time accessors for datetime. */ +#define DATE_SET_HOUR(o, v) (PyDateTime_DATE_GET_HOUR(o) = (v)) +#define DATE_SET_MINUTE(o, v) (PyDateTime_DATE_GET_MINUTE(o) = (v)) +#define DATE_SET_SECOND(o, v) (PyDateTime_DATE_GET_SECOND(o) = (v)) +#define DATE_SET_MICROSECOND(o, v) \ + (((o)->data[7] = ((v) & 0xff0000) >> 16), \ + ((o)->data[8] = ((v) & 0x00ff00) >> 8), \ + ((o)->data[9] = ((v) & 0x0000ff))) + +/* Time accessors for time. */ +#define TIME_GET_HOUR PyDateTime_TIME_GET_HOUR +#define TIME_GET_MINUTE PyDateTime_TIME_GET_MINUTE +#define TIME_GET_SECOND PyDateTime_TIME_GET_SECOND +#define TIME_GET_MICROSECOND PyDateTime_TIME_GET_MICROSECOND +#define TIME_SET_HOUR(o, v) (PyDateTime_TIME_GET_HOUR(o) = (v)) +#define TIME_SET_MINUTE(o, v) (PyDateTime_TIME_GET_MINUTE(o) = (v)) +#define TIME_SET_SECOND(o, v) (PyDateTime_TIME_GET_SECOND(o) = (v)) +#define TIME_SET_MICROSECOND(o, v) \ + (((o)->data[3] = ((v) & 0xff0000) >> 16), \ + ((o)->data[4] = ((v) & 0x00ff00) >> 8), \ + ((o)->data[5] = ((v) & 0x0000ff))) + +/* Delta accessors for timedelta. */ +#define GET_TD_DAYS(o) (((PyDateTime_Delta *)(o))->days) +#define GET_TD_SECONDS(o) (((PyDateTime_Delta *)(o))->seconds) +#define GET_TD_MICROSECONDS(o) (((PyDateTime_Delta *)(o))->microseconds) + +#define SET_TD_DAYS(o, v) ((o)->days = (v)) +#define SET_TD_SECONDS(o, v) ((o)->seconds = (v)) +#define SET_TD_MICROSECONDS(o, v) ((o)->microseconds = (v)) + +/* Forward declarations. */ +static PyTypeObject PyDateTime_DateType; +static PyTypeObject PyDateTime_DateTimeType; +static PyTypeObject PyDateTime_DateTimeTZType; +static PyTypeObject PyDateTime_DeltaType; +static PyTypeObject PyDateTime_TimeType; +static PyTypeObject PyDateTime_TZInfoType; +static PyTypeObject PyDateTime_TimeTZType; + +/* --------------------------------------------------------------------------- + * 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) \ + ((((RESULT) ^ (I)) & ((RESULT) ^ (J))) < 0) + +/* 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) +{ + int quo; + + assert(y > 0); + quo = x / y; + *r = x - quo * y; + if (*r < 0) { + --quo; + *r += y; + } + assert(0 <= *r && *r < y); + return quo; +} + +/* --------------------------------------------------------------------------- + * 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 int _days_in_month[] = { + 0, /* unused; this vector uses 1-based indexing */ + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static int _days_before_month[] = { + 0, /* unused; this vector uses 1-based indexing */ + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 +}; + +/* year -> 1 if leap year, else 0. */ +static int +is_leap(int year) +{ + /* Cast year to unsigned. The result is the same either way, but + * C can generate faster code for unsigned mod than for signed + * mod (especially for % 4 -- a good compiler should just grab + * the last 2 bits when the LHS is unsigned). + */ + const unsigned int ayear = (unsigned int)year; + return ayear % 4 == 0 && (ayear % 100 != 0 || ayear % 400 == 0); +} + +/* year, month -> number of days in that month in that year */ +static int +days_in_month(int year, int month) +{ + assert(month >= 1); + assert(month <= 12); + if (month == 2 && is_leap(year)) + return 29; + else + return _days_in_month[month]; +} + +/* year, month -> number of days in year preceeding first day of month */ +static int +days_before_month(int year, int month) +{ + int days; + + assert(month >= 1); + assert(month <= 12); + days = _days_before_month[month]; + if (month > 2 && is_leap(year)) + ++days; + return days; +} + +/* 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) +{ + int y = year - 1; + /* This is incorrect if year <= 0; we really want the floor + * here. But so long as MINYEAR is 1, the smallest year this + * can see is 0 (this can happen in some normalization endcases), + * so we'll just special-case that. + */ + assert (year >= 0); + if (y >= 0) + return y*365 + y/4 - y/100 + y/400; + else { + assert(y == -1); + return -366; + } +} + +/* 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 1461 /* days_before_year(5); days in 4 years */ +#define DI100Y 36524 /* days_before_year(101); days in 100 years */ +#define DI400Y 146097 /* days_before_year(401); days in 400 years */ + +/* ordinal -> year, month, day, considering 01-Jan-0001 as day 1. */ +static void +ord_to_ymd(int ordinal, int *year, int *month, int *day) +{ + int n, n1, n4, n100, n400, leapyear, preceding; + + /* ordinal is a 1-based index, starting at 1-Jan-1. The pattern of + * leap years repeats exactly every 400 years. The basic strategy is + * to find the closest 400-year boundary at or before ordinal, then + * work with the offset from that boundary to ordinal. Life is much + * clearer if we subtract 1 from ordinal first -- then the values + * of ordinal at 400-year boundaries are exactly those divisible + * by DI400Y: + * + * D M Y n n-1 + * -- --- ---- ---------- ---------------- + * 31 Dec -400 -DI400Y -DI400Y -1 + * 1 Jan -399 -DI400Y +1 -DI400Y 400-year boundary + * ... + * 30 Dec 000 -1 -2 + * 31 Dec 000 0 -1 + * 1 Jan 001 1 0 400-year boundary + * 2 Jan 001 2 1 + * 3 Jan 001 3 2 + * ... + * 31 Dec 400 DI400Y DI400Y -1 + * 1 Jan 401 DI400Y +1 DI400Y 400-year boundary + */ + assert(ordinal >= 1); + --ordinal; + n400 = ordinal / DI400Y; + n = ordinal % DI400Y; + *year = n400 * 400 + 1; + + /* Now n is the (non-negative) offset, in days, from January 1 of + * year, to the desired date. Now compute how many 100-year cycles + * precede n. + * Note that it's possible for n100 to equal 4! In that case 4 full + * 100-year cycles precede the desired day, which implies the + * desired day is December 31 at the end of a 400-year cycle. + */ + n100 = n / DI100Y; + n = n % DI100Y; + + /* Now compute how many 4-year cycles precede it. */ + n4 = n / DI4Y; + n = n % DI4Y; + + /* And now how many single years. Again n1 can be 4, and again + * meaning that the desired day is December 31 at the end of the + * 4-year cycle. + */ + n1 = n / 365; + n = n % 365; + + *year += n100 * 100 + n4 * 4 + n1; + if (n1 == 4 || n100 == 4) { + assert(n == 0); + *year -= 1; + *month = 12; + *day = 31; + return; + } + + /* Now the year is correct, and n is the offset from January 1. We + * find the month via an estimate that's either exact or one too + * large. + */ + leapyear = n1 == 3 && (n4 != 24 || n100 == 3); + assert(leapyear == is_leap(*year)); + *month = (n + 50) >> 5; + preceding = (_days_before_month[*month] + (*month > 2 && leapyear)); + if (preceding > n) { + /* estimate is too large */ + *month -= 1; + preceding -= days_in_month(*year, *month); + } + n -= preceding; + assert(0 <= n); + assert(n < days_in_month(*year, *month)); + + *day = n + 1; +} + +/* year, month, day -> ordinal, considering 01-Jan-0001 as day 1. */ +static int +ymd_to_ord(int year, int month, int day) +{ + return days_before_year(year) + days_before_month(year, month) + day; +} + +/* Day of week, where Monday==0, ..., Sunday==6. 1/1/1 was a Monday. */ +static int +weekday(int year, int month, int day) +{ + return (ymd_to_ord(year, month, day) + 6) % 7; +} + +/* 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) +{ + int first_day = ymd_to_ord(year, 1, 1); /* ord of 1/1 */ + /* 0 if 1/1 is a Monday, 1 if a Tue, etc. */ + int first_weekday = (first_day + 6) % 7; + /* ordinal of closest Monday at or before 1/1 */ + int week1_monday = first_day - first_weekday; + + if (first_weekday > 3) /* if 1/1 was Fri, Sat, Sun */ + week1_monday += 7; + return week1_monday; +} + +/* --------------------------------------------------------------------------- + * 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) +{ + if (-MAX_DELTA_DAYS <= days && days <= MAX_DELTA_DAYS) + return 0; + PyErr_Format(PyExc_OverflowError, + "days=%d; must have magnitude <= %d", + days); + return -1; +} + +/* 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) +{ + + if (year < MINYEAR || year > MAXYEAR) { + PyErr_SetString(PyExc_ValueError, + "year is out of range"); + return -1; + } + if (month < 1 || month > 12) { + PyErr_SetString(PyExc_ValueError, + "month must be in 1..12"); + return -1; + } + if (day < 1 || day > days_in_month(year, month)) { + PyErr_SetString(PyExc_ValueError, + "day is out of range for month"); + return -1; + } + return 0; +} + +/* 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) +{ + if (h < 0 || h > 23) { + PyErr_SetString(PyExc_ValueError, + "hour must be in 0..23"); + return -1; + } + if (m < 0 || m > 59) { + PyErr_SetString(PyExc_ValueError, + "minute must be in 0..59"); + return -1; + } + if (s < 0 || s > 59) { + PyErr_SetString(PyExc_ValueError, + "second must be in 0..59"); + return -1; + } + if (us < 0 || us > 999999) { + PyErr_SetString(PyExc_ValueError, + "microsecond must be in 0..999999"); + return -1; + } + return 0; +} + +/* --------------------------------------------------------------------------- + * 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) +{ + assert(factor > 0); + assert(lo != hi); + if (*lo < 0 || *lo >= factor) { + const int num_hi = divmod(*lo, factor, lo); + const int new_hi = *hi + num_hi; + assert(! SIGNED_ADD_OVERFLOWED(new_hi, *hi, num_hi)); + *hi = new_hi; + } + assert(0 <= *lo && *lo < 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) +{ + if (*us < 0 || *us >= 1000000) { + normalize_pair(s, us, 1000000); + /* |s| can't be bigger than about + * |original s| + |original us|/1000000 now. + */ + + } + if (*s < 0 || *s >= 24*3600) { + normalize_pair(d, s, 24*3600); + /* |d| can't be bigger than about + * |original d| + + * (|original s| + |original us|/1000000) / (24*3600) now. + */ + } + assert(0 <= *s && *s < 24*3600); + assert(0 <= *us && *us < 1000000); +} + +/* 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 void +normalize_y_m_d(int *y, int *m, int *d) +{ + int dim; /* # of days in month */ + + /* This gets muddy: the proper range for day can't be determined + * without knowing the correct month and year, but if day is, e.g., + * plus or minus a million, the current month and year values make + * no sense (and may also be out of bounds themselves). + * Saying 12 months == 1 year should be non-controversial. + */ + if (*m < 1 || *m > 12) { + --*m; + normalize_pair(y, m, 12); + ++*m; + /* |y| can't be bigger than about + * |original y| + |original m|/12 now. + */ + } + assert(1 <= *m && *m <= 12); + + /* Now only day can be out of bounds (year may also be out of bounds + * for a datetime object, but we don't care about that here). + * If day is out of bounds, what to do is arguable, but at least the + * method here is principled and explainable. + */ + dim = days_in_month(*y, *m); + if (*d < 1 || *d > dim) { + /* Move day-1 days from the first of the month. First try to + * get off cheap if we're only one day out of range + * (adjustments for timezone alone can't be worse than that). + */ + if (*d == 0) { + --*m; + if (*m > 0) + *d = days_in_month(*y, *m); + else { + --*y; + *m = 12; + *d = 31; + } + } + else if (*d == dim + 1) { + /* move forward a day */ + ++*m; + *d = 1; + if (*m > 12) { + *m = 1; + ++*y; + } + } + else { + int ordinal = ymd_to_ord(*y, *m, 1) + + *d - 1; + ord_to_ymd(ordinal, y, m, d); + } + } + assert(*m > 0); + assert(*d > 0); +} + +/* 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) +{ + int result; + + normalize_y_m_d(year, month, day); + if (MINYEAR <= *year && *year <= MAXYEAR) + result = 0; + else { + PyErr_SetString(PyExc_OverflowError, + "date value out of range"); + result = -1; + } + return result; +} + +/* 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) +{ + normalize_pair(second, microsecond, 1000000); + normalize_pair(minute, second, 60); + normalize_pair(hour, minute, 60); + normalize_pair(day, hour, 24); + return normalize_date(year, month, day); +} + +/* --------------------------------------------------------------------------- + * tzinfo helpers. + */ + +/* 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) +{ + PyObject *tzinfo = NULL; + + if (PyDateTimeTZ_Check(self)) + tzinfo = ((PyDateTime_DateTimeTZ *)self)->tzinfo; + else if (PyTimeTZ_Check(self)) + tzinfo = ((PyDateTime_TimeTZ *)self)->tzinfo; + + return tzinfo; +} + +/* 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 (p == Py_None || PyTZInfo_Check(p)) + return 0; + PyErr_Format(PyExc_TypeError, + "tzinfo argument must be None or of a tzinfo subclass, " + "not type '%s'", + p->ob_type->tp_name); + return -1; +} + +/* Internal helper. + * Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the + * result. tzinfo must be an instance of the tzinfo class. If the method + * returns None, this returns 0 and sets *none to 1. If the method doesn't + * return a Python int or long, TypeError is raised and this returns -1. + * If it does return an int or long, but is outside the valid range for + * a UTC minute offset, ValueError is raised and this returns -1. + * Else *none is set to 0 and the integer method result is returned. + */ +static int +call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg, + int *none) +{ + PyObject *u; + long result = -1; /* Py{Int,Long}_AsLong return long */ + + assert(tzinfo != NULL); + assert(PyTZInfo_Check(tzinfo)); + assert(tzinfoarg != NULL); + + *none = 0; + u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg); + if (u == NULL) + return -1; + + if (u == Py_None) { + result = 0; + *none = 1; + goto Done; + } + + if (PyInt_Check(u)) + result = PyInt_AS_LONG(u); + else if (PyLong_Check(u)) + result = PyLong_AsLong(u); + else { + PyErr_Format(PyExc_TypeError, + "tzinfo.%s() must return None or int or long", + name); + goto Done; + } + +Done: + Py_DECREF(u); + if (result < -1439 || result > 1439) { + PyErr_Format(PyExc_ValueError, + "tzinfo.%s() returned %ld; must be in " + "-1439 .. 1439", + name, result); + result = -1; + } + return (int)result; +} + +/* 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 a Python int or long, TypeError is raised and this + * returns -1. If utcoffset() returns an int outside the legitimate range + * for a UTC offset, ValueError is raised and this returns -1. Else + * *none is set to 0 and the offset is returned. + */ +static int +call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none) +{ + return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none); +} + +/* 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 a Python int or long, TypeError is raised and this + * returns -1. If dst() returns an int outside the legitimate range + * for a UTC offset, ValueError is raised and this returns -1. Else + * *none is set to 0 and the offset is returned. + */ +static int +call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none) +{ + return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none); +} + +/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be + * an instance of the tzinfo class. If tzname() doesn't return None or + * a string, TypeError is raised and this returns NULL. + */ +static PyObject * +call_tzname(PyObject *tzinfo, PyObject *tzinfoarg) +{ + PyObject *result; + + assert(tzinfo != NULL); + assert(PyTZInfo_Check(tzinfo)); + assert(tzinfoarg != NULL); + + result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg); + if (result != NULL && result != Py_None && !PyString_Check(result)) { + PyErr_Format(PyExc_TypeError, ".tzinfo.tzname() must " + "return None or a string, not '%s'", + result->ob_type->tp_name); + Py_DECREF(result); + result = NULL; + } + return result; +} + +typedef enum { + /* an exception has been set; the caller should pass it on */ + OFFSET_ERROR, + + /* type isn't date, datetime, datetimetz subclass, time, or + * timetz subclass + */ + OFFSET_UNKNOWN, + + /* date, + * datetime, + * datetimetz with None tzinfo, + * datetimetz where utcoffset() return None + * time, + * timetz with None tzinfo, + * timetz where utcoffset() returns None + */ + OFFSET_NAIVE, + + /* timetz where utcoffset() doesn't return None, + * datetimetz where utcoffset() doesn't return None + */ + OFFSET_AWARE, +} naivety; + +/* Classify a datetime object as to whether it's naive or offset-aware. See + * the "naivety" typedef for details. If the type is aware, *offset is set + * to minutes east of UTC (as returned by the tzinfo.utcoffset() method). + * If the type is offset-naive, *offset is set to 0. + */ +static naivety +classify_object(PyObject *op, int *offset) +{ + int none; + PyObject *tzinfo; + + *offset = 0; + if (PyDateTime_CheckExact(op) || + PyTime_CheckExact(op) || + PyDate_CheckExact(op)) + return OFFSET_NAIVE; + + tzinfo = get_tzinfo_member(op); /* NULL means none, not error */ + if (tzinfo == Py_None) + return OFFSET_NAIVE; + if (tzinfo == NULL) + return OFFSET_UNKNOWN; + + *offset = call_utcoffset(tzinfo, op, &none); + if (*offset == -1 && PyErr_Occurred()) + return OFFSET_ERROR; + return none ? OFFSET_NAIVE : OFFSET_AWARE; +} + +/* 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) +{ + PyObject *temp; + + assert(PyString_Check(repr)); + assert(tzinfo); + if (tzinfo == Py_None) + return repr; + /* Get rid of the trailing ')'. */ + assert(PyString_AsString(repr)[PyString_Size(repr)-1] == ')'); + temp = PyString_FromStringAndSize(PyString_AsString(repr), + PyString_Size(repr) - 1); + Py_DECREF(repr); + if (temp == NULL) + return NULL; + repr = temp; + + /* Append ", tzinfo=". */ + PyString_ConcatAndDel(&repr, PyString_FromString(", tzinfo=")); + + /* Append repr(tzinfo). */ + PyString_ConcatAndDel(&repr, PyObject_Repr(tzinfo)); + + /* Add a closing paren. */ + PyString_ConcatAndDel(&repr, PyString_FromString(")")); + return repr; +} + +/* --------------------------------------------------------------------------- + * String format helpers. + */ + +static PyObject * +format_ctime(PyDateTime_Date *date, + int hours, int minutes, int seconds) +{ + static char *DayNames[] = { + "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" + }; + static char *MonthNames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + char buffer[128]; + int wday = weekday(GET_YEAR(date), GET_MONTH(date), GET_DAY(date)); + + PyOS_snprintf(buffer, sizeof(buffer), "%s %s %2d %02d:%02d:%02d %04d", + DayNames[wday], MonthNames[GET_MONTH(date) - 1], + GET_DAY(date), hours, minutes, seconds, + GET_YEAR(date)); + return PyString_FromString(buffer); +} + +/* Add an hours & minutes 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 + * 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, int buflen, const char *sep, + PyObject *tzinfo, PyObject *tzinfoarg) +{ + int offset; + int hours; + int minutes; + char sign; + int none; + + offset = call_utcoffset(tzinfo, tzinfoarg, &none); + if (offset == -1 && PyErr_Occurred()) + return -1; + if (none) { + *buf = '\0'; + return 0; + } + sign = '+'; + if (offset < 0) { + sign = '-'; + offset = - offset; + } + hours = divmod(offset, 60, &minutes); + PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes); + return 0; +} + +/* 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 and %Z format codes via a preprocessing + * step on the format string. + */ +static PyObject * +wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple) +{ + PyObject *result = NULL; /* guilty until proved innocent */ + + PyObject *zreplacement = NULL; /* py string, replacement for %z */ + PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ + + char *pin; /* pointer to next char in input format */ + char ch; /* next char in input format */ + + PyObject *newfmt = NULL; /* py string, the output format */ + char *pnew; /* pointer to available byte in output format */ + char totalnew; /* number bytes total in output format buffer, + exclusive of trailing \0 */ + char usednew; /* number bytes used so far in output format buffer */ + + char *ptoappend; /* pointer to string to append to output buffer */ + int ntoappend; /* # of bytes to append to output buffer */ + + char buf[100]; /* scratch buffer */ + + assert(object && format && timetuple); + assert(PyString_Check(format)); + + /* Scan the input format, looking for %z and %Z escapes, building + * a new format. + */ + totalnew = PyString_Size(format); /* realistic if no %z/%Z */ + newfmt = PyString_FromStringAndSize(NULL, totalnew); + if (newfmt == NULL) goto Done; + pnew = PyString_AsString(newfmt); + usednew = 0; + + pin = PyString_AsString(format); + while ((ch = *pin++) != '\0') { + if (ch != '%') { + buf[0] = ch; + ptoappend = buf; + ntoappend = 1; + } + else if ((ch = *pin++) == '\0') { + /* There's a lone trailing %; doesn't make sense. */ + PyErr_SetString(PyExc_ValueError, "strftime format " + "ends with raw %"); + goto Done; + } + /* A % has been seen and ch is the character after it. */ + else if (ch == 'z') { + if (zreplacement == NULL) { + /* format utcoffset */ + PyObject *tzinfo = get_tzinfo_member(object); + zreplacement = PyString_FromString(""); + if (zreplacement == NULL) goto Done; + if (tzinfo != Py_None && tzinfo != NULL) { + if (format_utcoffset(buf, + (int)sizeof(buf), + "", + tzinfo, + object) < 0) + goto Done; + Py_DECREF(zreplacement); + zreplacement = PyString_FromString(buf); + if (zreplacement == NULL) goto Done; + } + } + assert(zreplacement != NULL); + ptoappend = PyString_AsString(zreplacement); + ntoappend = PyString_Size(zreplacement); + } + else if (ch == 'Z') { + /* format tzname */ + if (Zreplacement == NULL) { + PyObject *tzinfo = get_tzinfo_member(object); + Zreplacement = PyString_FromString(""); + if (Zreplacement == NULL) goto Done; + if (tzinfo != Py_None && tzinfo != NULL) { + PyObject *temp = call_tzname(tzinfo, + object); + if (temp == NULL) goto Done; + if (temp != Py_None) { + assert(PyString_Check(temp)); + /* Since the tzname is getting + * stuffed into the format, we + * have to double any % signs + * so that strftime doesn't + * treat them as format codes. + */ + Py_DECREF(Zreplacement); + Zreplacement = PyObject_CallMethod( + temp, "replace", + "ss", "%", "%%"); + Py_DECREF(temp); + if (Zreplacement == NULL) + goto Done; + } + else + Py_DECREF(temp); + } + } + assert(Zreplacement != NULL); + ptoappend = PyString_AsString(Zreplacement); + ntoappend = PyString_Size(Zreplacement); + } + else { + buf[0] = '%'; + buf[1] = ch; + ptoappend = buf; + ntoappend = 2; + } + + /* Append the ntoappend chars starting at ptoappend to + * the new format. + */ + assert(ntoappend >= 0); + if (ntoappend == 0) + continue; + while (usednew + ntoappend > totalnew) { + int bigger = totalnew << 1; + if ((bigger >> 1) != totalnew) { /* overflow */ + PyErr_NoMemory(); + goto Done; + } + if (_PyString_Resize(&newfmt, bigger) < 0) + goto Done; + totalnew = bigger; + pnew = PyString_AsString(newfmt) + usednew; + } + memcpy(pnew, ptoappend, ntoappend); + pnew += ntoappend; + usednew += ntoappend; + assert(usednew <= totalnew); + } /* end while() */ + + if (_PyString_Resize(&newfmt, usednew) < 0) + goto Done; + { + PyObject *time = PyImport_ImportModule("time"); + if (time == NULL) + goto Done; + result = PyObject_CallMethod(time, "strftime", "OO", + newfmt, timetuple); + Py_DECREF(time); + } + Done: + Py_XDECREF(zreplacement); + Py_XDECREF(Zreplacement); + Py_XDECREF(newfmt); + return result; +} + +static char * +isoformat_date(PyDateTime_Date *dt, char buffer[], int bufflen) +{ + int x; + x = PyOS_snprintf(buffer, bufflen, + "%04d-%02d-%02d", + GET_YEAR(dt), GET_MONTH(dt), GET_DAY(dt)); + return buffer + x; +} + +static void +isoformat_time(PyDateTime_DateTime *dt, char buffer[], int bufflen) +{ + int us = DATE_GET_MICROSECOND(dt); + + PyOS_snprintf(buffer, bufflen, + "%02d:%02d:%02d", /* 8 characters */ + DATE_GET_HOUR(dt), + DATE_GET_MINUTE(dt), + DATE_GET_SECOND(dt)); + if (us) + PyOS_snprintf(buffer + 8, bufflen - 8, ".%06d", us); +} + +/* --------------------------------------------------------------------------- + * 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() +{ + PyObject *result = NULL; + PyObject *time = PyImport_ImportModule("time"); + + if (time != NULL) { + result = PyObject_CallMethod(time, "time", "()"); + Py_DECREF(time); + } + return result; +} + +/* 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) +{ + PyObject *time; + PyObject *result = NULL; + + time = PyImport_ImportModule("time"); + if (time != NULL) { + result = PyObject_CallMethod(time, "struct_time", + "((iiiiiiiii))", + y, m, d, + hh, mm, ss, + weekday(y, m, d), + days_before_month(y, m) + d, + dstflag); + Py_DECREF(time); + } + return result; +} + +/* --------------------------------------------------------------------------- + * Miscellaneous helpers. + */ + +/* For obscure reasons, we need to use tp_richcompare instead of tp_compare. + * 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) +{ + PyObject *result; + int istrue; + + switch (op) { + case Py_EQ: istrue = diff == 0; break; + case Py_NE: istrue = diff != 0; break; + case Py_LE: istrue = diff <= 0; break; + case Py_GE: istrue = diff >= 0; break; + case Py_LT: istrue = diff < 0; break; + case Py_GT: istrue = diff > 0; break; + default: + assert(! "op unknown"); + istrue = 0; /* To shut up compiler */ + } + result = istrue ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +/* --------------------------------------------------------------------------- + * Helpers for setting object fields. These work on pointers to the + * appropriate base class. + */ + +/* For date, datetime and datetimetz. */ +static void +set_date_fields(PyDateTime_Date *self, int y, int m, int d) +{ + self->hashcode = -1; + SET_YEAR(self, y); + SET_MONTH(self, m); + SET_DAY(self, d); +} + +/* For datetime and datetimetz. */ +static void +set_datetime_time_fields(PyDateTime_Date *self, int h, int m, int s, int us) +{ + DATE_SET_HOUR(self, h); + DATE_SET_MINUTE(self, m); + DATE_SET_SECOND(self, s); + DATE_SET_MICROSECOND(self, us); +} + +/* For time and timetz. */ +static void +set_time_fields(PyDateTime_Time *self, int h, int m, int s, int us) +{ + self->hashcode = -1; + TIME_SET_HOUR(self, h); + TIME_SET_MINUTE(self, m); + TIME_SET_SECOND(self, s); + TIME_SET_MICROSECOND(self, us); +} + +/* --------------------------------------------------------------------------- + * Create various objects, mostly without range checking. + */ + +/* Create a date instance with no range checking. */ +static PyObject * +new_date(int year, int month, int day) +{ + PyDateTime_Date *self; + + self = PyObject_New(PyDateTime_Date, &PyDateTime_DateType); + if (self != NULL) + set_date_fields(self, year, month, day); + return (PyObject *) self; +} + +/* Create a datetime instance with no range checking. */ +static PyObject * +new_datetime(int year, int month, int day, int hour, int minute, + int second, int usecond) +{ + PyDateTime_DateTime *self; + + self = PyObject_New(PyDateTime_DateTime, &PyDateTime_DateTimeType); + if (self != NULL) { + set_date_fields((PyDateTime_Date *)self, year, month, day); + set_datetime_time_fields((PyDateTime_Date *)self, + hour, minute, second, usecond); + } + return (PyObject *) self; +} + +/* Create a datetimetz instance with no range checking. */ +static PyObject * +new_datetimetz(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo) +{ + PyDateTime_DateTimeTZ *self; + + self = PyObject_New(PyDateTime_DateTimeTZ, &PyDateTime_DateTimeTZType); + if (self != NULL) { + set_date_fields((PyDateTime_Date *)self, year, month, day); + set_datetime_time_fields((PyDateTime_Date *)self, + hour, minute, second, usecond); + Py_INCREF(tzinfo); + self->tzinfo = tzinfo; + } + return (PyObject *) self; +} + +/* Create a time instance with no range checking. */ +static PyObject * +new_time(int hour, int minute, int second, int usecond) +{ + PyDateTime_Time *self; + + self = PyObject_New(PyDateTime_Time, &PyDateTime_TimeType); + if (self != NULL) + set_time_fields(self, hour, minute, second, usecond); + return (PyObject *) self; +} + +/* Create a timetz instance with no range checking. */ +static PyObject * +new_timetz(int hour, int minute, int second, int usecond, PyObject *tzinfo) +{ + PyDateTime_TimeTZ *self; + + self = PyObject_New(PyDateTime_TimeTZ, &PyDateTime_TimeTZType); + if (self != NULL) { + set_time_fields((PyDateTime_Time *)self, + hour, minute, second, usecond); + Py_INCREF(tzinfo); + self->tzinfo = tzinfo; + } + return (PyObject *) self; +} + +/* 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(int days, int seconds, int microseconds, int normalize) +{ + PyDateTime_Delta *self; + + if (normalize) + normalize_d_s_us(&days, &seconds, µseconds); + assert(0 <= seconds && seconds < 24*3600); + assert(0 <= microseconds && microseconds < 1000000); + + if (check_delta_day_range(days) < 0) + return NULL; + + self = PyObject_New(PyDateTime_Delta, &PyDateTime_DeltaType); + if (self != NULL) { + self->hashcode = -1; + SET_TD_DAYS(self, days); + SET_TD_SECONDS(self, seconds); + SET_TD_MICROSECONDS(self, microseconds); + } + return (PyObject *) self; +} + + +/* --------------------------------------------------------------------------- + * Cached Python objects; these are set by the module init function. + */ + +/* Conversion factors. */ +static PyObject *us_per_us = NULL; /* 1 */ +static PyObject *us_per_ms = NULL; /* 1000 */ +static PyObject *us_per_second = NULL; /* 1000000 */ +static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */ +static PyObject *us_per_hour = NULL; /* 1e6 * 3600 as Python long */ +static PyObject *us_per_day = NULL; /* 1e6 * 3600 * 24 as Python long */ +static PyObject *us_per_week = NULL; /* 1e6*3600*24*7 as Python long */ +static PyObject *seconds_per_day = NULL; /* 3600*24 as Python int */ + +/* Callables to support unpickling. */ +static PyObject *date_unpickler_object = NULL; +static PyObject *datetime_unpickler_object = NULL; +static PyObject *datetimetz_unpickler_object = NULL; +static PyObject *tzinfo_unpickler_object = NULL; +static PyObject *time_unpickler_object = NULL; +static PyObject *timetz_unpickler_object = NULL; + +/* --------------------------------------------------------------------------- + * 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 or long. + * Doing mixed-radix arithmetic by hand instead is excruciating in C, + * due to ubiquitous overflow possibilities. + */ +static PyObject * +delta_to_microseconds(PyDateTime_Delta *self) +{ + PyObject *x1 = NULL; + PyObject *x2 = NULL; + PyObject *x3 = NULL; + PyObject *result = NULL; + + x1 = PyInt_FromLong(GET_TD_DAYS(self)); + if (x1 == NULL) + goto Done; + x2 = PyNumber_Multiply(x1, seconds_per_day); /* days in seconds */ + if (x2 == NULL) + goto Done; + Py_DECREF(x1); + x1 = NULL; + + /* x2 has days in seconds */ + x1 = PyInt_FromLong(GET_TD_SECONDS(self)); /* seconds */ + if (x1 == NULL) + goto Done; + x3 = PyNumber_Add(x1, x2); /* days and seconds in seconds */ + if (x3 == NULL) + goto Done; + Py_DECREF(x1); + Py_DECREF(x2); + x1 = x2 = NULL; + + /* x3 has days+seconds in seconds */ + x1 = PyNumber_Multiply(x3, us_per_second); /* us */ + if (x1 == NULL) + goto Done; + Py_DECREF(x3); + x3 = NULL; + + /* x1 has days+seconds in us */ + x2 = PyInt_FromLong(GET_TD_MICROSECONDS(self)); + if (x2 == NULL) + goto Done; + result = PyNumber_Add(x1, x2); + +Done: + Py_XDECREF(x1); + Py_XDECREF(x2); + Py_XDECREF(x3); + return result; +} + +/* Convert a number of us (as a Python int or long) to a timedelta. + */ +static PyObject * +microseconds_to_delta(PyObject *pyus) +{ + int us; + int s; + int d; + + PyObject *tuple = NULL; + PyObject *num = NULL; + PyObject *result = NULL; + + tuple = PyNumber_Divmod(pyus, us_per_second); + if (tuple == NULL) + goto Done; + + num = PyTuple_GetItem(tuple, 1); /* us */ + if (num == NULL) + goto Done; + us = PyLong_AsLong(num); + num = NULL; + if (us < 0) { + /* The divisor was positive, so this must be an error. */ + assert(PyErr_Occurred()); + goto Done; + } + + num = PyTuple_GetItem(tuple, 0); /* leftover seconds */ + if (num == NULL) + goto Done; + Py_INCREF(num); + Py_DECREF(tuple); + + tuple = PyNumber_Divmod(num, seconds_per_day); + if (tuple == NULL) + goto Done; + Py_DECREF(num); + + num = PyTuple_GetItem(tuple, 1); /* seconds */ + if (num == NULL) + goto Done; + s = PyLong_AsLong(num); + num = NULL; + if (s < 0) { + /* The divisor was positive, so this must be an error. */ + assert(PyErr_Occurred()); + goto Done; + } + + num = PyTuple_GetItem(tuple, 0); /* leftover days */ + if (num == NULL) + goto Done; + Py_INCREF(num); + + d = PyLong_AsLong(num); + if (d == -1 && PyErr_Occurred()) + goto Done; + result = new_delta(d, s, us, 0); + +Done: + Py_XDECREF(tuple); + Py_XDECREF(num); + return result; +} + +static PyObject * +multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta) +{ + PyObject *pyus_in; + PyObject *pyus_out; + PyObject *result; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + + pyus_out = PyNumber_Multiply(pyus_in, intobj); + Py_DECREF(pyus_in); + if (pyus_out == NULL) + return NULL; + + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + return result; +} + +static PyObject * +divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj) +{ + PyObject *pyus_in; + PyObject *pyus_out; + PyObject *result; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + + pyus_out = PyNumber_FloorDivide(pyus_in, intobj); + Py_DECREF(pyus_in); + if (pyus_out == NULL) + return NULL; + + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + return result; +} + +static PyObject * +delta_add(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left) && PyDelta_Check(right)) { + /* delta + delta */ + /* The C-level additions can't overflow because of the + * invariant bounds. + */ + int days = GET_TD_DAYS(left) + GET_TD_DAYS(right); + int seconds = GET_TD_SECONDS(left) + GET_TD_SECONDS(right); + int microseconds = GET_TD_MICROSECONDS(left) + + GET_TD_MICROSECONDS(right); + result = new_delta(days, seconds, microseconds, 1); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_negative(PyDateTime_Delta *self) +{ + return new_delta(-GET_TD_DAYS(self), + -GET_TD_SECONDS(self), + -GET_TD_MICROSECONDS(self), + 1); +} + +static PyObject * +delta_positive(PyDateTime_Delta *self) +{ + /* Could optimize this (by returning self) if this isn't a + * subclass -- but who uses unary + ? Approximately nobody. + */ + return new_delta(GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self), + 0); +} + +static PyObject * +delta_abs(PyDateTime_Delta *self) +{ + PyObject *result; + + assert(GET_TD_MICROSECONDS(self) >= 0); + assert(GET_TD_SECONDS(self) >= 0); + + if (GET_TD_DAYS(self) < 0) + result = delta_negative(self); + else + result = delta_positive(self); + + return result; +} + +static PyObject * +delta_subtract(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left) && PyDelta_Check(right)) { + /* delta - delta */ + PyObject *minus_right = PyNumber_Negative(right); + if (minus_right) { + result = delta_add(left, minus_right); + Py_DECREF(minus_right); + } + else + result = NULL; + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + */ +static PyObject * +delta_richcompare(PyDateTime_Delta *self, PyObject *other, int op) +{ + int diff; + + if (! PyDelta_CheckExact(other)) { + PyErr_Format(PyExc_TypeError, + "can't compare %s to %s instance", + self->ob_type->tp_name, other->ob_type->tp_name); + return NULL; + } + diff = GET_TD_DAYS(self) - GET_TD_DAYS(other); + if (diff == 0) { + diff = GET_TD_SECONDS(self) - GET_TD_SECONDS(other); + if (diff == 0) + diff = GET_TD_MICROSECONDS(self) - + GET_TD_MICROSECONDS(other); + } + return diff_to_bool(diff, op); +} + +static PyObject *delta_getstate(PyDateTime_Delta *self); + +static long +delta_hash(PyDateTime_Delta *self) +{ + if (self->hashcode == -1) { + PyObject *temp = delta_getstate(self); + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static PyObject * +delta_multiply(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left)) { + /* delta * ??? */ + if (PyInt_Check(right) || PyLong_Check(right)) + result = multiply_int_timedelta(right, + (PyDateTime_Delta *) left); + } + else if (PyInt_Check(left) || PyLong_Check(left)) + result = multiply_int_timedelta(left, + (PyDateTime_Delta *) right); + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_divide(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left)) { + /* delta * ??? */ + if (PyInt_Check(right) || PyLong_Check(right)) + result = divide_timedelta_int( + (PyDateTime_Delta *)left, + right); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* 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) +{ + PyObject *prod; + PyObject *sum; + + assert(num != NULL); + + if (PyInt_Check(num) || PyLong_Check(num)) { + prod = PyNumber_Multiply(num, factor); + if (prod == NULL) + return NULL; + sum = PyNumber_Add(sofar, prod); + Py_DECREF(prod); + return sum; + } + + if (PyFloat_Check(num)) { + double dnum; + double fracpart; + double intpart; + PyObject *x; + PyObject *y; + + /* The Plan: decompose num into an integer part and a + * fractional part, num = intpart + fracpart. + * Then num * factor == + * intpart * factor + fracpart * factor + * and the LHS can be computed exactly in long arithmetic. + * The RHS is again broken into an int part and frac part. + * and the frac part is added into *leftover. + */ + dnum = PyFloat_AsDouble(num); + if (dnum == -1.0 && PyErr_Occurred()) + return NULL; + fracpart = modf(dnum, &intpart); + x = PyLong_FromDouble(intpart); + if (x == NULL) + return NULL; + + prod = PyNumber_Multiply(x, factor); + Py_DECREF(x); + if (prod == NULL) + return NULL; + + sum = PyNumber_Add(sofar, prod); + Py_DECREF(prod); + if (sum == NULL) + return NULL; + + if (fracpart == 0.0) + return sum; + /* So far we've lost no information. Dealing with the + * fractional part requires float arithmetic, and may + * lose a little info. + */ + assert(PyInt_Check(factor) || PyLong_Check(factor)); + if (PyInt_Check(factor)) + dnum = (double)PyInt_AsLong(factor); + else + dnum = PyLong_AsDouble(factor); + + dnum *= fracpart; + fracpart = modf(dnum, &intpart); + x = PyLong_FromDouble(intpart); + if (x == NULL) { + Py_DECREF(sum); + return NULL; + } + + y = PyNumber_Add(sum, x); + Py_DECREF(sum); + Py_DECREF(x); + *leftover += fracpart; + return y; + } + + PyErr_Format(PyExc_TypeError, + "unsupported type for timedelta %s component: %s", + tag, num->ob_type->tp_name); + return NULL; +} + +static PyObject * +delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + + /* Argument objects. */ + PyObject *day = NULL; + PyObject *second = NULL; + PyObject *us = NULL; + PyObject *ms = NULL; + PyObject *minute = NULL; + PyObject *hour = NULL; + PyObject *week = NULL; + + PyObject *x = NULL; /* running sum of microseconds */ + PyObject *y = NULL; /* temp sum of microseconds */ + double leftover_us = 0.0; + + static char *keywords[] = { + "days", "seconds", "microseconds", "milliseconds", + "minutes", "hours", "weeks", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|OOOOOOO:__new__", + keywords, + &day, &second, &us, + &ms, &minute, &hour, &week) == 0) + goto Done; + + x = PyInt_FromLong(0); + if (x == NULL) + goto Done; + +#define CLEANUP \ + Py_DECREF(x); \ + x = y; \ + if (x == NULL) \ + goto Done + + if (us) { + y = accum("microseconds", x, us, us_per_us, &leftover_us); + CLEANUP; + } + if (ms) { + y = accum("milliseconds", x, ms, us_per_ms, &leftover_us); + CLEANUP; + } + if (second) { + y = accum("seconds", x, second, us_per_second, &leftover_us); + CLEANUP; + } + if (minute) { + y = accum("minutes", x, minute, us_per_minute, &leftover_us); + CLEANUP; + } + if (hour) { + y = accum("hours", x, hour, us_per_hour, &leftover_us); + CLEANUP; + } + if (day) { + y = accum("days", x, day, us_per_day, &leftover_us); + CLEANUP; + } + if (week) { + y = accum("weeks", x, week, us_per_week, &leftover_us); + CLEANUP; + } + if (leftover_us) { + /* Round to nearest whole # of us, and add into x. */ + PyObject *temp; + if (leftover_us >= 0.0) + leftover_us = floor(leftover_us + 0.5); + else + leftover_us = ceil(leftover_us - 0.5); + temp = PyLong_FromDouble(leftover_us); + if (temp == NULL) { + Py_DECREF(x); + goto Done; + } + y = PyNumber_Add(x, temp); + Py_DECREF(temp); + CLEANUP; + } + + self = microseconds_to_delta(x); + Py_DECREF(x); +Done: + return self; + +#undef CLEANUP +} + +static int +delta_nonzero(PyDateTime_Delta *self) +{ + return (GET_TD_DAYS(self) != 0 + || GET_TD_SECONDS(self) != 0 + || GET_TD_MICROSECONDS(self) != 0); +} + +static PyObject * +delta_repr(PyDateTime_Delta *self) +{ + if (GET_TD_MICROSECONDS(self) != 0) + return PyString_FromFormat("%s(%d, %d, %d)", + self->ob_type->tp_name, + GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self)); + if (GET_TD_SECONDS(self) != 0) + return PyString_FromFormat("%s(%d, %d)", + self->ob_type->tp_name, + GET_TD_DAYS(self), + GET_TD_SECONDS(self)); + + return PyString_FromFormat("%s(%d)", + self->ob_type->tp_name, + GET_TD_DAYS(self)); +} + +static PyObject * +delta_str(PyDateTime_Delta *self) +{ + int days = GET_TD_DAYS(self); + int seconds = GET_TD_SECONDS(self); + int us = GET_TD_MICROSECONDS(self); + int hours; + int minutes; + char buf[500]; + int i = 0; + + minutes = divmod(seconds, 60, &seconds); + hours = divmod(minutes, 60, &minutes); + + if (days) { + i += sprintf(buf + i, "%d day%s, ", days, + (days == 1 || days == -1) ? "" : "s"); + assert(i < sizeof(buf)); + } + + i += sprintf(buf + i, "%d:%02d:%02d", hours, minutes, seconds); + assert(i < sizeof(buf)); + + if (us) { + i += sprintf(buf + i, ".%06d", us); + assert(i < sizeof(buf)); + } + + return PyString_FromStringAndSize(buf, i); +} + +/* Pickle support. Quite a maze! While __getstate__/__setstate__ sufficed + * in the Python implementation, the C implementation also requires + * __reduce__, and a __safe_for_unpickling__ attr in the type object. + */ +static PyObject * +delta_getstate(PyDateTime_Delta *self) +{ + return Py_BuildValue("iii", GET_TD_DAYS(self), + GET_TD_SECONDS(self), + GET_TD_MICROSECONDS(self)); +} + +static PyObject * +delta_setstate(PyDateTime_Delta *self, PyObject *state) +{ + int day; + int second; + int us; + + if (!PyArg_ParseTuple(state, "iii:__setstate__", &day, &second, &us)) + return NULL; + + self->hashcode = -1; + SET_TD_DAYS(self, day); + SET_TD_SECONDS(self, second); + SET_TD_MICROSECONDS(self, us); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +delta_reduce(PyDateTime_Delta* self) +{ + PyObject* result = NULL; + PyObject* state = delta_getstate(self); + + if (state != NULL) { + /* The funky "()" in the format string creates an empty + * tuple as the 2nd component of the result 3-tuple. + */ + result = Py_BuildValue("O()O", self->ob_type, state); + Py_DECREF(state); + } + return result; +} + +#define OFFSET(field) offsetof(PyDateTime_Delta, field) + +static PyMemberDef delta_members[] = { + {"days", T_LONG, OFFSET(days), READONLY, + PyDoc_STR("Number of days.")}, + + {"seconds", T_LONG, OFFSET(seconds), READONLY, + PyDoc_STR("Number of seconds (>= 0 and less than 1 day).")}, + + {"microseconds", T_LONG, OFFSET(microseconds), READONLY, + PyDoc_STR("Number of microseconds (>= 0 and less than 1 second).")}, + {NULL} +}; + +static PyMethodDef delta_methods[] = { + {"__setstate__", (PyCFunction)delta_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__reduce__", (PyCFunction)delta_reduce, METH_NOARGS, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)delta_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL}, +}; + +static char delta_doc[] = +PyDoc_STR("Difference between two datetime values."); + +static PyNumberMethods delta_as_number = { + delta_add, /* nb_add */ + delta_subtract, /* nb_subtract */ + delta_multiply, /* nb_multiply */ + delta_divide, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + (unaryfunc)delta_negative, /* nb_negative */ + (unaryfunc)delta_positive, /* nb_positive */ + (unaryfunc)delta_abs, /* nb_absolute */ + (inquiry)delta_nonzero, /* nb_nonzero */ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + 0, /*nb_and*/ + 0, /*nb_xor*/ + 0, /*nb_or*/ + 0, /*nb_coerce*/ + 0, /*nb_int*/ + 0, /*nb_long*/ + 0, /*nb_float*/ + 0, /*nb_oct*/ + 0, /*nb_hex*/ + 0, /*nb_inplace_add*/ + 0, /*nb_inplace_subtract*/ + 0, /*nb_inplace_multiply*/ + 0, /*nb_inplace_divide*/ + 0, /*nb_inplace_remainder*/ + 0, /*nb_inplace_power*/ + 0, /*nb_inplace_lshift*/ + 0, /*nb_inplace_rshift*/ + 0, /*nb_inplace_and*/ + 0, /*nb_inplace_xor*/ + 0, /*nb_inplace_or*/ + delta_divide, /* nb_floor_divide */ + 0, /* nb_true_divide */ + 0, /* nb_inplace_floor_divide */ + 0, /* nb_inplace_true_divide */ +}; + +static PyTypeObject PyDateTime_DeltaType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.timedelta", /* tp_name */ + sizeof(PyDateTime_Delta), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)delta_repr, /* tp_repr */ + &delta_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)delta_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)delta_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */ + delta_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)delta_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + delta_methods, /* tp_methods */ + delta_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + delta_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_Date implementation. + */ + +/* Accessor properties. */ + +static PyObject * +date_year(PyDateTime_Date *self, void *unused) +{ + return PyInt_FromLong(GET_YEAR(self)); +} + +static PyObject * +date_month(PyDateTime_Date *self, void *unused) +{ + return PyInt_FromLong(GET_MONTH(self)); +} + +static PyObject * +date_day(PyDateTime_Date *self, void *unused) +{ + return PyInt_FromLong(GET_DAY(self)); +} + +static PyGetSetDef date_getset[] = { + {"year", (getter)date_year}, + {"month", (getter)date_month}, + {"day", (getter)date_day}, + {NULL} +}; + +/* Constructors. */ + +static PyObject * +date_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int year; + int month; + int day; + + static char *keywords[] = { + "year", "month", "day", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "iii", keywords, + &year, &month, &day)) { + if (check_date_args(year, month, day) < 0) + return NULL; + self = new_date(year, month, day); + } + return self; +} + +/* Return new date from localtime(t). */ +static PyObject * +date_local_from_time_t(PyObject *cls, time_t t) +{ + struct tm *tm; + PyObject *result = NULL; + + tm = localtime(&t); + if (tm) + result = PyObject_CallFunction(cls, "iii", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday); + else + PyErr_SetString(PyExc_ValueError, + "timestamp out of range for " + "platform localtime() function"); + return result; +} + +/* 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) +{ + PyObject *time; + PyObject *result; + + time = time_time(); + if (time == NULL) + return NULL; + + /* Note well: today() is a class method, so this may not call + * date.fromtimestamp. For example, it may call + * datetime.fromtimestamp. That's why we need all the accuracy + * time.time() delivers; if someone were gonzo about optimization, + * date.today() could get away with plain C time(). + */ + result = PyObject_CallMethod(cls, "fromtimestamp", "O", time); + Py_DECREF(time); + return result; +} + +/* Return new date from given timestamp (Python timestamp -- a double). */ +static PyObject * +date_fromtimestamp(PyObject *cls, PyObject *args) +{ + double timestamp; + PyObject *result = NULL; + + if (PyArg_ParseTuple(args, "d:fromtimestamp", ×tamp)) + result = date_local_from_time_t(cls, (time_t)timestamp); + return result; +} + +/* Return new date from proleptic Gregorian ordinal. Raises ValueError if + * the ordinal is out of range. + */ +static PyObject * +date_fromordinal(PyObject *cls, PyObject *args) +{ + PyObject *result = NULL; + int ordinal; + + if (PyArg_ParseTuple(args, "i:fromordinal", &ordinal)) { + int year; + int month; + int day; + + if (ordinal < 1) + PyErr_SetString(PyExc_ValueError, "ordinal must be " + ">= 1"); + else { + ord_to_ymd(ordinal, &year, &month, &day); + result = PyObject_CallFunction(cls, "iii", + year, month, day); + } + } + return result; +} + +/* + * 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) +{ + PyObject *result = NULL; + int year = GET_YEAR(date); + int month = GET_MONTH(date); + int deltadays = GET_TD_DAYS(delta); + /* C-level overflow is impossible because |deltadays| < 1e9. */ + int day = GET_DAY(date) + (negate ? -deltadays : deltadays); + + if (normalize_date(&year, &month, &day) >= 0) + result = new_date(year, month, day); + return result; +} + +static PyObject * +date_add(PyObject *left, PyObject *right) +{ + if (PyDateTime_Check(left) || PyDateTime_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + if (PyDate_CheckExact(left)) { + /* date + ??? */ + if (PyDelta_Check(right)) + /* date + delta */ + return add_date_timedelta((PyDateTime_Date *) left, + (PyDateTime_Delta *) right, + 0); + } + else { + /* ??? + date + * 'right' must be one of us, or we wouldn't have been called + */ + if (PyDelta_Check(left)) + /* delta + date */ + return add_date_timedelta((PyDateTime_Date *) right, + (PyDateTime_Delta *) left, + 0); + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyObject * +date_subtract(PyObject *left, PyObject *right) +{ + if (PyDateTime_Check(left) || PyDateTime_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + if (PyDate_CheckExact(left)) { + if (PyDate_CheckExact(right)) { + /* date - date */ + int left_ord = ymd_to_ord(GET_YEAR(left), + GET_MONTH(left), + GET_DAY(left)); + int right_ord = ymd_to_ord(GET_YEAR(right), + GET_MONTH(right), + GET_DAY(right)); + return new_delta(left_ord - right_ord, 0, 0, 0); + } + if (PyDelta_Check(right)) { + /* date - delta */ + return add_date_timedelta((PyDateTime_Date *) left, + (PyDateTime_Delta *) right, + 1); + } + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + + +/* Various ways to turn a date into a string. */ + +static PyObject * +date_repr(PyDateTime_Date *self) +{ + char buffer[1028]; + char *typename; + + typename = self->ob_type->tp_name; + PyOS_snprintf(buffer, sizeof(buffer), "%s(%d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyString_FromString(buffer); +} + +static PyObject * +date_isoformat(PyDateTime_Date *self) +{ + char buffer[128]; + + isoformat_date(self, buffer, sizeof(buffer)); + return PyString_FromString(buffer); +} + +/* str() calls the appropriate isofomat() method. */ +static PyObject * +date_str(PyDateTime_Date *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "()"); +} + + +static PyObject * +date_ctime(PyDateTime_Date *self) +{ + return format_ctime(self, 0, 0, 0); +} + +static PyObject * +date_strftime(PyDateTime_Date *self, PyObject *args, PyObject *kw) +{ + /* This method can be inherited, and needs to call the + * timetuple() method appropriate to self's class. + */ + PyObject *result; + PyObject *format; + PyObject *tuple; + static char *keywords[] = {"format", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:strftime", keywords, + &PyString_Type, &format)) + return NULL; + + tuple = PyObject_CallMethod((PyObject *)self, "timetuple", "()"); + if (tuple == NULL) + return NULL; + result = wrap_strftime((PyObject *)self, format, tuple); + Py_DECREF(tuple); + return result; +} + +/* ISO methods. */ + +static PyObject * +date_isoweekday(PyDateTime_Date *self) +{ + int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyInt_FromLong(dow + 1); +} + +static PyObject * +date_isocalendar(PyDateTime_Date *self) +{ + int year = GET_YEAR(self); + int week1_monday = iso_week1_monday(year); + int today = ymd_to_ord(year, GET_MONTH(self), GET_DAY(self)); + int week; + int day; + + week = divmod(today - week1_monday, 7, &day); + if (week < 0) { + --year; + week1_monday = iso_week1_monday(year); + week = divmod(today - week1_monday, 7, &day); + } + else if (week >= 52 && today >= iso_week1_monday(year + 1)) { + ++year; + week = 0; + } + return Py_BuildValue("iii", year, week + 1, day + 1); +} + +/* Miscellaneous methods. */ + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + */ +static PyObject * +date_richcompare(PyDateTime_Date *self, PyObject *other, int op) +{ + int diff; + + if (! PyDate_Check(other)) { + PyErr_Format(PyExc_TypeError, + "can't compare date to %s instance", + other->ob_type->tp_name); + return NULL; + } + diff = memcmp(self->data, ((PyDateTime_Date *)other)->data, + _PyDateTime_DATE_DATASIZE); + return diff_to_bool(diff, op); +} + +static PyObject * +date_timetuple(PyDateTime_Date *self) +{ + return build_struct_time(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + 0, 0, 0, -1); +} + +static PyObject *date_getstate(PyDateTime_Date *self); + +static long +date_hash(PyDateTime_Date *self) +{ + if (self->hashcode == -1) { + PyObject *temp = date_getstate(self); + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static PyObject * +date_toordinal(PyDateTime_Date *self) +{ + return PyInt_FromLong(ymd_to_ord(GET_YEAR(self), GET_MONTH(self), + GET_DAY(self))); +} + +static PyObject * +date_weekday(PyDateTime_Date *self) +{ + int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self)); + + return PyInt_FromLong(dow); +} + +/* Pickle support. Quite a maze! */ + +static PyObject * +date_getstate(PyDateTime_Date *self) +{ + return PyString_FromStringAndSize(self->data, + _PyDateTime_DATE_DATASIZE); +} + +static PyObject * +date_setstate(PyDateTime_Date *self, PyObject *state) +{ + const int len = PyString_Size(state); + unsigned char *pdata = (unsigned char*)PyString_AsString(state); + + if (! PyString_Check(state) || + len != _PyDateTime_DATE_DATASIZE) { + PyErr_SetString(PyExc_TypeError, + "bad argument to date.__setstate__"); + return NULL; + } + memcpy(self->data, pdata, _PyDateTime_DATE_DATASIZE); + self->hashcode = -1; + + Py_INCREF(Py_None); + return Py_None; +} + +/* XXX This seems a ridiculously inefficient way to pickle a short string. */ +static PyObject * +date_pickler(PyObject *module, PyDateTime_Date *date) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyDate_CheckExact(date)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to date pickler: %s", + date->ob_type->tp_name); + return NULL; + } + state = date_getstate(date); + if (state) { + result = Py_BuildValue("O(O)", date_unpickler_object, state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +date_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_Date *self; + + if (! PyString_CheckExact(arg)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to date unpickler: %s", + arg->ob_type->tp_name); + return NULL; + } + self = PyObject_New(PyDateTime_Date, &PyDateTime_DateType); + if (self != NULL) { + PyObject *res = date_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef date_methods[] = { + /* Class methods: */ + {"fromtimestamp", (PyCFunction)date_fromtimestamp, METH_VARARGS | + METH_CLASS, + PyDoc_STR("timestamp -> local date from a POSIX timestamp (like " + "time.time()).")}, + + {"fromordinal", (PyCFunction)date_fromordinal, METH_VARARGS | + METH_CLASS, + PyDoc_STR("int -> date corresponding to a proleptic Gregorian " + "ordinal.")}, + + {"today", (PyCFunction)date_today, METH_NOARGS | METH_CLASS, + PyDoc_STR("Current date or datetime: same as " + "self.__class__.fromtimestamp(time.time()).")}, + + /* Instance methods: */ + + {"ctime", (PyCFunction)date_ctime, METH_NOARGS, + PyDoc_STR("Return ctime() style string.")}, + + {"strftime", (PyCFunction)date_strftime, METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"timetuple", (PyCFunction)date_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"isocalendar", (PyCFunction)date_isocalendar, METH_NOARGS, + PyDoc_STR("Return a 3-tuple containing ISO year, week number, and " + "weekday.")}, + + {"isoformat", (PyCFunction)date_isoformat, METH_NOARGS, + PyDoc_STR("Return string in ISO 8601 format, YYYY-MM-DD.")}, + + {"isoweekday", (PyCFunction)date_isoweekday, METH_NOARGS, + PyDoc_STR("Return the day of the week represented by the date.\n" + "Monday == 1 ... Sunday == 7")}, + + {"toordinal", (PyCFunction)date_toordinal, METH_NOARGS, + PyDoc_STR("Return proleptic Gregorian ordinal. January 1 of year " + "1 is day 1.")}, + + {"weekday", (PyCFunction)date_weekday, METH_NOARGS, + PyDoc_STR("Return the day of the week represented by the date.\n" + "Monday == 0 ... Sunday == 6")}, + + {"__setstate__", (PyCFunction)date_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)date_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + + {NULL, NULL} +}; + +static char date_doc[] = +PyDoc_STR("Basic date type."); + +static PyNumberMethods date_as_number = { + date_add, /* nb_add */ + date_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_nonzero */ +}; + +static PyTypeObject PyDateTime_DateType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.date", /* tp_name */ + sizeof(PyDateTime_Date), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)date_repr, /* tp_repr */ + &date_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)date_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)date_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + date_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)date_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + date_methods, /* tp_methods */ + 0, /* tp_members */ + date_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + date_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_DateTime implementation. + */ + +/* Accessor properties. */ + +static PyObject * +datetime_hour(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_HOUR(self)); +} + +static PyObject * +datetime_minute(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_MINUTE(self)); +} + +static PyObject * +datetime_second(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_SECOND(self)); +} + +static PyObject * +datetime_microsecond(PyDateTime_DateTime *self, void *unused) +{ + return PyInt_FromLong(DATE_GET_MICROSECOND(self)); +} + +static PyGetSetDef datetime_getset[] = { + {"hour", (getter)datetime_hour}, + {"minute", (getter)datetime_minute}, + {"second", (getter)datetime_second}, + {"microsecond", (getter)datetime_microsecond}, + {NULL} +}; + +/* Constructors. */ + +static PyObject * +datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int year; + int month; + int day; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + + static char *keywords[] = { + "year", "month", "day", "hour", "minute", "second", + "microsecond", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiii", keywords, + &year, &month, &day, &hour, &minute, + &second, &usecond)) { + if (check_date_args(year, month, day) < 0) + return NULL; + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + self = new_datetime(year, month, day, + hour, minute, second, usecond); + } + return self; +} + + +/* TM_FUNC is the shared type of localtime() and gmtime(). */ +typedef struct tm *(*TM_FUNC)(const time_t *timer); + +/* 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) +{ + struct tm *tm; + PyObject *result = NULL; + + tm = f(&timet); + if (tm) + result = PyObject_CallFunction(cls, "iiiiiii", + tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + us); + else + PyErr_SetString(PyExc_ValueError, + "timestamp out of range for " + "platform localtime()/gmtime() function"); + return result; +} + +/* 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, double timestamp) +{ + time_t timet = (time_t)timestamp; + int us = (int)((timestamp - (double)timet) * 1e6); + + return datetime_from_timet_and_us(cls, f, timet, us); +} + +/* 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) +{ +#ifdef HAVE_GETTIMEOFDAY + struct timeval t; + +#ifdef GETTIMEOFDAY_NO_TZ + gettimeofday(&t); +#else + gettimeofday(&t, (struct timezone *)NULL); +#endif + return datetime_from_timet_and_us(cls, f, t.tv_sec, (int)t.tv_usec); + +#else /* ! HAVE_GETTIMEOFDAY */ + /* No flavor of gettimeofday exists on this platform. Python's + * time.time() does a lot of other platform tricks to get the + * best time it can on the platform, and we're not going to do + * better than that (if we could, the better code would belong + * in time.time()!) We're limited by the precision of a double, + * though. + */ + PyObject *time; + double dtime; + + time = time_time(); + if (time == NULL) + return NULL; + dtime = PyFloat_AsDouble(time); + Py_DECREF(time); + if (dtime == -1.0 && PyErr_Occurred()) + return NULL; + return datetime_from_timestamp(cls, f, dtime); +#endif /* ! HAVE_GETTIMEOFDAY */ +} + +/* Return new local datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetime_fromtimestamp(PyObject *cls, PyObject *args) +{ + double timestamp; + PyObject *result = NULL; + + if (PyArg_ParseTuple(args, "d:fromtimestamp", ×tamp)) + result = datetime_from_timestamp(cls, localtime, timestamp); + return result; +} + +/* Return new UTC datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetime_utcfromtimestamp(PyObject *cls, PyObject *args) +{ + double timestamp; + PyObject *result = NULL; + + if (PyArg_ParseTuple(args, "d:utcfromtimestamp", ×tamp)) + result = datetime_from_timestamp(cls, gmtime, timestamp); + return result; +} + +/* Return best possible local time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetime_now(PyObject *cls, PyObject *dummy) +{ + return datetime_best_possible(cls, localtime); +} + +/* Return best possible UTC time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetime_utcnow(PyObject *cls, PyObject *dummy) +{ + return datetime_best_possible(cls, gmtime); +} + +/* Return new datetime or datetimetz from date/datetime/datetimetz and + * time/timetz arguments. + */ +static PyObject * +datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) +{ + static char *keywords[] = {"date", "time", NULL}; + PyObject *date; + PyObject *time; + PyObject *result = NULL; + + if (PyArg_ParseTupleAndKeywords(args, kw, "O!O!:combine", keywords, + &PyDateTime_DateType, &date, + &PyDateTime_TimeType, &time)) + result = PyObject_CallFunction(cls, "iiiiiii", + GET_YEAR(date), + GET_MONTH(date), + GET_DAY(date), + TIME_GET_HOUR(time), + TIME_GET_MINUTE(time), + TIME_GET_SECOND(time), + TIME_GET_MICROSECOND(time)); + if (result && PyTimeTZ_Check(time) && PyDateTimeTZ_Check(result)) { + /* Copy the tzinfo field. */ + PyObject *tzinfo = ((PyDateTime_TimeTZ *)time)->tzinfo; + Py_INCREF(tzinfo); + Py_DECREF(((PyDateTime_DateTimeTZ *)result)->tzinfo); + ((PyDateTime_DateTimeTZ *)result)->tzinfo = tzinfo; + } + return result; +} + +/* datetime arithmetic. */ + +static PyObject * +add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta) +{ + /* Note that the C-level additions can't overflow, because of + * invariant bounds on the member values. + */ + int year = GET_YEAR(date); + int month = GET_MONTH(date); + int day = GET_DAY(date) + GET_TD_DAYS(delta); + int hour = DATE_GET_HOUR(date); + int minute = DATE_GET_MINUTE(date); + int second = DATE_GET_SECOND(date) + GET_TD_SECONDS(delta); + int microsecond = DATE_GET_MICROSECOND(date) + + GET_TD_MICROSECONDS(delta); + + if (normalize_datetime(&year, &month, &day, + &hour, &minute, &second, µsecond) < 0) + return NULL; + else + return new_datetime(year, month, day, + hour, minute, second, microsecond); +} + +static PyObject * +sub_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta) +{ + /* Note that the C-level subtractions can't overflow, because of + * invariant bounds on the member values. + */ + int year = GET_YEAR(date); + int month = GET_MONTH(date); + int day = GET_DAY(date) - GET_TD_DAYS(delta); + int hour = DATE_GET_HOUR(date); + int minute = DATE_GET_MINUTE(date); + int second = DATE_GET_SECOND(date) - GET_TD_SECONDS(delta); + int microsecond = DATE_GET_MICROSECOND(date) - + GET_TD_MICROSECONDS(delta); + + if (normalize_datetime(&year, &month, &day, + &hour, &minute, &second, µsecond) < 0) + return NULL; + else + return new_datetime(year, month, day, + hour, minute, second, microsecond); +} + +static PyObject * +sub_datetime_datetime(PyDateTime_DateTime *left, PyDateTime_DateTime *right) +{ + int days1 = ymd_to_ord(GET_YEAR(left), GET_MONTH(left), GET_DAY(left)); + int days2 = ymd_to_ord(GET_YEAR(right), + GET_MONTH(right), + GET_DAY(right)); + /* These can't overflow, since the values are normalized. At most + * this gives the number of seconds in one day. + */ + int delta_s = (DATE_GET_HOUR(left) - DATE_GET_HOUR(right)) * 3600 + + (DATE_GET_MINUTE(left) - DATE_GET_MINUTE(right)) * 60 + + DATE_GET_SECOND(left) - DATE_GET_SECOND(right); + int delta_us = DATE_GET_MICROSECOND(left) - + DATE_GET_MICROSECOND(right); + + return new_delta(days1 - days2, delta_s, delta_us, 1); +} + +static PyObject * +datetime_add(PyObject *left, PyObject *right) +{ + if (PyDateTime_Check(left)) { + /* datetime + ??? */ + if (PyDelta_Check(right)) + /* datetime + delta */ + return add_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right); + } + else if (PyDelta_Check(left)) { + /* delta + datetime */ + return add_datetime_timedelta((PyDateTime_DateTime *) right, + (PyDateTime_Delta *) left); + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyObject * +datetime_subtract(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDateTime_Check(left)) { + /* datetime - ??? */ + if (PyDateTime_Check(right)) { + /* datetime - datetime */ + result = sub_datetime_datetime( + (PyDateTime_DateTime *)left, + (PyDateTime_DateTime *)right); + } + else if (PyDelta_Check(right)) { + /* datetime - delta */ + result = sub_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right); + } + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* Various ways to turn a datetime into a string. */ + +static PyObject * +datetime_repr(PyDateTime_DateTime *self) +{ + char buffer[1000]; + char *typename = self->ob_type->tp_name; + + if (DATE_GET_MICROSECOND(self)) { + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d, %d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self)); + } + else if (DATE_GET_SECOND(self)) { + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + } + else { + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d, %d)", + typename, + GET_YEAR(self), GET_MONTH(self), GET_DAY(self), + DATE_GET_HOUR(self), DATE_GET_MINUTE(self)); + } + return PyString_FromString(buffer); +} + +static PyObject * +datetime_str(PyDateTime_DateTime *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "(s)", " "); +} + +static PyObject * +datetime_isoformat(PyDateTime_DateTime *self, + PyObject *args, PyObject *kw) +{ + char sep = 'T'; + static char *keywords[] = {"sep", NULL}; + char buffer[100]; + char *cp; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|c:isoformat", keywords, + &sep)) + return NULL; + cp = isoformat_date((PyDateTime_Date *)self, buffer, sizeof(buffer)); + assert(cp != NULL); + *cp++ = sep; + isoformat_time(self, cp, sizeof(buffer) - (cp - buffer)); + return PyString_FromString(buffer); +} + +static PyObject * +datetime_ctime(PyDateTime_DateTime *self) +{ + return format_ctime((PyDateTime_Date *)self, + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); +} + +/* Miscellaneous methods. */ + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + * Note that this routine handles all comparisons for datetime and datetimetz. + */ +static PyObject * +datetime_richcompare(PyDateTime_DateTime *self, PyObject *other, int op) +{ + int diff; + naivety n1, n2; + int offset1, offset2; + + if (! PyDateTime_Check(other)) { + /* Stop this from falling back to address comparison. */ + PyErr_Format(PyExc_TypeError, + "can't compare '%s' to '%s'", + self->ob_type->tp_name, + other->ob_type->tp_name); + return NULL; + } + n1 = classify_object((PyObject *)self, &offset1); + assert(n1 != OFFSET_UNKNOWN); + if (n1 == OFFSET_ERROR) + return NULL; + + n2 = classify_object(other, &offset2); + assert(n2 != OFFSET_UNKNOWN); + if (n2 == OFFSET_ERROR) + return NULL; + + /* If they're both naive, or both aware and have the same offsets, + * we get off cheap. Note that if they're both naive, offset1 == + * offset2 == 0 at this point. + */ + if (n1 == n2 && offset1 == offset2) { + diff = memcmp(self->data, ((PyDateTime_DateTime *)other)->data, + _PyDateTime_DATETIME_DATASIZE); + return diff_to_bool(diff, op); + } + + if (n1 == OFFSET_AWARE && n2 == OFFSET_AWARE) { + /* We want the sign of + * (self - offset1 minutes) - (other - offset2 minutes) = + * (self - other) + (offset2 - offset1) minutes. + */ + PyDateTime_Delta *delta; + int days, seconds, us; + + assert(offset1 != offset2); /* else last "if" handled it */ + delta = (PyDateTime_Delta *)sub_datetime_datetime(self, + (PyDateTime_DateTime *)other); + if (delta == NULL) + return NULL; + days = delta->days; + seconds = delta->seconds + (offset2 - offset1) * 60; + us = delta->microseconds; + Py_DECREF(delta); + normalize_d_s_us(&days, &seconds, &us); + diff = days; + if (diff == 0) + diff = seconds | us; + return diff_to_bool(diff, op); + } + + assert(n1 != n2); + PyErr_SetString(PyExc_TypeError, + "can't compare offset-naive and " + "offset-aware datetimes"); + return NULL; +} + +static PyObject *datetime_getstate(PyDateTime_DateTime *self); + +static long +datetime_hash(PyDateTime_DateTime *self) +{ + if (self->hashcode == -1) { + naivety n; + int offset; + PyObject *temp; + + n = classify_object((PyObject *)self, &offset); + assert(n != OFFSET_UNKNOWN); + if (n == OFFSET_ERROR) + return -1; + + /* Reduce this to a hash of another object. */ + if (n == OFFSET_NAIVE) + temp = datetime_getstate(self); + else { + int days; + int seconds; + + assert(n == OFFSET_AWARE); + assert(PyDateTimeTZ_Check(self)); + days = ymd_to_ord(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self)); + seconds = DATE_GET_HOUR(self) * 3600 + + (DATE_GET_MINUTE(self) - offset) * 60 + + DATE_GET_SECOND(self); + temp = new_delta(days, + seconds, + DATE_GET_MICROSECOND(self), + 1); + } + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static PyObject * +datetime_timetuple(PyDateTime_DateTime *self) +{ + return build_struct_time(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + -1); +} + +static PyObject * +datetime_getdate(PyDateTime_DateTime *self) +{ + return new_date(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self)); +} + +static PyObject * +datetime_gettime(PyDateTime_DateTime *self) +{ + return new_time(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self)); +} + +/* Pickle support. Quite a maze! */ + +static PyObject * +datetime_getstate(PyDateTime_DateTime *self) +{ + return PyString_FromStringAndSize(self->data, + _PyDateTime_DATETIME_DATASIZE); +} + +static PyObject * +datetime_setstate(PyDateTime_DateTime *self, PyObject *state) +{ + const int len = PyString_Size(state); + unsigned char *pdata = (unsigned char*)PyString_AsString(state); + + if (! PyString_Check(state) || + len != _PyDateTime_DATETIME_DATASIZE) { + PyErr_SetString(PyExc_TypeError, + "bad argument to datetime.__setstate__"); + return NULL; + } + memcpy(self->data, pdata, _PyDateTime_DATETIME_DATASIZE); + self->hashcode = -1; + + Py_INCREF(Py_None); + return Py_None; +} + +/* XXX This seems a ridiculously inefficient way to pickle a short string. */ +static PyObject * +datetime_pickler(PyObject *module, PyDateTime_DateTime *datetime) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyDateTime_CheckExact(datetime)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to datetime pickler: %s", + datetime->ob_type->tp_name); + return NULL; + } + state = datetime_getstate(datetime); + if (state) { + result = Py_BuildValue("O(O)", + datetime_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +datetime_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_DateTime *self; + + if (! PyString_CheckExact(arg)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to datetime unpickler: %s", + arg->ob_type->tp_name); + return NULL; + } + self = PyObject_New(PyDateTime_DateTime, &PyDateTime_DateTimeType); + if (self != NULL) { + PyObject *res = datetime_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef datetime_methods[] = { + /* Class methods: */ + {"now", (PyCFunction)datetime_now, + METH_NOARGS | METH_CLASS, + PyDoc_STR("Return a new datetime representing local day and time.")}, + + {"utcnow", (PyCFunction)datetime_utcnow, + METH_NOARGS | METH_CLASS, + PyDoc_STR("Return a new datetime representing UTC day and time.")}, + + {"fromtimestamp", (PyCFunction)datetime_fromtimestamp, + METH_VARARGS | METH_CLASS, + PyDoc_STR("timestamp -> local datetime from a POSIX timestamp " + "(like time.time()).")}, + + {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, + METH_VARARGS | METH_CLASS, + PyDoc_STR("timestamp -> UTC datetime from a POSIX timestamp " + "(like time.time()).")}, + + {"combine", (PyCFunction)datetime_combine, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("date, time -> datetime with same date and time fields")}, + + /* Instance methods: */ + {"timetuple", (PyCFunction)datetime_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"date", (PyCFunction)datetime_getdate, METH_NOARGS, + PyDoc_STR("Return date object with same year, month and day.")}, + + {"time", (PyCFunction)datetime_gettime, METH_NOARGS, + PyDoc_STR("Return time object with same hour, minute, second and " + "microsecond.")}, + + {"ctime", (PyCFunction)datetime_ctime, METH_NOARGS, + PyDoc_STR("Return ctime() style string.")}, + + {"isoformat", (PyCFunction)datetime_isoformat, METH_KEYWORDS, + PyDoc_STR("[sep] -> string in ISO 8601 format, " + "YYYY-MM-DDTHH:MM:SS[.mmmmmm].\n\n" + "sep is used to separate the year from the time, and " + "defaults\n" + "to 'T'.")}, + + {"__setstate__", (PyCFunction)datetime_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)datetime_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} +}; + +static char datetime_doc[] = +PyDoc_STR("Basic date/time type."); + +static PyNumberMethods datetime_as_number = { + datetime_add, /* nb_add */ + datetime_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_DateTimeType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.datetime", /* tp_name */ + sizeof(PyDateTime_DateTime), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)datetime_repr, /* tp_repr */ + &datetime_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)datetime_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)datetime_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + datetime_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)datetime_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + datetime_methods, /* tp_methods */ + 0, /* tp_members */ + datetime_getset, /* tp_getset */ + &PyDateTime_DateType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + datetime_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_Time implementation. + */ + +/* Accessor properties. */ + +static PyObject * +time_hour(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_HOUR(self)); +} + +static PyObject * +time_minute(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_MINUTE(self)); +} + +static PyObject * +time_second(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_SECOND(self)); +} + +static PyObject * +time_microsecond(PyDateTime_Time *self, void *unused) +{ + return PyInt_FromLong(TIME_GET_MICROSECOND(self)); +} + +static PyGetSetDef time_getset[] = { + {"hour", (getter)time_hour}, + {"minute", (getter)time_minute}, + {"second", (getter)time_second}, + {"microsecond", (getter)time_microsecond}, + {NULL} +}; + +/* Constructors. */ + +static PyObject * +time_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + + static char *keywords[] = { + "hour", "minute", "second", "microsecond", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiii", keywords, + &hour, &minute, &second, &usecond)) { + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + self = new_time(hour, minute, second, usecond); + } + return self; +} + +/* Various ways to turn a time into a string. */ + +static PyObject * +time_repr(PyDateTime_Time *self) +{ + char buffer[100]; + char *typename = self->ob_type->tp_name; + int h = TIME_GET_HOUR(self); + int m = TIME_GET_MINUTE(self); + int s = TIME_GET_SECOND(self); + int us = TIME_GET_MICROSECOND(self); + + if (us) + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d, %d)", typename, h, m, s, us); + else if (s) + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d, %d)", typename, h, m, s); + else + PyOS_snprintf(buffer, sizeof(buffer), + "%s(%d, %d)", typename, h, m); + return PyString_FromString(buffer); +} + +static PyObject * +time_str(PyDateTime_Time *self) +{ + return PyObject_CallMethod((PyObject *)self, "isoformat", "()"); +} + +static PyObject * +time_isoformat(PyDateTime_Time *self) +{ + char buffer[100]; + /* Reuse the time format code from the datetime type. */ + PyDateTime_DateTime datetime; + PyDateTime_DateTime *pdatetime = &datetime; + + /* Copy over just the time bytes. */ + memcpy(pdatetime->data + _PyDateTime_DATE_DATASIZE, + self->data, + _PyDateTime_TIME_DATASIZE); + + isoformat_time(pdatetime, buffer, sizeof(buffer)); + return PyString_FromString(buffer); +} + +static PyObject * +time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw) +{ + PyObject *result; + PyObject *format; + PyObject *tuple; + static char *keywords[] = {"format", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:strftime", keywords, + &PyString_Type, &format)) + return NULL; + + tuple = Py_BuildValue("iiiiiiiii", + 0, 0, 0, /* year, month, day */ + TIME_GET_HOUR(self), + TIME_GET_MINUTE(self), + TIME_GET_SECOND(self), + 0, 0, -1); /* weekday, daynum, dst */ + if (tuple == NULL) + return NULL; + assert(PyTuple_Size(tuple) == 9); + result = wrap_strftime((PyObject *)self, format, tuple); + Py_DECREF(tuple); + return result; +} + +/* Miscellaneous methods. */ + +/* This is more natural as a tp_compare, but doesn't work then: for whatever + * reason, Python's try_3way_compare ignores tp_compare unless + * PyInstance_Check returns true, but these aren't old-style classes. + * Note that this routine handles all comparisons for time and timetz. + */ +static PyObject * +time_richcompare(PyDateTime_Time *self, PyObject *other, int op) +{ + int diff; + naivety n1, n2; + int offset1, offset2; + + if (! PyTime_Check(other)) { + /* Stop this from falling back to address comparison. */ + PyErr_Format(PyExc_TypeError, + "can't compare '%s' to '%s'", + self->ob_type->tp_name, + other->ob_type->tp_name); + return NULL; + } + n1 = classify_object((PyObject *)self, &offset1); + assert(n1 != OFFSET_UNKNOWN); + if (n1 == OFFSET_ERROR) + return NULL; + + n2 = classify_object(other, &offset2); + assert(n2 != OFFSET_UNKNOWN); + if (n2 == OFFSET_ERROR) + return NULL; + + /* If they're both naive, or both aware and have the same offsets, + * we get off cheap. Note that if they're both naive, offset1 == + * offset2 == 0 at this point. + */ + if (n1 == n2 && offset1 == offset2) { + diff = memcmp(self->data, ((PyDateTime_Time *)other)->data, + _PyDateTime_TIME_DATASIZE); + return diff_to_bool(diff, op); + } + + if (n1 == OFFSET_AWARE && n2 == OFFSET_AWARE) { + assert(offset1 != offset2); /* else last "if" handled it */ + /* Convert everything except microseconds to seconds. These + * can't overflow (no more than the # of seconds in 2 days). + */ + offset1 = TIME_GET_HOUR(self) * 3600 + + (TIME_GET_MINUTE(self) - offset1) * 60 + + TIME_GET_SECOND(self); + offset2 = TIME_GET_HOUR(other) * 3600 + + (TIME_GET_MINUTE(other) - offset2) * 60 + + TIME_GET_SECOND(other); + diff = offset1 - offset2; + if (diff == 0) + diff = TIME_GET_MICROSECOND(self) - + TIME_GET_MICROSECOND(other); + return diff_to_bool(diff, op); + } + + assert(n1 != n2); + PyErr_SetString(PyExc_TypeError, + "can't compare offset-naive and " + "offset-aware times"); + return NULL; +} + +static PyObject *time_getstate(PyDateTime_Time *self); + +static long +time_hash(PyDateTime_Time *self) +{ + if (self->hashcode == -1) { + naivety n; + int offset; + PyObject *temp; + + n = classify_object((PyObject *)self, &offset); + assert(n != OFFSET_UNKNOWN); + if (n == OFFSET_ERROR) + return -1; + + /* Reduce this to a hash of another object. */ + if (offset == 0) + temp = time_getstate(self); + else { + int hour; + int minute; + + assert(n == OFFSET_AWARE); + assert(PyTimeTZ_Check(self)); + hour = divmod(TIME_GET_HOUR(self) * 60 + + TIME_GET_MINUTE(self) - offset, + 60, + &minute); + if (0 <= hour && hour < 24) + temp = new_time(hour, minute, + TIME_GET_SECOND(self), + TIME_GET_MICROSECOND(self)); + else + temp = Py_BuildValue("iiii", + hour, minute, + TIME_GET_SECOND(self), + TIME_GET_MICROSECOND(self)); + } + if (temp != NULL) { + self->hashcode = PyObject_Hash(temp); + Py_DECREF(temp); + } + } + return self->hashcode; +} + +static int +time_nonzero(PyDateTime_Time *self) +{ + return TIME_GET_HOUR(self) || + TIME_GET_MINUTE(self) || + TIME_GET_SECOND(self) || + TIME_GET_MICROSECOND(self); +} + +/* Pickle support. Quite a maze! */ + +static PyObject * +time_getstate(PyDateTime_Time *self) +{ + return PyString_FromStringAndSize(self->data, + _PyDateTime_TIME_DATASIZE); +} + +static PyObject * +time_setstate(PyDateTime_Time *self, PyObject *state) +{ + const int len = PyString_Size(state); + unsigned char *pdata = (unsigned char*)PyString_AsString(state); + + if (! PyString_Check(state) || + len != _PyDateTime_TIME_DATASIZE) { + PyErr_SetString(PyExc_TypeError, + "bad argument to time.__setstate__"); + return NULL; + } + memcpy(self->data, pdata, _PyDateTime_TIME_DATASIZE); + self->hashcode = -1; + + Py_INCREF(Py_None); + return Py_None; +} + +/* XXX This seems a ridiculously inefficient way to pickle a short string. */ +static PyObject * +time_pickler(PyObject *module, PyDateTime_Time *time) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyTime_CheckExact(time)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to time pickler: %s", + time->ob_type->tp_name); + return NULL; + } + state = time_getstate(time); + if (state) { + result = Py_BuildValue("O(O)", + time_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +time_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_Time *self; + + if (! PyString_CheckExact(arg)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to time unpickler: %s", + arg->ob_type->tp_name); + return NULL; + } + self = PyObject_New(PyDateTime_Time, &PyDateTime_TimeType); + if (self != NULL) { + PyObject *res = time_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef time_methods[] = { + {"isoformat", (PyCFunction)time_isoformat, METH_KEYWORDS, + PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm].")}, + + {"strftime", (PyCFunction)time_strftime, METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"__setstate__", (PyCFunction)time_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)time_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} +}; + +static char time_doc[] = +PyDoc_STR("Basic time type."); + +static PyNumberMethods time_as_number = { + 0, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + (inquiry)time_nonzero, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_TimeType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.time", /* tp_name */ + sizeof(PyDateTime_Time), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)time_repr, /* tp_repr */ + &time_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)time_hash, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)time_str, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + time_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)time_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + time_methods, /* tp_methods */ + 0, /* tp_members */ + time_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + time_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * 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 + * datetimetz and timetz 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) +{ + PyErr_Format(PyExc_NotImplementedError, + "a tzinfo subclass must implement %s()", + methodname); + return NULL; +} + +/* Methods. A subclass must implement these. */ + +static PyObject* +tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("tzname"); +} + +static PyObject* +tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("utcoffset"); +} + +static PyObject* +tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("dst"); +} + +/* + * Pickle support. This is solely so that tzinfo subclasses can use + * pickling -- tzinfo itself is supposed to be uninstantiable. The + * pickler and unpickler functions are given module-level private + * names, and registered with copy_reg, by the module init function. + */ + +static PyObject* +tzinfo_pickler(PyDateTime_TZInfo *self) { + return Py_BuildValue("O()", tzinfo_unpickler_object); +} + +static PyObject* +tzinfo_unpickler(PyObject * unused) { + return PyType_GenericNew(&PyDateTime_TZInfoType, NULL, NULL); +} + + +static PyMethodDef tzinfo_methods[] = { + {"tzname", (PyCFunction)tzinfo_tzname, METH_O, + PyDoc_STR("datetime -> string name of time zone.")}, + + {"utcoffset", (PyCFunction)tzinfo_utcoffset, METH_O, + PyDoc_STR("datetime -> minutes east of UTC (negative for " + "west of UTC).")}, + + {"dst", (PyCFunction)tzinfo_dst, METH_O, + PyDoc_STR("datetime -> DST offset in minutes east of UTC.")}, + + {NULL, NULL} +}; + +static char tzinfo_doc[] = +PyDoc_STR("Abstract base class for time zone info objects."); + + statichere PyTypeObject PyDateTime_TZInfoType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.tzinfo", /* tp_name */ + sizeof(PyDateTime_TZInfo), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + tzinfo_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + tzinfo_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ + 0, /* tp_free */ +}; + +/* + * PyDateTime_TimeTZ implementation. + */ + +/* Accessor properties. Properties for hour, minute, second and microsecond + * are inherited from time. + */ + +static PyObject * +timetz_tzinfo(PyDateTime_TimeTZ *self, void *unused) +{ + Py_INCREF(self->tzinfo); + return self->tzinfo; +} + +static PyGetSetDef timetz_getset[] = { + {"tzinfo", (getter)timetz_tzinfo}, + {NULL} +}; + +/* + * Constructors. + */ + +static PyObject * +timetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + PyObject *tzinfo = Py_None; + + static char *keywords[] = { + "hour", "minute", "second", "microsecond", "tzinfo", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|llllO", keywords, + &hour, &minute, &second, &usecond, + &tzinfo)) { + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = new_timetz(hour, minute, second, usecond, tzinfo); + } + return self; +} + +/* + * Destructor. + */ + +static void +timetz_dealloc(PyDateTime_TimeTZ *self) +{ + Py_XDECREF(self->tzinfo); + self->ob_type->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. One more "convenience function" and + * it won't be possible to find the useful methods anymore <0.5 wink>. + */ + +static PyObject * +timetz_convienience(PyDateTime_TimeTZ *self, char *name) +{ + PyObject *result; + + if (self->tzinfo == Py_None) { + result = Py_None; + Py_INCREF(result); + } + else + result = PyObject_CallMethod(self->tzinfo, name, "O", self); + return result; +} + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) { + return timetz_convienience(self, "utcoffset"); +} + +static PyObject * +timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) { + return timetz_convienience(self, "tzname"); +} + +static PyObject * +timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) { + return timetz_convienience(self, "dst"); +} + +/* + * Various ways to turn a timetz into a string. + */ + +static PyObject * +timetz_repr(PyDateTime_TimeTZ *self) +{ + PyObject *baserepr = time_repr((PyDateTime_Time *)self); + + if (baserepr == NULL) + return NULL; + return append_keyword_tzinfo(baserepr, self->tzinfo); +} + +/* Note: tp_str is inherited from time. */ + +static PyObject * +timetz_isoformat(PyDateTime_TimeTZ *self) +{ + char buf[100]; + PyObject *result = time_isoformat((PyDateTime_Time *)self); + + if (result == NULL || self->tzinfo == Py_None) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, + (PyObject *)self) < 0) { + Py_DECREF(result); + return NULL; + } + PyString_ConcatAndDel(&result, PyString_FromString(buf)); + return result; +} + +/* Note: strftime() is inherited from time. */ + +/* + * Miscellaneous methods. + */ + +/* Note: tp_richcompare and tp_hash are inherited from time. */ + +static int +timetz_nonzero(PyDateTime_TimeTZ *self) +{ + int offset; + int none; + + if (TIME_GET_SECOND(self) || TIME_GET_MICROSECOND(self)) { + /* Since utcoffset is in whole minutes, nothing can + * alter the conclusion that this is nonzero. + */ + return 1; + } + offset = 0; + if (self->tzinfo != Py_None) { + offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none); + if (offset == -1 && PyErr_Occurred()) + return -1; + } + return (TIME_GET_MINUTE(self) - offset + TIME_GET_HOUR(self)*60) != 0; +} + +/* + * Pickle support. Quite a maze! + */ + +/* Let basestate be the state string returned by time_getstate. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + */ +static PyObject * +timetz_getstate(PyDateTime_TimeTZ *self) +{ + PyObject *basestate; + PyObject *result = NULL; + + basestate = time_getstate((PyDateTime_Time *)self); + if (basestate != NULL) { + if (self->tzinfo == Py_None) + result = Py_BuildValue("(O)", basestate); + else + result = Py_BuildValue("OO", basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} + +static PyObject * +timetz_setstate(PyDateTime_TimeTZ *self, PyObject *state) +{ + PyObject *temp; + PyObject *basestate; + PyObject *tzinfo = Py_None; + + if (! PyArg_ParseTuple(state, "O!|O:__setstate__", + &PyString_Type, &basestate, + &tzinfo)) + return NULL; + temp = time_setstate((PyDateTime_Time *)self, basestate); + if (temp == NULL) + return NULL; + Py_DECREF(temp); + + Py_INCREF(tzinfo); + Py_XDECREF(self->tzinfo); + self->tzinfo = tzinfo; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +timetz_pickler(PyObject *module, PyDateTime_TimeTZ *timetz) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyTimeTZ_CheckExact(timetz)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to timetz pickler: %s", + timetz->ob_type->tp_name); + return NULL; + } + state = timetz_getstate(timetz); + if (state) { + result = Py_BuildValue("O(O)", + timetz_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +timetz_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_TimeTZ *self; + + self = PyObject_New(PyDateTime_TimeTZ, &PyDateTime_TimeTZType); + if (self != NULL) { + PyObject *res; + + self->tzinfo = NULL; + res = timetz_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + +static PyMethodDef timetz_methods[] = { + {"isoformat", (PyCFunction)timetz_isoformat, METH_KEYWORDS, + PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm]" + "[+HH:MM].")}, + + {"utcoffset", (PyCFunction)timetz_utcoffset, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, + + {"tzname", (PyCFunction)timetz_tzname, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.tzname(self).")}, + + {"dst", (PyCFunction)timetz_dst, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.dst(self).")}, + + {"__setstate__", (PyCFunction)timetz_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)timetz_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} + +}; + +static char timetz_doc[] = +PyDoc_STR("Time type."); + +static PyNumberMethods timetz_as_number = { + 0, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + (inquiry)timetz_nonzero, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_TimeTZType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.timetz", /* tp_name */ + sizeof(PyDateTime_TimeTZ), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)timetz_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)timetz_repr, /* tp_repr */ + &timetz_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + time_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + timetz_methods, /* tp_methods */ + 0, /* tp_members */ + timetz_getset, /* tp_getset */ + &PyDateTime_TimeType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + timetz_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* + * PyDateTime_DateTimeTZ implementation. + */ + +/* Accessor properties. Properties for day, month, year, hour, minute, + * second and microsecond are inherited from datetime. + */ + +static PyObject * +datetimetz_tzinfo(PyDateTime_DateTimeTZ *self, void *unused) +{ + Py_INCREF(self->tzinfo); + return self->tzinfo; +} + +static PyGetSetDef datetimetz_getset[] = { + {"tzinfo", (getter)datetimetz_tzinfo}, + {NULL} +}; + +/* + * Constructors. + * These are like the datetime methods of the same names, but allow an + * optional tzinfo argument. + */ + +/* Internal helper. + * self is a datetimetz. Replace its tzinfo member. + */ +void +replace_tzinfo(PyObject *self, PyObject *newtzinfo) +{ + assert(self != NULL); + assert(newtzinfo != NULL); + assert(PyDateTimeTZ_Check(self)); + Py_INCREF(newtzinfo); + Py_DECREF(((PyDateTime_DateTimeTZ *)self)->tzinfo); + ((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo; +} + +static PyObject * +datetimetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + int year; + int month; + int day; + int hour = 0; + int minute = 0; + int second = 0; + int usecond = 0; + PyObject *tzinfo = Py_None; + + static char *keywords[] = { + "year", "month", "day", "hour", "minute", "second", + "microsecond", "tzinfo", NULL + }; + + if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO", keywords, + &year, &month, &day, &hour, &minute, + &second, &usecond, &tzinfo)) { + if (check_date_args(year, month, day) < 0) + return NULL; + if (check_time_args(hour, minute, second, usecond) < 0) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = new_datetimetz(year, month, day, + hour, minute, second, usecond, + tzinfo); + } + return self; +} + +/* Return best possible local time -- this isn't constrained by the + * precision of a timestamp. + */ +static PyObject * +datetimetz_now(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"tzinfo", NULL}; + + if (PyArg_ParseTupleAndKeywords(args, kw, "|O:now", keywords, + &tzinfo)) { + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = datetime_best_possible(cls, localtime); + if (self != NULL) + replace_tzinfo(self, tzinfo); + } + return self; +} + +/* Return new local datetime from timestamp (Python timestamp -- a double). */ +static PyObject * +datetimetz_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) +{ + PyObject *self = NULL; + double timestamp; + PyObject *tzinfo = Py_None; + static char *keywords[] = {"timestamp", "tzinfo", NULL}; + + if (PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", + keywords, ×tamp, &tzinfo)) { + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = datetime_from_timestamp(cls, localtime, timestamp); + if (self != NULL) + replace_tzinfo(self, tzinfo); + } + return self; +} + +/* Note: utcnow() is inherited, and doesn't accept tzinfo. + * Ditto utcfromtimestamp(). Ditto combine(). + */ + + +/* + * Destructor. + */ + +static void +datetimetz_dealloc(PyDateTime_DateTimeTZ *self) +{ + Py_XDECREF(self->tzinfo); + self->ob_type->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. + */ + +/* Internal helper. + * Call a tzinfo object's method, or return None if tzinfo is None. + */ +static PyObject * +datetimetz_convienience(PyDateTime_DateTimeTZ *self, char *name) +{ + PyObject *result; + + if (self->tzinfo == Py_None) { + result = Py_None; + Py_INCREF(result); + } + else + result = PyObject_CallMethod(self->tzinfo, name, "O", self); + return result; +} + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) { + return datetimetz_convienience(self, "utcoffset"); +} + +static PyObject * +datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) { + return datetimetz_convienience(self, "tzname"); +} + +static PyObject * +datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) { + return datetimetz_convienience(self, "dst"); +} + +/* + * datetimetz arithmetic. + */ + +/* If base is Py_NotImplemented or NULL, just return it. + * Else base is a datetime, exactly one of {left, right} is a datetimetz, + * and we want to create a datetimetz with the same date and time fields + * as base, and with the tzinfo field from left or right. Do that, + * return it, and decref base. This is used to transform the result of + * a binary datetime operation (base) into a datetimetz result. + */ +static PyObject * +attach_tzinfo(PyObject *base, PyObject *left, PyObject *right) +{ + PyDateTime_DateTimeTZ *self; + PyDateTime_DateTimeTZ *result; + + if (base == NULL || base == Py_NotImplemented) + return base; + + assert(PyDateTime_CheckExact(base)); + + if (PyDateTimeTZ_Check(left)) { + assert(! PyDateTimeTZ_Check(right)); + self = (PyDateTime_DateTimeTZ *)left; + } + else { + assert(PyDateTimeTZ_Check(right)); + self = (PyDateTime_DateTimeTZ *)right; + } + result = PyObject_New(PyDateTime_DateTimeTZ, + &PyDateTime_DateTimeTZType); + if (result != NULL) { + memcpy(result->data, ((PyDateTime_DateTime *)base)->data, + _PyDateTime_DATETIME_DATASIZE); + Py_INCREF(self->tzinfo); + result->tzinfo = self->tzinfo; + } + Py_DECREF(base); + return (PyObject *)result; +} + +static PyObject * +datetimetz_add(PyObject *left, PyObject *right) +{ + return attach_tzinfo(datetime_add(left, right), left, right); +} + +static PyObject * +datetimetz_subtract(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDateTime_Check(left)) { + /* datetime - ??? */ + if (PyDateTime_Check(right)) { + /* datetime - datetime */ + naivety n1, n2; + int offset1, offset2; + PyDateTime_Delta *delta; + + n1 = classify_object(left, &offset1); + assert(n1 != OFFSET_UNKNOWN); + if (n1 == OFFSET_ERROR) + return NULL; + + n2 = classify_object(right, &offset2); + assert(n2 != OFFSET_UNKNOWN); + if (n2 == OFFSET_ERROR) + return NULL; + + if (n1 != n2) { + PyErr_SetString(PyExc_TypeError, + "can't subtract offset-naive and " + "offset-aware datetimes"); + return NULL; + } + delta = (PyDateTime_Delta *)sub_datetime_datetime( + (PyDateTime_DateTime *)left, + (PyDateTime_DateTime *)right); + if (delta == NULL || offset1 == offset2) + return (PyObject *)delta; + /* (left - offset1) - (right - offset2) = + * (left - right) + (offset2 - offset1) + */ + result = new_delta(delta->days, + delta->seconds + + (offset2 - offset1) * 60, + delta->microseconds, + 1); + Py_DECREF(delta); + } + else if (PyDelta_Check(right)) { + /* datetimetz - delta */ + result = sub_datetime_timedelta( + (PyDateTime_DateTime *)left, + (PyDateTime_Delta *)right); + result = attach_tzinfo(result, left, right); + } + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +/* Various ways to turn a datetime into a string. */ + +static PyObject * +datetimetz_repr(PyDateTime_DateTimeTZ *self) +{ + PyObject *baserepr = datetime_repr((PyDateTime_DateTime *)self); + + if (baserepr == NULL) + return NULL; + return append_keyword_tzinfo(baserepr, self->tzinfo); +} + +/* Note: tp_str is inherited from datetime. */ + +static PyObject * +datetimetz_isoformat(PyDateTime_DateTimeTZ *self, + PyObject *args, PyObject *kw) +{ + char buf[100]; + PyObject *result = datetime_isoformat((PyDateTime_DateTime *)self, + args, kw); + + if (result == NULL || self->tzinfo == Py_None) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, + (PyObject *)self) < 0) { + Py_DECREF(result); + return NULL; + } + PyString_ConcatAndDel(&result, PyString_FromString(buf)); + return result; +} + +/* Miscellaneous methods. */ + +/* Note: tp_richcompare and tp_hash are inherited from datetime. */ + +static PyObject * +datetimetz_timetuple(PyDateTime_DateTimeTZ *self) +{ + int dstflag = -1; + + if (self->tzinfo != Py_None) { + int none; + + dstflag = call_dst(self->tzinfo, (PyObject *)self, &none); + if (dstflag == -1 && PyErr_Occurred()) + return NULL; + + if (none) + dstflag = -1; + else if (dstflag != 0) + dstflag = 1; + + } + return build_struct_time(GET_YEAR(self), + GET_MONTH(self), + GET_DAY(self), + DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + dstflag); +} + +static PyObject * +datetimetz_utctimetuple(PyDateTime_DateTimeTZ *self) +{ + int y = GET_YEAR(self); + int m = GET_MONTH(self); + int d = GET_DAY(self); + int hh = DATE_GET_HOUR(self); + int mm = DATE_GET_MINUTE(self); + int ss = DATE_GET_SECOND(self); + int us = 0; /* microseconds are ignored in a timetuple */ + int offset = 0; + + if (self->tzinfo != Py_None) { + int none; + + offset = call_utcoffset(self->tzinfo, (PyObject *)self, &none); + if (offset == -1 && PyErr_Occurred()) + return NULL; + } + /* Even if offset is 0, don't call timetuple() -- tm_isdst should be + * 0 in a UTC timetuple regardless of what dst() says. + */ + if (offset) { + /* Subtract offset minutes & normalize. */ + int stat; + + mm -= offset; + stat = normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us); + if (stat < 0) { + /* At the edges, it's possible we overflowed + * beyond MINYEAR or MAXYEAR. + */ + if (PyErr_ExceptionMatches(PyExc_OverflowError)) + PyErr_Clear(); + else + return NULL; + } + } + return build_struct_time(y, m, d, hh, mm, ss, 0); +} + +static PyObject * +datetimetz_gettimetz(PyDateTime_DateTimeTZ *self) +{ + return new_timetz(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + self->tzinfo); +} + +/* + * Pickle support. Quite a maze! + */ + +/* Let basestate be the state string returned by datetime_getstate. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + */ +static PyObject * +datetimetz_getstate(PyDateTime_DateTimeTZ *self) +{ + PyObject *basestate; + PyObject *result = NULL; + + basestate = datetime_getstate((PyDateTime_DateTime *)self); + if (basestate != NULL) { + if (self->tzinfo == Py_None) + result = Py_BuildValue("(O)", basestate); + else + result = Py_BuildValue("OO", basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} + +static PyObject * +datetimetz_setstate(PyDateTime_DateTimeTZ *self, PyObject *state) +{ + PyObject *temp; + PyObject *basestate; + PyObject *tzinfo = Py_None; + + if (! PyArg_ParseTuple(state, "O!|O:__setstate__", + &PyString_Type, &basestate, + &tzinfo)) + return NULL; + temp = datetime_setstate((PyDateTime_DateTime *)self, basestate); + if (temp == NULL) + return NULL; + Py_DECREF(temp); + + Py_INCREF(tzinfo); + Py_XDECREF(self->tzinfo); + self->tzinfo = tzinfo; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +datetimetz_pickler(PyObject *module, PyDateTime_DateTimeTZ *datetimetz) +{ + PyObject *state; + PyObject *result = NULL; + + if (! PyDateTimeTZ_CheckExact(datetimetz)) { + PyErr_Format(PyExc_TypeError, + "bad type passed to datetimetz pickler: %s", + datetimetz->ob_type->tp_name); + return NULL; + } + state = datetimetz_getstate(datetimetz); + if (state) { + result = Py_BuildValue("O(O)", + datetimetz_unpickler_object, + state); + Py_DECREF(state); + } + return result; +} + +static PyObject * +datetimetz_unpickler(PyObject *module, PyObject *arg) +{ + PyDateTime_DateTimeTZ *self; + + self = PyObject_New(PyDateTime_DateTimeTZ, &PyDateTime_DateTimeTZType); + if (self != NULL) { + PyObject *res; + + self->tzinfo = NULL; + res = datetimetz_setstate(self, arg); + if (res == NULL) { + Py_DECREF(self); + return NULL; + } + Py_DECREF(res); + } + return (PyObject *)self; +} + + +static PyMethodDef datetimetz_methods[] = { + /* Class methods: */ + /* Inherited: combine(), utcnow(), utcfromtimestamp() */ + + {"now", (PyCFunction)datetimetz_now, + METH_KEYWORDS | METH_CLASS, + PyDoc_STR("[tzinfo] -> new datetimetz with local day and time.")}, + + {"fromtimestamp", (PyCFunction)datetimetz_fromtimestamp, + METH_KEYWORDS | METH_CLASS, + PyDoc_STR("timestamp[, tzinfo] -> local time from POSIX timestamp.")}, + + /* Instance methods: */ + /* Inherited: date(), time(), ctime(). */ + {"timetuple", (PyCFunction)datetimetz_timetuple, METH_NOARGS, + PyDoc_STR("Return time tuple, compatible with time.localtime().")}, + + {"utctimetuple", (PyCFunction)datetimetz_utctimetuple, METH_NOARGS, + PyDoc_STR("Return UTC time tuple, compatible with time.localtime().")}, + + {"timetz", (PyCFunction)datetimetz_gettimetz, METH_NOARGS, + PyDoc_STR("Return timetz object with same hour, minute, second, " + "microsecond, and tzinfo.")}, + + {"isoformat", (PyCFunction)datetimetz_isoformat, METH_KEYWORDS, + PyDoc_STR("[sep] -> string in ISO 8601 format, " + "YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].\n\n" + "sep is used to separate the year from the time, and " + "defaults to 'T'.")}, + + {"utcoffset", (PyCFunction)datetimetz_utcoffset, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, + + {"tzname", (PyCFunction)datetimetz_tzname, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.tzname(self).")}, + + {"dst", (PyCFunction)datetimetz_dst, METH_NOARGS, + PyDoc_STR("Return self.tzinfo.dst(self).")}, + + {"__setstate__", (PyCFunction)datetimetz_setstate, METH_O, + PyDoc_STR("__setstate__(state)")}, + + {"__getstate__", (PyCFunction)datetimetz_getstate, METH_NOARGS, + PyDoc_STR("__getstate__() -> state")}, + {NULL, NULL} +}; + +static char datetimetz_doc[] = +PyDoc_STR("date/time type."); + +static PyNumberMethods datetimetz_as_number = { + datetimetz_add, /* nb_add */ + datetimetz_subtract, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* nb_positive */ + 0, /* nb_absolute */ + 0, /* nb_nonzero */ +}; + +statichere PyTypeObject PyDateTime_DateTimeTZType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.datetimetz", /* tp_name */ + sizeof(PyDateTime_DateTimeTZ), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)datetimetz_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)datetimetz_repr, /* tp_repr */ + &datetimetz_as_number, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + datetimetz_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + datetimetz_methods, /* tp_methods */ + 0, /* tp_members */ + datetimetz_getset, /* tp_getset */ + &PyDateTime_DateTimeType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + datetimetz_new, /* tp_new */ + _PyObject_Del, /* tp_free */ +}; + +/* --------------------------------------------------------------------------- + * Module methods and initialization. + */ + +static PyMethodDef module_methods[] = { + /* Private functions for pickling support, registered with the + * copy_reg module by the module init function. + */ + {"_date_pickler", (PyCFunction)date_pickler, METH_O, NULL}, + {"_date_unpickler", (PyCFunction)date_unpickler, METH_O, NULL}, + {"_datetime_pickler", (PyCFunction)datetime_pickler, METH_O, NULL}, + {"_datetime_unpickler", (PyCFunction)datetime_unpickler,METH_O, NULL}, + {"_datetimetz_pickler", (PyCFunction)datetimetz_pickler,METH_O, NULL}, + {"_datetimetz_unpickler",(PyCFunction)datetimetz_unpickler,METH_O, NULL}, + {"_time_pickler", (PyCFunction)time_pickler, METH_O, NULL}, + {"_time_unpickler", (PyCFunction)time_unpickler, METH_O, NULL}, + {"_timetz_pickler", (PyCFunction)timetz_pickler, METH_O, NULL}, + {"_timetz_unpickler", (PyCFunction)timetz_unpickler, METH_O, NULL}, + {"_tzinfo_pickler", (PyCFunction)tzinfo_pickler, METH_O, NULL}, + {"_tzinfo_unpickler", (PyCFunction)tzinfo_unpickler, METH_NOARGS, + NULL}, + {NULL, NULL} +}; + +PyMODINIT_FUNC +initdatetime(void) +{ + PyObject *m; /* a module object */ + PyObject *d; /* its dict */ + PyObject *x; + + /* Types that use __reduce__ for pickling need to set the following + * magical attr in the type dict, with a true value. + */ + PyObject *safepickle = PyString_FromString("__safe_for_unpickling__"); + if (safepickle == NULL) + return; + + m = Py_InitModule3("datetime", module_methods, + "Fast implementation of the datetime type."); + + if (PyType_Ready(&PyDateTime_DateType) < 0) + return; + if (PyType_Ready(&PyDateTime_DateTimeType) < 0) + return; + if (PyType_Ready(&PyDateTime_DeltaType) < 0) + return; + if (PyType_Ready(&PyDateTime_TimeType) < 0) + return; + if (PyType_Ready(&PyDateTime_TZInfoType) < 0) + return; + if (PyType_Ready(&PyDateTime_TimeTZType) < 0) + return; + if (PyType_Ready(&PyDateTime_DateTimeTZType) < 0) + return; + + /* Pickling support, via registering functions with copy_reg. */ + { + PyObject *pickler; + PyObject *copyreg = PyImport_ImportModule("copy_reg"); + + if (copyreg == NULL) return; + + pickler = PyObject_GetAttrString(m, "_date_pickler"); + if (pickler == NULL) return; + date_unpickler_object = PyObject_GetAttrString(m, + "_date_unpickler"); + if (date_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_DateType, + pickler, + date_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_datetime_pickler"); + if (pickler == NULL) return; + datetime_unpickler_object = PyObject_GetAttrString(m, + "_datetime_unpickler"); + if (datetime_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_DateTimeType, + pickler, + datetime_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_time_pickler"); + if (pickler == NULL) return; + time_unpickler_object = PyObject_GetAttrString(m, + "_time_unpickler"); + if (time_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_TimeType, + pickler, + time_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_timetz_pickler"); + if (pickler == NULL) return; + timetz_unpickler_object = PyObject_GetAttrString(m, + "_timetz_unpickler"); + if (timetz_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_TimeTZType, + pickler, + timetz_unpickler_object); + if (x == NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_tzinfo_pickler"); + if (pickler == NULL) return; + tzinfo_unpickler_object = PyObject_GetAttrString(m, + "_tzinfo_unpickler"); + if (tzinfo_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_TZInfoType, + pickler, + tzinfo_unpickler_object); + if (x== NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + pickler = PyObject_GetAttrString(m, "_datetimetz_pickler"); + if (pickler == NULL) return; + datetimetz_unpickler_object = PyObject_GetAttrString(m, + "_datetimetz_unpickler"); + if (datetimetz_unpickler_object == NULL) return; + x = PyObject_CallMethod(copyreg, "pickle", "OOO", + &PyDateTime_DateTimeTZType, + pickler, + datetimetz_unpickler_object); + if (x== NULL) return; + Py_DECREF(x); + Py_DECREF(pickler); + + Py_DECREF(copyreg); + } + + /* timedelta values */ + d = PyDateTime_DeltaType.tp_dict; + + if (PyDict_SetItem(d, safepickle, Py_True) < 0) + return; + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(-MAX_DELTA_DAYS, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + /* date values */ + d = PyDateTime_DateType.tp_dict; + + x = new_date(1, 1, 1); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_date(MAXYEAR, 12, 31); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(1, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* datetime values */ + d = PyDateTime_DateTimeType.tp_dict; + + x = new_datetime(1, 1, 1, 0, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* time values */ + d = PyDateTime_TimeType.tp_dict; + + x = new_time(0, 0, 0, 0); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_time(23, 59, 59, 999999); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* timetz values */ + d = PyDateTime_TimeTZType.tp_dict; + + x = new_timetz(0, 0, 0, 0, Py_None); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_timetz(23, 59, 59, 999999, Py_None); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + /* datetimetz values */ + d = PyDateTime_DateTimeTZType.tp_dict; + + x = new_datetimetz(1, 1, 1, 0, 0, 0, 0, Py_None); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return; + Py_DECREF(x); + + x = new_datetimetz(MAXYEAR, 12, 31, 23, 59, 59, 999999, Py_None); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return; + Py_DECREF(x); + + x = new_delta(0, 0, 1, 0); + if (x == NULL || PyDict_SetItemString(d, "resolution", x) < 0) + return; + Py_DECREF(x); + + Py_DECREF(safepickle); + + /* module initialization */ + PyModule_AddIntConstant(m, "MINYEAR", MINYEAR); + PyModule_AddIntConstant(m, "MAXYEAR", MAXYEAR); + + Py_INCREF(&PyDateTime_DateType); + PyModule_AddObject(m, "date", (PyObject *) &PyDateTime_DateType); + + Py_INCREF(&PyDateTime_DateTimeType); + PyModule_AddObject(m, "datetime", + (PyObject *) &PyDateTime_DateTimeType); + + Py_INCREF(&PyDateTime_DeltaType); + PyModule_AddObject(m, "timedelta", (PyObject *) &PyDateTime_DeltaType); + + Py_INCREF(&PyDateTime_TimeType); + PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeType); + + Py_INCREF(&PyDateTime_TZInfoType); + PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); + + Py_INCREF(&PyDateTime_TimeTZType); + PyModule_AddObject(m, "timetz", (PyObject *) &PyDateTime_TimeTZType); + + Py_INCREF(&PyDateTime_DateTimeTZType); + PyModule_AddObject(m, "datetimetz", + (PyObject *)&PyDateTime_DateTimeTZType); + + /* A 4-year cycle has an extra leap day over what we'd get from + * pasting together 4 single years. + */ + assert(DI4Y == 4 * 365 + 1); + assert(DI4Y == days_before_year(4+1)); + + /* Similarly, a 400-year cycle has an extra leap day over what we'd + * get from pasting together 4 100-year cycles. + */ + assert(DI400Y == 4 * DI100Y + 1); + assert(DI400Y == days_before_year(400+1)); + + /* OTOH, a 100-year cycle has one fewer leap day than we'd get from + * pasting together 25 4-year cycles. + */ + assert(DI100Y == 25 * DI4Y - 1); + assert(DI100Y == days_before_year(100+1)); + + us_per_us = PyInt_FromLong(1); + us_per_ms = PyInt_FromLong(1000); + us_per_second = PyInt_FromLong(1000000); + us_per_minute = PyInt_FromLong(60000000); + seconds_per_day = PyInt_FromLong(24 * 3600); + if (us_per_us == NULL || us_per_ms == NULL || us_per_second == NULL || + us_per_minute == NULL || seconds_per_day == NULL) + return; + + /* The rest are too big for 32-bit ints, but even + * us_per_week fits in 40 bits, so doubles should be exact. + */ + us_per_hour = PyLong_FromDouble(3600000000.0); + us_per_day = PyLong_FromDouble(86400000000.0); + us_per_week = PyLong_FromDouble(604800000000.0); + if (us_per_hour == NULL || us_per_day == NULL || us_per_week == NULL) + return; +} |