Files
divkit/client/ios/DivKit/Expressions/Functions/DatetimeFunctions.swift
T
dashapalshau 17aa3c74ff Fix func nowLocal() to return date in local time zone
Fix func nowLocal() to return date in local time zone
2022-12-28 18:48:45 +03:00

199 lines
6.0 KiB
Swift

import Foundation
import CommonCore
enum DatetimeFunctions: String, CaseIterable {
enum Error {
case month(Date, Int)
case day(Date, Int)
case hours(Date, Int)
case minutes(Date, Int)
case seconds(Date, Int)
case millis(Date, Int)
var message: AnyCalcExpression.Error {
AnyCalcExpression.Error.message(description)
}
private var description: String {
switch self {
case let .month(date, value):
return "Failed to evaluate [setMonth(\(date.formatString), \(value))]. Expecting month in [1..12], instead got \(value)."
case let .day(date, value):
return "Failed to evaluate [setDay(\(date.formatString), \(value))]. Unable to set day \(value) for date \(date.formatString)."
case let .hours(date, value):
return "Failed to evaluate [setHours(\(date.formatString), \(value))]. Expecting hours in [0..23], instead got \(value)."
case let .minutes(date, value):
return "Failed to evaluate [setMinutes(\(date.formatString), \(value))]. Expecting minutes in [0..59], instead got \(value)."
case let .seconds(date, value):
return "Failed to evaluate [setSeconds(\(date.formatString), \(value))]. Expecting seconds in [0..59], instead got \(value)."
case let .millis(date, value):
return "Failed to evaluate [setMillis(\(date.formatString), \(value))]. Expecting millis in [0..999], instead got \(value)."
}
}
}
case parseUnixTime
case nowLocal
case addMillis
case setYear
case setMonth
case setDay
case setHours
case setMinutes
case setSeconds
case setMillis
var declaration: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] {
[.function(rawValue, arity: function.arity): function.symbolEvaluator]
}
var function: Function {
switch self {
case .parseUnixTime:
return FunctionUnary(impl: _parseUnixTime)
case .nowLocal:
return FunctionNullary(impl: _nowLocal)
case .addMillis:
return FunctionBinary(impl: _addMillis)
case .setYear:
return FunctionBinary(impl: _setYear)
case .setMonth:
return FunctionBinary(impl: _setMonth)
case .setDay:
return FunctionBinary(impl: _setDay)
case .setHours:
return FunctionBinary(impl: _setHours)
case .setMinutes:
return FunctionBinary(impl: _setMinutes)
case .setSeconds:
return FunctionBinary(impl: _setSeconds)
case .setMillis:
return FunctionBinary(impl: _setMillis)
}
}
}
private func _parseUnixTime(_ value: Int) -> Date {
Date(timeIntervalSince1970: Double(value))
}
private func _nowLocal() -> Date {
let dateUTC = Date()
let timeZoneOffset = Double(TimeZone.current.secondsFromGMT(for: dateUTC))
guard let localDate = Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: dateUTC) else {
return dateUTC
}
return localDate
}
private func _addMillis(_ date: Date, _ offsetInMillis: Int) -> Date {
let newTimeInterval = date.timeIntervalSince1970 + offsetInMillis.toSeconds
return Date(timeIntervalSince1970: newTimeInterval)
}
private func _setYear(date: Date, newYear: Int) -> Date {
var components = date.components
components.year = newYear
return calendar.date(from: components)!
}
private func _setMonth(date: Date, newMonth: Int) throws -> Date {
guard newMonth >= 1,
newMonth <= 12 else { throw DatetimeFunctions.Error.month(date, newMonth).message }
var components = date.components
components.month = newMonth
return calendar.date(from: components)!
}
private func _setDay(date: Date, newDay: Int) throws -> Date {
let range = calendar.range(of: .day, in: .month, for: date)!
var components = date.components
switch newDay {
case range:
components.day = newDay
case -1:
components.day = 0
default:
throw DatetimeFunctions.Error.day(date, newDay).message
}
return calendar.date(from: components)!
}
private func _setHours(date: Date, newHour: Int) throws -> Date {
guard newHour >= 0,
newHour <= 23 else { throw DatetimeFunctions.Error.hours(date, newHour).message }
var components = date.components
components.hour = newHour
return calendar.date(from: components)!
}
private func _setMinutes(date: Date, newMinutes: Int) throws -> Date {
guard newMinutes >= 0,
newMinutes <= 59 else { throw DatetimeFunctions.Error.minutes(date, newMinutes).message }
var components = date.components
components.minute = newMinutes
return calendar.date(from: components)!
}
private func _setSeconds(date: Date, newSeconds: Int) throws -> Date {
guard newSeconds >= 0,
newSeconds <= 59 else { throw DatetimeFunctions.Error.seconds(date, newSeconds).message }
var components = date.components
components.second = newSeconds
return calendar.date(from: components)!
}
private func _setMillis(date: Date, newMillis: Int) throws -> Date {
guard newMillis >= 0,
newMillis <= 999 else { throw DatetimeFunctions.Error.millis(date, newMillis).message }
let newTimeInterval = round(date.timeIntervalSince1970) + newMillis.toSeconds
return Date(timeIntervalSince1970: newTimeInterval)
}
private let dateFormat = "yyyy-MM-dd HH:mm:ss"
private let timeZone = "UTC"
extension String {
func toDatetime() -> Date? {
dateFormatter.date(from: self)
}
}
extension Date {
var formatString: String {
dateFormatter.string(from: self)
}
fileprivate var components: DateComponents {
calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)
}
}
extension Int {
fileprivate var toSeconds: TimeInterval {
Double(self) / 1000
}
}
private let calendar: Calendar = {
var calendar = Calendar.current
calendar.timeZone = TimeZone(abbreviation: timeZone)!
return calendar
}()
private let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
dateFormatter.timeZone = TimeZone(abbreviation: timeZone)
return dateFormatter
}()
private func castToDate(_ value: Any) -> Date? {
value as? Date
}
private func castToInt(_ value: Any) -> Int? {
value as? Int
}