Foundation offers great APIs for manipulating dates with time zones (e.g. working with something like
2017-09-29T15:00:00+0300). However, sometimes it might be a bit frustrating when you just need a naive date or time.
So what exactly is a naive date or a naive time? They are called “naive” because they don’t have a time zone associated with them. For example:
- naive date: "2017-09-29" - naive time: "15:00:00" - naive datetime: "2017-09-29T15:00:00"
In comparison here’s how you could represent specific points in time by specifying a time zone (or offset):
- datetime in UTC: "2017-09-29T15:00:00Z" - datetime with timezone offset: "2017-09-29T15:00:00+0300" - datetime with timezone: datetime="2017-09-29T15:00:00", timezone="Europe/London"
Turns out there are a lot of scenarios in which the time zone is not needed. For example, let’s say you want to show user’s birthday. You don’t need to know either a time zone, or a specific point in time -
Date - of birth. Or suppose that you want to display opening hours of a restaurant. There are many situations like these.
An obvious way to implement those scenarios using
Foundation is with a help of
DateComponents. However, there are a few problems:
- All of the components (e.g.
.hour, etc) are optional so it’s not (type) safe to pass them around.
- If you wanted to format a date to display it to the user you would have to use
DateFormatter, which requires conversion from
Datewhich might seems a bit confusing. In general, it’s fairly straightforward, but since both
DateComponentscan carry time zone information associated with them, there is always a room for error.
- Parsing date components might be challenging.
Since I’ve realized that in my recent app I mostly use naive dates and times, I decided to build a type-safe abstraction to eliminate the potential errors: NaiveDate.
The library implements three types:
2017-09-29T15:30:00- no time zone and no offset).
Each of them implements
Codable protocols. Naive types can also be converted to
Naive dates and times can be created from a string (using a predefined format), using
Decodable, or with a memberwise initializer:
NaiveDate("2017-10-01") NaiveDate(year: 2017, month: 10, day: 1) NaiveTime("15:30:00") NaiveTime(hour: 15, minute: 30, second: 0) NaiveDateTime("2017-10-01T15:30") NaiveDateTime( date: NaiveDate(year: 2017, month: 10, day: 1), time: NaiveTime(hour: 15, minute: 30, second: 0) )
Format dates without having to worry about time zones:
let date = NaiveDate("2017-11-01")! NaiveDateFormatter(dateStyle: .short).string(from: date) // prints "Nov 1, 2017" let time = NaiveTime("15:00")! NaiveDateFormatter(timeStyle: .short).string(from: time) // prints "3:00 PM" let dateTime = NaiveDateTime("2017-11-01T15:30:00")! NaiveDateFormatter(dateStyle: .short, timeStyle: .short).string(from: dateTime) // prints "Nov 1, 2017 at 3:30 PM"
When you do need time zones, convert
let date = NaiveDate(year: 2017, month: 10, day: 1) // Creates `Date` in a calendar's time zone // "2017-10-01T00:00:00+0300" if user is in MSK Calendar.current.date(from: date)
let dateTime = NaiveDateTime( date: NaiveDate(year: 2017, month: 10, day: 1), time: NaiveTime(hour: 15, minute: 30, second: 0) ) // Creates `Date` in a calendar's time zone // "2017-10-01T15:30:00+0300" if user is in MSK Calendar.current.date(from: dateTime)
Naive dates and times are extremely easy to use. There are no time zones to worry about. You can easily parse them without having to use custom date formatters. It works just as you expect. But, it’s important to understand the limitations behind them.
They are called “naive” because they don’t have a time zone associated with them. This means the date may not actually exist in some areas in the world, even though they are “valid”. For example, when daylight saving changes are applied the clock typically moves forward or backward by one hour. This means certain dates never occur or may occur more than once. The moment you need to do any precise manipulations with time, always use native