Understanding UILocalNotification timeZone

Swift 5.2 with UNCalendarNotificationTrigger on iOS 13

I used @brnunes code as an inspiration to do the same thing with UNCalendarNotificationTrigger.

Use TimeZone.autoupdatingCurrent if you want to send your notification at a local time. Example: You schedule a notification at 6pm GMT, then change your time zone on your device to CET and the notification will still be scheduled at 6pm on your device.

Use any other time zone like TimeZone.current or TimeZone.init(abbreviation: "GMT") if you want a fire date that is fixed in time. Example: You schedule a notification at 2pm Eastern Daylight Time (EDT GMT-4), then change your time zone on your device to CEST (GMT+2) and the notification will now be scheduled at 8pm on your device. It is like having a meeting at 6pm GMT. The meeting time doesn't change, only for EDT it's at 2pm and for CEST it's at 8pm.

I used the following code to add notifications:

func addNotification(identifier: String, timeZone: TimeZone?) {
    let content = UNMutableNotificationContent()
    content.title = "Title \(identifier)"
    content.body = "Body"

    let trigger = UNCalendarNotificationTrigger(
        dateMatching: DateComponents(
            calendar: Calendar.current,
            timeZone: timeZone,
            year: 2020,
            month: 04,
            day: 01,
            hour: 18,
            minute: 30,
            second: 0),
        repeats: false)

    let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)

    UNUserNotificationCenter
        .current()
        .add(request) { _ in
            print("Scheduled \(identifier)")
    }
}

I added four different notifications.

addNotification(identifier: "1", timeZone: nil)
addNotification(identifier: "2", timeZone: TimeZone.init(abbreviation: "GMT")!)
addNotification(identifier: "3", timeZone: TimeZone.autoupdatingCurrent)
addNotification(identifier: "4", timeZone: TimeZone.current)

I used the following code to print the scheduled requests.

UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { notificationRequests in
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .medium
    dateFormatter.timeStyle = .medium
    dateFormatter.timeZone = TimeZone.current

    print(TimeZone.current)
    notificationRequests.forEach({
        let nextTriggerDate = ($0.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()!
        print("\($0.identifier): \(dateFormatter.string(from: nextTriggerDate)) (\(nextTriggerDate))")
    })
})

First, I ran the code with CEST (GMT+2). Then, I went to my iOS settings and changed my time zone to Miami, EDT (GMT-4). Here are the results.

Europe/Berlin (current)
1: 1. Apr 2020 at 18:30:00 (2020-04-01 16:30:00 +0000)
2: 1. Apr 2020 at 20:30:00 (2020-04-01 18:30:00 +0000)
3: 1. Apr 2020 at 18:30:00 (2020-04-01 16:30:00 +0000)
4: 1. Apr 2020 at 18:30:00 (2020-04-01 16:30:00 +0000)

US/Eastern (current)
1: 1. Apr 2020 at 12:30:00 (2020-04-01 16:30:00 +0000)
2: 1. Apr 2020 at 14:30:00 (2020-04-01 18:30:00 +0000)
3: 1. Apr 2020 at 18:30:00 (2020-04-01 22:30:00 +0000)
4: 1. Apr 2020 at 12:30:00 (2020-04-01 16:30:00 +0000)

As you can see for 1, 2 and 4, the date in time never changes. It's 2020-04-01 16:30:00 +0000, 2020-04-01 18:30:00 +0000 and 2020-04-01 16:30:00 +0000 both times. Only the local time when the notification is fired changes and is updated.

With 3, the actual date changes from 2020-04-01 16:30:00 +0000 to 2020-04-01 22:30:00 +0000. However, the local time doesn't change and remains at 18:30:00.

As you can see here, there is a difference in how time zones worked in @brnunes's answer with UILocalNotification and UNCalendarNotifactionTrigger. In his second conclusion he says that UILocalNotifictions auto-updates for different time zones. With UNCalendarNotificationTrigger, that is not the case anymore. As pointed out by @Nicolas Buquet, TimeZone.autoupdatingCurrent is the equivalent for defaultTimeZone.


I just ran some tests on iOS 6.1.3. Here's what I got:

I'm in Seattle, at 1:00PM (Pacific Daylight Time, GMT-7). I created a NSDate:

NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
// 2013-08-31 @ 12:00:00 (noon)
dateComponents.year = 2013;
dateComponents.month = 8;
dateComponents.day = 31;
dateComponents.hour = 12;
dateComponents.minute = 0;
dateComponents.second = 0;

NSDate *fireDate = [gregorianCalendar dateFromComponents:dateComponents];

now I have

fireDate = 2013-08-31 19:00:00 +0000 (2013-08-31 12:00:00 -0700)

Then I created and scheduled the notifications:

notification1 = [[UILocalNotification alloc] init];
notification1.fireDate = fireDate;
// notification1.timeZone is nil by default

NSLog(@"%@", notification1);

notification2 = [[UILocalNotification alloc] init];
notification2.fireDate = fireDate;
notification2.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];

NSLog(@"%@", notification2);

notification3 = [[UILocalNotification alloc] init];
notification3.fireDate = fireDate;
notification3.timeZone = [NSTimeZone defaultTimeZone];

NSLog(@"%@", notification3);

Logs of the notifications just created, in Seattle (Pacific Daylight Time, GMT-7):

notification1:
fire date = Saturday, August 31, 2013, 12:00:00 PM Pacific Daylight Time,
time zone = (null),
next fire date = Saturday, August 31, 2013, 12:00:00 PM Pacific Daylight Time

notification2:
fire date = Saturday, August 31, 2013, 7:00:00 PM GMT,
time zone = GMT (GMT) offset 0,
next fire date = Saturday, August 31, 2013, 7:00:00 PM Pacific Daylight Time

notification3:
fire date = Saturday, August 31, 2013, 12:00:00 PM Pacific Daylight Time,
time zone = US/Pacific (PDT) offset -25200 (Daylight),
next fire date = Saturday, August 31, 2013, 12:00:00 PM Pacific Daylight Time

I changed the phone's timezone to Chicago, where now it is 3:00PM (Central Daylight Time, GMT-5).

Logs of the notifications, in Chicago (Central Daylight Time, GMT-5)

notification1:
fire date = Saturday, August 31, 2013, 2:00:00 PM Central Daylight Time,
time zone = (null),
next fire date = Saturday, August 31, 2013, 2:00:00 PM Central Daylight Time

notification2:
fire date = Saturday, August 31, 2013, 7:00:00 PM GMT,
time zone = GMT (GMT) offset 0,
next fire date = Saturday, August 31, 2013, 7:00:00 PM Central Daylight Time

notification3:
fire date = Saturday, August 31, 2013, 12:00:00 PM Pacific Daylight Time,
time zone = US/Pacific (PDT) offset -25200 (Daylight),
next fire date = Saturday, August 31, 2013, 12:00:00 PM Central Daylight Time

Conclusions:

  1. When UILocalNotification timeZone is nil, the fire date is fixed in time. That means the notification will be fired at 12:00PM GMT-7, 2:00PM GMT-5, or 7:00 GMT.
  2. When UILocalNotification timeZone is set to GMT, the fire date is calculated for GMT time and will auto-update if the user goes to another time zone. In this example, the time 12:00 GMT-7 was converted to 19:00 GMT, and the notification was set to 19:00 local time, whatever time zone we are (19:00 GMT, 19:00 GMT-5 or 19:00 GMT-7).
  3. When UILocalNotification timeZone is set to the local time zone (Pacific Daylight Time, GMT-7), the fire date is calculated for the local time and will auto-update if the user goes to another time zone. In this example, the time was 12:00 GMT-7, so the notification will be fired at 12:00 local time, whatever time zone we are (12:00 GMT, 12:00 GMT-5 or 12:00 GMT-7).