class VirtualDate

Overview

VirtualDate builds on VirtualTime to represent due/omit rules plus higher-level scheduling semantics.

Included Modules

Defined in:

virtualdate.cr

Constant Summary

MAX_STRETCH_STEPS = 64

Furthest #due_stretch_end follows a stretch over the due times that continue it.

VERSION = [VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION].join('.')
VERSION_MAJOR = 1
VERSION_MINOR = 11
VERSION_REVISION = 0

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) #

[View source]
def self.new(id : String = "") #

[View source]

Class Method Detail

def self.yaml_keys : Array(String) #

All mapping keys .from_yaml reads, derived from the serialized properties at compile time so YamlValidator's allowed-key check cannot drift from the class. (A method rather than a constant because instance_vars is only available inside method bodies.)


[View source]

Instance Method Detail

def begin : VirtualTime::TimeOrVirtualTime | Nil #

Absolute begin date/time. Item is never "on" before this date.


[View source]
def begin=(begin __arg0 : VirtualTime::TimeOrVirtualTime | Nil) #

Absolute begin date/time. Item is never "on" before this date.


[View source]
def clamp_to_begin=(clamp_to_begin : Bool) #

Governs what the Scheduler does with an occurrence that is already running when an absolute (Time) #begin arrives -- e.g. due: hour 8..12 with begin: 10:30. By default the occurrence is dropped: it began before the vdate existed. With this set, it is clamped to start at #begin instead, the same way an occurrence already running at the window's from is clamped to from. Has no effect on a VirtualTime-pattern #begin, which is matched as a pattern rather than used as a boundary.


[View source]
def clamp_to_begin? : Bool #

Governs what the Scheduler does with an occurrence that is already running when an absolute (Time) #begin arrives -- e.g. due: hour 8..12 with begin: 10:30. By default the occurrence is dropped: it began before the vdate existed. With this set, it is clamped to start at #begin instead, the same way an occurrence already running at the window's from is clamped to from. Has no effect on a VirtualTime-pattern #begin, which is matched as a pattern rather than used as a boundary.


[View source]
def deadline : VirtualTime::TimeOrVirtualTime | Nil #

Hard deadline — vdate MUST finish before this time or fails scheduling


[View source]
def deadline=(deadline : VirtualTime::TimeOrVirtualTime | Nil) #

Hard deadline — vdate MUST finish before this time or fails scheduling


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

Optional dependencies: scheduler will try to place this vdate after all dependencies. (Used only by Scheduler; VirtualDate itself does not enforce this.)


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

Optional dependencies: scheduler will try to place this vdate after all dependencies. (Used only by Scheduler; VirtualDate itself does not enforce this.)


[View source]
def depends_on_ids : Array(String) #

Serialized form

Written out by #on_to_yaml, not by the generated serializer; see the note there.


[View source]
def depends_on_ids=(depends_on_ids : Array(String)) #

Serialized form

Written out by #on_to_yaml, not by the generated serializer; see the note there.


[View source]
def due : Array(VirtualTime) #

List of VirtualTimes on which the vdate is "on"/due/active.


[View source]
def due=(due : Array(VirtualTime)) #

List of VirtualTimes on which the vdate is "on"/due/active.


[View source]
def due_on?(time : VirtualTime::TimeOrVirtualTime = Time.local, times = @due) #

Returns whether any of the vdate's #due rules is on at time.

An empty list means the vdate is always due.


[View source]
def due_on_any_date?(time : VirtualTime::TimeOrVirtualTime = Time.local, times = @due) #

[View source]
def due_on_any_time?(time : VirtualTime::TimeOrVirtualTime = Time.local, times = @due) #

[View source]
def duration : Time::Span #

[View source]
def duration=(duration : Time::Span) #

[View source]
def end : VirtualTime::TimeOrVirtualTime | Nil #

Absolute end date/time. Item is never "on" after this date.


[View source]
def end=(end __arg0 : VirtualTime::TimeOrVirtualTime | Nil) #

Absolute end date/time. Item is never "on" after this date.


[View source]
def fixed=(fixed : Bool) #

If true, Scheduler treats this vdate as non-movable due to conflicts (still movable by omit-rescheduling rules if you keep shift != false).


[View source]
def fixed? : Bool #

If true, Scheduler treats this vdate as non-movable due to conflicts (still movable by omit-rescheduling rules if you keep shift != false).


[View source]
def flags : Set(String) #

