Understanding Date and DateComponents
Table of Contents
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.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
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.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Related Resources
- Calendar - Apple Documentation
- DateFormatter - Apple Documentation
- Date - Apple Documentation
- DateComponents - Apple Documentation
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
Enjoy the read?
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. No strings attached. Unsubscribe anytime.
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 Buy me a coffee Tweet ShareWhat 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.
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.