Created
October 14, 2020 05:19
-
-
Save bok-/b2f930f5f7f1106d511dc4ab20091e8c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public extension JSONDecoder.DateDecodingStrategy { | |
static var betterISO8601: JSONDecoder.DateDecodingStrategy { | |
return .custom({ try decodeDate(from: $0, calendar: Calendar(identifier: .gregorian)) }) | |
} | |
static func betterISO8601 (calendar: Calendar) -> JSONDecoder.DateDecodingStrategy { | |
return .custom({ try decodeDate(from: $0, calendar: calendar) }) | |
} | |
} | |
private func decodeDate (from decoder: Decoder, calendar: Calendar) throws -> Date { | |
let container = try decoder.singleValueContainer() | |
let string = try container.decode(String.self) | |
guard let date = Date(string: string) else { | |
throw DateDecodingError.invalidDate(string) | |
} | |
return date | |
} | |
private enum DateDecodingError: Swift.Error { | |
case invalidDate(String) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension DateComponents | |
{ | |
/// An initializer to create an instance of `NSDateComponents` from an ISO8601-compatible string. | |
/// | |
/// - Parameters: | |
/// - iso8601String string: An ISO8601-compatible date string. | |
/// | |
public init? (string: String) | |
{ | |
self.init() | |
let scanner = Scanner(string: string) | |
scanner.charactersToBeSkipped = nil | |
// Date | |
guard let year = scanner.scanInt() else { return nil } | |
self.year = year | |
guard scanner.scanString("-") != nil, let month = scanner.scanInt() else { return } | |
self.month = month | |
guard scanner.scanString("-") != nil, let day = scanner.scanInt() else { return } | |
self.day = day | |
// There should now be a 'T', for time. | |
guard scanner.scanCharacters(from: CharacterSet(charactersIn: "T ")) != nil else { return } | |
guard let hour = scanner.scanInt() else { return } | |
self.hour = hour | |
guard scanner.scanString(":") != nil, let minute = scanner.scanInt() else { return } | |
self.minute = minute | |
let index = scanner.currentIndex | |
// seconds? | |
if scanner.scanString(":") != nil, let second = scanner.scanInt() { | |
self.second = second | |
// support for fractions of seconds | |
let i = scanner.currentIndex | |
if scanner.scanString(".") != nil { | |
scanner.currentIndex = i | |
guard let fraction = scanner.scanDouble() else { return } | |
self.nanosecond = Int(fraction * 1000000000) | |
} | |
// no seconds, undo that | |
} else { | |
scanner.currentIndex = index | |
} | |
self.timeZone = scanner.scanTimeZone() | |
} | |
} | |
extension Date | |
{ | |
public init? (string: String?, calendar: Calendar = Calendar.current) { | |
guard let string = string else { return nil } | |
guard let components = DateComponents(string: string) else { return nil } | |
var calendar = calendar | |
calendar.timeZone = components.timeZone ?? TimeZone(identifier: "UTC")! | |
guard let date = calendar.date(from: components) else { return nil } | |
self.init(timeInterval: 0, since: date); | |
} | |
} | |
extension Scanner { | |
/// Scans a TimeZone out of the receiver, or returns nil if not found. | |
/// | |
/// Supports the following TimeZone formats: | |
/// • Z (for UTC) | |
/// • +hh:mm | |
/// | |
func scanTimeZone () -> TimeZone? { | |
let start = self.currentIndex | |
// check for Zulu | |
if self.scanString("Z") != nil { | |
return TimeZone(identifier: "UTC") | |
} | |
// Check for the + or - that denotes the start of a TimeZone specifier. | |
self.currentIndex = start | |
let signs = CharacterSet(charactersIn: "+-") | |
_ = self.scanUpToCharacters(from: signs) | |
guard let sign = self.scanCharacters(from: signs) else { return nil } | |
// scan the number of hours | |
guard var hours = self.scanInt() else { return nil } | |
// check for colon and therefore minutes | |
var minutes: Int | |
if self.scanString(":") == nil && hours > 14 { | |
minutes = hours % 100; | |
hours = Int(floor(Double(hours) / 100.0)); | |
} else { | |
minutes = scanInt() ?? 0 | |
} | |
var offset = (hours * 3600) + (minutes * 60) | |
if sign == "-" { | |
offset *= -1 | |
} | |
return TimeZone(secondsFromGMT: offset); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment