[Python-checkins] r87829 - in python/branches/py3k: Doc/library/time.rst Lib/test/test_time.py Misc/NEWS Modules/timemodule.c

alexander.belopolsky python-checkins at python.org
Fri Jan 7 20:59:19 CET 2011


Author: alexander.belopolsky
Date: Fri Jan  7 20:59:19 2011
New Revision: 87829

Log:
Issue #10827: Changed the rules for 2-digit years.  The time.asctime
function will now format any year when time.accept2dyear is false and
will accept years >= 1000 otherwise.  The year range accepted by
time.mktime and time.strftime is still system dependent, but
time.mktime will now accept full range supported by the OS. Conversion
of 2-digit years to 4-digit is deprecated.


Modified:
   python/branches/py3k/Doc/library/time.rst
   python/branches/py3k/Lib/test/test_time.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Modules/timemodule.c

Modified: python/branches/py3k/Doc/library/time.rst
==============================================================================
--- python/branches/py3k/Doc/library/time.rst	(original)
+++ python/branches/py3k/Doc/library/time.rst	Fri Jan  7 20:59:19 2011
@@ -24,9 +24,9 @@
 
 .. index:: single: Year 2038
 
-* The functions in this module do not handle dates and times before the epoch or
+* The functions in this module may not handle dates and times before the epoch or
   far in the future.  The cut-off point in the future is determined by the C
-  library; for Unix, it is typically in 2038.
+  library; for 32-bit systems, it is typically in 2038.
 
 .. index::
    single: Year 2000
@@ -34,20 +34,31 @@
 
 .. _time-y2kissues:
 
-* **Year 2000 (Y2K) issues**:  Python depends on the platform's C library, which
+* **Year 2000 (Y2K) issues**: Python depends on the platform's C library, which
   generally doesn't have year 2000 issues, since all dates and times are
-  represented internally as seconds since the epoch.  Functions accepting a
-  :class:`struct_time` (see below) generally require a 4-digit year.  For backward
-  compatibility, 2-digit years are supported if the module variable
-  ``accept2dyear`` is a non-zero integer; this variable is initialized to ``1``
-  unless the environment variable :envvar:`PYTHONY2K` is set to a non-empty
-  string, in which case it is initialized to ``0``.  Thus, you can set
-  :envvar:`PYTHONY2K` to a non-empty string in the environment to require 4-digit
-  years for all year input.  When 2-digit years are accepted, they are converted
-  according to the POSIX or X/Open standard: values 69-99 are mapped to 1969-1999,
-  and values 0--68 are mapped to 2000--2068. Values 100--1899 are always illegal.
-  Note that this is new as of Python 1.5.2(a2); earlier versions, up to Python
-  1.5.1 and 1.5.2a1, would add 1900 to year values below 1900.
+  represented internally as seconds since the epoch.  Function :func:`strptime`
+  can parse 2-digit years when given ``%y`` format code.  When 2-digit years are
+  parsed, they are converted according to the POSIX and ISO C standards: values
+  69--99 are mapped to 1969--1999, and values 0--68 are mapped to 2000--2068.
+
+  For backward compatibility, years with less than 4 digits are treated
+  specially by :func:`asctime`, :func:`mktime`, and :func:`strftime` functions
+  that operate on a 9-tuple or :class:`struct_time` values. If year (the first
+  value in the 9-tuple) is specified with less than 4 digits, its interpretation
+  depends on the value of ``accept2dyear`` variable.
+
+  If ``accept2dyear`` is true (default), a backward compatibility behavior is
+  invoked as follows:
+
+    - for 2-digit year, century is guessed according to POSIX rules for
+      ``%y`` strptime format.  A deprecation warning is issued when century
+      information is guessed in this way.
+
+    - for 3-digit or negative year, a :exc:`ValueError` exception is raised.
+
+  If ``accept2dyear`` is false (set by the program or as a result of a
+  non-empty value assigned to ``PYTHONY2K`` environment variable) all year
+  values are interpreted as given.
 
 .. index::
    single: UTC

Modified: python/branches/py3k/Lib/test/test_time.py
==============================================================================
--- python/branches/py3k/Lib/test/test_time.py	(original)
+++ python/branches/py3k/Lib/test/test_time.py	Fri Jan  7 20:59:19 2011
@@ -3,6 +3,7 @@
 import unittest
 import locale
 import sysconfig
+import warnings
 
 class TimeTestCase(unittest.TestCase):
 
@@ -19,10 +20,10 @@
         time.clock()
 
     def test_conversions(self):
