classification
Title: datetime.isoformat() -> explicitly mark UTC string as such
Type: behavior Stage: resolved
Components: Versions: Python 3.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: BreamoreBoy, belopolsky, mirabilos, mirkovogt, r.david.murray
Priority: normal Keywords:

Created on 2015-01-27 17:37 by mirkovogt, last changed 2015-08-28 21:36 by belopolsky. This issue is now closed.

Messages (23)
msg234829 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 17:37
I trapped into a pitfall today with web programming using DateTime.isoformat() in the backend and Javascript on the frontend side.

isoformat() string'yfies a DateTime-object according to ISO 8601, which states that, if no timezone is specified, the string is supposed to be interpreted as UTC implicitly. isoformat() doesn't append any TZ if the object is UTC - so far so good.

However when interacting with JavaScript that leads to errors, as the ECMAScript ed 6 draft states (and most browser act like that): "date time strings without a time zone are to be treated as local, not UTC)."[1]

That is not Python's fault - however considering the issues this behaviour could cause as well as following the philosophy 'explicit is better than implicit', I'd suggest explicitly marking UTC-strings as such by adding a trailing 'Z'. According ISO 8601 that is totally fine, optional though.

[1]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
msg234831 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-01-27 17:53
I wonder what the chances are that doing that would break other software with the opposite assumption (an assumption based on past Python behavior).
msg234832 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 17:57
Both implementations behave according the standard. If you assume otherwise you violate the standard - as JavaScript does.

If that change would break software, that software was broken from the beginning.
msg234834 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 18:40
Actually I'm not sure anymore whether NOT specifying a timezone is valid at all.

Even though the Spidermonkey documentation itself states, that it doesn't behave according to the ISO standard, several documents say that specifying the timezone is crucial (either "Z" or "+/-XX:XX").

I also found documents describing the standard which explicitly state that by default the local time is assumed, as JavaScript does.

Either way - there's so much different information and therewith confusion out there, that I highly recommend always specifying the timezone and would consider behaving otherwise as a bug.

Implementations usually don't throw an error but just assume something when the TZ designator is missing, which results in just different meanings. Needless to say that doesn't make the situation any better.
msg234835 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-27 18:47
Do I understand correctly that the request is to append '+00:00' to the result of dt.isoformat() when dt is naive?  If so, -1.  Python's datetime module does not dictate how naive datetime instances should be interpreted.  UTC by default and local by default are both valid choices, but local by default is often preferable.  There are at least two reasons for that:

1. Local time is the "naïve" time.  Using UTC implies certain amount of sophistication.

2. Interpreting naive times as UTC is unnecessary because it is very easy to create aware instances with tzinfo=timezone.utc.

When communicating with javascript and in general when writing software for the Internet, I recommend using aware datetime instances.  For example,

>>> from datetime import *
>>> datetime.now(timezone.utc).isoformat()
'2015-01-27T18:27:33.216857+00:00'
msg234836 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 18:52
from datetime import *
In [4]: datetime.utcnow().isoformat()
Out[4]: '2015-01-27T18:51:18.332566'

When using utcnow() e.g. I would expect the result definitely being marked as UTC.
msg234837 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-27 18:56
> I highly recommend always specifying the timezone and would consider behaving otherwise as a bug.

I agree, and the datetime module lets you do that already:

>>> dt = datetime.now(timezone.utc)
>>> dt.isoformat()
'2015-01-27T18:53:40.380075+00:00'

or if you prefer local timezone,

>>> dt.astimezone().isoformat()
'2015-01-27T13:53:40.380075-05:00'
msg234838 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-27 19:00
> When using utcnow() e.g. I would expect the result definitely being marked as UTC.

Don't use utcnow().  This is a leftover from the times when there was no timezone support in datetime.

The documentation for utcnow [1] already points you in the right direction, but we can consider formally deprecating it together with utcfromtimestamp.

[1] https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow
msg234839 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 19:02
I never said there is no way to result in an ISO 8601 string with UTC stated explicitly.

I showed a case where I think it is correct to assume UTC is stated ( e.g. utcnow()), however the result of isoformat() doesn't do so.
msg234841 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-27 19:09
What is your specific proposal?

As I explained, we cannot assume that naive timezone instances are in UTC because while sometimes they are (as in datetime.utcnow()), more often they are not (as in datetime.now()).  So changing dt.isoformat() when dt is naive is wrong.

Another alternative would be to return aware instances from utcnow(), but that would break a lot of code.
msg234842 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-27 19:11
s/timezone instances/datetime instances/
msg234843 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 19:20
I got that - so marking utcnow() as deprecated seems like a good idea.
But it's not just about utcnow() but also now().

now() also doesn't return any timezone stated by default - which I would still consider as a bug. However making now() require a TZ being specified would probably also break a lot of code. But then, using function called isoformat() - on whatever object, naive or not - I'd expect a timezone being specified - since that's what I finally think the ISO-standard actually says.

I see that my initial proposal doesn't work out, but I'm still not happy with the current situation I'm however also not sure how to change properly without breaking existing code using those functions.
msg234844 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-27 19:26
Mirko,

You may want to review #9527.  I don't think we left any stone unturned in this area.
msg234845 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-01-27 19:32
Just to clarify my problem - then I'll just happily use datetime.now(tzutc()).isoformat()

 - There is datetime.now() which is supposed to be used (no utcnow() anymore)
 - datetime.now() might return a naive object, when no TZ is specified
 - *However* also the naive variant implements the class isoformat() which is described as "Return a string representing the date in ISO 8601 format"
 - ISO 8601 can and should be understood such as the TZ-designator is required (I think we agreed on that).
 - However isoformat() called on a naive object returns a string with no TZ designator

