如何将 datetime/timestamp 从一个时区转换为另一个时区?

How do you convert a datetime/timestamp from one timezone to another timezone?

具体来说,给定我的服务器的时区(系统时间角度)和时区输入,我如何计算系统时间就好像它在那个新时区(不管夏令时等)?

import datetime
current_time = datetime.datetime.now() #system time

server_timezone = "US/Eastern"
new_timezone = "US/Pacific"

current_time_in_new_timezone = ???

如果您知道您的原始时区和要将其转换到的新时区,结果非常简单:

  1. 创建两个 pytz.timezone 对象,一个用于当前时区,一个用于新时区,例如pytz.timezone("US/Pacific")。您可以在 pytz 库中找到所有官方时区的列表:import pytz; pytz.all_timezones

  2. 将感兴趣的datetime/timestamp本地化到当前时区,例如

current_timezone = pytz.timezone("US/Eastern")
localized_timestamp = current_timezone.localize(timestamp)
  1. 在步骤 2 中新本地化的 datetime/timestamp 上使用 .astimezone() 转换为新时区,并将所需时区的 pytz 对象作为输入,例如localized_timestamp.astimezone(new_timezone).

完成!

作为一个完整的例子:

import datetime
import pytz

# a timestamp I'd like to convert
my_timestamp = datetime.datetime.now()

# create both timezone objects
old_timezone = pytz.timezone("US/Eastern")
new_timezone = pytz.timezone("US/Pacific")

# two-step process
localized_timestamp = old_timezone.localize(my_timestamp)
new_timezone_timestamp = localized_timestamp.astimezone(new_timezone)

# or alternatively, as an one-liner
new_timezone_timestamp = old_timezone.localize(my_timestamp).astimezone(new_timezone) 

奖励:但如果您只需要特定时区的当前时间,您可以方便地将那个时区直接传递给 datetime.now() 以直接获取当前时间:

datetime.datetime.now(new_timezone)

当涉及到通常需要时区转换时,我强烈建议您应该以 UTC 格式将所有时间戳存储在您的数据库中,UTC 没有夏令时 (DST) 转换。作为一种好的做法,应该始终选择启用时区支持(即使您的用户都在同一个时区!)。这将帮助您避免困扰当今众多软件的 DST 转换问题。

除 DST 之外,软件时间通常非常棘手。要了解一般情况下在软件中处理时间有多么困难,这里有一个可能具有启发性的资源:http://yourcalendricalfallacyis.com

即使是将 datetime/timestamp 转换为日期这样看似简单的操作也可能变得不那么明显。正如 this helpful documentation 指出的那样:

A datetime represents a point in time. It’s absolute: it doesn’t depend on anything. On the contrary, a date is a calendaring concept. It’s a period of time whose bounds depend on the time zone in which the date is considered. As you can see, these two concepts are fundamentally different.

了解这种差异是避免基于时间的错误的关键一步。祝你好运。

How do you convert datetime/timestamp from one timezone to another timezone?

有两个步骤:

  1. 根据系统时间和时区创建感知日期时间对象 例如,获取给定时区的当前系统时间:

    #!/usr/bin/env python
    from datetime import datetime
    import pytz
    
    server_timezone = pytz.timezone("US/Eastern")
    server_time = datetime.now(server_timezone) # you could pass *tz* directly
    

    注意: 例如,在夏令时转换期间,server_timezone.localize(datetime.now()) 可能会失败(50% 的几率)。

    如果您确定您的输入时间存在于服务器的时区中并且它是唯一的,那么您可以传递 is_dst=None 来断言:

    server_time = server_timezone.localize(naive_time, is_dst=None)
    

    它引发无效时间的异常。
    如果忽略最多 一天 错误是可以接受的(尽管通常由于 DST 导致的错误大约是 一个小时 ),那么您可以删除 is_dst参数:

    server_time = server_timezone.normalize(server_timezone.localize(naive_time))
    

    .normalize() 被调用以调整不存在的时间(间隙中的本地时间,在 "spring forward" 转换期间)。如果时区规则没有改变;您的服务器不应生成不存在的时间。参见

  2. 将感知日期时间对象转换为目标时区tz:

    tz = pytz.timezone("US/Pacific")
    server_time_in_new_timezone = server_time.astimezone(tz)
    

对于 Python 3.9,标准库拥有您需要的一切:zoneinfo. pytz is not needed anymore (deprecated; -> pytz deprecation shim)。

例如:

from datetime import datetime
from zoneinfo import ZoneInfo

server_timezone = "US/Eastern"
new_timezone = "US/Pacific"

current_time = datetime.now(ZoneInfo(server_timezone)) 

# current_time_in_new_timezone = ???
current_time_in_new_timezone = current_time.astimezone(ZoneInfo(new_timezone))

给你举个例子

print(current_time.isoformat(timespec='seconds'))
# 2021-10-04T02:42:54-04:00

print(repr(current_time))
# datetime.datetime(2021, 10, 4, 2, 42, 54, 40600, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))

print(current_time_in_new_timezone.isoformat(timespec='seconds'))
# 2021-10-03T23:42:54-07:00

print(repr(current_time_in_new_timezone))
# datetime.datetime(2021, 10, 3, 23, 42, 54, 40600, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific'))