要将发布时间格式化为另一个时区,请保留原始时刻,并将目标时区传给格式化器。本 Mac 终端指南会生成易读的发布时间标签,无需解析显示文本或手动添加小时数。

转换之前需要哪些信息?

假设你正在 Mac 上准备一份文档,并收到合成的发布时间戳 2025-07-15T00:30:00Z。你需要以 America/Los_Angeles 时区显示的发布日期和时间。任务是用目标时区的日历日期和时钟时间来描述同一时刻。

以下四个术语决定了你是否掌握了足够的信息:

术语 合成示例 对本任务的含义
民用日期 2025-07-15 一个日历日,不包含时间或时区
时刻 2025-07-15T00:30:00Z 表示的时刻 你要保留的时间点
UTC 偏移量 -07:00 特定时刻的当地时间与 UTC 之间的差值
命名时区 America/Los_Angeles 用于确定适用偏移量的规则

RFC 3339 定义了包含完整日期、时间和 UTC 偏移量的时间戳。RFC 9557 进一步解释了两者的区别:命名时区提供将某个时刻映射到偏移量的规则,而时间戳中的数字偏移量本身并不能说明某地的偏移量在其他时间如何变化。RFC 3339,第 5.6 节、RFC 9557,第 1.2 节。

转换之前,请根据以下情况判断输入类型:

  • 你已经有一个 Foundation Date: 直接将它传给配置了目标时区的格式化器。
  • 你有带 Z 或明确数字偏移量的时间戳: 解析一次原始时间戳,然后格式化得到的时刻。
  • 你只有一个民用日期: 除非任务明确提供时间和时区,否则请将其作为日期保留。
  • 你有一个不带偏移量的本地日期和时间: 在将它视为时刻之前,先获取源时区并解决任何歧义。

例如,2025-07-15 无法告诉你发布时间是在另一个时区的午夜之前还是之后。指定午夜会引入一个假设。同样,在偏移量转换期间,本地时钟读数可能对应零个或多个时刻;RFC 9557 记录了这一边界情况。转换已确定的时刻可以避免这个反向映射问题。RFC 9557,第 1.2 节。

为什么应避免重新解析显示文本?

Apple 将 Foundation 的 Date 定义为一个独立于任何日历或时区的特定时间点。它的值无需变成另一个 Date,就能以另一个时区显示。格式化时提供日历和时区,以生成文本。Date。

显示标签可能会省略信息。如果将某个时刻格式化为不含时间的日期,然后再解析该标签,就无法从标签中恢复缺失的时间。Apple 的格式化文档演示了一个仅含日期的往返示例,该示例会重建出午夜。如果预期时间是午夜,这很有用,但它无法保留任意的发布时间时刻。Date.FormatStyle。

按以下顺序进行操作:

  1. 读取原始时间戳,或获取已有的 Date。
  2. 将该时刻保留为工作值。
  3. 选择目标时区、日历和显示区域设置。
  4. 将保留的时刻格式化为最终标签。

手动添加偏移量也会改变任务。Apple 将 addingTimeInterval(_:) 记录为通过添加经过的时间来创建一个新日期。因此,添加七小时会产生另一个时刻;选择格式化器的时区则会改变原始时刻的表示方式。Date。

在 Mac 上运行完整的发布时间示例

此示例需要 macOS 12 或更高版本(该版本提供 Apple 的 Date.FormatStyle API)以及可用的 Swift 工具链。它使用 Foundation,无需其他软件包。Date.FormatStyle。

打开终端并检查工具链:

swift --version

如果需要 Apple 的命令行工具,请运行以下命令并完成安装对话框。Apple 记录了这种安装方式,并指出完整安装 Xcode 已包含命令行工具。然后再次检查版本。安装命令行工具。

xcode-select --install

在文本编辑中创建新文稿,并选择 格式 > 制作为纯文本。粘贴下面的完整 Swift 程序,并将其保存到桌面,文件名为 publication-time.swift。确保最终文件名以 .swift 结尾,不要额外添加 .txt 扩展名。Apple 的文本编辑指南介绍了如何创建、转换和保存纯文本文稿。在 Mac 上的文本编辑中创建、打开和转换文稿。

此程序接受给定的整秒时间戳,解析一次,并根据同一个 instant 创建三种表示形式。Apple 记录了用于解析和生成 ISO 8601 表示形式的 ISO8601DateFormatter,以及用于 RFC 3339 格式的 withInternetDateTime 选项。ISO8601DateFormatter、withInternetDateTime。

import Foundation

// Synthetic publication data. Change these two values as needed.
let sourceTimestamp = "2025-07-15T00:30:00Z"
let destinationIdentifier = "America/Los_Angeles"

enum ConversionError: Error {
    case invalidTimestamp(String)
    case unknownTimeZone(String)
}

let inputFormatter = ISO8601DateFormatter()
inputFormatter.formatOptions = [.withInternetDateTime]
inputFormatter.timeZone = TimeZone(secondsFromGMT: 0)

guard let instant = inputFormatter.date(from: sourceTimestamp) else {
    throw ConversionError.invalidTimestamp(sourceTimestamp)
}

guard let destination = TimeZone(identifier: destinationIdentifier) else {
    throw ConversionError.unknownTimeZone(destinationIdentifier)
}

let destinationFormatter = ISO8601DateFormatter()
destinationFormatter.formatOptions = [.withInternetDateTime]
destinationFormatter.timeZone = destination

let publicationLabelStyle = Date.FormatStyle(
    date: .long,
    time: .complete,
    locale: Locale(identifier: "en_GB"),
    calendar: Calendar(identifier: .gregorian),
    timeZone: destination
)

let publicationDateStyle = Date.FormatStyle(
    date: .long,
    time: .omitted,
    locale: Locale(identifier: "en_GB"),
    calendar: Calendar(identifier: .gregorian),
    timeZone: destination
)

print("Source instant: \(inputFormatter.string(from: instant))")
print("Destination zone: \(destination.identifier)")
print("Destination timestamp: \(destinationFormatter.string(from: instant))")
print("Publication label: \(instant.formatted(publicationLabelStyle))")
print("Publication date: \(instant.formatted(publicationDateStyle))")

运行保存的程序:

swift "$HOME/Desktop/publication-time.swift"

对于给定的值,目标时区时间戳应为 2025-07-14T17:30:00-07:00,发布日期应显示为 2025 年 7 月 14 日。易读标签还会包含时间和时区信息;具体措辞和标点由格式化系统决定。

我们对照检索到的 API 文档检查了 Swift 程序,但由于研究环境中没有 Swift,因此未运行该程序。我们另行使用 Python 的日期、时间和时区 API 核对了数值转换示例。

哪些设置会影响结果?

格式化器初始化器接受彼此独立的区域设置、日历和时区参数。此处,en_GB 指定英式英语格式惯例,公历提供日期系统,而 destination 决定当地日期和时钟时间。需要另一个目标时区时,请更改时区标识符;需要另一种显示惯例时,请更改区域设置。init(date:time:locale:calendar:timeZone:capitalizationContext:)。

仅含日期的结果仍直接来自 instant。设置 time: .omitted 会从输出标签中移除时间,无需在午夜构造一个新时刻。

检查跨午夜的情况和季节性偏移量

使用以下合成示例检查结果是否保留了预期操作。这些是示意性的发布时间戳及计算得出的目标时区表示形式。

原始时刻 目标时区 目标时区表示形式
2025-01-15T00:30:00Z America/Los_Angeles 2025-01-14T16:30:00-08:00
2025-07-15T00:30:00Z America/Los_Angeles 2025-07-14T17:30:00-07:00
2025-07-15T00:30:00Z Asia/Tokyo 2025-07-15T09:30:00+09:00
2025-07-15T23:30:00Z Asia/Tokyo 2025-07-16T08:30:00+09:00

前两行说明,对于命名目标时区,固定时差并不足够:适用的偏移量可能取决于时刻。后两行说明,你必须连同时间一起推导目标时区的完整日历日期。发布时间在目标时区可能显示为前一天、当天或后一天。

命名时区使用时区数据库中的规则。IANA 说明 macOS 使用其数据库,并且更新通常会通过操作系统更新发送到客户端。如果不同机器上的结果不同,请先检查它们的时区标识符和可用更新,再修改源时间戳。时区和夏令时数据。

输入或输出不符合预期时该怎么办?

按以下顺序检查:

  1. 确认来源标识的是一个时刻。 保留其 Z 或数字偏移量。仅含日期的值或本地时钟读数需要补充信息。
  2. 确认目标时区标识符。 使用 America/Los_Angeles 这样的标识符。Apple 的初始化器会对未知标识符返回 nil;此程序会停止并报错,而不是替换为另一个时区。
  3. 检查时间戳是否符合支持的格式。 此示例针对整秒的互联网时间戳进行配置。小数秒或其他输入表示形式需要相应配置解析器。更改时请保留原始输入。
  4. 比较目标时区的数字时间戳。 在检查显示标点之前,先用日期、时间和偏移量评估转换结果。
  5. 确认所有输出都使用原始的 instant。 删除任何先格式化标签、再解析该标签以进行下一次转换的中间步骤。init(identifier:)、ISO8601DateFormatter。

根据 RFC 9557,扩展时间戳也可以同时包含命名时区和偏移量。但该标准的存在,并不代表此示例使用的解析器支持带方括号的时区注释。除非你有意实现并验证扩展格式的输入路径,否则请将目标标识符保留在上文单独使用的 destinationIdentifier 值中。RFC 9557。

对于发布时间标签,请将原始时刻保留为可重复使用的值,并根据它生成各个目标时区的文本。这样,你可以更改目标时区、显示语言或可见细节,同时让每个标签都对应同一个发布时间时刻。

来源