dvCronToOnCalendar function

String dvCronToOnCalendar(
  1. String line
)

A five-field cron line as a systemd OnCalendar= expression.

Numbers, *, lists, ranges and steps. Names and @daily are refused rather than guessed at. A line restricting both day of month and day of week is refused too: cron fires when either matches and systemd only when both do, so the same text would back up on different days.

Implementation

String dvCronToOnCalendar(String line) {
  final List<String> fields = line.trim().split(RegExp(r'\s+'));
  if (fields.length != 5) {
    throw FormatException('"$line" is not a five-field cron line');
  }
  final _CronField minute = _CronField.parse(fields[0], 0, 59, 'minute');
  final _CronField hour = _CronField.parse(fields[1], 0, 23, 'hour');
  final _CronField dom = _CronField.parse(fields[2], 1, 31, 'day of month');
  final _CronField month = _CronField.parse(fields[3], 1, 12, 'month');
  final _CronField dow = _CronField.parse(fields[4], 0, 7, 'day of week');
  if (!dom.any && !dow.any) {
    throw FormatException(
      '"$line" restricts both day of month and day of week; cron matches '
      'either and a systemd timer would need both, so it is refused rather '
      'than translated into a different schedule',
    );
  }

  final StringBuffer out = StringBuffer();
  if (!dow.any) {
    final List<int> days = <int>{
      for (final int d in dow.values) d == 0 ? 7 : d,
    }.toList()..sort();
    final List<String> parts = <String>[];
    int i = 0;
    while (i < days.length) {
      int j = i;
      while (j + 1 < days.length && days[j + 1] == days[j] + 1) {
        j++;
      }
      String name(int d) => _weekdays[d % 7];
      if (j - i >= 2) {
        parts.add('${name(days[i])}..${name(days[j])}');
      } else {
        for (int k = i; k <= j; k++) {
          parts.add(name(days[k]));
        }
      }
      i = j + 1;
    }
    out.write('${parts.join(',')} ');
  }
  out.write('*-${month.render(1)}-${dom.render(1)} ');
  out.write('${hour.render(0)}:${minute.render(0)}:00');
  return out.toString();
}