How would I get the Date of 2:00 AM the next day in Swift?

Viewed 44

Somebody asked this question as a comment in an answer I already posted to another question. Its awkward and confusing to have extra questions in comments, so I'm posting this as a new question.

The new question is this:

How can i check if my timestamp is Less than 2:00 am the next day?

1 Answers

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
Related