Как мне получить время UTC "полуночи" для данного часового пояса?

Лучшее, что я могу сейчас придумать, это это чудовище:

>>> datetime.utcnow() \
...   .replace(tzinfo=pytz.UTC) \
...   .astimezone(pytz.timezone("Australia/Melbourne")) \
...   .replace(hour=0,minute=0,second=0,microsecond=0) \
...   .astimezone(pytz.UTC) \
...   .replace(tzinfo=None)
datetime.datetime(2008, 12, 16, 13, 0)

То есть на английском языке получить текущее время (в UTC), преобразовать его в другой часовой пояс, установить время в полночь, а затем преобразовать обратно в UTC.

Я не просто использую now() или localtime(), поскольку это будет использовать часовой пояс сервера, а не часовой пояс пользователя.

Я не могу не чувствовать, что что-то упустил, какие-нибудь идеи?

4 ответа

Решение

Я думаю, что вы можете сбрить несколько вызовов методов, если вы сделаете это так:

>>> from datetime import datetime
>>> datetime.now(pytz.timezone("Australia/Melbourne")) \
            .replace(hour=0, minute=0, second=0, microsecond=0) \
            .astimezone(pytz.utc)

НО... в вашем коде есть более серьезная проблема, чем эстетика: это даст неверный результат в день перехода на летнее время или с него.

Причиной этого является то, что ни конструкторы даты и времени, ни replace() принять во внимание изменения DST.

Например:

>>> now = datetime(2012, 4, 1, 5, 0, 0, 0, tzinfo=pytz.timezone("Australia/Melbourne"))
>>> print now
2012-04-01 05:00:00+10:00
>>> print now.replace(hour=0)
2012-04-01 00:00:00+10:00 # wrong! midnight was at 2012-04-01 00:00:00+11:00
>>> print datetime(2012, 3, 1, 0, 0, 0, 0, tzinfo=tz)
2012-03-01 00:00:00+10:00 # wrong again!

Тем не менее, документация для tz.localize() состояния:

Этот метод следует использовать для создания локальных времен, а не для передачи аргумента tzinfo в конструктор datetime.

Таким образом, ваша проблема решается так:

>>> import pytz
>>> from datetime import datetime, date, time

>>> tz = pytz.timezone("Australia/Melbourne")
>>> the_date = date(2012, 4, 1) # use date.today() here

>>> midnight_without_tzinfo = datetime.combine(the_date, time())
>>> print midnight_without_tzinfo
2012-04-01 00:00:00

>>> midnight_with_tzinfo = tz.localize(midnight_without_tzinfo)
>>> print midnight_with_tzinfo
2012-04-01 00:00:00+11:00

>>> print midnight_with_tzinfo.astimezone(pytz.utc)
2012-03-31 13:00:00+00:00

Никаких гарантий на даты до 1582 года.

@ хоп ответ неправильный в день перехода на летнее время (DST), например, 1 апреля 2012 года. Чтобы исправить это tz.localize() может быть использован:

tz = pytz.timezone("Australia/Melbourne")
today = datetime.now(tz).date()
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)
utc_dt = midnight.astimezone(pytz.utc)        

То же самое с комментариями:

#!/usr/bin/env python
from datetime import datetime, time
import pytz # pip instal pytz

tz = pytz.timezone("Australia/Melbourne") # choose timezone

# 1. get correct date for the midnight using given timezone.
today = datetime.now(tz).date()

# 2. get midnight in the correct timezone (taking into account DST)
#NOTE: tzinfo=None and tz.localize()
# assert that there is no dst transition at midnight (`is_dst=None`)
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)

# 3. convert to UTC (no need to call `utc.normalize()` due to UTC has no 
#    DST transitions)
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
print midnight.astimezone(pytz.utc).strftime(fmt)

Это проще с dateutil.tz чем pytz:

>>>import datetime
>>>import dateutil.tz
>>>midnight=(datetime.datetime
             .now(dateutil.tz.gettz('Australia/Melbourne'))
             .replace(hour=0, minute=0, second=0, microsecond=0)
             .astimezone(dateutil.tz.tzutc()))
>>>print(midnight)
2019-04-26 14:00:00+00:00

Документация tzinfo рекомендует dateutil.tz начиная с Python 3.6. У объектов tzinfo из dateutil.tz нет проблем с аномалиями, такими как DST, не требуя функции локализации pytz. Используя пример из user3850:

>>> now = (datetime.datetime(2012, 4, 1, 5,  
...         tzinfo = dateutil.tz.gettz('Australia/Melbourne'))) 
>>> print(now.replace(hour = 0).astimezone(dateutil.tz.tzutc()))
2012-03-31 13:00:00+00:00

Установка переменной окружения TZ изменяет временную зону, с которой работают функции даты и времени Python.

>>> time.gmtime()
(2008, 12, 17, 1, 16, 46, 2, 352, 0)
>>> time.localtime()
(2008, 12, 16, 20, 16, 47, 1, 351, 0)
>>> os.environ['TZ']='Australia/Melbourne'
>>> time.localtime()
(2008, 12, 17, 12, 16, 53, 2, 352, 1)

У каждого часового пояса есть номер, например, США / Центральный = -6. Это определяется как смещение в часах от UTC. Поскольку 0000 - полночь, вы можете просто использовать это смещение, чтобы найти время в любом часовом поясе, когда это полночь UTC. Я считаю, что для доступа к этому вы можете использовать

 time.timezone 

Согласно The Python Docs, time.timezone фактически дает отрицательное значение этого числа:

time.timezone

Смещение местного (не летнего) часового пояса в секундах к западу от UTC (отрицательный в большинстве стран Западной Европы, положительный в США, нулевой в Великобритании).

Таким образом, вы просто используете это число для времени в часах, если оно положительное (т. Е. Если в Чикаго полночь (значение часового пояса +6), то это 6000 = 6 утра по Гринвичу).

Если число отрицательное, вычтите из 24. Например, Берлин даст -1, поэтому 24 - 1 => 2300 = 11 вечера.

Стоит отметить, что мы можем адаптировать ответ, данный @jfs, чтобы найти завтрашнюю полночь или вчерашнюю полночь и т. Д. Хитрость заключается в том, чтобы добавить определенное количество дней к известному часовому поясу. Это работает, потому что, хотя обычно это добавляет 24 часа, иногда может добавляться 23 или 25 в зависимости от проблем с летним временем.

from datetime import datetime, time, timedelta
import pytz

def midnight_UTC(offset):

    # Construct a timezone object
    tz = pytz.timezone('Australia/Melbourne')

    # Work out today/now as a timezone-aware datetime
    today = datetime.now(tz)

    # Adjust by the offset. Note that that adding 1 day might actually move us 23 or 25
    # hours into the future, depending on daylight savings. This works because the {today}
    # variable is timezone aware
    target_day = today + timedelta(days=1) * offset

    # Discard hours, minutes, seconds and microseconds
    midnight_aware = tz.localize(
        datetime.combine(target_day, time(0, 0, 0, 0)), is_dst=None)

    # Convert to UTC
    midnight_UTC = midnight_aware.astimezone(pytz.utc)

    return midnight_UTC

print("The UTC time of the previous midnight is:", midnight_UTC(0))
print("The UTC time of the upcoming midnight is:", midnight_UTC(1))
Другие вопросы по тегам