上海时区偏差6分钟问题
今天写一个计算当天还剩余多少时间的函数时,发现了一个问题:上海时区偏差6分钟。
我需要用这个函数返回的时间来设置Redis
的缓存时间,当我去Redis
中查询记录的有效时间时,发现这个时间和当前时间差了几分钟,于是我开始在本地调试,最后发现是上海时区偏差6分钟。
问题
出现问题的的完整测试代码如下:
#!/Users/matias/matias/MT/MTGithub/bingwallpaper/venv/bin/python3
# coding=utf-8
'''
Author: matiastang
Date: 2025-01-17 11:26:02
LastEditors: matiastang
LastEditTime: 2025-01-17 12:32:30
FilePath: /bingwallpaper/app/utils/time_util.py
Description: 时间
'''
import pytz
from datetime import datetime
from enum import Enum
class TimezoneName(Enum):
"""时区名称"""
UTC = 'UTC'
SHANGHAI = 'Asia/Shanghai'
"""上海"""
def get_remaining_seconds_in_day(timezone_name: TimezoneName = TimezoneName.UTC) -> int:
"""
获取指定时区当天剩余的时间(秒数)
Args:
timezone_name (str): 时区名称(如 "Asia/Shanghai", "UTC")
Raises:
ValueError: 未知时区
Returns:
int: 当天剩余时间的秒数
"""
try:
# 获取指定时区
timezone = pytz.timezone(timezone_name.value)
# 当前时区时间
now = datetime.now(timezone)
# 当天结束时间(午夜)
end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59, tzinfo=timezone)
# 计算剩余时间
print(end_of_day, now) # 2025-01-17 23:59:59+08:06 2025-01-17 12:21:27.589801+08:00
remaining_time = end_of_day - now
return int(remaining_time.total_seconds())
except pytz.UnknownTimeZoneError:
raise ValueError(f"未知时区: {timezone_name}")
if __name__ == "__main__":
"""
测试
python3 -m app.utils.time_util
"""
print(get_remaining_seconds_in_day(TimezoneName.SHANGHAI))
运行测试之后,打印的信息如下:
$ python3 -m app.utils.time_util
2025-01-17 23:59:59+08:06 2025-01-17 12:21:27.589801+08:00
41551
可以看到时区这里偏差了6分钟。
分析
那这个偏差,到底是怎么回事呢?
原来这是上海的历史时区规则的问题。在现代时区(中国标准时间 CST
)中,北京时间是 UTC+8
,这已成为标准。虽然上海和北京是不同的城市,但它们同属于中国的标准时区(中国标准时间,CST
),并且在时区数据库中共享 Asia/Shanghai
的时区名称。在 20
世纪初,中国各地时区标准未统一,有多个地方时间。上海曾经使用东八区 +8:06:00
的时间偏移(1900
年代早期)。一些时区数据库会保留这样的历史信息。
Python
的 pytz
或内置的 zoneinfo
模块会基于 IANA
(IANA Time Zone Database
)时区数据库解释时间。具体表现为:
- 如果你创建的时间是一个未来的时间(例如
2025-01-17
),它会使用现代标准,即+08:00
。 - 如果你创建的时间是在历史时间(例如
20
世纪初),则可能会显示历史偏移,如+08:06
。
虽然我们使用了pytz
库,但是我们只用它来获取时区,生成时间使用的是datetime
。所以我们可以怀疑是datetime
的问题:指定了时区为Asia/Shanghai
后生成的时间为+8:06:00
的时间偏移。
我们使用下面的几种获取时间的方式测试一下
timezone = pytz.timezone(timezone_name.value)
end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59)
end_of_day_test = datetime(now.year, now.month, now.day, 23, 59, 59, tzinfo=timezone)
end_of_day_time_tz = timezone.localize(end_of_day)
print(end_of_day, end_of_day_test, end_of_day_time_tz)
# 2025-01-17 23:59:59 2025-01-17 23:59:59+08:06 2025-01-17 23:59:59+08:00
可以看出,确实是datetime
指定时区生成时,还是使用的老的时区+8:06:00
。
修改
知道原因后我们将函数进行修改如下:
def get_remaining_seconds_in_day(timezone_name: TimezoneName = TimezoneName.UTC) -> int:
"""
获取指定时区当天剩余的时间(秒数)
Args:
timezone_name (str): 时区名称(如 "Asia/Shanghai", "UTC")
Raises:
ValueError: 未知时区
Returns:
int: 当天剩余时间的秒数
"""
try:
# 获取指定时区
timezone = pytz.timezone(timezone_name.value)
# 当前时区时间
now = datetime.now()
# 当天结束时间(午夜)
end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59)
# 转换时间
now_time_tz = timezone.localize(now)
end_of_day_time_tz = timezone.localize(end_of_day)
# 计算剩余时间
print(end_of_day_time_tz, now_time_tz) # 2025-01-17 23:59:59+08:00 2025-01-17 12:33:35.750785+08:00
remaining_time = end_of_day_time_tz - now_time_tz
return int(remaining_time.total_seconds())
except pytz.UnknownTimeZoneError:
raise ValueError(f"未知时区: {timezone_name}")
这次我们使用timezone.localize
来将标准时间,转换为Asia/Shanghai
时区的时间。 再次测试,结果如下:
$ python3 -m app.utils.time_util
2025-01-17 23:59:59+08:00 2025-01-17 12:33:35.750785+08:00
41183
思考
既然是时区标准的问题,那可能其他语言或库可能也会有类似的问题。后面用到其他语言或库时,可以测试一下,看对这个问题有兼容没。