python中時間處理標準庫DateTime加強版庫:pendulum

金色旭光發表於2021-09-17

DateTime 的時區問題

Python的datetime可以處理2種型別的時間,分別為offset-naiveoffset-aware。前者是指沒有包含時區資訊的時間,後者是指包含時區資訊的時間,只有同型別的時間才能進行減法運算和比較。

datetime模組的函式在預設情況下都只生成offset-naive型別的datetime物件,例如now()、utcnow()、fromtimestamp()、utcfromtimestamp()和strftime()。

其中now()和fromtimestamp()可以接受一個tzinfo物件來生成offset-aware型別的datetime物件,但是標準庫並不提供任何已實現的tzinfo類,只能自己實現。

一旦生成了一個offset-aware型別的datetime物件,我們就能呼叫它的astimezone()方法,生成其他時區的時間(會根據時差來計算)。

而如果拿到的是offset-naive型別的datetime物件,也是可以呼叫它的replace()方法來替換tzinfo的,只不過這種替換不會根據時差來調整其他時間屬性。

因此,如果拿到一個格林威治時間的offset-naive型別的datetime物件,直接呼叫replace(tzinfo=UTC())即可轉換成offset-aware型別,然後再呼叫astimezone()生成其他時區的datetime物件。

不帶時區datetime例項
普通的datetime都是不帶時區資訊

>>> from datetime import datetime
>>> datetime_now = datetime.now()
>>> 
>>> datetime_now
datetime.datetime(2021, 9, 15, 9, 34, 18, 600690)

帶時區datetime例項

>>> import pytz
>>> datetime_now = datetime.now(tz=pytz.utc)
>>> 
>>> datetime_now
datetime.datetime(2021, 9, 14, 3, 28, 31, 421979, tzinfo=<UTC>)

pendulum

pendulum是對datetime的高階封裝,主要是用的是帶時區的datetime

>>> import pendulum
>>> from datetime import datetime
>>> 
>>> now = pendulum.now()
>>> now
DateTime(2021, 9, 14, 11, 36, 52, 473028, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> isinstance(now, datetime)
True

建立DateTime例項

pendulum是簡化時間操作的模組。
特別注意確保正確處理時區,並基於底層tzinfo實現。例如,所有比較均以UTC或所用日期時間的時區進行。pendulum預設建立帶時區的datetime,但時區並不是當地時區,而是標準時區UTC時區。

建立

dt = pendulum.datetime(2015, 2, 5)
>>> dt
DateTime(2015, 2, 5, 0, 0, 0, tzinfo=Timezone('UTC'))

如果沒有指明時分秒,那麼就是00:00:00,並且時區是UTC。此外它也可以是Timezone例項或一個簡單的時區字值

pendulum.datetime(2015, 2, 5, tz='Europe/Paris')

tz = pendulum.timezone('Europe/Paris')
pendulum.datetime(2015, 2, 5, tz=tz)

local
local 和 datetime()很類似,但是會自動設定時區為本地時區

dt = pendulum.local(2015, 2, 5)
>>> dt
DateTime(2015, 2, 5, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))

local 類似於
pendulum.datetime(2015, 2, 5, tz='local')

now
now 和 local一樣,也會自動設定時區為本地時區

now = pendulum.now()
>>> 
>>> now
DateTime(2021, 9, 13, 22, 39, 53, 257567, tzinfo=Timezone('Asia/Shanghai'))

today
獲取當天的起始時間,零時零點。帶時區

>>> to =pendulum.today()
>>> to
DateTime(2021, 9, 13, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> print(to)
2021-09-13T00:00:00+08:00

tommorrow
獲取明天的起始時間,零時零點。帶時區

>>> tomm = pendulum.tomorrow()
>>> 
>>> tomm
DateTime(2021, 9, 14, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))
>>> print(tomm)
2021-09-14T00:00:00+08:00

yesterday
獲取昨天的起始時間,零時零點,帶時區

>>> yes = pendulum.yesterday()
>>> 
>>> yes
DateTime(2021, 9, 12, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))
>>> print(yes)
2021-09-12T00:00:00+08:00

naive 不帶時區例項
pendulum是強制使用時區的,使用它們是使用庫的首選和推薦方式。但是如果你真的需要一個簡單的DateTime物件,可以使用naive()

