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

1

2

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?

Rhonarhonchus answered 2/9, 2022 at 15:8 Comment(0)
R
2

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
Rhonarhonchus answered 2/9, 2022 at 15:11 Comment(4)
I would be careful when adding a day to the current time. You should use noon to avoid some pitfallsTallow
Question: France is changing its hour (saving time) 31th October this month. What's the output in your case, since at "2AM", it will be in fact "3AM"? And if you want to check "before/after" 2:30 AM instead? I'm curious about the limitations, and how to handle them, and also show to other SO users that "time" have particularities.Premed
@Premed I agree with your concern. The best approach in my opinion is using nextDate after date method and define the matching policy option accordinglyTallow
I forgot about nextDate(after:matching:options:). Good suggestion. I may edit my answer to demonstrate using that method instead.Rhonarhonchus

© 2022 - 2024 — McMap. All rights reserved.