Как мне хранить время с часовыми поясами в postgresql, используя python3? - PullRequest
0 голосов
/ 31 марта 2019

Я читал о лучших методах обработки datetime в Python и их хранения в postgresql (используя utc, насколько я могу, с pytz для преобразований, избегая использования параметра tzinfo в конструкторе datetime и т. Д.).

Но теперь я сомневаюсь, что я удивлен, не обнаружив ничего об объекте datetime.time и его лучших практиках.

Ради примера, представьте, что яЯ хочу сохранить только время, например, 20:30, потому что я планирую какую-то задачу каждую неделю на пару дней в это время, но день недели может меняться каждую неделю.И, вероятно, пользователь ввел время в своем часовом поясе.В моей ситуации это будет пользователь из испанского часового пояса «Европа / Мадрид».

Мои вопросы:

  • 1) Как только я получу время в качестве даты и времени.time, как мне сохранить информацию о часовом поясе в переменной datetime.time.Можно ли использовать

    datetime.time(h, m, s, ms, tzinfo=pytz_spanish_timezone) ???

  • 2) Если не с предыдущей строкой кода, как правильно локализовать наивное время?datetime.datetime использует my_datetime.localize(pytz_spanish_timezone)

  • 3) Как преобразовать один объект datetime.time из часового пояса в другой, учитывая, что с datetime и pytz он будет использовать

    new_tz_datetime = my_datetime.astimezone(pytz_spanish_timezone)

    , но со временем аналогичного метода не существует

  • 4) Как мне хранить datetime.time в базе данных postgresql?Я знаю, что существуют типы данных time и timetz.Я полагаю, я должен хранить время как UTC.Будет ли иметь значение часовой пояс?Должен ли я хранить его как-нибудь?

  • 5) Как разобрать время из строки, не проходя через datetime?(Я сделал себя функцией, но я уверен, что должен существовать какой-то способ, использующий datetime или какой-нибудь надежный модуль, который, вероятно, покрывает случаи, которых у меня нет).

1 Ответ

1 голос
/ 03 апреля 2019

2) [H] Как правильно локализовать наивное время? datetime.datetime использует my_datetime.localize(pytz_spanish_timezone)

На самом деле, все наоборот. localize - это метод Pyzz для часовых поясов, а не datetime метод:

import pytz
madrid = pytz.timezone('Europe/Madrid')
aware_datetime = madrid.localize(naive_datetime)

Тебе нужен datetime.datetime здесь. Для datetime.time объектов нет эквивалента. См. Ниже причину, по которой.

3) Как преобразовать один datetime.time объект из часового пояса в другой?

Рассмотрим следующую ситуацию: Мы знаем, что время 20:30, а часовой пояс Europe/Madrid, и мы хотим преобразовать его в UTC. Результат будет отличаться в зависимости от того, попадает ли дата в летнее время (CEST) или нет (CET): Например,

import datetime as DT
import pytz
madrid = pytz.timezone('Europe/Madrid')
utc = pytz.utc

CET_date = madrid.localize(DT.datetime(2019, 3, 30, 20, 30, 0), is_dst=None)
# the most recent transition occurred at `2019-03-31 02:00:00+01:00 CEST` 
CEST_date = madrid.localize(DT.datetime(2019, 3, 31, 20, 30, 0), is_dst=None)
print(CET_date.astimezone(utc))
print(CEST_date.astimezone(utc))

# 2019-03-30 19:30:00+00:00
# 2019-03-31 18:30:00+00:00

Обратите внимание, что когда дата находится в CET, время 20:30 «конвертируется» в 19:30, но когда дата в CEST, время конвертируется в 18:30. Нет (простого) ответа на ваш вопрос без предварительного знания даты.

4a) Как хранить datetime.time в базе данных postgresql? Я знаю, что существуют типы данных time и timetz.

За документы :

Тип time with time zone определяется стандартом SQL, но определение обладает свойствами, которые приводят к сомнительной полезности.

Я думаю, что документы ссылаются на проблему, показанную выше. Не используйте time with time zone. Если вы хотите сохранить время, используйте тип PostgreSQL plain time.

