Feature or enhancement
Note: in this issue datetime refers to datetime.datetime, while time refers to datetime.time. This issue doesn't talk about the time module.
Background
Currently, in datetime.strftime/time.strftime, the only sub-second formatting code available is %f, which produces a fixed-width string of microsecond digits, padded from the left with zeros, like "004312".
Despite %f coding for an integer, its zero-padded nature makes it usable in producing the "fraction of the second" part of a datetime string. For example "%T.%f" results in a string like 22:11:55.012345.
Problem
There is still no way for datetime.strftime/time.strftime, alone to produce milliseconds, like 22:11:55.012, or any other fraction length between 1 and 5.
Non-solutions
If you control the code, you can call d.isoformat(timespec="milliseconds"). If all you have is configuration where you specify the datetime.strftime format string, you're in no luck.
Proposal
I'm proposing an expanded variant of the %f datetime.strftime format code in the form of %Nf where N∈{1,2,3,4,5,6}. It would produce datetime microseconds, zero-padded to 6 decimal positions and truncated to N most significant positions.
Examples:
- If
%f → "012345", then:
%6f → "012345"
%5f → "01234"
%4f → "0123"
%3f → "012"
%2f → "01"
%1f → "0"
- An ISO 8601 format specifying millisecond precision:
%Y-%m-%dT%H:%M:%S.%3f%:z
- A human-friendly format with 2 sub-second decimal places:
%Y-%m-%d %H:%M:%S.%2f %z
Issues and choices
Truncation or rounding
For microseconds equal to 012345, should %5f produce:
"01234" (truncation)
"01235" (arithmetic rounding)
- some other rounding mode?
My proposed change truncates.
Format code syntax
N in %Nf theoretically conflicts with "optional minimum field width" in POSIX libc. The post by @jb2170 highlights that the conflict is very theoretical because %f is a Python extension (and libc doesn't include sub-seconds in struct tm).
This theoretical conflict could still be avoided by following alternative codes:
%.Nf instead of %Nf. This however was deemed confusing, since having a dot in the format code suggests that the code would also produce a dot in its output, and generally, round a float rather than truncating an integer.
%:Nf (where :N is analogous to the slicing operation) was floated, with no traction.
Overall, the 2 recently active discussion members are in favour of %Nf. I believe that either %Nf or %:Nf could be valid, which makes 3/3 recent posters in favour of %Nf.
Format code corner cases
In my initial implementation, if N falls outside of the supported [1, 6] range, a ValueError is raised. Since format code scanning operates on characters, such detection will only occur for 0, 7, 8 and 9. A code like "%10f" will not be intercepted and will hit the libc - handling always-incorrect multi-digit codes would complicate parsing in the C variant of wrap_strftime so I'm not considering that.
As an alternative to the ValueError, N=0 could produce nothing while values of 7, 8, 9 could act like N=6.
My preference: No change to initial code.
strptime leniency
In my initial implementation a %Nf code in datetime.strptime/time.strptime means that exactly N digits are expected. "%3f" will accept "111" but not "11" or "1111". This is OK since the existing %f code still accepts any number of digits in the [1, 6] range, as before.
Alternatively, a relaxation could be made instead: for a %Nf code, any number of digits from the range [1, N] could be accepted.
My preference: No relaxation.
Prior art
Rust's chrono lib permits %3f, %6f, and %9f (datetime cannot support %9f, since it doesn't go beyond microseconds).
Has this already been discussed elsewhere?
I have already discussed this feature proposal on Discourse
Links to previous discussion of this feature:
https://discuss.python.org/t/add-millisecond-formatting-support-to-datetime-strftime
Implementation
My initial implementation is in this PR:
Linked PRs
Feature or enhancement
Note: in this issue
datetimerefers todatetime.datetime, whiletimerefers todatetime.time. This issue doesn't talk about thetimemodule.Background
Currently, in
datetime.strftime/time.strftime, the only sub-second formatting code available is%f, which produces a fixed-width string of microsecond digits, padded from the left with zeros, like"004312".Despite
%fcoding for an integer, its zero-padded nature makes it usable in producing the "fraction of the second" part of a datetime string. For example"%T.%f"results in a string like22:11:55.012345.Problem
There is still no way for
datetime.strftime/time.strftime, alone to produce milliseconds, like22:11:55.012, or any other fraction length between 1 and 5.Non-solutions
If you control the code, you can call
d.isoformat(timespec="milliseconds"). If all you have is configuration where you specify thedatetime.strftimeformat string, you're in no luck.Proposal
I'm proposing an expanded variant of the
%fdatetime.strftimeformat code in the form of%Nfwhere N∈{1,2,3,4,5,6}. It would produce datetime microseconds, zero-padded to 6 decimal positions and truncated to N most significant positions.Examples:
%f→"012345", then:%6f→"012345"%5f→"01234"%4f→"0123"%3f→"012"%2f→"01"%1f→"0"%Y-%m-%dT%H:%M:%S.%3f%:z%Y-%m-%d %H:%M:%S.%2f %zIssues and choices
Truncation or rounding
For microseconds equal to
012345, should%5fproduce:"01234"(truncation)"01235"(arithmetic rounding)My proposed change truncates.
Format code syntax
N in
%Nftheoretically conflicts with "optional minimum field width" in POSIX libc. The post by @jb2170 highlights that the conflict is very theoretical because%fis a Python extension (and libc doesn't include sub-seconds instruct tm).This theoretical conflict could still be avoided by following alternative codes:
%.Nfinstead of%Nf. This however was deemed confusing, since having a dot in the format code suggests that the code would also produce a dot in its output, and generally, round a float rather than truncating an integer.%:Nf(where:Nis analogous to the slicing operation) was floated, with no traction.Overall, the 2 recently active discussion members are in favour of
%Nf. I believe that either%Nfor%:Nfcould be valid, which makes 3/3 recent posters in favour of%Nf.Format code corner cases
In my initial implementation, if N falls outside of the supported [1, 6] range, a ValueError is raised. Since format code scanning operates on characters, such detection will only occur for 0, 7, 8 and 9. A code like
"%10f"will not be intercepted and will hit the libc - handling always-incorrect multi-digit codes would complicate parsing in the C variant ofwrap_strftimeso I'm not considering that.As an alternative to the ValueError, N=0 could produce nothing while values of 7, 8, 9 could act like N=6.
My preference: No change to initial code.
strptimeleniencyIn my initial implementation a
%Nfcode indatetime.strptime/time.strptimemeans that exactly N digits are expected."%3f"will accept"111"but not"11"or"1111". This is OK since the existing%fcode still accepts any number of digits in the [1, 6] range, as before.Alternatively, a relaxation could be made instead: for a
%Nfcode, any number of digits from the range [1, N] could be accepted.My preference: No relaxation.
Prior art
Rust's chrono lib permits %3f, %6f, and %9f (datetime cannot support %9f, since it doesn't go beyond microseconds).
Has this already been discussed elsewhere?
I have already discussed this feature proposal on Discourse
Links to previous discussion of this feature:
https://discuss.python.org/t/add-millisecond-formatting-support-to-datetime-strftime
Implementation
My initial implementation is in this PR:
Linked PRs