[Patches] [ python-Patches-762963 ] timemodule.c: Python loses current timezone

SourceForge.net noreply at sourceforge.net
Sat Feb 24 01:02:53 CET 2007


Patches item #762963, was opened at 2003-06-30 04:18
Message generated for change (Comment added) made by pboddie
You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=305470&aid=762963&group_id=5470

Please note that this message will contain a full copy of the comment thread,
including the initial issue submission, for this request,
not just the latest update.
Category: Modules
Group: None
Status: Open
Resolution: None
Priority: 5
Private: No
Submitted By: Doug Quale (quale)
Assigned to: Martin v. Löwis (loewis)
Summary: timemodule.c: Python loses current timezone

Initial Comment:
Python routines in timemodule.c sometimes force glibc
to use GMT instead of the correct timezone.

I think this is a longstanding bug, but I have only
looked at Python 2.2 and 2.33b1. This bug has been
reported before (510218) but it was misdiagnosed and
closed. It also came up recently in on the python-list
(http://aspn.activestate.com/ASPN/Mail/Message/python-list/1564384)
where it was also misdiagnosed.

Standard C and Python use a struct tm with 9 values. 
BSD influenced C libraries like glibc have an extra
field (tm_gmtoff) that keeps the offset from UTC. 
BSD-style struct tm values know the correct timezone
associated with the time, but Python and Standard C
struct tm values don't so they can only assume the
current timezone.

Ideally Python would always treat stuct tm-style time
tuples as being associated with the current timezone.
Unfortunately in many cases Python forces glibc to use
GMT rather than the current timezone.  You can see the
problem most graphically with the %z format in
strftime().  In the transcript Python incorrectly gives
the timezone offset as +0000 even though it gets the
timezone name (CDT) correct:

$ python2.3
Python 2.3b1+ (#2, Jun  4 2003, 03:03:32) 
[GCC 3.3 (Debian)] on linux2
Type "help", "copyright", "credits" or "license" for
more information.
>>> import time
>>> now = time.localtime()
>>> print now
(2003, 6, 29, 20, 3, 52, 6, 180, 1)
>>> RFC822 = '%a, %d %b %Y %T %z'
>>> print time.strftime(RFC822, now)
Sun, 29 Jun 2003 20:03:52 +0000
>>> print time.strftime(RFC822)
Sun, 29 Jun 2003 20:05:27 -0500
>>> print time.strftime('%Z', now)
CDT

The correct timezone is CDT and the correct offset is
-0500. Notice that when time.strftime() computes the
current time it it gets the correct timezone offset,
but when the struct tm tuple is passed in it thinks the
timezone offset is 0.  (glibc does know that the
correct timezone name is CDT even when passed a bad
struct tm value, but that's not surprising since the
timezone name is not stored in struct tm and it is not
computed from timezone offset.)

The problem is in the gettmargs() function in
timemodule.c. When getmargs() parses a Python tm tuple
it leaves the tm_gmtoff field zeroed. This specifically
tells glibc that the timezone offset is 0, which is
wrong for most people. (BSD libc may also be affected.)
This problem does not occur when time_strfrtime() gets
the current time itself since that codepath uses a
struct tm value directly from the libc localtime(),
leaving the tm_gmtoff field intact.

Fortunately the fix is almost trivial. A call to
mktime() will normalize the fields in the struct tm
value, including the tm_gmtoff field.  I
conditionalized the patch only on HAVE_MKTIME. I'm
pretty sure there's an autoconfigure test for tm_gmtoff
and it would probably be better to use that.


----------------------------------------------------------------------

Comment By: Paul Boddie (pboddie)
Date: 2007-02-24 01:02

Message:
Logged In: YES 
user_id=226443
Originator: NO

I've just tried this patch against Python 2.4.4, changing the define
tested to HAVE_TM_ZONE (in line with the autoconf test AC_STRUCT_TIMEZONE),
but it didn't seem to produce the desired result (despite activating the
new code in calls to gettmargs):

Python 2.4.4 (#1, Feb 23 2007, 12:37:26)
[GCC 3.3.5 (Debian 1:3.3.5-8ubuntu2.1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> lt = time.localtime()
>>> time.strftime("%Y-%m-%d %H:%M:%S %z", lt)
'2007-02-24 00:45:23 +0000'

The %z output should be '+0100'. According to the mktime man page, the
time zone should be set "as though mktime() called tzset()", but a simple C
test program revealed that either the tm_gmtoff field remains unset or is
set to zero (which is not appropriate on my system).

In other words, mktime does not miraculously restore the time zone
information prior to the structure initialisation in gettmargs, at least on
my system.

----------------------------------------------------------------------

Comment By: Doug Quale (quale)
Date: 2003-08-21 18:57

Message:
Logged In: YES 
user_id=812266

I have attached a little unittest with two tests showing the
problem. I stole some code from Lib/test/test_time.py for
the test that checks behavior in the US Eastern timezone.
That test won't be run if tzset() isn't available, but this
is OK since the only libc's that will show this problem are
BSD-inspired and will have tzset().

----------------------------------------------------------------------

Comment By: Raymond Hettinger (rhettinger)
Date: 2003-08-17 23:54

Message:
Logged In: YES 
user_id=80475

Can you attach a unittest that fails before the patch and 
succeeds afterward?

----------------------------------------------------------------------

Comment By: Doug Quale (quale)
Date: 2003-08-09 19:16

Message:
Logged In: YES 
user_id=812266

Martin is right. The call to mktime() in my patch overwrites
the tm_isdst field. This field is in the Python time tuple
and is correctly set by the code immediately above. I put
the call to mktime in the wrong place. Instead of going at
the end of the routine it belongs immediately after the
memset used to zero the structure.

Sorry about this botch. I have attached a corrected patch.

----------------------------------------------------------------------

Comment By: Martin v. Löwis (loewis)
Date: 2003-08-09 15:00

Message:
Logged In: YES 
user_id=21627

The patch is wrong. It changes the behaviour of

time.asctime(time.gmtime(time.time()))

which it shouldn't do. The problem is real, though, and
might need to be solved by exposing the tm_gmtoff field
where available.

----------------------------------------------------------------------

Comment By: Raymond Hettinger (rhettinger)
Date: 2003-07-12 01:52

Message:
Logged In: YES 
user_id=80475

Martin, can you diagnose whether this should be closed, 
applied, or deferred?

----------------------------------------------------------------------

Comment By: Doug Quale (quale)
Date: 2003-07-10 20:14

Message:
Logged In: YES 
user_id=812266

The problem isn't in strftime. The problem is in gettmargs() in 
timemodule.c.

Python assumes that broken down time tuples are in the local 
timezone. The gettmargs() routine in timemodule.c is bugged 
on GNU libc and possibly other BSD-inspired C libraries. 
gettmargs() is supposed to take a Python broken down time 
tuple and convert it to a C struct tm. The Python time tuple 
is assumed to be in the local time zone, so the struct tm 
should be in the local timezone also. In glibc, struct tm has 
timezone fields so each struct tm knows its own timezone. 
The gettmargs() routine never fills in these extra fields so it 
always creates a struct tm in GMT. The appropriate behavior 
would be to set tm_gmtoff to the local timezone offset. My 
patch fixes gettmargs() to create struct tm's in the local 
timezone for C libraries that have the tm_gmtoff field in struct 
tm.

As to the docs issue, the Python docs say that other formats 
may be supported than the ones listed. In reality, strftime() is 
passed to the underlying C library so the format codes 
supported are whatever the C library supports. The doc 
statement "Additional directives may be supported on certain 
platforms, but only the ones listed here have a meaning 
standardized by ANSI C" is wrong, or at least not up to date. 
C99 specifies several strftime format codes that are not listed 
including %z. I think Tim Smith also mentions this in a Python 
list posting from earlier this year.

In the Python time module, the docs say strftime('format') is 
the same as strftime('format', localtime()). This is simply 
broken right now on glibc as has been reported by more than 
one person:

>>> strftime('%z')
'-0500'
>>> strftime('%z', localtime())
'+0000'

This is wrong. Unsupported format specifiers do not have this 
effect, for example:

>>> strftime('%L')
'%L'
>>> strftime('%L', localtime())
'%L'

This behavior is correct.

A final note on the patch: I should have looked closer at the 
timemodule.c source. It already uses the appropriate #ifdef in 
other places.  Instead of #ifdef HAVE_MKTIME my patch 
should be conditionalized #ifdef HAVE_STRUCT_TM_TM_ZONE.

It's kind of amusing to write up this long of a justification for 
what is essentially a 3 line patch.

----------------------------------------------------------------------

Comment By: Brett Cannon (bcannon)
Date: 2003-07-10 00:36

Message:
Logged In: YES 
user_id=357491

So the problem is with the %z directive not reporting the proper 
timezone offset, correct?  If you look at http://www.python.org/
dev/doc/devel/lib/module-time.html , though, you will notice that 
%T is not supported as a directive for strftime or strptime nor has 
it ever been supported.  Same goes for %T although it works in 
your example.

Since the docs do not say that these directives are supported I am 
closing this patch since it would require altering both strftime and 
stprtime to support %z on all platforms which this patch does not.

----------------------------------------------------------------------

You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=305470&aid=762963&group_id=5470


More information about the Patches mailing list