Вы можете сохранить time и timezone в базе данных и восстановить дата-время с учетом часового пояса, когда у вас есть дата. Но обратите внимание, что есть подводные камни:

  1. Существуют неоднозначные локальные даты

    import datetime as DT
    import pytz
    madrid = pytz.timezone('Europe/Madrid')
    date = madrid.localize(DT.datetime(2019, 10, 27, 2, 0, 0), is_dst=None)
    

    повышает pytz.exceptions.AmbiguousTimeError: 2019-10-27 02:00:00. Чтобы избежать AmbiguousTimeError, is_dst должен быть указан явно:

    import datetime as DT
    import pytz
    madrid = pytz.timezone('Europe/Madrid')
    date = madrid.localize(DT.datetime(2019, 10, 27, 2, 0, 0), is_dst=False)
    print(date)
    date = madrid.localize(DT.datetime(2019, 10, 27, 2, 0, 0), is_dst=True)
    print(date)
    
    # 2019-10-27 02:00:00+01:00
    # 2019-10-27 02:00:00+02:00
    
  2. Существует несуществующее местное время

    import datetime as DT
    import pytz
    madrid = pytz.timezone('Europe/Madrid')
    madrid.localize(DT.datetime(2019, 3, 31, 2, 0, 0), is_dst=None)
    

    поднимает pytz.exceptions.NonExistentTimeError: 2019-03-31 02:00:00

    Вы можете избежать ошибки NonExistentTimeError, указав, относится ли наивное локальное время ко времени в летнее время (летнее время):

    import datetime as DT
    import pytz
    madrid = pytz.timezone('Europe/Madrid')
    
    date = madrid.normalize(madrid.localize(DT.datetime(2019, 3, 31, 2, 0, 0), is_dst=False))
    print(date)
    date = madrid.normalize(madrid.localize(DT.datetime(2019, 3, 31, 2, 0, 0), is_dst=True))
    print(date)
    
    # 2019-03-31 03:00:00+02:00
    # 2019-03-31 01:00:00+01:00
    
  3. Могут существовать даты и время, которые невозможно представить, учитывая местное время и конкретный часовой пояс.

    AmbiguousTimeError и NonExistentTimeError выше показывают важность определения значения is_dst. Чтобы избежать этих ошибок, вам нужно хранить логическое значение is_dst вместе с time и timezone в базе данных.

    Вы можете подумать, что можете избежать проблемы, просто выбрав одно значение is_dst за все время. Но ты ошибаешься. Вот своеобразный пример (взято из PyTZ DOCS ), который показывает, если вы всегда выбирайте is_dst = False (или is_dst = True), могут быть даты UTC который не может быть выражен, учитывая только наивное местное время и часовой пояс!

    import datetime as DT
    import pytz
    
    warsaw = pytz.timezone('Europe/Warsaw')
    utc = pytz.utc
    
    date1 = warsaw.localize(DT.datetime(1915, 8, 4, 23, 35, 59), is_dst=False).astimezone(utc)
    date2 = warsaw.localize(DT.datetime(1915, 8, 4, 23, 36, 0), is_dst=False).astimezone(utc)
    print('Datetimes between {} and {} can not be expressed if we assume is_dist=False.'.format(date1, date2))
    
    date3 = warsaw.localize(DT.datetime(1915, 8, 4, 23, 59, 59), is_dst=True).astimezone(utc)
    date4 = warsaw.localize(DT.datetime(1915, 8, 5, 0, 0, 0), is_dst=True).astimezone(utc)
    print('Datetimes between {} and {} can not be expressed if we assume is_dist=True.'.format(date1, date2))
    

    печать

    Datetimes between 1915-08-04 22:11:59+00:00 and 1915-08-04 22:36:00+00:00 can not be expressed if we assume is_dist=False.
    Datetimes between 1915-08-04 22:11:59+00:00 and 1915-08-04 22:36:00+00:00 can not be expressed if we assume is_dist=True.
    

4b) Полагаю, мне следует хранить время как UTC. Будет ли иметь значение часовой пояс? Должен ли я хранить его как-нибудь?

По указанным выше причинам в UTC отсутствует время (без даты). Но вы можете избежать проблем, упомянутых выше, просто сохранив datetimes в UTC.

Если вы создаете таблицу со столбцом с типом данных timestamptz, то вы можете использовать адаптер базы данных, такой как psycopg2, для хранения времени Python с учетом часового пояса как PostgreSQL timestamptz с. Когда вы запрашиваете базу данных, psycopg2 преобразует timestamptz s обратно в с учетом часовых поясов.

Внутри PostgreSQL хранит все timestamptz в UTC, но сообщает о значениях относительно настройка часового пояса пользователя PostgreSQL. На стороне Python, учитывая дату / время с учетом часового пояса, Вы можете использовать метод astimezone, чтобы преобразовать его в любой часовой пояс.

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

5) Как разобрать время из строки, не переходя через datetime?

Вы можете использовать regex для разбора строк времени:

import re
import datetime as DT
atime = DT.time(*map(int, re.search(r'(\d{,2}):(\d{,2}):(\d{,2})', 'blueberry jam at 13:32:02').groups()))
print(repr(atime))
# datetime.time(13, 32, 2)

Выше шаблон регулярного выражения \d соответствует одной цифре. \d{1,2} соответствует 1 или 2 цифрам.

Кроме того, сторонний dateutil пакет может анализировать временные строки в различных форматах:

import dateutil.parser as DP
print(DP.parse("13:32:02").time())
# 13:32:02

print(DP.parse("blueberry jam at 13:32:02", fuzzy=True).time())
# 13:32:02

print(DP.parse("30 minutes 12 hours").time())
# 12:30:00

print(DP.parse("2:30pm").time())
# 14:30:00

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

...