Last active
October 8, 2020 04:25
-
-
Save ole/c8069be73079ae7a18e7595c009badfc to your computer and use it in GitHub Desktop.
Type-safe date format strings using string interpolation. Requires Swift 5.0.
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
// Type-safe date format strings using string interpolation | |
// Requires Swift 5.0. | |
import Foundation | |
enum DateFormatComponent { | |
case era(AlphaStyle) | |
case year(MinimumDigits) | |
case yearForWeekOfYear(MinimumDigits) | |
case quarter(AlphaNumericStyle) | |
case month(AlphaNumericStyle) | |
case day(MinimumDigits) | |
case dayOfYear(MinimumDigits) | |
case dayOfWeekInMonth | |
case amPM(AlphaStyle) | |
case hour12(MinimumDigits) | |
case hour24(MinimumDigits) | |
case minute(MinimumDigits) | |
case second(MinimumDigits) | |
case fractionalSecond(NumberOfDigits) | |
case timeZone(AlphaStyle) | |
struct MinimumDigits: ExpressibleByIntegerLiteral { | |
var value: Int | |
init(integerLiteral value: Int) { | |
self.value = value | |
} | |
} | |
struct NumberOfDigits: ExpressibleByIntegerLiteral { | |
var value: Int | |
init(integerLiteral value: Int) { | |
self.value = value | |
} | |
} | |
enum AlphaStyle: Int { | |
case abbreviated = 1 | |
case long = 4 | |
case narrow = 5 | |
} | |
enum AlphaNumericStyle: Int { | |
/// One digit | |
case one = 1 | |
/// Two digits | |
case two = 2 | |
case abbreviated = 3 | |
case full = 4 | |
case narrow = 5 | |
} | |
var formatString: String { | |
switch self { | |
case let .era(style): return String(repeating: "G", count: style.rawValue) | |
case let .year(digits): return String(repeating: "y", count: digits.value) | |
case let .yearForWeekOfYear(digits): return String(repeating: "Y", count: digits.value) | |
case let .quarter(style): return String(repeating: "Q", count: style.rawValue) | |
case let .month(style): return String(repeating: "M", count: style.rawValue) | |
case let .day(digits): return String(repeating: "d", count: digits.value) | |
case let .dayOfYear(digits): return String(repeating: "D", count: digits.value) | |
case .dayOfWeekInMonth: return "F" | |
case let .amPM(style): return String(repeating: "a", count: style.rawValue) | |
case let .hour12(digits): return String(repeating: "h", count: digits.value) | |
case let .hour24(digits): return String(repeating: "H", count: digits.value) | |
case let .minute(digits): return String(repeating: "m", count: digits.value) | |
case let .second(digits): return String(repeating: "s", count: digits.value) | |
case let .fractionalSecond(digits): return String(repeating: "S", count: digits.value) | |
case let .timeZone(style): return String(repeating: "Z", count: style.rawValue) | |
} | |
} | |
} | |
struct DateFormat { | |
var value: String | |
} | |
extension DateFormat: ExpressibleByStringInterpolation { | |
init(stringLiteral value: String) { | |
self.init(value: value) | |
} | |
init(stringInterpolation: StringInterpolation) { | |
self.init(value: stringInterpolation.value) | |
} | |
struct StringInterpolation: StringInterpolationProtocol { | |
var value: String = "" | |
init(literalCapacity: Int, interpolationCount: Int) { | |
value.reserveCapacity(literalCapacity) | |
} | |
mutating func appendLiteral(_ literal: String) { | |
guard !literal.isEmpty else { return } | |
value.append("'\(literal)'") | |
} | |
mutating func appendInterpolation(_ interpolation: DateFormatComponent) { | |
value.append(interpolation.formatString) | |
} | |
} | |
} | |
// Example: | |
let formatter = DateFormatter() | |
formatter.locale = Locale(identifier: "en_US_POSIX") | |
let dateFormat: DateFormat = "\(.year(1))-\(.month(.narrow))-\(.day(2))" | |
dateFormat.value | |
formatter.dateFormat = dateFormat.value | |
formatter.string(from: Date()) | |
let iso8601: DateFormat = """ | |
\(.year(1))-\(.month(.two))-\(.day(2))\ | |
T\ | |
\(.hour24(2)):\(.minute(2)):\(.second(2))\ | |
\(.timeZone(.narrow)) | |
""" | |
iso8601.value | |
formatter.dateFormat = iso8601.value | |
formatter.string(from: Date()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment