移动开发中的日期和时间:概念、格式及处理方法

作者: IT Sectr 发布日期: 2026-07-22 阅读时间: 11 分钟

移动开发中的日期和时间 — 是移动工程中的关键主题之一。iOS 使用 DateFormatter 和 ISO8601DateFormatter,Android 使用 DateTimeFormatter 和 java.time 中的 LocalDate。根据 Android Developer Docs,正确选择用于格式化日期和时间的移动 API 直接影响不同时区中的性能和显示准确性。

要点

  • DateFormatter 在 iOS 上 — 功能强大,但频繁创建时速度慢。缓存实例或使用 ISO8601DateFormatter 处理 ISO 8601 格式。
  • DateTimeFormatter 在 Android 上 — 来自 java.time 的线程安全格式化器。从 API 26 开始取代了过时的 SimpleDateFormat。
  • ThreeTenABP — java.time 对 Android API 26 以下版本的反向移植。为旧设备添加了 LocalDate、ZonedDateTime 和 DateTimeFormatter。
  • TimeZone — 对于 UTC 到本地时间的转换至关重要。以 UTC 存储时间,在用户的时区中显示。
  • Unix Timestamp — 标准的时间存储格式。通过 NTP 同步确保毫秒级精度。

iOS 上的日期和时间:DateFormatter 和 ISO8601DateFormatter

在 iOS 上,日期和时间传统上通过 Foundation 中的 DateFormatter 进行格式化。该类使用指定的模板、区域设置和时区将 Date 转换为字符串并反向转换。DateFormatter 的主要问题 — 它不是线程安全的,创建速度慢,因此在移动代码中实例必须被缓存。iOS 移动开发中的日期需要正确的格式化器。

DateFormatter 和性能

创建 DateFormatter 大约需要 1 毫秒,因为需要解析 dateFormat 模板、加载区域设置和确定时区。在格式化包含数百个日期的列表时,重复创建会导致明显的 UI 延迟。解决方案 — 为整个屏幕创建一次格式化器并重复使用。对于多线程访问,使用 DispatchQueue 进行同步。

swift
import Foundation

// 缓存的 DateFormatter — 创建一次
private let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "dd.MM.yyyy HH:mm"
    formatter.locale = Locale(identifier: "ru_RU")
    formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
    return formatter
}()

// ISO8601DateFormatter — 轻量级替代方案
private let isoFormatter: ISO8601DateFormatter = {
    let formatter = ISO8601DateFormatter()
    formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    return formatter
}()

let now = Date()
let formattedDate = dateFormatter.string(from: now)
// "01.07.2026 14:30"
let isoDate = isoFormatter.string(from: now)
// "2026-07-01T14:30:00.000Z"

对于 ISO 8601 格式,使用 ISO8601DateFormatter — 它不需要指定模板,且比 DateFormatter 更快。在 iOS 10+ 上,该类是处理服务器日期的首选。对于旧格式,保留 DateFormatter,但务必将实例缓存为静态属性或单例。

Android 上的日期和时间:java.time 和 DateTimeFormatter

在 Android 上,日期和时间通过 java.time 包处理,自 API 26 起可用。主要类:LocalDate(仅日期)、LocalTime(仅时间)、LocalDateTime(无时区的日期和时间)和 ZonedDateTime(含时区)。DateTimeFormatter — 用于 Android 移动平台的线程安全格式化器。移动应用中的日期通过 DateTimeFormatter 和 LocalDate 格式化,不会损失性能。

LocalDate、ZonedDateTime 和格式化

LocalDate 存储不带时间的日期 — 非常适合生日。ZonedDateTime 存储带时区的完整信息。DateTimeFormatter 支持预定义模板(ISO_LOCAL_DATE、ISO_DATE_TIME)和通过 ofPattern 的自定义模板。所有 java.time 类都是不可变且线程安全的,消除了多线程操作中的竞态条件。

kotlin
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.ZoneId

// 创建和格式化当前日期
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatted = today.format(formatter)

