100 Days of Code Day 13 - Date Formatting Disaster!

|

I Caught a Big Bug

Today I upgraded the calendar library I use in Moodsmitten to its master branch. Previously, I was using a random branch that supported Swift 3, but it’s since been merged. There were a couple of big changes with methods, but it didn’t seem like a big deal. Except… it was!

Turns out, I overlooked one of the most important things about calendars, and it seemed like the past version of JTAppleCalendar, Swift 2.1, or my non-refactored date methods hid my logic errors. I neglected dealing with timezone in DateFormatter. Trying to use my newly refactored date class resulted in days that were a day or more before the expected result. I fixed this up, setting all dates to GMT +0000, but there’s a huge problem. When I try running 1.0 and upgrade to 1.1, all of the data disappears, because the Dates in the calendar are different from the new GMT +0000 dates!

I always knew that migrations were a fact of life, but until Moodsmitten I never had to deal with preserving data integrity in an upgrade. 1.1 has to migrate Core Data’s persistent store to a shared location in order to have the today extension and the watchOS app. I made a DataMigrationDelegate and set it to a new DataMigrator class. The lone delegate method is called after a 1.0->1.1 migration has occurred In the delegate it was a matter of making a little loop writing to Core Data:

func migrationCompleted() {
    guard let days = dayService.getAllDays() else {
      print("can't get days")
     return
    }
    days.forEach {
       day in
         guard let date = day.date else {
         return
      }
     day.date? = DateLib.sanitizeDate(date) // set up a date with the proper timezone
      coreDataStack.saveContext(managedObjectContext)
     }
  }

I’m Lucky

I’m very lucky to have caught this bug and averted a v1.1 train wreck wrought with all past thought records from v1.0 disappearing. It took an entire afternoon to figure this out, but I’m a more experienced developer because of it.

UPDATE 2016-10-22

I decided to do the date calculations at run-time when the calendar is shown. I don’t want to touch the data, especially when I consider CloudKit syncing. It’s a recipe for disaster. I’m not thrilled with this solution because it’s slightly expensive, but it’s safer.

func calendar(_ calendar: JTAppleCalendarView, willDisplayCell cell: JTAppleDayCellView, date: Date, cellState: CellState) {

        var myCalendar = Calendar(identifier: .gregorian)
        myCalendar.timeZone = NSTimeZone.local
        var mood = -999
        var days = dayService.days
        if let index = days.index(where: {
        let argDay = myCalendar.component(.day, from: $0.date!)
        let argMonth = myCalendar.component(.month, from: $0.date!)
        let argYear = myCalendar.component(.year, from: $0.date!)
        let dateDay = myCalendar.component(.day, from: date)
        let dateMonth = myCalendar.component(.month, from: date)
        let dateYear = myCalendar.component(.year, from: date)

        return argDay == dateDay && argMonth == dateMonth && argYear == dateYear }) {
        mood = Int(days[index].mood!)
        }

        (cell as! CalendarCell).setupCellBeforeDisplay(cellState, date: date, mood: mood, selectedDate: selectedDate!)

    }

UPDATE 2016-10-27

Making that date-checking loop freezes the calendar for a couple seconds, which is unacceptable. Today I figured out that making Core Data queries is much faster and there is no lag. This is probably because Core Data is using really performant faulting.

func calendar(_ calendar: JTAppleCalendarView, willDisplayCell cell: JTAppleDayCellView, date: Date, cellState: CellState) {

            var myCalendar = Calendar(identifier: .gregorian)
            myCalendar.timeZone = NSTimeZone.local
            var mood = -999

            if let day = dayService.getDay(date) {
                mood = Int(day.mood!)
            }

            (cell as! CalendarCell).setupCellBeforeDisplay(cellState, date: date, mood: mood, selectedDate: selectedDate!)

        }

Support Moodsmitten

Did I mention that Moodsmitten 1.0 is already on the App Store? It’s free-to-download with a $4.99 in-app purchase to unlock all functionality. If you’re into CBT or need a place to challenge your automatic thoughts, look no further!

Did you like this post? Are you smitten for Soundsmitten? Click the "Tweet" link below to retweet my post. It keeps my livelihood afloat, brightens my day, and I'm always eternally grateful. Thanks for your help!