Skip to content

Commit

Permalink
chore: kickoff release
Browse files Browse the repository at this point in the history
  • Loading branch information
phantumcode authored Dec 14, 2023
2 parents a5a981a + 0211104 commit 8d98ae7
Show file tree
Hide file tree
Showing 53 changed files with 954 additions and 179 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/closed_issue_message.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name: Closed Issue Message
on:
issues:
types: [closed]

permissions:
issues: write

jobs:
auto_comment:
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct ModelDateFormatting {
public static let encodingStrategy: JSONEncoder.DateEncodingStrategy = {
let strategy = JSONEncoder.DateEncodingStrategy.custom { date, encoder in
var container = encoder.singleValueContainer()
try container.encode(Temporal.DateTime(date).iso8601String)
try container.encode(Temporal.DateTime(date, timeZone: .utc).iso8601String)
}
return strategy
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Foundation
/// - `Temporal.Time`
/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly
/// by host applications. The behavior of this may change without warning.
public protocol Persistable {}
public protocol Persistable: Encodable {}

extension Bool: Persistable {}
extension Double: Persistable {}
Expand Down
8 changes: 6 additions & 2 deletions Amplify/Categories/DataStore/Model/Temporal/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@ extension Temporal {
///
/// - Note: `.medium`, `.long`, and `.full` are the same date format.
public struct Date: TemporalSpec {

// Inherits documentation from `TemporalSpec`
public let foundationDate: Foundation.Date

// Inherits documentation from `TemporalSpec`
public let timeZone: TimeZone? = .utc

// Inherits documentation from `TemporalSpec`
public static func now() -> Self {
Temporal.Date(Foundation.Date())
Temporal.Date(Foundation.Date(), timeZone: .utc)
}

// Inherits documentation from `TemporalSpec`
public init(_ date: Foundation.Date) {
public init(_ date: Foundation.Date, timeZone: TimeZone?) {
self.foundationDate = Temporal
.iso8601Calendar
.startOfDay(for: date)
Expand Down
14 changes: 11 additions & 3 deletions Amplify/Categories/DataStore/Model/Temporal/DateTime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,33 @@ extension Temporal {
/// * `.long` => `yyyy-MM-dd'T'HH:mm:ssZZZZZ`
/// * `.full` => `yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ`
public struct DateTime: TemporalSpec {

// Inherits documentation from `TemporalSpec`
public let foundationDate: Foundation.Date

// Inherits documentation from `TemporalSpec`
public let timeZone: TimeZone?

// Inherits documentation from `TemporalSpec`
public static func now() -> Self {
Temporal.DateTime(Foundation.Date())
Temporal.DateTime(Foundation.Date(), timeZone: .utc)
}

/// `Temporal.Time` of this `Temporal.DateTime`.
public var time: Time {
Time(foundationDate)
Time(foundationDate, timeZone: timeZone)
}

// Inherits documentation from `TemporalSpec`
public init(_ date: Foundation.Date) {
public init(_ date: Foundation.Date, timeZone: TimeZone? = .utc) {
let calendar = Temporal.iso8601Calendar
let components = calendar.dateComponents(
DateTime.iso8601DateComponents,
from: date
)

self.timeZone = timeZone

foundationDate = calendar
.date(from: components) ?? date
}
Expand All @@ -57,3 +63,5 @@ extension Temporal {

// Allow date unit and time unit operations on `Temporal.DateTime`
extension Temporal.DateTime: DateUnitOperable, TimeUnitOperable {}

extension Temporal.DateTime: Sendable { }
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation
@usableFromInline
internal struct SpecBasedDateConverting<Spec: TemporalSpec> {
@usableFromInline
internal typealias DateConverter = (_ string: String, _ format: TemporalFormat?) throws -> Date
internal typealias DateConverter = (_ string: String, _ format: TemporalFormat?) throws -> (Date, TimeZone)

@usableFromInline
internal let convert: DateConverter
Expand All @@ -28,19 +28,21 @@ internal struct SpecBasedDateConverting<Spec: TemporalSpec> {
internal static func `default`(
iso8601String: String,
format: TemporalFormat? = nil
) throws -> Date {
) throws -> (Date, TimeZone) {
let date: Foundation.Date
let tz: TimeZone = TimeZone(iso8601DateString: iso8601String) ?? .utc
if let format = format {
date = try Temporal.date(
from: iso8601String,
with: [format(for: Spec.self)]
)

} else {
date = try Temporal.date(
from: iso8601String,
with: TemporalFormat.sortedFormats(for: Spec.self)
)
}
return date
return (date, tz)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import Foundation
extension TemporalSpec where Self: Comparable {

public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.iso8601String == rhs.iso8601String
return lhs.iso8601FormattedString(format: .full, timeZone: .utc)
== rhs.iso8601FormattedString(format: .full, timeZone: .utc)
}

public static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.iso8601String < rhs.iso8601String
return lhs.iso8601FormattedString(format: .full, timeZone: .utc)
< rhs.iso8601FormattedString(format: .full, timeZone: .utc)
}
}

Expand Down
16 changes: 10 additions & 6 deletions Amplify/Categories/DataStore/Model/Temporal/Temporal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public protocol TemporalSpec {
/// by a Foundation `Date` instance.
var foundationDate: Foundation.Date { get }

/// The timezone field is an optional field used to specify the timezone associated
/// with a particular date.
var timeZone: TimeZone? { get }

/// The ISO-8601 formatted string in the UTC `TimeZone`.
/// - SeeAlso: `iso8601FormattedString(TemporalFormat, TimeZone) -> String`
var iso8601String: String { get }
Expand Down Expand Up @@ -57,7 +61,7 @@ public protocol TemporalSpec {
/// Constructs a `TemporalSpec` from a `Date` object.
/// - Parameter date: The `Date` instance that will be used as the reference of the
/// `TemporalSpec` instance.
init(_ date: Foundation.Date)
init(_ date: Foundation.Date, timeZone: TimeZone?)

/// A string representation of the underlying date formatted using ISO8601 rules.
///
Expand Down Expand Up @@ -90,25 +94,25 @@ extension TemporalSpec {
/// The ISO8601 representation of the scalar using `.full` as the format and `.utc` as `TimeZone`.
/// - SeeAlso: `iso8601FormattedString(format:timeZone:)`
public var iso8601String: String {
iso8601FormattedString(format: .full)
iso8601FormattedString(format: .full, timeZone: timeZone ?? .utc)
}

@inlinable
public init(iso8601String: String, format: TemporalFormat) throws {
let date = try SpecBasedDateConverting<Self>()
let (date, tz) = try SpecBasedDateConverting<Self>()
.convert(iso8601String, format)

self.init(date)
self.init(date, timeZone: tz)
}

@inlinable
public init(
iso8601String: String
) throws {
let date = try SpecBasedDateConverting<Self>()
let (date, tz) = try SpecBasedDateConverting<Self>()
.convert(iso8601String, nil)

self.init(date)
self.init(date, timeZone: tz)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ extension TemporalSpec {
"""
)
}
return Self.init(date)
return Self.init(date, timeZone: timeZone)
}
}
8 changes: 5 additions & 3 deletions Amplify/Categories/DataStore/Model/Temporal/Time.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ extension Temporal {
// Inherits documentation from `TemporalSpec`
public let foundationDate: Foundation.Date

// Inherits documentation from `TemporalSpec`
public let timeZone: TimeZone? = .utc

// Inherits documentation from `TemporalSpec`
public static func now() -> Self {
Temporal.Time(Foundation.Date())
Temporal.Time(Foundation.Date(), timeZone: .utc)
}

// Inherits documentation from `TemporalSpec`
public init(_ date: Foundation.Date) {
public init(_ date: Foundation.Date, timeZone: TimeZone?) {
// Sets the date to a fixed instant so time-only operations are safe
let calendar = Temporal.iso8601Calendar
var components = calendar.dateComponents(
Expand All @@ -45,7 +48,6 @@ extension Temporal {
components.year = 2_000
components.month = 1
components.day = 1

self.foundationDate = calendar
.date(from: components) ?? date
}
Expand Down
150 changes: 150 additions & 0 deletions Amplify/Categories/DataStore/Model/Temporal/TimeZone+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//


import Foundation

extension TimeZone {

@usableFromInline
internal init?(iso8601DateString: String) {
switch ISO8601TimeZonePart.from(iso8601DateString: iso8601DateString) {
case .some(.utc):
self.init(abbreviation: "UTC")
case let .some(.hh(hours: hours)):
self.init(secondsFromGMT: hours * 60 * 60)
case let .some(.hhmm(hours: hours, minutes: minutes)),
let .some(.hh_mm(hours: hours, minuts: minutes)):
self.init(secondsFromGMT: hours * 60 * 60 +
(hours > 0 ? 1 : -1) * minutes * 60)
case let .some(.hh_mm_ss(hours: hours, minutes: minutes, seconds: seconds)):
self.init(secondsFromGMT: hours * 60 * 60 +
(hours > 0 ? 1 : -1) * minutes * 60 +
(hours > 0 ? 1 : -1) * seconds)
case .none:
return nil
}
}
}


/// ISO8601 Time Zone formats
/// - Note:
/// `±hh:mm:ss` is not a standard of ISO8601 date formate. It's supported by `AWSDateTime` exclusively.
///
/// references:
/// https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators
/// https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html#graph-ql-aws-appsync-scalars
fileprivate enum ISO8601TimeZoneFormat {
case utc, hh, hhmm, hh_mm, hh_mm_ss

var format: String {
switch self {
case .utc:
return "Z"
case .hh:
return "±hh"
case .hhmm:
return "±hhmm"
case .hh_mm:
return "±hh:mm"
case .hh_mm_ss:
return "±hh:mm:ss"
}
}

var regex: NSRegularExpression? {
switch self {
case .utc:
return try? NSRegularExpression(pattern: "^Z$")
case .hh:
return try? NSRegularExpression(pattern: "^[+-]\\d{2}$")
case .hhmm:
return try? NSRegularExpression(pattern: "^[+-]\\d{2}\\d{2}$")
case .hh_mm:
return try? NSRegularExpression(pattern: "^[+-]\\d{2}:\\d{2}$")
case .hh_mm_ss:
return try? NSRegularExpression(pattern: "^[+-]\\d{2}:\\d{2}:\\d{2}$")
}
}

var parts: [NSRange] {
switch self {
case .utc:
return []
case .hh:
return [NSRange(location: 0, length: 3)]
case .hhmm:
return [
NSRange(location: 0, length: 3),
NSRange(location: 3, length: 2)
]
case .hh_mm:
return [
NSRange(location: 0, length: 3),
NSRange(location: 4, length: 2)
]
case .hh_mm_ss:
return [
NSRange(location: 0, length: 3),
NSRange(location: 4, length: 2),
NSRange(location: 7, length: 2)
]
}
}
}

fileprivate enum ISO8601TimeZonePart {
case utc
case hh(hours: Int)
case hhmm(hours: Int, minutes: Int)
case hh_mm(hours: Int, minuts: Int)
case hh_mm_ss(hours: Int, minutes: Int, seconds: Int)


static func from(iso8601DateString: String) -> ISO8601TimeZonePart? {
return tryExtract(from: iso8601DateString, with: .utc)
?? tryExtract(from: iso8601DateString, with: .hh)
?? tryExtract(from: iso8601DateString, with: .hhmm)
?? tryExtract(from: iso8601DateString, with: .hh_mm)
?? tryExtract(from: iso8601DateString, with: .hh_mm_ss)
?? nil
}
}

fileprivate func tryExtract(
from dateString: String,
with format: ISO8601TimeZoneFormat
) -> ISO8601TimeZonePart? {
guard dateString.count > format.format.count else {
return nil
}

let tz = String(dateString.dropFirst(dateString.count - format.format.count))

guard format.regex.flatMap({
$0.firstMatch(in: tz, range: NSRange(location: 0, length: tz.count))
}) != nil else {
return nil
}

let parts = format.parts.compactMap { range in
Range(range, in: tz).flatMap { Int(tz[$0]) }
}

guard parts.count == format.parts.count else {
return nil
}

switch format {
case .utc: return .utc
case .hh: return .hh(hours: parts[0])
case .hhmm: return .hhmm(hours: parts[0], minutes: parts[1])
case .hh_mm: return .hh_mm(hours: parts[0], minuts: parts[1])
case .hh_mm_ss: return .hh_mm_ss(hours: parts[0], minutes: parts[1], seconds: parts[2])
}
}
Loading

0 comments on commit 8d98ae7

Please sign in to comment.