A friendly language to express and evaluate real-world schedules.
Allows building complex scheduling rulesets:
- to trigger the timely execution of tasks
- to calculate intervals of time to be processed by batch jobs
- to manage the impacts of potential scheduling rule changes on dependent tasks & jobs.
...Because neither cron
or airflow
address this problem.
A playa instant is a specific point in time. Instants are precise to the second. Instant litterals start with @
. Their regular format follows the ISO 8601 syntax. For brevity, smaller units can be omitted and will be assumed to be 0 (or 1 for date & month).
# Fully specified instant
@2018-05-22T12:34:55
# Midnight, 22nd of May 2018
@2018-05-22
TODO Instants alt-specified as the number of seconds since the epoch?
A playa duration is the time interval between two instants. An instant's duration is zero. A closed period's duration is the difference between its bounds. An open period's duration is infinite.
days(3)
months(6)
A playa period represents a range of time. A period is delimited by one or two instants, called bounds.
A period with two bounds is closed. A closed period can be expressed using the [ partial ]
syntax, following ISO8601 format but omitting smaller fields.
# The whole 9th hour of the 22nd day of May 2018
[ 2018-05-22T09 ]
A closed period can also be expressed using the [ bound .. bound ]
syntax.
# From June 2018 (inclusively) until June 2020 (exclusively)
[ @2018-06 .. @2020-06 ]
A period with a single bound is open.
# Any time after July 1st 1978
[ @1978-07-01 .. ]
# Any time before July 1st 1978
[ .. @1978-07-01 ]
A playa period set defines a sequence of periods. A period set can define zero periods (be empty), multiple periods or an infinite number of periods. Like periods, infinite sets are said to be open by opposition to empty or finite sets which are closed.
Infinite period set litterals are delimited by {
and }
and follow ISO8601 format with some date or time fields replaced by *
(fifty shades of cron).
# every christmas day
{ *-12-25 }
# every day from 6AM to 7AM
{ 6:*:* }
# the sixteenth minute after 3PM on the 22nd each month
{ *-*-22T15:16:* }
Period litterals can be used to define single-period sets.
# A single period set
[ 2018-12-23 ]
Period sets can be combined to form more complex sets using operators.
# A closed set of two day-long periods
[ 2018-12-23 ], [ 2018-12-25 ]
# First hour past midnight every day of the month of May 2018
[ 2018-06 ] & { 00:*:* }
Sets can be named by prefixing their definition with an alphanumeric string and a semicolon:
christmas: { *-12-25 }
Sets can be built by referring to other sets by name.
Playa defines a few named sets by default: months (january
to december
), days (monday
to sunday
), noon
and midnight
.
TODO namespaces? overrides?
Playa period sets are combined with operators. Operators transform the sequence or the periods returned by sets, thereby defining a new set.
Operators acting on a single set are said to be unary. Operators combining two sets are said to be binary. Unary operators have precedence over binary operators. Operations can be grouped using parenthesis (
, )
to force precedence.
from returns a forward, inclusive, open period.
# From July 1st 1978
|> @1978-07-01
from @1978-07-01
If applied to a closed range, the range's start instant is used.
# From July 1st 1978
|> [ 1978-07-01 ]
from [ 1978-07-01 ]
after returns a forward, exclusive, open period.
# From Midnight, July 2nd 1978
|>> [ 1978-07-01 ]
after [ 1978-07-01 ]
Applied to an instant, it has the same result as from
.
until returns a backward, exclusive, open period
# Until July 8th 2003
>| @2003-07-08
>| [ 1978-07-01 ]
until [ 1978-07-01 ]
By design, there is no inclusive backward open period operator.
not turns a closed period into two open periods:
- an open period until the period's start
- an open period from the period's start
Periods can overlap.
( A, B, C )
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
A A A A A A A A
B B B B B B B B
C C C C C C C C
Periods sorted and truncated to not overlap. Can be applied forward or backward.
( A ~ B ~ C )
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
A A A A A A A A
B B B B B B
C C C C C C
Overlapping periods (A.stop > B.start
) are merged into one.
( A | B )
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
A A A A D D D D D D C C
B B B B
C C
Consecutive or overlapping periods (A.stop >= B.start
) are merged into one.
( A |+ B )
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
A A A A D D D D D D D D
B B B B
C C
Overlapping periods combine to form multiple ranges only where they all apply.
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A D D
B B B B
Skip every nth period.
( A skip 2)
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A A A A A A
TODO offset? syntax / verb / operator?
Period A shifts towards future by duration B
( A >> 2)
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A
Period A shifts towards past by duration B
( A << 2)
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A
Substracts duration B from period A, starting from period's end.
( A - 1 )
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A A
Substracting from start requires shitfing forward by the same amount that is substracted.
( A - 1 >> 1 )
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A A
TODO Instant: returns the duration from instant A to instant B
Append duration B to period A.
( A + 2 )
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A A A
Prepending is achieved by appending and shifting back by the same amount.
( A + 2 << 2 )
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 0
A A A A A A
Returns the number of duration Bs that can fit in period A's. TODO really?
Sets declared without a name are pushed to the set stack.
Sets are queried by iterating from an instant named origin.
The origin can be set by the user.
The default origin is the current time.
set $origin @2015-06-25
Iteration state is maintained as an instant named now. The value of now is altered by iterators.
When sub-iteration is performed, now is copied to the iterator stack.
When the origin is set, it also resets to the now.
get $now
The last period returned by the iterator. If no iterator was called, this is empty.
Describes the set on top of the stack.
Iterate forward.
from @2018-08-04
& @09:00 & (monday, tuesday)
print next
>>> 2018-08-06T09:00:00
Iterate backward.
until @2018-08-04
@09:00 & (monday, tuesday)
print prev
>>> 2018-07-31T09:00:00
- Identation is ignored
- Lines begining with
#
are ignored (comments) - Statements delimited by
;
or newline if no () are opened.
fn = name(params)
every(n, r): $r % $n
every(3, monday)
blob: (
blab(z): z + 4
)
statutory_holidays: (
christmas
easter
)
vacations_length: weeks(2)
vacations: [ 2018-07%2w ] + vacations_length
weekend: sat | sun
business_hours: (
(mon | tue | wed) & [ @09-@18 ]
(thu | fri) & [ @09-@21 ]
weekend & [ @10-@17 ]
!vacations
!statutory_holidays
)