Understanding Date and DateComponents

⋅ 8 min read ⋅ Swift Date

Date and time might be among your list of the hardest things in programming (It is for me). Today, I'm going to talk about a basic concept of a Date and its companion DateComponents.

What is Date?

Date is a specific point in time, independent of any calendar or time zone. Date values represent a time interval relative to an absolute reference date. You might come across this concept if you have ever work with Unix timestamp, which describes a date in the form of a time interval that has elapsed since the Unix epoch[1] (00:00:00 UTC on 1 January 1970).

Representing a date by a time interval makes it an easy task for the Date structure to provide methods for comparing dates, calculating the time interval between two dates, and creating a new date from a time interval relative to another date.

The key takes away here is Date is a specific point in time, independent of any calendar or time zone.

Date in our daily life

I point out that a Date is independent of a calendar or time zone because it is a crucial difference from date and time as we know and use it in our daily life.

If you ask the question, "what day is today?" in your office, you might get the same answer regardless of who you ask. But if you ask the same question on the internet, you might get a different one. That's because people in your office assume the same calendar and time zone as you, but the people on the internet think in their calendar and time zone. That's why you might get a different answer to the very same question.

Right now, in Thailand, it is 22:43 (GMT+7) October 26, 2563 BE (Check current local time in Bangkok here), which is unlikely to be the same date as you. Especially the calendar part.

let date = Date()

So the same date in code might not be the same date as we comprehend it. To create meaningful localized representations of dates and times, we need a calendar and timezone.

In iOS, we have many classes that do the hard lifting, such as DateFormatter that converts between dates and their textual representations.

let date = Date()

let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none

dateFormatter.string(from: date)

I said time zone and calendar are crucial in creating meaningful representations of dates and times, but I didn't set any of that in the previous example. That's because DateFormatter will assume the current calendar and time zone setting from your device. So, please make sure you aware of these two hidden components when dealing with date and time. Failing to do so is a cause of many bugs.

What is DateComponents?

Date is a good struct to represent a specific point in time, but it not so useful when we want to manipulate a date as we know and understand. Let's say I want to get a date of the same day but in the next month from now. It is quite hard and error-prone to do that on a Date value.

Since Date is all about time interval, you could do something like this.

let date = Date()
let nextWeek = date.addingTimeInterval(60 * 60 * 24 * 30) // <1>

<1> 30 days equal 60 seconds x 60 minutes x 24 hours & 30 days.

But the above example didn't take time zone and calendar into account, so the result might not be the one we expected since not every month has 30 days, and in some areas, there is a daylight saving concept, which means some days might be more or less than 24 hours. That's why we have DateComponents.

DateComponents is a struct representing a date in terms of units (such as year, month, day, hour, and minute).

To use it, we first create a DateComponents. Then, set all the components that make up a date. In this case, we want a date to be October 21, 2020, without time components (which will default to 00:00:00).

var comps = DateComponents() // <1>
comps.day = 21
comps.month = 10
comps.year = 2020

let date = Calendar.current.date(from: comps)! // <2>
print(date)

<1> Set all units that make up a date to DateComponents.
<2> Get a date created from the specified components.

This looks like a straightforward API, but there is a lot of pitfalls here and there.

Quiz

To test out your understanding, let's see if you can answer this question. What is the return date in UTC of the above DateComponents?

If your answer is it depends, you are on the right path. If you run the above code, you might get a different answer. Mine is 2020-10-20 17:00:00 +0000. You will get a different answer if you are not in a country with a timezone of GMT+7.

Key

As you can see, just a unit of time is not sufficient to pinpoint a specific point in time. Because when we say January 1, 2020, we leave out two hidden components. That is a time zone and calendar. If you open a television on a new year's day, you would see that the firework doesn't happen at the same time. Because we interpret a new years' day in our own time zone.

Let's go back a little bit and see the definition of DateComponents from Apple documentation.

DateComponents
A date or time specified in terms of units (such as year, month, day, hour, and minute) to be evaluated in a calendar system and time zone.

You will see that time components need to be evaluated in a specified calendar and time zone to be able to get a specific date.

So, let's look back at our question. We leave out calendar and timezone as nil here. So, the system use calendar and timezone of the receiver instead let date = Calendar.current.date(from: comps)!. In this case, it is the current calendar and time zone, Calendar.current.

