Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,9 @@ Other constructors, all class methods:
>>> when.strftime("%B %d") # doctest: +SKIP
'February 29'

.. versionchanged::
Added the ``%F`` directive.


Class attributes:

Expand Down Expand Up @@ -2593,6 +2596,9 @@ convenience. These parameters all correspond to ISO 8601 date values.
| | (empty string if the object is | +06:34:15, | |
| | naive). | -03:07:12.345216 | |
+-----------+--------------------------------+------------------------+-------+
| ``%F`` | Optional microseconds as a | (empty), .000003, | \(11) |
| | decimal number. | .123456, .3 | |
+-----------+--------------------------------+------------------------+-------+

These may not be available on all platforms when used with the :meth:`~.datetime.strftime`
method. The ISO 8601 year and ISO 8601 week directives are not interchangeable
Expand Down Expand Up @@ -2765,6 +2771,10 @@ Notes:
:exc:`DeprecationWarning`. In 3.15 or later we may change this into
an error or change the default year to a leap year. See :gh:`70647`.

(11)
The ``%F`` directive can only be used with :meth:`~.datetime.strptime`
and :meth:`~.time.strptime`.

.. rubric:: Footnotes

.. [#] If, that is, we ignore the effects of Relativity
Expand Down
15 changes: 15 additions & 0 deletions Doc/library/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -614,12 +614,27 @@ Functions
except for recognizing UTC and GMT which are always known (and are considered to
be non-daylight savings timezones).

The ``%F`` directive is exclusive to :func:`!time.strptime` and allows for
optional microseconds as a decimal.::

>>> import time
>>> time.strptime("09:10:13", "%H:%M:%S%F")
time.struct_time(tm_year=1900, tm_mon=1, tm_mday=1, tm_hour=9, tm_min=10,
tm_sec=13, tm_wday=0, tm_yday=1, tm_isdst=-1)
>>> time.strptime("09:10:13.22", "%H:%M:%S%F")
time.struct_time(tm_year=1900, tm_mon=1, tm_mday=1, tm_hour=9, tm_min=10,
tm_sec=13, tm_wday=0, tm_yday=1, tm_isdst=-1)


Only the directives specified in the documentation are supported. Because
``strftime()`` is implemented per platform it can sometimes offer more
directives than those listed. But ``strptime()`` is independent of any platform
and thus does not necessarily support all directives available that are not
documented as supported.

.. versionchanged::
Added the ``%F`` directive.


.. class:: struct_time

Expand Down
5 changes: 5 additions & 0 deletions Lib/_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ def __init__(self, locale_time=None):
# The " [1-9]" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'f': r"(?P<f>[0-9]{1,6})",
'F': r"(?:\.(?P<F>[0-9]{1,6}))?",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])",
'G': r"(?P<G>\d\d\d\d)",
Expand Down Expand Up @@ -522,6 +523,10 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
# Pad to always return microseconds.
s += "0" * (6 - len(s))
fraction = int(s)
elif group_key == "F":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif group_key == "F":
elif group_key == 'F':

I'd keep current style as in the rest of branches.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ill get back to these if the issue is not resolved without a pr.

s = found_dict["F"] or "0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

s += "0" * (6 - len(s))
fraction = int(s)
elif group_key == 'A':
weekday = locale_time.f_weekday.index(found_dict['A'].lower())
elif group_key == 'a':
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2890,6 +2890,16 @@ def test_strptime(self):
strptime("-00:02:01.000003", "%z").utcoffset(),
-timedelta(minutes=2, seconds=1, microseconds=3)
)

# Test %F
inputs = [
(self.theclass(2025, 3, 23, 13, 2, 47, 197000), "2025-03-23 13:02:47.197", "%Y-%m-%d %H:%M:%S%F"),
(self.theclass(2025, 3, 23, 13, 2, 47), "2025-03-23 13:02:47", "%Y-%m-%d %H:%M:%S%F"),
]
for expected, string, format in inputs:
with self.subTest(expected=expected, string=string, format=format):
self.assertEqual(expected, self.theclass.strptime(string, format))

# Only local timezone and UTC are supported
for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
(-_time.timezone, _time.tzname[0])):
Expand Down Expand Up @@ -3858,6 +3868,15 @@ def test_strftime(self):
# A naive object replaces %z, %:z and %Z with empty strings.
self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''")

# Test %F
inputs = [
(self.theclass(13, 2, 47, 197000), "13:02:47.197", "%H:%M:%S%F"),
(self.theclass(13, 2, 47), "13:02:47", "%H:%M:%S%F"),
]
for expected, string, format in inputs:
with self.subTest(expected=expected, string=string, format=format):
self.assertEqual(expected, self.theclass.strptime(string, format))

# bpo-34482: Check that surrogates don't cause a crash.
try:
t.strftime('%H\ud800%M')
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,16 @@ def test_fraction(self):
tup, frac, _ = _strptime._strptime(str(d), format="%Y-%m-%d %H:%M:%S.%f")
self.assertEqual(frac, d.microsecond)

def test_optional_fraction(self):
# Test optional microseconds
import datetime
d = datetime.datetime(2012, 12, 20, 12, 34, 56, 78987)
tup, frac, _ = _strptime._strptime(str(d), format="%Y-%m-%d %H:%M:%S%F")
self.assertEqual(frac, d.microsecond)
dn = datetime.datetime(2012, 12, 20, 12, 34, 56)
tup, frac, _ = _strptime._strptime(str(dn), format="%Y-%m-%d %H:%M:%S%F")
self.assertEqual(frac, dn.microsecond)

def test_weekday(self):
# Test weekday directives
self.roundtrip('%w', 6)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add ``%F`` format code to :func:`time.strptime` and :func:`datetime.datetime.strptime`
which allows for optional microseconds.
Loading