若要在 Mac 上製作月份與日期的出版標籤,請使用 Swift 的 Foundation 格式化器要求這兩個欄位,並讓地區設定決定排列順序。首先判斷輸入的是日曆日期還是時間戳記:這會決定時區是否應改變顯示的日期。

本指南將示範一個小型 Terminal 腳本,使用合成日期產生出版標籤。主要操作使用 Date.FormatStyle,也會介紹既有 DateFormatter 程式碼可採用的範本替代方案。Apple 說明了這兩種本地化日期輸出方法。Date.FormatStyle、DateFormatter

要求月份與日期欄位,而非完整格式樣式

像 MMMM d 這樣的格式樣式會固定月份在日期之前。本地化欄位要求只指定要包含哪些內容,排列方式則交由格式化器決定。在 Swift 中,相關表示式是 Date.FormatStyle().month(.wide).day()。Apple 明確指出,格式修飾詞的順序不會影響產生的字串。因此,先呼叫 .day() 並不會強制輸出日期在前的格式。Date.FormatStyle

以合成出版日期 2026 年 11 月 12 日為例,使用 en_US 時的示意標籤為 November 12,使用 en_GB 時則為 12 November。這些是明確指定預覽地區設定的結果;實際運作的腳本也會使用 Mac 目前的地區設定列印標籤。Apple 以本地化範本 API 示範了相同的月份與日期排列差異。DateFormatter

Unicode 透過日期骨架說明這項基本差異:骨架包含要求的欄位及欄位長度,軟體會將它們比對至適當的本地化格式樣式。例如,MMMd 要求縮寫月份和日期,但不會指定斜線、空格或最後的顯示順序。這是適用於不同 API 的實用原則:選取元件,再讓格式化器組合標籤。Unicode Technical Standard #35, Part 4: Dates

判斷出版日期代表什麼

格式化前,請從下表選擇適用的列。這項判斷可避免格式化器雖然正確,卻顯示了非預期的日曆日期。

輸入 意義 格式化決策
年 2026、月 11、日 12 民用日期:指定日曆中的某一天 保留這些欄位;透過 Date 轉換時,使用一致的日曆和時區
2026-11-12T00:30:00Z 表示某個瞬間的時間戳記 解析該瞬間,再選擇顯示時所用的時區
+02:00 相對於 UTC 的數值偏移量 足以將本地時間戳記與 UTC 建立關聯;但無法識別具名時區
Europe/London 具名時區識別碼 使用該時區對應於要顯示瞬間的規則

Apple 將 Date 定義為不依賴任何日曆或時區的時間點。DateComponents 則包含年、月、日等欄位,並透過日曆和時區解讀。因此,只有日期的出版紀錄在傳入格式化 Date 的 API 時,需要明確的轉換慣例。Date、DateComponents

在本例中,慣例很簡單:保留來源的年、月、日,在 UTC 時區建立 Gregorian 日期,再使用相同的日曆與 UTC 時區進行格式化。此處選擇中午僅是範例中的時間錨點。建立和格式化時保持一致,才能保留日期。這個錨點並不表示實際的出版時間。

若是實際的時間戳記,請改選預期的顯示時區。地區設定負責控制呈現方式,不會提供時間戳記預期使用的時區。具名時區在不同瞬間可能有不同偏移量,而固定數值偏移量不包含這類規則。Apple 提供時區識別碼和依日期取得偏移量的操作。RFC 3339 定義時間戳記偏移量,RFC 9557 則擴充此表示法,使其能攜帶包括時區在內的額外資訊。TimeZone、RFC 3339、RFC 9557

在 Terminal 執行完整的月份與日期格式化器

請使用 macOS 12 或更新版本,並備妥 Swift 工具鏈和 macOS SDK。Apple 說明此處使用的 Date.FormatStyle 初始化器支援 macOS 12 及更新版本。Swift 官方安裝頁面提供 Mac 設定說明,包括 Apple 平台開發所需的 Xcode。格式樣式初始化器、安裝 Swift — macOS

使用純文字編輯器將以下完整程式儲存為 MonthDay.swift。程式包含自己的合成範例資料、驗證日曆欄位,並列印目前地區設定及指定地區設定的預覽。它也會列印以範本方式產生的對應結果,方便比較這兩種 API。

import Foundation

enum LabelError: Error {
    case unavailableUTC
    case invalidPublicationDate
}

func main() throws {
    guard let utc = TimeZone(secondsFromGMT: 0) else {
        throw LabelError.unavailableUTC
    }

    var calendar = Calendar(identifier: .gregorian)
    calendar.timeZone = utc

    // Synthetic civil publication date, not an actual timestamp.
    var fields = DateComponents()
    fields.calendar = calendar
    fields.timeZone = utc
    fields.year = 2026
    fields.month = 11
    fields.day = 12
    fields.hour = 12

    guard fields.isValidDate(in: calendar),
          let publicationDate = calendar.date(from: fields) else {
        throw LabelError.invalidPublicationDate
    }

    let previews: [(String, Locale)] = [
        ("Current locale", .current),
        ("en_US", Locale(identifier: "en_US")),
        ("en_GB", Locale(identifier: "en_GB"))
    ]

    for (name, locale) in previews {
        let style = Date.FormatStyle(
            locale: locale,
            calendar: calendar,
            timeZone: utc
        )
        .month(.wide)
        .day()

        print("\(name): \(publicationDate.formatted(style))")

        let formatter = DateFormatter()
        formatter.locale = locale
        formatter.calendar = calendar
        formatter.timeZone = utc
        formatter.setLocalizedDateFormatFromTemplate("MMMMd")

        print("\(name), template: \(formatter.string(from: publicationDate))")
    }
}

