Как создать объект Time с учетом часового пояса, который работает со всеми датами? - PullRequest
0 голосов
/ 20 ноября 2018

Я уже давно борюсь с этой проблемой.Я чувствую, что хорошо понимаю часовые пояса, и думаю, что понимаю, как правильно использовать pytz, поэтому я обычно использую его, и у меня обычно не возникает никаких проблем.Возможно, я пытаюсь использовать не те инструменты, которые мне нужны.

В моем текущем приложении мне нужно работать с абстрактными time объектами.То есть меня волнует то, что происходит в 16:00.Мне все равно, что это произойдет в 16:00 23 декабря.Кажется, что pytz предпочитает работать с datetime объектами: и это имеет смысл.Он не может рассчитать смещение, поскольку он отличается в зависимости от дня для летнего времени и исторических причин и т. Д.

Я пытаюсь позволить пользователям координировать ежедневные события по всему миру.Если пользователь в Японии говорит, что мероприятие проводится с 21:00 до 23:00 каждый день, я хочу, чтобы пользователи в США и Центральной Америке ежедневно видели с 6:00 до 8:00.И я думал, что у меня это работает ... пока две недели назад это так.Видите ли, летнее время только что закончилось в большинстве Соединенных Штатов, так что сейчас 6: 00-8: 00 было на самом деле раньше 7: 00-9: 00.

Это ломает мою идею, что мне нужно обычно хранитьраз в UTC, то конвертируйте их только для просмотра.Часовой пояс, в котором он был создан, на самом деле очень важен.Если мы изменим это на обратное, и пользователь в часовом поясе США, который наблюдает за переходом на летнее время, устанавливает время события, то это время должно измениться в Японии, даже если они его не соблюдают!Если я сохраню это время как UTC, в Японии ничего не изменится, но это не та функциональность, которую я хочу.

Так что я хотел бы сохранить как time объект с tzinfo.Но вы не можете создать объект времени с точным pytz tzinfo без даты, но дата не важна.Если я использую текущую дату, чтобы выяснить tzinfo, тогда она фактически перестанет быть точной, как только этот часовой пояс изменится.

Я предполагаю, что мой вопрос: Какой лучший способ хранения "4PM Eastern "таким образом, который может быть найден в любой точке мира, в любое время в будущем ?Это включает в себя восточные!Я хочу, чтобы это было 4 вечера во время летнего времени, а также за пределами летнего времени.Я не могу сохранить его как UTC, потому что 12:00 UTC - это то же время, что и 12:00 UTC в течение всего года, но я этого не хочу.То, что я думаю, я хочу, это «абстрактный» или «временный» pytz.timezone, который не имеет фактического смещения до тех пор, пока не будет указана дата (дата просмотра).Это вещь?Я прочитал бесчисленные вопросы на этом сайте, как на python, так и на pytz docs, и не могу найти ничего подобного или кого-либо с подобной проблемой.Кажется, все говорит о конкретной datetimes или о работе только в пределах datetimes, но это не имеет отношения к моей проблеме.

Мое приложение довольно большое, поэтому сложно выделить отдельные части, но ямогу попытаться показать, что я пробовал и почему все не работает.

event_time = datetime.time(hour=12, tzinfo=pytz.timezone("US/Eastern")) было бы моим идеальным решением.Но использование pytz для создания tzinfo не очень хорошая идея (это даст мне смещение, например -5: 04 по историческим причинам) - есть ли способ указать версию US / Eastern для использования?

datetime.now(pytz.timezone("US/Eastern")).replace(hour=12, minute=0, second=0, microsecond=0).timetz() дает мне то, что похоже на то, что я хочу, но оно работает правильно только до тех пор, пока США / Восток не изменится.Если я применяю это к дате, которую я перенес назад до изменения летнего времени, это дает мне 13:00, а это не то, чего я хочу.

Ответы [ 2 ]

0 голосов
/ 21 ноября 2018

Я создал класс tzinfo, который автоматически обрабатывает преобразование DST. Я получил идею из примера класса USTimeZone в документации datetime.

Хитрость в том, что в базе данных о часовых поясах pytz есть все исторические даты вступления в силу летнего времени. Вот почему, когда вы создаете объект datetime без даты, он неправильно конвертирует DST; он основывается на первой записи в базе данных.

from datetime import datetime, tzinfo, timedelta
import pytz

ZERO = timedelta(0)

def format_timedelta(td):
    if td < timedelta(0):
        return '-' + format_timedelta(-td)
    else:
        # Change this to format positive timedeltas the way you want
        return str(td)



