Python: strftime() UTC Offset Not working as Expected in Windows
Asked Answered
I

5

16

Every time I use:

time.strftime("%z")

I get:

Eastern Daylight Time

However, I would like the UTC offset in the form +HHMM or -HHMM. I have even tried:

time.strftime("%Z")

Which still yields:

Eastern Daylight Time

I have read several other posts related to strftime() and %z always seems to return the UTC offset in the proper +HHMM or -HHMM format. How do I get strftime() to output in the +HHMM or -HHMM format for python 3.3?

Edit: I'm running Windows 7

Inaugurate answered 7/8, 2014 at 16:20 Comment(9)
I can't reproduce what you've observed: In [2]: time.strftime("%z") Out[2]: '-0500'. Can you give us more details? What you're saying contradicts the documentation.Monotone
@PatrickCollins I’m also not getting an offset but 'Mitteleuropäische Sommerzeit' instead (Windows 8, Python 2.7, 3.3, and 3.4).Gillispie
I'm getting similar results on Windows 7 time.strftime("%z") -> "Eastern Daylight Time" for Python 2.6.6, 2.7.2, 3.2.2, and 3.3.2.Soothsay
Unfortunately the libraries under Windows don't support %z, and Python relies on those libraries. See msdn.microsoft.com/en-us/library/fe06s4ak.aspxCyna
@MarkRansom So this looks like a documentation error on Python's part that %z does not work properly on Windows.Soothsay
@cpburnz True, there should at least be a note saying you might get different results on different platforms.Cyna
Window's incorrect result for %z is an open bug: bugs.python.org/issue20010Soothsay
The bug is basically arguing for changing the documentation to better match the facts. (Also, for those interested in 2.x, the docs don't even mention %z, so the issue doesn't arise in the first place.)Jaal
What answer do you actually want here? Eastern Time is defined as -04:56, with a 4-minute correction in 1920, and of course yearly corrections back and forth an hour as DST begins and ends. So, do you want -04:56, or -05:00, or either -05:00 or -04:00 depending on whether the program is being run during DST, or any of the above depending on which offset was in effect on a particular date, or…?Jaal
G
7

For a proper solution, see abarnert’s answer below.


You can use time.altzone which returns a negative offset in seconds. For example, I’m on CEST at the moment (UTC+2), so I get this:

>>> time.altzone
-7200

And to put it in your desired format:

>>> '{}{:0>2}{:0>2}'.format('-' if time.altzone > 0 else '+', abs(time.altzone) // 3600, abs(time.altzone // 60) % 60)
'+0200'

As abarnert mentioned in the comments, time.altzone gives the offset when DST is active while time.timezone does for when DST is not active. To figure out which to use, you can do what J.F. Sebastian suggested in his answer to a different question. So you can get the correct offset like this:

time.altzone if time.daylight and time.localtime().tm_isdst > 0 else time.timezone

As also suggested by him, you can use the following in Python 3 to get the desired format using datetime.timezone:

>>> datetime.now(timezone.utc).astimezone().strftime('%z')
'+0200'
Gillispie answered 7/8, 2014 at 16:35 Comment(12)
This is a good answer. I edited because your code had some syntax errors. I think it is also worth pointing out MarkRansom's comment: "Unfortunately the libraries under Windows don't support %z, and Python relies on those libraries. See msdn.microsoft.com/en-us/library/fe06s4ak.aspx"Inaugurate
@Inaugurate Your attempted edit did not fix anything and was at such rejected. // is integer division and a valid operation in Python. Your edit broke the code to produce the desired output.Gillispie
That's odd. My edit runs fine for me while your code fails. However, I'm running Python 3. You are running Python 2 I guess?Inaugurate
@Inaugurate No, it works on both Python 3 and 2. What error do you get?Gillispie
I'll have to take that back. It's working for me now. I must not have fully copied your text the first time.Inaugurate
altzone is "The offset of the local DST timezone, in seconds west of UTC, if one is defined. This is negative if the local DST timezone is east of UTC (as in Western Europe, including the UK). Only use this if daylight is nonzero." Of course if you're specifically asking for "Eastern Daylight Time" rather than "Eastern Time", this is the right answer, but in general, it's going to be wrong half the year in timezones with DST and useless in timezones without.Jaal
1. time.daylight does not say whether DST is in effect right now. 2. the whole approach might fail in some cases. To resolve both issues, see my answer to "Getting computer's utc offset in Python".Biyearly
@J.F.Sebastian: Right, time.daylight says whether DST exists (or ever existed) in the timezone. If it's true, you don't know whether to use altzone or timezone unless you specify which date you're asking about. (Which isn't surprising, given that the question is meaningless unless you know which date you're asking about.)Jaal
@abarnert: a) "ever" is incorrect: daylight may be zero for a timezone even it had observed DST in the past b) as I said the whole [daylight] approach is not flexible enough: "For years where the rules change, these constants can provide incorrect data.", for example, see mercurial bug mentioned there. For the reliable way to get utc offset, see my answer (with pytz) c) the date is implied. time.strftime("%z") in the question works with the current time.Biyearly
@poke: use tm_isdst > 0 test. In general, tm_isdst can be less than zero.Biyearly
@J.F.Sebastian: I was agreeing with you that "the whole approach is not flexible enough". I said as much in my comment yesterday.Jaal
@Jaal I just linked to your answer now considering that it covers the problem better than mine.Gillispie
J
8

