Last active
May 10, 2025 14:21
-
-
Save wata/9cd2d4779a55f5663fed0e07b05af1e6 to your computer and use it in GitHub Desktop.
A simple ISO string parsing and converting library for Swift
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
public struct ISOString { | |
/// Parse ISO 6709 string. | |
/// e.g. "+34.0595-118.4460+091.541/" | |
/// SeeAlso: [ISO 6709](https://en.wikipedia.org/wiki/ISO_6709) | |
public static func parse(iso6709 text: String?) -> CLLocation? { | |
guard | |
let results = text?.capture(pattern: "([+-][0-9.]+)([+-][0-9.]+)"), | |
let latitude = results[safe: 1] as NSString?, | |
let longitude = results[safe: 2] as NSString? | |
else { return nil } | |
return CLLocation(latitude: latitude.doubleValue, longitude: longitude.doubleValue) | |
} | |
/// Convert `CLLocation` to ISO 6709 string. | |
/// e.g. "+34.0595-118.4460+091.541/" | |
/// SeeAlso: [ISO 6709](https://en.wikipedia.org/wiki/ISO_6709) | |
public static func iso6709(from location: CLLocation) -> String { | |
String( | |
format: "%+09.5f%+010.5f%+.0fCRSWGS_84/", | |
location.coordinate.latitude, | |
location.coordinate.longitude, | |
location.altitude | |
) | |
} | |
/// Parse ISO 8601 string. | |
/// e.g. "2019-07-19T13:40:59+0900" | |
/// SeeAlso: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) | |
public static func parse(iso8601 text: String?) -> Date? { | |
guard | |
let text = text, | |
let date = ISO8601DateFormatter().date(from: text) | |
else { return nil } | |
return Date(timeInterval: 0, since: date) | |
} | |
/// Convert `Date` to ISO 8601 string. | |
/// e.g. "2019-07-19T13:40:59+0900" | |
/// SeeAlso: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) | |
public static func iso8601(from date: Date) -> String { | |
let formatter = ISO8601DateFormatter() | |
formatter.timeZone = TimeZone.current | |
formatter.formatOptions.remove(.withColonSeparatorInTimeZone) // iOS camera output format | |
return formatter.string(from: date) | |
} | |
} | |
private extension String { | |
func capture(pattern: String) -> [String] { | |
guard | |
let regex = try? NSRegularExpression(pattern: pattern), | |
let result = regex.firstMatch(in: self, range: NSRange(location: 0, length: count)) | |
else { return [] } | |
return (0..<result.numberOfRanges).map { String(self[Range(result.range(at: $0), in: self)!]) } | |
} | |
} | |
private extension Collection { | |
subscript(safe index: Index) -> Element? { | |
return indices.contains(index) ? self[index] : nil | |
} | |
} | |
public extension CLLocation { | |
convenience init?(iso6709 text: String?) { | |
guard let location = ISOString.parse(iso6709: text) else { return nil } | |
self.init(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) | |
} | |
var iso6709: String { | |
ISOString.iso6709(from: self) | |
} | |
} | |
public extension Date { | |
init?(iso8601 text: String?) { | |
guard let date = ISOString.parse(iso8601: text) else { return nil } | |
self = date | |
} | |
var iso8601: String { | |
ISOString.iso8601(from: self) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage