Index: Include/datetime.h =================================================================== --- Include/datetime.h (revision 81449) +++ Include/datetime.h (working copy) @@ -45,7 +45,12 @@ PyObject_HEAD /* a pure abstract base clase */ } PyDateTime_TZInfo; +typedef struct +{ + PyDateTime_TZInfo tzinfo; /* Inherit from tzinfo */ +} PyDateTime_UTC; + /* The datetime and time types have hashcodes, and an optional tzinfo member, * present if and only if hastzinfo is true. */ @@ -144,6 +149,7 @@ PyTypeObject *TimeType; PyTypeObject *DeltaType; PyTypeObject *TZInfoType; + PyTypeObject *UTCType; /* constructors */ PyObject *(*Date_FromDate)(int, int, int, PyTypeObject*); Index: Doc/library/datetime.rst =================================================================== --- Doc/library/datetime.rst (revision 81449) +++ Doc/library/datetime.rst (working copy) @@ -30,11 +30,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:`UTC` 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: @@ -101,6 +102,14 @@ time adjustment (for example, to account for time zone and/or daylight saving time). +.. class:: UTC + + A class that implements the :class:`tzinfo` abstract base class for + the UTC timezone. + + .. versionadded:: 2.7 + + Objects of these types are immutable. Objects of the :class:`date` type are always naive. @@ -118,6 +127,7 @@ object timedelta tzinfo + UTC time date datetime @@ -629,12 +639,16 @@ See also :meth:`today`, :meth:`utcnow`. -.. classmethod:: datetime.utcnow() +.. classmethod:: datetime.utcnow([tz_aware]) - 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 + Return the current UTC date and time. If *tz_aware* is true, :attr:`tzinfo` is + set to an instance of :class:`UTC`, otherwise :attr:`tzinfo` is set to ``None``. + The default value for *tz_aware* is ``False``. This is like :meth:`now`, but + returns the current UTC date and time, as a :class:`datetime` object. See also :meth:`now`. + .. versionchanged:: 2.7 + Added *tz_aware* parameter. .. classmethod:: datetime.fromtimestamp(timestamp[, tz]) @@ -1493,9 +1507,9 @@ 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:`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)). .. _strftime-strptime-behavior: @@ -1668,3 +1682,8 @@ (5) For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is replaced with the string ``'-0330'``. + +.. rubric:: Footnotes + +.. [#] For an extended timezone implementation, see + `Pytz `_. Index: Doc/includes/tzinfo-examples.py =================================================================== --- Doc/includes/tzinfo-examples.py (revision 81449) +++ Doc/includes/tzinfo-examples.py (working copy) @@ -3,22 +3,6 @@ ZERO = timedelta(0) HOUR = timedelta(hours=1) -# A UTC class. - -class UTC(tzinfo): - """UTC""" - - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO - -utc = UTC() - # A class building tzinfo objects for fixed-offset time zones. # Note that FixedOffset(0, "UTC") is a different way to build a # UTC tzinfo object. Index: Lib/test/test_datetime.py =================================================================== --- Lib/test/test_datetime.py (revision 81449) +++ Lib/test/test_datetime.py (working copy) @@ -14,6 +14,7 @@ from datetime import timedelta from datetime import tzinfo from datetime import time +from datetime import UTC from datetime import date, datetime pickle_choices = [(pickler, unpickler, proto) @@ -41,6 +42,7 @@ # tzinfo tests class FixedOffset(tzinfo): + def __init__(self, offset, name, dstoffset=42): if isinstance(offset, int): offset = timedelta(minutes=offset) @@ -59,6 +61,7 @@ return self.__dstoffset class PicklableFixedOffset(FixedOffset): + def __init__(self, offset=None, name=None, dstoffset=None): FixedOffset.__init__(self, offset, name, dstoffset) @@ -123,11 +126,32 @@ self.assertEqual(derived.utcoffset(None), offset) self.assertEqual(derived.tzname(None), 'cookie') +class TestUTC(unittest.TestCase): + + def test_inheritance(self): + self.assertTrue(isinstance(UTC(), tzinfo)) + + def test_utcoffset(self): + self.assertRaises(TypeError, UTC().utcoffset, None) + self.assertRaises(TypeError, UTC().utcoffset, 10) + self.assertEquals(timedelta(0), UTC().utcoffset(datetime(2009, 1, 1))) + + def test_dst(self): + self.assertRaises(TypeError, UTC().dst, None) + self.assertRaises(TypeError, UTC().dst, 10) + self.assertEquals(timedelta(0), UTC().dst(datetime(2009, 1, 1))) + + def test_tzname(self): + self.assertRaises(TypeError, UTC().tzname, None) + self.assertRaises(TypeError, UTC().tzname, 10) + self.assertEquals('UTC', UTC().tzname(datetime(2009, 1, 1))) + ############################################################################# # Base clase for testing a particular aspect of timedelta, time, date and # datetime comparisons. class HarmlessMixedComparison: + # Test that __eq__ and __ne__ don't complain for mixed-type comparisons. # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a @@ -1540,6 +1564,10 @@ # Else try again a few times. self.assertTrue(abs(from_timestamp - from_now) <= tolerance) + def test_utcnow_not_tz_aware(self): + now = datetime.utcnow(tz_aware=True) + self.assertTrue(isinstance(now.tzinfo, UTC)) + def test_strptime(self): import _strptime @@ -2694,12 +2722,15 @@ meth = self.theclass.utcnow # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). base = meth() - # Try with and without naming the keyword; for whatever reason, - # utcnow() doesn't accept a tzinfo argument. - off42 = FixedOffset(42, "42") - self.assertRaises(TypeError, meth, off42) - self.assertRaises(TypeError, meth, tzinfo=off42) + # Test tzinfo parameter. + self.assertTrue(isinstance(meth(True).tzinfo, UTC)) + self.assertTrue(isinstance(meth(1).tzinfo, UTC)) + self.assertTrue(isinstance(meth(object()).tzinfo, UTC)) + + self.assertEquals(None, meth(False).tzinfo, UTC) + self.assertEquals(None, meth(0).tzinfo, UTC) + def test_tzinfo_utcfromtimestamp(self): import time meth = self.theclass.utcfromtimestamp @@ -3314,7 +3345,6 @@ start += HOUR fstart += HOUR - ############################################################################# # oddballs Index: Modules/datetimemodule.c =================================================================== --- Modules/datetimemodule.c (revision 81449) +++ Modules/datetimemodule.c (working copy) @@ -103,6 +103,7 @@ static PyTypeObject PyDateTime_DeltaType; static PyTypeObject PyDateTime_TimeType; static PyTypeObject PyDateTime_TZInfoType; +static PyTypeObject PyDateTime_UTCType; /* --------------------------------------------------------------------------- * Math utilities. @@ -751,6 +752,15 @@ #define new_delta(d, s, us, normalize) \ new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType) +static PyObject * +new_utc_ex(PyTypeObject *type) +{ + return type->tp_alloc(type, 0); +} + +#define new_utc() \ + new_utc_ex(&PyDateTime_UTCType) + /* --------------------------------------------------------------------------- * tzinfo helpers. */ @@ -3078,6 +3088,98 @@ 0, /* tp_free */ }; +static PyObject * +utc_tzname(PyObject *self, PyObject *dt) +{ + if (! PyObject_TypeCheck(dt, &PyDateTime_DateTimeType)) { + PyErr_SetString(PyExc_TypeError, "argument must be datetime"); + return NULL; + } + + return PyString_FromString("UTC"); +} + +static PyObject * +utc_utcoffset(PyObject *self, PyObject *dt) +{ + if (! PyObject_TypeCheck(dt, &PyDateTime_DateTimeType)) { + PyErr_SetString(PyExc_TypeError, "argument must be datetime"); + return NULL; + } + + return new_delta(0, 0, 0, 0); +} + +static PyObject * +utc_dst(PyObject *self, PyObject *dt) +{ + if (! PyObject_TypeCheck(dt, &PyDateTime_DateTimeType)) { + PyErr_SetString(PyExc_TypeError, "argument must be datetime"); + return NULL; + } + + return new_delta(0, 0, 0, 0); +} + +static PyMethodDef utc_methods[] = { + + {"tzname", (PyCFunction)utc_tzname, METH_O, + PyDoc_STR("Always 'UTC'.")}, + + {"utcoffset", (PyCFunction)utc_utcoffset, METH_O, + PyDoc_STR("Always 0 minutes.")}, + + {"dst", (PyCFunction)utc_dst, METH_O, + PyDoc_STR("Always 0 minutes.")}, + + {NULL, NULL} +}; + +static char utc_doc[] = +PyDoc_STR("UTC implementation of tzinfo."); + +statichere PyTypeObject PyDateTime_UTCType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.UTC", /* tp_name */ + sizeof(PyDateTime_UTC), /* 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 */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + utc_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + utc_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 */ + PyType_GenericNew, /* tp_new */ +}; + /* * PyDateTime_Time implementation. */ @@ -3874,9 +3976,23 @@ * precision of a timestamp. */ static PyObject * -datetime_utcnow(PyObject *cls, PyObject *dummy) +datetime_utcnow(PyObject *cls, PyObject *args, PyObject *kw) { - return datetime_best_possible(cls, gmtime, Py_None); + PyObject *utc = Py_None; + PyObject *tz_aware = Py_False; + static char *keywords[] = {"tz_aware", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kw, "|O:utcnow", + keywords, &tz_aware)) + return NULL; + + if (PyObject_IsTrue(tz_aware)) { + if (! (utc = new_utc())) { + return NULL; + } + } + + return datetime_best_possible(cls, gmtime, utc); } /* Return new local datetime from timestamp (Python timestamp -- a double). */ @@ -4599,8 +4715,9 @@ PyDoc_STR("[tz] -> new datetime with tz's local day and time.")}, {"utcnow", (PyCFunction)datetime_utcnow, - METH_NOARGS | METH_CLASS, - PyDoc_STR("Return a new datetime representing UTC day and time.")}, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("[tz_aware] -> Return a new datetime "\ + "representing UTC day and time.")}, {"fromtimestamp", (PyCFunction)datetime_fromtimestamp, METH_VARARGS | METH_KEYWORDS | METH_CLASS, @@ -4748,6 +4865,7 @@ &PyDateTime_TimeType, &PyDateTime_DeltaType, &PyDateTime_TZInfoType, + &PyDateTime_UTCType, new_date_ex, new_datetime_ex, new_time_ex, @@ -4779,6 +4897,8 @@ return; if (PyType_Ready(&PyDateTime_TZInfoType) < 0) return; + if (PyType_Ready(&PyDateTime_UTCType) < 0) + return; /* timedelta values */ d = PyDateTime_DeltaType.tp_dict; @@ -4872,6 +4992,9 @@ Py_INCREF(&PyDateTime_TZInfoType); PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); + Py_INCREF(&PyDateTime_UTCType); + PyModule_AddObject(m, "UTC", (PyObject *) &PyDateTime_UTCType); + x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL); if (x == NULL) return;