>>> naive = pendulum.naive(2015, 2, 5)
>>> naive
DateTime(2015, 2, 5, 0, 0, 0)
>>> print(naive)
2015-02-05T00:00:00

從字串建立

form_format
form_format和原生的datetime.strptime()很類似,但使用自定義標記建立DateTime例項。

>>> dt = pendulum.from_format('1975-05-21 22', 'YYYY-MM-DD HH')
>>> 
>>> dt
DateTime(1975, 5, 21, 22, 0, 0, tzinfo=Timezone('UTC'))
>>> 

同樣可以接受一個時區

>>> dt = pendulum.from_format('1975-05-21 22', 'YYYY-MM-DD HH', tz='Asia/Shanghai')
>>> 
>>> dt
DateTime(1975, 5, 21, 22, 0, 0, tzinfo=Timezone('Asia/Shanghai'))

form_timestamp
最後一個助手用於處理unix時間戳。from_timestamp()將建立一個與給定時間戳相等的DateTime例項,並設定時區或將其預設為UTC。

>>> dt = pendulum.from_timestamp(-1)
>>> 
>>> dt
DateTime(1969, 12, 31, 23, 59, 59, tzinfo=Timezone('UTC'))

instance:從DateTime建立
如果你發現你繼承了一個datetime.datetime的例項,你可以建立一個DateTime例項通過instance方法

>>> dt = datetime(2008, 1, 1)
>>> p = pendulum.instance(dt)
>>> print(p)
DateTime(2008, 1, 1, 0, 0, 0, tzinfo=Timezone('UTC'))

parse
pendulum.parse() 可以將字串轉peewee物件,預設不設定本地時區,可通過tz手動新增時區。字串格式支援:

  • RFC 3339 format
  • ISO 8601 formats
>>> dt = pendulum.parse('1975-05-21T22:00:00')
>>> print(dt)
'1975-05-21T22:00:00+00:00

pendulum 物件的屬性和方法

pendulum 物件有十分龐大的屬性和方法

輸出當前時刻的年月日,時分秒

>>> dt.year
2021
>>> dt.month
9
>>> dt.day
14
>>> dt.hour
15
>>> dt.minute
24
>>> dt.second
15
>>> dt.microsecond
928205
>>>

輸出當前時刻是一年中的第幾周,第幾天,第幾個月

>>> dt.day_of_week
2
>>> dt.day_of_year
257
>>> dt.week_of_month
3
>>> dt.week_of_year
37
>>> 
>>> dt.days_in_month
30
>>>

輸出物件的時區相關,時區名字,時區型別本地時區或標準時區

>>> dt.timezone
Timezone('Asia/Shanghai')
>>> 
>>> dt.is_local()
True
>>> dt.is_utc()
False

date和time屬性

date通常是指年月日的時間
time通常是指時分秒的時間
pendulum屬性中可以輸出date和time

date

>>> date_obj = now.date()
>>> date_obj
Date(2021, 9, 14)
>>>
>>> print(date_obj)
2021-09-14
>>>
>>> type(date_obj)
<class 'pendulum.date.Date'>

time

>>> time_ojb = now.time()
>>> time_obj
Time(19, 27, 53, 49291)
>>> 
>>> print(time_obj)
19:27:53.049291
>>> 
>>> type(time_obj)
<class 'pendulum.time.Time'>

date,time都不是datetime型別的。所以在一些時間加減運算,比較等操作是date和time都不可以和pendulum直接運算。