Flags/categories (e.g. meeting, task, or passive/active/unimportant, or color-coded, or anything). Used by Scheduler for parallelism.


[View source]
def flags=(flags : Set(String)) #

Flags/categories (e.g. meeting, task, or passive/active/unimportant, or color-coded, or anything). Used by Scheduler for parallelism.


[View source]
def id : String #

Identifier (important for dependencies)


[View source]
def id=(id : String) #

Identifier (important for dependencies)


[View source]
def matches_any?(time : VirtualTime::TimeOrVirtualTime, times : Array(VirtualTime), default) #

Returns default if times is empty, true if at least one of them matches time outright, and nil otherwise.

A rule has to match on both its date and its time part. Taking the date from one rule and the time from another would report a match that none of the rules on its own expresses -- and would, for instance, make an #omit of "December 25" together with "12:00" omit every instant of the year, since the first rule constrains no time and the second no date.


[View source]
def matches_any_date?(time : VirtualTime::TimeOrVirtualTime, times : Array(VirtualTime), default) #

Returns default if times is empty, true if at least one of them matches time outright, and nil otherwise.

A rule has to match on both its date and its time part. Taking the date from one rule and the time from another would report a match that none of the rules on its own expresses -- and would, for instance, make an #omit of "December 25" together with "12:00" omit every instant of the year, since the first rule constrains no time and the second no date.

Considers only the date part of each rule.


[View source]
def matches_any_time?(time : VirtualTime::TimeOrVirtualTime, times : Array(VirtualTime), default) #

Returns default if times is empty, true if at least one of them matches time outright, and nil otherwise.

A rule has to match on both its date and its time part. Taking the date from one rule and the time from another would report a match that none of the rules on its own expresses -- and would, for instance, make an #omit of "December 25" together with "12:00" omit every instant of the year, since the first rule constrains no time and the second no date.

Considers only the time part of each rule.


[View source]
def max_shift : Time::Span | Nil #

Max amount of total time by which vdate can be shifted before it's considered unschedulable (false)


[View source]
def max_shift=(max_shift : Time::Span | Nil) #

Max amount of total time by which vdate can be shifted before it's considered unschedulable (false)


[View source]
def max_shifts : Int32 #

Max amount of shift attempts, before it's considered unschedulable (false)


[View source]
def max_shifts=(max_shifts : Int32) #

Max amount of shift attempts, before it's considered unschedulable (false)


[View source]
def next_on(after : Time, *, max_candidates : Int32 = 1000) : Time | Nil #

Returns the start of the earliest occurrence strictly after after, or nil when there is none: the series has ended, #due is empty, or nothing was found within max_candidates occurrences.

An occurrence is a contiguous stretch of due time (see MatchRun): a due time naming only a day is due for the whole of it, and consecutive due days run together into one occurrence. A rule meant to recur once a day therefore names a time of day too (hour: 0), and a day-granular caller asks from the end of the current day to get the next one. An after that falls inside an occurrence is answered with the one following it.

Each occurrence start is settled with #resolve: one that is on as asked is the answer, one that is omitted and shifted counts at the time it shifts to, and one that is omitted without a shift is passed over. A shifted start is only accepted once no later one settles earlier than it.

Only occurrences starting after after are considered. One that started at or before it and shifts past it is not reported, even though #on? answers true for its shifted time: finding it would mean walking back over every occurrence within the total shift bound.


[View source]
def omit : Array(VirtualTime) #

List of VirtualTimes on which the vdate should be "omitted".


[View source]
def omit=(omit : Array(VirtualTime)) #

List of VirtualTimes on which the vdate should be "omitted".


[View source]
def omit_on?(time : VirtualTime::TimeOrVirtualTime = Time.local, times = @omit) #

Returns whether any of the vdate's #omit rules covers time.

An empty list means the vdate is never omitted.


[View source]
def omit_on_dates?(time : VirtualTime::TimeOrVirtualTime = Time.local, times = @omit) #

[View source]
def omit_on_times?(time : VirtualTime::TimeOrVirtualTime = Time.local, times = @omit) #

[View source]
def on : Nil | Bool | Time::Span #

Fixed override of #on? for this vdate. If set, takes precedence over begin/end/due/omit. Same union as #shift.

Written out by #on_to_yaml, not by the generated serializer; see the note there.


[View source]
def on=(on : Nil | Bool | Time::Span) #

