You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add option to output UTC datetimes as "Z" in .isoformat() #90772
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee='https://github.com/pganssle'closed_at=Nonecreated_at=<Date2022-02-02.17:31:46.548>labels= ['type-feature', 'library', '3.11']
title='Add option to output UTC datetimes as "Z" in `.isoformat()`'updated_at=<Date2022-04-04.01:30:42.788>user='https://github.com/pganssle'
As part of bpo-35829, it was suggested that we add the ability to output the "Z" suffix in isoformat(), so that fromisoformat() can both be the exact functional inverse of isoformat() and parse datetimes with "Z" outputs. I think that that's not a particularly compelling motivation for this, but I also see plenty of examples of datetime.utcnow().isoformat() + "Z" out there, so it seems like this is a feature that we would want to have anyway, particularly if we want to deprecate and remove utcnow.
I've spun this off into its own issue so that we can discuss how to implement the feature. The two obvious questions I see are:
What do we call the option? use_utc_designator, allow_Z, utc_as_Z?
What do we consider as "UTC"? Is it anything with +00:00? Just timezone.utc? Anything that seems like a fixed-offset zone with 0 offset?
The only "odd man out" is dateutil.tz.tzfile objects representing fixed offsets, since all dateutil.tz.tzfile objects return None when utcoffset or dst are passed None. This can and will be changed in future versions.
I feel like "If the offset is 00:00, use Z" is the wrong rule to use conceptually, but considering that people will be opting into this behavior, it is more likely that they will be surprised by datetime(2022, 3, 1, tzinfo=ZoneInfo("Europe/London").isoformat(utc_as_z=True) returning 2022-03-01T00:00:00+00:00 than alternation between Z and +00:00.
Yet another option might be to add a completely separate function, utc_isoformat(*args, **kwargs), which is equivalent to (in the parlance of the other proposal) dt.astimezone(timezone.utc).isoformat(*args, **kwargs, utc_as_z=True). Basically, convert any datetime to UTC and append a Z to it. The biggest footgun there would be people using it on naïve datetimes and not realizing that it would interpret them as system local times.
Would it be horrible to have the timezone instance control this?
Reacted by Fionah Kimutai
godlygeek commented on Feb 11, 2022
godlygeekmannequin
Mannequin
More actions
I feel like "If the offset is 00:00, use Z" is the wrong rule to use conceptually
This is a really good point that I hadn't considered: +00:00 and Z are semantically different, and just because a datetime has a UTC offset of 0 doesn't mean it should get a Z; Z is reserved specifically for UTC.
It seems like the most semantically correct thing would be to only use Z if tzname() returns exactly "UTC". That would do the right thing for your London example for every major timezone library I'm aware of:
I think the right rule to use conceptually is "if use_utc_designator is true and the timezone name is 'UTC' then use Z". We could also check the offset, but I'm not convinced we need to.
I think this approach is probably the best we can do, but I could also imagine that users might find it to be confusing behavior. I wonder if there's any informal user testing we can do?
I guess the ISO 8601 spec does call "Z" the "UTC designator", so use_utc_designator seems like approximately the right name. My main hesitation with this name is that I suspect users may think that use_utc_designator means that they unconditionally want to use Z — without reading the documentation (which we can assume 99% of users won't do) — you might assume that dt.isoformat(use_utc_designator=True) would translate to dt.astimezone(timezone.utc).replace(tzinfo=None).isoformat() + "Z".
A name like utc_as_z is definitely less... elegant, but conveys the concept a bit more clearly. Would be worth throwing it to a poll or something before merging.
It is short, memorable if you know about it, otherwise obscure enough to push people to read the docs and be clear about what it does.
Also strange and far from obvious, so a bad idea. Unless… ?
godlygeek commented on Apr 4, 2022
godlygeekmannequin
Mannequin
More actions
My main hesitation with this name is that I suspect users may think that use_utc_designator means that they unconditionally want to use Z — without reading the documentation (which we can assume 99% of users won't do)
I was thinking along similar lines when I used use_utc_designator in the PR, but I drew a different conclusion. I was thinking that the name use_utc_designator is sufficiently abstruse that no one would even be able to guess that it's referring to "Z" without actually reading the documentation for the parameter. In particular, I worry that zulu=True or allow_Z=True might lead people to make the mistake of thinking that they'll always get "Z" instead of "+00:00".
A name like utc_as_z is definitely less... elegant, but conveys the concept a bit more clearly.
This would definitely be more memorable and more approachable. If we stick with making it conditional on tzname() == "UTC", I definitely think we want to have "utc" in the name of the parameter, and utc_as_z satisfies that.
utc_as_z seems reasonable to me. Let me know if you'd like me to update the PR.
After running a survey where I tried out 4 different keyword arguments at random with 972 participants (not all of them completed the survey all the way, admittedly) and asked people what they thought it does in various situations, I got the following results:
This is strange because I think none of us like allow_z, including the survey participants. When told the actual semantics of the keyword argument, they overwhelmingly prefer utc_as_z:
utc_as_z 165
format_utc_as_z 78
I don't like any of these 42
allow_z 19
use_utc_designator 12
I was hoping the results here would be less... ambiguous.
Given these results and the fact that people don't seem to have a great grasp on what this is supposed to do, let's push this feature to 3.12 to try to come up with a better design.
Mainly because you may expect that your local TZ always return the 2nd format instead of the 1st one (the shortcut of +00:00)
I think this should be only done when the timezone is not local but UTC only (or when missing timezone?)
I suggest this is controlled in part by the tzinfo or time instance.
For instance, in datetime.py this code in class time
class time:
# [...]
def _tzstr(self):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
off = self.utcoffset()
return _format_offset(off)
can be extended as something like
class time:
# [...]
def _tzstr(self):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
off = self.utcoffset()
if not off and self.tzname() == "UTC":
return "Z"
return _format_offset(off)
A respective change would be needed in class datetime.
The string UTC is produced by
class timezone(tzinfo):
# ...
@staticmethod
def _name_from_offset(delta):
if not delta:
return 'UTC'
# ...
Now changing the default behavior of datetime formatting function to start outputting Z instead of 00:00 for UTC timestamps might cause some backward compatibility problems, so that utc_as_z=True argument might be needed.
But I think the proposal utc_as_z=True is not too elegant nor useful.
So looking at the declaration of isoformat
class datetime:
#....
def isoformat(self, sep='T', timespec='auto'):
#....
class time:
#....
def isoformat(self, timespec='auto'):
# ....
Perhaps we could use a new tzspec argument to match the name of the timespec, with values 'auto', 'utcz', 'hours', 'minutes', 'seconds', etc. 'auto' would be the current behavior. 'utcz' would be the new behavior or using 'Z' instead of '00:00', and more values could be reserved to be fixed later ('hours', 'minutes', 'seconds') to control the behavior of the _format_offset() function.
It should just automatically do the right thing, i.e. use Z when the timezone is UTC. This is what happens in eg. JavaScript. There's no need for a new option.
It should just automatically do the right thing, i.e. use Z when the timezone is UTC. This is what happens in eg. JavaScript. There's no need for a new option.
In principle, I support this. In practice, I suspect this won't be done because changing the default behaviour of isoformat would break code that depends on the UTC offset always being formatted the way it currently is.
I object to the idea that using Z unconditionally is "the right thing", but also there are several logistical problems that make the idea of changing the default behavior a non-starter:
Backwards compatibility — this alone will scuttle the proposal, because there are absolutely people relying on the fact that this outputs +00:00 instead of Z
What it means to be "UTC" is ill-defined, as mentioned in the first post. Non-UTC datetimes might incidentaly have a +00:00 offset, and it isn't clear that it is appropriate to automatically give them Z, in which case we need to come up with a reliable heuristic for what it means to be "UTC". Making this behavior auto-magical will make it even harder for people to discover the edge cases.
Whatever we do needs to be explicit, but it seems very hard to do that, since when we ran the survey people seemed to have conflicting understandings of what any of this stuff might do; there doesn't seem to be an unambiguous way to convey what the behavior might be.
If this were a high priority and in high demand, I would suggest we might bite the bullet and have this be just one more thing that end users need to learn about datetime, but it seems like the main motivation for adding this option was to make it so that formats ending with Z would technically satisfy the pre-3.11 contract of fromisoformat (parsing any format that .isoformat can emit), which doesn't even apply anymore.
It might be worth adding a isoformatutc method in that case. It would hit two birds with one stone - convert the time zone to UTC and provide it with the Z suffix - which is useful for machine processing. This is the same as JS's toISOString.
Reacted by Anton Agestam, rindeal and Petr Viktorin
JFYI:
The Z vs. +00:00 issue has been debated extensively during the update to RFC 3339 that allows adding further information to a timestamp.
RFC3339 distinguishes the semantics of expressing a time zone offset from that of not expressing one.
Unfortunately, it chose -00:00 for the latter, which is not allowed in ISO/IEC 8601.
The fix we ultimately arrived at is:
Note that almost all timestamps in reality do not carry the intention to express a time zone offset, so Z is now the normal way to write an RFC 3339 timestamp -- the spec now follows the established practice for instance in the JavaScript community.
Reacted by Juhan Oskar Hennoste, Tuukka Mustonen and rindeal
Sorry Paul but you have been assigned for a few years and no pr so I assume you have not been actively working on this. Continued the work from the old pr. Making it mandatory is a discussion for later as its a backwards compatibility nightmare.
Activity
pganssle commentedon Feb 2, 2022
As part of bpo-35829, it was suggested that we add the ability to output the "Z" suffix in
isoformat(), so thatfromisoformat()can both be the exact functional inverse ofisoformat()and parse datetimes with "Z" outputs. I think that that's not a particularly compelling motivation for this, but I also see plenty of examples ofdatetime.utcnow().isoformat() + "Z"out there, so it seems like this is a feature that we would want to have anyway, particularly if we want to deprecate and removeutcnow.I've spun this off into its own issue so that we can discuss how to implement the feature. The two obvious questions I see are:
use_utc_designator,allow_Z,utc_as_Z?timezone.utc? Anything that seems like a fixed-offset zone with 0 offset?For example, do we want this?
Another possible definition might be if the
tzinfois a fixed-offset zone with offset 0:The only "odd man out" is
dateutil.tz.tzfileobjects representing fixed offsets, since alldateutil.tz.tzfileobjects returnNonewhenutcoffsetordstare passedNone. This can and will be changed in future versions.I feel like "If the offset is 00:00, use Z" is the wrong rule to use conceptually, but considering that people will be opting into this behavior, it is more likely that they will be surprised by
datetime(2022, 3, 1, tzinfo=ZoneInfo("Europe/London").isoformat(utc_as_z=True)returning2022-03-01T00:00:00+00:00than alternation betweenZand+00:00.Yet another option might be to add a completely separate function,
utc_isoformat(*args, **kwargs), which is equivalent to (in the parlance of the other proposal)dt.astimezone(timezone.utc).isoformat(*args, **kwargs, utc_as_z=True). Basically, convert any datetime to UTC and append a Z to it. The biggest footgun there would be people using it on naïve datetimes and not realizing that it would interpret them as system local times.merwok commentedon Feb 8, 2022
Would it be horrible to have the timezone instance control this?
godlygeek commentedon Feb 11, 2022
This is a really good point that I hadn't considered:
+00:00andZare semantically different, and just because a datetime has a UTC offset of 0 doesn't mean it should get aZ;Zis reserved specifically for UTC.It seems like the most semantically correct thing would be to only use
Ziftzname()returns exactly "UTC". That would do the right thing for your London example for every major timezone library I'm aware of:I think the right rule to use conceptually is "if
use_utc_designatoris true and the timezone name is 'UTC' then use Z". We could also check the offset, but I'm not convinced we need to.pganssle commentedon Apr 3, 2022
I think this approach is probably the best we can do, but I could also imagine that users might find it to be confusing behavior. I wonder if there's any informal user testing we can do?
I guess the ISO 8601 spec does call "Z" the "UTC designator", so
use_utc_designatorseems like approximately the right name. My main hesitation with this name is that I suspect users may think thatuse_utc_designatormeans that they unconditionally want to useZ— without reading the documentation (which we can assume 99% of users won't do) — you might assume thatdt.isoformat(use_utc_designator=True)would translate todt.astimezone(timezone.utc).replace(tzinfo=None).isoformat() + "Z".A name like
utc_as_zis definitely less... elegant, but conveys the concept a bit more clearly. Would be worth throwing it to a poll or something before merging.merwok commentedon Apr 3, 2022
Bad idea: pass
zulu=TrueIt is short, memorable if you know about it, otherwise obscure enough to push people to read the docs and be clear about what it does.
Also strange and far from obvious, so a bad idea. Unless… ?
godlygeek commentedon Apr 4, 2022
I was thinking along similar lines when I used
use_utc_designatorin the PR, but I drew a different conclusion. I was thinking that the nameuse_utc_designatoris sufficiently abstruse that no one would even be able to guess that it's referring to "Z" without actually reading the documentation for the parameter. In particular, I worry thatzulu=Trueorallow_Z=Truemight lead people to make the mistake of thinking that they'll always get "Z" instead of "+00:00".This would definitely be more memorable and more approachable. If we stick with making it conditional on
tzname() == "UTC", I definitely think we want to have "utc" in the name of the parameter, andutc_as_zsatisfies that.utc_as_zseems reasonable to me. Let me know if you'd like me to update the PR.pganssle commentedon May 3, 2022
After running a survey where I tried out 4 different keyword arguments at random with 972 participants (not all of them completed the survey all the way, admittedly) and asked people what they thought it does in various situations, I got the following results:
This is strange because I think none of us like
allow_z, including the survey participants. When told the actual semantics of the keyword argument, they overwhelmingly preferutc_as_z:I was hoping the results here would be less... ambiguous.
pganssle commentedon May 3, 2022
Given these results and the fact that people don't seem to have a great grasp on what this is supposed to do, let's push this feature to 3.12 to try to come up with a better design.
Saphyel commentedon May 6, 2022
I have to disagree about this approach:
Mainly because you may expect that your local TZ always return the 2nd format instead of the 1st one (the shortcut of +00:00)
I think this should be only done when the timezone is not local but UTC only (or when missing timezone?)
At the end what we want is to avoid this => https://i.redd.it/ocpk67fp6tx81.jpg
merwok commentedon Aug 8, 2023
After seeing usage in the PR, I find
use_utc_designator=Truea bit unwieldy, especially as it will nearly always be a keyword param.joaoe commentedon Aug 8, 2023
I suggest this is controlled in part by the
tzinfoortimeinstance.For instance, in
datetime.pythis code inclass timecan be extended as something like
A respective change would be needed in
class datetime.The string
UTCis produced byNow changing the default behavior of datetime formatting function to start outputting
Zinstead of00:00for UTC timestamps might cause some backward compatibility problems, so thatutc_as_z=Trueargument might be needed.But I think the proposal
utc_as_z=Trueis not too elegant nor useful.So looking at the declaration of
isoformatPerhaps we could use a new
tzspecargument to match the name of thetimespec, with values'auto', 'utcz', 'hours', 'minutes', 'seconds', etc.'auto'would be the current behavior.'utcz'would be the new behavior or using'Z'instead of'00:00', and more values could be reserved to be fixed later('hours', 'minutes', 'seconds')to control the behavior of the_format_offset()function.mohd-akram commentedon May 11, 2024
It should just automatically do the right thing, i.e. use
Zwhen the timezone is UTC. This is what happens in eg. JavaScript. There's no need for a new option.FeldrinH commentedon May 11, 2024
In principle, I support this. In practice, I suspect this won't be done because changing the default behaviour of isoformat would break code that depends on the UTC offset always being formatted the way it currently is.
pganssle commentedon May 11, 2024
I object to the idea that using
Zunconditionally is "the right thing", but also there are several logistical problems that make the idea of changing the default behavior a non-starter:+00:00instead ofZZ, in which case we need to come up with a reliable heuristic for what it means to be "UTC". Making this behavior auto-magical will make it even harder for people to discover the edge cases.Whatever we do needs to be explicit, but it seems very hard to do that, since when we ran the survey people seemed to have conflicting understandings of what any of this stuff might do; there doesn't seem to be an unambiguous way to convey what the behavior might be.
If this were a high priority and in high demand, I would suggest we might bite the bullet and have this be just one more thing that end users need to learn about
datetime, but it seems like the main motivation for adding this option was to make it so that formats ending withZwould technically satisfy the pre-3.11 contract offromisoformat(parsing any format that.isoformatcan emit), which doesn't even apply anymore.mohd-akram commentedon May 11, 2024
It might be worth adding a
isoformatutcmethod in that case. It would hit two birds with one stone - convert the time zone to UTC and provide it with the Z suffix - which is useful for machine processing. This is the same as JS'stoISOString.cabo commentedon Nov 10, 2024
JFYI:
The Z vs. +00:00 issue has been debated extensively during the update to RFC 3339 that allows adding further information to a timestamp.
RFC3339 distinguishes the semantics of expressing a time zone offset from that of not expressing one.
Unfortunately, it chose -00:00 for the latter, which is not allowed in ISO/IEC 8601.
The fix we ultimately arrived at is:
https://www.rfc-editor.org/rfc/rfc9557#name-updating-rfc-3339
Note that almost all timestamps in reality do not carry the intention to express a time zone offset, so Z is now the normal way to write an RFC 3339 timestamp -- the spec now follows the established practice for instance in the JavaScript community.
StanFromIreland commentedon Mar 22, 2025
Sorry Paul but you have been assigned for a few years and no pr so I assume you have not been actively working on this. Continued the work from the old pr. Making it mandatory is a discussion for later as its a backwards compatibility nightmare.
merwok commentedon Feb 6, 2026
The linked PR was rejected.