UILocalNotifications and time zones

This is an old post!

This post is over 2 years old. Solutions referenced in this article may no longer be valid. Please consider this when utilizing any information referenced here.

Here’s a tip when dealing with UILocalNotifications.

If you want to schedule a notification for a specific time using fireDate, you need to apply a timeZone to the UILocalNotification object. Otherwise, iOS will intepret this as an absolute, countdown-based date based on GMT.

To put it another way, suppose you have this code:

NSDate *date = [NSDate date];
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:date];
UILocalNotification *notification = [[UILocalNotification alloc] init];

// If hour has already passed, schedule for tomorrow.
if (components.hour >= 8) {
    components.day += 1;
}

// Schedule for 8
components.hour = 8;
components.minute = 0;

// Schedule it to repeat every day and give it a body.
notification.alertBody = @"Hello!";
NSDate *fireDate = [[NSCalendar currentCalendar] dateFromComponents:components];
notification.fireDate = fireDate;
notification.repeatInterval = NSDayCalendarUnit;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];

You will get a local notification at 8am in your current timezone, just like you would expect. But say you’re in Huntsville (in the Central timezone), and you fly to Utah (in the Mountain time zone). You now get a local notification at 7am. What?

In the AppleDoc for UILocalNotification, we find this:

The date specified in fireDate is interpreted according to the value of this property. If you specify nil (the default), the fire date is interpreted as an absolute GMT time, which is suitable for cases such as countdown timers. If you assign a valid NSTimeZone object to this property, the fire date is interpreted as a wall-clock time that is automatically adjusted when there are changes in time zones; an example suitable for this case is an an alarm clock.

So unless you specify a timezone on your UILocalNotification object, you will get a repeating countdown timer. Which is a little odd from a developer standpoint. This could be implemented a bit more clearly by Apple.

To fix this, you have to apply a timezone to the local notification itself.

// Have to set a timezone or this defaults to a coundown based timer. This
// should update based on the current user's timezone.
notification.timeZone = [NSTimeZone defaultTimeZone];

According to the docs, this should update automatically if the user’s timezone changes.

Comments (0)

Interested in why you can't leave comments on my blog? Read the article about why comments are uniquely terrible and need to die. If you are still interested in commenting on this article, feel free to reach out to me directly and/or share it on social media.

Contact Me
Share It
Apple
So I was confronted with an interesting bug this week, and I wanted to share it with everyone so maybe it will save you some time. Put simply, NSAttributedString with NSHTMLTextDocumentType is slow. Dog slow. So obscenely slow that it should probably never, ever be used.
Read More
Swift
It usually doesn’t take beginning macOS/iOS developers long to discover NotificationCenter and see it as the solution to every single problem of passing data around to different controllers. And NotificationCenter is great, but it has some downsides. Notably, it is very easy to introduce retain cycles (and memory leaks) unless you are very careful to track and free the listener when the object is released. This has bitten me on several occasions. In general, excessive use of NotificationCenter ends up creating a difficult to maintain app where it is not entirely clear what is responding to what and where.
Read More
Swift
Object oriented programming is great, but sometimes things don’t fit neatly into a superclass/subclass hierarchy. You may have a piece of code that would be needed in several contexts, but for technical reasons beyond your control you cannot merge them into a single hierarchy. Some languages have the concept of multiple inheritence, where a subclass can specifically inherit from several parents. But this has it’s own set of problems. Many other languages, however, solve this through the use of traits or mixins. These allow us to have a set of methods that are basically copied into the object at compile time. This way they can be used anywhere they are needed. Swift doesn’t have the concept of mixins or traits per se. But, starting with Swift 3, you can get very equivalent functionality using protocol default implementations.
Read More