Fixed override of #on? for this vdate. If set, takes precedence over begin/end/due/omit. Same union as #shift.

Written out by #on_to_yaml, not by the generated serializer; see the note there.


[View source]
def on?(time : Time, *, max_shift = @max_shift, max_shifts = @max_shifts) : Bool #

Checks if the vdate is effectively scheduled at time. Returns:

  • true if it is on at time directly, OR due at some base time that resolves (via shifting) to exactly time
  • false otherwise (not applicable, not scheduled, or unschedulable)

Unlike #strict_on?, this method never returns nil or Time::Span.

(For omit-driven shifting (Time::Span), we can search candidate base times by shifting back in the opposite direction. This is deterministic, bounded by max_shifts/max_shift.)


[View source]
def parallel : Int32 #

Max number of overlapping vdates that share at least one common flag with this vdate. Example: flags={meeting}, parallel=2 means up to 2 meetings can overlap.


[View source]
def parallel=(parallel : Int32) #

Max number of overlapping vdates that share at least one common flag with this vdate. Example: flags={meeting}, parallel=2 means up to 2 meetings can overlap.


[View source]
def priority : Int32 #

Higher wins conflict resolution when Scheduler must choose.


[View source]
def priority=(priority : Int32) #

Higher wins conflict resolution when Scheduler must choose.


[View source]
def resolve(time : VirtualTime::TimeOrVirtualTime = Time.local, *, max_shift = @max_shift, max_shifts = @max_shifts, hint = time.is_a?(Time) ? time : Time.local) : Time | Bool | Nil #

Resolves the asked VirtualTime to an effective scheduled Time.

Returns:

  • Time : resolved scheduled time
  • true : scheduled "as asked" (same as returning time, but preserved for symmetry)
  • nil : not scheduled
  • false : due but unschedulable

Notes:

  • For Time input, returned Time preserves the timezone/location of the input time.
  • For VirtualTime input, uses hint for materialization as in legacy #on?.

[View source]
def resolve_dependencies!(index : Hash(String, VirtualDate)) #

[View source]
def shift : Nil | Bool | Time::Span #

Decision about an vdate to make if it falls on an omitted date/time.

Allowed values are:

  • nil: treat the vdate as non-applicable/not-scheduled on the specified date/time
  • false: treat the vdate as not due due to falling on an omitted date/time, after a reschedule was not attempted or was not able to find another spot
  • true: treat the vdate as due regardless of falling on an omitted date/time
  • Time::Span: shift the scheduled date/time by specified time span. Can be negative or positive.

Written out by #on_to_yaml, not by the generated serializer; see the note there.


[View source]
def shift=(shift : Nil | Bool | Time::Span) #

Decision about an vdate to make if it falls on an omitted date/time.

Allowed values are:

  • nil: treat the vdate as non-applicable/not-scheduled on the specified date/time
  • false: treat the vdate as not due due to falling on an omitted date/time, after a reschedule was not attempted or was not able to find another spot
  • true: treat the vdate as due regardless of falling on an omitted date/time
  • Time::Span: shift the scheduled date/time by specified time span. Can be negative or positive.

Written out by #on_to_yaml, not by the generated serializer; see the note there.


[View source]
def stagger : Time::Span | Nil #

Optional staggered parallel scheduling


[View source]
def stagger=(stagger : Time::Span | Nil) #

Optional staggered parallel scheduling


[View source]
def strict_on?(time : VirtualTime::TimeOrVirtualTime = Time.local, *, max_shift = @max_shift, max_shifts = @max_shifts, hint = time.is_a?(Time) ? time : Time.local) : Nil | Bool | Time::Span #

Checks whether the vdate is "on" on the specified date/time.

Return values: nil - vdate is not "on" / not due / not scheduled true - vdate is "on" (due and not omitted) false - vdate is due but omitted and no reschedule requested or possible Time::Span - the span to add to asked date to reach earliest/closest time when vdate is "on"

IMPORTANT: If the vdate is rescheduled away from the asked time, #strict_on? returns a Time::Span, but querying #strict_on? at the rescheduled time will not necessarily return true. Use #resolve or #on? to return a true value for shifted dates/times. ameba:disable Metrics/CyclomaticComplexity


[View source]
def within_bounds?(time : Time) : Bool #

Returns whether time falls within #begin and #end.

A bound that is a VirtualTime is matched as a pattern rather than used as an ordering bound, the way #strict_on? treats it.


[View source]