Skip to content

Commit 2f6c0ce

Browse files
author
marat
committed
gh-137165: Add non-zero-padded Windows support for datetime.strftime
1 parent 01cc532 commit 2f6c0ce

File tree

4 files changed

+76
-0
lines changed

4 files changed

+76
-0
lines changed

Lib/_pydatetime.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ def _need_normalize_century():
213213
_normalize_century = True
214214
return _normalize_century
215215

216+
def _make_dash_replacement(ch, timetuple):
217+
fmt = '%' + ch
218+
val = _time.strftime(fmt, timetuple)
219+
return val.lstrip('0') or '0'
220+
216221
# Correctly substitute for %z and %Z escapes in strftime formats.
217222
def _wrap_strftime(object, format, timetuple):
218223
# Don't call utcoffset() or tzname() unless actually needed.
@@ -284,6 +289,16 @@ def _wrap_strftime(object, format, timetuple):
284289
push('{:04}'.format(year))
285290
if ch == 'F':
286291
push('-{:02}-{:02}'.format(*timetuple[1:3]))
292+
elif ch == '-':
293+
if i < n:
294+
next_ch = format[i]
295+
i += 1
296+
if sys.platform.startswith('win') or sys.platform.startswith('android'):
297+
push(_make_dash_replacement(next_ch, timetuple))
298+
else:
299+
push('%-' + next_ch)
300+
else:
301+
push('%-')
287302
else:
288303
push('%')
289304
push(ch)

Lib/test/datetimetester.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import textwrap
1515
import unittest
1616
import warnings
17+
import platform
1718

1819
from array import array
1920

@@ -1588,6 +1589,15 @@ def test_strftime(self):
15881589
self.assertEqual(t.strftime(""), "") # SF bug #761337
15891590
self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
15901591

1592+
# SF bug #137165
1593+
if platform.system() == 'Darwin':
1594+
self.assertEqual(t.strftime("m:%-m d:%-d y:%-y"), "m:3 d:2 y:05")
1595+
elif platform.system() == 'Windows':
1596+
self.assertEqual(t.strftime("m:%#m d:%#d y:%#y"), "m:3 d:2 y:5")
1597+
self.assertEqual(t.strftime("m:%-m d:%-d y:%-y"), "m:3 d:2 y:5")
1598+
else:
1599+
self.assertEqual(t.strftime("m:%-m d:%-d y:%-y"), "m:3 d:2 y:5")
1600+
15911601
self.assertRaises(TypeError, t.strftime) # needs an arg
15921602
self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
15931603
self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
@@ -3890,6 +3900,11 @@ def test_strftime(self):
38903900
# A naive object replaces %z, %:z and %Z with empty strings.
38913901
self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''")
38923902

3903+
# SF bug #137165
3904+
self.assertEqual(t.strftime('%-H %-M %-S %f'), "1 2 3 000004")
3905+
if platform.system() == 'Windows':
3906+
self.assertEqual(t.strftime('%#H %#M %#S %f'), "1 2 3 000004")
3907+
38933908
# bpo-34482: Check that surrogates don't cause a crash.
38943909
try:
38953910
t.strftime('%H\ud800%M')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for Windows non-zero-padded formatting directives in
2+
:func:`datetime.datetime.strftime` (e.g., ``"m:%-m d:%-d y:%-y"``).

Modules/_datetimemodule.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,39 @@ make_freplacement(PyObject *object)
18561856
return PyUnicode_FromString(freplacement);
18571857
}
18581858

1859+
#if defined(MS_WINDOWS) || defined(__ANDROID__)
1860+
static PyObject *
1861+
make_dash_replacement(PyObject *object, Py_UCS4 ch, PyObject *timetuple)
1862+
{
1863+
PyObject *strftime = PyImport_ImportModuleAttrString("time", "strftime");
1864+
if (!strftime) {
1865+
return NULL;
1866+
}
1867+
1868+
char fmt[3] = {'%', (char)ch, 0};
1869+
PyObject *fmt_obj = PyUnicode_FromString(fmt);
1870+
if (!fmt_obj) {
1871+
Py_DECREF(strftime);
1872+
return NULL;
1873+
}
1874+
1875+
PyObject *res = PyObject_CallFunctionObjArgs(strftime, fmt_obj, timetuple, NULL);
1876+
Py_DECREF(fmt_obj);
1877+
Py_DECREF(strftime);
1878+
if (!res) {
1879+
return NULL;
1880+
}
1881+
1882+
PyObject *stripped = PyObject_CallMethod(res, "lstrip", "s", "0");
1883+
Py_DECREF(res);
1884+
if (!stripped) {
1885+
return NULL;
1886+
}
1887+
1888+
return stripped;
1889+
}
1890+
#endif
1891+
18591892
/* I sure don't want to reproduce the strftime code from the time module,
18601893
* so this imports the module and calls it. All the hair is due to
18611894
* giving special meanings to the %z, %:z, %Z and %f format codes via a
@@ -2002,6 +2035,17 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
20022035
}
20032036
continue;
20042037
}
2038+
#if defined(MS_WINDOWS) || defined(__ANDROID__)
2039+
/* non-0-pad Windows support */
2040+
else if (ch == '-' && i < flen) {
2041+
Py_UCS4 next_ch = PyUnicode_READ_CHAR(format, i);
2042+
i++;
2043+
replacement = make_dash_replacement(object, next_ch, timetuple);
2044+
if (replacement == NULL) {
2045+
goto Error;
2046+
}
2047+
}
2048+
#endif
20052049
else {
20062050
/* percent followed by something else */
20072051
continue;

0 commit comments

Comments
 (0)