首页 > 解决方案 > 将具有 UTC 偏移量的 Unix 时间戳转换为不同时区的 Python 日期时间?

问题描述

如果我运行以下 git log 命令(在此 repo 中:https ://github.com/rubyaustralia/rubyconfau-2013-cfp ):

$ git --no-pager log --reverse --date=raw --pretty='%ad %h'
1344507869 -0700 314b3d4
1344508222 +1000 dffde53
1344510528 +1000 17e7d3b
...

...我得到一个列表,其中对于每个提交,我都有 Unix 时间戳(自纪元以来的秒数)和 UTC 偏移量。我想做的是获得一个时区感知日期时间,这将:

在第一种情况下,我所拥有的只是一个 UTC 偏移量,而不是作者的时区——因此我没有关于可能的夏令时更改的信息。

在第二种情况下,我的操作系统很可能会设置为特定的语言环境,包括(地理)时区,它会知道 DST 的变化;说 CET 时区在冬季的 UTC 偏移量为 +0100,但在夏季夏令时,它的 UTC 偏移量为 +0200(然后称为 CEST)

无论如何,我想从 UTC 时间戳开始,因为纪元秒数的“1344508222”计数与时区无关;偏移量 +1000 只会帮助我们看到作者看到的人类可读的输出。

我需要为 Python 2.7 项目执行此操作,并且我搜索了大量资源(SO 帖子), - 我想出了以下示例(它试图解析上述代码段中的第二行,“ 1344508222 +1000 dffde53”)。但是,我真的不确定它是否正确;所以最终,我的问题是 - 这样做的正确方法是什么?

前言:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import datetime
import pytz
import dateutil.tz
import time

def getUtcOffsetFromString(in_offset_str): # SO:1101508
  offset = int(in_offset_str[-4:-2])*60 + int(in_offset_str[-2:])
  if in_offset_str[0] == "-":
    offset = -offset
  return offset

class FixedOffset(datetime.tzinfo): # SO:1101508
  """Fixed offset in minutes: `time = utc_time + utc_offset`."""
  def __init__(self, offset):
    self.__offset = datetime.timedelta(minutes=offset)
    hours, minutes = divmod(offset, 60)
    #NOTE: the last part is to remind about deprecated POSIX GMT+h timezones
    #  that have the opposite sign in the name;
    #  the corresponding numeric value is not used e.g., no minutes
    self.__name = '<%+03d%02d>%+d' % (hours, minutes, -hours)
  def utcoffset(self, dt=None):
    return self.__offset
  def tzname(self, dt=None):
    return self.__name
  def dst(self, dt=None):
    return datetime.timedelta(0)
  def __repr__(self):
    return 'FixedOffset(%d)' % (self.utcoffset().total_seconds() / 60)

解析开始:

tstr = "1344508222 +1000 dffde53"
tstra = tstr.split(" ")
unixepochsecs = int(tstra[0])
utcoffsetstr = tstra[1]
print(unixepochsecs, utcoffsetstr)  # (1344508222, '+1000')

1528917616 +0000获取 UTC 时间戳 -首先我尝试解析字符串dateutil.parser.parse

justthetstz = " ".join(tstra[:2])
print(justthetstz)  # '1344508222 +1000'
#print(dateutil.parser.parse(justthets)) # ValueError: Unknown string format

...但不幸的是失败了。

这有助于获得 UTC 时间戳:

# SO:12978391: "datetime.fromtimestamp(self.epoch) returns localtime that shouldn't be used with an arbitrary timezone.localize(); you need utcfromtimestamp() to get datetime in UTC and then convert it to a desired timezone"
dtstamp = datetime.datetime.utcfromtimestamp(unixepochsecs).replace(tzinfo=pytz.utc)
print(dtstamp)                # 2012-08-09 10:30:22+00:00
print(dtstamp.isoformat())    # 2012-08-09T10:30:22+00:00 # ISO 8601

好的,到目前为止一切顺利 - 这个 UTC 时间戳看起来很合理。

现在,尝试获取作者的 UTC 偏移量中的日期 - 显然这里需要一个自定义类:

utcoffset = getUtcOffsetFromString(utcoffsetstr)
fixedtz = FixedOffset(utcoffset)
print(utcoffset, fixedtz)   # (600, FixedOffset(600))
dtstampftz = dtstamp.astimezone(fixedtz)
print(dtstampftz)             # 2012-08-09 20:30:22+10:00
print(dtstampftz.isoformat()) # 2012-08-09T20:30:22+10:00

这看起来也很合理,UTC 的 10:30 将是 +1000 的 20:30;再说一次,偏移量是偏移量,这里没有歧义。

现在我正在尝试在我的本地时区导出日期时间 - 首先,看起来我不应该使用该.replace方法:

print(time.tzname[0]) # CET
tzlocal = dateutil.tz.tzlocal()
print(tzlocal) # tzlocal()
dtstamplocrep = dtstamp.replace(tzinfo=tzlocal)
print(dtstamp)                # 2012-08-09 10:30:22+00:00
print(dtstamplocrep)          # 2012-08-09 10:30:22+02:00 # not right!

这看起来不对,我得到了完全相同的“时钟字符串”和不同的偏移量。

但是,.astimezone似乎有效:

dtstamploc = dtstamp.astimezone(dateutil.tz.tzlocal())
print(dtstamp)                # 2012-08-09 10:30:22+00:00
print(dtstamploc)             # 2012-08-09 12:30:22+02:00 # was August -> summer -> CEST: UTC+2h

我对命名的结果相同pytz.timezone

cphtz = pytz.timezone('Europe/Copenhagen')
dtstamploc = dtstamp.astimezone(cphtz)
print(dtstamp)                # 2012-08-09 10:30:22+00:00
print(dtstamploc)             # 2012-08-09 12:30:22+02:00 # is August -> summer -> CEST: UTC+2h

...但是,我不能.localize在这里使用,因为我的输入dtstamp已经有一个与之关联的时区,因此不再“幼稚”:

# dtstamploc = cphtz.localize(dtstamp, is_dst=True) # ValueError: Not naive datetime (tzinfo is already set)

最终,到目前为止,这看起来是正确的,但我真的不确定 - 特别是因为我看到了这个:

pytz.astimezone 不考虑夏令时?

您不能在 datetime 构造函数中分配时区,因为它没有给时区对象一个调整夏令时的机会 - 它无法访问日期。这会给世界某些地区带来更多问题,这些地区的时区名称和偏移量多年来一直在变化。

从 pytz 文档:

不幸的是,对于许多时区,使用标准日期时间构造函数的 tzinfo 参数对 pytz “不起作用”。

改为使用带有天真的日期时间的 localize 方法。

...最终让我感到困惑:说我想这样做,并且我已经有一个正确的时区时间戳, - 我将如何为它推导出一个“天真的”日期时间?只是摆脱时区信息?还是从以 UTC 表示的时间戳版本派生的正确“天真”日期时间(例如2012-08-09 20:30:22+10:00-> 2012-08-09 10:30:22+00:00,因此正确的“天真”日期时间将是2012-08-09 10:30:22)?

标签: pythondatetimetimezone

解决方案


推荐阅读