Skip to content

Instantly share code, notes, and snippets.

@momvart
Last active April 10, 2022 16:39
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 momvart/476d07cd819b325a00977b2cb27357c3 to your computer and use it in GitHub Desktop.
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#.
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>&lt;start>/&lt;end></c></item>
/// <item><c>&lt;start>/&lt;duration></c></item>
/// <item><c>&lt;duration>/&lt;end></c></item>
/// <item><c>&lt;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]/&lt;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