-        self.assertTrue(time.ctime(self.t)
-                     == time.asctime(time.localtime(self.t)))
-        self.assertTrue(int(time.mktime(time.localtime(self.t)))
-                     == int(self.t))
+        self.assertEqual(time.ctime(self.t),
+                         time.asctime(time.localtime(self.t)))
+        self.assertEqual(int(time.mktime(time.localtime(self.t))),
+                         int(self.t))
 
     def test_sleep(self):
         time.sleep(1.2)
@@ -44,7 +45,7 @@
 
         # Check year [1900, max(int)]
         self.assertRaises(ValueError, func,
-                            (1899, 1, 1, 0, 0, 0, 0, 1, -1))
+                            (999, 1, 1, 0, 0, 0, 0, 1, -1))
         if time.accept2dyear:
             self.assertRaises(ValueError, func,
                                 (-1, 1, 1, 0, 0, 0, 0, 1, -1))
@@ -97,7 +98,8 @@
         # No test for daylight savings since strftime() does not change output
         # based on its value.
         expected = "2000 01 01 00 00 00 1 001"
-        result = time.strftime("%Y %m %d %H %M %S %w %j", (0,)*9)
+        with support.check_warnings():
+            result = time.strftime("%Y %m %d %H %M %S %w %j", (0,)*9)
         self.assertEqual(expected, result)
 
     def test_strptime(self):
@@ -141,14 +143,15 @@
         self.assertEqual(time.ctime(t), 'Sun Sep 16 01:03:52 1973')
         t = time.mktime((2000, 1, 1, 0, 0, 0, 0, 0, -1))
         self.assertEqual(time.ctime(t), 'Sat Jan  1 00:00:00 2000')
-        try:
-            bigval = time.mktime((10000, 1, 10) + (0,)*6)
-        except (ValueError, OverflowError):
-            # If mktime fails, ctime will fail too.  This may happen
-            # on some platforms.
-            pass
-        else:
-            self.assertEqual(time.ctime(bigval)[-5:], '10000')
+        for year in [-100, 100, 1000, 2000, 10000]:
+            try:
+                testval = time.mktime((year, 1, 10) + (0,)*6)
+            except (ValueError, OverflowError):
+                # If mktime fails, ctime will fail too.  This may happen
+                # on some platforms.
+                pass
+            else:
+                self.assertEqual(time.ctime(testval)[20:], str(year))
 
     @unittest.skipIf(not hasattr(time, "tzset"),
         "time module has no attribute tzset")
@@ -239,14 +242,14 @@
         gt1 = time.gmtime(None)
         t0 = time.mktime(gt0)
         t1 = time.mktime(gt1)
-        self.assertTrue(0 <= (t1-t0) < 0.2)
+        self.assertAlmostEqual(t1, t0, delta=0.2)
 
     def test_localtime_without_arg(self):
         lt0 = time.localtime()
         lt1 = time.localtime(None)
         t0 = time.mktime(lt0)
         t1 = time.mktime(lt1)
-        self.assertTrue(0 <= (t1-t0) < 0.2)
+        self.assertAlmostEqual(t1, t0, delta=0.2)
 
 class TestLocale(unittest.TestCase):
     def setUp(self):
@@ -274,16 +277,18 @@
         time.accept2dyear = self.saved_accept2dyear
 
     def yearstr(self, y):
-        return time.strftime('%Y', (y,) + (0,) * 8)
+        # return time.strftime('%Y', (y,) + (0,) * 8)
+        return time.asctime((y,) + (0,) * 8).split()[-1]
 
     def test_2dyear(self):
-        self.assertEqual(self.yearstr(0), '2000')
-        self.assertEqual(self.yearstr(69), '1969')
-        self.assertEqual(self.yearstr(68), '2068')
-        self.assertEqual(self.yearstr(99), '1999')
+        with support.check_warnings():
+            self.assertEqual(self.yearstr(0), '2000')
+            self.assertEqual(self.yearstr(69), '1969')
+            self.assertEqual(self.yearstr(68), '2068')
+            self.assertEqual(self.yearstr(99), '1999')
 
     def test_invalid(self):
-        self.assertRaises(ValueError, self.yearstr, 1899)
+        self.assertRaises(ValueError, self.yearstr, 999)
         self.assertRaises(ValueError, self.yearstr, 100)
         self.assertRaises(ValueError, self.yearstr, -1)
 
@@ -293,10 +298,15 @@
 class TestDontAccept2Year(TestAccept2Year):
     accept2dyear = 0
     def test_2dyear(self):
