NCrusher74 / SwiftTaggerID3

A Swift library for reading and editing ID3 tags
Apache License 2.0
5 stars 2 forks source link

working on the date frame #7

Closed NCrusher74 closed 4 years ago

NCrusher74 commented 4 years ago

Okay, I could use some guidance on this, because I feel like I'm overthinking it or making it more complicated than it needs to be.

I'm starting off with a variation on the Date extension you created for AudiobookTagger:

extension Date {

    internal init?(id3TimeStamp: (year: Int?, month: Int?, day: Int?, hour: Int?, minute: Int?)) {
        if let date = DateComponents(
            calendar: Calendar(identifier: .iso8601),
            timeZone: TimeZone(secondsFromGMT: 0),
            year: id3TimeStamp.year,
            month: id3TimeStamp.month,
            day: id3TimeStamp.day,
            hour: id3TimeStamp.hour,
            minute: id3TimeStamp.minute
        ).date {
            self = date
        } else {
            return nil
        }
    }

    internal var id3TimeStamp: (year: Int?, month: Int?, day: Int?, hour: Int?, minute: Int?) {
        let components = Calendar(identifier: .iso8601)
            .dateComponents(
                in: TimeZone(secondsFromGMT: 0)!,
                from: self)
        return (year: components.year!,
                month: components.month!,
                day: components.day!,
                components.hour!,
                components.minute!)
    }
}

According to the specs, a date in an ID3 tag is supposed to be in ISO-8601 format, so I changed the calendar to that, and added the hour/minute ints, because with the exception of the v2.2/2.3 time/date frames, they're supposed to be in there.

I also created these formatters for DateFormatter, though I haven't found any use for them yet. Again, this is going off what the spec calls for.

extension DateFormatter {
    // This is the format used in the ID3 "Date" frame
    static let id3DayMonth: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd-MM"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

    // This is the format used in the ID3 "Time" frame
    static let id3HourMinute: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH-mm"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

    // This is the format used in the ID3 "Year" frame
    static let id3Year: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

    // this is the format used in all other ID3 date-related frames
    static let id3TimeStamp: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

My properties for the DateFrame are where I'm probably making things more complicated than they need to be. My initial thinking was that if I can parse whatever is coming in from a frame as a string into a timestamp, I can then use that to initialize all the individual components:

    var year: Int?
    var month: Int?
    var day: Int?
    var hour: Int?
    var minute: Int?
    var timeStamp: Date
    var timeStampString: String?

Where things start breaking down is in the parsing, which I don't think I've implemented properly:

        // assuming the timestamp is in ISO-8601 format as per the ID3 spec
        self.timeStampString = parsing.extractPrefixAsStringUntilNullTermination(encoding) ?? ""
        let formatter = DateFormatter()
        let formattedTimeStamp = formatter.date(from: timeStampString ?? "")?.id3TimeStamp ?? (year: 0000, month: 00, day: 00, hour: 00, minute: 00)
        self.timeStamp = Date(id3TimeStamp: formattedTimeStamp) ?? Date()

        self.year = timeStamp.id3TimeStamp.year
        self.month = timeStamp.id3TimeStamp.month
        self.day = timeStamp.id3TimeStamp.day
        self.hour = timeStamp.id3TimeStamp.hour
        self.minute = timeStamp.id3TimeStamp.minute

And then the individual getter/setter properties can use whichever variables they need for a particular frame:

    /// - (Release) Date frame getter-setter. Valid for versions 2.2 and 2.3 only.
    /// ID3 Identifier: `TDA`/`TDAT`
    public var date: (day: Int?, month: Int?)? {
        get {
            if let frame = self.frames[.date],
                case .dateFrame(let dateFrame) = frame {
                return (dateFrame.day, dateFrame.month)
            } else {
                return nil
            }
        }
        set {
            let date = Date(id3TimeStamp: (year: nil, month: newValue?.month, day: newValue?.day, hour: nil, minute: nil)) ?? Date()
            let frame = DateFrame(.known(.date), timeStamp: date)
            frames[.date] = .dateFrame(frame)
        }
    }

But yeah, like I said, I don't think I'm implementing it properly.