var comps = DateComponents()
comps.day = 21
comps.month = 10
comps.year = 2020

Here is my current calendar.

gregorian (current)
- identifier : Foundation.Calendar.Identifier.gregorian
- kind : "current"
▿ locale : Optional<Locale>
▿ some : en (current)
- identifier : "en"
- kind : "current"
▿ timeZone : Asia/Bangkok (current)
- identifier : "Asia/Bangkok"
- kind : "current"
▿ abbreviation : Optional<String>
- some : "GMT+7"
- secondsFromGMT : 25200
- isDaylightSavingTime : false
- firstWeekday : 1
- minimumDaysInFirstWeek : 1

So our date is October 21, 2020, in Asia/Bangkok timezone (GMT+7). That's why when we print out this date in a console, which is a UTC format, we get 2020-10-20 17:00:00 +0000.

Examples

The following are examples of variation setting calendar/timezone for DateComponents.

Not set calendar and timezone in DateComponents would use a calendar and timezone from the receiver which is Calendar.current (GMT+7).

var comps = DateComponents()
comps.day = 21
comps.month = 10
comps.year = 2020

let date = Calendar.current.date(from: comps)!

// 2020-10-20 17:00:00 +0000


Set a calendar to DateComponents would not effect a timezone eventhough you set a timezone to that calendar. The system still pickup a timezone from the receiver which is Calendar.current (GMT+7).

var calendar = Calendar(identifier: .gregorian)
let timezone = TimeZone(secondsFromGMT: 0)!
calendar.timeZone = timezone

var comps = DateComponents()
comps.calendar = calendar
comps.day = 21
comps.month = 10
comps.year = 2020

let date = Calendar.current.date(from: comps)!

// 2020-10-20 17:00:00 +0000


If you want to specify a time zone for the DateComponents, explicitly set it in timezone. The result date would be October 21, 2020 at GMT+0 in this case (Ignore our Calendar.current).

let timezone = TimeZone(secondsFromGMT: 0)!

var comps = DateComponents()
comps.timeZone = timezone
comps.day = 21
comps.month = 10
comps.year = 2020

let date = Calendar.current.date(from: comps)!

// 2020-10-21 00:00:00 +0000


If you want to use the same time zone for all DateComponents, you can create a custom calendar for the conversion.

var calendar = Calendar(identifier: .gregorian)
let timezone = TimeZone(secondsFromGMT: 0)!
calendar.timeZone = timezone

var comps = DateComponents()
comps.day = 21
comps.month = 10
comps.year = 2020

let date = calendar.date(from: comps)!

// 2020-10-21 00:00:00 +0000


Timezone (and other values) in DateComponents would have precedance over value in receiver calendar. In this case timezone from DateComponents will use in a conversion (GMT+7).

var calendar = Calendar(identifier: .gregorian)
let timezone = TimeZone(secondsFromGMT: 0)!
calendar.timeZone = timezone

var comps = DateComponents()
comps.timeZone = TimeZone.current
comps.day = 21
comps.month = 10
comps.year = 2020

let date = calendar.date(from: comps)!

// 2020-10-20 17:00:00 +0000

Conclusion

It is a lot easier to manipulate date and time with DateComponents because it aligns with how we interpret date and time in the real world. That's why you will see a lot of Calendar methods involve DateComponents.

func dateComponents(Set<Calendar.Component>, from: Date) -> DateComponents
func date(from: DateComponents) -> Date?
func date(byAdding: Calendar.Component, value: Int, to: Date, wrappingComponents: Bool) -> Date?

Knowing what Date really is and how to convert them between time zone and calendars is important when working with date and time.


  1. An epoch, for the purposes of chronology and periodization, is an instant in time chosen as the origin of a particular calendar era. The "epoch" serves as a reference point from which time is measured. https://en.wikipedia.org/wiki/Epoch#Notable_epoch_dates_in_computing ↩︎


Read more article about Swift, Date, or see all available topic

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Tweet Share
Previous
What is @escaping in Swift closures

Learn the meaning of @escaping, so you know what to do when you see it or when you need to write one.

Next
Getting the number of days between two dates in Swift

There are a few variations when dealing with counting days. You need to ask yourself some questions beforehand.

← Home