淺談Python3中datetime不同時(shí)區(qū)轉(zhuǎn)換介紹與踩坑
最近的項(xiàng)目需要根據(jù)用戶所屬時(shí)區(qū)制定一些特定策略,學(xué)習(xí)、應(yīng)用了若干python3的時(shí)區(qū)轉(zhuǎn)換相關(guān)知識(shí),這里整理一部分記錄下來(lái)。
下面涉及的幾個(gè)概念及知識(shí)點(diǎn):
GMT時(shí)間:Greenwich Mean Time, 格林尼治平均時(shí)間
UTC時(shí)間:Universal Time Coordinated 世界協(xié)調(diào)時(shí),可以認(rèn)為是更精準(zhǔn)的GMT時(shí)間,但兩者誤差極小,在1s以內(nèi),一般可視為等同
LMT:Local Mean Time, 當(dāng)?shù)貥?biāo)準(zhǔn)時(shí)間
Python中的北京時(shí)間:Python的標(biāo)準(zhǔn)timezone中信息中并沒(méi)有Asia/Beijing,原因要追溯到國(guó)民政府期間上報(bào)給國(guó)際標(biāo)準(zhǔn)的五個(gè)時(shí)區(qū)城市沒(méi)有北京,因此一般使用Asia/Shanghai獲取東8區(qū)時(shí)間
Python使用到的時(shí)間相關(guān)函數(shù)及概念:
包含時(shí)區(qū)信息的datetime稱為: offset-aware datetime,反之稱為offset-naive datetime
pytz.timezone(x): pytz package中預(yù)定義的時(shí)區(qū)相關(guān)對(duì)象, pytz可通過(guò) python3 -m pip install pytz 安裝
datetime(...) : 直接指定year/month/day/hour/second生成naive datetime
datetime(...tzinfo=tz) : 直接指定year/month/day/hour/second+時(shí)區(qū)信息生成offset-aware datetime
datetime.now(): 生成當(dāng)前默認(rèn)時(shí)區(qū)的 naive datetime
datetime.now(tzinfo=tz): 生成指定時(shí)區(qū)的offset-aware datetime
datetime.strptime(string, format) : 生成當(dāng)前默認(rèn)時(shí)區(qū)的string、format表示的 naive datetime
datetime.replace(tzinfo=tz): 直接替換datetime 時(shí)區(qū)信息為tz時(shí)區(qū)offset-aware datetime--不針對(duì)時(shí)區(qū)進(jìn)行任何轉(zhuǎn)換
datetime.astimezone(tz): 將時(shí)間轉(zhuǎn)換為新的tz時(shí)區(qū)的offset-aware datetime
下述代碼示例中,由于云主機(jī)位于日本,所以默認(rèn)時(shí)區(qū)為東9區(qū)(Asia/Tokyo)
Python中獲取當(dāng)前時(shí)刻時(shí)間:
In [1]: import pytz In [2]: from datetime import datetime, timedelta In [3]: datetime.now() # 默認(rèn)時(shí)區(qū)當(dāng)前時(shí)間 Out[3]: datetime.datetime(2021, 8, 1, 18, 36, 8, 352873) In [4]: datetime.now(pytz.timezone('Asia/Tokyo')) # 指定Tokyo時(shí)區(qū)當(dāng)前時(shí)間 Out[4]: datetime.datetime(2021, 8, 1, 18, 36, 25, 421048, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
可以看到,datetime.now()未指定時(shí)區(qū)時(shí),獲取到的對(duì)象是offset-navie datetime,而指定時(shí)區(qū)后則是offset-aware datetime,naive和aware的datetime是不可以執(zhí)行比較、相減相關(guān)操作的,只有同類型的datetime才能求時(shí)間差值、比較大小,如下:
In [5]: datetime.now() - datetime.now(pytz.timezone('Asia/Tokyo')) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-5-8b6c111dc5de> in <module> ----> 1 datetime.now() - datetime.now(pytz.timezone('Asia/Tokyo')) TypeError: can't subtract offset-naive and offset-aware datetimes In [6]: datetime.now() - datetime.now() # 只有同樣的offset-naive datetime才能求差值 Out[6]: datetime.timedelta(days=-1, seconds=86399, microseconds=999991) In [8]: datetime.now(pytz.timezone('Asia/Tokyo')) - datetime.now(pytz.timezone('Asia/Tokyo')) # 同樣的offset-aware datetime才能求差值 Out[8]: datetime.timedelta(days=-1, seconds=86399, microseconds=999976)
這里碰到了第一個(gè)坑,比如我們想獲得北京時(shí)間2021年1月1日0點(diǎn)的datetime,然后將其轉(zhuǎn)換為東京時(shí)間,直覺(jué)上我們很可能這么寫(xiě):
In [19]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')) # 這里獲取北京時(shí)間20210101 0點(diǎn)的datetime Out[19]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>) # 注意獲取的是LMT時(shí)間 In [21]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')).astimezone(pytz.timezone('Asia/Tokyo')) # 將北京時(shí)轉(zhuǎn)換為東京時(shí)間 Out[21]: datetime.datetime(2021, 1, 1, 0, 54, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>) # 獲取的是日本標(biāo)準(zhǔn)時(shí)間JST+9 In [22]: datetime.now(pytz.timezone('Asia/Shanghai')) # 示例獲取當(dāng)前時(shí)刻北京時(shí)間 Out[22]: datetime.datetime(2021, 8, 1, 18, 11, 6, 706727, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>) # 獲取的是中國(guó)標(biāo)準(zhǔn)時(shí)間(CST+8)
仔細(xì)一看,北京時(shí)間的0點(diǎn)轉(zhuǎn)化為東京時(shí)間卻是0:54,相差是54分鐘,而不是1個(gè)小時(shí),這就奇怪了,仔細(xì)一看tzinfo中的信息是LMT+8:06:00 STD,表示這是LMT時(shí)間,相比UTC快8小時(shí)6分鐘,而不是東8區(qū)標(biāo)準(zhǔn)時(shí)間,而通過(guò)astimezone方法轉(zhuǎn)換后得到的就是日本標(biāo)準(zhǔn)時(shí)間(東9區(qū)),所以兩者之前的差值并不是1小時(shí)整。
第一個(gè)坑究其原因,通過(guò)datetime(..tzinfo=..)指定時(shí)區(qū)獲取的是LMT,而datetime.now(tz)、datetime.astimezone(tz) 獲取的卻是UTC(GMT)標(biāo)準(zhǔn)時(shí)間,LMT和GMT標(biāo)準(zhǔn)時(shí)間可能會(huì)有甚至十分鐘級(jí)的差值,這已經(jīng)足夠影響到程序的正常邏輯了。
所以如果要保證獲取標(biāo)準(zhǔn)時(shí)區(qū)的時(shí)間,建議避免使用Asia/Shanghai、Asia/Tokyo這類大洲/城市 字符串表示時(shí)間,而使用GMT、UTC這些無(wú)歧義的標(biāo)準(zhǔn)時(shí)區(qū),如下:
In [45]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-9')) Out[45]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-9'>) # 東9區(qū)應(yīng)使用GMT-9
這里第二個(gè)坑出現(xiàn)了,由于歷史原因,Python中timezone的表示中,時(shí)區(qū)偏移以西為正,以東為負(fù),和我們熟悉的ISO標(biāo)準(zhǔn)剛好相反,所以東9區(qū)應(yīng)該表示為Etc/GMT-9, 而Etc/GMT+9表示的其實(shí)是西9區(qū),如下可以驗(yàn)證GMT-9與JST相差0, GMT+9與JST相差18小時(shí)(64800s):
In [50]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-9')) - datetime(2021, 1, 1).astimezone(pytz.timezone('Asia/Tokyo')) Out[50]: datetime.timedelta(0) In [51]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT+9')) - datetime(2021, 1, 1).astimezone(pytz.timezone('Asia/Tokyo')) Out[51]: datetime.timedelta(seconds=64800)
最后,獲取指定時(shí)區(qū)2021年1月1日datetime的方式,以北京時(shí)間為例:
In [56]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8')) Out[56]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-8'>) In [58]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8')).astimezone(pytz.timezone('Asia/Shanghai')) Out[58]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>) # 可見(jiàn)GMT-8和東八區(qū)標(biāo)準(zhǔn)時(shí)間(CST+8)一致
進(jìn)一步如果要獲取指定時(shí)區(qū)零點(diǎn)的時(shí)間戳就很簡(jiǎn)單了:
In [44]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT0')).timestamp() # 獲取格林尼治時(shí)區(qū)2021年1月1日0點(diǎn)時(shí)間戳 Out[44]: 1609459200.0
另外兩種獲取指定時(shí)區(qū)時(shí)刻的方法,此三種方式彼此等價(jià):
In [51]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT0')) == datetime(2021, 1, 1).replace(tzinfo=pytz.timezone('Etc/GMT0')) Out[51]: True In [53]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT0')) == datetime.strptime('20210101', '%Y%m%d').replace(tzinfo=pytz.timezone('Etc/GMT0'))
到此這篇關(guān)于淺談Python3中datetime不同時(shí)區(qū)轉(zhuǎn)換介紹與踩坑的文章就介紹到這了,更多相關(guān)Python3 datetime不同時(shí)區(qū)轉(zhuǎn)換 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Python中os模塊及shutil模塊的常規(guī)操作
這篇文章主要介紹了淺談Python中os模塊及shutil模塊的常規(guī)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04詳解Python利用configparser對(duì)配置文件進(jìn)行讀寫(xiě)操作
這篇文章主要介紹了詳解Python利用configparser對(duì)配置文件進(jìn)行讀寫(xiě)操作,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11python中HTMLParser模塊知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是一篇關(guān)于python中HTMLParser模塊知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們可以跟著學(xué)習(xí)下。2021-01-01Django零基礎(chǔ)入門(mén)之路由path和re_path詳解
這篇文章主要介紹了Django零基礎(chǔ)入門(mén)之路由path和re_path,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09python機(jī)器學(xué)習(xí)理論與實(shí)戰(zhàn)(二)決策樹(shù)
這篇文章主要介紹了python機(jī)器學(xué)習(xí)理論與實(shí)戰(zhàn)第二篇,決策樹(shù)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Python之Anaconda啟動(dòng)過(guò)程中的異常錯(cuò)誤問(wèn)題及解決
這篇文章主要介紹了Python之Anaconda啟動(dòng)過(guò)程中的異常錯(cuò)誤問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09如何將自己的python庫(kù)打包成wheel文件并上傳到pypi
這篇文章主要介紹了如何將自己的python庫(kù)打包成wheel文件并上傳到pypi,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04