Skip to content

Instantly share code, notes, and snippets.

@a-bronx
Last active August 11, 2021 07:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save a-bronx/0ce7ba4b675a145321e49849dffce767 to your computer and use it in GitHub Desktop.
Save a-bronx/0ce7ba4b675a145321e49849dffce767 to your computer and use it in GitHub Desktop.
class Schedule
{
// "Timetables" for different recurrence intervals.
private bool[] years;
private bool[] months;
private bool[] days;
private bool[] weekdays;
private bool[] hours;
private bool[] minutes;
private bool[] seconds;
private bool[] milliseconds;
public Schedule(string scheduleString)
{
var parts = scheduleString?.Split(' ');
var valid = (parts != null) && (
(parts.Length == 1) ? TryParse(parts[0]) :
(parts.Length == 2) ? TryParse(parts[0], parts[1]) || TryParse(parts[1], parts[0]) :
(parts.Length == 3) ? TryParse(parts[0], parts[2], parts[1]) || TryParse(parts[2], parts[0], parts[1]) :
false);
if (!valid)
throw new FormatException("Invalid schedule format");
}
// Parsing 3 main parts of the schedule into corresponding "timetables"
private bool TryParse(string timePattern, string datePattern = "*.*.*", string weekdaysPattern = "*")
{
return
TryParseDate(datePattern, out this.years, out this.months, out this.days) &&
TryParseTime(timePattern, out this.hours, out this.minutes, out this.seconds, out this.milliseconds) &&
TryParseInterval(weekdaysPattern, 7, out this.weekdays);
}
// Parsing a date part in a form of "YYYY.MM.DD", where
// "YYYY" is a year recurrence interval pattern,
// "MM" is a month recurrence interval pattern,
// "DD" is a day recurrence interval pattern.
private bool TryParseDate(string datePattern, out bool[] years, out bool[] months, out bool[] days)
{
years = months = days = null;
var parts = datePattern.Split('.');
return (parts.Length != 3) &&
TryParseInterval(parts[0], 101, out years, 2000) &&
TryParseInterval(parts[1], 12, out months, 1) &&
TryParseInterval(parts[2], 32, out days, 1);
}
// Parsing a time part in a form of "hh:mm:ss[.uuu]", where
// "hh" is a hour recurrence interval pattern,
// "mm" is a minute recurrence interval pattern,
// "ss" is a second recurrence interval pattern.
// "uuu" is an optional millisecond recurrence interval pattern.
private bool TryParseTime(string timePattern, out bool[] hours, out bool[] minutes, out bool[] seconds, out bool[] milliseconds)
{
hours = minutes = seconds = milliseconds = null;
var parts = timePattern.Split('.');
if (parts.Length < 1 || parts.Length > 2) return false;
var msPart = (parts.Length == 2) ? parts[1] : "0";
var hmsParts = parts[0].Split(':');
if (hmsParts.Length != 3) return false;
return
TryParseInterval(hmsParts[0], 24, out hours) &&
TryParseInterval(hmsParts[1], 60, out minutes) &&
TryParseInterval(hmsParts[2], 60, out seconds) &&
TryParseInterval(msPart, 1000, out milliseconds);
}
// Parsing comma-separated parts of a recurrence interval and returning a "timetable" array.
// Each part is either a point data ("10"), or a range with an optional step ("*", "10-20", "*/4", "10-20/3")
private bool TryParseInterval(string intervalPattern, int maxIntervalSize, out bool[] interval, int offset = 0)
{
interval = new bool[maxIntervalSize];
var parts = intervalPattern.Split(',');
foreach (var part in parts)
{
if (int.TryParse(part, out var i))
{
i -= offset;
if ((i < 0) || (i >= interval.Length)) return false;
interval[i] = true;
}
else if (!TryParseRange(part, ref interval, offset))
return false;
}
return true;
}
// Parses range patterns like "*/4", "10-20/3" etc, where left part is a range and right part is a step,
// and fills a "timetable" array provided in the `interval` ref parameter.
// Offset is used to adjust input values (e.g. "2000-2100") to a "timetable" range [0..maxRangeSize)
private bool TryParseRange(string rangePattern, ref bool[] interval, int offset = 0)
{
int min = 0;
int max = 0;
int step = 1;
var parts = rangePattern.Split('/');
if (parts.Length < 1 || parts.Length > 2) return false;
if (!TryParseRangeBounds(parts[0], interval.Length, out min, out max, offset)) return false;
if ((parts.Length == 2) && !int.TryParse(parts[1], out step)) return false;
if (min < 0 || step < 1) return false;
while (min < max)
{
interval[min] = true;
min += step;
}
return true;
}
// Parses range bounds. It can be a full range ("*") or a limited range ("min-max").
// Offset is used to adjust input values (e.g. "2000-2100") to a "timetable" range [0..maxRangeSize]
private bool TryParseRangeBounds(string boundsPattern, int maxRangeSize, out int min, out int max, int offset = 0)
{
min = max = 0;
if (boundsPattern == "*")
{
max = maxRangeSize;
return true;
}
var parts = boundsPattern.Split('-');
if (parts.Length != 2) return false;
if (!int.TryParse(parts[0], out min) || !int.TryParse(parts[1], out max)) return false;
min -= offset;
max -= offset - 1; // inclusive right bound, i.e. "1-12" means [1..12], not [1..12)
return (min >= 0) && (min < max) && (max <= maxRangeSize);
}
// the rest is omitted ....
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment