1

I have a date value in windows console application like this one "P0Y0M0DT23H43M52.103S", and I want to parse this datetime value in c# to get minutes out of datetime value. It is easily done in Java by using new java.time.Duration.parse("P0Y0M0DT23H43M52.103S").toMinutes().

The documentation for Duration.parse(CharSequence text) says:

This will parse a textual representation of a duration, including the string produced by toString(). The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS with days considered to be exactly 24 hours.

I am looking for a similar functionality in .net c# which can help me workout time in minutes accurately without having to run loop and split.

Thank you

Dai
  • 141,631
  • 28
  • 261
  • 374

3 Answers3

3

You can use my Noda Time library and its Period type:

using NodaTime;
using NodaTime.Text;

var parseResult = PeriodPattern.NormalizingIso.Parse("P0Y0M0DT23H43M52.103S");
if (parseResult.Success)
{
    Period period = parseResult.Value;
    Console.WriteLine($"Success! Parsed to {period}");
}

Note that even in Java, parsing an ISO-8601 period as a Duration is somewhat inappropriate... durations should be fixed lengths of time whereas if you have an ISO-8601 period of something like "P1M", that isn't a fixed length of time (ditto years). That's why the Duration.parse method requires it to only have days, hours, minutes etc.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

Use XmlConvert.ToTimeSpan():

TimeSpan duration = XmlConvert.ToTimeSpan("P0Y0M0DT23H43M52.103S");
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
0

This regular expression parses ISO-8601 duration strings e.g., P1DT2H, into named groups for easy extraction.

(?n)^P(?=.)(((?<CY>\d+)Y)?((?<CM>\d+)M)?((?<CW>\d+)W)?((?<CD>\d+)D)?)?(T(?=.+)(((?<TH>\d+)H)?((?<TM>\d+)M)?((?<TS>\d+)S)?)?)?$

See the PowerShell script below for how this was generated.

PowerShell

This PowerShell script uses regex to parse the ISO8601 duration and then applies the calendar-based time deltas to the specified date.

<#
.SYNOPSIS
    Adds an ISO-8601 calendar-based duration to a date.
.PARAM Duration
    The duration to add
.PARAM Date
    The reference date, defaults to Now.
.EXAMPLES 
    P1Y: 1 year 
    P1Y2M3W4D: 1 year, 2months, 3 weeks, 4 days 
    PT5H6M7S:  5 hours, 6 minutes, 7 seconds
    P1Y2M3W4DT5H6M7S: 1 year, 2months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds
#>
function Add-Duration {
    param (
        [Parameter(Mandatory)]
        [string]$Duration,
        [DateTime]$Date = (Get-Date),
        [Switch]$Past
    )
    # Compute ISO-8601 duration from the anchor
    $ReCalendarParts=("YMWD".ToCharArray()  | %{ "((?<C$_>\d+)$_)?" }) -join ""
    $ReTimeParts=("HMS".ToCharArray()  | %{ "((?<T$_>\d+)$_)?" }) -join ""
    $ReISO8601P = "(?n)^P(?=.)($ReCalendarParts)?(T(?=.+)($ReTimeParts)?)?$"
    $m = ([Regex]$ReISO8601P).Match($Duration)
    if (-Not $m.Success) { throw "Unexpected -Duration value $Duration. Expected P30D or similar ISO-8601 duration" }
    foreach ($group in $m.Groups) {
        if ($group.Name -ne "0" -and $group.Success) {
            $interval = [int]$group.Value
            if ($Past) { $interval *= -1 }
            $Date = switch -exact ($group.Name) {
                CY { $Date.AddYears($interval) }
                CM { $Date.AddMonths($interval) }
                CW { $Date.AddDays($interval * 7) }
                CD { $Date.AddDays($interval) }
                TH { $Date.AddHours($interval) }
                TM { $Date.AddMinutes($interval) }
                TS { $Date.AddSeconds($interval) }
                default { throw "Unsupported interval $($Group.Name)" }
            }
        }
    }
    $Date
}

C#

This C# code uses regex to parse the ISO8601 duration and then applies the calendar-based time deltas to the specified date.

See the PowerShell example above for how the Regex pattern is constructed.


    Console.WriteLine(DateTime.Now.AddDuration("PT1H"));

    internal static class DateTimeExtensions
    {

        const string Iso8601DurationPattern = @"(?n)^P(?=.)(((?<CY>\d+)Y)?((?<CM>\d+)M)?((?<CW>\d+)W)?((?<CD>\d+)D)?)?(T(?=.+)(((?<TH>\d+)H)?((?<TM>\d+)M)?((?<TS>\d+)S)?)?)?$";

        private static readonly Regex Iso8601DurationRegex = new Regex(Iso8601DurationPattern, RegexOptions.Compiled);

        public static DateTime AddDuration(this DateTime value, string duration, bool past = false)
        {
            var match = Iso8601DurationRegex.Match(duration);
            if (!match.Success)
            {
                throw new ArgumentOutOfRangeException(nameof(duration));
            }
            foreach (Group group in match.Groups)
            {
                if (group.Success && group.Name != "0")
                {
                    var interval = Int32.Parse(group.Value);
                    if (past) { interval *= -1; }
                    value = group.Name switch
                    {
                        "CY" => value.AddYears(interval),
                        "CM" => value.AddMonths(interval),
                        "CW" => value.AddDays(interval * 7),
                        "CD" => value.AddDays(interval),
                        "TH" => value.AddHours(interval),
                        "TM" => value.AddMinutes(interval),
                        "TS" => value.AddSeconds(interval),
                        _ => throw new NotSupportedException("Unsupported duration element $($Group.Name)")
                    };
                }
            }
            return value;
        }

    }