Last active
April 10, 2022 16:39
-
-
Save momvart/476d07cd819b325a00977b2cb27357c3 to your computer and use it in GitHub Desktop.
A set of functions to parse some data and time data expressed in ISO 8601 format for C#.
This file contains hidden or 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
using System; | |
using System.Text.RegularExpressions; | |
namespace RBC.WorkflowEngine.Core.Utilities; | |
/// <summary> | |
/// Provides a set of functions for parsing some of date- and time-related | |
/// data represented in ISO 8601 formats. | |
/// </summary> | |
/// <see href="https://en.wikipedia.org/wiki/ISO_8601"> | |
/// ISO 8601 - Wikipedia. | |
/// </see> | |
public static class ISO8601Parser | |
{ | |
private const string DateTimePseudoPattern = @"[\d:\-+\.TZ]+"; | |
private const string DurationPattern = @" | |
(?<sign>[-+])?P | |
(?:(?<day>[-+]?\d+)D)? | |
(?:T | |
(?:(?<hour>[-+]?\d+)H)? | |
(?:(?<min>[-+]?\d+)M)? | |
(?: | |
(?<sec_sign>[-+])? | |
(?<sec>\d+)? | |
(?:[.,](?<millisec>\d{0,3}))? | |
S)? | |
)?"; | |
private const string IntervalPattern = | |
@"(?:(?<start>" + DateTimePseudoPattern + @")\/)?" + | |
@"(?<duration>" + DurationPattern + @")?" + | |
@"(?:(?:(?<!\/)\/)?(?<=\/)(?<end>" + DateTimePseudoPattern + @"))?"; | |
private const string RepeatingIntervalPattern = | |
@"R(?<count>\d+)?\/" + | |
@"(?<interval>" + IntervalPattern + @")"; | |
private const RegexOptions Options = | |
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace; | |
private static readonly Regex DurationRegex = | |
new($"^{DurationPattern}$", Options); | |
private static readonly Regex IntervalRegex = | |
new($"^{IntervalPattern}$", Options); | |
private static readonly Regex RepeatingIntervalRegex = | |
new($"^{RepeatingIntervalPattern}$", Options); | |
/// <summary> | |
/// Parses a date and time represented in the standard format. | |
/// </summary> | |
/// <param name="s">The string to parse.</param> | |
/// <returns>The parsed datetime instance.</returns> | |
/// <example><c>2021-10-23T14:19:40.3512921+03:30</c>.</example> | |
public static DateTimeOffset ParseDateTime(string s) => | |
/* The built-in DateTime is able to parse the format. */ | |
DateTimeOffset.Parse(s); | |
/// <summary> | |
/// Parses a duration represented in <c>PnDTnHnMnS</c> format. | |
/// </summary> | |
/// <param name="s">The string to parse.</param> | |
/// <returns>The timespan equal to the amount of duration.</returns> | |
/// <example> | |
/// <c>P4DT12H30M5S</c>. | |
/// </example> | |
public static TimeSpan ParseDuration(string s) => | |
ParseDuration(DurationRegex.Match(s).EnsureSuccess()); | |
/// <summary> | |
/// Parses an interval represented in the standard format. | |
/// </summary> | |
/// <remarks> | |
/// The string should be in one the formats listed below. | |
/// <list type="bullet"> | |
/// <item><c><start>/<end></c></item> | |
/// <item><c><start>/<duration></c></item> | |
/// <item><c><duration>/<end></c></item> | |
/// <item><c><duration></c></item> | |
/// </list> | |
/// </remarks> | |
/// <param name="s">The string to parse.</param> | |
/// <returns>The parts of the interval.</returns> | |
/// <example> | |
/// <list type="bullet"> | |
/// <item><c>2007-03-01T13:00:00Z/2008-05-11T15:30:00Z</c></item> | |
/// <item><c>2007-03-01T13:00:00Z/P10DT2H30M</c></item> | |
/// <item><c>P10DT2H30M/2008-05-11T15:30:00Z</c></item> | |
/// <item><c>P10DT2H30M</c></item> | |
/// </list> | |
/// </example> | |
public static (DateTimeOffset start, DateTimeOffset end, TimeSpan duration) | |
ParseInterval(string s) => | |
ParseInterval(IntervalRegex.Match(s).EnsureSuccess()); | |
/// <summary> | |
/// Parses a repeating interval represented in the standard format. | |
/// </summary> | |
/// <remarks> | |
/// The string should be in the format of <c>R[n]/<interval></c>. | |
/// If the number of repetitions is not provided a -1 will be returned | |
/// as count. | |
/// </remarks> | |
/// <param name="s">The string to parse.</param> | |
/// <returns>The parts of the repeating interval.</returns> | |
/// <example><c>R5/2008-03-01T13:00:00Z/P10DT2H30M</c>.</example> | |
public static | |
(DateTimeOffset start, DateTimeOffset end, TimeSpan duration, int count) | |
ParseRepeatingInterval(string s) => | |
ParseRepeatingInterval( | |
RepeatingIntervalRegex.Match(s).EnsureSuccess()); | |
private static TimeSpan ParseDuration(Match match) | |
{ | |
var sign = int.Parse(match.Groups["sign"].Value + "1"); | |
var days = ParsePart("day"); | |
var hours = ParsePart("hour"); | |
var minutes = ParsePart("min"); | |
var secondsSign = match.Groups["sec_sign"].Value == "-" ? -1 : 1; | |
var seconds = ParsePart("sec") * secondsSign; | |
var milliseconds = int.Parse( | |
match.Groups["millisec"].Value.PadRight(3, '0')) * secondsSign; | |
return new TimeSpan( | |
days, hours, minutes, seconds, milliseconds) * | |
sign; | |
int ParsePart(string groupName) | |
{ | |
var group = match.Groups[groupName]; | |
return group.Success ? int.Parse(group.Value) : 0; | |
} | |
} | |
private static (DateTimeOffset start, DateTimeOffset end, TimeSpan duration) | |
ParseInterval(Match match) | |
{ | |
return ( | |
ParsePart("start"), | |
ParsePart("end"), | |
match.Groups["duration"].Success | |
? ParseDuration(match) : default); | |
DateTimeOffset ParsePart(string groupName) | |
{ | |
var group = match.Groups[groupName]; | |
return group.Success ? ParseDateTime(group.Value) : default; | |
} | |
} | |
private static | |
(DateTimeOffset start, DateTimeOffset end, TimeSpan duration, int count) | |
ParseRepeatingInterval(Match match) | |
{ | |
var countValue = match.Groups["count"].Value; | |
var count = | |
string.IsNullOrEmpty(countValue) ? -1 : int.Parse(countValue); | |
var (start, end, duration) = ParseInterval(match); | |
return (start, end, duration, count); | |
} | |
private static Match EnsureSuccess(this Match match) | |
{ | |
if (!match.Success) | |
{ | |
throw new FormatException(); | |
} | |
return match; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment