Преобразование меток времени Unix со смещением UTC в дату и время Python в разных часовых поясах? - PullRequest
0 голосов
/ 25 декабря 2018

Если я запускаю следующую команду git log (здесь, в этом репо: 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)
  • Покажите мне дату / время, как я видел бы это в моем местном часовом поясе

В первом случае все, что у меня есть, этоСмещение UTC, а не часовой пояс автора - и поэтому у меня не будет информации о возможных изменениях летнего времени.

Во втором случае моя ОС, скорее всего, будет настроена на определенный языковой стандарт, включая (географический) часовой пояс, который будет знать об изменениях летнего времени, скажем, часовой пояс 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')

Get UTCметка времени - сначала я попытался проанализировать строку 1528917616 +0000 с 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

Это также выглядит разумно, 10:30 в UTC будет 20:30 в +1000;с другой стороны, смещение - это смещение, здесь нет двусмысленности.

Теперь я пытаюсь получить дату и время в моем местном часовом поясе - во-первых, похоже, что я не должен использовать метод .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)?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...