In 2.x, if you look at the docs for time.strftime, they don't even mention %z. It's not guaranteed to exist at all, much less to be consistent across platforms. In fact, as footnote 1 implies, it's left up to the C strftime function. In 3.x, on the other hand, they do mention %z, and the footnote that explains that it doesn't work the way you'd expect is not easy to see; that's an open bug.

However, in 2.6+ (including all 3.x versions), datetime.strftime is guaranteed to support %z as "UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive)." So, that makes for a pretty easy workaround: use datetime instead of time. Exactly how to change things depends on what exactly you're trying to do — using Python-dateutil tz then datetime.now(tz.tzlocal()).strftime('%z') is the way to get just the local timezone formatted as a GMT offset, but if you're trying to format a complete time the details will be a little different.

If you look at the source, time.strftime basically just checks the format string for valid-for-the-platform specifiers and calls the native strftime function, while datetime.strftime has a bunch of special handling for different specifiers, including %z; in particular, it will replace the %z with a formatted version of utcoffset before passing things on to strftime. The code has changed a few times since 2.7, and even been radically reorganized once, but the same difference is basically there even in the pre-3.5 trunk.

Jaal answered 14/10, 2014 at 20:34 Comment(1)
On Python 3, using only stdlib: datetime.now(timezone.utc).astimezone().strftime('%z')Biyearly
G
7

For a proper solution, see abarnert’s answer below.


You can use time.altzone which returns a negative offset in seconds. For example, I’m on CEST at the moment (UTC+2), so I get this:

>>> time.altzone
-7200

And to put it in your desired format:

