]> granicus.if.org Git - python/commitdiff
Issue #6641: The datetime.strptime method now supports the %z directive.
authorAlexander Belopolsky <alexander.belopolsky@gmail.com>
Thu, 17 Jun 2010 18:30:34 +0000 (18:30 +0000)
committerAlexander Belopolsky <alexander.belopolsky@gmail.com>
Thu, 17 Jun 2010 18:30:34 +0000 (18:30 +0000)
Doc/library/datetime.rst
Lib/_strptime.py
Lib/test/test_datetime.py
Misc/NEWS
Modules/datetimemodule.c

index cb55afb717633085725f06608c90d96c259b167d..ae73e162ec2f0c05b659484a9df829628c6df78d 100644 (file)
@@ -1760,3 +1760,10 @@ Notes:
 (5)
    For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``,
    ``%z`` is replaced with the string ``'-0330'``.
+
+.. versionadded:: 3.2
+
+   When the ``%z`` directive is provided to the :meth:`strptime`
+   method, an aware :class:`datetime` object will be produced.  The
+   ``tzinfo`` of the result will be set to a :class:`timezone`
+   instance.
\ No newline at end of file
index ee30b4216c25c8fa9825d8f13fc0ccdbefd15575..728a9dc855df5be373c965f688b0462ed29f7c4f 100644 (file)
@@ -16,7 +16,10 @@ import calendar
 from re import compile as re_compile
 from re import IGNORECASE, ASCII
 from re import escape as re_escape
-from datetime import date as datetime_date
+from datetime import (date as datetime_date,
+                      datetime as datetime_datetime,
+                      timedelta as datetime_timedelta,
+                      timezone as datetime_timezone)
 try:
     from _thread import allocate_lock as _thread_allocate_lock
 except:
@@ -204,6 +207,7 @@ class TimeRE(dict):
             #XXX: Does 'Y' need to worry about having less or more than
             #     4 digits?
             'Y': r"(?P<Y>\d\d\d\d)",
+            'z': r"(?P<z>[+-]\d\d[0-5]\d)",
             'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
             'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
             'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
@@ -293,7 +297,9 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
 
 
 def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
-    """Return a time struct based on the input string and the format string."""
+    """Return a 2-tuple consisting of a time struct and an int containg
+    the number of microseconds based on the input string and the
+    format string."""
 
     for index, arg in enumerate([data_string, format]):
         if not isinstance(arg, str):
@@ -333,10 +339,12 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
     if len(data_string) != found.end():
         raise ValueError("unconverted data remains: %s" %
                           data_string[found.end():])
+
     year = 1900
     month = day = 1
     hour = minute = second = fraction = 0
     tz = -1
+    tzoffset = None
     # Default to -1 to signify that values not known; not critical to have,
     # though
     week_of_year = -1
@@ -417,6 +425,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
             else:
                 # W starts week on Monday.
                 week_of_year_start = 0
+        elif group_key == 'z':
+            z = found_dict['z']
+            tzoffset = int(z[1:3]) * 60 + int(z[3:5])
+            if z.startswith("-"):
+                tzoffset = -tzoffset
         elif group_key == 'Z':
             # Since -1 is default value only need to worry about setting tz if
             # it can be something other than -1.
@@ -453,9 +466,35 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
         day = datetime_result.day
     if weekday == -1:
         weekday = datetime_date(year, month, day).weekday()
-    return (time.struct_time((year, month, day,
-                              hour, minute, second,
-                              weekday, julian, tz)), fraction)
+    # Add timezone info
+    tzname = found_dict.get("Z")
+    if tzoffset is not None:
+        gmtoff = tzoffset * 60
+    else:
+        gmtoff = None
+
+    return (year, month, day,
+            hour, minute, second,
+            weekday, julian, tz, gmtoff, tzname), fraction
 
 def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
-    return _strptime(data_string, format)[0]
+    """Return a time struct based on the input string and the
+    format string."""
+    tt = _strptime(data_string, format)[0]
+    return time.struct_time(tt[:9])
+
+def _strptime_datetime(data_string, format="%a %b %d %H:%M:%S %Y"):
+    """Return a datetime instace based on the input string and the
+    format string."""
+    tt, fraction = _strptime(data_string, format)
+    gmtoff, tzname = tt[-2:]
+    args = tt[:6] + (fraction,)
+    if gmtoff is not None:
+        tzdelta = datetime_timedelta(seconds=gmtoff)
+        if tzname:
+            tz = datetime_timezone(tzdelta, tzname)
+        else:
+            tz = datetime_timezone(tzdelta)
+        args += (tz,)
+
+    return datetime_datetime(*args)
index de7cf0f88efedc811ad87e4e6d292c970ed75845..b320e1f80292b81c082952dcd81f13d66a984f9e 100644 (file)
@@ -17,6 +17,7 @@ from datetime import tzinfo
 from datetime import time
 from datetime import timezone
 from datetime import date, datetime
+import time as _time
 
 pickle_choices = [(pickle, pickle, proto) for proto in range(3)]
 assert len(pickle_choices) == 3
@@ -1731,11 +1732,41 @@ class TestDateTime(TestDate):
 
         string = '2004-12-01 13:02:47.197'
         format = '%Y-%m-%d %H:%M:%S.%f'
-        result, frac = _strptime._strptime(string, format)
-        expected = self.theclass(*(result[0:6]+(frac,)))
+        expected = _strptime._strptime_datetime(string, format)
         got = self.theclass.strptime(string, format)
         self.assertEqual(expected, got)
 
