How to have a Task wait until some Foundation.Date?

Hello,

I'm trying to have a Task wait until a Foundation Date, but I can't quite figure out the necessary incantation:

// I don't know how to build a ContinuousClock.Instant from a Date
let instant: ContinuousClock.Instant = ???
try await Task.sleep(until: instant)

// I can't find the UTCClock mentioned in SE-0329
// <https://github.com/apple/swift-evolution/blob/main/proposals/0329-clock-instant-duration.md>
try await Task.sleep(until: date, using: ???)

I did try to Google for a solution, but I remain clueless :grimacing:

May I ask for your help, if you know a technique that achieves this goal?

1 Like

I'd do it like this:

let targetDate: Date = …
let seconds = targetDate.timeIntervalSinceNow
try await Task.sleep(for: .seconds(seconds))

Alternatively, you can first create an Instant with ContinuousClock.Instant.now + .seconds(seconds) and then use sleep(until:), but that seems unnecessary.

3 Likes

Thank you @ole :slightly_smiling_face::+1:

1 Like

And by the power of Swift, you can make it as elegant as:

try await Task.sleep2(until: targetDate)

Demo:

import Foundation

extension Task {
	static func sleep(until target: Date) async throws where Success == Never, Failure == Never {
		let duration = target.timeIntervalSinceNow
		try await Self.sleep(for: .seconds(duration))
	}
}

print("Waiting for 1s...")
try await Task.sleep(until: Date.now.addingTimeInterval(1))
print("... done.")

(Ironically, to test it, I'm using a relative time, kinda defeating the point :smiley: )

3 Likes

Yes, I was about to add this extension :-)

Maybe the promised UTCClock of SE-0329 will ship one day!

1 Like

Retrospectively, I asked the wrong question :sweat_smile:

I do indeed have a target Date, but the current date (now) is injected, for testability and previews. I ended up with:

// Works for both past and future dates.
// For past dates, seconds is negative, and Task.sleep
// returns immediately.
let seconds = targetDate.timeIntervalSince(currentDate())
try await Task.sleep(for: .seconds(seconds))

Thank you all for your support :+1:

2 Likes

In case anyone was wondering (I did for a beat), there's really really no reason to use the until form instead:

  public func sleep(
    for duration: Instant.Duration,
    tolerance: Instant.Duration? = nil
  ) async throws {
    try await sleep(until: now.advanced(by: duration), tolerance: tolerance)
  }