Index: Include/datetime.h =================================================================== --- Include/datetime.h (revision 81646) +++ Include/datetime.h (working copy) @@ -45,7 +45,14 @@ PyObject_HEAD /* a pure abstract base clase */ } PyDateTime_TZInfo; +typedef struct +{ + PyObject_HEAD + int offset; + PyObject *name; +} PyDateTime_UTC; + /* The datetime and time types have hashcodes, and an optional tzinfo member, * present if and only if hastzinfo is true. */ @@ -144,6 +151,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 81646) +++ 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:`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: @@ -99,6 +100,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. @@ -116,6 +125,7 @@ object timedelta tzinfo + UTC time date datetime @@ -654,12 +664,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=None) @@ -1517,9 +1531,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: @@ -1687,3 +1701,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 81646) +++ 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 81646) +++ 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,11 +134,41 @@ self.assertEqual(derived.utcoffset(None), offset) self.assertEqual(derived.tzname(None), 'cookie') +class TestTimeZone:#(unittest.TestCase): + + def test_inheritance(self): + self.assertTrue(isinstance(timezone(0), tzinfo)) + + def test_utcoffset(self): + self.assertRaises(TypeError, timezone(0).utcoffset, None) + self.assertRaises(TypeError, timezone(0).utcoffset, 10) + self.assertEquals(timedelta(0), timezone(0).utcoffset(datetime(2009, 1, 1))) + + def test_dst(self): + self.assertRaises(TypeError, timezone(0).dst, None) + self.assertRaises(TypeError, timezone(0).dst, 10) + self.assertEquals(timedelta(0), timezone(0).dst(datetime(2009, 1, 1))) + + def test_tzname(self): + self.assertRaises(TypeError, timezone(0).tzname, None) + self.assertRaises(TypeError, timezone(0).tzname, 10) + self.assertEquals('UTC', timezone(0).tzname(datetime(2009, 1, 1))) + self.assertEquals('UTC-0500', timezone(-300).tzname(datetime(2009, 1, 1))) + self.assertEquals('XYZ', timezone(-300, 'XYZ').tzname(datetime(2009, 1, 1))) + + def test_comparison(self): + self.assertNotEqual(timezone(0), timezone(1)) + self.assertEqual(timezone(1), timezone(1)) + self.assertEqual(timezone(-300), timezone(-300, 'EST')) + self.assertRaises(TypeError, timezone(0)._le_, timezone(0)) + self.assertIn(timezone(0), {timezone(0)}) + ############################################################################# # 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 @@ -1632,6 +1665,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, timezone)) + def test_strptime(self): import _strptime @@ -2783,12 +2820,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, timezone)) + self.assertTrue(isinstance(meth(1).tzinfo, timezone)) + self.assertTrue(isinstance(meth(object()).tzinfo, timezone)) + + self.assertEquals(None, meth(False).tzinfo, timezone) + self.assertEquals(None, meth(0).tzinfo, timezone) + def test_tzinfo_utcfromtimestamp(self): import time meth = self.theclass.utcfromtimestamp @@ -3404,7 +3444,6 @@ start += HOUR fstart += HOUR - ############################################################################# # oddballs Index: Modules/datetimemodule.c =================================================================== --- Modules/datetimemodule.c (revision 81646) +++ Modules/datetimemodule.c (working copy) @@ -102,6 +102,7 @@ static PyTypeObject PyDateTime_DeltaType; static PyTypeObject PyDateTime_TimeType; static PyTypeObject PyDateTime_TZInfoType; +static PyTypeObject PyDateTime_UTCType; /* --------------------------------------------------------------------------- * Math utilities. @@ -771,6 +772,25 @@ #define new_delta(d, s, us, normalize) \ new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType) +static PyObject * +new_utc_ex(PyTypeObject *type, int offset, PyObject *name) +{ + PyDateTime_UTC *tz = (PyDateTime_UTC *)type->tp_alloc(type, 0); + if (tz == NULL) + return NULL; + if (offset < -12*60 || offset > 12*60) { + PyErr_Format(PyExc_ValueError, "offset must be between -720 and 720 minutes"); + return NULL; + } + tz->offset = offset; + Py_XINCREF(name); + tz->name = name; + return (PyObject *)tz; +} + +#define new_utc(offset, name) \ + new_utc_ex(&PyDateTime_UTCType, offset, name) + /* --------------------------------------------------------------------------- * tzinfo helpers. */ @@ -3283,6 +3303,154 @@ 0, /* tp_free */ }; +static char *utc_kws[] = {"offset", "name", NULL}; + +static PyObject * +utc_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + int offset; + PyObject *name = NULL; + if (PyArg_ParseTupleAndKeywords(args, kw, "i|O!:timezone", utc_kws, + &offset, &PyUnicode_Type, &name)) + return new_utc(offset, name); + + return NULL; +} + + +static void +utc_dealloc(PyDateTime_UTC *self) +{ + Py_XDECREF(self->name); + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject * +utc_richcompare(PyDateTime_UTC *self, PyDateTime_UTC *other, int op) +{ + if (op != Py_EQ) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + if (self->offset == other->offset) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +static long +utc_hash(PyDateTime_UTC *self) +{ + return self->offset; +} + +static PyObject * +utc_tzname(PyDateTime_UTC *self, PyObject *dt) +{ + char buf[10]; + int hours, minutes, offset; + char sign; + + if (! PyObject_TypeCheck(dt, &PyDateTime_DateTimeType)) { + PyErr_SetString(PyExc_TypeError, "argument must be datetime"); + return NULL; + } + if (self->name != NULL) { + Py_INCREF(self->name); + return self->name; + } + offset = self->offset; + if (offset == 0) + return PyUnicode_FromString("UTC"); + if (offset < 0) { + sign = '-'; + offset = -offset; + } + else + sign = '+'; + hours = divmod(offset, 60, &minutes); + PyOS_snprintf(buf, sizeof(buf), "UTC%c%02d%02d", sign, hours, minutes); + + return PyUnicode_FromString(buf); +} + +static PyObject * +utc_utcoffset(PyDateTime_UTC *self, PyObject *dt) +{ + if (! PyObject_TypeCheck(dt, &PyDateTime_DateTimeType)) { + PyErr_SetString(PyExc_TypeError, "argument must be datetime"); + return NULL; + } + + return new_delta(0, self->offset * 60, 0, 1); +} + +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("Returns 'UTC(+|-)HHMM'.")}, + + {"utcoffset", (PyCFunction)utc_utcoffset, METH_O, PyDoc_STR("Returns fixed offset.")}, + + {"dst", (PyCFunction)utc_dst, METH_O, PyDoc_STR("Always 0 minutes.")}, + + {NULL, NULL} +}; + +static char utc_doc[] = +PyDoc_STR("UTC implementation of tzinfo."); + +static PyTypeObject PyDateTime_UTCType = { + PyVarObject_HEAD_INIT(NULL, 0) + "datetime.timezone", /* tp_name */ + sizeof(PyDateTime_UTC), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)utc_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)utc_hash, /* 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 */ + (richcmpfunc)utc_richcompare, /* 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 */ + utc_new, /* tp_new */ +}; + /* * PyDateTime_Time implementation. */ @@ -4069,9 +4237,35 @@ * 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 *result; + PyObject *utc; + PyObject *tz_aware = Py_False; + int aware; + + static char *keywords[] = {"tz_aware", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|O:utcnow", + keywords, &tz_aware)) + return NULL; + aware = PyObject_IsTrue(tz_aware); + if (aware == -1) + return NULL; + if (aware) { + utc = new_utc(0, NULL); + if (utc == NULL) { + return NULL; + } + } + else { + utc = Py_None; + Py_INCREF(utc); + } + result = datetime_best_possible(cls, gmtime, utc); + Py_DECREF(utc); + + return result; } /* Return new local datetime from timestamp (Python timestamp -- a double). */ @@ -4797,8 +4991,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, @@ -4943,6 +5138,7 @@ &PyDateTime_TimeType, &PyDateTime_DeltaType, &PyDateTime_TZInfoType, + &PyDateTime_UTCType, new_date_ex, new_datetime_ex, new_time_ex, @@ -4986,6 +5182,8 @@ return NULL; if (PyType_Ready(&PyDateTime_TZInfoType) < 0) return NULL; + if (PyType_Ready(&PyDateTime_UTCType) < 0) + return; /* timedelta values */ d = PyDateTime_DeltaType.tp_dict; @@ -5079,6 +5277,9 @@ Py_INCREF(&PyDateTime_TZInfoType); PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); + Py_INCREF(&PyDateTime_UTCType); + PyModule_AddObject(m, "timezone", (PyObject *) &PyDateTime_UTCType); + x = PyCapsule_New(&CAPI, PyDateTime_CAPSULE_NAME, NULL); if (x == NULL) return NULL;