mirror of https://github.com/python/cpython
Issue #9527: datetime.astimezone() method will now supply a class
timezone instance corresponding to the system local timezone when called with no arguments.
This commit is contained in:
parent
8f904daee9
commit
fdc860f310
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -4685,18 +4685,88 @@ datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
|
|||
return clone;
|
||||
}
|
||||
|
||||
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(×tamp);
|
||||
#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;
|
||||
((PyDateTime_DateTime *)result)->tzinfo = 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_DECREF(temp);
|
||||
|
||||
temp = result;
|
||||
|
|
Loading…
Reference in New Issue