Cron
A visual builder for a cron expression, read back in plain language, with the next few runs worked out.
Building a schedule
The tabs are the familiar phrasings of a schedule; each one writes the expression underneath. The expression stays visible and editable throughout - the builder is meant to teach cron, not hide it - and anything the tabs cannot say can still be typed by hand.
At 09:00, only on Monday, Tuesday, Wednesday, Thursday and Friday
<VetoCron @bind-Value="_schedule" />What the expression actually means
Every expression is read back as a sentence, and the next runs are worked out from it. Between them they catch the mistakes a cron expression is famous for - the schedule that looks daily and fires every minute, or the one that looks monthly and never fires at all.
* * * * *
Every minute*/15 * * * *
Every 15 minutes0 9 * * 1-5
At 09:00, only on Monday, Tuesday, Wednesday, Thursday and Friday0 9,17 * * *
At 09:00 and 17:000 0 1 * *
At 00:00, on the 1st0 0 1 1 *
At 00:00, on the 1st, only in January@* The describer is usable on its own, without the builder: *@
@VetoCronDescriber.Describe("0 9 * * 1-5")
@* "At 09:00, only on Monday, Tuesday, Wednesday, Thursday and Friday" *@
@* So is the scheduler: *@
VetoCronExpression.TryParse("0 9 * * 1-5", out var cron, out var error);
var next = VetoCronSchedule.Next(cron, DateTime.Now, 5);The day trap
When both day-of-month and day-of-week are restricted, cron fires when EITHER matches - not both. It surprises nearly everyone, so the builder says so rather than letting the expression quietly mean something else. Notice the next runs: the 1st, and every Monday.
At 00:00, on the 1st or only on Monday
Both day fields are set, so this fires when either matches - not only when both do.
@* Fires on the 1st AND on every Monday - not only on a Monday the 1st. *@
<VetoCron Value="0 0 1 * MON" ShowModes="false" />When it will not parse
The error names the field that is wrong rather than rejecting the whole expression, because the field is what you have to go and fix.
Hours: '99' is outside 0-23.
<VetoCron Value="0 99 * * *" />
@* "Hours: '99' is outside 0-23." *@Other languages
The built-in describer is English only, deliberately: Veto ships with no package dependencies, and one sentence is not the feature to break that for. Describer is the seam. Hand it CronExpressionDescriptor - which the docs site references, and your project would too - and you get its two dozen languages without the library ever depending on it.
At 09:00, Monday through Friday
@* In your project, not in Veto: *@
@using CronExpressionDescriptor
@foreach (var language in new[] { "en", "fr", "de", "es" })
{
var code = language;
<VetoButton OnClick="() => _locale = code">@code</VetoButton>
}
<VetoCron @bind-Value="_schedule" Describer="Describe" />
@code {
private string _schedule = "0 9 * * 1-5";
private string _locale = "en";
private string? Describe(string expression)
{
try
{
return ExpressionDescriptor.GetDescription(expression, new Options
{
Locale = _locale,
Use24HourTimeFormat = true,
ThrowExceptionOnParseError = true,
});
}
catch
{
return null; // fall back to the built-in describer
}
}
}Fewer tabs, or none
AllowedModes narrows the builder to the shapes a particular schedule is allowed to take, and ShowModes false leaves just the expression, its description and the runs - which is what you want when the component is reviewing an expression rather than building one.
At 02:30, only on Sunday
Backups may run daily or weekly.
Every 10 minutes
<VetoCron @bind-Value="_backup"
Label="Backup schedule"
AllowedModes="[VetoCronMode.Daily, VetoCronMode.Weekly, VetoCronMode.Advanced]"
InitialMode="VetoCronMode.Weekly" />
@* Reviewing a schedule rather than building one: *@
<VetoCron Value="*/10 * * * *" ShowModes="false" ShowExpression="false" />Without the boxes
Bordered false drops every border - the box around the builder and the box around each control inside it - leaving the schedule reading as a sentence on the page rather than as a form. The focus ring stays: the borders were decoration, the ring is how a keyboard user knows where they are.
At 07:00, only on Monday, Wednesday and Friday
<VetoCron @bind-Value="_schedule" Bordered="false" />Disabled and read-only
Read-only still shows the description and the runs - which is the point of showing a schedule nobody may edit.
At 03:00, only on Sunday
At 03:00, only on Sunday
<VetoCron Value="0 3 * * 0" ReadOnly="true" ShowModes="false" />
<VetoCron Value="0 3 * * 0" Disabled="true" />Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Value |
string |
"0 9 * * *" |
The cron expression. Use @bind-Value. |
Describer |
Func<string, string?>? |
null |
Turns an expression into a sentence. Null uses the built-in English describer; return null to fall back to it. |
AllowedModes |
IReadOnlyList<VetoCronMode>? |
null |
Which tabs are offered. Null offers all seven. |
InitialMode |
VetoCronMode |
Daily |
The tab shown first. |
ShowModes / ShowExpression / ShowDescription / ShowNextRuns |
bool |
true |
Which parts are shown. |
NextRunCount |
int |
5 |
How many upcoming runs to list. |
NextRunFormat |
string |
"ddd dd MMM yyyy, HH:mm" |
How each run is formatted. |
Now |
DateTime? |
null |
The moment the runs count from. Set it to pin the list for a screenshot or a test. |
Culture |
CultureInfo? |
null |
The culture the runs are formatted in. |
IncludeSeconds |
bool |
false |
Parse the expression as six fields, seconds first. |
Label / HelperText |
string? |
null |
Text above and below the builder. |
Disabled / ReadOnly |
bool |
false |
The usual states. Read-only still shows the description and the runs. |
Size |
VetoSize |
Medium |
ExtraSmall, Small, Medium, Large or ExtraLarge. |
Bordered |
bool |
true |
False drops every border - the builder's box and each control's - keeping the focus ring. |
OnValidChanged |
EventCallback<VetoCronExpression> |
— |
Raised when the expression changes and parses. |
IsValid / Expression |
bool / VetoCronExpression? |
— |
Read-only: whether the expression parses, and its parsed form. |