Index: Misc/maintainers.rst =================================================================== --- Misc/maintainers.rst (revision 81837) +++ Misc/maintainers.rst (working copy) @@ -80,7 +80,7 @@ csv ctypes theller curses andrew.kuchling -datetime +datetime alexander.belopolsky dbm decimal facundobatista, rhettinger, mark.dickinson difflib tim_one @@ -207,7 +207,7 @@ test textwrap threading -time +time alexander.belopolsky timeit tkinter gpolo token georg.brandl Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 81837) +++ Misc/NEWS (working copy) @@ -2954,6 +2954,16 @@ Extension Modules ----------------- +- Issue #6641: The ``datetime.strptime`` method now supports ``%z`` directive. + When ``%z`` directive is present, an aware ``datetime`` object is returned. + +- Issue #5094: The ``datetime`` module now has a simple concrete class implementing + ``datetime.tzinfo`` ABC. Instances of the new class, ``datetime.timezone``, + return fixed name and UTC offset from their ``tzname(dt)`` and ``utcoffset(dt)`` + methods. The ``dt`` argument is ignored. The ``dst(dt)`` method always returns + ``None``. A class attribute, ``utc`` contains an instance representing the UTC + timezone. + - Issue #3745: Fix hashlib to always reject unicode and non buffer-api supporting objects as input no matter how it was compiled (built in implementations or external openssl library). Index: Doc/library/datetime.rst =================================================================== --- Doc/library/datetime.rst (revision 81837) +++ Doc/library/datetime.rst (working copy) @@ -28,11 +28,12 @@ have an optional time zone information member, :attr:`tzinfo`, that can contain an instance of a subclass of the abstract :class:`tzinfo` class. These :class:`tzinfo` objects capture information about the offset from UTC time, the -time zone name, and whether Daylight Saving Time is in effect. Note that no -concrete :class:`tzinfo` classes are supplied by the :mod:`datetime` module. -Supporting timezones at whatever level of detail is required is up to the -application. The rules for time adjustment across the world are more political -than rational, and there is no standard suitable for every application. +time zone name, and whether Daylight Saving Time is in effect. Note that only +one concrete :class:`tzinfo` class, the :class:`timezone` class, is supplied by the +:mod:`datetime` module. Supporting timezones at whatever level of detail is +required is up to the application. The rules for time adjustment across the +world are more political than rational, change frequently, and there is no +standard suitable for every application aside from UTC [#]_. The :mod:`datetime` module exports the following constants: @@ -99,6 +100,14 @@ time adjustment (for example, to account for time zone and/or daylight saving time). +.. class:: timezone + + A class that implements the :class:`tzinfo` abstract base class as a + fixed offset from the UTC. + + .. versionadded:: 3.2 + + Objects of these types are immutable. Objects of the :class:`date` type are always naive. @@ -116,6 +125,7 @@ object timedelta tzinfo + timezone time date datetime @@ -658,9 +668,9 @@ Return the current UTC date and time, with :attr:`tzinfo` ``None``. This is like :meth:`now`, but returns the current UTC date and time, as a naive - :class:`datetime` object. See also :meth:`now`. + :class:`datetime` object. An aware current UTC datetime can be obtained by + calling ``datetime.now(timezeone.utc)``. See also :meth:`now`. - .. classmethod:: datetime.fromtimestamp(timestamp, tz=None) Return the local date and time corresponding to the POSIX timestamp, such as is @@ -1517,11 +1527,67 @@ standard local time. Applications that can't bear such ambiguities should avoid using hybrid -:class:`tzinfo` subclasses; there are no ambiguities when using UTC, or any -other fixed-offset :class:`tzinfo` subclass (such as a class representing only -EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). +:class:`tzinfo` subclasses; there are no ambiguities when using :class:`timezone`, +or any other fixed-offset :class:`tzinfo` subclass (such as a class representing +only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). +.. _datetime-timezone: + +:class:`timezone` Objects +-------------------------- + +A :class:`timezone` object represents a timezone that is defined by a +fixed offset from UTC. Note that objects of this class cannot be used +to represent timezone information in the locations where different +offsets are used in different days of the year or where historical +changes have been made to civil time. + + +.. class:: timezone(offset[, name]) + + The ``offset`` argument must be specified as a :class:`timedelta` + object representing the difference between the local time and UTC. It must + be within the range [``-timedelta(hours=23, minutes=59), + ``timedelta(hours=23, minutes=59)``] and represent whole number of minutes, + otherwise :exc:`ValueError` is raised. + + The ``name`` argument is optional. If specified it must be a string that + used as the value returned by the ``tzname(dt)`` method. Otherwise, + ``tzname(dt)`` returns a string 'UTCsHHMM', where s is the sign of + ``offset``, HH and MM are two digits of ``offset.hours`` and + ``offset.minutes`` respectively. + +.. method:: timezone.utcoffset(self, dt) + + Returns the fixed value specified when the :class:`timezone` instance is + constructed. The ``dt`` argument is ignored. The return value is a + :class:`timedelta` instance equal to the difference between the + local time and UTC. + +.. method:: timezone.tzname(self, dt) + + Returns the fixed value specified when the :class:`timezone` instance is + constructed or a string 'UTCsHHMM', where s is the sign of + ``offset``, HH and MM are two digits of ``offset.hours`` and + ``offset.minutes`` respectively. The ``dt`` argument is ignored. + +.. method:: timezone.dst(self, dt) + + Always returns ``None``. + +.. method:: timezone.fromutc(self, dt) + + Returns ``dt + offset``. The ``dt`` argument must be aware with ``tzinfo`` + set to ``self``. + +Class attributes: + +.. attribute:: timezone.utc + + The UTC timezone, ``timezone(0, 'UTC')``. + + .. _strftime-strptime-behavior: :meth:`strftime` and :meth:`strptime` Behavior @@ -1687,3 +1753,9 @@ (5) For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is replaced with the string ``'-0330'``. + + +.. versionadded:: 3.2 + +When ``%z`` directive is provided to the :meth:`strptime` method, an aware +:class:`datetime` object will be produced. \ No newline at end of file Index: Lib/_strptime.py =================================================================== --- Lib/_strptime.py (revision 81837) +++ Lib/_strptime.py (working copy) @@ -16,7 +16,10 @@ from re import compile as re_compile from re import IGNORECASE, ASCII from re import escape as re_escape -from datetime import date as datetime_date +from datetime import (date as datetime_date, + datetime as datetime_datetime, + timedelta as datetime_timedelta, + timezone as datetime_timezone) try: from _thread import allocate_lock as _thread_allocate_lock except: @@ -204,6 +207,7 @@ #XXX: Does 'Y' need to worry about having less or more than # 4 digits? 'Y': r"(?P\d\d\d\d)", + 'z': r"(?P[+-]\d\d[0-5]\d)", 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), @@ -333,10 +337,12 @@ if len(data_string) != found.end(): raise ValueError("unconverted data remains: %s" % data_string[found.end():]) + year = 1900 month = day = 1 hour = minute = second = fraction = 0 tz = -1 + tzoffset = None # Default to -1 to signify that values not known; not critical to have, # though week_of_year = -1 @@ -417,6 +423,11 @@ else: # W starts week on Monday. week_of_year_start = 0 + elif group_key == 'z': + z = found_dict['z'] + tzoffset = int(z[1:3]) * 60 + int(z[3:5]) + if z.startswith("-"): + tzoffset = -tzoffset elif group_key == 'Z': # Since -1 is default value only need to worry about setting tz if # it can be something other than -1. @@ -453,9 +464,31 @@ day = datetime_result.day if weekday == -1: weekday = datetime_date(year, month, day).weekday() - return (time.struct_time((year, month, day, - hour, minute, second, - weekday, julian, tz)), fraction) + # Add timezone info + tzname = found_dict.get("Z") + if tzoffset is not None: + gmtoff = tzoffset * 60 + else: + gmtoff = None + return (year, month, day, + hour, minute, second, + weekday, julian, tz, gmtoff, tzname), fraction + def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"): - return _strptime(data_string, format)[0] + tt = _strptime(data_string, format)[0] + return time.struct_time(tt[:9]) + +def _strptime_datetime(data_string, format="%a %b %d %H:%M:%S %Y"): + tt, fraction = _strptime(data_string, format) + gmtoff, tzname = tt[-2:] + args = tt[:6] + (fraction,) + if gmtoff is not None: + tzdelta = datetime_timedelta(seconds=gmtoff) + if tzname: + tz = datetime_timezone(tzdelta, tzname) + else: + tz = datetime_timezone(tzdelta) + args += (tz,) + + return datetime_datetime(*args) Index: Lib/test/test_datetime.py =================================================================== --- Lib/test/test_datetime.py (revision 81837) +++ Lib/test/test_datetime.py (working copy) @@ -15,6 +15,7 @@ from datetime import timedelta from datetime import tzinfo from datetime import time +from datetime import timezone from datetime import date, datetime pickle_choices = [(pickle, pickle, proto) for proto in range(3)] @@ -49,6 +50,7 @@ # tzinfo tests class FixedOffset(tzinfo): + def __init__(self, offset, name, dstoffset=42): if isinstance(offset, int): offset = timedelta(minutes=offset) @@ -67,6 +69,7 @@ return self.__dstoffset class PicklableFixedOffset(FixedOffset): + def __init__(self, offset=None, name=None, dstoffset=None): FixedOffset.__init__(self, offset, name, dstoffset) @@ -131,6 +134,76 @@ self.assertEqual(derived.utcoffset(None), offset) self.assertEqual(derived.tzname(None), 'cookie') +class TestTimeZone(unittest.TestCase): + + def setUp(self): + self.ACDT = timezone(timedelta(hours=9.5), 'ACDT') + self.EST = timezone(-timedelta(hours=5), 'EST') + self.DT = datetime(2010, 1, 1) + + def test_class_members(self): + limit = timedelta(hours=23, minutes=59) + self.assertEquals(timezone.utc.utcoffset(None), ZERO) + self.assertEquals(timezone.min.utcoffset(None), -limit) + self.assertEquals(timezone.max.utcoffset(None), limit) + + + def test_constructor(self): + self.assertEquals(timezone.utc, timezone(timedelta(0))) + # invalid offsets + for invalid in [timedelta(microseconds=1), timedelta(1, 1), + timedelta(seconds=1)]: + self.assertRaises(ValueError, timezone, invalid) + self.assertRaises(ValueError, timezone, -invalid) + + def test_inheritance(self): + self.assertTrue(isinstance(timezone.utc, tzinfo)) + self.assertTrue(isinstance(self.EST, tzinfo)) + + def test_utcoffset(self): + dummy = self.DT + for h in [0, 1.5, 12]: + offset = h * HOUR + self.assertEquals(offset, timezone(offset).utcoffset(dummy)) + self.assertEquals(-offset, timezone(-offset).utcoffset(dummy)) + + def test_dst(self): + self.assertEquals(None, timezone.utc.dst(self.DT)) + + def test_tzname(self): + self.assertEquals('UTC', timezone(ZERO).tzname(None)) + self.assertEquals('UTC-0500', timezone(-5 * HOUR).tzname(None)) + self.assertEquals('UTC+0930', timezone(9.5 * HOUR).tzname(None)) + self.assertEquals('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None)) + + def test_fromutc(self): + with self.assertRaises(ValueError): + timezone.utc.fromutc(self.DT) + for tz in [self.EST, self.ACDT, Eastern]: + utctime = self.DT.replace(tzinfo=tz) + local = tz.fromutc(utctime) + self.assertEquals(local - utctime, tz.utcoffset(local)) + self.assertEquals(local, + self.DT.replace(tzinfo=timezone.utc)) + + def test_comparison(self): + self.assertNotEqual(timezone(ZERO), timezone(HOUR)) + self.assertEqual(timezone(HOUR), timezone(HOUR)) + self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST')) + self.assertRaises(TypeError, lambda: timezone(ZERO) < timezone(ZERO)) + self.assertIn(timezone(ZERO), {timezone(ZERO)}) + + def test_aware_datetime(self): + # test that timezone instances can be used by datetime + t = datetime(1, 1, 1) + for tz in [timezone.min, timezone.max, timezone.utc]: + self.assertEquals(tz.tzname(t), + t.replace(tzinfo=tz).tzname()) + self.assertEquals(tz.utcoffset(t), + t.replace(tzinfo=tz).utcoffset()) + self.assertEquals(tz.dst(t), + t.replace(tzinfo=tz).dst()) + ############################################################################# # Base clase for testing a particular aspect of timedelta, time, date and # datetime comparisons. @@ -1637,11 +1710,32 @@ string = '2004-12-01 13:02:47.197' format = '%Y-%m-%d %H:%M:%S.%f' - result, frac = _strptime._strptime(string, format) - expected = self.theclass(*(result[0:6]+(frac,))) + expected = _strptime._strptime_datetime(string, format) got = self.theclass.strptime(string, format) self.assertEqual(expected, got) + strptime = self.theclass.strptime + minute = timedelta(minutes=1) + hour = timedelta(hours=1) + self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * minute) + self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * minute) + dt = strptime("-0500 EST", "%z %Z") + self.assertEqual(dt.utcoffset(), -5 * hour) + self.assertEqual(dt.tzname(), 'EST') + # Can produce inconsistent datetime + dtstr, fmt = "+1234 UTC", "%z %Z" + dt = strptime(dtstr, fmt) + self.assertEqual(dt.utcoffset(), 12 * hour + 34 * minute) + self.assertEqual(dt.tzname(), 'UTC') + # yet will roundtrip + self.assertEqual(dt.strftime(fmt), dtstr) + + # Produce naive datetime if no %z is provided + self.assertEqual(strptime("UTC", "%Z").tzinfo, None) + + with self.assertRaises(ValueError): strptime("-2400", "%z") + with self.assertRaises(ValueError): strptime("-000", "%z") + def test_more_timetuple(self): # This tests fields beyond those tested by the TestDate.test_timetuple. t = self.theclass(2004, 12, 31, 6, 22, 33) @@ -2729,20 +2823,21 @@ # We don't know which time zone we're in, and don't have a tzinfo # class to represent it, so seeing whether a tz argument actually # does a conversion is tricky. - weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0) utc = FixedOffset(0, "utc", 0) - for dummy in range(3): - now = datetime.now(weirdtz) - self.assertTrue(now.tzinfo is weirdtz) - utcnow = datetime.utcnow().replace(tzinfo=utc) - now2 = utcnow.astimezone(weirdtz) - if abs(now - now2) < timedelta(seconds=30): - break - # Else the code is broken, or more than 30 seconds passed between - # calls; assuming the latter, just try again. - else: - # Three strikes and we're out. - self.fail("utcnow(), now(tz), or astimezone() may be broken") + for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0), + timezone(timedelta(hours=15, minutes=58), "weirdtz"),]: + for dummy in range(3): + now = datetime.now(weirdtz) + self.assertTrue(now.tzinfo is weirdtz) + utcnow = datetime.utcnow().replace(tzinfo=utc) + now2 = utcnow.astimezone(weirdtz) + if abs(now - now2) < timedelta(seconds=30): + break + # Else the code is broken, or more than 30 seconds passed between + # calls; assuming the latter, just try again. + else: + # Three strikes and we're out. + self.fail("utcnow(), now(tz), or astimezone() may be broken") def test_tzinfo_fromtimestamp(self): import time Index: Modules/datetimemodule.c =================================================================== --- Modules/datetimemodule.c (revision 81837) +++ Modules/datetimemodule.c (working copy) @@ -102,6 +102,7 @@ static PyTypeObject PyDateTime_DeltaType; static PyTypeObject PyDateTime_TimeType; static PyTypeObject PyDateTime_TZInfoType; +static PyTypeObject PyDateTime_TimeZoneType; /* --------------------------------------------------------------------------- * Math utilities. @@ -771,6 +772,58 @@ #define new_delta(d, s, us, normalize) \ new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType) + +typedef struct +{ + PyObject_HEAD + PyObject *offset; + PyObject *name; +} PyDateTime_TimeZone; + +static PyObject * +new_timezone_ex(PyTypeObject *type, PyObject *offset, PyObject *name) +{ + PyDateTime_TimeZone *self; + + if (offset == NULL) { + offset = new_delta(0, 0, 0, 0); + if (offset == NULL) + return NULL; + } + else if (PyDelta_Check(offset)) + Py_INCREF(offset); + else { + PyErr_Format(PyExc_ValueError, "offset must be a timedelta"); + return NULL; + } + if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) { + Py_DECREF(offset); + PyErr_Format(PyExc_ValueError, "offset must be a timedelta" + " representing a whole number of minutes"); + return NULL; + } + if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) || + GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) { + Py_DECREF(offset); + PyErr_Format(PyExc_ValueError, "offset must be a timedelta" + " between timedelta(1) and -timedelta(1)."); + return NULL; + } + + self = (PyDateTime_TimeZone *)type->tp_alloc(type, 0); + if (self == NULL) { + Py_DECREF(offset); + return NULL; + } + self->offset = offset; + Py_XINCREF(name); + self->name = name; + return (PyObject *)self; +} + +#define new_timezone(offset, name) \ + new_timezone_ex(&PyDateTime_TimeZoneType, offset, name) + /* --------------------------------------------------------------------------- * tzinfo helpers. */ @@ -3283,6 +3336,176 @@ 0, /* tp_free */ }; +static char *timezone_kws[] = {"offset", "name", NULL}; + +static PyObject * +timezone_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + PyObject *offset; + PyObject *name = NULL; + if (PyArg_ParseTupleAndKeywords(args, kw, "O|O!:timezone", timezone_kws, + &offset, &PyUnicode_Type, &name)) + return new_timezone(offset, name); + + return NULL; +} + + +static void +timezone_dealloc(PyDateTime_TimeZone *self) +{ + Py_DECREF(self->offset); + Py_XDECREF(self->name); + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject * +timezone_richcompare(PyDateTime_TimeZone *self, PyDateTime_TimeZone *other, int op) +{ + if (op != Py_EQ) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + return delta_richcompare(self->offset, other->offset, op); +} + +static long +timezone_hash(PyDateTime_TimeZone *self) +{ + return delta_hash((PyDateTime_Delta *)self->offset); +} + +static PyObject * +timezone_tzname(PyDateTime_TimeZone *self, PyObject *dt) +{ + char buf[10]; + int hours, minutes, seconds; + PyObject *offset; + char sign; + + if (self->name != NULL) { + Py_INCREF(self->name); + return self->name; + } + if (delta_bool((PyDateTime_Delta *)self->offset) == 0) + return PyUnicode_FromString("UTC"); + if (GET_TD_DAYS(self->offset) < 0) { + sign = '-'; + offset = delta_negative((PyDateTime_Delta *)self->offset); + if (offset == NULL) + return NULL; + } + else { + sign = '+'; + offset = self->offset; + Py_INCREF(offset); + } + seconds = GET_TD_SECONDS(offset); + Py_DECREF(offset); + minutes = divmod(seconds, 60, &seconds); + hours = divmod(minutes, 60, &minutes); + /* XXX ignore sub-minute data */ + PyOS_snprintf(buf, sizeof(buf), "UTC%c%02d%02d", sign, hours, minutes); + + return PyUnicode_FromString(buf); +} + +static PyObject * +timezone_utcoffset(PyDateTime_TimeZone *self, PyObject *dt) +{ + Py_INCREF(self->offset); + return self->offset; +} + +static PyObject * +timezone_dst(PyObject *self, PyObject *dt) +{ + Py_RETURN_NONE; +} + +static PyObject * +add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta, + int factor); + +static PyObject * +timezone_fromutc(PyDateTime_TimeZone *self, PyDateTime_DateTime *dt) +{ + if (! PyDateTime_Check(dt)) { + PyErr_SetString(PyExc_TypeError, + "fromutc: argument must be a datetime"); + return NULL; + } + if (! HASTZINFO(dt) || dt->tzinfo != (PyObject *)self) { + PyErr_SetString(PyExc_ValueError, "fromutc: dt.tzinfo " + "is not self"); + return NULL; + } + + return add_datetime_timedelta(dt, (PyDateTime_Delta *)self->offset, 1); +} + +static PyMethodDef timezone_methods[] = { + {"tzname", (PyCFunction)timezone_tzname, METH_O, + PyDoc_STR("If name is specified when timezone is created, returns the name." + " Otherwise returns offset as 'UTC(+|-)HHMM'.")}, + + {"utcoffset", (PyCFunction)timezone_utcoffset, METH_O, + PyDoc_STR("Returns fixed offset. Ignores its argument.")}, + + {"dst", (PyCFunction)timezone_dst, METH_O, + PyDoc_STR("Returns None. Ignores its argument.")}, + + {"fromutc", (PyCFunction)timezone_fromutc, METH_O, + PyDoc_STR("datetime in UTC -> datetime in local time.")}, + + {NULL, NULL} +}; + +static char timezone_doc[] = +PyDoc_STR("Fixed offset from UTC implementation of tzinfo."); + +static PyTypeObject PyDateTime_TimeZoneType = { + PyVarObject_HEAD_INIT(NULL, 0) + "datetime.timezone", /* tp_name */ + sizeof(PyDateTime_TimeZone), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)timezone_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + (hashfunc)timezone_hash, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + timezone_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)timezone_richcompare,/* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + timezone_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyDateTime_TZInfoType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + timezone_new, /* tp_new */ +}; + /* * PyDateTime_Time implementation. */ @@ -4115,81 +4338,25 @@ return result; } -/* Return new datetime from time.strptime(). */ +/* Return new datetime from _strptime.strptime_datetime(). */ static PyObject * datetime_strptime(PyObject *cls, PyObject *args) { static PyObject *module = NULL; - PyObject *result = NULL, *obj, *st = NULL, *frac = NULL; + PyObject *result = NULL; const Py_UNICODE *string, *format; if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format)) return NULL; - if (module == NULL && - (module = PyImport_ImportModuleNoBlock("_strptime")) == NULL) - return NULL; + if (module == NULL) { + module = PyImport_ImportModuleNoBlock("_strptime"); + if( module == NULL) + return NULL; + } + result = PyObject_CallMethod(module, "_strptime_datetime", "uu", + string, format); - /* _strptime._strptime returns a two-element tuple. The first - element is a time.struct_time object. The second is the - microseconds (which are not defined for time.struct_time). */ - obj = PyObject_CallMethod(module, "_strptime", "uu", string, format); - if (obj != NULL) { - int i, good_timetuple = 1; - long int ia[7]; - if (PySequence_Check(obj) && PySequence_Size(obj) == 2) { - st = PySequence_GetItem(obj, 0); - frac = PySequence_GetItem(obj, 1); - if (st == NULL || frac == NULL) - good_timetuple = 0; - /* copy y/m/d/h/m/s values out of the - time.struct_time */ - if (good_timetuple && - PySequence_Check(st) && - PySequence_Size(st) >= 6) { - for (i=0; i < 6; i++) { - PyObject *p = PySequence_GetItem(st, i); - if (p == NULL) { - good_timetuple = 0; - break; - } - if (PyLong_Check(p)) - ia[i] = PyLong_AsLong(p); - else - good_timetuple = 0; - Py_DECREF(p); - } -/* if (PyLong_CheckExact(p)) { - ia[i] = PyLong_AsLongAndOverflow(p, &overflow); - if (overflow) - good_timetuple = 0; - } - else - good_timetuple = 0; - Py_DECREF(p); -*/ } - else - good_timetuple = 0; - /* follow that up with a little dose of microseconds */ - if (PyLong_Check(frac)) - ia[6] = PyLong_AsLong(frac); - else - good_timetuple = 0; - } - else - good_timetuple = 0; - if (good_timetuple) - result = PyObject_CallFunction(cls, "iiiiiii", - ia[0], ia[1], ia[2], - ia[3], ia[4], ia[5], - ia[6]); - else - PyErr_SetString(PyExc_ValueError, - "unexpected value from _strptime._strptime"); - } - Py_XDECREF(obj); - Py_XDECREF(st); - Py_XDECREF(frac); return result; } @@ -4971,6 +5138,7 @@ PyObject *m; /* a module object */ PyObject *d; /* its dict */ PyObject *x; + PyObject *delta; m = PyModule_Create(&datetimemodule); if (m == NULL) @@ -4986,6 +5154,8 @@ return NULL; if (PyType_Ready(&PyDateTime_TZInfoType) < 0) return NULL; + if (PyType_Ready(&PyDateTime_TimeZoneType) < 0) + return NULL; /* timedelta values */ d = PyDateTime_DeltaType.tp_dict; @@ -5059,6 +5229,37 @@ return NULL; Py_DECREF(x); + /* timezone values */ + d = PyDateTime_TimeZoneType.tp_dict; + + delta = new_delta(0, 0, 0, 0); + if (delta == NULL) + return NULL; + x = new_timezone(delta, NULL); + Py_DECREF(delta); + if (x == NULL || PyDict_SetItemString(d, "utc", x) < 0) + return NULL; + Py_DECREF(x); + + delta = new_delta(-1, 60, 0, 1); /* -23:59 */ + if (delta == NULL) + return NULL; + x = new_timezone(delta, NULL); + Py_DECREF(delta); + if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) + return NULL; + Py_DECREF(x); + + delta = new_delta(0, (23 * 60 + 59) * 60, 0, 0); /* +23:59 */ + if (delta == NULL) + return NULL; + x = new_timezone(delta, NULL); + Py_DECREF(delta); + if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) + return NULL; + Py_DECREF(x); + + /* module initialization */ PyModule_AddIntConstant(m, "MINYEAR", MINYEAR); PyModule_AddIntConstant(m, "MAXYEAR", MAXYEAR); @@ -5079,6 +5280,9 @@ Py_INCREF(&PyDateTime_TZInfoType); PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); + Py_INCREF(&PyDateTime_TimeZoneType); + PyModule_AddObject(m, "timezone", (PyObject *) &PyDateTime_TimeZoneType); + x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL); if (x == NULL) return NULL;