TeraCivilDateInput.swift (1970B)
1 import Foundation 2 3 /// Partial native component entry stays separate from a validated civil date. 4 struct TeraCivilDateInput { 5 let raw: String? 6 7 func component(_ index: Int) -> String { 8 let parts = raw?.split(separator: "-", omittingEmptySubsequences: false) ?? [] 9 return parts.indices.contains(index) ? String(parts[index]) : "" 10 } 11 12 var value: TeraCivilDate? { 13 guard let raw, raw.utf8.count <= 64, 14 raw.split(separator: "-", omittingEmptySubsequences: false).count == 3, 15 let year = number(component(0), maximumDigits: 4), 16 let month = number(component(1), maximumDigits: 2), 17 let day = number(component(2), maximumDigits: 2), 18 let typedYear = UInt16(exactly: year), let typedMonth = UInt8(exactly: month), 19 let typedDay = UInt8(exactly: day) 20 else { return nil } 21 return try? TeraCivilDate(year: typedYear, month: typedMonth, day: typedDay) 22 } 23 24 func replacing(_ index: Int, with input: String) -> String? { 25 guard (0 ... 2).contains(index) else { return raw } 26 var parts = (0 ... 2).map(component) 27 parts[index] = String(input.prefix(index == 0 ? 4 : 2)) 28 guard parts.contains(where: { !$0.isEmpty }) else { return nil } 29 return parts.joined(separator: "-") 30 } 31 32 var canonicalOrRaw: String? { 33 value?.canonical ?? raw 34 } 35 36 private func number(_ text: String, maximumDigits: Int) -> Int? { 37 guard !text.isEmpty, text.count <= maximumDigits else { return nil } 38 var value = 0 39 for character in text { 40 guard let digit = character.wholeNumberValue, (0 ... 9).contains(digit) else { return nil } 41 value = value * 10 + digit 42 } 43 return value 44 } 45 } 46 47 extension TeraCivilDate { 48 var nextDay: Self? { 49 if let next = try? Self(year: year, month: month, day: day + 1) { 50 return next 51 } 52 if let next = try? Self(year: year, month: month + 1, day: 1) { 53 return next 54 } 55 return try? Self(year: year + 1, month: 1, day: 1) 56 } 57 }