]> granicus.if.org Git - python/commitdiff
Implemented datetime.astimezone() and datetimetz.astimezone().
authorTim Peters <tim.peters@gmail.com>
Wed, 25 Dec 2002 07:40:55 +0000 (07:40 +0000)
committerTim Peters <tim.peters@gmail.com>
Wed, 25 Dec 2002 07:40:55 +0000 (07:40 +0000)
Doc/lib/libdatetime.tex
Lib/test/test_datetime.py
Modules/datetimemodule.c

index f29db5b7b3fc6e8df447bbe88d70ee052aacf31a..1adbc8e863cc60abace76b5192c4432b5c270e07 100644 (file)
@@ -601,6 +601,11 @@ Instance methods:
     Return a datetime with the same value, except for those fields given
     new values by whichever keyword arguments are specified.
 
+  - astimezone(tz)
+    Return a \class{datetimetz} with the same date and time fields, and
+    with \member{tzinfo} member \var{tz}.  \var{tz} must be an instance
+    of a \class{tzinfo} subclass.
+
   - timetuple()
     Return a 9-element tuple of the form returned by
     \function{time.localtime()}.
@@ -1083,6 +1088,23 @@ Instance methods:
     \code{tzinfo=None} can be specified to create a naive datetimetz from
     an aware datetimetz.
 
