Испытание нечетного вывода из django.utils.timezone.now (), datetime.datetime.now () и pytz.timezone - PullRequest
0 голосов
/ 25 марта 2019

Я испытываю странное поведение при попытке конвертировать между 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 (этот конкретный часовой пояс переключился на летнее время 23 марта).Но когда я пытаюсь повторить проблему, используя следующий код:

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

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

1 Ответ

2 голосов
/ 25 марта 2019

Ваша функция get_local_and_utc_date_ranges() дает неверные результаты, потому что она выполняет арифметику даты и времени (то есть вычитает timedelta) с локализованным временем, которое не работает.

seller_today = timezone.now().astimezone(seller_timezone)
seller_days_ago = seller_today - timezone.timedelta(days=days_ago)

Это отмечено в документация по модулю даты и времени :

Что касается сложения, результат [вычитания timedelta] имеет тот же атрибут tzinfo, что и у ввода даты и времени, и никаких настроек часового поясавыполняются, даже если ввод известен.

Это также отмечено в документации pytz :

Если вы выполняете арифметику дат по местному временикоторые пересекают границы летнего времени, результат может быть в неправильном часовом поясе.

pytz предлагает исправление:

A 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)

Однако в документации также отмечается, что :

Предпочтительный способ работы свремя всегда должно работать в 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)
...