>>> '{}{:0>2}{:0>2}'.format('-' if time.altzone > 0 else '+', abs(time.altzone) // 3600, abs(time.altzone // 60) % 60)
'+0200'

As abarnert mentioned in the comments, time.altzone gives the offset when DST is active while time.timezone does for when DST is not active. To figure out which to use, you can do what J.F. Sebastian suggested in his answer to a different question. So you can get the correct offset like this:

time.altzone if time.daylight and time.localtime().tm_isdst > 0 else time.timezone

As also suggested by him, you can use the following in Python 3 to get the desired format using datetime.timezone:

>>> datetime.now(timezone.utc).astimezone().strftime('%z')
'+0200'
Gillispie answered 7/8, 2014 at 16:35 Comment(12)
This is a good answer. I edited because your code had some syntax errors. I think it is also worth pointing out MarkRansom's comment: "Unfortunately the libraries under Windows don't support %z, and Python relies on those libraries. See msdn.microsoft.com/en-us/library/fe06s4ak.aspx"Inaugurate
@Inaugurate Your attempted edit did not fix anything and was at such rejected. // is integer division and a valid operation in Python. Your edit broke the code to produce the desired output.Gillispie
That's odd. My edit runs fine for me while your code fails. However, I'm running Python 3. You are running Python 2 I guess?Inaugurate
@Inaugurate No, it works on both Python 3 and 2. What error do you get?Gillispie
I'll have to take that back. It's working for me now. I must not have fully copied your text the first time.Inaugurate
altzone is "The offset of the local DST timezone, in seconds west of UTC, if one is defined. This is negative if the local DST timezone is east of UTC (as in Western Europe, including the UK). Only use this if daylight is nonzero." Of course if you're specifically asking for "Eastern Daylight Time" rather than "Eastern Time", this is the right answer, but in general, it's going to be wrong half the year in timezones with DST and useless in timezones without.Jaal
1. time.daylight does not say whether DST is in effect right now. 2. the whole approach might fail in some cases. To resolve both issues, see my answer to "Getting computer's utc offset in Python".Biyearly
@J.F.Sebastian: Right, time.daylight says whether DST exists (or ever existed) in the timezone. If it's true, you don't know whether to use altzone or timezone unless you specify which date you're asking about. (Which isn't surprising, given that the question is meaningless unless you know which date you're asking about.)Jaal
@abarnert: a) "ever" is incorrect: daylight may be zero for a timezone even it had observed DST in the past b) as I said the whole [daylight] approach is not flexible enough: "For years where the rules change, these constants can provide incorrect data.", for example, see mercurial bug mentioned there. For the reliable way to get utc offset, see my answer (with pytz) c) the date is implied. time.strftime("%z") in the question works with the current time.Biyearly
@poke: use tm_isdst > 0 test. In general, tm_isdst can be less than zero.Biyearly
@J.F.Sebastian: I was agreeing with you that "the whole approach is not flexible enough". I said as much in my comment yesterday.Jaal
@Jaal I just linked to your answer now considering that it covers the problem better than mine.Gillispie
S
4

Use time.timezone to get the time offset in seconds.

Format it using :

("-" if time.timezone > 0 else "+") + time.strftime("%H:%M", time.gmtime(abs(time.timezone)))

to convert the same to +/-HH:MM format.

BTW isn't this supposed to be a bug ? According to strftime docs.

Also I thought this SO answer might help you to convert from Zone offset string to HH:MM format. But since "%z" is not working as expected, I feel its moot.

NOTE: The time.timezone is immune to Daylight savings.

Sturrock answered 7/8, 2014 at 16:40 Comment(1)
Considering that the question asks about the offset for Eastern Daylight Time, I don't think he wants "immune to daylight savings". (Unfortunately, I think the question is underspecified as to what he actually does want, but I don't think this is it.)Jaal
D
0

It will come as no surprise that this bug persists in, what is the latest Windows version available currently, Win 10 Version 1703 (Creators). However, time marches on and there is a lovely date-and-time library called pendulum that does what the question asks for. Sébastien Eustace (principal author of the product?) has shown me this.

>>> pendulum.now().strftime('%z')
'-0400'

pendulum assumes UTC/GMT unless told otherwise, and keeps timezone with the date-time object. There are many other possibilities, amongst them these:

>>> pendulum.now(tz='Europe/Paris').strftime('%z')
'+0200'
>>> pendulum.create(year=2016, month=11, day=5, hour=16, minute=23, tz='America/Winnipeg').strftime('%z')
'-0500'
>>> pendulum.now(tz='America/Winnipeg').strftime('%z')
'-0500'
Decanter answered 15/6, 2017 at 15:49 Comment(0)
S
0

I came here looking for a solution as I needed to output the full date and time including timezone for another program which required it in RFC3339 format https://datatracker.ietf.org/doc/html/rfc3339 (yeah picky right?!) :)

The output from strftime doesn't confirm to this particular standard it appears, so in the end I ended up using:

from datetime import datetime, timezone
print(datetime.now(timezone.utc).astimezone().isoformat())

which outputs the following:

2024-03-06T10:43:00.348657+00:00

I know this isn't quite an answer to the question, but it seemed a sensible place to put it in case anyone is headed down the same rabbit hole as I was :)

Sanative answered 6/3 at 10:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.