>>> now
DateTime(2021, 9, 14, 19, 27, 53, 49291, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> isinstance(now, datetime)
True
>>> isinstance(date_obj, datetime)
False
>>> 
>>> isinstance(time_obj, datetime)
False
>>> 

date,time,datetime之間無法比較計算

>>> date_obj - time_obj
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'Date' and 'Time'
>>> 
>>> now - date_obj
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'DateTime' and 'Date'
>>> 
>>> now - time_obj
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'DateTime' and 'Time'
>>> 

字串輸出格式

pendulum例項的輸出格式,字串的輸出格式相關函式。

date,time,datetime 相關字串格式的輸出

>>> dt = pendulum.now()
>>> 
>>> dt.to_date_string()
'2021-09-14'
>>> 
>>> dt.to_time_string()
'15:57:47'
>>> 
>>> dt.to_datetime_string()
'2021-09-14 15:57:47'
>>> 

自定義輸出格式

>>> dt.format('dddd Do [of] MMMM YYYY HH:mm:ss A')
'Tuesday 14th of September 2021 15:57:47 PM'
>>> 
>>> dt.strftime('%A %-d%t of %B %Y %I:%M:%S %p')
'Tuesday 14\t of September 2021 03:57:47 PM'
>>> 

format 比 strftime 使用更加直觀,而且支援更多引數

>>> dt.format('YYYY-MM-DD HH:mm:ss')
'2021-09-14 15:57:47'

可以使用的引數:

- TOKEN OUTPUT
YEAR YYYY 2000, 2001, 2002 ... 2012, 2013
YY 00, 01, 02 ... 12, 13
Y 2000, 2001, 2002 ... 2012, 2013
QUARTER Q 1 2 3 4
Qo 1st 2nd 3rd 4th
MONTH MMMM January, February, March ...
MMM Jan, Feb, Mar ...
MM 01, 02, 03 ... 11, 12
M 1, 2, 3 ... 11, 12
Mo 1st 2nd ... 11th 12th
DAY OF YEAR DDDD 001, 002, 003 ... 364, 365
DDD 1, 2, 3 ... 4, 5
DAY OF MONTH DD 01, 02, 03 ... 30, 31
D 1, 2, 3 ... 30, 31
Do 1st, 2nd, 3rd ... 30th, 31st
DAY OF WEEK dddd Monday, Tuesday, Wednesday ...
ddd Mon, Tue, Wed ...
dd Mo, Tu, We ...
d 0, 1, 2 ... 6
DAYS OF ISO WEEK E 1, 2, 3 ... 7
HOUR HH 00, 01, 02 ... 23, 24
H 0, 1, 2 ... 23, 24
hh 01, 02, 03 ... 11, 12
h 1, 2, 3 ... 11, 12
MINUTE mm 00, 01, 02 ... 58, 59
m 0, 1, 2 ... 58, 59
SECOND ss 00, 01, 02 ... 58, 59
s 0, 1, 2 ... 58, 59

轉義字元

要轉義格式字串中的字元,可以將字元括在方括號中。

>>> import pendulum

>>> dt = pendulum.now()
>>> dt.format('[today] dddd')
'today Sunday'

時間比較

pendulum的時間可以直接比較。但是隻能限制在相同物件之間。如datetime和datetime之間可以比較,datetime和date無法比較,帶時區物件和不帶時區物件無法比較。

pendulum物件之間比較

>>> one = pendulum.now()
>>> 
>>> two = pendulum.now()
>>> 
>>> 
>>> one > two
False
>>>
>>> three = pendulum.datetime(2021,9,10)
>>> 
>>> one > three

datetime 和 date之間無法比較

>>> three = pendulum.datetime(2021,9,10).date()
>>> 
>>> 
>>> three
Date(2021, 9, 10)
>>> 
>>> one > three
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare DateTime to Date

帶時區和不帶時區不可以比較

>>> now = pendulum.now()
>>> datetime_demo = datetime(2021,9,12)
>>> 
>>> now - datetime_demo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/.virtualenvs/python3.8/lib/python3.8/site-packages/pendulum/datetime.py", line 1417, in __sub__
    return other.diff(self, False)
  File "/root/.virtualenvs/python3.8/lib/python3.8/site-packages/pendulum/datetime.py", line 794, in diff
    return Period(self, dt, absolute=abs)
  File "/root/.virtualenvs/python3.8/lib/python3.8/site-packages/pendulum/period.py", line 33, in __new__
    raise TypeError("can't compare offset-naive and offset-aware datetimes")
TypeError: can't compare offset-naive and offset-aware datetimes

時間加減運算

有一些需要對時間操作的場景,比如定時4個小時執行一段程式碼,這是需要對時間加減運算。時間加減返回DateTime例項,不會造成時區變化。加減運算僅限於同一種型別的時間。

方法:

  1. add 加法
  2. subtract 減法

pendulum支援的加減的時間顆粒度:

  1. years
  2. months
  3. days
  4. hours
  5. minutes
  6. seconds

同時也支援多個引數

>>> dt = pendulum.now()
>>> 
>>> 
>>> dt.add(years=1)
DateTime(2022, 9, 14, 16, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> dt.subtract(years=1)
DateTime(2020, 9, 14, 16, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> 
>>> 
>>> dt.add(months=2)
DateTime(2021, 11, 14, 16, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> dt.subtract(months=2)
DateTime(2021, 7, 14, 16, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> 
>>> 
>>> dt.add(days=3)
DateTime(2021, 9, 17, 16, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> dt.subtract(days=3)
DateTime(2021, 9, 11, 16, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> 
>>> 
>>> dt.add(hours=4)
DateTime(2021, 9, 14, 20, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> dt.subtract(hours=4)
DateTime(2021, 9, 14, 12, 16, 42, 375986, tzinfo=Timezone('Asia/Shanghai'))
>>> 

時間間距計算

diff

diff 方法來計算兩個時間之間的間距。但是時間間距計算只限於同一種型別,帶時區和不帶時區時間無法計算。

diff()方法返回一個Period例項,它表示兩個DateTime例項之間的時間,這個間隔可以用不同的單位表示。

diff方法有兩個引數,第一個引數是DateTime,第二個是可選引數,表示時間差距是否以相對值返回。預設情況下差距是絕對差距,即沒有正負。預設是True,相對為False

絕對值

>>> one = pendulum.now()
>>> two = pendulum.datetime(2021,9,12)
>>> 
>>> one.diff(two)
<Period [2021-09-12T00:00:00+00:00 -> 2021-09-14T17:10:53.346499+08:00]>
>>> 
>>> one.diff(two).in_hours()
57
>>> two.diff(one).in_hours()
57

相對值

>>> one.diff(two, False).in_hours()
-57
>>> 
>>> two.diff(one, False).in_hours()
57

差值可以轉換的時間顆粒度

  1. 天 in_days()
  2. 小時 in_hours()
  3. 分鐘 in_minutes()

時間間隔物件 period 屬性和方法

當您從另一個DateTime例項中減去一個DateTime例項,或者使用diff()方法時,它將返回一個Period例項,可以叫做:時間間隔物件。
Period 繼承了Duration類,還增加了一個好處,即它知道生成它的例項,因此它可以訪問更多的方法和屬性。Duration類繼承自本機timedelta類。它對基類有很多改進。

>>> now = pendulum.now()
>>> 
>>> past = pendulum.datetime(2021,9,10)
>>> 
>>> (now - past).days
4
>>> 
>>> (past-now).days
-4
>>> 

period例項擁有的屬性和方法:

as_interval
as_timedelta
days
end
hours
in_days
in_hours
in_minutes
in_months
in_seconds
in_weeks
in_words
in_years
invert
max
microseconds
min
minutes
months
range
remaining_days
remaining_seconds
resolution
seconds
start
total_days
total_hours
total_minutes
total_seconds
total_weeks
weeks
years

從period中輸出時間間隔的方法:

  1. 年:years
  2. 星期:weeks
  3. 月:months
  4. 天:days
  5. 小時:hours
  6. 分鐘:minutes

時間間隔 period 例項

可以建立一個持續週期例項,來完成一些轉換。

>>> it = pendulum.duration(years=2, months=3)
>>> it.days
820
>>> it.total_seconds()
70848000.0
>>> 

如果你想獲得每個支援單元的持續時間,你可以使用適當的方法。將所有的時間以周,天,小時的時間來計算。

>>> one
DateTime(2021, 9, 12, 0, 0, 0, tzinfo=Timezone('UTC'))
>>> two
DateTime(2021, 9, 23, 0, 0, 0, tzinfo=Timezone('UTC'))
>>> t = two - one
>>> 
>>> t.total_weeks()
1.5714285714285714
>>>
>>> t.total_days()
11.0
>>> t.total_hours()
264.0
>>> t.total_minutes()
15840.0

pendulum常用方法

獲取時區的方法

>>> now =pendulum.now()
>>> pendulum.now()
DateTime(2021, 9, 13, 22, 47, 28, 47882, tzinfo=Timezone('Asia/Shanghai'))
>>> now
DateTime(2021, 9, 13, 22, 47, 27, 873328, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> 
>>> now.timezone.name
'Asia/Shanghai'

轉換時區

可以使用in_timezone()方法輕鬆更改DateTime例項的時區。

v>>> now = pendulum.datetime(2021,9,15)
>>> 
>>> now
DateTime(2021, 9, 15, 0, 0, 0, tzinfo=Timezone('UTC'))
>>> 
>>> now.in_timezone('Asia/Shanghai')
DateTime(2021, 9, 15, 8, 0, 0, tzinfo=Timezone('Asia/Shanghai'))

注意:需要避免在python3.6之前使用時區庫

period例項的建立

可以通過period()來建立一個例項。

>>> one
DateTime(2021, 9, 14, 17, 10, 53, 346499, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> 
>>> past
DateTime(2021, 9, 10, 0, 0, 0, tzinfo=Timezone('UTC'))
>>> 
>>> period = pendulum.period(one, past)
>>>
v>>> period
<Period [2021-09-14T17:10:53.346499+08:00 -> 2021-09-10T00:00:00+00:00]>

起始日期

pendulum提供了起始日期的獲取方法

開始時間
獲取一個時間的開始時間,可以為:year,month,day,week

>>> dt = pendulum.now()
>>> 
>>> dt.start_of('year')
DateTime(2021, 1, 1, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))
>>>
>>> dt.start_of('month')
DateTime(2021, 9, 1, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> dt.start_of('day')
DateTime(2021, 9, 14, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))
>>>
>>> dt.start_of('week')
DateTime(2021, 9, 13, 0, 0, 0, tzinfo=Timezone('Asia/Shanghai'))

截止日期
獲取一個時間的截止日期,可以為year,month,day,week

>>> dt.end_of('year')
DateTime(2021, 12, 31, 23, 59, 59, 999999, tzinfo=Timezone('Asia/Shanghai'))
>>> dt.end_of('month')
DateTime(2021, 9, 30, 23, 59, 59, 999999, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> dt.end_of('day')
DateTime(2021, 9, 14, 23, 59, 59, 999999, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> dt.end_of('week')
DateTime(2021, 9, 19, 23, 59, 59, 999999, tzinfo=Timezone('Asia/Shanghai'))

迭代時間範圍

如果希望在一個週期內進行迭代,可以使用range()方法。使用period物件的range方法,可以產生一個以引數為粒度的可迭代時間物件。

>>> one
DateTime(2021, 9, 14, 17, 10, 53, 346499, tzinfo=Timezone('Asia/Shanghai'))
>>> 
>>> 
>>> past
DateTime(2021, 9, 10, 0, 0, 0, tzinfo=Timezone('UTC'))
>>> 
>>> period = pendulum.period(one, past)
>>> 
>>> for i in period.range('months'):
...     print(i)
... 
2021-09-14T17:10:53.346499+08:00
>>> 
>>> for i in period.range('days'):
...     print(i)
... 
2021-09-14T17:10:53.346499+08:00
2021-09-13T17:10:53.346499+08:00
2021-09-12T17:10:53.346499+08:00
2021-09-11T17:10:53.346499+08:00
2021-09-10T17:10:53.346499+08:00
>>> for i in period.range('hours'):
...     print(i)
... 
2021-09-14T17:10:53.346499+08:00
2021-09-14T16:10:53.346499+08:00
2021-09-14T15:10:53.346499+08:00
2021-09-14T14:10:53.346499+08:00
2021-09-14T13:10:53.346499+08:00
.....

修改時間

pendulum提供一些幫助,通過對舊例項修改,返回一個新的例項。
但是,除了顯式設定時區外,這些幫助程式都不會更改例項的時區。具體而言,設定時間戳不會將相應的時區設定為UTC。

>>> dt = pendulum.now()
>>> dt.set(year=1975, month=5, day=21).to_datetime_string()
'1975-05-21 15:24:15'

in_timezone() in_tz()可以將時間轉化到設定的時區。

>>> dt = pendulum.now()
>>> 
>>> dt
DateTime(2021, 9, 14, 15, 50, 9, 590527, tzinfo=Timezone('Asia/Shanghai'))
>>> dt.in_timezone('Europe/London')
DateTime(2021, 9, 14, 8, 50, 9, 590527, tzinfo=Timezone('Europe/London'))
>>> 

相關文章