class VirtualDate::Scheduler

Overview

A simple, deterministic scheduler for VirtualDate vdates.

This scheduler:

Notes:

Defined in:

virtualdate.cr

Constant Summary

MAX_BLOCK_TRANSITIONS = 3

Furthest a single matching stretch is followed across UTC offset changes. Two is already more than any zone puts inside one.

MAX_DURATION = (Time.utc(9999, 12, 31)) - (Time.utc(1, 1, 1))

Holds the vdates to the same limits VirtualDateFile enforces on a document, so that a value set in code cannot do what one read from YAML is refused for.

A negative duration in particular has consequences beyond the vdate itself: it finishes before it starts, which switches off overlap detection, so other vdates are then free to take a slot it holds. Longest duration a vdate can carry: any more and adding it to a start runs off the end of the calendar Time can hold.

MAX_RULE_ITERATIONS = 100000

Maximum number of step-iterator advances per due rule while scanning for occurrence starts. Guards against effectively-continuous rules scanned at too fine a granularity (e.g. an always-matching rule over a year-long window).

MAX_RUN_STEPS = 512

Furthest the run walk follows matching stretches in one call. A walk that stops here says so, and the caller resumes it from where it left off, so the work over a whole run stays proportional to the stretches in it.

MAX_SEED_REFINEMENTS = 3

How many times #first_occurrence re-asks with a more canonical hint before settling. Two rounds cover the shapes that arise; the third only confirms that nothing moved.

Constructors

Instance Method Summary

Constructor Detail

def self.new(vdates : Array(VirtualDate) = [] of VirtualDate) #

[View source]

Instance Method Detail

def build(from : Time, to : Time, *, granularity : Time::Span = 1.minute, max_candidates : Int32 = 1000) : Array(Scheduled) #

Produces scheduled vdates in [from, to).

Each due rule generates one candidate per distinct occurrence in the window. Times that match contiguously at granularity spacing (e.g. every minute of hour: 10) coalesce into a single occurrence starting at the first match.

Parameters:

  • granularity: resolution at which due rules are scanned and distinct occurrences are told apart (defaults to 1 minute)
  • max_candidates: safety limit on candidates per vdate, to avoid runaway generation for very broad rules

Returns: Array(Scheduled), sorted by start time.


[View source]
def on_in_schedule?(scheduled_vdates : Array(Scheduled), vdate : VirtualDate, time : Time) : Bool #

True if vdate is considered “on” at time in the produced schedule.


[View source]
def resolve_dependencies! #

Turns each vdate's serialized depends_on ids into object references.

#build runs this too, because #vdates is writable: a vdate appended after construction -- one just loaded from YAML, say -- would otherwise keep its dependencies as unresolved ids and be scheduled as though it had none, quite possibly before the vdates it depends on.


[View source]
def schedule_candidate(candidate : Candidate, scheduled_vdates : Array(Scheduled), *, horizon : Time, depended_upon : Set(VirtualDate) = vdates_with_dependents, displaced : Array(Scheduled) | Nil = nil, origin : Time | Nil = nil) : Scheduled | Nil #

Schedules a vdate, resolving conflicts by shifting forward (using vdate.shift when Time::Span), respecting vdate.fixed and max_shift/max_shifts.

depended_upon is the set of vdates that others depend on; those are scheduled even when they conflict with a fixed vdate. It defaults to deriving that set from #vdates, and exists so that #build can compute it once instead of once per conflict.

A Scheduled this call displaces is appended to displaced when one is given, so the caller can try to place it again; without one the displacement is final.

origin overrides where max_shift is measured from -- a re-placement after displacement passes the occurrence's original start, so the documented total shift bound holds across the displacement rather than restarting at the start it lost. ameba:disable Metrics/CyclomaticComplexity


[View source]
def vdates : Array(VirtualDate) #

[View source]
def vdates=(vdates : Array(VirtualDate)) #

[View source]
def vdates_with_dependents : Set(VirtualDate) #

Returns the vdates that at least one other vdate depends on.

Scheduling consults this per candidate and per conflict, so it is built in one pass rather than re-scanning every vdate's dependencies each time.


[View source]