Last active
August 11, 2021 07:06
-
-
Save a-bronx/0ce7ba4b675a145321e49849dffce767 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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