]> granicus.if.org Git - python/commitdiff
Issue #2736: Added datetime.timestamp() method.
authorAlexander Belopolsky <alexander.belopolsky@gmail.com>
Fri, 8 Jun 2012 16:33:09 +0000 (12:33 -0400)
committerAlexander Belopolsky <alexander.belopolsky@gmail.com>
Fri, 8 Jun 2012 16:33:09 +0000 (12:33 -0400)
Doc/library/datetime.rst
Lib/datetime.py
Lib/test/datetimetester.py
Misc/NEWS
Modules/_datetimemodule.c

index 669236aaed103b40e33d41735f23753e1c7d5b4b..72e35df075de8c33e439e2bb87ca9f2f1a76045a 100644 (file)
@@ -752,17 +752,6 @@ Other constructors, all class methods:
 
      datetime(1970, 1, 1) + timedelta(seconds=timestamp)
 
-   There is no method to obtain the timestamp from a :class:`datetime`
-   instance, but POSIX timestamp corresponding to a :class:`datetime`
-   instance ``dt`` can be easily calculated as follows.  For a naive
-   ``dt``::
-
-      timestamp = (dt - datetime(1970, 1, 1)) / timedelta(seconds=1)
-
-   And for an aware ``dt``::
-
-      timestamp = (dt - datetime(1970, 1, 1, tzinfo=timezone.utc)) / timedelta(seconds=1)
-
    .. versionchanged:: 3.3
       Raise :exc:`OverflowError` instead of :exc:`ValueError` if the timestamp
       is out of the range of values supported by the platform C
@@ -1054,6 +1043,39 @@ Instance methods:
    Return the proleptic Gregorian ordinal of the date.  The same as
    ``self.date().toordinal()``.
 
+.. method:: datetime.timestamp()
+
+   Return POSIX timestamp corresponding to the :class:`datetime`
+   instance.  The return value is a :class:`float` similar to that
+   returned by :func:`time.time`.
+
+   Naive :class:`datetime` instances are assumed to represent local
+   time and this method relies on the platform C :c:func:`mktime`
+   function to perform the conversion.  Since :class:`datetime`
+   supports wider range of values than :c:func:`mktime` on many
+   platforms, this method may raise :exc:`OverflowError` for times far
+   in the past or far in the future.
+
+   For aware :class:`datetime` instances, the return value is computed
+   as::
+
+      (dt - datetime(1970, 1, 1, tzinfo=timezone.utc)).total_seconds()
+
+   .. versionadded:: 3.3
+
+   .. note::
+
+      There is no method to obtain the POSIX timestamp directly from a
+      naive :class:`datetime` instance representing UTC time.  If your
+      application uses this convention and your system timezone is not
+      set to UTC, you can obtain the POSIX timestamp by supplying
+      ``tzinfo=timezone.utc``::
+
+         timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
+
+      or by calculating the timestamp directly::
+
+         timestamp = (dt - datetime(1970, 1, 1)) / timedelta(seconds=1)
 
 .. method:: datetime.weekday()
 
index 59f3c68788a9132eaa0f93c63d86be8e74a0b0a8..5d8d9b3dfcec235881186bc1e59a378aa81b6b75 100644 (file)
@@ -1434,6 +1434,15 @@ class datetime(date):
                                   self.hour, self.minute, self.second,
                                   dst)
 
+    def timestamp(self):
+        "Return POSIX timestamp as float"
+        if self._tzinfo is None:
+            return _time.mktime((self.year, self.month, self.day,
+                                 self.hour, self.minute, self.second,
+                                 -1, -1, -1)) + self.microsecond / 1e6
+        else:
+            return (self - _EPOCH).total_seconds()
+
     def utctimetuple(self):
         "Return UTC time tuple compatible with time.gmtime()."
         offset = self.utcoffset()
@@ -1889,7 +1898,7 @@ class timezone(tzinfo):
 timezone.utc = timezone._create(timedelta(0))
 timezone.min = timezone._create(timezone._minoffset)
 timezone.max = timezone._create(timezone._maxoffset)
