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. .month, .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 DateComponents to Date which might seems a bit confusing. In general, it’s fairly straightforward, but since both DateFormatter and DateComponents can 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.

NaiveDate

The library implements three types:

  • NaiveDate (e.g. 2017-09-29)
  • NaiveTime (e.g. 15:30:00)
  • NaiveDateTime (e.g. 2017-09-29T15:30:00 - no time zone and no offset).

Each of them implements Equatable, Comparable, LosslessStringConvertible, and Codable protocols. Naive types can also be converted to Date, and DateComponents.

Create

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

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"

Convert

When you do need time zones, convert NaiveDate to Date:

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)

Notes

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 Date and Calendar types.

Resources