首页 > 解决方案 > pytz 时区在夏令时的偏移量错误

问题描述

我对 pytz 和夏令时有疑问。当我使用 timezone 时Europe/Berlin,它​​总是使用没有 DST 的时区偏移量。

最小的例子:

print(repr(pytz.timezone("Europe/Berlin")))
<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>
# Should probably be something like <DstTzInfo 'Europe/Berlin' CET+2:00:00 DST>

# Usage
from django.utils import timezone
from datetime import datetime
datetime_now = timezone.now()
print(my_time)
# Result: 00:00:00
print(datetime.combine(datetime_now, my_time, tzinfo=timezone.get_current_timezone()))
# Result: 2020-04-04 00:00:00+01:00, should be 2020-04-04 00:00:00+02:00

我的用例的最小示例是闹钟。用户将时钟设置为06:00(不考虑时区)并且时钟应该06:00在当前时区响起,即,06:00+02当它是 DST 时,06:00+01否则为Europe/Berlin.


该实现是一个 Django 模型,django.models.TimeField用于不知道时间(例如06:00),我想通过创建一个日期时间对象来将它与当前时间和其他时间字段进行比较,该对象具有存储在TimeField.

我愿意接受关于时间对象的不同建议(例如使用或不使用django.utils.timezone),只要我可以创建可以相互比较的日期时间对象并使用 timedelta 对象(或一些类似方法)递增/递减。


另一个最小的例子(Django 仅用于获取当前时区):

from django.utils import timezone
import datetime

tz = timezone.get_current_timezone()
time_now = datetime.datetime.now(tz=tz)
clock_time = datetime.time(1,2)
combined_time = datetime.combine(time_now, clock_time, tzinfo=tz)
print(tz)
print(time)
print(time_now)
print(combined_time)

结果是

Europe/Berlin
01:02:00
2020-04-12 18:50:11.934754+02:00
2020-04-12 01:02:00+01:00

标签: pythontimezonepytz

解决方案


tzinfo在构建时区感知日期时间时避免使用。看到这个帖子

由于您使用的是 Django,假设TIME_ZONE = 'Europe/Berlin'我们可以使用make_aware

from django.utils import timezone
from datetime import datetime, time

# Get a localized datetime so that .combine gets the local date
local_now = timezone.localtime()
# localtime() is a shortcut for
# timezone.now().astimezone(timezone.get_current_timezone())

clock_time = time(1, 2)
combined_time = timezone.make_aware(datetime.combine(local_now, clock_time))
print(combined_time)

它会打印

2020-04-21 01:02:00+02:00

或者,使用localizepytz中的函数(无论如何都在make_aware函数定义中使用,但请查看下面的详细信息):

tz = timezone.get_current_timezone()  # or pytz.timezone('Europe/Berlin')
combined_time = tz.localize(datetime.combine(local_now, clock_time))
# 2020-04-21 01:02:00+02:00

如果您看到timezone.py 的 Django 代码,这些函数基本上是 pytz 包装器。特别要检查 和make_awarelocaltime定义now

make_aware不过和之间有一个特别的区别localize。两者都接受参数is_dst,但对于 Django make_aware,它是None默认的,而它是False用于 pytz的。如果用户在输入 DST 时写入的时间不存在或发生两次,则此差异对您的情况很重要。在这里,具有is_dst=None将分别使函数 raiseNonExistentTimeErrorAmbiguousTimeError。否则,布尔值会导致它猜测


示例: 今年Europe/Berlin,时钟在 3 月 29 日凌晨 2:00 前进了一小时。因此,当地时间凌晨 2:30 并没有发生。Python 根据以下内容处理此输入is_dst

time_doesnt_exist = datetime(2020, 3, 29, 2, 30, 0)
print(tz.localize(time_doesnt_exist, is_dst=None))
# Raises NonExistentTimeError
print(tz.localize(time_doesnt_exist, is_dst=True))
2020-03-29 02:30:00+02:00
print(tz.localize(time_doesnt_exist, is_dst=False))
2020-03-29 02:30:00+01:00

要获得引发异常的行为localize

combined_time = tz.localize(datetime.combine(local_now, clock_time), is_dst=None)

make_aware改为不加注:

combined_time = timezone.make_aware(
  datetime.combine(local_now, clock_time),
  is_dst=False,  # Or True...
)

提醒一句:本地化时间的算术

对本地化日期时间进行算术运算需要调用normalize作为 DST 问题的解决方法,当它们出现时

time_before_dst = datetime(2020, 3, 29, 1, 50, 0)
local_time_before_dst = tz.localize(time_before_dst) 
new_time = local_time_before_dst + timedelta(minutes=40)
print(new_time)
# 2020-03-29 02:30:00+01:00
# Didn't switch to DST!
print(tz.normalize(new_time))
# 2020-03-29 03:30:00+02:00
# Correctly did the switch

推荐阅读