-
+_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
 """
 Some time zone algebra.  For a datetime x, let
     x.n = x stripped of its timezone -- its naive time.
index 853806b02c8501696df7116bce0500013da4d2f6..9d84b9dea0ddbc50a3127f8e285a137a059f0a90 100644 (file)
@@ -1735,6 +1735,42 @@ class TestDateTime(TestDate):
         got = self.theclass.utcfromtimestamp(ts)
         self.verify_field_equality(expected, got)
 
+    # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
+    # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
+    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
+    def test_timestamp_naive(self):
+        t = self.theclass(1970, 1, 1)
+        self.assertEqual(t.timestamp(), 18000.0)
+        t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
+        self.assertEqual(t.timestamp(),
+                         18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
+        # Missing hour defaults to standard time
+        t = self.theclass(2012, 3, 11, 2, 30)
+        self.assertEqual(self.theclass.fromtimestamp(t.timestamp()),
+                                  t + timedelta(hours=1))
+        # Ambiguous hour defaults to DST
+        t = self.theclass(2012, 11, 4, 1, 30)
+        self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
+
+        # Timestamp may raise an overflow error on some platforms
+        for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]:
+            try:
+                s = t.timestamp()
+            except OverflowError:
+                pass
+            else:
+                self.assertEqual(self.theclass.fromtimestamp(s), t)
+
+    def test_timestamp_aware(self):
+        t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
+        self.assertEqual(t.timestamp(), 0.0)
+        t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
+        self.assertEqual(t.timestamp(),
+                         3600 + 2*60 + 3 + 4*1e-6)
+        t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
+                          tzinfo=timezone(timedelta(hours=-5), 'EST'))
+        self.assertEqual(t.timestamp(),
+                         18000 + 3600 + 2*60 + 3 + 4*1e-6)
     def test_microsecond_rounding(self):
         for fts in [self.theclass.fromtimestamp,
                     self.theclass.utcfromtimestamp]:
index caa31b37d184e3edd329a342df61d18aebc4df22..c4dde35f0125ed44ec42fe7cbe7eef4bac68fe9b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -21,6 +21,8 @@ Core and Builtins
 Library
 -------
 
+- Issue #2736: Added datetime.timestamp() method.
+
 - Issue #13854: Make multiprocessing properly handle non-integer
   non-string argument to SystemExit.
 
index 963a1acdf02421d968fb0432aecf4a4be925a953..31501242d1a4cbdccb526dcdadeacfde5926addf 100644 (file)
@@ -766,6 +766,8 @@ typedef struct
 
 /* The interned UTC timezone instance */
 static PyObject *PyDateTime_TimeZone_UTC;
+/* The interned Epoch datetime instance */
+static PyObject *PyDateTime_Epoch;
 
 /* Create new timezone instance checking offset range.  This
    function does not check the name argument.  Caller must assure
@@ -4747,6 +4749,44 @@ datetime_timetuple(PyDateTime_DateTime *self)
                              dstflag);
 }
 
+static PyObject *
+datetime_timestamp(PyDateTime_DateTime *self)
+{
+    PyObject *result;
+
+    if (HASTZINFO(self) && self->tzinfo != Py_None) {
+        PyObject *delta;
+        delta = datetime_subtract((PyObject *)self, PyDateTime_Epoch);
+        if (delta == NULL)
+            return NULL;
+        result = delta_total_seconds(delta);
+        Py_DECREF(delta);
+    }
+    else {
+        struct tm time;
+        time_t timestamp;
+        memset((void *) &time, '\0', sizeof(struct tm));
+        time.tm_year = GET_YEAR(self) - 1900;
+        time.tm_mon = GET_MONTH(self) - 1;
+        time.tm_mday = GET_DAY(self);
+        time.tm_hour = DATE_GET_HOUR(self);
+        time.tm_min = DATE_GET_MINUTE(self);
+        time.tm_sec = DATE_GET_SECOND(self);
+        time.tm_wday = -1;
+        time.tm_isdst = -1;
+        timestamp = mktime(&time);
+        /* Return value of -1 does not necessarily mean an error, but tm_wday
+         * cannot remain set to -1 if mktime succeeded. */
+        if (timestamp == (time_t)(-1) && time.tm_wday == -1) {
+            PyErr_SetString(PyExc_OverflowError,
+                            "timestamp out of range");
+            return NULL;
+        }
+        result = PyFloat_FromDouble(timestamp + DATE_GET_MICROSECOND(self) / 1e6);
+    }
+    return result;
+}
+
 static PyObject *
 datetime_getdate(PyDateTime_DateTime *self)
 {
@@ -4894,6 +4934,9 @@ static PyMethodDef datetime_methods[] = {
     {"timetuple",   (PyCFunction)datetime_timetuple, METH_NOARGS,
      PyDoc_STR("Return time tuple, compatible with time.localtime().")},
 
+    {"timestamp",   (PyCFunction)datetime_timestamp, METH_NOARGS,
+     PyDoc_STR("Return POSIX timestamp as float.")},
+
     {"utctimetuple",   (PyCFunction)datetime_utctimetuple, METH_NOARGS,
      PyDoc_STR("Return UTC time tuple, compatible with time.localtime().")},
 
@@ -5151,6 +5194,12 @@ PyInit__datetime(void)
         return NULL;
     Py_DECREF(x);
 
+    /* Epoch */
+    PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0,
+                                    PyDateTime_TimeZone_UTC);
+    if (PyDateTime_Epoch == NULL)
+      return NULL;
+
     /* module initialization */
     PyModule_AddIntConstant(m, "MINYEAR", MINYEAR);
     PyModule_AddIntConstant(m, "MAXYEAR", MAXYEAR);