Time

Swift Package Manager iOS + macOS Twitter: @ctrl_group

欢迎使用 Time,这是一个简化日历、日期和时间处理的 Swift 包。

简介

Time 由 Ctrl Group 构建,旨在简化日期和时间的操作,并避免使用标准 Date 对象时常犯的错误。

Time 相对于 Foundation 库提供了什么

Foundation 库提供了一个 Date 对象,它表示一个独立于任何日历或时区的单一时间点。 这个对象需要与 DateFormatterTimeZoneLocaleDateComponentsCalendar 结合使用,才能执行日历算术并创建有意义的日期和时间表示。

Time 使用这些工具并提供一个简单的 API,允许您表示 CalendarDateTimeOfDayTimestampCalendarDateRangeTimeRange 等。

主要特性

  1. 提供准确表示您想要存储的信息的类型,并在语义上将存储的内容传达给代码的读者。
  2. 提供将上下文信息(如 TimeZone)分组的类型。
  3. 使执行日历算术变得快速而简单。
  4. 简化了日期和时间信息的序列化和解析。
  5. 提供了一种简单的方法来为单元测试桩化时间。

安装

Time 使用 Swift Package Manager 分发。 要将其安装到项目中,请将其作为依赖项添加到您的 Package.swift 清单中。

let package = Package(
    ...
    dependencies: [
        .package(url: "https://github.com/ctrlgroup/Time.git", from: "1.0.0")
    ],
    ...
)

然后在您想使用它的地方导入 Time

import Time

示例

日历日期

CalendarDate 允许您在公历中表示日期,不包含时间,并且与时区无关。

您可以显式地使用日、月和年创建它们,也可以使用 DateTimeZone 创建它们。

let europeLondon: TimeZone = ...
let date: Date = ...

// April 4th, 1994
let dateOfBirth = try? CalendarDate(day: 03, month: 04, year: 1994)

// Can also be created from a `Date`
let calendarDate = CalendarDate(date: date, timeZone: europeLondon)

实例化后,您可以查询它们的日、月、年,甚至星期几。

print(calendarDate.month)
print(calendarDate.dayOfWeek)

它们也可以转换回 Date(表示该日午夜的时刻)

// Can be converted back to a `Date` using a time zone
let date: Date = calendarDate.date(in: europeLondon)

您还可以轻松地进行日历算术运算,通常无需进行任何复杂的 Calendar 转换。

let nextWeek = dateOfBirth + 7 // Adds 7 days
let difference: Int = nextWeek - dateOfBirth // 7

// Calendar dates are comparable
let firstOfMay = try? CalendarDate(day: 01, month: 05, year: 2022)
let tenthOfJune = try? CalendarDate(day: 10, month: 06, year: 2022)
tenthOfJune > firstOfMay // true

// Easily find weekdays (uses a Calendar to compute the weekday)
dateOfBirth.previous(weekday: .sunday) // Finds the previous Sunday
dateOfBirth.next(weekday: .sunday) // Finds the next Sunday

使用日历日期进行算术运算是一个简单的操作,因为底层原始值只是一个 Int。 这意味着您无需担心使用 Calendar 的开销,并且可以轻松地执行诸如迭代 CalendarDateRange 之类的操作。

时间间隔

我们添加了一些便捷方法,以便以易于阅读的方式编写 TimeInterval

let seconds: TimeInterval = 12.hours + 34.minutes + 56.seconds
let fiveDays: TimeInterval = 5.days

TimeOfDay

要仅表示时间(不包含日期),您可以使用 TimeOfDay。 这对于记录重复提醒或闹钟的时间等非常有用。

可以通过各种不同的方式创建 TimeOfDay

let example1 = try? TimeOfDay(hour: 18, minute: 30, second: 0)
let example2 = try? TimeOfDay(secondsSinceMidnight: 3600 * 12)
let example3 = TimeOfDay(date: Date(), timeZone: europeLondon)
let example4 = 1830 // Use the "tricolon" operator
let example5 = 123456 // Also supports seconds

您可以对 TimeOfDayTimeInterval 进行算术运算

let midday = 1130 + 30.minutes
let elevenAM = 1130 - 30.minutes
let fiveMins: TimeInterval = 0600 - 0555
let oneAM = 2330 + (1.hours + 30.minutes) // Can span over midnight

它还支持各种字符串转换

let timeOfDay = 153456
let locale = Locale(identifier: "en-US")
timeOfDay.string(style: (.short, .twelveHour), locale: locale) // 3:34 PM
timeOfDay.string(style: (.medium, .twentyFourHour)) // 15:34:56

// Converting from a string
let result = TimeOfDay(string: "3:34 PM", 
                       style: (.short, .twelveHour),
                       locale: locale)

TimeOfDay 也是 EquatableHashableCodableComparable

CalendarDateRange

CalendarDateRange 允许您轻松表示可以迭代的日历日期范围,并使用 Range 的所有常用运算符。

// A calendar date range is simply a Swift Range
public typealias CalendarDateRange = Range<CalendarDate>

// Can be created in the usual way
let range = startDate ..< endDate

// It can be indexed using integers
sut[0] == startDate
sut[1] == startDate + 1

// Convenience initializers make calendar date ranges easy to understand
CalendarDateRange(firstDate: startDate, lastIncludedDate: lastDate)
CalendarDateRange(startDate: startDate, endDate: endDate)

TimeRange

TimeRange 稍微复杂一些,因为 TimeOfDay 不是严格连续的(当持续添加秒时,它们会循环)。 但是,能够在其上形成范围仍然很有用。

// Can be created in the usual way
let timeRange: TimeRange = 1130 ..< 1800
let timeRangeOverMidnight: TimeRange = 2330 ..< 0100

// Can test is a time exists in the range
timeRange.contains(1230) // true

DateTime

当您想要在特定日历日期上表示特定时间(但在任何时区)时,可以使用 DateTime

let calendarDate: CalendarDate = ...
let timeOfDay: timeOfDay = ...
let dateTime = DateTime(calendarDate: calendarDate, timeOfDay: timeOfDay)

// Can be created from a `Date` and `TimeZone`
let dateTime = DateTime(date: Date(), timeZone: europeLondon)

// Can be converted to a `Date` or `Timestamp`
let date = dateTime.date(in: europeLondon)
let timestamp = dateTime.timestamp(in: europeLondon)

Timestamp

大多数情况下,Timestamp 将是替换 Date 的更合适的类型。 它只是 Date 的封装,包含记录时的 TimeZone,因此类似地,它表示一个单一的时间点,但包含理解它表示的日期和时间所需的上下文。

let timestamp = Timestamp(date: Date(), timeZone: europeLondon)
timestamp.day // 1st March
timestamp.timeOfDay // 12pm
timestamp.dateTime = // 1st March at 12pm

TimestampDate 之间的另一个区别在于它实现 Equatable 的方式。 由于 Date 基于 Double,因此它会受到浮点算术运算不准确的影响。 这意味着比较两个 Date 对象有时会给出意想不到的结果…

let date1 = dateFormatter.date(from: "2022-01-01T12:34:56.1234Z")!
let date2 = Date(timeIntervalSinceReferenceDate: 662733296.123)
print(date1) // 2022-01-01 12:34:56 +0000
print(date2) // 2022-01-01 12:34:56 +0000
date1 == date2 // false

Timestamp 通过考虑彼此在一毫秒之内的日期是否相等来解决此问题。

Clock

Clock 是一个可以读取以了解当前日期和时间的类。 应该使用它来代替使用 Date() 初始化程序。 通过将 Clock 实例注入到您的代码中,您可以在测试中将其桩化,以便对依赖于当前时间的代码进行单元测试。

其他功能

许多类型(CalendarDateTimeOfDay 等)也是 EquatableComparableHashableCodable

使用 Codable 进行序列化时,该包将首选将类型保存为标准 ISO8601 字符串,例如

Timestamp 需要 TimeZone 的上下文,因此将在 JSON 中进行如下编码

{
  "timestamp": "...",
  "timeZone": "Europe/London"
}

在此,timestamp 属性是一个编码的 Date。 这将以默认方式(作为双精度值,自参考日期以来的秒数)进行编码,除非您指定使用 date encoding strategy 属性使用 ISO8601 表示形式

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let date = try? encoder.encode(timestamp)

字符串格式化

Time 试图提供一种更简单的方法来从日期和时间创建字符串,而无需直接使用 DateFormatter 类。

let dateFormat = "dd-MM-yyyy"

// Using Foundation
let date: Date = ...
let timeZone: TimeZone = ...
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timeZone
dateFormatter.locale = .current
dateFormatter.setLocalizedDateFormatFromTemplate(dateFormat)
let result = dateFormatter.string(from: date)

// Using Time
let calendarDate: CalendarDate = ...
let result = calendarDate.string(withFormat: dateFormat, locale: .current)

TimeOfDay 也存在类似的方法,并且 CalendarDateTimeOfDayDateTimeTimestamp 上存在用于创建 ISO8601 字符串的附加方法。

作者

Time 由 Ctrl Group 的团队设计和实现。

许可

Time 在 MIT 许可下可用。 有关更多信息,请参见 LICENSE 文件。

贡献和支持

我们希望 Time 在未来完全公开开发,您的贡献非常受欢迎!

如果您发现任何问题、有任何建议或希望改进文档,我们鼓励您打开一个 Pull Request,我们将积极审查并接受贡献。

我们希望您会发现此软件包有用且愉快!