2024-09-02 12:03:24 -07:00

355 lines
12 KiB
Swift

////
//// DateParser.swift
////
////
//// Created by Brent Simmons on 8/28/24.
////
//
//import Foundation
//
//private struct TimeZoneSpecifier {
// let abbreviation: String
// let offsetHours: Int
// let offsetMinutes: Int
//
// init(_ abbreviation: String, _ offsetHours: Int, _ offsetMinutes: Int) {
// self.abbreviation = abbreviation
// self.offsetHours = offsetHours
// self.offsetMinutes = offsetMinutes
// }
//}
//
//// See http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations for list
//private let timeZoneTable: [TimeZoneAbbreviationAndOffset] = [
// // Most common at top for performance
// TimeZoneSpecifier("GMT", 0, 0),
// TimeZoneSpecifier("PDT", -7, 0),
// TimeZoneSpecifier("PST", -8, 0),
// TimeZoneSpecifier("EST", -5, 0),
// TimeZoneSpecifier("EDT", -4, 0),
// TimeZoneSpecifier("MDT", -6, 0),
// TimeZoneSpecifier("MST", -7, 0),
// TimeZoneSpecifier("CST", -6, 0),
// TimeZoneSpecifier("CDT", -5, 0),
// TimeZoneSpecifier("ACT", -8, 0),
// TimeZoneSpecifier("AFT", 4, 30),
// TimeZoneSpecifier("AMT", 4, 0),
// TimeZoneSpecifier("ART", -3, 0),
// TimeZoneSpecifier("AST", 3, 0),
// TimeZoneSpecifier("AZT", 4, 0),
// TimeZoneSpecifier("BIT", -12, 0),
// TimeZoneSpecifier("BDT", 8, 0),
// TimeZoneSpecifier("ACST", 9, 30),
// TimeZoneSpecifier("AEST", 10, 0),
// TimeZoneSpecifier("AKST", -9, 0),
// TimeZoneSpecifier("AMST", 5, 0),
// TimeZoneSpecifier("AWST", 8, 0),
// TimeZoneSpecifier("AZOST", -1, 0),
// TimeZoneSpecifier("BIOT", 6, 0),
// TimeZoneSpecifier("BRT", -3, 0),
// TimeZoneSpecifier("BST", 6, 0),
// TimeZoneSpecifier("BTT", 6, 0),
// TimeZoneSpecifier("CAT", 2, 0),
// TimeZoneSpecifier("CCT", 6, 30),
// TimeZoneSpecifier("CET", 1, 0),
// TimeZoneSpecifier("CEST", 2, 0),
// TimeZoneSpecifier("CHAST", 12, 45),
// TimeZoneSpecifier("ChST", 10, 0),
// TimeZoneSpecifier("CIST", -8, 0),
// TimeZoneSpecifier("CKT", -10, 0),
// TimeZoneSpecifier("CLT", -4, 0),
// TimeZoneSpecifier("CLST", -3, 0),
// TimeZoneSpecifier("COT", -5, 0),
// TimeZoneSpecifier("COST", -4, 0),
// TimeZoneSpecifier("CVT", -1, 0),
// TimeZoneSpecifier("CXT", 7, 0),
// TimeZoneSpecifier("EAST", -6, 0),
// TimeZoneSpecifier("EAT", 3, 0),
// TimeZoneSpecifier("ECT", -4, 0),
// TimeZoneSpecifier("EEST", 3, 0),
// TimeZoneSpecifier("EET", 2, 0),
// TimeZoneSpecifier("FJT", 12, 0),
// TimeZoneSpecifier("FKST", -4, 0),
// TimeZoneSpecifier("GALT", -6, 0),
// TimeZoneSpecifier("GET", 4, 0),
// TimeZoneSpecifier("GFT", -3, 0),
// TimeZoneSpecifier("GILT", 7, 0),
// TimeZoneSpecifier("GIT", -9, 0),
// TimeZoneSpecifier("GST", -2, 0),
// TimeZoneSpecifier("GYT", -4, 0),
// TimeZoneSpecifier("HAST", -10, 0),
// TimeZoneSpecifier("HKT", 8, 0),
// TimeZoneSpecifier("HMT", 5, 0),
// TimeZoneSpecifier("IRKT", 8, 0),
// TimeZoneSpecifier("IRST", 3, 30),
// TimeZoneSpecifier("IST", 2, 0),
// TimeZoneSpecifier("JST", 9, 0),
// TimeZoneSpecifier("KRAT", 7, 0),
// TimeZoneSpecifier("KST", 9, 0),
// TimeZoneSpecifier("LHST", 10, 30),
// TimeZoneSpecifier("LINT", 14, 0),
// TimeZoneSpecifier("MAGT", 11, 0),
// TimeZoneSpecifier("MIT", -9, 30),
// TimeZoneSpecifier("MSK", 3, 0),
// TimeZoneSpecifier("MUT", 4, 0),
// TimeZoneSpecifier("NDT", -2, 30),
// TimeZoneSpecifier("NFT", 11, 30),
// TimeZoneSpecifier("NPT", 5, 45),
// TimeZoneSpecifier("NT", -3, 30),
// TimeZoneSpecifier("OMST", 6, 0),
// TimeZoneSpecifier("PETT", 12, 0),
// TimeZoneSpecifier("PHOT", 13, 0),
// TimeZoneSpecifier("PKT", 5, 0),
// TimeZoneSpecifier("RET", 4, 0),
// TimeZoneSpecifier("SAMT", 4, 0),
// TimeZoneSpecifier("SAST", 2, 0),
// TimeZoneSpecifier("SBT", 11, 0),
// TimeZoneSpecifier("SCT", 4, 0),
// TimeZoneSpecifier("SLT", 5, 30),
// TimeZoneSpecifier("SST", 8, 0),
// TimeZoneSpecifier("TAHT", -10, 0),
// TimeZoneSpecifier("THA", 7, 0),
// TimeZoneSpecifier("UYT", -3, 0),
// TimeZoneSpecifier("UYST", -2, 0),
// TimeZoneSpecifier("VET", -4, 30),
// TimeZoneSpecifier("VLAT", 10, 0),
// TimeZoneSpecifier("WAT", 1, 0),
// TimeZoneSpecifier("WET", 0, 0),
// TimeZoneSpecifier("WEST", 1, 0),
// TimeZoneSpecifier("YAKT", 9, 0),
// TimeZoneSpecifier("YEKT", 5, 0)
//]
//
//private enum Month: Int {
// case January = 1, February, March, April, May, June, July, August, September, October, November, December
//}
//
//private func nextMonthValue(bytes: String, startingIndex: Int, finalIndex: inout Int) -> Int? {
//
// // Months are 1-based -- January is 1, Dec is 12.
// // Lots of short-circuits here. Not strict. GIGO
//
// var i = startingIndex
// var numberOfBytes = bytes.count
// var numberOfAlphaCharactersFound = 0
// var monthCharacters = [Character]()
//
// while index < bytes.count {
//
//
// }
//
//
// var index = startingIndex
// var numberOfAlphaCharactersFound = 0
// var monthCharacters: [Character] = []
//
// while index < bytes.count {
// let character = bytes[bytes.index(bytes.startIndex, offsetBy: index)]
//
// if !character.isLetter, numberOfAlphaCharactersFound < 1 {
// index += 1
// continue
// }
// if !character.isLetter, numberOfAlphaCharactersFound > 0 {
// break
// }
//
// numberOfAlphaCharactersFound += 1
// if numberOfAlphaCharactersFound == 1 {
// switch character.lowercased() {
// case "f": return (.February.rawValue, index)
// case "s": return (.September.rawValue, index)
// case "o": return (.October.rawValue, index)
// case "n": return (.November.rawValue, index)
// case "d": return (.December.rawValue, index)
// default: break
// }
// }
//
// monthCharacters.append(character)
// if numberOfAlphaCharactersFound >= 3 {
// break
// }
// index += 1
// }
//
// if numberOfAlphaCharactersFound < 2 {
// return (nil, index)
// }
//
// if monthCharacters[0].lowercased() == "j" {
// if monthCharacters[1].lowercased() == "a" {
// return (.January.rawValue, index)
// }
// if monthCharacters[1].lowercased() == "u" {
// if monthCharacters.count > 2 && monthCharacters[2].lowercased() == "n" {
// return (.June.rawValue, index)
// }
// return (.July.rawValue, index)
// }
// return (.January.rawValue, index)
// }
//
// if monthCharacters[0].lowercased() == "m" {
// if monthCharacters.count > 2 && monthCharacters[2].lowercased() == "y" {
// return (.May.rawValue, index)
// }
// return (.March.rawValue, index)
// }
//
// if monthCharacters[0].lowercased() == "a" {
// if monthCharacters[1].lowercased() == "u" {
// return (.August.rawValue, index)
// }
// return (.April.rawValue, index)
// }
//
// return (.January.rawValue, index)
//}
//
//func nextNumericValue(bytes: String, startingIndex: Int, maximumNumberOfDigits: Int) -> (Int?, Int) {
// let digits = bytes.dropFirst(startingIndex).prefix(maximumNumberOfDigits)
// guard let value = Int(digits) else {
// return (nil, startingIndex)
// }
// return (value, startingIndex + digits.count)
//}
//
//func hasAtLeastOneAlphaCharacter(_ s: String) -> Bool {
// return s.contains { $0.isLetter }
//}
//
//func offsetInSeconds(forTimeZoneAbbreviation abbreviation: String) -> Int {
// for zone in timeZoneTable {
// if zone.abbreviation.caseInsensitiveCompare(abbreviation) == .orderedSame {
// if zone.offsetHours < 0 {
// return (zone.offsetHours * 3600) - (zone.offsetMinutes * 60)
// }
// return (zone.offsetHours * 3600) + (zone.offsetMinutes * 60)
// }
// }
// return 0
//}
//
//func offsetInSeconds(forOffsetCharacters timeZoneCharacters: String) -> Int {
// let isPlus = timeZoneCharacters.hasPrefix("+")
// let numericValue = timeZoneCharacters.filter { $0.isNumber || $0 == "-" }
// let (hours, finalIndex) = nextNumericValue(bytes: numericValue, startingIndex: 0, maximumNumberOfDigits: 2)
// let (minutes, _) = nextNumericValue(bytes: numericValue, startingIndex: finalIndex + 1, maximumNumberOfDigits: 2)
//
// let seconds = ((hours ?? 0) * 3600) + ((minutes ?? 0) * 60)
// return isPlus ? seconds : -seconds
//}
//
//func parsedTimeZoneOffset(bytes: String, startingIndex: Int) -> Int {
// var timeZoneCharacters: String = ""
// var numberOfCharactersFound = 0
// var i = startingIndex
//
// while i < bytes.count, numberOfCharactersFound < 5 {
// let character = bytes[bytes.index(bytes.startIndex, offsetBy: i)]
// if character != ":" && character != " " {
// timeZoneCharacters.append(character)
// numberOfCharactersFound += 1
// }
// i += 1
// }
//
// if numberOfCharactersFound < 1 || timeZoneCharacters.lowercased() == "z" {
// return 0
// }
//
// if timeZoneCharacters.range(of: "GMT", options: .caseInsensitive) != nil ||
// timeZoneCharacters.range(of: "UTC", options: .caseInsensitive) != nil {
// return 0
// }
//
// if hasAtLeastOneAlphaCharacter(timeZoneCharacters) {
// return offsetInSeconds(forTimeZoneAbbreviation: timeZoneCharacters)
// }
// return offsetInSeconds(forOffsetCharacters: timeZoneCharacters)
//}
//
//func dateWithYearMonthDayHourMinuteSecondAndTimeZoneOffset(
// year: Int, month: Int, day: Int,
// hour: Int, minute: Int, second: Int,
// milliseconds: Int, timeZoneOffset: Int) -> Date? {
//
// var dateComponents = DateComponents()
// dateComponents.year = year
// dateComponents.month = month
// dateComponents.day = day
// dateComponents.hour = hour
// dateComponents.minute = minute
// dateComponents.second = second
// dateComponents.timeZone = TimeZone(secondsFromGMT: timeZoneOffset)
//
// let calendar = Calendar.current
// return calendar.date(from: dateComponents)
//}
//
//func parsePubDate(bytes: String) -> Date? {
// let (day, finalIndex) = nextNumericValue(bytes: bytes, startingIndex: 0, maximumNumberOfDigits: 2)
// let (month, finalIndex2) = nextMonthValue(bytes: bytes, startingIndex: finalIndex + 1)
// let (year, finalIndex3) = nextNumericValue(bytes: bytes, startingIndex: finalIndex2 + 1, maximumNumberOfDigits: 4)
// let (hour, finalIndex4) = nextNumericValue(bytes: bytes, startingIndex: finalIndex3 + 1, maximumNumberOfDigits: 2)
// let (minute, finalIndex5) = nextNumericValue(bytes: bytes, startingIndex: finalIndex4 + 1, maximumNumberOfDigits: 2)
//
// var second = 0
// let currentIndex = finalIndex5 + 1
// if currentIndex < bytes.count, bytes[bytes.index(bytes.startIndex, offsetBy: currentIndex)] == ":" {
// second = nextNumericValue(bytes: bytes, startingIndex: currentIndex, maximumNumberOfDigits: 2).0 ?? 0
// }
//
// let timeZoneOffset = parsedTimeZoneOffset(bytes: bytes, startingIndex: currentIndex + 1)
//
// return dateWithYearMonthDayHourMinuteSecondAndTimeZoneOffset(
// year: year ?? 1970,
// month: month ?? RSMonth.January.rawValue,
// day: day ?? 1,
// hour: hour ?? 0,
// minute: minute ?? 0,
// second: second,
// milliseconds: 0,
// timeZoneOffset: timeZoneOffset
// )
//}
//
//func parseW3C(bytes: String) -> Date? {
// let (year, finalIndex) = nextNumericValue(bytes: bytes, startingIndex: 0, maximumNumberOfDigits: 4)
// let (month, finalIndex2) = nextNumericValue(bytes: bytes, startingIndex: finalIndex + 1, maximumNumberOfDigits: 2)
// let (day, finalIndex3) = nextNumericValue(bytes: bytes, startingIndex: finalIndex2 + 1, maximumNumberOfDigits: 2)
// let (hour, finalIndex4) = nextNumericValue(bytes: bytes, startingIndex: finalIndex3 + 1, maximumNumberOfDigits: 2)
// let (minute, finalIndex5) = nextNumericValue(bytes: bytes, startingIndex: finalIndex4 + 1, maximumNumberOfDigits: 2)
// let (second, finalIndex6) = nextNumericValue(bytes: bytes, startingIndex: finalIndex5 + 1, maximumNumberOfDigits: 2)
//
// var milliseconds = 0
// let currentIndex = finalIndex6 + 1
// if currentIndex < bytes.count, bytes[bytes.index(bytes.startIndex, offsetBy: currentIndex)] == "." {
// milliseconds = nextNumericValue(bytes: bytes, startingIndex: currentIndex + 1, maximumNumberOfDigits: 3).0 ?? 0
// }
//
// let timeZoneOffset = parsedTimeZoneOffset(bytes: bytes, startingIndex: currentIndex + 1)
//
// return dateWithYearMonthDayHourMinuteSecondAndTimeZoneOffset(
// year: year ?? 1970,
// month: month ?? RSMonth.January.rawValue,
// day: day ?? 1,
// hour: hour ?? 0,
// minute: minute ?? 0,
// second: second ?? 0,
// milliseconds: milliseconds,
// timeZoneOffset: timeZoneOffset
// )
//}
//
//func dateWithBytes(bytes: String) -> Date? {
// guard !bytes.isEmpty else { return nil }
//
// if bytes.range(of: "-") != nil {
// return parseW3C(bytes: bytes)
// }
// return parsePubDate(bytes: bytes)
//}