Search code examples
swiftcore-datacalendarstoragefoundation

Is there a way to store Set<Calendar.Component>?


I have a pretty old project on Swift that still uses NSCalendar.Unit, and it's pretty convenient because its rawValue is a UInt that can be stored in my Core Data database. I want to move to newer Swift APIs, so I have to use Set<Calendar.Component>, but it seems like Calendar.Component is just a Swift enum without a rawValue. I have found that it has a hashValue, but Apple does not recommend to save it, and also Calendar.Component does not conform to Encodable. So is there any way to store Set<Calendar.Component> in Core Data or am I stuck with old NSCalendar.Unit?

enter image description here


Solution

  • You can make Calendar.Component conform to RawRepresentable and hence have a rawValue and then store it as the RawValue. Or you can also make it conform to Codable and then store it as Data.

    extension Calendar.Component: RawRepresentable {
        public var rawValue: Int {
            switch self {
            case .calendar:
                return 0
            case .day:
                return 1
            case .era:
                return 2
            case .hour:
                return 3
            case .minute:
                return 4
            case .month:
                return 5
            case .nanosecond:
                return 6
            case .quarter:
                return 7
            case .second:
                return 8
            case .timeZone:
                return 9
            case .weekday:
                return 10
            case .weekdayOrdinal:
                return 11
            case .weekOfMonth:
                return 12
            case .weekOfYear:
                return 13
            case .year:
                return 14
            case .yearForWeekOfYear:
                return 15
            }
        }
    
        public init?(rawValue: Int) {
            switch rawValue {
            case 0:
                self = .calendar
            case 1:
                self = .day
            case 2:
                self = .era
            case 3:
                self = .hour
            case 4:
                self = .minute
            case 5:
                self = .month
            case 6:
                self = .nanosecond
            case 7:
                self = .quarter
            case 8:
                self = .second
            case 9:
                self = .timeZone
            case 10:
                self = .weekday
            case 11:
                self = .weekdayOrdinal
            case 12:
                self = .weekOfMonth
            case 13:
                self = .weekOfYear
            case 14:
                self = .year
            case 15:
                self = .yearForWeekOfYear
            default:
                return nil
            }
        }
    }
    

    Codable conformance isn't necessary, RawRepresentable should be enough, but leaving it here as a reference.

    extension Calendar.Component: Codable {
        enum DecodingError: Error {
            case unknownRawValue
        }
    
        /// Throwable initialiser that throws when an unknown `RawValue` is passed to it
        /// Necessary for `init(from decoder:)` to be able to delegate to a non-failable init
        init(value: Int) throws {
            guard let component = Calendar.Component(rawValue: value) else {
                throw DecodingError.unknownRawValue
            }
            self = component
        }
    
        public init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let rawValue = try container.decode(Int.self)
            try self.init(value: rawValue)
        }
    
        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encode(self.rawValue)
        }
    }