+        strptime = self.theclass.strptime
+        self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
+        self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
+        # Only local timezone and UTC are supported
+        for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
+                                 (-_time.timezone, _time.tzname[0])):
+            if tzseconds < 0:
+                sign = '-'
+                seconds = -tzseconds
+            else:
+                sign ='+'
+                seconds = tzseconds
+            hours, minutes = divmod(seconds//60, 60)
+            dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
+            dt = strptime(dtstr, "%z %Z")
+            self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
+            self.assertEqual(dt.tzname(), tzname)
+        # Can produce inconsistent datetime
+        dtstr, fmt = "+1234 UTC", "%z %Z"
+        dt = strptime(dtstr, fmt)
+        self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
+        self.assertEqual(dt.tzname(), 'UTC')
+        # yet will roundtrip
+        self.assertEqual(dt.strftime(fmt), dtstr)
+
+        # Produce naive datetime if no %z is provided
+        self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
+
+        with self.assertRaises(ValueError): strptime("-2400", "%z")
+        with self.assertRaises(ValueError): strptime("-000", "%z")
+
     def test_more_timetuple(self):
         # This tests fields beyond those tested by the TestDate.test_timetuple.
         t = self.theclass(2004, 12, 31, 6, 22, 33)
@@ -3196,6 +3227,7 @@ def first_sunday_on_or_after(dt):
     return dt
 
 ZERO = timedelta(0)
+MINUTE = timedelta(minutes=1)
 HOUR = timedelta(hours=1)
 DAY = timedelta(days=1)
 # In the US, DST starts at 2am (standard time) on the first Sunday in April.
index 9c5ea77ff3717eeee8f81fb44f5191c029cf10e7..41ac42dd6b2bf1f8205a8c69cecfc595f7c5a703 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -1322,6 +1322,14 @@ Library
 Extension Modules
 -----------------
 
+- Issue #6641: The ``datetime.strptime`` method now supports the
+  ``%z`` directive.  When the ``%z`` directive is present in the
+  format string, an aware ``datetime`` object is returned with
+  ``tzinfo`` bound to a ``datetime.timezone`` instance constructed
+  from the parsed offset.  If both ``%z`` and ``%Z`` are present, the
+  data in ``%Z`` field is used for timezone name, but ``%Z`` data
+  without ``%z`` is discarded.
+
 - Issue #5094: The ``datetime`` module now has a simple concrete class
   implementing ``datetime.tzinfo`` interface.  Instances of the new
   class, ``datetime.timezone``, return fixed name and UTC offset from
index 71c5cf5d557ba7e4ae95ece4c24eee68c03399d9..ed045205fe795bdd250e1c860feb2a6be0b5bf61 100644 (file)
@@ -4362,82 +4362,23 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
     return result;
 }
 
-/* Return new datetime from time.strptime(). */
+/* Return new datetime from _strptime.strptime_datetime(). */
 static PyObject *
 datetime_strptime(PyObject *cls, PyObject *args)
 {
     static PyObject *module = NULL;
-    PyObject *result = NULL, *obj, *st = NULL, *frac = NULL;
     const Py_UNICODE *string, *format;
 
     if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format))
         return NULL;
 
-    if (module == NULL &&
-        (module = PyImport_ImportModuleNoBlock("_strptime")) == NULL)
-        return NULL;
-
-    /* _strptime._strptime returns a two-element tuple.  The first
-       element is a time.struct_time object.  The second is the
-       microseconds (which are not defined for time.struct_time). */
-    obj = PyObject_CallMethod(module, "_strptime", "uu", string, format);
-    if (obj != NULL) {
-        int i, good_timetuple = 1;
-        long int ia[7];
-        if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
-            st = PySequence_GetItem(obj, 0);
-            frac = PySequence_GetItem(obj, 1);
-            if (st == NULL || frac == NULL)
-                good_timetuple = 0;
-            /* copy y/m/d/h/m/s values out of the
-               time.struct_time */
-            if (good_timetuple &&
-                PySequence_Check(st) &&
-                PySequence_Size(st) >= 6) {
-                for (i=0; i < 6; i++) {
-                    PyObject *p = PySequence_GetItem(st, i);
-                    if (p == NULL) {
-                        good_timetuple = 0;
-                        break;
-                    }
-                    if (PyLong_Check(p))
-                        ia[i] = PyLong_AsLong(p);
-                    else
-                        good_timetuple = 0;
-                    Py_DECREF(p);
-                }
-/*                              if (PyLong_CheckExact(p)) {
-                                        ia[i] = PyLong_AsLongAndOverflow(p, &overflow);
-                                        if (overflow)
-                                                good_timetuple = 0;
-                                }
-                                else
-                                        good_timetuple = 0;
-                                Py_DECREF(p);
-*/                      }
-                        else
-                                good_timetuple = 0;
-                        /* follow that up with a little dose of microseconds */
-            if (PyLong_Check(frac))
-                ia[6] = PyLong_AsLong(frac);
-            else
-                good_timetuple = 0;
-        }
-        else
-            good_timetuple = 0;
-        if (good_timetuple)
-            result = PyObject_CallFunction(cls, "iiiiiii",
-                                           ia[0], ia[1], ia[2],
-                                           ia[3], ia[4], ia[5],
-                                           ia[6]);
-        else
-            PyErr_SetString(PyExc_ValueError,
-                "unexpected value from _strptime._strptime");
+    if (module == NULL) {
+        module = PyImport_ImportModuleNoBlock("_strptime");
+        if(module == NULL)
+            return NULL;
     }
-    Py_XDECREF(obj);
-    Py_XDECREF(st);
-    Py_XDECREF(frac);
-    return result;
+    return PyObject_CallMethod(module, "_strptime_datetime", "uu",
+                               string, format);
 }
 
 /* Return new datetime from date/datetime and time arguments. */