【超详细】Python datetime(当前日期、时间戳转换、前一天日期等)【附:时区原理详解】
文章目录
作者:小猪快跑
基础数学&计算数学,从事优化领域6年+,主要研究方向:MIP求解器、整数规划、随机规划、智能优化算法
python3 的时区是一个很容易出错的地方。本篇将从原理层面剖析时区概念,让读者真正学懂时区,不踩坑。
如有错误,欢迎指正。如有更好的算法,也欢迎交流!!!——@小猪快跑
相关文献
当前时间
from datetime import datetime
print(datetime.now())
# 2024-07-12 14:39:59.531525
前一天日期、后一天日期
from datetime import datetime, timedelta
dt = datetime(2024, 1, 1)
dt + timedelta(1)
# datetime.datetime(2024, 1, 2, 0, 0)
dt - timedelta(1)
# datetime.datetime(2023, 12, 31, 0, 0)
东八区(北京)时间
import pytz
from datetime import datetime
print(datetime(2024, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8')))
# 2024-01-01 00:00:00+08:00
时间戳转换
datetime -> str
import pytz
from datetime import datetime
dt = datetime(2024, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8'))
fmt = '%Y-%m-%d %H:%M:%S%z'
dt.strftime(fmt)
# 2024-01-01 00:00:00+0800
fmt = '%a %d %b %Y, %I:%M%p'
dt.strftime(fmt)
# Mon 01 Jan 2024, 12:00AM
fmt = '%d/%m/%y %H:%M:%S.%f'
dt.strftime(fmt)
# 01/01/24 00:00:00.000000
'The {1} is {0:%d}, the {2} is {0:%B}, the {3} is {0:%I:%M%p}.'.format(dt, "day", "month", "time")
# 'The day is 01, the month is January, the time is 12:00AM.'
str -> datetime
from datetime import datetime
datetime.strptime("21/11/06 16:30", "%d/%m/%y %H:%M")
# datetime.datetime(2006, 11, 21, 16, 30)
datetime -> timestamp(时间戳)
from datetime import datetime
dt = datetime(2024, 1, 1)
dt.timestamp()
# 1704038400.0
timestamp -> datetime
from datetime import datetime
datetime.fromtimestamp(1704038400)
# datetime.datetime(2024, 1, 1, 0, 0)
下面列出了 1989 年 C 标准所要求的所有格式代码,这些代码可在所有使用标准 C 实现的平台上运行。
指令 | 含义 | 样例 |
---|---|---|
%a | 工作日作为地方缩写名称。 | Sun, Mon, …, Sat (en_US);So, Mo, …, Sa (de_DE) |
%A | 作为地区全称的工作日。 | Sunday, Monday, …, Saturday (en_US);Sonntag, Montag, …, Samstag (de_DE) |
%w | 以十进制数字表示的工作日,其中 0 代表周日,6 代表周六。 | 0, 1, …, 6 |
%d | 以小数点后 0 位数字表示的月日。 | 01, 02, …, 31 |
%b | 月(Month)作为本地语言的缩写名称。 | Jan, Feb, …, Dec (en_US);Jan, Feb, …, Dez (de_DE) |
%B | 以本地全称表示的月份。 | January, February, …, December (en_US);Januar, Februar, …, Dezember (de_DE) |
%m | 以十进制零位表示的月份。 | 01, 02, …, 12 |
%y | 以小数点后零位的数字表示不带世纪的年份。 | 00, 01, …, 99 |
%Y | 带世纪的年为十进制数。 | 0001, 0002, …, 2013, 2014, …, 9998, 9999 |
%H | 小时(24 小时制时钟)为零位小数。 | 00, 01, …, 23 |
%I | 小时(12 小时钟)为零位小数。 | 01, 02, …, 12 |
%p | 当地的上午或下午。 | AM, PM (en_US);am, pm (de_DE) |
%M | 分钟,小数点后零位。 | 00, 01, …, 59 |
%S | 秒(小数点后零位)。 | 00, 01, …, 59 |
%f | 微秒为十进制数,置零后为 6 位数。 | 000000, 000001, …, 999999 |
%z | UTC偏移量,格式为±HHMM[SS[.fffffff]](如果对象为空字符串)。 | (empty), +0000, -0400, +1030, +063415, -030712.345216 |
%Z | 时区名称(如果对象为非正则表达式,则为空字符串)。 | (empty), UTC, GMT |
%j | 以十进制零位形式表示的年日。 | 001, 002, …, 366 |
%U | 年份的星期数(星期日为一周的第一天),以十进制零位表示。在新的一年中,第一个星期日之前的所有天数都被视为第 0 周。 | 00, 01, …, 53 |
%W | 年的周号(周一为一周的第一天),小数点后加 0。在新的一年中,第一个星期一之前的所有日子都被视为第 0 周。 | 00, 01, …, 53 |
%c | 当地语言的日期和时间表示法。 | Tue Aug 16 21:30:00 1988 (en_US);Di 16 Aug 21:30:00 1988 (de_DE) |
%x | 本地语言的日期表示法。 | 08/16/88 (None);08/16/1988 (en_US);16.08.1988 (de_DE) |
%X | 当地语言中合适的时间表示法。 | 21:30:00 (en_US);21:30:00 (de_DE) |
%% | 字面"%"字符。 | % |
获取日期中的年、季度、月、周、日、小时、分、秒等
from datetime import datetime
dt = datetime.strptime("21/11/06 16:30", "%d/%m/%y %H:%M")
# Using datetime.timetuple() to get tuple of all attributes
tt = dt.timetuple()
for it in tt:
print(it)
2006 # year
11 # month
21 # day
16 # hour
30 # minute
0 # second
1 # weekday (0 = Monday)
325 # number of days since 1st January
-1 # dst - method tzinfo.dst() returned None
时区原理
时区问题复杂性的来源
DST (Daylight Saving Time) 夏令时
是一种在夏季期间将时钟向前调整一小时的做法,目的是为了在白天较长的季节里更有效地利用自然光照,从而节省能源。在夏令时期间,人们可以享受到更多的日光时间,理论上可以减少照明需求。
夏令时的实施通常遵循以下模式:
在春季,时钟在特定的周末(通常是三月或四月的某个周日)凌晨2点时向前调整一小时,变为3点。
在秋季,时钟则在特定的周末(通常是十月或十一月的某个周日)凌晨2点时向后调整一小时,回到1点。
不过,每个国家和地区的具体规则可能有所不同,有些地区可能不实行夏令时。
在中国,曾经在1986年至1991年间实行过夏令时,具体的调整时间为:
自1992年起,中国暂停实行夏令时,目前中国全境全年使用的是北京时间,即东八区的标准时间,没有进行夏令时调整。
ST(Standard Time) 冬令时
GMT (Greenwich Mean Time) 格林尼治标准时间
UT (Universal Time) 世界时
TAI(International Atomic Time)国际原子时
UTC (Universal Time Coordinated) 协调世界时:
LMT (Local Mean Time) 地方平时
CST时间 China Standard Time
以上几个因素是时区问题复杂度的来源,为了解决这个问题,人们成立了时区信息数据库,Linux 系统也是采用了该数据库来维护系统时间。
深入理解 datetime 的坑
不同函数时区标准不同
创建 datetime 对象使用的是 LMT
import pytz
from datetime import datetime
# LMT
datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')) # 2021-01-01 00:00:00+08:06
datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Tokyo')) # 2021-01-01 00:00:00+09:19
转换时区函数 astimezone 输出是 ST/DST,除非输入时区 == 输出时区
import pytz
from datetime import datetime
tz_sh = datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')) # 2021-01-01 00:00:00+08:06
# UTC
tz_sh.astimezone(pytz.timezone('Asia/Tokyo')) # 2021-01-01 00:54:00+09:00
# LMT
tz_sh.astimezone(pytz.timezone('Asia/Shanghai')) # 2021-01-01 00:00:00+08:06
替换时区函数 replace 是 LMT
import pytz
from datetime import datetime
tz_sh = datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')) # 2021-01-01 00:00:00+08:06
tz_sh.replace(tzinfo=pytz.timezone('Asia/Tokyo')) # 2021-01-01 00:00:00+09:19
获取当前时间 datetime.now 是 ST/DST
import pytz
from datetime import datetime
# UTC
datetime.now(tz=pytz.timezone('Asia/Shanghai')) # 2024-07-18 14:37:33.706649+08:00
normalize: LMT 转换成 ST/DST(注意1988年中国有夏令时)
import pytz
from datetime import datetime
tz = pytz.timezone("Asia/ShangHai")
tz.normalize(datetime(2021, 1, 1, tzinfo=tz)) # 2020-12-31 23:54:00+08:00
tz.normalize(datetime(1988, 8, 1, tzinfo=tz)) # 1988-08-01 00:54:00+09:00
localize:增加 ST/DST(注意1988年中国有夏令时)
import pytz
from datetime import datetime
tz = pytz.timezone("Asia/ShangHai")
tz.localize(datetime(2021, 1, 1)) # 2021-01-01 00:00:00+08:00
tz.localize(datetime(1988, 8, 1)) # 1988-08-01 00:00:00+09:00
timezone 时区偏移以西为正,以东为负,和ISO标准相反
import pytz
from datetime import datetime
# 东八区
datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8')) # 2021-01-01 00:00:00+08:00
# 西八区
datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT+8')) # 2021-01-01 00:00:00-08:00
笔者建议
'Etc/GMT-8'
。datetime 源码解读
或许是由于时区非常复杂,datetime 时区仅提供了 tzinfo 的抽象类。可以使用常见的时区库:如 pytz。
tzinfo 两个接口值得注意:
class tzinfo:
@abstractmethod
def tzname(self, __dt: datetime | None) -> str | None: ...
@abstractmethod
def utcoffset(self, __dt: datetime | None) -> timedelta | None: ...
# Return the timezone offset as timedelta positive east of UTC (negative west of UTC).
# 这是描述了该时区当地平均时间与 UTC 时间的偏移
@abstractmethod
def dst(self, __dt: datetime | None) -> timedelta | None: ...
def fromutc(self, __dt: datetime) -> datetime: ...
# datetime in UTC -> datetime in local time.
# 这个接口做的事情是将该UTC时间更改为当地标准时间。
# 这是一个法律概念上的时间,需要考虑到夏令时,历史等因素。
datetime 中更换时区的基本过程,通过两个时区相对于 utc 时间的偏移,计算出两个时区的时间间隔。加上该间隔,然后直接更换时区:
(dt+ timedelta).raplace(tz)
pytz 源码解读
pytz 是时区信息数据库的 python 接口,使用了 tzif 文件来存储、描述时区,与 linux 相同。
pytz.timezone("Asia/ShangHai")
读取 tzif 文件中的信息创建对象。
一般我们认为 pytz.timezone("Asia/ShangHai")
是一个时区,其实这不完全正确,准确来说,pytz.timezone("Asia/ShangHai")
应该是一个地方时区库。
它存储了三个时区:
可以通过一下代码进行查看
import pytz
pytz.timezone("Asia/ShangHai")._tzinfos
# {(datetime.timedelta(seconds=29160), datetime.timedelta(0), 'LMT'): <DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>, (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST'): <DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>, (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT'): <DstTzInfo 'Asia/Shanghai' CDT+9:00:00 DST>}
pytz 库之所以被诟病的原因就是这三个时区的转换,让人有些难以把握。
除了这些信息,他还记录了上海时区变迁的历史过程。 可以使用以下代码查看
import pytz
tz = pytz.timezone("Asia/ShangHai")
for a, b in zip(tz._utc_transition_times, tz._transition_info):
print(a, b)
"""
0001-01-01 00:00:00 (datetime.timedelta(seconds=29160), datetime.timedelta(0), 'LMT')
1901-12-13 20:45:52 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1919-04-12 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1919-09-30 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1940-05-31 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1940-10-12 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1941-03-14 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1941-11-01 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1942-01-30 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1945-09-01 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1946-05-14 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1946-09-30 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1947-04-14 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1947-10-31 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1948-04-30 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1948-09-30 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1949-04-30 16:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1949-05-27 15:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1986-05-03 18:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1986-09-13 17:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1987-04-11 18:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1987-09-12 17:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1988-04-16 18:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1988-09-10 17:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1989-04-15 18:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1989-09-16 17:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1990-04-14 18:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1990-09-15 17:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
1991-04-13 18:00:00 (datetime.timedelta(seconds=32400), datetime.timedelta(seconds=3600), 'CDT')
1991-09-14 17:00:00 (datetime.timedelta(seconds=28800), datetime.timedelta(0), 'CST')
"""
作者:小猪快跑爱摄影