Reference
Yarunoka
Section titled “Yarunoka”final readonly class Yrnk
The root of the typed tree of a Yrnk document. The unit of exchange between the DSL and objects — the output of YrnkParser and the input of YrnkBuilder. Not something an application runtime carries around (in an execution context, use YrnkEvaluator + YrnkSchedule).
The declared names ride here rather than on the calendar: they are what the whole document leaves to its host, and the names they cover are written on both sides of the document (a calendar definition and the days axis of a schedule).
Constants
Section titled “Constants”SUPPORTED_VERSION
Properties
Section titled “Properties”array $schedulesDateTimeZone $timezoneYrnkCalendar $calendararray $resolvers
Methods
Section titled “Methods”__construct(string $version, DateTimeZone $timezone, YrnkCalendar $calendar, array $schedules, array $resolvers)
YrnkBuilder
Section titled “YrnkBuilder”final class YrnkBuilder
The mirror image of YrnkParser. Yrnk → a Yrnk document (an array / JSON). Round-tripping is the identity: building a Yrnk parsed from the DSL yields the original array representation.
Methods
Section titled “Methods”__construct(YrnkScheduleBuilder $scheduleBuilder, YrnkCalendarBuilder $calendarBuilder)build(Yrnk $document): arraytoJson(Yrnk $document): string
YrnkDate
Section titled “YrnkDate”final class YrnkDate extends DateTimeImmutable
A whole day of the schedule — the value an all-day occurrence is answered with. It stands at the start of that day on the document timezone’s clock, which makes it a DateTimeInterface any other library accepts; YrnkDateTime is the timed counterpart, and the two are told apart by type rather than by value (an all-day day and a timed point at its 00:00 are distinct occurrences).
The start of the day resolves like any other wall-clock point (RFC 5545 3.3.5), so in a zone whose midnight is skipped by a transition the value stands slightly later in the day, and on the rare occasion that a zone skips a whole day, it stands on the day that followed — that resulting day is what the value means.
YrnkDateTime
Section titled “YrnkDateTime”final class YrnkDateTime extends DateTimeImmutable
A point in time on the document timezone’s clock — the value a timed occurrence is answered with, and the type of a schedule’s validity range. YrnkDate is the all-day counterpart.
Seconds are accepted even though no literal in the DSL can spell them: the interval every ([“every”, N, “second”]) lands occurrences on a non-zero second, and this type carries both those and the from / until literals. Rejecting seconds in a document is therefore the parser’s job, not this type’s.
YrnkEvaluator
Section titled “YrnkEvaluator”final class YrnkEvaluator
The evaluator. A service holding configuration (the definitions and the timezone), asked questions “about a schedule” by handing it a YrnkSchedule. There are three questions — the judgment at a point (matches), the judgment over a period (hasMatchIn), and the enumeration (occurrencesIn). It reads the tree and interprets it with the internal calculators and matchers the content calls for (the layer model, calendar arithmetic, hierarchical evaluation, grid expansion). Questions about the top-level OR (the schedules list) are composed by the caller asking per branch (any for the judgments; a merge of the per-branch lists for the enumeration).
“Should this fire” does not live here. Firing, catch-up, and grace are expressed by the caller through how it cuts the period it asks about (hasMatchIn(last_run_at, now) is the firing decision). Definitions resolve per question and are not carried between them, so an answer always rests on what the resolvers say at the time it is asked.
Methods
Section titled “Methods”__construct(YrnkCalendar $calendar, DateTimeZone $timezone)fromYrnk(Yrnk $document): self— One built from a whole document, for a caller that has one. The two things this needs are the document’s, and taking them apart to hand them back in is where a calendar can end up read on a timezone that is not the one it was written against.matches(YrnkSchedule $schedule, DateTimeInterface $at): bool— Is the given instant an occurrence? For a timed occurrence the answer is instant equality — the given instant, ignoring anything finer than a second (no scheduled point is finer), equals the occurrence’s instant. The comparison is between instants, never wall-clock values. An all-day occurrence matches on the day alone: the answer is yes for every instant whose local date — read in the configured timezone — is that day. Granularity adjustments (rounding to minutes and the like) are done by the caller on the value it passes.hasMatchIn(YrnkSchedule $schedule, DateTimeInterface $after, DateTimeInterface $through): bool— Is there a scheduled point after $after, through $through? The substance of a firing decision — “is there a scheduled point after the previous run, through now?” maps onto it directly. A point exactly at $after does not count — it was the previous judgment’s “now”, already counted; a point exactly at $through counts in this judgment, not the next. Each judgment’s “now” becomes the next one’s start, so every timed point is seen exactly once. An all-day occurrence counts when its day overlaps the period, however late in the day it is asked: a day is due for as long as it lasts. That does not make it a timed occurrence at 00:00.occurrencesIn(YrnkSchedule $schedule, DateTimeInterface $from, DateTimeInterface $through): array— Which occurrences lie from $from through $through? The answer is the occurrence set cut to [$from, $through] — every timed occurrence whose instant lies in the closed interval, and every all-day occurrence whose day overlaps it. Timed occurrences are answered as instants on the configured timezone’s clock, all-day occurrences as dates (YrnkDate); the two kinds stay distinct, and the answer is in ascending order, an all-day occurrence taking the start of its day as its place in the order. Unlike the judgment over a period — whose start is excluded because that instant was the previous judgment’s “now” — an enumeration has no previous window: the caller names two instants, and both are part of what it names. Adjacent windows sharing a boundary instant therefore both contain a point exactly on it, and a caller that means to exclude a boundary moves it.
YrnkParser
Section titled “YrnkParser”final class YrnkParser
Parses a Yrnk document (RawYrnk) into a Yrnk. Delegates each element of schedules to the YrnkScheduleParser, and validates here what can only be validated with the whole document and its definitions together — resolvability of every name, the data behind the built-in vocabulary, and the declarations the document makes.
Methods
Section titled “Methods”__construct(YrnkResolverContainer $resolverContainer, YrnkScheduleParser $scheduleParser, YrnkCalendarParser $calendarParser)parse(string|array $input): Yrnk
YrnkSchedule
Section titled “YrnkSchedule”final readonly class YrnkSchedule
The definition corresponding 1:1 to one element of the DSL’s schedules[]. Carries structure only; evaluation happens by handing it to YrnkEvaluator. The date axes (years / months / days) combine with AND, and null means “no restriction on that axis”. from / until is the validity range — a boundary that clips this schedule’s set of points to [from, until), not a recurrence condition.
Yarunoka\Calendar
Section titled “Yarunoka\Calendar”YrnkBusinessDaysDateSet
Section titled “YrnkBusinessDaysDateSet”final class YrnkBusinessDaysDateSet extends Yarunoka\Calendar\YrnkDateSet
The “we work this day” definition. The top layer of the layer model — it overrides everything below (a built-in definition).
YrnkBusinessHolidaysDateSet
Section titled “YrnkBusinessHolidaysDateSet”final class YrnkBusinessHolidaysDateSet extends Yarunoka\Calendar\YrnkDateSet
The organization’s own closures. In the layer model, the layer above holidays (a built-in definition).
YrnkBusinessHours
Section titled “YrnkBusinessHours”final readonly class YrnkBusinessHours
The window list behind the business_hour vocabulary (a built-in definition). Kept in written order. Overlapping windows would be the quiet accident of duplicated grid points, so the invariant rejects them (the intervals are half-open, so touching windows do not overlap and are legal).
Properties
Section titled “Properties”array $windows
Methods
Section titled “Methods”__construct(array $windows)
YrnkCalendar
Section titled “YrnkCalendar”final readonly class YrnkCalendar
The definitions part of a Yrnk document. Made of the built-in definitions (the five reserved keys, carrying the layer-model semantics) and date_sets (the open namespace), together with what the names it references resolve to.
The bindings ride along because a definition naming a resolver is only half a definition without them: whoever holds the calendar holds everything needed to answer from it, and there is no second place to hand them over and forget.
A date set is written as either of the two forms the DSL accepts: the date list itself, or the name of what resolves it. The name is written as the name — there is no wrapper for it, because the wrapper would carry nothing the string does not. Whether the name is an entry of date_sets or one the host binds makes no difference to where it may be written: the two share one namespace.
null means “undefined” — a document that uses vocabulary or references requiring that definition is a parse error. This is distinct from an explicit empty list (the statement that there are no such days). Only an undefined workweek means the default (Mon–Fri) instead.
Properties
Section titled “Properties”YrnkHolidaysDateSet|string|null $holidaysYrnkBusinessHolidaysDateSet|string|null $businessHolidaysYrnkBusinessDaysDateSet|string|null $businessDays?YrnkWorkweek $workweek?YrnkBusinessHours $businessHoursarray $dateSetsYrnkResolverContainer $resolverContainer
Methods
Section titled “Methods”__construct(YrnkHolidaysDateSet|string|null $holidays, YrnkBusinessHolidaysDateSet|string|null $businessHolidays, YrnkBusinessDaysDateSet|string|null $businessDays, ?YrnkWorkweek $workweek, ?YrnkBusinessHours $businessHours, array $dateSets, YrnkResolverContainer $resolverContainer)
YrnkCalendarBuilder
Section titled “YrnkCalendarBuilder”final class YrnkCalendarBuilder
The mirror image of CalendarParser. YrnkCalendar node → RawCalendar. A resolver name reference comes out as the name itself (output that preserves the intent, on the premise that the reader holds the same resolver), so the output is what the input was in every case.
Methods
Section titled “Methods”build(YrnkCalendar $calendar, DateTimeZone $timezone): array
YrnkCalendarParser
Section titled “YrnkCalendarParser”final class YrnkCalendarParser
The parser for the definitions part (RawCalendar). The top level is the closed set of reserved keys (the built-in definitions); under date_sets is the open namespace.
Methods
Section titled “Methods”parse(mixed $raw, DateTimeZone $timezone, YrnkResolverContainer $resolverContainer): YrnkCalendar
YrnkDateSet
Section titled “YrnkDateSet”class YrnkDateSet
A written date list, and the base of every kind of one. On its own it is a named date list of the open namespace (an entry of calendar.date_sets): a flat “membership in a set” that takes no part in the layers. The built-in definitions extend it with the layer meaning their key carries, and nothing else separates them — a date set is a date set, whichever position holds it.
The subclasses exist so a position can refuse the wrong one: a calendar takes YrnkHolidaysDateSet where it means holidays, and no amount of shared implementation lets the business_days list land there.
This is where a document holds the dates it names. A name whose dates come from elsewhere is left to a resolver instead, and is written as the name — that form needs no type of its own.
Properties
Section titled “Properties”array $dates
YrnkHolidaysDateSet
Section titled “YrnkHolidaysDateSet”final class YrnkHolidaysDateSet extends Yarunoka\Calendar\YrnkDateSet
The holidays definition — public holidays. In the layer model, the “closed by default” layer (a built-in definition).
YrnkWorkweek
Section titled “YrnkWorkweek”final readonly class YrnkWorkweek
The weekly pattern (the day-of-week set that sets the working default). The bottom layer of the layer model. Left undefined (null on YrnkCalendar), the default is Mon–Fri.
Properties
Section titled “Properties”array $days
Methods
Section titled “Methods”__construct(array $days)
Yarunoka\Exceptions
Section titled “Yarunoka\Exceptions”ExceptionInterface
Section titled “ExceptionInterface”interface ExceptionInterface extends Throwable
Every exception this library throws. Implemented rather than inherited, so each exception is free to extend the SPL type that describes what went wrong — a caller can catch this to mean “Yarunoka failed”, or catch the SPL type to treat it alongside failures of the same kind from elsewhere.
InvalidCalendarDataException
Section titled “InvalidCalendarDataException”class InvalidCalendarDataException extends UnexpectedValueException implements Yarunoka\Exceptions\ExceptionInterface
Injected calendar data (date lists, time windows, or a weekly pattern) violates its contract.
What a resolver handed back, checked at the moment it was called — so it says nothing about whether the document is well formed.
InvalidValueException
Section titled “InvalidValueException”class InvalidValueException extends InvalidArgumentException implements Yarunoka\Exceptions\ExceptionInterface
A value such as a date or a time of day violates the literal format rules.
A logic error rather than a runtime one: every path that feeds document data into a node repacks this into an InvalidYrnkException or an InvalidCalendarDataException, so it reaches a caller only when the caller built the node itself with an argument that cannot be right.
InvalidYrnkException
Section titled “InvalidYrnkException”class InvalidYrnkException extends RuntimeException implements Yarunoka\Exceptions\ExceptionInterface
A document validation error: the structure or a value of a Yrnk document violates the language (unknown key, malformed shape, or invalid value).
The base of everything that means “the document is wrong”, so catching it covers that whole family. A document arrives at runtime, which is why this side of the hierarchy is not a logic error.
MissingCalendarDataException
Section titled “MissingCalendarDataException”class MissingCalendarDataException extends Yarunoka\Exceptions\InvalidYrnkException
A definition required by a calendar word has not been injected. Raised at build or resolution time — never a silent “no match”.
ReservedNameException
Section titled “ReservedNameException”class ReservedNameException extends Yarunoka\Exceptions\InvalidYrnkException
A name that cannot be registered as a definition name (it collides with a reserved word, or it looks like a literal — digits only, date-shaped, or time-shaped).
UndefinedNameException
Section titled “UndefinedNameException”class UndefinedNameException extends Yarunoka\Exceptions\InvalidYrnkException
A name referenced by a schedule exists neither in the built-in vocabulary nor among the user definitions. A resolver name the host never bound is the other story and has its own exception.
UnregisteredResolverException
Section titled “UnregisteredResolverException”class UnregisteredResolverException extends RuntimeException implements Yarunoka\Exceptions\ExceptionInterface
A definition names a resolver the host never bound. The document is well formed and says what it wants; what is missing is the binding handed to the parser or the evaluator, which is why this is not a document error.
UnsupportedVersionException
Section titled “UnsupportedVersionException”class UnsupportedVersionException extends Yarunoka\Exceptions\InvalidYrnkException
A declared version this implementation does not know. Rejected rather than silently interpreted — a safeguard against the PHP and TS implementations evolving separately.
Yarunoka\Resolvers
Section titled “Yarunoka\Resolvers”YrnkBusinessDaysResolverInterface
Section titled “YrnkBusinessDaysResolverInterface”interface YrnkBusinessDaysResolverInterface extends Yarunoka\Resolvers\YrnkResolverInterface
The contract for supplying the business_days layer (a marker that identifies the layer).
YrnkBusinessHolidaysResolverInterface
Section titled “YrnkBusinessHolidaysResolverInterface”interface YrnkBusinessHolidaysResolverInterface extends Yarunoka\Resolvers\YrnkResolverInterface
The contract for supplying the business_holidays layer (a marker that identifies the layer).
YrnkHolidaysResolverInterface
Section titled “YrnkHolidaysResolverInterface”interface YrnkHolidaysResolverInterface extends Yarunoka\Resolvers\YrnkResolverInterface
The contract for supplying the holidays layer. The body is the same as the base contract; DI container bindings are keyed by class name, so this marker distinguishes by type which layer is being supplied.
YrnkResolverContainer
Section titled “YrnkResolverContainer”final class YrnkResolverContainer
What the host binds the document’s resolver names to. A calendar carries one, so a binding is handed over once and reaches both the reference validation and the evaluation.
Holding the bindings in a container rather than a plain array is what makes a duplicate name an error: a PHP array literal keyed by name keeps the last of two entries and says nothing, and every place that accepts bindings would otherwise have to check for itself.
Methods
Section titled “Methods”__construct()add(string $name, YrnkResolverInterface $resolver): void— Binds a name to what resolves it. The name is the document’s, so it is held to the rules every name is held to — a name that can be bound is a name that can be written.has(string $name): boolget(string $name): ?YrnkResolverInterface
YrnkResolverInterface
Section titled “YrnkResolverInterface”interface YrnkResolverInterface
The contract for supplying a date set — what a resolver name in the definitions resolves to. The range asked for is the range the answer has to cover; dates outside it are ignored, and dates missing inside it read as “not in this set”. The format of the return value is validated by the evaluating side.
An implementation is called again whenever a range it has not covered is reached, so it is free to compute only what it is asked for. Holding results across calls is the implementation’s own decision.
Methods
Section titled “Methods”resolve(YrnkDate $from, YrnkDate $through): array
Yarunoka\Schedule
Section titled “Yarunoka\Schedule”AllDay
Section titled “AllDay”final readonly class AllDay implements Yarunoka\Schedule\TimesSpecInterface
An all-day occurrence ({“allday”: true}) — the explicit statement of a day-level occurrence that carries no time.
BusinessHourRef
Section titled “BusinessHourRef”final readonly class BusinessHourRef
The “business_hour” reference of between. The actual windows (the business_hours definition) are resolved by YrnkEvaluator.
DateSetRef
Section titled “DateSetRef”final readonly class DateSetRef implements Yarunoka\Schedule\DayAtomInterface
A name atom of the days axis — a reference to a date set. Which of the two kinds the name is (an entry of calendar.date_sets, or one the host binds to a resolver) makes no difference here: the reference is a self-contained value, and that the referent exists is validated by the holder of the definitions (YrnkParser / YrnkEvaluator).
DayAtomInterface
Section titled “DayAtomInterface”interface DayAtomInterface
An atom of a day expression. Marker for the values that can appear in the days enumeration, as a shift landing condition, or as an if condition in the DSL. Nodes carry structure only; evaluation is done by YrnkEvaluator.
DayCycle
Section titled “DayCycle”final readonly class DayCycle implements Yarunoka\Schedule\DayAtomInterface
The day-cycle tuple atom ([“every”, 2, “day”] — every N days). The matching days count the date of the schedule’s from as day one, so a schedule that uses this atom requires from (an invariant of YrnkSchedule). Allowed only as an element of the days enumeration (not as a shift landing condition or an if condition).
DayExpression
Section titled “DayExpression”final readonly class DayExpression
The day expression of days (an enumeration of atoms, combined with OR). Kept in written order.
EveryGrid
Section titled “EveryGrid”final readonly class EveryGrid implements Yarunoka\Schedule\TimesSpecInterface
The clock grid ({“every”: [90, “minute”], “between”: …}). The count and the unit are kept as written (not folded into seconds, so that round-tripping is the identity). A null between means the whole day [00:00, 24:00).
EverySequence
Section titled “EverySequence”final readonly class EverySequence implements Yarunoka\Schedule\TimesSpecInterface
The from-anchored interval sequence ({“from”: …, “every”: [36, “hour”]}). The points are from + k × interval (k = 0, 1, 2, …), and it keeps counting across days (unlike the times clock grid there is no per-day re-anchoring). The count and the unit are kept as written. The count has no upper bound — the grid’s one-day cap is a consequence of its per-day re-anchoring semantics and does not apply to a from-anchored sequence.
FixedTimes
Section titled “FixedTimes”final readonly class FixedTimes implements Yarunoka\Schedule\TimesSpecInterface
An enumeration of fixed times. Kept in written order so that round-tripping is the identity (sorting is evaluation’s job).
IfGuard
Section titled “IfGuard”final readonly class IfGuard
The if modifier — filtering by the base day itself or a neighbour. shift moves the day; if filters without moving. A null direction means “the day itself”. Evaluation is done by YrnkEvaluator.
LastDayOfMonth
Section titled “LastDayOfMonth”final readonly class LastDayOfMonth implements Yarunoka\Schedule\DayAtomInterface
The end-of-month atom. The end of the month is the only month boundary that moves, so it is the one special word.
MonthDay
Section titled “MonthDay”final readonly class MonthDay implements Yarunoka\Schedule\DayAtomInterface
A day-of-month atom (the nth day of every month).
OrdinalWeekday
Section titled “OrdinalWeekday”final readonly class OrdinalWeekday implements Yarunoka\Schedule\DayAtomInterface
The ordinal-tuple atom ([“3rd”, “mon”] / [“last”, “fri”] — the third Monday / last Friday of the month).
final readonly class Shift
The shift modifier — rounding. Takes each base day selected by the days condition and moves it in a fixed direction until the landing condition holds. orSame is the inclusive / exclusive distinction (the same as java.time’s previous / previousOrSame). Evaluation is done by YrnkEvaluator.
TimesSpecInterface
Section titled “TimesSpecInterface”interface TimesSpecInterface
Marker for the time part of a schedule: the two spellings of times (FixedTimes | EveryGrid), AllDay, and EverySequence (the interval every). A schedule has exactly one of times / allday / every.
Weekday
Section titled “Weekday”final readonly class Weekday implements Yarunoka\Schedule\DayAtomInterface
A day-of-week atom (every given weekday).
YrnkScheduleBuilder
Section titled “YrnkScheduleBuilder”final class YrnkScheduleBuilder
The mirror image of YrnkScheduleParser. YrnkSchedule → RawSchedule (one element of the DSL’s schedules[]).
Methods
Section titled “Methods”build(YrnkSchedule $schedule): array
YrnkScheduleParser
Section titled “YrnkScheduleParser”final class YrnkScheduleParser
Parses one element of the DSL’s schedules[] (RawSchedule) into a YrnkSchedule, fully parsed and validated as such. That names are not checked for resolvability here is not a limitation but a property of the data: a YrnkSchedule carries no definitions (resolving names is the job of YrnkParser / YrnkEvaluator).
Methods
Section titled “Methods”parse(array $raw, DateTimeZone $timezone): YrnkSchedule
Yarunoka\Time
Section titled “Yarunoka\Time”TimeOfDay
Section titled “TimeOfDay”final readonly class TimeOfDay
A time of day expressed as seconds elapsed since midnight. Time literals in the DSL are zero-padded HH:MM only (“24:00” is a token allowed only as a window end and is not a time of day).
YrnkTimeWindow
Section titled “YrnkTimeWindow”final readonly class YrnkTimeWindow
A time window as the half-open interval [start, end). Only the end accepts “24:00” (= 86400 seconds) as the end of the day. Windows crossing midnight (start >= end) cannot be expressed.
Constants
Section titled “Constants”END_OF_DAY_SECONDS
Properties
Section titled “Properties”int $startSecondsint $endSeconds
Methods
Section titled “Methods”fromStrings(string $start, string $end): selftoStrings(): array— The [HH:MM, HH:MM] notation paired with fromStrings (used by the builder). An end at the end of the day becomes “24:00”.
Yarunoka\Vocabulary
Section titled “Yarunoka\Vocabulary”YrnkDayName
Section titled “YrnkDayName”enum YrnkDayName: string
A day-of-week name. An atom of the DSL and, at the same time, the representation of a date’s day of week.
MonTueWedThuFriSatSun