尝试在UTC和特定时区之间进行转换时,我遇到奇怪的行为。我希望有人能解释为什么我会看到这种行为,以及获取时区信息的更“正确”的方式是什么。
代码:
import pytz
import datetime
from django.utils import timezone
print(timezone.now())
print(pytz.utc.localize(datetime.datetime.now()))
print('\n')
def get_local_and_utc_date_ranges(days=1500, days_ago=2, local_timezone="America/Asuncion"):
seller_timezone = pytz.timezone(local_timezone)
utc_timezone = pytz.utc
seller_today = timezone.now().astimezone(seller_timezone)
seller_days_ago = seller_today - timezone.timedelta(days=days_ago)
local_date_end = seller_days_ago.replace(hour=23, minute=59, second=59, microsecond=999999)
local_date_start = (local_date_end - timezone.timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
utc_date_end = local_date_end.astimezone(utc_timezone)
utc_date_start = local_date_start.astimezone(utc_timezone)
date_ranges = {
"local_date_end": local_date_end,
"local_date_start": local_date_start,
"utc_date_end": utc_date_end,
"utc_date_start": utc_date_start,
}
return date_ranges
def get_utc_and_local_date_ranges(days=1500, days_ago=2, local_timezone='America/Asuncion'):
seller_timezone = pytz.timezone(local_timezone)
utc_timezone = pytz.utc
utc_today = datetime.datetime.utcnow()
utc_days_ago = utc_today - datetime.timedelta(days=days_ago)
local_date_end = seller_timezone.localize(utc_days_ago).replace(
hour=23, minute=59, second=59, microsecond=999999
)
local_date_start = (local_date_end - datetime.timedelta(days=days)).replace(
hour=0, minute=0, second=0, microsecond=0
)
utc_date_end = local_date_end.astimezone(utc_timezone)
utc_date_start = local_date_start.astimezone(utc_timezone)
date_ranges = {
'local_date_end': local_date_end,
'local_date_start': local_date_start,
'utc_date_end': utc_date_end,
'utc_date_start': utc_date_start,
}
return date_ranges
days = 1500
days_ago = 2
dates = get_local_and_utc_date_ranges(days=days, days_ago=days_ago)
dates2 = get_utc_and_local_date_ranges(days=days, days_ago=days_ago)
print('dates1:')
print('local_date_start:', dates['local_date_start'])
print('local_date_end:', dates['local_date_end'])
print('utc_date_start:', dates['utc_date_start'])
print('utc_date_end:', dates['utc_date_end'])
print('\n')
print('dates2:')
print('local_date_start:', dates2['local_date_start'])
print('local_date_end:', dates2['local_date_end'])
print('utc_date_start:', dates2['utc_date_start'])
print('utc_date_end:', dates2['utc_date_end'])
print('\n')
输出:
2019-03-25 18:57:55.929908+00:00
2019-03-25 18:57:55.930005+00:00
dates1:
local_date_start: 2015-02-12 00:00:00-04:00
local_date_end: 2019-03-23 23:59:59.999999-04:00
utc_date_start: 2015-02-12 04:00:00+00:00
utc_date_end: 2019-03-24 03:59:59.999999+00:00
dates2:
local_date_start: 2015-02-12 00:00:00-03:00
local_date_end: 2019-03-23 23:59:59.999999-03:00
utc_date_start: 2015-02-12 03:00:00+00:00
utc_date_end: 2019-03-24 02:59:59.999999+00:00
请注意不一致的UTC偏移(特定时区在3月23日切换为DST)。但是,当我尝试使用以下代码复制问题时:
import pytz
import datetime
from django.utils import timezone
now1 = timezone.now() - datetime.timedelta(days=2)
now2 = pytz.utc.localize(datetime.datetime.now()) - datetime.timedelta(days=2)
seller_timezone = pytz.timezone('America/Asuncion')
print(now1.astimezone(seller_timezone).replace(
hour=23, minute=59, second=59, microsecond=999999
))
print(now2.astimezone(seller_timezone).replace(
hour=23, minute=59, second=59, microsecond=999999
))
输出正确:
2019-03-23 23:59:59.999999-03:00
2019-03-23 23:59:59.999999-03:00
我希望有人能理解为什么会发生这种行为,以及如何避免这种不一致。
答案 0 :(得分:2)
您的get_local_and_utc_date_ranges()
函数产生不正确的结果,因为它正在执行具有本地化时间的日期时间算术(即减去timedelta
)。
seller_today = timezone.now().astimezone(seller_timezone)
seller_days_ago = seller_today - timezone.timedelta(days=days_ago)
这在datetime module documentation中有记载:
关于加法,[减去
timedelta
]的结果具有与输入日期时间相同的tzinfo
属性,即使输入知道,也不会进行时区调整。
这在pytz documentation中也有记录:
如果您对跨越DST边界的本地时间执行日期算术,则结果可能在错误的时区中。
pytz提供了一个解决方法:
提供了一种
normalize()
方法来纠正此问题。
因此您可以使用:
seller_days_ago = seller_timezone.normalize(seller_today - timezone.timedelta(days=days_ago))
...
local_date_start = seller_timezone.normalize(local_date_end - timezone.timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
但是,文档also notes that:
处理时间的首选方式是始终使用UTC。
因此,更好的解决方案是仅使用UTC进行算术:
utc_today = datetime.datetime.utcnow()
utc_date_end = utc_today - datetime.timedelta(days=days_ago)
utc_date_start = utc_date_end - datetime.timedelta(days=days)
local_date_end = seller_timezone.localize(utc_date_end).replace(hour=23, minute=59, second=59, microsecond=999999)
local_date_start = seller_timezone.localize(utc_date_start).replace(hour=0, minute=0, second=0, microsecond=0)