本文 AI 產出,尚未審核

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

技巧:如果只需要日期或時間,盡量使用 datetime,可以避免不必要的資訊干擾。


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 偏移,直接加減小時會出錯。 使用 pytzzoneinfo(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)

最佳實踐總結

  1. 永遠在儲存層使用 UTC aware datetime
  2. 在顯示層才轉換為本地時區,避免「同一筆資料在不同機器上呈現不同」的問題。
  3. 使用 zoneinfo(Python 3.9+)或 pytz 處理時區,切勿自行手動加減時差。
  4. 對外提供 API 時,建議採用 ISO 8601 格式datetime.isoformat()),兼容性最佳。
  5. 單元測試中加入時區與 DST 的情境,確保程式在跨時區環境也能正確運作。

實際應用場景

  1. 日誌系統:將所有日誌條目以 UTC aware datetime 寫入,之後透過前端或分析工具轉換為使用者所在時區。
  2. 金融交易:交易時間必須精確到毫秒,使用 datetime 的 microsecond 屬性並儲存為 UTC,可避免因時區差異產生的資金爭議。
  3. 排程服務(Cron 替代):結合 scheduleAPScheduler 等套件,以 datetime 計算下一次執行時間,支援複雜的「每月最後一個工作日」等規則。
  4. 跨國會議系統:利用 timezone_offset 計算不同參與者的時差,自動產生各自的會議邀請時間。
  5. 報表產生:使用 month_days 產生月份的日期清單,再以 timedelta 計算每日的 KPI,最後以 strftime 輸出符合在地化需求的報表檔名。

總結

datetime 模組是 Python 處理時間的核心工具,從 基本的日期/時間表示字串解析與格式化、到 時區 aware 計算,它提供了完整且安全的 API。掌握以下重點,即可在日常開發與大型系統中自如運用:

  • 使用 aware datetime(UTC)作為內部標準
  • 透過 pytz / zoneinfo 正確處理時區與 DST
  • 善用 timedelta 進行加減運算,避免手動換算
  • 在 UI 層才進行本地化格式化,確保資料的一致性。

只要遵循上述最佳實踐,無論是 日誌、排程、金融、跨國協作,或是任何需要時間資訊的應用,都能以簡潔、正確且易維護的方式完成。祝你在 Python 的時間世界裡玩得開心,寫出更可靠的程式!