Python 日期與時間(Datetime)
課程單元:datetime 模組
簡介
在所有程式語言中,日期與時間的處理往往是最容易出錯的部份。無論是記錄使用者的登入時間、計算兩筆交易的間隔、或是產生排程任務,都離不開精確且可操作的時間資訊。Python 內建的 datetime 模組提供了完整且直觀的介面,讓開發者可以安全地進行時間的表示、運算與格式化。
本篇文章將從 基礎概念、常見操作、陷阱與最佳實踐,一步步帶你掌握 datetime,並透過實務範例說明它在日常開發中的應用。適合剛入門的初學者,也能為已有一定經驗的開發者提供中級層次的技巧與思考方向。
核心概念
1. datetime、date、time、timedelta 四大類別
| 類別 | 代表意義 | 常見屬性/方法 |
|---|---|---|
datetime.datetime |
同時包含日期與時間(精確到微秒) | .year、.month、.day、.hour、.minute、.second、.microsecond |
datetime.date |
只有日期(年、月、日) | .year、.month、.day |
datetime.time |
只有時間(時、分、秒、微秒) | .hour、.minute、.second、.microsecond |
datetime.timedelta |
時間差或時間長度 | .days、.seconds、.microseconds |
技巧:如果只需要日期或時間,盡量使用
date或time,可以避免不必要的資訊干擾。
2. 建立日期與時間物件
from datetime import datetime, date, time, timedelta
# 1. 取得現在的日期與時間
now = datetime.now() # 本地時區
utc_now = datetime.utcnow() # UTC 時間
# 2. 指定特定的日期或時間
d = date(2023, 12, 31) # 2023-12-31
t = time(14, 30, 15, 123456) # 14:30:15.123456
# 3. 直接建立 datetime
dt = datetime(2023, 12, 31, 23, 59, 59) # 2023-12-31 23:59:59
datetime.now() 會根據執行環境的時區返回本機時間,而 datetime.utcnow() 則固定為協調世界時間(UTC),在跨時區系統中尤為重要。
3. 解析與格式化(Parsing & Formatting)
# 1. 文字轉 datetime(parse)
s = "2023-07-15 08:30:00"
dt_from_str = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
# 2. datetime 轉文字(format)
formatted = dt_from_str.strftime("%A, %d %B %Y %H:%M") # 'Saturday, 15 July 2023 08:30'
%Y:四位數年份%m:兩位數月份(01‑12)%d:兩位數日期(01‑31)%H:24 小時制小時(00‑23)%M:分鐘(00‑59)%S:秒(00‑59)%A、%B:星期名稱、月份名稱(依語系變化)
小技巧:如果要處理多種日期格式,建議使用
dateutil.parser.parse(第三方套件),可自動偵測常見樣式。
4. 時間運算與差值
# 1. 加減 timedelta
one_day = timedelta(days=1)
tomorrow = now + one_day
yesterday = now - timedelta(days=1, hours=5)
# 2. 計算兩個 datetime 之間的差距
delta = datetime(2023, 12, 31) - datetime(2023, 1, 1)
print(delta.days) # 364
print(delta.total_seconds()) # 31449600.0
timedelta 支援 天、秒、微秒 三種單位;如果需要更細的單位(如毫秒、分鐘),只要把相對應的值換算成秒或微秒即可。
5. 時區(Timezone)與 aware/naive 物件
import pytz # 需要先 pip install pytz
# 1. 取得 UTC 時區物件
utc = pytz.UTC
# 2. 讓 datetime 變成 aware(具備時區資訊)
aware_dt = utc.localize(datetime(2023, 7, 15, 12, 0, 0))
# 3. 轉換時區
tw_tz = pytz.timezone("Asia/Taipei")
tw_dt = aware_dt.astimezone(tw_tz) # 2023-07-15 20:00:00+08:00
- Naive datetime:沒有時區資訊(預設)。
- Aware datetime:包含
tzinfo,能正確處理跨時區計算。
最佳實踐:在任何需要持久化或跨系統傳遞時間的情況下,統一使用 UTC aware datetime,只在 UI 層面轉換為本地時區。
程式碼範例(實用篇)
範例 1:計算使用者上次登入距離現在的天數
from datetime import datetime, timedelta
def days_since_last_login(last_login_str: str) -> int:
"""
參數:
last_login_str: 形如 '2023-08-01 14:30:00' 的字串
回傳:
與現在相差的天數(整數)
"""
last_login = datetime.strptime(last_login_str, "%Y-%m-%d %H:%M:%S")
now = datetime.now()
delta = now - last_login
return delta.days
# 使用範例
print(days_since_last_login("2023-08-01 14:30:00")) # 依執行時間不同而異
這個範例展示 字串解析 → datetime 物件 → 差值計算 的完整流程,是許多網站或 App 常見的功能。
範例 2:排程每日凌晨 02:00 執行任務(使用 schedule)
import schedule
import time
from datetime import datetime, timedelta
def job():
print(f"Task run at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# 每天的 02:00 執行
schedule.every().day.at("02:00").do(job)
while True:
schedule.run_pending()
time.sleep(30) # 每 30 秒檢查一次
說明:schedule 套件會在本機時區的 02:00 觸發 job,在 job 裡我們使用 datetime.now() 記錄執行時間,方便日誌追蹤。
範例 3:將 UTC 時間轉為台北時間並顯示中文月份
import pytz
from datetime import datetime
def utc_to_taipei(utc_dt: datetime) -> str:
"""
參數:
utc_dt: 已是 aware 的 UTC datetime
回傳:
台北時間字串,格式為 'YYYY年MM月DD日 HH時mm分ss秒'
"""
tz_taipei = pytz.timezone("Asia/Taipei")
local_dt = utc_dt.astimezone(tz_taipei)
return local_dt.strftime("%Y年%m月%d日 %H時%M分%S秒")
# 範例
utc_now = datetime.utcnow().replace(tzinfo=pytz.UTC)
print(utc_to_taipei(utc_now))
此範例示範 aware datetime 與 自訂格式化,適合產生符合在地化需求的時間字串(例如報表或通知訊息)。
範例 4:產生一個月份的所有日期清單
from datetime import date, timedelta
def month_days(year: int, month: int):
"""回傳指定月份的所有 date 物件(含起始與結束)"""
first_day = date(year, month, 1)
# 取得下個月的第一天,再減一天即為本月最後一天
if month == 12:
next_month = date(year + 1, 1, 1)
else:
next_month = date(year, month + 1, 1)
last_day = next_month - timedelta(days=1)
days = []
current = first_day
while current <= last_day:
days.append(current)
current += timedelta(days=1)
return days
# 使用範例:列出 2023 年 2 月的所有日期
for d in month_days(2023, 2):
print(d)
此程式碼在 報表產生、行事曆建構 時非常有用,避免手動計算每個月的天數(尤其是閏年)。
範例 5:計算兩個時區間的時差(以小時為單位)
import pytz
from datetime import datetime
def timezone_offset(tz_name1: str, tz_name2: str) -> float:
"""
回傳 tz_name1 與 tz_name2 之間的時差(小時),正值表示 tz_name1 > tz_name2
"""
tz1 = pytz.timezone(tz_name1)
tz2 = pytz.timezone(tz_name2)
now = datetime.utcnow()
offset1 = tz1.utcoffset(now).total_seconds() / 3600
offset2 = tz2.utcoffset(now).total_seconds() / 3600
return offset1 - offset2
print(timezone_offset("Asia/Taipei", "America/New_York")) # 12.0(或因夏令時間而變)
此函式可協助 跨國協作平台 自動調整會議時間或排程。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| Naive vs Aware | 混用沒有時區資訊的 datetime 會導致錯誤的加減運算。 | 統一使用 aware datetime,在資料庫或 API 中儲存 UTC。 |
| 夏令時間(DST) | 某些時區會因夏令時間前後改變 UTC 偏移,直接加減小時會出錯。 | 使用 pytz 或 zoneinfo(Python 3.9+)的 localize / astimezone,讓系統自行處理 DST。 |
| 字串格式不一致 | strptime 必須完全匹配格式,稍有差異就會拋例外。 |
使用 dateutil.parser.parse,或在接受外部輸入前先正規化字串。 |
| 微秒精度遺失 | 輸入/輸出時未保留微秒,會造成時間比較失敗。 | 在格式化時加入 %f,或在儲存時使用 datetime.isoformat()。 |
| 時間戳記(timestamp)與 datetime 混用 | timestamp() 回傳的是 UTC 秒數,直接與本地 naive datetime 比較會錯。 |
先把 timestamp 轉為 aware datetime:datetime.fromtimestamp(ts, tz=pytz.UTC)。 |
最佳實踐總結
- 永遠在儲存層使用 UTC aware datetime。
- 在顯示層才轉換為本地時區,避免「同一筆資料在不同機器上呈現不同」的問題。
- 使用
zoneinfo(Python 3.9+)或pytz處理時區,切勿自行手動加減時差。 - 對外提供 API 時,建議採用 ISO 8601 格式(
datetime.isoformat()),兼容性最佳。 - 單元測試中加入時區與 DST 的情境,確保程式在跨時區環境也能正確運作。
實際應用場景
- 日誌系統:將所有日誌條目以 UTC aware datetime 寫入,之後透過前端或分析工具轉換為使用者所在時區。
- 金融交易:交易時間必須精確到毫秒,使用
datetime的 microsecond 屬性並儲存為 UTC,可避免因時區差異產生的資金爭議。 - 排程服務(Cron 替代):結合
schedule、APScheduler等套件,以datetime計算下一次執行時間,支援複雜的「每月最後一個工作日」等規則。 - 跨國會議系統:利用
timezone_offset計算不同參與者的時差,自動產生各自的會議邀請時間。 - 報表產生:使用
month_days產生月份的日期清單,再以timedelta計算每日的 KPI,最後以strftime輸出符合在地化需求的報表檔名。
總結
datetime 模組是 Python 處理時間的核心工具,從 基本的日期/時間表示、字串解析與格式化、到 時區 aware 計算,它提供了完整且安全的 API。掌握以下重點,即可在日常開發與大型系統中自如運用:
- 使用 aware datetime(UTC)作為內部標準。
- 透過
pytz/zoneinfo正確處理時區與 DST。 - 善用
timedelta進行加減運算,避免手動換算。 - 在 UI 層才進行本地化格式化,確保資料的一致性。
只要遵循上述最佳實踐,無論是 日誌、排程、金融、跨國協作,或是任何需要時間資訊的應用,都能以簡潔、正確且易維護的方式完成。祝你在 Python 的時間世界裡玩得開心,寫出更可靠的程式!