I’m frequently wanting to work with truncated dates in Swift apps, especially when dealing with Swift Charts. Foundation provides Calendar.startOfDay(for:)
to get the first moment of a day, and that’s been really useful. The extension below will do something similar for an arbitrary1 Calendar.Component
to truncate that date to the first moment of that component.
extension Date {
/// Returns the first moment of a given Calendar.Component, as a Date.
///
/// For example, pass in `.hour` called on `Date()`, if you want the start of
/// the current hour of the current day.
/// Supported Calendar.Components are .year, .month, .day, .hour, .minute,
/// .second. Passing an unsupported component will cause `self` to be returned
/// unchanged.
/// Calling `.start(of: .day)` is equivalent to calling `.startOfDay`.
/// - parameter to: The smallest component you want
/// - returns: The first moment of the given date.
func start(of component: Calendar.Component) -> Date {
let allComponents = [Calendar.Component.year, .month, .day, .hour, .minute, .second]
guard let idx = allComponents.firstIndex(of: component) else { return self }
let components = Calendar.current.dateComponents(
Set<Calendar.Component>(allComponents[0...idx]),
from: self
)
return Calendar.current.date(from: components)!
}
}
This has been super useful for me in building time-series charts from objects with timestamps. For example, here is the data that backs a chart showing the number of events per minute over the last hour:
var eventsByMinute: OrderedDictionary<Date, Int> {
let oneHourAgo = Calendar.current.date(byAdding: .hour, value: -1, to: Date())!
return events
.filter { $0.time >= oneHourAgo }
.reduce(into: [:]) { result, event in
result[event.time.start(of: .minute), default: 0] += 1
}
}
This is quick-and-dirty and doesn’t consider time zones, but hopefully it’s useful to you if you need something similar.
-
Provided that the component is one of the ones specified in
allComponents
. It’s easy enough to add additional ones if they’re useful to you. ↩