1

EDIT2: This question is assuming a POSIX-ish platform with Python linked against Glibc.

On my system, round-trip conversion using the %z formatting directive using Python’s time library fails to parse the offset part of ISO 8601 formatted timestamps. This snippet:

import time
time.daylight = 0
fmt = "%Y-%m-%dT%H:%M:%SZ%z"
a=time.gmtime()
b=time.strftime(fmt, a)
c=time.strptime(b, fmt)
d=time.strftime(fmt, c)

print ("»»»»", a == c, b == d)
print ("»»»»", a.tm_zone, b)
print ("»»»»", c.tm_zone, d)

outputs:

»»»» False False
»»»» GMT 2018-02-16T09:26:34Z+0000
»»»» None 2018-02-16T09:26:34Z

whereas the expected output would be

»»»» True True
»»»» GMT 2018-02-16T09:26:34Z+0000
»»»» GMT 2018-02-16T09:26:34Z+0000

How do I get %z to respect that offset?

  • Python 3.3.2 and 3.6.4
  • [Glibc 2.17 and 2.25 ⇒ see below!]

EDIT: Glibc can be acquitted as proven by this C analogue:

#define _XOPEN_SOURCE
#define _DEFAULT_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* 2018-02-16T09:59:21Z+0000 */
#define ISO8601_FMT "%Y-%m-%dT%H:%M:%SZ%z"

int main () {
    const time_t t0 = time (NULL);
    struct tm a;
    char b [27];
    struct tm c;
    char d [27];

    (void)setenv ("TZ", "UTC", 1);
    tzset ();
    daylight = 0;

    (void)gmtime_r (&t0, &a);                       /* a=time.gmtime () */
    (void)strftime (b, sizeof(b), ISO8601_FMT, &a); /* b=time.strftime (fmt, a) */
    (void)strptime (b,            ISO8601_FMT, &c); /* c=time.strptime (b, fmt) */
    (void)strftime (d, sizeof(d), ISO8601_FMT, &c); /* d=time.strftime (fmt, c) */

    printf ("»»»» b ?= d %s\n",  strcmp (b, d) == 0 ? "yep" : "hell, no");
    printf ("»»»» %d <%s> %s\n", a.tm_isdst, a.tm_zone, b);
    printf ("»»»» %d <%s> %s\n", c.tm_isdst, c.tm_zone, d);
}

Which outputs

»»»» b ?= d yep
»»»» 0 <GMT> 2018-02-16T10:28:18Z+0000
»»»» 0 <(null)> 2018-02-16T10:28:18Z+0000
phg
  • 286
  • 4
  • 15

2 Answers2

2

With the "time.gmtime()" naturally you are getting the UTC time, so the offset will be always +0000, therefore an output string "2018-02-16T09:26:34Z" is correct for the ISO8601. If you want absolutely the "+0000" add it manually because it will be alway the same:

d = time.strftime(fmt, c) + '+0000'
debiandado
  • 46
  • 3
  • The timestamp is correct on the first invocation of ``strftime()``. Then ``strptime()`` results in a ``time.struct_time`` object with different values: ``.tm_zone`` field is *None* instead of *"GMT"*, and ``.tm_isdst`` has *-1* instead of *0*. I’d expect these to be preserved as they are with the obsolete ``%Z`` specifier. – phg Feb 16 '18 at 10:02
  • Changed in version 3.3: tm_gmtoff and tm_zone attributes are available on platforms with C library supporting the corresponding fields in struct tm. [link](https://docs.python.org/3.5/library/time.html#time.strptime). Also my plattform doesn t support it. – debiandado Feb 16 '18 at 12:47
  • Can u use the datetime library? Normally I used it to solve these kind of issues. – debiandado Feb 16 '18 at 12:48
1

I don't pretend to have the solution to generate the proper hour shift according to the time zone, but I can explain what happens here.

As hinted in Python timezone '%z' directive for datetime.strptime() not available answers:

  • strptime is implemeted in pure python so it has a constant behaviour
  • strftime depends on the platform/C library it was linked against.

On my system (Windows, Python 3.4), %z returns the same thing as %Z ("Paris, Madrid"). So when strptime tries to parse it back as digits, it fails. Your code gives me:

ValueError: time data '2018-02-16T10:00:49ZParis, Madrid' does not match format '%Y-%m-%dT%H:%M:%SZ%z'

It's system dependent for the generation, and not for the parsing.

This dissymetry explains the weird behaviour.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219