try main()

開啟 Terminal,切換至檔案所在的資料夾,然後執行 swift MonthDay.swift。如果無法使用 Swift 工具鏈,請先完成官方 Mac 安裝步驟,再繼續操作。此範例已依據文件檢視,但由於本研究環境無法使用 Swift,因此未執行。

兩個指定地區設定的預覽應會為同一個 11 月 12 日示範月份在前和日期在前的標籤。目前地區設定的那一行則會依照 Mac 目前的地區設定。兩種格式化器路徑都會要求完整月份名稱和日期,不加入年份或時間。請將輸出視為本地化顯示文字;避免讓後續處理依賴特定空格或標點字元。Unicode 的格式樣式比對模型允許各地區設定採用不同排列方式。Unicode Technical Standard #35, Part 4: Dates

程式刻意使用 Gregorian 日曆,因為合成來源欄位是以該日曆定義的。即使地區設定改變呈現方式,日曆仍維持不變。如果來源日期使用不同日曆,請先依該日曆解讀欄位,再決定如何顯示。Apple 的初始化器可分別設定地區設定、日曆和時區,正是為此用途而設。格式樣式初始化器

在既有的 DateFormatter 程式碼中使用本地化範本

程式中的第二種方法使用 setLocalizedDateFormatFromTemplate("MMMMd")。其中,MMMM 要求完整月份名稱,d 則要求月份中的日期。這個字串是傳給本地化方法的範本,不是直接指定為最終的 dateFormat。Apple 自己的範例也使用這個確切範本,產生不同的月份與日期排列順序。DateFormatter

呼叫範本方法之前,先設定 locale。Apple 明確要求依此順序操作,因為該方法會依據當時格式化器的地區設定推導格式。若之後選擇另一個地區設定,請再次呼叫範本方法。範例為每個預覽建立新的格式化器,讓這項依賴關係清楚可見。setLocalizedDateFormatFromTemplate(_:)

針對這項標籤工作,請選擇符合既有程式碼的方式:使用現代 Swift API 的欄位修飾詞,或使用 DateFormatter 工作流程中的本地化範本方法。避免自行用分隔符號組合分別格式化的月份與日期字串,因為這會讓日期格式化器失去決定排列方式的責任。

處理時間戳記時避免改變原本的任務

假設來源提供的是合成時間戳記 2026-11-12T00:30:00Z。它代表一個瞬間。以偏移量 +00:00 顯示,結果是 11 月 12 日;以偏移量 -02:00 顯示同一瞬間,結果則是 11 月 11 日 22:30。這只是示意性的偏移量計算,並非實際觀察到的出版事件。RFC 3339 將數值偏移量定義為本地時間減去 UTC。RFC 3339,第 4.2 節

請使用依實際表示法設定的 ISO 8601 API 解析這類輸入,再以選定的顯示時區格式化所得的瞬間。Apple 說明 ISO8601DateFormatter 及其 .withInternetDateTime 選項可用於 RFC 3339 日期與時間字串。此選項是解析設定,與本地化的月份和日期輸出要求分開。ISO8601DateFormatter formatOptions

除非已刻意定義轉換慣例,否則不要將此時間戳記工作流程用於只提供民用日期的來源。同樣地,也不要假設 Z 後綴能識別出版地點的具名時區。RFC 9557 將具名時區資訊視為基本時間戳記表示法以外的額外資訊。RFC 9557

依正確順序診斷非預期的標籤

依照以下順序找出需要調整的決策:

  1. **日期錯誤:**確認輸入是民用日期還是某個瞬間。對於民用日期程式,建立日期和格式化時都使用 UTC。若輸入是某個瞬間,請確認預期的顯示時區。
  2. **月份和日期的排列順序錯誤:**檢查傳給格式化器的地區設定,並確認是否直接指定了 dateFormat,或是否使用了自行串接的字串。
  3. **改變修飾詞順序沒有效果:**這是文件所述的行為。請選擇適當的地區設定,而非調換 .month() 和 .day()。
  4. **範本輸出仍保留先前的排列順序:**設定新的地區設定,再用 setLocalizedDateFormatFromTemplate 重新產生格式。
  5. **來源欄位無效:**回報或修正輸入。完整程式會在轉換前檢查有效性,不會以其他出版日期取代。

這些檢查遵循文件所述的區分:瞬間、其日曆解讀方式,以及本地化呈現。Apple 在對應 API 文件中說明了修飾詞順序的獨立性、範本設定順序,以及日期元件驗證。Date.FormatStyle、setLocalizedDateFormatFromTemplate(_:)、DateComponents

產生標籤後,請保留完整的來源日期或時間戳記。月份與日期字串刻意省略年份,格式化的時間戳記標籤也會省略時間和時區;顯示文字無法保留完整輸入。若要產生可靠的出版標籤,請保留原始值,審慎選擇日曆和時區,並讓地區設定決定所要求月份與日期的排列方式。

來源