I would at least suggesting adding a note for isoformat() about being called on naive datetime objects.
msg234889 - (view) Author: mirabilos (mirabilos) Date: 2015-01-28 11:36
There’s another minor bug here: UTC should append “Z”, not “+00:00”, which other timezones at that offset can do.

Agreed about no timezone being “floating” time in many instances, e.g. the iCalendar format uses that.
msg234891 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-28 13:36
Why do you call it a bug?  Specifying UTC as +00:00 is perfectly valid by ISO 8601 and some RFCs that are based on the ISO standard recommend against using the Z code.
msg234892 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-01-28 13:38
> ISO 8601 can and should be understood such as the TZ-designator is required (I think we agreed on that).

No.  There is no such requirement in ISO 8601 as far as I remember.
msg236748 - (view) Author: mirabilos (mirabilos) Date: 2015-02-27 14:02
Hm, RFCs are just RFCs and not standards, they can recommend whatever they want, and they can (and do) contradict each other.

I’ve seen things (mostly related to eMail and PIM synchronisation) that require ‘Z’ for UTC proper.

Additionally, +00:00 can be UTC, but it can also be British Winter Time, or DST of UTC-1. ‘Z’ is clear.
msg236750 - (view) Author: Mirko Vogt (mirkovogt) Date: 2015-02-27 14:17
The proper response to that comment probably is: It's called ISO8601 and not RFC8601. And unfortunately ISO stands for "International Standard".
msg236751 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2015-02-27 14:19
I'm a British citizen and I've never once heard the term "British Winter Time", so where does it come from?
msg236752 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-02-27 14:35
mirabilos was referring to Alexander's reference to RFCs that advise against using 'Z'.  RFC are standards once they become formally accepted as such, and often they become de-facto standards before formal acceptance.  

Given that the method is supposedly conforming to a specific standard, it ought to do so...but in addition to the ISO standard there are other de-jure and de-facto standards and deviations to contend with.  Concrete examples are required for decision, I think, if the base standard is ambiguous.  It may be that a new method or a flag controlling the behavior needs to be introduced in order to satisfy specific wide-spread use cases, but those use cases need to be enough motivation to support such an enhancement.  By my reading, so far there have been no such concrete wide spread use cases brought forward to motivate any change other than deprecating utcnow.  ('now' must return naive datetimes to preserve backward compatibility.  If you don't want to use naive datetimes, make sure you don't...the datetime module was originally directly supported only naive datetimes (timezone is recent), so some care is needed.)
msg236762 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-02-27 15:22
> RFCs are just RFCs and not standards

RFCs have a standards track which includes steps such as "Proposed Standard", "Draft Standard", and "Internet Standard".  Once they become Internet Standards, they get an additional designation as STD.  For example, RFC 822 (which is relevant here) is an Internet Standard and also known as STD 11. RFC 3339 ("Date and Time on the Internet: Timestamps") is a Proposed Standard, but widely used and implemented.
msg249299 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2015-08-28 21:36
The premise of this issue is factually incorrect:

> ISO 8601, which states that, if no timezone is specified,
> the string is supposed to be interpreted as UTC implicitly.

The opposite is true: "If no UTC relation information is given with a time representation, the time is assumed to be in local time." <https://en.wikipedia.org/wiki/ISO_8601>

To get timezone specification included in isoformat() output - use aware datetime objects:

>>> from datetime import *
>>> datetime.now(timezone.utc).isoformat()
'2015-01-27T18:27:33.216857+00:00'
History
Date User Action Args
2015-08-28 21:36:37belopolskysetstatus: open -> closed
resolution: not a bug
messages: + msg249299

stage: resolved
2015-02-27 15:22:45belopolskysetmessages: + msg236762
2015-02-27 14:35:26r.david.murraysetmessages: + msg236752
2015-02-27 14:19:39BreamoreBoysetnosy: + BreamoreBoy
messages: + msg236751
2015-02-27 14:17:52mirkovogtsetmessages: + msg236750
2015-02-27 14:02:58mirabilossetmessages: + msg236748
2015-01-28 13:38:29belopolskysetmessages: + msg234892
2015-01-28 13:36:27belopolskysetmessages: + msg234891
2015-01-28 11:36:06mirabilossetnosy: + mirabilos
messages: + msg234889
2015-01-27 19:32:57mirkovogtsetmessages: + msg234845
2015-01-27 19:26:18belopolskysetmessages: + msg234844
2015-01-27 19:20:27mirkovogtsetmessages: + msg234843
2015-01-27 19:11:06belopolskysetmessages: + msg234842
2015-01-27 19:09:44belopolskysetmessages: + msg234841
2015-01-27 19:02:28mirkovogtsetmessages: + msg234839
2015-01-27 19:00:51belopolskysetmessages: + msg234838
2015-01-27 18:56:19belopolskysetmessages: + msg234837
2015-01-27 18:52:56mirkovogtsettype: enhancement -> behavior
messages: + msg234836
2015-01-27 18:47:49belopolskysettype: behavior -> enhancement
messages: + msg234835
versions: + Python 3.6
2015-01-27 18:40:16mirkovogtsetmessages: + msg234834
2015-01-27 17:57:48mirkovogtsetmessages: + msg234832
2015-01-27 17:53:59r.david.murraysetnosy: + r.david.murray, belopolsky
messages: + msg234831
2015-01-27 17:37:29mirkovogtcreate