// 处理时区
val utcTime = ZonedDateTime.now(ZoneId.of("UTC"))
val moscowTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Moscow"))

DateTimeFormatter 明显优于 SimpleDateFormat,因为它不会在每次调用时解析模板。建议在类或伴生对象级别创建格式化器常量。要转换 Unix Timestamp,请使用 Instant.ofEpochSecond,然后通过时区转换为 LocalDateTime 或 ZonedDateTime。

时区:移动应用中的日期和时间及 TimeZone

用户屏幕上正确的日期和时间取决于时区的正确处理。Android 移动开发中的日期和时间需要考虑设备的时区。黄金法则:以 UTC 存储时间,仅用于显示时转换为本地时区。iOS 提供 Foundation 中的 TimeZone,Android 提供 java.time 中的 ZoneId。两个移动平台 API 都支持自动检测设备的当前时区。移动应用中的日期取决于 ZoneId 和 TimeZone。

夏令时和 IANA 标识符

夏令时会产生歧义:同一时刻可能由两个不同的本地时间表示。java.time 和 Foundation 会自动处理 DST。使用 IANA 标识符(Europe/Moscow、America/New_York),而不是缩写(MSK、EST)— 缩写有歧义且会忽略夏令时转换。

swift
import Foundation

// UTC 到本地时间的转换(iOS)
func formatToLocalTime(utcDate: Date, timeZoneId: String) -> String {
    let formatter = DateFormatter()
    formatter.dateFormat = "dd.MM.yyyy HH:mm"
    formatter.timeZone = TimeZone(identifier: timeZoneId)
    return formatter.string(from: utcDate)
}

// 确定设备时区
let localTimeZone = TimeZone.current
let isDST = localTimeZone.isDaylightSavingTime()
let secondsFromGMT = localTimeZone.secondsFromGMT()

Android 上,使用 ZoneId.systemDefault() 获取设备的时区。在将 UTC 转换为本地时间时,创建带有 ZoneOffset.UTC 的 ZonedDateTime 并应用 withZoneSameInstant。对于没有 java.time 的旧 API,使用 TimeZone.getDefault() 和 Calendar — 但最好直接引入 ThreeTenABP。

kotlin
import java.time.Instant
import java.time.ZonedDateTime
import java.time.ZoneId

// 将 Unix Timestamp 转换为本地时间
fun toLocalDisplay(unixSeconds: Long): String {
    val instant = Instant.ofEpochSecond(unixSeconds)
    val local = instant.atZone(ZoneId.systemDefault())
    return local.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
}

ThreeTenABP — 旧版 Android 的日期和时间

ThreeTenABP — 一个适配器库,将 java.time 移植到低于 26 的 Android API。它提供相同的类:LocalDate、LocalTime、ZonedDateTime、DateTimeFormatter、Instant。如果应用支持 Android 5 或 6(API 21-25),ThreeTenABP 是无需切换到过时的 Calendar 即可使用现代移动 API 的唯一方式。旧设备的移动开发中的日期和时间通过此库实现。对于旧设备,日期和时间通过 ThreeTenABP 实现。

ThreeTenABP 的引入和配置

该库需要在 Application.onCreate() 方法中通过调用 AndroidThreeTen.init() 进行初始化。这会从 assets 加载时区数据。初始化后,API 与 java.time 完全一致 — API 26+ 和 API 21+ 的代码将相同。库体积约为 400 KB,对于大多数移动应用来说是可以接受的。

kotlin
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter

// 在 Application 中初始化
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        AndroidThreeTen.init(this)
    }
}

// API 与 java.time 相同
val today = LocalDate.now()
val formatted = today.format(DateTimeFormatter.ISO_LOCAL_DATE)

ThreeTenABP 的性能在现代设备上与原始 java.time 相当。在旧设备上,由于时区数据的加载,首次调用可能会有延迟。逐步将现有代码从 Calendar 迁移到 ThreeTenABP:在新模块中用 Instant 和 LocalDate 替换 Date 和 Calendar,保留旧代码不变。

