Scheduling daily notifications on iOS using Calendar and DateComponents
Tags: ios, swift
Published: 2019-12-12 13:18:44

On iOS, there are several ways to send notifications to users. And typically every method of sending push notifications has a different goal. For example, when you're sending a remote push notification to a user, you will typically do this because something interesting happened outside of the user's device. Somebody might have sent them a message for example, or something exciting happened during a sports game.

However, when we schedule notifications on the device locally, we typically do this as a reminder, or in response to a user action. Like, for example, entering or exiting a geofence. In today's post, I'm going to focus on scheduling notifications that are essentially repeating reminders. They are notifications that are scheduled to show up every day at a certain time, or maybe every Monday at 09:00 am, or on every first day of the month. The same technique can be used to schedule each of these notifications.

In today's post you will learn about the following:

* Working with the `Calendar` and `DateComponents`.
* Scheduling notifications using a `UNCalendarNotificationTrigger`.

Let's get started with learning about the `Calendar`, shall we?

## Working with the Calendar and DateComponents

If you have worked extensively with dates and times on a global scale, you know that pretty much any assumptions you may have about how dates work are [wrong](https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca). A good way to explore how complicated dates can be is by playing with the `Calendar` object that is part of the `Foundation` framework. If you're reading this, it's likely that you're used to the Gregorian calendar. That's the calendar that typically has 365 days in a year unless it's a leap year and 24 hours in a day unless a leap second is added. I don't want to go into how messy dates are, so I'm going to leave it at that, but let's have some fun exploring calendars!

Take a look at the following code:

```swift
var gregorianCalendar = Calendar(identifier: .gregorian)
var japaneseCalendar = Calendar(identifier: .japanese)
var hebrewCalendar = Calendar(identifier: .hebrew)

func currentDate(for calendar: Calendar) -> DateComponents {
  calendar.dateComponents([.year, .month, .day], from: Date())
}

print("Gregorian", currentDate(for: gregorianCalendar))
print("Japanese", currentDate(for: japaneseCalendar))
print("Hebrew", currentDate(for: hebrewCalendar))
```

How different do you expect the output for each call to `currentDate(for:)` to be? Will the years all be the same? What about the months? Or the day of the month? Let's look at the output of the preceding code:

```bash
Gregorian year: 2019 month: 12 day: 11 isLeapMonth: false 
Japanese year: 1 month: 12 day: 11 isLeapMonth: false 
Hebrew year: 5780 month: 3 day: 13 isLeapMonth: false 
```

If you guessed that the dates would all be completely different, you were right! The `Calendar` object can take dates, like the current date and represent them as `DateComponents` for whatever the `Calendar` represents. So we can take the current date on your machine, and convert it to a date representation that makes sense for the calendar you use to represent dates.

This also works the other way around, we can use `DateComponents` to represent a date that's based on a specific `Calendar`:

```swift
var components = DateComponents()
components.year = 1989
components.month = 11
components.day = 15

func date(for components: DateComponents, using calendar: Calendar) -> Date? {
  return calendar.date(from: components)
}

print("Gregorian", date(for: components, using: gregorianCalendar)!)
print("Japanese", date(for: components, using: japaneseCalendar)!)
print("Hebrew", date(for: components, using: hebrewCalendar)!)
```

The preceding code creates a `DateComponents` object that's based on the current calendar of the machine that's running this code. So in my case, it's Gregorian. Let's see what my birthday is on the Japanese and Hebrew calendars:

```bash
Gregorian 1989-11-14 23:00:00 +0000
Japanese 4007-11-14 23:00:00 +0000
Hebrew -1771-06-25 23:40:28 +0000
```

Notice how the Gregorian date is not what you would expect. The `DateComponents` were configured for November 15th. However, the printed date is November 14th. The reason for this is that we didn't specify a timezone for the calendar. Because of this, it defaults to `GMT` and since I'm not in the `GMT` timezone but in the `UTC` timezone, I get the wrong date. If you set `components.timeZone` to the timezone that you're representing your date in, you will get the expected output. So setting the timezone on the components objects like this: `components.timeZone = TimeZone(identifier: "UTC")` will produce the following output:

