使Python响应Windows时区变化

13 投票
3 回答
5371 浏览
提问于 2025-04-16 08:05

当Python在Windows系统上运行时,如果在Python程序运行期间更改了时区,time.localtime这个函数就不会报告正确的时间。在Linux系统中,可以随时运行time.tzset来解决类似的问题,但在Windows上似乎没有这样的功能。

有没有办法解决这个问题,而不是做一些奇怪的事情,比如,我不知道……

#!/bin/env python
real_localtime = eval(subprocess.Popen(
    ["python","-c", "import time;repr(time.localtime())"],
    stdout=subprocess.PIPE).communicate()[0])

3 个回答

4

总的来说,在VC版本6中,tzset()这个函数工作得不太好。不过在VC版本8中,tzset()现在可以正常工作了(我觉得版本7也可能可以,但我没有那个版本来确认)。

现在需要做的就是在源代码中启用HAVE_WORKING_TZSET,然后重新编译(并进行测试)。

根据我的经验,处理不同的时区设置时,tzset()这个函数是必不可少的。每当你改变C语言中的TZ变量或者Windows的TIME_ZONE_INFORMATION时,都必须调用tzset()。在VC版本6中这是做不到的,所以HAVE_WORKING_TZSET没有被启用(但现在至少在VC版本8及以上应该启用了)。

顺便说一下,针对我处理的所有日期/时间的内容,我总是有一个SetUtcTime()和UnsetUtcTime()函数,用来将时区设置为GMT,并相应地调用tzset()。我还有一些函数可以临时设置到其他时区。这是唯一正确的方法!根据我多年的经验,其他方法都会出问题。而calendar.timegm()是不正确的,应该使用tzset()。

这里有证据证明它现在可以正常工作(所以去找作者修复Windows代码吧):

(我是在另一台电脑上输入的,希望没有拼写错误,但我想表达的重点不是代码本身)。


注意:以下所有内容都是Python(我通过Python的ctype接口调用C语言)
注意:因为我在这里通过DLL边界设置TZ,并且涉及线程本地等问题,所以不能作为解决方法。必须重新启用HAVE_WORKING_TZSET。

import time
from ctypes import *

dTime = time.time ()
nTime = int (dTime)
intTime = c_int (nTime)

print time.ctime (dTime)
print c_char_p (cdll.msvcrt.ctime (addressof (intTime))).value

-> ... 21:02:40 ... (python)
-> ... 21:02:40 ... (C lang)

cdll.msvcrt._putenv ('TZ=GMT')
cdll.msvcrt._tzset ()

(通常会调用time.tzset(),并且还会调用inittimezone()来更新Python的时区变量)

print time.ctime (dTime)
print c_char_p (cdll.msvcrt.ctime (addressof (intTime))).value

-> ... 21:02:40 ... (python)
-> ... 11:02:40 ... (C lang) <- 底层的VC版本8现在可以正常工作了!

(所以如果HAVE_WORKING_TZSET在版本8及以上被定义,你会得到这个结果:)

-> ... 11:02:40 ... (python)
-> ... 11:02:40 ... (C lang)


只需查看源代码就能明白我的意思。

我刚刚用最新的Python '2.0'系列:2.7.2检查过这个。

4

不,这个问题不能通过其他方式解决,必须按照你之前的做法来处理。虽然这听起来有点荒谬,但如果你在Windows上需要正确的时区,而这个时区在程序运行过程中发生了变化,那就必须这样做。

这可能并不是一个bug(文档上很清楚地说明了tzset()函数只在Unix系统上可用)。更可能是Windows的一个缺陷,导致Python的开发者无法在这个系统上实现tzset()。你可以提出一个功能增强的请求,但自从Python 2.3(已经7年了)以来,一直都是这样,所以实际上很可能不会被实现。

8

一个更合理的解决方案是使用Kernel32的GetLocalTime,可以通过pywin32或者ctypes来实现。这样一来,任何时区的变化都会立即反映出来。

import ctypes
class SYSTEMTIME(ctypes.Structure):
    _fields_ = [
        ('wYear', ctypes.c_int16),
        ('wMonth', ctypes.c_int16),
        ('wDayOfWeek', ctypes.c_int16),
        ('wDay', ctypes.c_int16),
        ('wHour', ctypes.c_int16),
        ('wMinute', ctypes.c_int16),
        ('wSecond', ctypes.c_int16),
        ('wMilliseconds', ctypes.c_int16)]

SystemTime = SYSTEMTIME()
lpSystemTime = ctypes.pointer(SystemTime)
ctypes.windll.kernel32.GetLocalTime(lpSystemTime)
print SystemTime.wHour, SystemTime.wMinute 

撰写回答