Skip to content

Instantly share code, notes, and snippets.

@ole
Last active October 8, 2020 04:25
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ole/c8069be73079ae7a18e7595c009badfc to your computer and use it in GitHub Desktop.
Save ole/c8069be73079ae7a18e7595c009badfc to your computer and use it in GitHub Desktop.
Type-safe date format strings using string interpolation. Requires Swift 5.0.
// 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