```bash
Gregorian 1989-11-15 00:00:00 +0000
Japanese 4007-11-15 00:00:00 +0000
Hebrew -1771-06-26 00:00:00 +0000
```

Much better. As you might realize by now, `DateComponents` describe dates using their components. In addition to `year`, `month` and `day` it's possible to specify things like `era`, `minute`, `second`, `quarter`, `microsecond` and much more. It's also possible to take a date and extract its `DateComponents` as I've shown in the first code snippet of this section.

Now that you have some working knowledge of `Calendar` and `DateComponent`, let's see how this applies to scheduling recurring notifications with `UNCalendarNotificationTrigger`.

## Scheduling notifications using a UNCalendarNotificationTrigger

You probably didn't come here to spend a bunch of time reading about calendars and dates, you wanted to see how to schedule recurring notifications based on the time of day, day of the week, day of the month or pretty much any other similar kind of rule. To do that, I had to show you some examples of calendars. If you've never seen calendars and date components before, it would be incredibly hard to explain `UNCalendarNotificationTrigger` to you.

I'm going to assume that you have basic knowledge of asking a user for permission to send them notifications, and what the best way is to do this. If you need a quick reminder, here's how you ask for notification permissions in code:

```swift
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { success, error in
  
  if error == nil && success {
    print("we have permission")
  } else {
    print("something went wrong or we don't have permission")
  }
}
```

Once you have the user's permission to send them notifications, you can schedule your local notifications as needed. Let's start with a simple example. Imagine sending the user a local notification every day at 09:00 am. You would use the following code to do this:

```swift
// 1
var dateComponents = DateComponents()
dateComponents.hour = 9
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

// 2
let content = UNMutableNotificationContent()
content.title = "Daily reminder"
content.body = "Enjoy your day!"

let randomIdentifier = UUID().uuidString
let request = UNNotificationRequest(identifier: randomIdentifier, content: content, trigger: trigger)

// 3
UNUserNotificationCenter.current().add(request) { error in
  if error != nil {
    print("something went wrong")
  }
}
```

The preceding code snippet creates a `UNCalendarNotificationTrigger` using a `DateComponents` object where only the `hour` is set to `9`. This means that the notification will trigger as soon as the user's current date reaches the ninth hour of the day. Keep in mind that for this to work, the calendar that the user uses and the one that's used to configure the `DateComponent` must match. Since `DateComponent` uses the current calendar it's not a problem in this case. But if you allow the user to configure their reminder, and your UI assumes a Gregorian calendar while the user's device uses a different calendar, you might run into trouble.

You can configure your date components however you want, and every time the current date on the device matches your criteria, a notification will be shown to the user. If you want to show a notification only on Wednesdays at 2:00 pm, you could use the following `DateComponents` configuration:

```swift
var dateComponents = DateComponents()
dateComponents.hour = 14
dateComponents.weekday = 3
```

Keep in mind that not all calendars start their week on the same day. Most calendars will start their week on a Monday, but some might start on a Sunday. You can account for this by grabbing the current calendar's `firstWeekday` property and calculating its offset from the day you're targeting. Math for dates is quite complicated so make sure to always triple check your work.

With this knowledge, you should now be able to schedule recurring notification using `DateComponents`!

## In Summary

In this article, you learned that dates are not the same everywhere in the world. Some calendars that are used in certain places of the world are completely different from the Gregorian calendar that I am familiar with. Luckily, the `Calendar` and `DateComponents` objects from `Foundation` make these differences somewhat manageable because they allow us to convert from a date represented in one calendar, to the same moment in time on another calendar. You saw an example of this in the first section of this article.

Once you learned the basics of `Calendar` and `DateComponents`, you learned how to use your new knowledge to schedule recurring notifications based on `DateComponents` using the `UNCalendarNotificationTrigger` class. It's really cool to be able to schedule notifications using this trigger because it allows you to set up notifications that repeat indefinitely and fire on a certain time, day of the week, month or any other interval that you can represent using `DateComponent`.

If you have any questions, feedback or anything else for me, don't hesitate to reach out on [Twitter](https://twitter.com/donnywals). I love hearing from you!