Skip to content

Commit f97cd24

Browse files
atierianlawmicha
andauthored
feat(DataStore): Temporal performance enhancements (#1760)
Co-authored-by: Michael Law <[email protected]>
1 parent 14ae820 commit f97cd24

21 files changed

+730
-388
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 86 additions & 72 deletions
Large diffs are not rendered by default.

Amplify/Categories/DataStore/Model/Temporal/Date+Operation.swift

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,58 +7,114 @@
77

88
import Foundation
99

10-
public enum DateUnit {
11-
12-
case days(_ value: Int)
13-
case weeks(_ value: Int)
14-
case months(_ value: Int)
15-
case years(_ value: Int)
10+
/// `DateUnit` for use in adding and subtracting units from `Temporal.Date`, `Temporal.DateTime`, and `Temporal.Time`.
11+
///
12+
/// `DateUnit` uses `Calendar.Component` under the hood to ensure date oddities are accounted for.
13+
///
14+
/// let tomorrow = Temporal.Date.now() + .days(1)
15+
/// let twoWeeksFromNow = Temporal.Date.now() + .weeks(2)
16+
/// let nextYear = Temporal.Date.now() + .years(1)
17+
///
18+
/// let yesterday = Temporal.Date.now() - .days(1)
19+
/// let sixMonthsAgo = Temporal.Date.now() - .months(6)
20+
public struct DateUnit {
21+
let calendarComponent: Calendar.Component
22+
let value: Int
1623

24+
/// One day. Equivalent to 1 x `Calendar.Component.day`
1725
public static let oneDay: DateUnit = .days(1)
26+
27+
/// One week. Equivalent to 7 x `Calendar.Component.day`
1828
public static let oneWeek: DateUnit = .weeks(1)
29+
30+
/// One month. Equivalent to 1 x `Calendar.Component.month`
1931
public static let oneMonth: DateUnit = .months(1)
32+
33+
/// One year. Equivalent to 1 x `Calendar.Component.year`
2034
public static let oneYear: DateUnit = .years(1)
2135

22-
public var calendarComponent: Calendar.Component {
23-
switch self {
24-
case .days, .weeks:
25-
return .day
26-
case .months:
27-
return .month
28-
case .years:
29-
return .year
30-
}
36+
/// DateUnit amount of days.
37+
/// One day is 1 x `Calendar.Component.day`
38+
///
39+
/// let fiveDays = DateUnit.days(5)
40+
/// // or
41+
/// let fiveDays: DateUnit = .days(5)
42+
///
43+
/// - Parameter value: Amount of days in this `DateUnit`
44+
/// - Returns: A `DateUnit` with the defined number of days.
45+
public static func days(_ value: Int) -> Self {
46+
.init(calendarComponent: .day, value: value)
47+
}
48+
49+
/// DateUnit amount of weeks.
50+
/// One week is 7 x the `Calendar.Component.day`
51+
///
52+
/// let twoWeeks = DateUnit.weeks(2)
53+
/// // or
54+
/// let twoWeeks: DateUnit = .weeks(2)
55+
///
56+
/// - Parameter value: Amount of weeks in this `DateUnit`
57+
/// - Returns: A `DateUnit` with the defined number of weeks.
58+
public static func weeks(_ value: Int) -> Self {
59+
.init(calendarComponent: .day, value: value * 7)
3160
}
3261

33-
public var value: Int {
34-
switch self {
35-
case .days(let value),
36-
.months(let value),
37-
.years(let value):
38-
return value
39-
case .weeks(let value):
40-
return value * 7
41-
}
62+
/// DateUnit amount of months.
63+
/// One month is 1 x `Calendar.Component.month`
64+
///
65+
/// let sixMonths = DateUnit.months(6)
66+
/// // or
67+
/// let sixMonths: DateUnit = .months(6)
68+
///
69+
/// - Parameter value: Amount of months in this `DateUnit`
70+
/// - Returns: A `DateUnit` with the defined number of months.
71+
public static func months(_ value: Int) -> Self {
72+
.init(calendarComponent: .month, value: value)
4273
}
4374

75+
/// DateUnit amount of years.
76+
/// One year is 1 x `Calendar.Component.year`
77+
///
78+
/// let oneYear = DateUnit.years(1)
79+
/// // or
80+
/// let oneYear: DateUnit = .years(1)
81+
///
82+
/// - Parameter value: Amount of years in this `DateUnit`
83+
/// - Returns: A `DateUnit` with the defined number of years.
84+
public static func years(_ value: Int) -> Self {
85+
.init(calendarComponent: .year, value: value)
86+
}
4487
}
4588

89+
/// Supports addition and subtraction of `Temporal.Date` and `Temporal.DateTime` with `DateUnit`
4690
public protocol DateUnitOperable {
47-
4891
static func + (left: Self, right: DateUnit) -> Self
49-
5092
static func - (left: Self, right: DateUnit) -> Self
51-
5293
}
5394

5495
extension TemporalSpec where Self: DateUnitOperable {
5596

97+
/// Add a `DateUnit` to a `Temporal.Date` or `Temporal.DateTime`
98+
///
99+
/// let tomorrow = Temporal.Date.now() + .days(1)
100+
///
101+
/// - Parameters:
102+
/// - left: `Temporal.Date` or `Temporal.DateTime`
103+
/// - right: `DateUnit` to add to `left`
104+
/// - Returns: A new `Temporal.Date` or `Temporal.DateTime` the `DateUnit` was added to.
56105
public static func + (left: Self, right: DateUnit) -> Self {
57106
return left.add(value: right.value, to: right.calendarComponent)
58107
}
59108

109+
/// Subtract a `DateUnit` from a `Temporal.Date` or `Temporal.DateTime`
110+
///
111+
/// let yesterday = Temporal.Date.now() - .day(1)
112+
///
113+
/// - Parameters:
114+
/// - left: `Temporal.Date` or `Temporal.DateTime`
115+
/// - right: `DateUnit` to subtract from `left`
116+
/// - Returns: A new `Temporal.Date` or `Temporal.DateTime` the `DateUnit` was subtracted from.
60117
public static func - (left: Self, right: DateUnit) -> Self {
61118
return left.add(value: -right.value, to: right.calendarComponent)
62119
}
63-
64120
}

Amplify/Categories/DataStore/Model/Temporal/Date.swift

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,31 @@ import Foundation
99

1010
extension Temporal {
1111

12-
/// An extension that makes the `Date` struct conform with the `TemporalSpec` protocol.
13-
/// When used in persistence operations, the granularity of the different date representations
14-
/// is set by using different scalar types: `Date`, `DateTime` and `Time`.
12+
/// `Temporal.Date` represents a `Date` with specific allowable formats.
1513
///
16-
/// In those scenarios, the standard `Date` is formatted to ISO-8601 without the time.
17-
/// When the full date information is required, use `DateTime` instead.
18-
public struct Date: TemporalSpec, DateUnitOperable {
19-
20-
public static func now() -> Self {
21-
return Temporal.Date(Foundation.Date())
22-
}
23-
14+
/// * `.short` => `yyyy-MM-dd`
15+
/// * `.medium` => `yyyy-MM-ddZZZZZ`
16+
/// * `.long` => `yyyy-MM-ddZZZZZ`
17+
/// * `.full` => `yyyy-MM-ddZZZZZ`
18+
///
19+
/// - Note: `.medium`, `.long`, and `.full` are the same date format.
20+
public struct Date: TemporalSpec {
21+
// Inherits documentation from `TemporalSpec`
2422
public let foundationDate: Foundation.Date
2523

26-
public init(iso8601String: String) throws {
27-
guard let date = Temporal.Date.iso8601Date(from: iso8601String) else {
28-
throw DataStoreError.invalidDateFormat(iso8601String)
29-
}
30-
self.init(date)
24+
// Inherits documentation from `TemporalSpec`
25+
public static func now() -> Self {
26+
Temporal.Date(Foundation.Date())
3127
}
3228

29+
// Inherits documentation from `TemporalSpec`
3330
public init(_ date: Foundation.Date) {
34-
// sets the date to a fixed instant so date-only operations are safe
35-
self.foundationDate = Date.iso8601Calendar.startOfDay(for: date)
31+
self.foundationDate = Temporal
32+
.iso8601Calendar
33+
.startOfDay(for: date)
3634
}
3735
}
3836
}
37+
38+
// Allow date unit operations on `Temporal.Date`
39+
extension Temporal.Date: DateUnitOperable {}

Amplify/Categories/DataStore/Model/Temporal/DateTime.swift

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,52 @@
88
import Foundation
99

1010
extension Temporal {
11-
12-
/// `DateTime` is an immutable `TemporalSpec` object that represents a date with a time,
13-
/// often viewed as `yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ`.
11+
/// `Temporal.DateTime` represents a `DateTime` with specific allowable formats.
1412
///
15-
/// `DateTime` can be represented to nanosecond precision and it also holds a reference
16-
/// to a TimeZone. As all Date scalars, `DateTime` relies on the ISO-8601 calendar.
17-
public struct DateTime: TemporalSpec, DateUnitOperable, TimeUnitOperable {
18-
19-
public static var iso8601DateComponents: Set<Calendar.Component> {
20-
[.year, .month, .day, .hour, .minute, .second, .nanosecond, .timeZone]
21-
}
13+
/// * `.short` => `yyyy-MM-dd'T'HH:mm`
14+
/// * `.medium` => `yyyy-MM-dd'T'HH:mm:ss`
15+
/// * `.long` => `yyyy-MM-dd'T'HH:mm:ssZZZZZ`
16+
/// * `.full` => `yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ`
17+
public struct DateTime: TemporalSpec {
18+
// Inherits documentation from `TemporalSpec`
19+
public let foundationDate: Foundation.Date
2220

23-
public static func now() -> DateTime {
24-
return DateTime(Foundation.Date())
21+
// Inherits documentation from `TemporalSpec`
22+
public static func now() -> Self {
23+
Temporal.DateTime(Foundation.Date())
2524
}
2625

27-
public let foundationDate: Foundation.Date
28-
26+
/// `Temporal.Time` of this `Temporal.DateTime`.
2927
public var time: Time {
3028
Time(foundationDate)
3129
}
3230

31+
// Inherits documentation from `TemporalSpec`
3332
public init(_ date: Foundation.Date) {
34-
let calendar = DateTime.iso8601Calendar
35-
let components = calendar.dateComponents(DateTime.iso8601DateComponents, from: date)
36-
self.foundationDate = components.date ?? date
37-
}
38-
39-
public init(iso8601String: String) throws {
40-
guard let date = DateTime.iso8601Date(from: iso8601String) else {
41-
throw DataStoreError.invalidDateFormat(iso8601String)
42-
}
43-
self.foundationDate = date
33+
let calendar = Temporal.iso8601Calendar
34+
let components = calendar.dateComponents(
35+
DateTime.iso8601DateComponents,
36+
from: date
37+
)
38+
39+
foundationDate = calendar
40+
.date(from: components) ?? date
4441
}
4542

43+
/// `Calendar.Component`s used in `init(_ date:)`
44+
static let iso8601DateComponents: Set<Calendar.Component> =
45+
[
46+
.year,
47+
.month,
48+
.day,
49+
.hour,
50+
.minute,
51+
.second,
52+
.nanosecond,
53+
.timeZone
54+
]
4655
}
47-
4856
}
57+
58+
// Allow date unit and time unit operations on `Temporal.DateTime`
59+
extension Temporal.DateTime: DateUnitOperable, TimeUnitOperable {}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
/// Internal generic method to reduce code reuse in the `init`s of `TemporalSpec`
11+
/// conforming types
12+
@usableFromInline
13+
internal struct SpecBasedDateConverting<Spec: TemporalSpec> {
14+
@usableFromInline
15+
internal typealias DateConverter = (_ string: String, _ format: TemporalFormat?) throws -> Date
16+
17+
@usableFromInline
18+
internal let convert: DateConverter
19+
20+
@inlinable
21+
@inline(never)
22+
init(converter: @escaping DateConverter = Self.default) {
23+
self.convert = converter
24+
}
25+
26+
@inlinable
27+
@inline(never)
28+
internal static func `default`(
29+
iso8601String: String,
30+
format: TemporalFormat? = nil
31+
) throws -> Date {
32+
let date: Foundation.Date
33+
if let format = format {
34+
date = try Temporal.date(
35+
from: iso8601String,
36+
with: [format(for: Spec.self)]
37+
)
38+
} else {
39+
date = try Temporal.date(
40+
from: iso8601String,
41+
with: TemporalFormat.sortedFormats(for: Spec.self)
42+
)
43+
}
44+
return date
45+
}
46+
}

0 commit comments

Comments
 (0)