Skip to content

Commit

Permalink
Latest scheduling should update based off the latest of nextScheduled…
Browse files Browse the repository at this point in the history
…Data or now (#44)
  • Loading branch information
adam-fowler authored Dec 21, 2024
1 parent f1cdfe6 commit 6aeb0a1
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 12 deletions.
18 changes: 12 additions & 6 deletions Sources/Jobs/Scheduler/JobSchedule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ public struct JobSchedule: MutableCollection, Sendable {
mutating func updateNextScheduledDate(jobIndex: Int) {
let dateFrom: Date =
switch self.self[jobIndex].accuracy {
case .latest: .now
case .latest: Swift.max(.now, self[jobIndex].nextScheduledDate)
case .all: self[jobIndex].nextScheduledDate
default: .now
default: Swift.max(.now, self[jobIndex].nextScheduledDate)
}
if let nextScheduledDate = self[jobIndex].schedule.nextDate(after: dateFrom) {
self[jobIndex].nextScheduledDate = nextScheduledDate
Expand All @@ -112,6 +112,12 @@ public struct JobSchedule: MutableCollection, Sendable {
}
}

mutating func setInitialNextDate(after date: Date) {
for index in 0..<self.count {
self[index].nextScheduledDate = self[index].schedule.setInitialNextDate(after: date) ?? .distantFuture
}
}

/// AsyncSequence of Jobs based on a JobSchedule
struct JobSequence: AsyncSequence {
typealias Element = (job: JobParameters, date: Date)
Expand Down Expand Up @@ -141,7 +147,9 @@ public struct JobSchedule: MutableCollection, Sendable {
let nextScheduledDate = job.element.nextScheduledDate
let timeInterval = nextScheduledDate.timeIntervalSinceNow
do {
try await Task.sleep(until: .now + .seconds(timeInterval))
if timeInterval > 0 {
try await Task.sleep(until: .now + .seconds(timeInterval))
}
self.jobSchedule.updateNextScheduledDate(jobIndex: job.offset)
return (job.element.jobParameters, nextScheduledDate)
} catch {
Expand Down Expand Up @@ -179,9 +187,7 @@ public struct JobSchedule: MutableCollection, Sendable {
date = .now
}
self.jobQueue.logger.debug("Last scheduled date \(date).")
for index in 0..<jobSchedule.count {
jobSchedule[index].nextScheduledDate = jobSchedule[index].schedule.setInitialNextDate(after: date) ?? .distantFuture
}
jobSchedule.setInitialNextDate(after: date)
} catch {
self.jobQueue.logger.error("Failed to get last scheduled job date.")
}
Expand Down
57 changes: 51 additions & 6 deletions Tests/JobsTests/JobSchedulerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,14 @@ final class JobSchedulerTests: XCTestCase {

func testScheduleTimeZone() throws {
let startDate = ISO8601DateFormatter().date(from: "2021-06-21T21:10:15Z")!
var schedule = Schedule.daily(hour: 4, timeZone: .init(secondsFromGMT: 0)!)
var schedule = Schedule.daily(hour: 4, timeZone: .init(secondsFromGMT: 7200)!)
let scheduledDate = try XCTUnwrap(schedule.nextDate(after: startDate))
let dateComponents = Calendar.current.dateComponents([.hour], from: scheduledDate)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = .init(secondsFromGMT: 0)!

let dateComponents = calendar.dateComponents([.hour], from: scheduledDate)
// check timezone difference is the same as the difference in the schedule
XCTAssertEqual(TimeZone.current.secondsFromGMT(), (dateComponents.hour! - 4) * 3600)
XCTAssertEqual((dateComponents.hour! - 4) * 3600, -7200)
}

func testJobSchedule() throws {
Expand All @@ -189,9 +192,8 @@ final class JobSchedulerTests: XCTestCase {
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
let startDate = dateFormatter.date(from: "2024-04-14T02:00:00Z")!
for jobIndex in schedule.startIndex..<schedule.endIndex {
schedule[jobIndex].nextScheduledDate = schedule[jobIndex].schedule.nextDate(after: startDate)!
}
schedule.setInitialNextDate(after: startDate)

// first two jobs should be Job1
var job = try XCTUnwrap(schedule.nextJob())
for _ in 0..<2 {
Expand All @@ -214,6 +216,49 @@ final class JobSchedulerTests: XCTestCase {
XCTAssert(job.element.jobParameters is Job2)
}

func testJobScheduledAtSameTimeSequence() async throws {
struct Job1: JobParameters {
static let jobName = "Job1"
}
struct Job2: JobParameters {
static let jobName = "Job2"
}
var jobSchedule = JobSchedule([
.init(job: Job1(), schedule: .everyMinute(second: 45)),
.init(job: Job2(), schedule: .everyMinute(second: 45)),
])
jobSchedule.setInitialNextDate(after: .now)

let job = try XCTUnwrap(jobSchedule.nextJob())
jobSchedule.updateNextScheduledDate(jobIndex: job.offset)
XCTAssert(job.element.jobParameters is Job1)
let job2 = try XCTUnwrap(jobSchedule.nextJob())
jobSchedule.updateNextScheduledDate(jobIndex: job2.offset)
XCTAssert(job2.element.jobParameters is Job2)

}

func testJobScheduledAfterLongWait() async throws {
struct Job1: JobParameters {
static let jobName = "Job1"
}
struct Job2: JobParameters {
static let jobName = "Job2"
}
var jobSchedule = JobSchedule([
.init(job: Job1(), schedule: .onMinutes([10, 45]))
])
jobSchedule.setInitialNextDate(after: .now - 30 * 24 * 60 * 60)

let job = try XCTUnwrap(jobSchedule.nextJob())
// first job scheduled date should be before now
XCTAssert(job.element.nextScheduledDate < .now)
jobSchedule.updateNextScheduledDate(jobIndex: job.offset)
let job2 = try XCTUnwrap(jobSchedule.nextJob())
// second job scheduled date should be after now
XCTAssert(job2.element.nextScheduledDate > .now)
}

func testJobScheduleSequence() async throws {
struct Job1: JobParameters {
static let jobName = "Job1"
Expand Down

0 comments on commit 6aeb0a1

Please sign in to comment.