After some extensive and independent bikeshedding on the syntax, what follows below is a proposal for how the new syntax might look like and behave. But first, the high-level goals that the proposal attempts to achieve:
- Good readability, although some initial familiarity may be required
- Good usability in the command line (no escaping, no quoting)
- Support for time windows rather than just individual times
- Support for fuzzing inside time windows
- Support for terse and readable recurring events
- Comfortable scheduling of weekly and monthly events
A timer string is composed of one or more event lists.
Each event list defines the weekdays and the time windows in which events may occur. The next event will be scheduled inside the soonest opportunity that matches both one of the provided weekdays and one of the provided time windows. If no weekdays are provided, the default is every day. If no time windows are provided, the default is an arbitrary time in the day.
For example, consider the timer:
Assuming today is Sunday, the next 5 events are, in order:
- Monday 10am
- Monday 15pm
- Friday 10am
- Friday 15pm
- Monday 10am
On the other hand, consider the slightly different timer:
The next 3 events in this case are:
- Monday 10am
- Friday 15pm
- Monday 10am
All of these examples work on a weekly basis, but certain events are better scheduled on a monthly basis. To support that, weekdays may be suffixed by a number that defines the week number inside the month. As an example, the following timer defines two events every month, on the first and third Mondays at 3pm:
As a special case, the 5th week is considered the last one to hold the given day, so that specifying an event on the last Friday of the month, for instance, is done simply as fri5.
In addition to specifying precise weekdays, an interval may be used to define a larger span:
This represents an event per day at 15:00, every workday of the week.
The same syntax also works to define time spans, but the meaning is slightly different. For instance, consider this time span:
It defines an event every Monday that will take place at the earliest chance between 2pm and 4pm. Note the difference in meaning between weekday spans and time spans: weekday spans define an event every day within the span, while time spans define a single event inside the defined span.
That inconsistency may sound uncomfortable from a design perspective, but it’s an important aspect that preserves the readability of these intervals since the most natural way to read mon-fri is “Monday to Friday”, and when people say that they really mean every day. On the other hand, when people are talking about times, the most natural way to read 14:00-16:00 is actually “between 2pm and 4pm”, which represents a single event.
That latter aspect may be changed via an explicit divisor, though, which may be specified either as a count or as a time string. For instance, consider this time span:
This represents two events every day, one in the morning between 8am and 12pm, and another one between 12pm and 16pm.
Specifying the divisor as a time string works as expected. These two timers mean exactly the same thing, for instance:
They both represent an event every hour every day.
Dividing the whole day in N equal parts is such a common task that there’s a shorthand notation for it. The above time spans are exactly equivalent to:
All of the time spans defined so far work similarly in the sense that the start of the span defines the earliest chance in which the event may start, and the end of the span defines the latest chance for the event to have started. For various reasons, though, it’s often useful to introduce some level of randomization inside the time span so that events won’t all start at exactly the same time. This may be achieved by replacing the time span dash character ("-") by a tilde ("~"). For instance:
All of these examples specify the same schedule: 4 events that will take place at a random time inside time spans of 6 hours each.
The ABNF definition for the proposed syntax follows below:
(note: strings are case sensitive)
eventlist = eventset *( ".." eventset )
eventset = wdaylist / timelist / wdaylist "," timelist
wdaylist = wdayset *( "," wdayset )
wdayset = wday / wdayspan
wday = ( "sun" / "mon" / "tue" / "wed" / "thu" / "fri" / "sat" ) [ DIGIT ]
wdayspan = wday "-" wday
timelist = timeset *( "," timeset )
timeset = time / timespan
time = 2DIGIT ":" 2DIGIT
timespan = time ( "-" / "~" ) time [ "/" ( time / count ) ]
count = 1*DIGIT
Refresh configuration option
The proposed syntax is ambiguous with the existing syntax, so we cannot reuse the same configuration setting for it. Some reasonable names for it are “calendar”, “agenda”, or “timer”. That last one seems to be the easiest to use in separate contexts. For example, we’re about to introduce timer services as a follow up from the monthly scheduling work, and they will use the same syntax. It seems natural to have a “timer” field that may be defined using the syntax above. If we follow on with that, I also suggest we use “refresh.timer”. for the core configuration option, and that we then change the “–time” flag in the “snap refresh” command to be “–timer” as well (preserving the old one hidden for compatibility), and its output to mention “timer:” instead of “schedule:”.
A quick check list with those proposals, so we don’t get lost:
Replace “refresh.schedule” with “refresh.timer”, preserving the old one for compatibility.
Replace “snap refresh --time” by “snap refresh --timer”, preserving the old one for compatibility (hidden)
Replace “schedule:” in the output of “snap refresh --timer” by “timer:”, preserving the old one for compatibility if the old setting is present only and --time (no r) was used!
Introduce timer services with “timer:” under the application yaml.
How does that sound?