2022年8月28日 星期日

Python 學習筆記 : 時間模組 time

Python 並沒有提供時間的資料型態 (像 int, float, str, tuple, list 等那樣), 而是在標準函式庫中以 time, datetime, 與 calender 這三個模組來處理日期與時間資訊. 本篇測試並整理 time 模組的用法. 

time 模組主要是依賴底層的 C 函式庫來取得系統的時鐘資訊與處理器的執行時間, 還提供了基本的剖析與字串格式化功能. 在 MicroPython 的物聯網應用中, time 模組的 sleep() 函式常用來控制致動器 (actuator) 的延遲時間.  

本篇主要是我閱讀下列書籍時隨手測試所做的整理 :
  1. Python 零基礎入門班 (碁峰 2018, 文淵閣工作室)
  2. 增壓的 Python-讓程式碼進化到全新境界 (碁峰 2020)
  3. Python Bible 自學聖經 (碁峰 2020, 文淵閣工作室)
首先來檢視 random 模組的公開成員, 以下使用一個自訂模組 members, 其 list_members() 函式會列出模組或套件中的公開成員 (即屬性與方法), 參考 :

# Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法

使用 time 模組之前須先用 import 匯入 : 

>>> import time   
>>> members.list_members(time)   
altzone <class 'int'>
asctime <class 'builtin_function_or_method'>
clock <class 'builtin_function_or_method'>
ctime <class 'builtin_function_or_method'>
daylight <class 'int'>
get_clock_info <class 'builtin_function_or_method'>
gmtime <class 'builtin_function_or_method'>
localtime <class 'builtin_function_or_method'>
mktime <class 'builtin_function_or_method'>
monotonic <class 'builtin_function_or_method'>
monotonic_ns <class 'builtin_function_or_method'>
perf_counter <class 'builtin_function_or_method'>
perf_counter_ns <class 'builtin_function_or_method'>
process_time <class 'builtin_function_or_method'>
process_time_ns <class 'builtin_function_or_method'>
sleep <class 'builtin_function_or_method'>
strftime <class 'builtin_function_or_method'>
strptime <class 'builtin_function_or_method'>
struct_time <class 'type'>
thread_time <class 'builtin_function_or_method'>
thread_time_ns <class 'builtin_function_or_method'>
time <class 'builtin_function_or_method'>
time_ns <class 'builtin_function_or_method'>
timezone <class 'int'>
tzname <class 'tuple'>

常用函式如下表 : 


 time 模組常用函式 說明
 time() 傳回自初始計時 1970/1/1 至今的系統秒數時戳 (精確至微秒)
 localtime([sec]) 傳回目前時間或指定時戳秒數 sec 的 struct_time 物件
 ctime([sec]) 傳回目前時間或指定時戳秒數 sec 的時間字串
 sleep(sec) 程序暫停執行 sec 秒 (可為浮點數)
 process_time() 傳回程序執行至此之時戳
 mktime(t) 將傳入之 struct_time 物件或元組 t 轉成時戳傳回, 乃 localtime() 反函數
 strftime(format [, t]) 以格式化字串顯示 localtime() 傳回之時戳 t


Python 處理時間的一個 tick 是微秒 (百萬分之一秒), 呼叫 time() 函式所傳回的時戳是一個浮點數, 單位為秒, 此時戳為自初始計時日 1970/1/1 日零時零分零秒開始計算到目前的秒數, 例如 : 

>>> time.time()    
1661485827.710501   

這表示自 1970/1/1 日至目前已過了 1661485827.710501 秒. 這時戳必須經過換算才知道是幾年幾個月幾日幾時幾分幾秒, 看起來似乎實用性不高, 但其實它是一個基礎函式, 主要是用來給 time 模組內其他函式計算之用. 

localtime([sec]) 函式會傳回目前或指定時戳秒數之 struct_time 物件, 例如 : 

