我应该如何使用 python3 在 postgresql 中存储带时区的时间?

How should I store times with timezones in postgresql using python3?

我一直在阅读有关在 Python 中处理日期时间并将它们存储到 postgresql 中的最佳实践(尽可能多地使用 utc,使用 pytz 进行转换,避免 datetime 构造函数中的 tzinfo 参数,等等...).

但我现在的疑问是,我很惊讶没有找到任何关于 datetime.time 对象及其最佳实践的信息。

举个例子,假设我只想存储一个时间,比如 20:30,因为我每周都会在那个时间的几天安排一些任务,但是一周中的那一天每周都可以改变。并且可能用户输入了 his/her 时区的时间。在我的情况下,它将是西班牙时区 'Europe/Madrid'.

的用户

我的问题是:

2) [H]ow do I localize properly a naive time? datetime.datetime uses my_datetime.localize(pytz_spanish_timezone)

其实恰恰相反。 localize 是 pytz 时区方法,而不是 datetime 方法:

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

这里需要 datetime.datetimedatetime.time 对象没有等效项。原因见下文。

3) How do I convert one datetime.time object from a timezone to another?

考虑以下情况:我们知道时间是 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是"converted"到19:30,但是当日期是CEST时,时间被转换成18:30. 如果不知道日期,就无法(简单)回答您的问题。

4a) How should I store the datetime.time in postgresql database? I know there are time and timetz data types.

根据 the docs:

The type time with time zone is defined by the SQL standard, but the definition exhibits properties which lead to questionable usefulness.

我认为文档暗示了上面显示的问题。不要使用 time with time zone。如果要存储时间,请使用 PostgreSQL plain time 类型。

您可以将 timetimezone 存储在数据库中,然后重新构造 约会后的时区感知日期时间。但请注意,有 陷阱:

  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

    您可以通过指定本地时间是否指 DST(夏令时)期间的时间来避免 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. 在给定本地日期时间和特定时区的情况下,可能存在无法表示的日期时间table。

    上面的AmbiguousTimeErrorNonExistentTimeError说明了指定is_dst值的重要性。 为避免这些错误,您需要在数据库中存储布尔值 is_dst 以及 timetimezone

    您可能认为只要选择一个值就可以避免这个问题 is_dst 所有时间。但你会错的。这是一个特殊的例子 (取自 the 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) I suppose I should store the time as UTC. Would the timezone matter? Should I store it somehow?

由于上述原因,UTC 中没有时间(没有日期)这样的东西。 但是您可以通过简单地在 UTC 中存储 datetimes 来避免上述问题。

如果您创建一个 table 并且列的数据类型为 timestamptz,则 您可以使用 psycopg2 等数据库适配器来存储 Python 时区感知日期时间 作为 PostgreSQL timestamptzs。当您查询数据库时,psycopg2 会将 timestamptz 转换回 为您提供时区感知日期时间。

在内部,PostgreSQL 以 UTC 格式存储所有 timestamptzs,但它报告的值是关于 PostgreSQL 用户的时区设置。在 Python 方面,给定一个时区感知日期时间, 您可以使用其 astimezone 方法将其转换为您喜欢的任何时区。

除非你想报告,否则你不需要单独存储时区 不同时区的不同日期时间。

5) How to parse a time from a string without going through 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 个数字。

或者,第 3 方 dateutil package 可以解析 多种格式的时间字符串:

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

这里有很多东西要消化,可能还有更多可以说的 关于这些问题中的每一个。将来,您可能希望将 post 拆分为 多个问题。这将降低那些可能希望 回答一个问题而不是全部问题,将帮助您更快地获得更多答案。