Unix Timestamp 和通过 NTP 的时间同步

Unix Timestamp — 自 1970 年 1 月 1 日 00:00 UTC 以来经过的秒数。这是服务器和客户端之间存储和交换时间的通用格式。然而,设备上的时间可能由于用户设置而与实际时间不同。同步使用 NTP — 通过 UDP 的精确时间协议。Unix Timestamp 是服务器上存储日期和时间的格式。

获取 Unix Timestamp 和 NTP 同步

Date().timeIntervalSince1970 在 iOS 上返回自 1970 年以来的秒数。在 Android 上,System.currentTimeMillis() 返回毫秒数。要获得精度,请使用 NTP:适用于 iOS 的 TrueTime 库和适用于 Android 的 AndroidNtp 库向 NTP 服务器发送请求并计算时间偏移。NTP 精度在局域网中为 1-10 毫秒,通过互联网为 10-100 毫秒。

swift
import Foundation

// 在 iOS 上获取 Unix Timestamp
let seconds = Date().timeIntervalSince1970
let milliseconds = Int64(seconds * 1000)

// 通过 TrueTime 进行 NTP 同步
TrueTime.shared.start { result in
    switch result {
    case .success:
        let ntpTime = TrueTime.shared.now()
        print("精确时间:\(ntpTime)")
    case .failure(let error):
        print("NTP 错误:\(error)")
    }
}

没有同步,移动应用中可能会出现错误:事件标记不正确,与服务器不同步。移动开发中的时间在应用启动时通过 NTP 一次性同步,然后使用计算出的偏移量。对于对精度要求高的移动应用(金融、物流),在向服务器发送数据之前,NTP 协议是必需的。

常见问题

为什么 DateFormatter 在 iOS 上很慢?

DateFormatter 在每次创建实例时都会解析格式化模板、加载区域设置并确定时区。将格式化器缓存为静态常量,或使用 ISO8601DateFormatter 处理 ISO 8601 — 它更轻量且更快。

在 Android 上用什么替代 SimpleDateFormat?

来自 java.time 包的 DateTimeFormatter — 线程安全且高性能。自 API 26 起可用。对于旧版本,使用提供相同 API 的 ThreeTenABP。

如何在 Android 上处理 Java 8 之前的日期?

引入 ThreeTenABP — java.time 对 Android API 21+ 的反向移植。通过在 Application.onCreate() 中调用 AndroidThreeTen.init() 进行初始化。API 与原始 java.time 完全一致。

是否需要以 UTC 存储时间?

是的,在服务器和本地数据库中以 UTC 存储时间。仅在显示阶段转换为用户的本地时区。这样可以消除时区变更或旅行时的错误。

如何在 iOS 上获取 Unix Timestamp?

Date().timeIntervalSince1970 返回自 1970 年 1 月 1 日以来的秒数。要获得毫秒数,请乘以 1000 并转换为 Int64。要精确同步,请使用带有 NTP 服务器的 TrueTime 库。

总结

  • iOS 上的 DateFormatter — 用于格式化日期和时间的主要工具,需要强制缓存实例。ISO8601DateFormatter 是 ISO 8601 的更轻量替代方案。
  • Android 上的 DateTimeFormatter — 来自 java.time 的线程安全格式化器,适用于 API 26+。LocalDate 和 ZonedDateTime 是处理日期的主要类。
  • ThreeTenABP — 适用于所有 Android 版本的统一 API。引入以实现与 API 21-25 的向后兼容性。代码与 java.time 没有区别。
  • TimeZone 和 UTC — 以 UTC 存储时间,仅用于显示时转换为本地时区。使用 IANA 标识符。
  • Unix Timestamp — 服务器和客户端之间交换时间的通用格式。在 iOS 上通过 Date().timeIntervalSince1970 获取。
  • NTP 同步 — 确保毫秒级的时间精度。TrueTime(iOS)和 AndroidNtp(Android)是标准库。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目