class WorldTimeZone(tzinfo):
    """
    A self adjusting according to DST rules in the PYTZ database tzinfo class
    See pytz.all_timezones for a list of all zone names and offsets.
    """

    def __init__(self, zone):
        """
        :param zone:  (str) Proper tzdatabase timze zone name. 
        """
        # initialize the pytz timezone with current time
        # this is done to avoid confusing tznames found in the start of the tz database
        # _utcoffset should always be STD rather than DST.
        self.pytzinfo = self.__getSTD(zone)
        self._utcoffset = self.pytzinfo._utcoffset


    @staticmethod
    def __getSTD(tname):
        """
        This returns a pytz timezone object normalized to standard time for the zone requested.
        If the zone does not follow DST or a future transition time cannot be found, it normalizes to NOW instead.

        :param tname:  Proper timezone name found in the tzdatabase. example: "US/Central"
        """

        # This defaults to the STD time for the zone rather than current time which could be DST
        tzone = pytz.timezone(tname)
        NOW = datetime.now(tz=pytz.UTC)
        std_date = NOW
        hasdst = False
        try:
            #transitions are in UTC.  They need to be converted to localtime once we find the correct STD transition.
            for utcdate, info in zip(tzone._utc_transition_times, tzone._transition_info):
                utcdate = utcdate.replace(tzinfo=pytz.UTC)
                utcoffset, dstoffset, tzname = info
                if dstoffset == ZERO:
                    std_date = utcdate
                if utcdate > NOW:
                    hasdst = True
                    break
        except AttributeError:
            std_date = NOW
        if not hasdst:
            std_date = NOW
        std_date = tzone.normalize(std_date)
        return std_date.tzinfo

    # This needs to be dynamic because pytzinfo updates everytime .dst() is called; which is a lot.
    @property
    def _dst(self):
        return self.pytzinfo._dst

    def __repr__(self):
        # return self.pytzinfo.__repr__()
        if self._dst:
            dst = 'DST'
        else:
            dst = 'STD'
        if self._utcoffset > timedelta(seconds=0):
            msg = '<WorldTimeZone %r %s+%s %s>'
        else:
            msg = '<WorldTimeZone %r %s%s %s>'
        return msg % (self.pytzinfo.zone, self.pytzinfo._tzname,
                      format_timedelta(self._utcoffset + self._dst), dst)

    def __str__(self):
        return "%s %s" % (self.pytzinfo._tzname, self.pytzinfo)

    def tzname(self, dt):
        # print "   TZNAME called"
        return "%s %s" % (self.pytzinfo._tzname, self.pytzinfo)

    def utcoffset(self, dt):
        # print "   UTCOFFSET CALLED"
        return self._utcoffset + self.dst(dt)

    def dst(self, dt):
        # print "   DST CALLED"
        if dt is None or dt.tzinfo is None:
            # An exception may be sensible here, in one or both cases.
            # It depends on how you want to treat them.  The default
            # fromutc() implementation (called by the default astimezone()
            # implementation) passes a datetime with dt.tzinfo is self.
            return ZERO

        assert dt.tzinfo is self  # WE ASSUME THE TZINFO ON THE DATE PASSED IN IS OUR TZINFO OBJECT.
        tmpdt = self.pytzinfo.normalize(dt)
        self.pytzinfo = tmpdt.tzinfo
        return tmpdt.tzinfo._dst

Пример кода

EST = WorldTimeZone('US/Eastern')
PST = WorldTimeZone('US/Pacific')
dt_dst = datetime(2018, 11, 1, 1, 30, 00)
dt_std = datetime(2018, 11, 6, 1, 30, 00)
est_dst = dt_dst.replace(tzinfo=EST)
est_std = dt_std.replace(tzinfo=EST)
pst_dst = est_dst.astimezone(PST)
pst_std = est_std.astimezone(PST)

print(f"{dt_dst} >> {est_dst} >> {pst_dst}")
print(f"{dt_std} >> {est_std} >> {pst_std}")

выходы

2018-11-01 01:30:00 >> 2018-11-01 01:30:00-04:00 >> 2018-10-31 22:30:00-07:00
2018-11-06 01:30:00 >> 2018-11-06 01:30:00-05:00 >> 2018-11-05 22:30:00-08:00
0 голосов
/ 20 ноября 2018

Похоже, что pytz предоставляет это с помощью метода 'localize', как предложено в ` this answer .

Из документации pytz :

Если вы настаиваете на работе с местным временем, эта библиотека предоставляет возможность для их однозначного построения:

loc_dt = datetime(2002, 10, 27, 1, 30, 00)
est_dt = eastern.localize(loc_dt, is_dst=True)
edt_dt = eastern.localize(loc_dt, is_dst=False)
print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt))
2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500`

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

...