The Foundation framework has a rich set of functions to do "Calendrical Calculations".
The Calendar class has functions for adding "date component" values to dates, or forcing an existing Date to a specific time-of-day. We can combine those to give a variety of results.
Edit:
Leo Dabus pointed out in the comments that there is a built-in Calendar function nextDate(after:matching:options:)
that lets us calculate a specific time in the next day in one shot.
That version of the function looks like this:
extension Date {
func twoAmNextDay() -> Date {
let components = DateComponents(hour: 2, minute: 0, second: 0)
return Calendar.current.nextDate(after: self, matching: components, matchingPolicy: .nextTime)!
}
Note that my implementation uses a force-unwrap to make the result non-optional. That's bad form. It would be better to make the function throw or return an Optional.
For completeness, my original function was this:
extension Date {
func twoAmNextDay() -> Date {
guard let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: self),
let result = Calendar.current.date(bySettingHour: 2, minute: 0, second: 0,of: tomorrow) else { fatalError() }
return result
}
}
I don't know if the nextDate(after:matching:options:)
based version handles things like the transition between daylight savings time and standard time better, but it's certainly simpler.
We can include that extension with some other code to make a working demo:
extension Date {
func twoAmNextDay() -> Date {
let components = DateComponents(hour: 2, minute: 0, second: 0)
return Calendar.current.nextDate(after: self, matching: components, matchingPolicy: .nextTime)!
}
func localDateString(dateStyle: DateFormatter.Style = .medium, timeStyle: DateFormatter.Style = .medium) -> String {
DateFormatter.localizedString(from: self, dateStyle: dateStyle, timeStyle: timeStyle)
}
func isOtherDateBefore2AmNextDay(_ otherDate: Date) -> Bool {
otherDate < self.twoAmNextDay()
}
}
let now = Date()
let secondsInDay = 24.0 * 60.0 * 60.0
print( "Current date = " + now.localDateString() + ". Tomorrow at 2:00 AM is " + Date().twoAmNextDay().localDateString() + "\n")
for _ in 1...5 {
let randomOffset = Double.random(in: -secondsInDay*3...secondsInDay*3)
let randomizedDate = now.addingTimeInterval(randomOffset)
print("Randomized date = " + randomizedDate.localDateString())
let notString = now.isOtherDateBefore2AmNextDay(randomizedDate) ? "" : "not "
print("Randomized date is " + notString + "before 2AM tomorrow")
print()
}
Sample output from that code looks like this:
Current date = Sep 2, 2022 at 11:07:15 AM. Tomorrow at 2:00 AM is Sep 3, 2022 at 2:00:00 AM
Randomized date = Sep 1, 2022 at 1:59:02 AM
Randomized date is before 2AM tomorrow
Randomized date = Sep 3, 2022 at 6:21:36 AM
Randomized date is not before 2AM tomorrow
Randomized date = Sep 3, 2022 at 9:13:19 PM
Randomized date is not before 2AM tomorrow
Randomized date = Sep 3, 2022 at 9:17:17 PM
Randomized date is not before 2AM tomorrow
Randomized date = Aug 30, 2022 at 11:49:46 PM
Randomized date is before 2AM tomorrow