open-wether / Sources /App /Helper /Time /IsoDateTime.swift
soiz1's picture
Migrated from GitHub
6ee917b verified
import Foundation
/**
Simple year, month, day container which is decoded to iso dates `2022-01-01T00:00:00`
*/
public struct IsoDateTime {
/// Encoded as integer `20220101235959`
public let date: Int
public init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) {
date = year * 10000000000 + month * 100000000 + day * 1000000 + hour * 10000 + minute * 100 + second
}
/// year like 2022
public var year: Int {
date / 10000000000
}
/// month from 1 to 12
public var month: Int {
date / 100000000 % 100
}
/// day from 1 to 31
public var day: Int {
date / 1000000 % 100
}
/// hour from 0 to 23
public var hour: Int {
date / 10000 % 100
}
/// minute from 0 to 59
public var minute: Int {
date / 100 % 100
}
/// second from 0 to 59
public var second: Int {
date % 100
}
/// convert to unix timestamp
public func toTimestamp() -> Timestamp {
Timestamp(year, month, day, hour, minute, second)
}
/// Convert to strideable `YearMonth`
public func toYearMonth() -> YearMonth {
return YearMonth(year: year, month: month)
}
/*public init(from decoder: Decoder) throws {
let str = try decoder.singleValueContainer().decode(String.self)
try self.init(fromIsoString: str)
}*/
/// To iso string like `2022-12-23T00:00:00`
public func toIsoString() -> String {
return "\(year)-\(month.zeroPadded(len: 2))-\(day.zeroPadded(len: 2))T\(hour.zeroPadded(len: 2)):\(minute.zeroPadded(len: 2)):\(second.zeroPadded(len: 2))"
}
/// Init form unxtimestamp
public init(timeIntervalSince1970: Int) {
var time = timeIntervalSince1970
var t = tm()
gmtime_r(&time, &t)
// day of year = Int(t.tm_yday+1)
// day of week = Int(t.tm_wday)
self.init(year: Int(t.tm_year+1900), month: Int(t.tm_mon+1), day: Int(t.tm_mday), hour: Int(t.tm_hour), minute: Int(t.tm_min), second: Int(t.tm_sec))
}
/// Decode from `2022-12-23` or `2022-12-23T00:00` or `2022-12-23T00:00:00`
public init(fromIsoString str: String) throws {
guard str.count >= 10, str.count <= 19, str[4..<5] == "-", str[7..<8] == "-" else {
throw TimeError.InvalidDateFromat
}
guard let year = Int(str[0..<4]), let month = Int(str[5..<7]), let day = Int(str[8..<10]) else {
throw TimeError.InvalidDateFromat
}
guard year >= 1900, year <= 2050,
month >= 1, month <= 12,
day >= 1, day <= 31
else {
throw TimeError.InvalidDate
}
if str.count <= 10 {
self.init(year: year, month: month, day: day, hour: 0, minute: 0, second: 0)
return
}
guard str.count >= 13, str[10..<11] == "T", let hour = Int(str[11..<13]) else {
throw TimeError.InvalidDateFromat
}
guard hour >= 0, hour <= 23 else {
throw TimeError.InvalidDate
}
if str.count <= 13 {
self.init(year: year, month: month, day: day, hour: hour, minute: 0, second: 0)
return
}
guard str.count >= 16, str[13..<14] == ":", let minute = Int(str[14..<16]) else {
throw TimeError.InvalidDateFromat
}
guard minute >= 0, minute <= 59 else {
throw TimeError.InvalidDate
}
if str.count <= 16 {
self.init(year: year, month: month, day: day, hour: hour, minute: minute, second: 0)
return
}
guard str.count >= 19, str[16..<17] == ":", let second = Int(str[17..<19]) else {
throw TimeError.InvalidDateFromat
}
guard second >= 0, second <= 59 else {
throw TimeError.InvalidDate
}
self.init(year: year, month: month, day: day, hour: hour, minute: minute, second: second)
}
}
extension IsoDateTime {
static func load(commaSeparated: [String]) throws -> [IsoDateTime] {
try commaSeparated.flatMap { s in
try s.split(separator: ",").map { date in
return try IsoDateTime.init(fromIsoString: String(date))
}
}
}
static func loadRange(start: [String], end: [String]) throws -> [ClosedRange<Timestamp>] {
if start.isEmpty, end.isEmpty {
return []
}
let startDate = try load(commaSeparated: start)
let endDate = try load(commaSeparated: end)
guard startDate.count == endDate.count else {
throw ForecastapiError.startAndEndDateCountMustBeTheSame
}
return try zip(startDate, endDate).map { (startDate, endDate) in
let start = startDate.toTimestamp()
let includedEnd = endDate.toTimestamp()
guard includedEnd.timeIntervalSince1970 >= start.timeIntervalSince1970 else {
throw ForecastapiError.enddateMustBeLargerEqualsThanStartdate
}
return start...includedEnd
}
}
}