>>> local=time.localtime()   
>>> local   
time.struct_time(tm_year=2022, tm_mon=8, tm_mday=26, tm_hour=14, tm_min=51, tm_sec=3, tm_wday=4, tm_yday=238, tm_isdst=0)
>>> type(local)   
<class 'time.struct_time'>   


 struct_time 物件屬性 說明
 tm_year 年
 tm_mon 月 (1~12)
 tm_mday 日 (1~31)
 tm_hour 時 (0~23)
 tm_min 分 (0~59)
 tm_sec 秒 (0~59)
 tm_wday 一星期中的第幾天 (0~6, 0=星期一, 6=星期日)
 tm_yday 一年中的第幾天 (1~366)
 tm_isdst 是否在日光節約時間, 0=否, 1=是


可以透過 struct_time 物件之屬性取得日期時間資訊, 例如 : 

>>> local.tm_year         # 年
2022
>>> local.tm_mon         # 月
8
>>> local.tm_mday       # 日
26
>>> local.tm_hour         # 時
14
>>> local.tm_min          # 分
51
>>> local.tm_sec            # 秒
3
>>> local.tm_wday        # 周
4
>>> local.tm_yday         # 年中之日
238
>>> local.tm_isdst          # 是否在日光節約時間
0

因為 localtime() 的傳入參數是時戳秒數, 故可以將 time.time() 的秒數傳給 localtime() :

>>> time.localtime(time.time())    
time.struct_time(tm_year=2022, tm_mon=8, tm_mday=26, tm_hour=19, tm_min=38, tm_sec=10, tm_wday=4, tm_yday=238, tm_isdst=0)

另一個函式 ctime() 功能與 localtime() 相同, 差別是 ctime() 的傳回值是可讀性較高的時間字串 (但時間精度僅到秒), 預設傳回目前時間, 例如 : 

>>> time.ctime()                        # 未傳入參數預設為目前時間
'Fri Aug 26 19:47:55 2022'

也可以將時戳秒數傳入 ctime() 轉成時間字串, 例如 :

>>> time.ctime(1661514622)       # 傳入時戳秒數
'Fri Aug 26 19:50:22 2022'

sleep() 函式會讓程序暫停指定之秒數進入休眠狀態, 等過了這時間再繼續往下執行, 在互動環境下提示號會在這段時間過後才會出現 :

>>> time.sleep(10)       # 等待 10 秒
>>> 

process_time() 函式會傳回程序執行時的時戳, 前後兩次呼叫 process_time() 之差值即為程序執行所花的時間, 此函式是 Python 3.3 版新增的, 用來取代舊版的 clock(), 在 Python 3.8 版中已正式移除 clock() 了. 

process_time() 與 time() 都可以用來計算程式執行時間, 但 time() 是牆上時鐘 (wall clock), 包含了 sleep() 的時間; 而 process_time() 則是程序時鐘, 不包含 sleep() 秒數, 參考 : 


我把該文之範例改編成如下程式碼 : 

# elapsed_time.py 
import time

process_start=time.process_time()
time_start=time.time()
i=0
for i in range(100000000):
    i += 1
time.sleep(5)    
process_end=time.process_time()
time_end=time.time()
print("process_time() elapsed : %f" % (process_end - process_start))
print("time() elapsed : %f" % (time_end - time_start))

結果如下 : 

>>> %Run elapsed_time.py
process_time() elapsed : 9.609375    
time() elapsed : 14.626676   

可見呼叫 process_time() 不會將 time.sleep() 休眠時間算進去. 如果將 time.sleep() 這列註解掉後重新執行, 結果會差不多 : 

>>> %Run test2.py
process_time() elapsed : 9.203125     
time() elapsed : 9.200775    

接下來看 mktime(t) 函式. 它是 localtime() 的反函式, 其傳入參數有兩種, 一是 time_struct 物件, 例如 local_time() 的傳回值就是此種物件, mktime() 的傳回值為傳入參數所代表的時戳 :

>>> time.mktime(time.localtime())      
1661653676.0

將 localtime() 傳回之 time_struct 物件傳入 mktime() 會得到該物件所代表之時戳. 

第二種傳入參數是一個 9 元素的 tuple (一定要 9 個元素, 少一個都不行, 前六個年月日時分秒決定傳回值, 後面三個元素可隨意填, 不影響傳回之時戳), 其元素依序是 time_struct 物件的屬性值, 例如 :

