As mentioned last time in my post Date — Class Reference, now we go a bit more in depth with DateComponents. In my opinion, this is the real powerhouse for dealing with time in Swift or Objective-C. When I was first learning iOS, I looked, understandably, at Date to work with dates. As such, a test project used Date and it’s initializers, including NSTimeInterval. When I had mentioned that at my local NSCoderNight, I was told to look into DateComponents, and boy was that good advice! Not only is it a pain to calculate the seconds for the intervals you want, what about dealing with Daylight Savings Time, leap years, or just plain different calendars! Now with DateComponents, I can set a date in a way that I understand, and in concert with Date and Calendar, it will even deal appropriately with the intricacies of calendars without me having to do so myself.
DateComponents can be used to either specify a specific date, or to specify a timespan in your Swift iOS apps. There is no difference between the uses as far as DateComponents is concerned you just set the appropriate components (year, day, hour, second, etc).
The Components on DateComponents
So, what can you specify with DateComponents? Here is a list of all properties for a DateComponents object you can set, and any special notes about them as interpreted for the Gregorian Calendar.
Property | Notes |
era | Index for DateComponents’s EraSymbol Array, which is [“BC”, “AD”] for the Gregorian Calendar. |
year | |
month | |
day | |
hour | |
minute | |
second | |
nanosecond | |
weekday | The index of which day of the week it is, so Sunday is 1, and Saturday is 7. |
weekdayOrdinal | Works with the above weekday in specifying which weekday in the larger context (the month in this case). |
quarter | The quarter of the year the date is in. |
weekOfMonth | The week number within the month. |
weekOfYear | The week number within the year based on the ISO week date. |
yearForWeekOfYear | The year for the above weekOfYear based on the ISO week date. |
isLeapMonth | Boolean of whether the month is a leap month (not used in Gregorian Calendar). |
calendar | The Calendar this should be interpreted against. |
timeZone | The NSTimeZone to interpret this DateComponents object. |
That is a lot of properties. Also, if you’re curious the weekOfYear and yearForWeekOfYear are based off of something called the ISO Week Date, which is part of the ISO 8601 standard. You can read more about it on this Wikipedia page. The 52nd week of the year as we normally use it may not be the 52nd week according to the weekOfYear property, depending on which year you are in, and what day of the week the year starts on in the normal Gregorian calendar (as opposed to the ISO Week Date version of the Gregorian Calendar).
A little more explanation on the weekday and weekdayOrdinal property. They work together, so, for instance the day this post goes out is November 14, 2014. The values for that are weekday of 6, and weekdayOrdinal of 2. The weekday of 6 refers to a Friday. The 2 says that this is the second of that weekday this month. In other words, together they say that this is the second Friday of the month.
Creating a DateComponents Object
Unlike its predecessor NSDateComponents, Swift’s DateComponents has an initializer that lets you set the components directly, instead of having to create a blank one and fill them in later (like you used to). The initializer has a whopping 16 parameters, but thankfully they all optional and have default values of nil, so you only need to define the ones you need, like so:
let swiftDayComponents = DateComponents(year: 2014, month: 6, day: 2)
Those that are not set are internally set to nil. By setting just the Year, Month, and Day of a date, you do not get the weekday or weekOfMonth values for free. You actually have to fully construct a Date based on these components and create a new DateComponents object based off of that date to get that information calculated for you.
Now, if you want to make a Date out of that, the usual way is to create a Calendar instance and ask it to do so, like this:
if let swiftDate = Calendar.current.date(from: swiftDayComponents) { print(swiftDate) }
If you want, you could set the calendar property of your DateComponents object, and ask it to make a date for you as well:
let swiftDayComponents = DateComponents(calendar: .current, year: 2014, month: 6, day: 2) if let swiftDate = swiftDayComponents.date { print(swiftDate) }
It just has to have some Calendrical context to be able to understand the DateComponents object.
DateComponents’s Methods
There are 4 methods in DateComponents. They fall into two camps. According to DateComponents’ header file, they were all added in iOS 8.
Accessing A Component
The first two set or get a component by specifying a Calendar.Component, instead of accessing them via dot-syntax. This lets you dynamically request whichever one you wish, based on sending it the appropriate Calendar.Component.
mutableSwiftDayComponents.setValue(11, for: .hour) let swiftHour = mutableSwiftDayComponents.value(for: .hour) //swiftHour now equals 11.
Those statements are equivalent to:
mutableSwiftDayComponents.hour = 11 let swiftHour = mutableSwiftDayComponents.hour
It is more verbose, but if you need to be able to set a component, but you aren’t sure which one by compile-time, then these methods could be useful.
Verifying Date Validity
The other two verify if a DateComponents object is a valid date for a certain Calendar. DateComponents can be used to denote a timespan, not only dates as we have shown so far. They are not set up differently, but you can for instance set the seconds greater than 59, and it will just interpret that as however many minutes. However, doing that would not be a valid date. It would roll over, and show like a valid date, but why write 2 minutes, 129 seconds in a DateComponents object when you really mean 4 minutes, 9 seconds? That is what these two methods check.
This one lets you get a bool denoting whether this date is valid in the current calendar:
let dateValid = swiftDayComponents.isValidDate(in: .current) //dateValid now equals true.
The other one requires you to set the calendar property of the DateComponents object. With that information you don’t need to specify to the method what calendar to check against like the previous method did. You would use it like this:
mutableSwiftDayComponents.calendar = .current let dateValid = mutableSwiftDayComponents.isValidDate //dateValid now equals true.
It actually is implemented, at least as far as Swift is concerned, more like a computed property, hence not having the parenthesis at the end of validDate.
The comments in DateComponents’ header file do mention that these methods are “not necessarily cheap.” Simple checks are fine, like our 129 seconds mentioned earlier, but checking more components makes this a bit more computationally intensive, so, be careful where you use these methods.
Conclusion
DateComponents makes it easy to specify a specific date and time, or even a timespan. While it would make sense to put Date and DateComponents together as far as what they conceptually do, I believe they kept them separate for performance reasons.
DateComponents does a lot more than Date, and that adds a significant amount of overhead. A Date is an immutable and lightweight object, making it very easy for the compiler to optimize. When you ask a Calendar to create a Date from a DateComponents object, it takes the components and the intricacies of the calendar into account to make a Date object that stores just enough to specify a particular moment in time.
As a side note from the future, I am glad that now in swift you can use much cleaner Calendar.Components for setValue and valueFor methods. In the original version of this its full name was NSCalendarUnit.HourCalendarUnit, but in the update to this post, you can just us Calendar.Component.hour (which, with Swift’s type inference was .HourCalendarUnit and now just .hour). It is so much nicer to write .Hour instead of .HourCalendarUnit.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!