summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/datetime.rst11
-rw-r--r--Lib/datetime.py28
-rw-r--r--Lib/test/datetimetester.py21
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/_datetimemodule.c86
5 files changed, 138 insertions, 12 deletions
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index 5c2d3d9..85bbb89 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -958,17 +958,22 @@ Instance methods:
datetime with no conversion of date and time data.
-.. method:: datetime.astimezone(tz)
+.. method:: datetime.astimezone(tz=None)
- Return a :class:`.datetime` object with new :attr:`tzinfo` attribute *tz*,
+ Return a :class:`datetime` object with new :attr:`tzinfo` attribute *tz*,
adjusting the date and time data so the result is the same UTC time as
*self*, but in *tz*'s local time.
- *tz* must be an instance of a :class:`tzinfo` subclass, and its
+ If provided, *tz* must be an instance of a :class:`tzinfo` subclass, and its
:meth:`utcoffset` and :meth:`dst` methods must not return ``None``. *self* must
be aware (``self.tzinfo`` must not be ``None``, and ``self.utcoffset()`` must
not return ``None``).
+ If called without arguments (or with ``tz=None``) the system local
+ timezone is assumed. The ``tzinfo`` attribute of the converted
+ datetime instance will be set to an instance of :class:`timezone`
+ with the zone name and offset obtained from the OS.
+
If ``self.tzinfo`` is *tz*, ``self.astimezone(tz)`` is equal to *self*: no
adjustment of date or time data is performed. Else the result is local
time in time zone *tz*, representing the same UTC time as *self*: after
diff --git a/Lib/datetime.py b/Lib/datetime.py
index 6ab2499..ce88d85 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1493,8 +1493,32 @@ class datetime(date):
return datetime(year, month, day, hour, minute, second,
microsecond, tzinfo)
- def astimezone(self, tz):
- if not isinstance(tz, tzinfo):
+ def astimezone(self, tz=None):
+ if tz is None:
+ if self.tzinfo is None:
+ raise ValueError("astimezone() requires an aware datetime")
+ ts = (self - _EPOCH) // timedelta(seconds=1)
+ localtm = _time.localtime(ts)
+ local = datetime(*localtm[:6])
+ try:
+ # Extract TZ data if available
+ gmtoff = localtm.tm_gmtoff
+ zone = localtm.tm_zone
+ except AttributeError:
+ # Compute UTC offset and compare with the value implied
+ # by tm_isdst. If the values match, use the zone name
+ # implied by tm_isdst.
+ delta = local - datetime(*_time.gmtime(ts)[:6])
+ dst = _time.daylight and localtm.tm_isdst > 0
+ gmtoff = _time.altzone if dst else _time.timezone
+ if delta == timedelta(seconds=-gmtoff):
+ tz = timezone(delta, _time.tzname[dst])
+ else:
+ tz = timezone(delta)
+ else:
+ tz = timezone(timedelta(seconds=-gmtoff), zone)
+
+ elif not isinstance(tz, tzinfo):
raise TypeError("tz argument must be an instance of tzinfo")
mytz = self.tzinfo
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 048d63c..e045447 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -1972,7 +1972,7 @@ class TestDateTime(TestDate):
# simply can't be applied to a naive object.
dt = self.theclass.now()
f = FixedOffset(44, "")
- self.assertRaises(TypeError, dt.astimezone) # not enough args
+ self.assertRaises(ValueError, dt.astimezone) # naive
self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
self.assertRaises(ValueError, dt.astimezone, f) # naive
@@ -3253,8 +3253,6 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
self.assertTrue(dt.tzinfo is f44m)
# Replacing with degenerate tzinfo raises an exception.
self.assertRaises(ValueError, dt.astimezone, fnone)
- # Ditto with None tz.
- self.assertRaises(TypeError, dt.astimezone, None)
# Replacing with same tzinfo makes no change.
x = dt.astimezone(dt.tzinfo)
self.assertTrue(x.tzinfo is f44m)
@@ -3274,6 +3272,23 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
self.assertTrue(got.tzinfo is expected.tzinfo)
self.assertEqual(got, expected)
+ @support.run_with_tz('UTC')
+ def test_astimezone_default_utc(self):
+ dt = self.theclass.now(timezone.utc)
+ self.assertEqual(dt.astimezone(None), dt)
+ self.assertEqual(dt.astimezone(), dt)
+
+ @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
+ def test_astimezone_default_eastern(self):
+ dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
+ local = dt.astimezone()
+ self.assertEqual(dt, local)
+ self.assertEqual(local.strftime("%z %Z"), "+0500 EST")
+ dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
+ local = dt.astimezone()
+ self.assertEqual(dt, local)
+ self.assertEqual(local.strftime("%z %Z"), "+0400 EDT")
+
def test_aware_subtract(self):
cls = self.theclass
diff --git a/Misc/NEWS b/Misc/NEWS
index 8abe981..8b7e3be 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -40,6 +40,10 @@ Core and Builtins
Library
-------
+- Issue #9527: datetime.astimezone() method will now supply a class
+ timezone instance corresponding to the system local timezone when
+ called with no arguments.
+
- Issue #14653: email.utils.mktime_tz() no longer relies on system
mktime() when timezone offest is supplied.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index d3a502d..f28d03c 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -4686,17 +4686,87 @@ datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
}
static PyObject *
+local_timezone(PyObject *utc_time)
+{
+ PyObject *result = NULL;
+ struct tm *timep;
+ time_t timestamp;
+ long offset;
+ PyObject *delta;
+ PyObject *one_second;
+ PyObject *seconds;
+ PyObject *nameo = NULL;
+ const char *zone = NULL;
+
+ delta = datetime_subtract((PyObject *)utc_time, PyDateTime_Epoch);
+ if (delta == NULL)
+ return NULL;
+ one_second = new_delta(0, 1, 0, 0);
+ if (one_second == NULL)
+ goto error;
+ seconds = divide_timedelta_timedelta((PyDateTime_Delta *)delta,
+ (PyDateTime_Delta *)one_second);
+ Py_DECREF(one_second);
+ if (seconds == NULL)
+ goto error;
+ Py_DECREF(delta);
+ timestamp = PyLong_AsLong(seconds);
+ Py_DECREF(seconds);
+ if (timestamp == -1 && PyErr_Occurred())
+ return NULL;
+ timep = localtime(&timestamp);
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ offset = timep->tm_gmtoff;
+ zone = timep->tm_zone;
+ delta = new_delta(0, -offset, 0, 0);
+#else /* HAVE_STRUCT_TM_TM_ZONE */
+ {
+ PyObject *local_time;
+ Py_INCREF(utc_time->tzinfo);
+ local_time = new_datetime(timep->tm_year + 1900, timep->tm_mon + 1,
+ timep->tm_mday, timep->tm_hour, timep->tm_min,
+ timep->tm_sec, utc_time->tzinfo);
+ if (local_time == NULL) {
+ Py_DECREF(utc_time->tzinfo);
+ goto error;
+ }
+ delta = datetime_subtract(local_time, utc_time);
+ /* XXX: before relying on tzname, we should compare delta
+ to the offset implied by timezone/altzone */
+ if (daylight && timep->tm_isdst >= 0)
+ zone = tzname[timep->tm_isdst % 2];
+ else
+ zone = tzname[0];
+ Py_DECREF(local_time);
+ }
+#endif /* HAVE_STRUCT_TM_TM_ZONE */
+ if (zone != NULL) {
+ nameo = PyUnicode_DecodeLocale(zone, "surrogateescape");
+ if (nameo == NULL)
+ goto error;
+ }
+ result = new_timezone(delta, nameo);
+ Py_DECREF(nameo);
+ error:
+ Py_DECREF(delta);
+ return result;
+}
+
+static PyObject *
datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
{
PyObject *result;
PyObject *offset;
PyObject *temp;
- PyObject *tzinfo;
+ PyObject *tzinfo = Py_None;
_Py_IDENTIFIER(fromutc);
static char *keywords[] = {"tz", NULL};
- if (! PyArg_ParseTupleAndKeywords(args, kw, "O!:astimezone", keywords,
- &PyDateTime_TZInfoType, &tzinfo))
+ if (! PyArg_ParseTupleAndKeywords(args, kw, "|O:astimezone", keywords,
+ &tzinfo))
+ return NULL;
+
+ if (check_tzinfo_subclass(tzinfo) == -1)
return NULL;
if (!HASTZINFO(self) || self->tzinfo == Py_None)
@@ -4729,8 +4799,16 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
/* Attach new tzinfo and let fromutc() do the rest. */
temp = ((PyDateTime_DateTime *)result)->tzinfo;
+ if (tzinfo == Py_None) {
+ tzinfo = local_timezone(result);
+ if (tzinfo == NULL) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ }
+ else
+ Py_INCREF(tzinfo);
((PyDateTime_DateTime *)result)->tzinfo = tzinfo;
- Py_INCREF(tzinfo);
Py_DECREF(temp);
temp = result;