diff -r 3151f6f9df85 Doc/library/datetime.rst --- a/Doc/library/datetime.rst Thu Jun 26 01:41:06 2014 -0400 +++ b/Doc/library/datetime.rst Tue Jul 01 16:33:19 2014 +0300 @@ -1895,6 +1895,10 @@ | ``%X`` | Locale's appropriate time || 21:30:00 (en_US); | \(1) | | | representation. || 21:30:00 (de_DE) | | +-----------+--------------------------------+------------------------+-------+ +| ``%s`` | Number of seconds since | 587763000 | \(8) | +| | the Epoch, | (1988-08-16 21:30:00 | | +| | 1970-01-01 00:00:00 UTC. | +0200) | | ++-----------+--------------------------------+------------------------+-------+ | ``%%`` | A literal ``'%'`` character. | % | | +-----------+--------------------------------+------------------------+-------+ @@ -1964,6 +1968,10 @@ When used with the :meth:`strptime` method, ``%U`` and ``%W`` are only used in calculations when the day of the week and the year are specified. +(8) + .. versionadded:: 3.5 + ``%s`` format code support is added for only :meth:`strftime`. In previous + versions, ``%s`` format code behaviour was undefined and incidental. + .. rubric:: Footnotes - .. [#] If, that is, we ignore the effects of Relativity diff -r 3151f6f9df85 Lib/datetime.py --- a/Lib/datetime.py Thu Jun 26 01:41:06 2014 -0400 +++ b/Lib/datetime.py Tue Jul 01 16:33:19 2014 +0300 @@ -165,8 +165,9 @@ freplace = None # the string to use for %f zreplace = None # the string to use for %z Zreplace = None # the string to use for %Z + sreplace = None # the string to use for %s - # Scan format for %z and %Z escapes, replacing as needed. + # Scan format for %z %Z %f and %s escapes, replacing as needed. newformat = [] push = newformat.append i, n = 0, len(format) @@ -207,6 +208,10 @@ # strftime is going to have at this: escape % Zreplace = s.replace('%', '%%') newformat.append(Zreplace) + elif ch == 's' and isinstance(object, datetime): + if sreplace is None: + sreplace = '%d' % _math.floor(object.timestamp()) + newformat.append(sreplace) else: push('%') push(ch) diff -r 3151f6f9df85 Lib/test/datetimetester.py --- a/Lib/test/datetimetester.py Thu Jun 26 01:41:06 2014 -0400 +++ b/Lib/test/datetimetester.py Tue Jul 01 16:33:19 2014 +0300 @@ -1142,6 +1142,7 @@ for fmt in ["m:%m d:%d y:%y", "m:%m d:%d y:%y H:%H M:%M S:%S", "%z %Z", + "%s", ]: self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) @@ -1525,6 +1526,46 @@ t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name')) self.assertRaises(TypeError, t.strftime, '%Z') + # 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_strftime_naive(self): + t = self.theclass(1970, 1, 1) + self.assertEqual(t.strftime('%s'), '18000') + t = self.theclass(1970, 1, 1, 1, 2, 3, 4) + self.assertEqual(t.strftime('%s'), + str(18000 + 3600 + 2*60 + 3)) + + def test_strftime_respect_timezone(self): + # Issue 12750: datetime.strftime('%s') should respect tzinfo + t = self.theclass(1970, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + self.assertEqual(t.strftime('%s'), '0') + + t = self.theclass(1970, 1, 1, 1, 2, 3, 600000, tzinfo=timezone.utc) + self.assertEqual(t.strftime('%s'), + str(3600 + 2*60 + 3)) + + t = self.theclass(1970, 1, 1, 1, 2, 3, 400000, + tzinfo=timezone(timedelta(hours=-5), 'EST')) + self.assertEqual(t.strftime('%s'), + str(18000 + 3600 + 2*60 + 3)) + + t = self.theclass(1969, 1, 1, 0, 0, 0, 700000, + tzinfo=timezone.utc) + self.assertEqual(t.strftime('%s'), '-31536000') + + t = self.theclass(1970, 1, 1, 0, 5, 0, 0, + tzinfo=FixedOffset(5, "")) + self.assertEqual(t.strftime('%s'), '0') + + t = self.theclass(1970, 1, 1, 0, 0, 0, 0, + tzinfo=FixedOffset(-5, "")) + self.assertEqual(t.strftime('%s'), '300') + + t = self.theclass(1969, 1, 1, 0, 0, 0, 700000, + tzinfo=FixedOffset(-5, "")) + self.assertEqual(t.strftime('%s'), '-31535700') + def test_bad_constructor_arguments(self): # bad years self.theclass(MINYEAR, 1, 1) # no exception diff -r 3151f6f9df85 Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c Thu Jun 26 01:41:06 2014 -0400 +++ b/Modules/_datetimemodule.c Tue Jul 01 16:33:19 2014 +0300 @@ -1118,6 +1118,38 @@ } static PyObject * +make_sreplacement(PyObject *object) +{ + PyObject *math == NULL; + PyObject *sreplacement; + PyObject *temp; + PyObject *timestamp; + _Py_IDENTIFIER(timestamp); + _Py_IDENTIFIER(floor); + + timestamp = _PyObject_CallMethodId(object, &PyId_timestamp, ""); + if(timestamp == NULL) + return NULL; + + math = PyImport_ImportModule("math"); + if (math == NULL) { + Py_DECREF(timestamp); + return NULL; + } + + temp = _PyObject_CallMethodId(math, &PyId_floor, "O", timestamp); + Py_DECREF(math); + Py_DECREF(timestamp); + if(temp == NULL) { + return NULL; + } + + sreplacement = PyObject_Str(temp); + Py_DECREF(temp); + return sreplacement; +} + +static PyObject * make_freplacement(PyObject *object) { char freplacement[64]; @@ -1147,6 +1179,7 @@ PyObject *zreplacement = NULL; /* py string, replacement for %z */ PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ PyObject *freplacement = NULL; /* py string, replacement for %f */ + PyObject *sreplacement = NULL; /* py string, replacement for %s */ const char *pin; /* pointer to next char in input format */ Py_ssize_t flen; /* length of input format */ @@ -1237,6 +1270,18 @@ if (ptoappend == NULL) goto Done; } + else if (ch == 's' && PyDateTime_Check(object)) { + /* format timestamp */ + if (sreplacement == NULL) { + sreplacement = make_sreplacement(object); + if (sreplacement == NULL) + goto Done; + } + assert(sreplacement != NULL); + assert(PyUnicode_Check(sreplacement)); + ptoappend = _PyUnicode_AsStringAndSize(sreplacement, + &ntoappend); + } else if (ch == 'f') { /* format microseconds */ if (freplacement == NULL) { @@ -1298,6 +1343,7 @@ Py_XDECREF(freplacement); Py_XDECREF(zreplacement); Py_XDECREF(Zreplacement); + Py_XDECREF(sreplacement); Py_XDECREF(newfmt); return result; }