>>> time.localtime()     
time.struct_time(tm_year=2022, tm_mon=8, tm_mday=28, tm_hour=10, tm_min=28, tm_sec=31, tm_wday=6, tm_yday=240, tm_isdst=0)
>>> time.mktime((2022, 8, 28, 10, 26, 31, 6, 240, 0))       
1661653591.0

strftime(fomat [, t]) 函式用來將 localtime() 等函式傳回之時戳 t 轉成 format 字串所指定之格式, 如果沒有傳入時戳預設為目前時間, 可用之格式化字串 foormat 如下表 :


 format 格式化字串 說明
 %a 星期的縮寫, 例如 Sun, Mon, Tue 等
 %A 星期的全寫, 例如 Sunday, Monday, Tuesday 等
 %b 月份的縮寫, 例如 Jan, Feb, Mar 等
 %B 月份的全寫, 例如 January, Feburary, March 等
 %c 本地之日期時間, 例如 'Sun Aug 28 13:17:18 2022'
 %d 一個月中的第幾日 (1~31)
 %H 24 小時制的時 (0~23), 例如 13 (下午一點)
 %I 12 小時制的時 (0~12), 例如 01 (下午一點或凌晨一點)
 %j 一年中的第幾日 (1~366)
 %m 數字月份 (1~12)
 %M 分鐘 (0~59)
 %p 上午 ('AM') 或下午 ('PM')
 %S 秒 (0~59)
 %U 一年中的第幾周 (0~53), 第一個星期日之前為第0 周 (週日為一周之始)
 %w 一周中的第幾天 (0~6), 週日為 0
 %W 一年中的第幾周 (0~53), 第一個星期一之前為第0 周 (週一為一周之始)
 %x 本地之日期, 格式=月/日/年(兩位), 例如 '08/28/22'
 %X 本地之日期, 格式=時:分:秒, 例如 '14:14:48'
 %y 無世紀之年份 (2 位數), 例如 '22' (表示 2022 年)
 %Y 有世紀之年份 (4 位數), 例如 '2022' (表示 2022 年)
 %z 相對於 GMT 的時區偏移值 (24 小時制), 例如台灣是 '+0800' 表示 GMT+8 
 %Z 時區名稱 (已棄用)
 %% 顯示 '%' 字元


例如 :

>>> time.strftime('%a')   
'Sun'
>>> time.strftime('%A')   
'Sunday'
>>> time.strftime('%b')   
'Aug'
>>> time.strftime('%B')   
'August'
>>> time.strftime('%C')   
'20'
>>> time.strftime('%c')   
'Sun Aug 28 13:17:18 2022'
>>> time.strftime('%d')   
'28'
>>> time.strftime('%H')   
'13'
>>> time.strftime('%I')   
'01'
>>> time.strftime('%j')   
'240'
>>> time.strftime('%m')     
'08'
>>> time.strftime('%M')    
'04'
>>> time.strftime('%p')    
'PM'
>>> time.strftime('%U')    
'35'
>>> time.strftime('%x')     
'08/28/22'
>>> time.strftime('%X')       
'14:14:48'
>>> time.strftime('%y')     
'22'
>>> time.strftime('%Y')    
'2022'
>>> time.strftime('%Z')     
'¥x¥_¼Ð·Ç®É¶¡'
>>> time.strftime('%z')     
'+0800'
>>> time.strftime('%%') 
'%'

注意, 格式 '%Z' 傳回的時區是亂碼, 因為那是 byte 資料, 參考下面這篇文章用 encode('latin1') 可顯示 16 進位編碼, 但接著用 decode('utf-8') 或 decode('utf-8') 卻出現錯誤 :


>>> time.strftime('%Z').encode('latin1')   
b'\xa5x\xa5_\xbc\xd0\xb7\xc7\xae\xc9\xb6\xa1'
>>> time.strftime('%Z').encode('latin1').decode('utf8')     
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa5 in position 0: invalid start byte

不過此格式字串已被棄用了. 

參考 : 


沒有留言:

張貼留言