將僅含日期的出版值解析為日曆日期,並以該日期為準,即可保留此值。若匯出格式要求 UTC 時間戳記,請明確採用 UTC 午夜慣例。這份 Mac 終端機操作指南會說明兩者的差異,並測試往返轉換。
判斷出版欄位實際代表什麼
假設出版清單中有合成值 2028-06-15。你的任務是驗證此值並為匯出做好準備,同時避免意外顯示為 6 月 14 日。在選擇解析器之前,先判斷此欄位記錄的是出版日期,還是實際出版時刻。
以下四個概念各有不同用途:
| 概念 | 合成範例 | 意義 |
|---|---|---|
| 民用日期 | 2028-06-15 |
日曆上的某一天,不含時間或時差 |
| 時刻 | 2028-06-15T00:00:00+00:00 |
以 UTC 表示的特定時刻 |
| UTC 時差 | +09:00 |
當地時鐘讀數與 UTC 之間的差異 |
| 命名時區 | America/Los_Angeles |
用來取得適用時區規則的識別碼 |
RFC 3339 將 full-date 與 date-time 分別定義;其時間戳記語法會在日期上加上時間和時差。因此,單獨的出版日期缺少識別出版時刻所需的資訊。選擇午夜會加入原始輸入中不存在的資訊。RFC 3339,第 5.6 節
命名時區所含資訊也多於單一數值時差。Python 的 zoneinfo 說明文件示範了時區如何在日光節約時間轉換期間使用不同的時差。對僅含日期的標籤而言,無須解析這些規則。Python:IANA 時區支援
匯出前請依照以下規則判斷:
- **欄位表示「在這一天出版」:**儲存和顯示時都保留僅含日期的值。
- **接收格式要求時間戳記:**保留日期,並依據有文件記載的慣例,衍生出 UTC 午夜載體值。
- **欄位表示「在這一刻出版」:**取得實際時間和時差,或取得能確定該時刻所需的時間和命名時區。
載體慣例有助於相容性,但無法還原未知的出版時間。像 utc_carrier 這樣的欄位名稱能清楚表達此限制;若稱為 published_at,則會暗示來源從未提供的資訊。
了解前一日錯誤從何而來
以 2028-06-15 為例,考慮兩種可能的處理路徑。第一種路徑中,程式在固定時差 +09:00 下自行假定午夜,然後將該時刻轉換為 UTC。結果是 2028-06-14T15:00:00+00:00:減去九小時後,日期跨過了界線。
第二種路徑中,程式刻意建立 UTC 午夜:2028-06-15T00:00:00+00:00。以固定時差 -07:00 顯示同一時刻,會得到 2028-06-14T17:00:00-07:00。這些是示範性的時差計算,並非宣稱某個命名時區在該日期使用該時差。
兩種計算都保留了各自代表的時刻,但無論在哪個顯示時區,都無法保留原始日曆標籤。Python 說明,astimezone() 會調整時鐘欄位,同時保留所表示的 UTC 時間。Python:datetime.astimezone()
實務上,解析和顯示需要遵循同一份契約。如果日期已放入 UTC 午夜載體值,請在 UTC 中還原其日曆欄位。若希望無論何人開啟匯出檔,出版標籤都保持不變,請顯示保留的民用日期。
不要假設每種語言都會補入相同的缺失資訊。ECMAScript 規定,不含時差的僅日期格式會解讀為 UTC,而不含時差的日期時間格式會解讀為當地時間。因此,在字串後面附加 T00:00:00 可能會改變 JavaScript 對它的解讀。ECMAScript:Date.parse
在 Mac 終端機中執行完整的出版日期檢查
這份操作指南會驗證三個合成日期、列印以定位字元分隔的預覽,並測試是否拒絕格式錯誤的輸入。它不會修改出版清單,也不會建立匯出檔案。
在 Finder 中開啟 /Applications/Utilities,然後連按兩下 Terminal。Apple 說明,這是開啟命令列 App 的支援方式。Apple:在 Mac 上開啟或結束終端機
執行以下命令以檢查直譯器:
python3 --version
這份操作指南需要 Python 3.11 或更新版本。如果無法使用 python3 或顯示的版本較舊,請先依照官方 macOS 安裝說明完成安裝,接著重新開啟 Terminal 再次檢查。Python 說明了 macOS 安裝程式的使用方式,以及如何透過 python3 執行指令碼。Python:在 macOS 上使用 Python
貼上以下完整區塊,包括最後的 PY,然後按下 Return。- 引數會告訴 Python 從標準輸入讀取程式。所有範例資料和匯入項目都已包含在內。Python:命令列與環境
python3 - <<'PY'
import re
from datetime import date, datetime, time, timezone
def parse_publication_date(text):
if not isinstance(text, str) or re.fullmatch(
r"[0-9]{4}-[0-9]{2}-[0-9]{2}", text
) is None:
raise ValueError("Expected YYYY-MM-DD")
return date.fromisoformat(text)
def utc_carrier(day):
return datetime.combine(day, time(0), tzinfo=timezone.utc)
samples = ["2028-02-29", "2028-06-15", "2028-12-31"]
parsed = [(text, parse_publication_date(text)) for text in samples]
print("publication_date\tutc_carrier")
for original, day in parsed:
carrier = utc_carrier(day)
assert day.isoformat() == original
assert carrier.astimezone(timezone.utc).date() == day
print(day.isoformat(), carrier.isoformat(timespec="seconds"), sep="\t")
for invalid in [
"2027-02-29",
"2028-6-15",
"2028-06-15T00:00:00",
" 2028-06-15",
]:
try:
parse_publication_date(invalid)
except ValueError:
pass
else:
raise AssertionError(f"Unexpected acceptance: {invalid!r}")
print("Round-trip and rejection checks passed.")
PY
預期輸出如下:
publication_date utc_carrier
2028-02-29 2028-02-29T00:00:00+00:00
2028-06-15 2028-06-15T00:00:00+00:00
2028-12-31 2028-12-31T00:00:00+00:00
往返轉換與拒絕檢查均已通過。
此範例和兩種固定時差計算已在可用的 Python 直譯器中成功執行。Mac 專屬的開啟方式和直譯器操作說明則依據連結的官方文件。
為什麼此範例能保留出版日期
解析器會分別檢查兩項內容:字串格式是否完全符合,以及日期是否為有效日曆日期。其正規表示式要求四位年份、兩位月份和兩位日期。接著,日期解析器會拒絕不存在的日期。這可避免出版欄位悄悄接受時間戳記、前後空格或縮寫輸入。
明確檢查格式十分重要,因為 date.fromisoformat() 也接受其他 ISO 表示形式,包括緊湊日期和週日期。接著,datetime.combine() 會刻意提供午夜和 UTC。Python:基本日期與時間型別
程式會先解析整份範例清單,再列印標頭。如果你將某個範例替換為無效值,程式會在顯示任何預覽列之前停止執行。最後明確列出的無效輸入檢查屬於獨立測試;預期會發生的例外都會被捕捉,因此可以檢查所有拒絕情況。
兩個往返轉換斷言表達了此契約。第一個會確認解析並格式化日期後,能重現原始標籤。第二個會確認衍生的載體值在 UTC 中解讀時會得到相同日期。兩者共同檢查的是日期是否確實保留,而非只確認能否產生某種時間戳記。
輸出會明確使用 +00:00。RFC 3339 也允許使用 Z 作為 UTC 時差標記,但即使在單獨的日期後加上其中任何一種尾碼,仍無法補上缺少的時間部分。請保留僅含日期的格式,或刻意建構完整時間戳記。RFC 3339,第 5.6 節
診斷日期仍然變動的匯出結果
如果下游預覽顯示前一天,請檢查每個處理邊界上的值。使用合成範例中的中間日期 2028-06-15,即可將每個階段與一個已知標籤進行比較。
| 檢查點 | 採用載體慣例時的預期值 | 出現差異時的可能原因 |
|---|---|---|
| 原始出版欄位 | 2028-06-15 |
輸入在解析前已變更 |
| 解析後以文字顯示的日期 | 2028-06-15 |
解析或正規化改變了標籤 |
| 衍生的 UTC 載體值 | 2028-06-15T00:00:00+00:00 |
引入了不同時間或時差 |
| 以 UTC 還原的日期 | 2028-06-15 |
轉換或擷取採用了錯誤的契約 |
| 顯示給讀者的出版標籤 | 2028-06-15 |
檢視器可能正以另一個時區顯示載體值 |
請獨立檢查顯示器和解析器。即使 UTC 載體值正確,使用當地時區設定顯示時,仍可能出現不同的日曆日期。若要顯示出版標籤,請將保留的僅含日期值傳給顯示器。如果它只接受時間戳記,請使用有文件記載的 UTC 顯示選項,並確認結果。
若接收工具只支援時刻,且一律以當地時間顯示,就無法透過 UTC 午夜慣例保證日期標籤不變。請另外保留僅含日期的文字欄位、使用受支援的僅日期欄位,或選擇能明確指定 UTC 格式的顯示器。這項限制源自上述時差計算;單靠更改解析器無法消除問題。
也不要把既有時間戳記當成僅含日期的輸入來處理。對於真正的時刻,改變其時區代表進行轉換。Python 區分了這項操作和替換 tzinfo 的做法;後者是在不調整時鐘欄位的情況下附加時區資訊。Python:時區轉換行為
對你的出版清單而言,請將 publication_date 保留為權威的日曆值。僅在需要時間戳記的介面衍生 UTC 載體值,並將慣例與欄位一併保存;在相信預覽結果之前,先確認能以 UTC 擷取日期。如此便能以可重複的方法保留提供的日期,同時不虛構出版時刻。
資料來源
- 在 Mac 上開啟或結束終端機 — Apple。
- 在 macOS 上使用 Python — Python Software Foundation。
- 命令列與環境 — Python Software Foundation。
- datetime — 基本日期與時間型別 — Python Software Foundation。
- zoneinfo — IANA 時區支援 — Python Software Foundation。
- RFC 3339:網際網路上的日期與時間:時間戳記 — RFC Editor。
- ECMAScript 語言規範:數字與日期 — Ecma International / TC39。