bitops
bitops

Reputation: 4292

Can I use a DateComponentsFormatter to render the time of day?

I would like to store a time of day in a simple way. For the purposes of my app, I only care about a specific time of day, like 2:30pm or 1:10am.

Using a DateComponentsFormatter I can get really close.

import Foundation

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [
    .hour,
    .minute
]
 
// Use the configured formatter to generate the string.
let timeAmount = 9000.0
let outputString = formatter.string(from: timeAmount)
let desiredString = "2:30 pm"import Foundation

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [
    .hour,
    .minute
]
 
// Use the configured formatter to generate the string.
let timeAmount = 9000.0
let outputString = formatter.string(from: timeAmount) // yields "2:30"
let desiredString = "2:30 pm"

Now, it seems that the formatter is really just saying "two hours and thirty minutes," so maybe I am barking up the wrong tree here. Does DateComponentsFormatter support my use case?

Upvotes: 0

Views: 1527

Answers (2)

Rob
Rob

Reputation: 437552

The DateComponentsFormatter is for expressing the amount of time between two dates. DateFormatter is for formatting dates or times. Use DateFormatter.

If your intent is to display a time in the UI, use DateFormatter, but

  • Do not use dateFormat, but rather use timeStyle;
  • Do not override locale;
  • Do not set timeZone;

E.g. to show the current time in the UI:

let date = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let string = formatter.string(from: date)

By the way, this will format time strings as preferred by the user (e.g. AM/PM for some of us, but in other locales, users may prefer 24 hour clock representation). When displaying information in the UI, we should always honor the user’s preferences.


If you’re trying to convert your 9,000 seconds to 2:30am:

let startOfDay = Calendar.current.startOfDay(for: Date())
let date = startOfDay.addingTimeInterval(9000)

let formatter = DateFormatter()
formatter.timeStyle = .short
let string = formatter.string(from: date)    // 2:30 AM

Clearly, if you happen to be on a date where you are shifting to or from daylight savings, your result may vary.

Upvotes: 2

Sweeper
Sweeper

Reputation: 271555

From the documentation:

An DateComponentsFormatter object takes quantities of time and formats them as a user-readable string.

The point is that a DateComponentsFormatter operates on quantities of time (e.g. 2 hours and 10 minutes), whereas you want a point in time. These are very different things. Trying to use DateComponentsFormatter to produce a time of day could work if you try hard enough, it's just that you are misusing/abusing it.

It seems like you are storing the times of day as the number of seconds since midnight. To format this with DateComponentsFormatter, you would need to do a lot of checks and calculations with the stored number:

  • You need to add the am/pm string yourself.
  • You need to add 12 hours to any time that's below 1 hour.
  • You need to subtract 12 hours when the stored time is greater than 12 hours.
  • There's probably more that you need to consider that I missed

Plus, the am/pm is not localisable. There also might be some cultures in the world where they use a different format to display quantities of time from times of day (I don't know!), which will cause this approach to break down completely.


DateFormatter should be used to format times of day. Just add the stored time to the UNIX epoch time (or any other midnight) and format with the UTC timezone:

let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "hh:mm a"
// or you can replace the above 2 lines with something more localised:
// formatter.timeStyle = .short
formatter.timeZone = TimeZone(identifier: "UTC")
let output = formatter.string(from: Date(timeIntervalSince1970: storedTime))

Upvotes: 2

Related Questions