+  - astimezone(tz)
+    Return a \class{datetimetz} with new tzinfo member \var{tz}.  \var{tz}
+    must be an instance of a \class{tzinfo} subclass.  If self is naive, or
+    if \code(tz.utcoffset(self)} returns \code{None},
+    \code{self.astimezone(tz)} is equivalent to
+    \code{self.replace(tzinfo=tz)}:  a new timezone object is attached
+    without any conversion of date or time fields.  If self is aware and
+    \code{tz.utcoffset(self)} does not return \code{None}, the date and
+    time fields are adjusted so that the result is local time in timezone
+    tz, representing the same UTC time as self.  \code{self.astimezone(tz)}
+    is then equivalent to
+    \begin{verbatim}
+        (self - (self.utcoffset() - tz.utcoffset(self)).replace(tzinfo=tz)
+    \end{verbatim}
+    where the result of \code{tz.uctcoffset(self)} is converted to a
+    \class{timedelta} if it's an integer.
+
   - utcoffset()
     If \member{tzinfo} is \code{None}, returns \code{None}, else
     \code{tzinfo.utcoffset(self)} converted to a \class{timedelta}
index 25c771622505478b0a379373d5e453af949db29e..de0d17ab096f6b0dd064ca8bdf49913bedac5dd3 100644 (file)
@@ -1295,6 +1295,21 @@ class TestDateTime(TestDate):
         base = cls(2000, 2, 29)
         self.assertRaises(ValueError, base.replace, year=2001)
 
+    def test_astimezone(self):
+        # Pretty boring for a datetime!  datetimetz is more interesting here.
+        dt = self.theclass.now()
+        f = FixedOffset(44, "")
+        for dtz in dt.astimezone(f), dt.astimezone(tz=f):
+            self.failUnless(isinstance(dtz, datetimetz))
+            self.assertEqual(dt.date(), dtz.date())
+            self.assertEqual(dt.time(), dtz.time())
+            self.failUnless(dtz.tzinfo is f)
+            self.assertEqual(dtz.utcoffset(), timedelta(minutes=44))
+
+        self.assertRaises(TypeError, dt.astimezone) # not enough args
+        self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
+        self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
+
 
 class TestTime(unittest.TestCase):
 
@@ -2308,6 +2323,44 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
         base = cls(2000, 2, 29)
         self.assertRaises(ValueError, base.replace, year=2001)
 
+    def test_more_astimezone(self):
+        # The inherited test_astimezone covered some trivial and error cases.
+        fnone = FixedOffset(None, "None")
+        f44m = FixedOffset(44, "44")
+        fm5h = FixedOffset(-timedelta(hours=5), "m300")
+
+        dt = self.theclass.now(tzinfo=f44m)
+        self.failUnless(dt.tzinfo is f44m)
+        # Replacing with degenerate tzinfo doesn't do any adjustment.
+        for x in dt.astimezone(fnone), dt.astimezone(tz=fnone):
+            self.failUnless(x.tzinfo is fnone)
+            self.assertEqual(x.date(), dt.date())
+            self.assertEqual(x.time(), dt.time())
+        # Ditt with None tz.
+        x = dt.astimezone(tz=None)
+        self.failUnless(x.tzinfo is None)
+        self.assertEqual(x.date(), dt.date())
+        self.assertEqual(x.time(), dt.time())
+        # Ditto replacing with same tzinfo.
+        x = dt.astimezone(dt.tzinfo)
+        self.failUnless(x.tzinfo is f44m)
+        self.assertEqual(x.date(), dt.date())
+        self.assertEqual(x.time(), dt.time())
+
+        # Replacing with different tzinfo does adjust.
+        got = dt.astimezone(fm5h)
+        self.failUnless(got.tzinfo is fm5h)
+        self.assertEqual(got.utcoffset(), timedelta(hours=-5))
+        expected = dt - dt.utcoffset()  # in effect, convert to UTC
+        expected += fm5h.utcoffset(dt)  # and from there to local time
+        expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
+        self.assertEqual(got.date(), expected.date())
+        self.assertEqual(got.time(), expected.time())
+        self.assertEqual(got.timetz(), expected.timetz())
+        self.failUnless(got.tzinfo is expected.tzinfo)
+        self.assertEqual(got, expected)
+
+
 def test_suite():
     allsuites = [unittest.makeSuite(klass, 'test')
                  for klass in (TestModule,
index 6fd10ed558d4e2eebc7284a2f6e998c202a8eb43..d7c6005acd1dfb816023c4ff1df84d18fe99f124 100644 (file)
@@ -600,6 +600,18 @@ get_tzinfo_member(PyObject *self)
        return tzinfo;
 }
 
+/* self is a datetimetz.  Replace its tzinfo member. */
+void
+replace_tzinfo(PyObject *self, PyObject *newtzinfo)
+{
+       assert(self != NULL);
+       assert(PyDateTimeTZ_Check(self));
+       assert(check_tzinfo_subclass(newtzinfo) >= 0);
+       Py_INCREF(newtzinfo);
+       Py_DECREF(((PyDateTime_DateTimeTZ *)self)->tzinfo);
+       ((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo;
+}
+
 /* Internal helper.
  * Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the
  * result.  tzinfo must be an instance of the tzinfo class.  If the method
@@ -2915,10 +2927,7 @@ datetime_combine(PyObject *cls, PyObject *args, PyObject *kw)
                                                TIME_GET_MICROSECOND(time));
        if (result && PyTimeTZ_Check(time) && PyDateTimeTZ_Check(result)) {
                /* Copy the tzinfo field. */
-               PyObject *tzinfo = ((PyDateTime_TimeTZ *)time)->tzinfo;
-               Py_INCREF(tzinfo);
-               Py_DECREF(((PyDateTime_DateTimeTZ *)result)->tzinfo);
-               ((PyDateTime_DateTimeTZ *)result)->tzinfo = tzinfo;
+               replace_tzinfo(result, ((PyDateTime_TimeTZ *)time)->tzinfo);
        }
        return result;
 }
@@ -3246,6 +3255,24 @@ datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
        return clone;
 }
 
+static PyObject *
+datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
+{
+       PyObject *tzinfo;
+       static char *keywords[] = {"tz", NULL};
+
+       if (! PyArg_ParseTupleAndKeywords(args, kw, "O:astimezone", keywords,
+                                         &tzinfo))
+               return NULL;
+       if (check_tzinfo_subclass(tzinfo) < 0)
+               return NULL;
+       return new_datetimetz(GET_YEAR(self), GET_MONTH(self), GET_DAY(self),
+                             DATE_GET_HOUR(self), DATE_GET_MINUTE(self),
+                             DATE_GET_SECOND(self),
+                             DATE_GET_MICROSECOND(self),
+                             tzinfo);
+}
+
 static PyObject *
 datetime_timetuple(PyDateTime_DateTime *self)
 {
@@ -3397,6 +3424,9 @@ static PyMethodDef datetime_methods[] = {
        {"replace",     (PyCFunction)datetime_replace,  METH_KEYWORDS,
         PyDoc_STR("Return datetime with new specified fields.")},
 
+       {"astimezone",  (PyCFunction)datetime_astimezone, METH_KEYWORDS,
+        PyDoc_STR("tz -> datetimetz with same date & time, and tzinfo=tz\n")},
+
        {"__setstate__", (PyCFunction)datetime_setstate, METH_O,
         PyDoc_STR("__setstate__(state)")},
 
@@ -4398,20 +4428,6 @@ static PyGetSetDef datetimetz_getset[] = {
  * optional tzinfo argument.
  */
 
-/* Internal helper.
- * self is a datetimetz.  Replace its tzinfo member.
- */
-void
-replace_tzinfo(PyObject *self, PyObject *newtzinfo)
-{
-       assert(self != NULL);
-       assert(newtzinfo != NULL);
-       assert(PyDateTimeTZ_Check(self));
-       Py_INCREF(newtzinfo);
-       Py_DECREF(((PyDateTime_DateTimeTZ *)self)->tzinfo);
-       ((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo;
-}
-
 static char *datetimetz_kws[] = {
        "year", "month", "day", "hour", "minute", "second",
        "microsecond", "tzinfo", NULL
@@ -4696,6 +4712,53 @@ datetimetz_replace(PyDateTime_DateTimeTZ *self, PyObject *args, PyObject *kw)
        return clone;
 }
 
+static PyObject *
+datetimetz_astimezone(PyDateTime_DateTimeTZ *self, PyObject *args,
+                     PyObject *kw)
+{
+       int y = GET_YEAR(self);
+       int m = GET_MONTH(self);
+       int d = GET_DAY(self);
+       int hh = DATE_GET_HOUR(self);
+       int mm = DATE_GET_MINUTE(self);
+       int ss = DATE_GET_SECOND(self);
+       int us = DATE_GET_MICROSECOND(self);
+
+       PyObject *tzinfo;
+       static char *keywords[] = {"tz", NULL};
+
+       if (! PyArg_ParseTupleAndKeywords(args, kw, "O:astimezone", keywords,
+                                         &tzinfo))
+               return NULL;
+       if (check_tzinfo_subclass(tzinfo) < 0)
+               return NULL;
+
+       if (tzinfo != Py_None && self->tzinfo != Py_None) {
+               int none;
+               int selfoffset;
+               selfoffset = call_utcoffset(self->tzinfo,
+                                           (PyObject *)self,
+                                           &none);
+               if (selfoffset == -1 && PyErr_Occurred())
+                       return NULL;
+               if (! none) {
+                       int tzoffset;
+                       tzoffset = call_utcoffset(tzinfo,
+                                                 (PyObject *)self,
+                                                 &none);
+                       if (tzoffset == -1 && PyErr_Occurred())
+                               return NULL;
+                       if (! none) {
+                               mm -= selfoffset - tzoffset;
+                               if (normalize_datetime(&y, &m, &d,
+                                                      &hh, &mm, &ss, &us) < 0)
+                                       return NULL;
+                       }
+               }
+       }
+       return new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+}
+
 static PyObject *
 datetimetz_timetuple(PyDateTime_DateTimeTZ *self)
 {
@@ -4908,6 +4971,9 @@ static PyMethodDef datetimetz_methods[] = {
        {"replace",     (PyCFunction)datetimetz_replace,        METH_KEYWORDS,
         PyDoc_STR("Return datetimetz with new specified fields.")},
 
+       {"astimezone",  (PyCFunction)datetimetz_astimezone, METH_KEYWORDS,
+        PyDoc_STR("tz -> convert to local time in new timezone tz\n")},
+
        {"__setstate__", (PyCFunction)datetimetz_setstate, METH_O,
         PyDoc_STR("__setstate__(state)")},