-        self.assertRaises(ValueError, self.yearstr, 0)
-        self.assertRaises(ValueError, self.yearstr, 69)
-        self.assertRaises(ValueError, self.yearstr, 68)
-        self.assertRaises(ValueError, self.yearstr, 99)
+        self.assertEqual(self.yearstr(0), '0')
+        self.assertEqual(self.yearstr(69), '69')
+        self.assertEqual(self.yearstr(68), '68')
+        self.assertEqual(self.yearstr(99), '99')
+        self.assertEqual(self.yearstr(999), '999')
+        self.assertEqual(self.yearstr(9999), '9999')
+
+    def test_invalid(self):
+        pass
 
 class TestDontAccept2YearBool(TestDontAccept2Year):
     accept2dyear = False

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Fri Jan  7 20:59:19 2011
@@ -36,6 +36,14 @@
 Library
 -------
 
+- Issue #10827: Changed the rules for 2-digit years.  The time.asctime
+  function will now format any year when ``time.accept2dyear`` is
+  false and will accept years >= 1000 otherwise.  The year range
+  accepted by ``time.mktime`` and ``time.strftime`` is still system
+  dependent, but ``time.mktime`` will now accept full range supported
+  by the OS.  Conversion of 2-digit years to 4-digit is deprecated.
+
+
 - Issue #7858: Raise an error properly when os.utime() fails under Windows
   on an existing file.
 

Modified: python/branches/py3k/Modules/timemodule.c
==============================================================================
--- python/branches/py3k/Modules/timemodule.c	(original)
+++ python/branches/py3k/Modules/timemodule.c	Fri Jan  7 20:59:19 2011
@@ -312,34 +312,42 @@
                           &p->tm_wday, &p->tm_yday, &p->tm_isdst))
         return 0;
 
-    /* XXX: Why 1900?  If the goal is to interpret 2-digit years as those in
-     * 20th / 21st century according to the POSIX standard, we can just treat
-     * 0 <= y < 100 as special.  Year 100 is probably too ambiguous and should
-     * be rejected, but years 101 through 1899 can be passed through.
+    /* If year is specified with less than 4 digits, its interpretation
+     * depends on the accept2dyear value.
+     *
+     * If accept2dyear is true (default), a backward compatibility behavior is
+     * invoked as follows:
+     *
+     *   - for 2-digit year, century is guessed according to POSIX rules for
+     *      %y strptime format: 21st century for y < 69, 20th century
+     *      otherwise.  A deprecation warning is issued when century
+     *      information is guessed in this way.
+     *
+     *   - for 3-digit or negative year, a ValueError exception is raised.
+     *
+     * If accept2dyear is false (set by the program or as a result of a
+     * non-empty value assigned to PYTHONY2K environment variable) all year
+     * values are interpreted as given.
      */
-    if (y < 1900) {
+    if (y < 1000) {
         PyObject *accept = PyDict_GetItemString(moddict,
                                                 "accept2dyear");
         int acceptval = accept != NULL && PyObject_IsTrue(accept);
         if (acceptval == -1)
             return 0;
         if (acceptval) {
-            if (69 <= y && y <= 99)
-                y += 1900;
-            else if (0 <= y && y <= 68)
+            if (0 <= y && y < 69)
                 y += 2000;
+            else if (69 <= y && y < 100)
+                y += 1900;
             else {
                 PyErr_SetString(PyExc_ValueError,
                                 "year out of range");
                 return 0;
             }
-        }
-        /* XXX: When accept2dyear is false, we don't have to reject y < 1900.
-         * Consider removing the following else-clause. */
-        else {
-            PyErr_SetString(PyExc_ValueError,
-                            "year out of range");
-            return 0;
+            if (PyErr_WarnEx(PyExc_DeprecationWarning,
+                    "Century info guessed for a 2-digit year.", 1) != 0)
+                return 0;
         }
     }
     p->tm_year = y - 1900;
@@ -462,6 +470,15 @@
     else if (!gettmarg(tup, &buf) || !checktm(&buf))
         return NULL;
 
+    /* XXX: Reportedly, some systems have issues formating dates prior to year
+     * 1900.  These systems should be identified and this check should be
+     * moved to appropriate system specific section below. */
+    if (buf.tm_year < 0) {
+        PyErr_Format(PyExc_ValueError, "year=%d is before 1900; "
+                     "the strftime() method requires year >= 1900",
+                     buf.tm_year + 1900);
+    }
+
     /* Normalize tm_isdst just in case someone foolishly implements %Z
        based on the assumption that tm_isdst falls within the range of
        [-1, 1] */


More information about the Python-checkins mailing list