You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

329 lines
12 KiB

// Copyright © 2015 Abhishek Banthia
import Cocoa
import CoreLoggerKit
struct ModelConstants {
static let customLabel = "customLabel"
static let timezoneName = "formattedAddress"
static let placeIdentifier = "place_id"
static let timezoneID = "timezoneID"
static let emptyString = ""
static let latitude = "latitude"
static let longitude = "longitude"
static let note = "note"
}
public enum DateFormat {
public static let twelveHour = "h:mm a"
public static let twelveHourWithSeconds = "h:mm:ss a"
public static let twentyFourHour = "HH:mm"
public static let twentyFourHourWithSeconds = "HH:mm:ss"
public static let twelveHourWithZero = "hh:mm a"
public static let twelveHourWithZeroSeconds = "hh:mm:ss a"
public static let twelveHourWithoutSuffix = "hh:mm"
public static let twelveHourWithoutSuffixAndSeconds = "hh:mm:ss"
public static let epochTime = "epoch"
}
// Non-class type cannot conform to NSCoding!
public class TimezoneData: NSObject, NSCoding, NSSecureCoding {
public static var supportsSecureCoding: Bool {
return true
}
public enum SelectionType: Int {
case city
case timezone
}
public enum DateDisplayType: Int {
case panel
case menu
}
public enum TimezoneOverride: Int {
case globalFormat = 0
case twelveHourFormat = 1
case twentyFourFormat = 2
case twelveHourWithSeconds = 4
case twentyHourWithSeconds = 5
case twelveHourPrecedingZero = 7
case twelveHourPrecedingZeroSeconds = 8
case twelveHourWithoutSuffix = 10
case twelveHourWithoutSuffixAndSeconds = 11
case epochTime = 12
}
static let values = [
NSNumber(integerLiteral: 0): DateFormat.twelveHour,
NSNumber(integerLiteral: 1): DateFormat.twentyFourHour,
// Seconds
NSNumber(integerLiteral: 3): DateFormat.twelveHourWithSeconds,
NSNumber(integerLiteral: 4): DateFormat.twentyFourHourWithSeconds,
// Preceding Zero
NSNumber(integerLiteral: 6): DateFormat.twelveHourWithZero,
NSNumber(integerLiteral: 7): DateFormat.twelveHourWithZeroSeconds,
// Suffix
NSNumber(integerLiteral: 9): DateFormat.twelveHourWithoutSuffix,
NSNumber(integerLiteral: 10): DateFormat.twelveHourWithoutSuffixAndSeconds,
NSNumber(integerLiteral: 11): DateFormat.epochTime,
]
public var customLabel: String?
public var formattedAddress: String?
public var placeID: String?
public var timezoneID: String? = ModelConstants.emptyString
public var latitude: Double?
public var longitude: Double?
public var note: String? = ModelConstants.emptyString
public var nextUpdate: Date? = Date()
public var sunriseTime: Date?
public var sunsetTime: Date?
public var isFavourite: Int = 0
public var isSunriseOrSunset = false
public var selectionType: SelectionType = .city
public var isSystemTimezone = false
public var overrideFormat: TimezoneOverride = .globalFormat
override public init() {
selectionType = .timezone
isFavourite = 0
note = ModelConstants.emptyString
isSystemTimezone = false
overrideFormat = .globalFormat
placeID = UUID().uuidString
}
public init(with dictionary: [String: Any]) {
customLabel = dictionary[ModelConstants.customLabel] as? String
timezoneID = (dictionary[ModelConstants.timezoneID] as? String) ?? "Error"
latitude = dictionary[ModelConstants.latitude] as? Double ?? -0.0
longitude = dictionary[ModelConstants.longitude] as? Double ?? -0.0
placeID = (dictionary[ModelConstants.placeIdentifier] as? String) ?? "Error"
formattedAddress = (dictionary[ModelConstants.timezoneName] as? String) ?? "Error"
isFavourite = 0
selectionType = .city
note = (dictionary[ModelConstants.note] as? String) ?? ModelConstants.emptyString
isSystemTimezone = false
overrideFormat = .globalFormat
}
public required init?(coder aDecoder: NSCoder) {
customLabel = aDecoder.decodeObject(forKey: "customLabel") as? String
formattedAddress = aDecoder.decodeObject(forKey: "formattedAddress") as? String
placeID = aDecoder.decodeObject(forKey: "place_id") as? String
timezoneID = aDecoder.decodeObject(forKey: "timezoneID") as? String
latitude = aDecoder.decodeObject(forKey: "latitude") as? Double
longitude = aDecoder.decodeObject(forKey: "longitude") as? Double
note = aDecoder.decodeObject(forKey: "note") as? String
nextUpdate = aDecoder.decodeObject(forKey: "nextUpdate") as? Date
sunriseTime = aDecoder.decodeObject(forKey: "sunriseTime") as? Date
sunsetTime = aDecoder.decodeObject(forKey: "sunsetTime") as? Date
isFavourite = aDecoder.decodeInteger(forKey: "isFavourite")
let selection = aDecoder.decodeInteger(forKey: "selectionType")
selectionType = SelectionType(rawValue: selection)!
isSystemTimezone = aDecoder.decodeBool(forKey: "isSystemTimezone")
let override = aDecoder.decodeInteger(forKey: "overrideFormat")
overrideFormat = TimezoneOverride(rawValue: override)!
}
public class func customObject(from encodedData: Data?) -> TimezoneData? {
guard let dataObject = encodedData else {
return TimezoneData()
}
if let timezoneObject = NSKeyedUnarchiver.unarchiveObject(with: dataObject) as? TimezoneData {
return timezoneObject
}
return nil
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(placeID, forKey: "place_id")
aCoder.encode(formattedAddress, forKey: "formattedAddress")
aCoder.encode(customLabel, forKey: "customLabel")
aCoder.encode(timezoneID, forKey: "timezoneID")
aCoder.encode(nextUpdate, forKey: "nextUpdate")
aCoder.encode(latitude, forKey: "latitude")
aCoder.encode(longitude, forKey: "longitude")
aCoder.encode(isFavourite, forKey: "isFavourite")
aCoder.encode(sunriseTime, forKey: "sunriseTime")
aCoder.encode(sunsetTime, forKey: "sunsetTime")
aCoder.encode(selectionType.rawValue, forKey: "selectionType")
aCoder.encode(note, forKey: "note")
aCoder.encode(isSystemTimezone, forKey: "isSystemTimezone")
aCoder.encode(overrideFormat.rawValue, forKey: "overrideFormat")
}
public func formattedTimezoneLabel() -> String {
// First check if there's an user preferred custom label set
if let label = customLabel, !label.isEmpty {
return label
}
// No custom label, return the formatted address/timezone
if let address = formattedAddress, !address.isEmpty {
return address
}
// No formatted address, return the timezoneID
if let timezone = timezoneID, !timezone.isEmpty {
let hashSeperatedString = timezone.components(separatedBy: "/")
// Return the second component!
if let first = hashSeperatedString.first {
return first
}
// Second component not available, return the whole thing!
return timezone
}
// Return error
return "Error"
}
public func setLabel(_ label: String) {
customLabel = !label.isEmpty ? label : ModelConstants.emptyString
}
public func setShouldOverrideGlobalTimeFormat(_ shouldOverride: Int) {
if shouldOverride == 0 {
overrideFormat = .globalFormat
} else if shouldOverride == 1 {
overrideFormat = .twelveHourFormat
} else if shouldOverride == 2 {
overrideFormat = .twentyFourFormat
} else if shouldOverride == 4 {
overrideFormat = .twelveHourWithSeconds
} else if shouldOverride == 5 {
overrideFormat = .twentyHourWithSeconds
} else if shouldOverride == 7 {
overrideFormat = .twelveHourPrecedingZero
} else if shouldOverride == 8 {
overrideFormat = .twelveHourPrecedingZeroSeconds
} else if shouldOverride == 10 {
overrideFormat = .twelveHourWithoutSuffix
} else if shouldOverride == 11 {
overrideFormat = .twelveHourWithoutSuffixAndSeconds
} else if shouldOverride == 12 {
overrideFormat = .epochTime
} else {
Logger.info("Chosen a wrong timezone format: \(shouldOverride)")
}
}
public func timezone() -> String {
if isSystemTimezone {
timezoneID = TimeZone.autoupdatingCurrent.identifier
formattedAddress = TimeZone.autoupdatingCurrent.identifier
return TimeZone.autoupdatingCurrent.identifier
}
if let timezone = timezoneID {
return timezone
}
return TimeZone.autoupdatingCurrent.identifier
}
public func timezoneFormat(_ currentFormat: NSNumber) -> String {
let chosenDefault = currentFormat
let timeFormat = TimezoneData.values[chosenDefault] ?? DateFormat.twelveHour
switch overrideFormat {
case .globalFormat:
return timeFormat
case .twelveHourFormat:
return DateFormat.twelveHour
case .twentyFourFormat:
return DateFormat.twentyFourHour
case .twelveHourWithSeconds:
return DateFormat.twelveHourWithSeconds
case .twentyHourWithSeconds:
return DateFormat.twentyFourHourWithSeconds
case .twelveHourPrecedingZero:
return DateFormat.twelveHourWithZero
case .twelveHourPrecedingZeroSeconds:
return DateFormat.twelveHourWithZeroSeconds
case .twelveHourWithoutSuffix:
return DateFormat.twelveHourWithoutSuffix
case .twelveHourWithoutSuffixAndSeconds:
return DateFormat.twelveHourWithoutSuffixAndSeconds
case .epochTime:
return DateFormat.epochTime
}
}
public func shouldShowSeconds(_ currentFormat: NSNumber) -> Bool {
if overrideFormat == .globalFormat {
let formatInString = TimezoneData.values[currentFormat] ?? DateFormat.twelveHour
return formatInString.contains("ss")
}
// We subtract 1 because the timezone format in the dropdown contains 1 extra row for "Respecting global preferences"
let key = NSNumber(integerLiteral: overrideFormat.rawValue - 1)
let formatInString = TimezoneData.values[key] ?? DateFormat.twelveHour
return formatInString.contains("ss")
}
public func isDaylightSavings() -> Bool {
guard let timezone = TimeZone(abbreviation: timezone()) else {
return false
}
return timezone.isDaylightSavingTime(for: Date())
}
override public var hash: Int {
guard let placeIdentifier = placeID, let timezone = timezoneID else {
return -1
}
return placeIdentifier.hashValue ^ timezone.hashValue
}
override public func isEqual(_ object: Any?) -> Bool {
guard let compared = object as? TimezoneData else {
return false
}
// Plain timezones might have similar placeID. Adding another check for timezone identifier.
return placeID == compared.placeID && timezoneID == compared.timezoneID
}
}
public extension TimezoneData {
override var description: String {
return objectDescription()
}
override var debugDescription: String {
return objectDescription()
}
private func objectDescription() -> String {
let customString = """
TimezoneID: \(String(describing: timezoneID))
Formatted Address: \(formattedAddress ?? "Error")
Custom Label: \(customLabel ?? "Error")
Latitude: \(latitude ?? -0.0)
Longitude: \(longitude ?? -0.0)
Place Identifier: \(String(describing: placeID))
Is Favourite: \(isFavourite)
Sunrise Time: \(String(describing: sunriseTime))
Sunset Time: \(String(describing: sunsetTime))
Selection Type: \(selectionType.rawValue)
Note: \(String(describing: note))
Is System Timezone: \(isSystemTimezone)
Override: \(overrideFormat)
"""
return customString
}
}