< Summary

Information
Class: AmbientServices.Extensions.TimeSpanExtensions
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Extensions/TimeSpanExtensions.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 175
Line coverage: 100%
Branch coverage
84%
Covered branches: 109
Total branches: 129
Branch coverage: 84.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
TryParseTimeSpan(...)77.53%8989100%
ToShortHumanReadableString(...)100%1414100%
ToLongHumanReadableString(...)100%1414100%
MatchUnits(...)100%22100%
UnitString(...)100%44100%
UnitStringWithPlural(...)100%66100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Extensions/TimeSpanExtensions.cs

#LineLine coverage
 1using System;
 2
 3namespace AmbientServices.Extensions;
 4
 5/// <summary>
 6/// A static class that extends <see cref="System.TimeSpan"/>.
 7/// </summary>
 8/// <remarks>
 9/// <pitch>Human-oriented duration handling: parse strings like &quot;30s&quot;, &quot;5 minutes&quot;, or &quot;1:30:00
 10/// <pledge>
 11/// Parsing accepts standard colon-delimited <see cref="TimeSpan"/> syntax, or a number followed by an optional unit (ye
 12/// Rendering is lossy by design: it picks a single unit large enough for the value to read comfortably, keeps at most o
 13/// </pledge>
 14/// </remarks>
 15public static class TimeSpanExtensions
 16{
 217    private static readonly char[] Alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
 218    private static readonly char[] Numeric = "-0123456789. \t".ToCharArray();
 19    /// <summary>
 20    /// Attempts to parse a string as a timespan.
 21    /// </summary>
 22    /// <param name="span">The candidate string.</param>
 23    /// <returns>A <see cref="TimeSpan"/>, if one could be parsed, or <b>null</b> if not.</returns>
 24    public static TimeSpan? TryParseTimeSpan(this string span)
 25    {
 226        if (string.IsNullOrEmpty(span)) return null;
 27        // is a span specified and does it contain a : and does it parse correctly as a timespan?
 28        TimeSpan timeSpan;
 229        if (span.Contains(':', StringComparison.Ordinal) && TimeSpan.TryParse(span, out timeSpan))
 30        {
 31            // use that timespan
 232            return timeSpan;
 33        }
 34        // split out the numeric part
 35        double units;
 236        if (!double.TryParse(span.TrimEnd(Alpha), out units))
 37        {
 238            return null;
 39        }
 40        // figure out the unit type
 241        string unitType = span.TrimStart(Numeric);
 42        // handle "M" specially because it's months as opposed to minutes
 243        if (unitType == "m") unitType = "MINUTES";
 244        return unitType.ToUpperInvariant() switch {
 245            "Y" or "YEAR" or "YEARS" => TimeSpan.FromDays(365.25 * units),
 246            "M" or "MONTH" or "MONTHS" => TimeSpan.FromDays(30.4375 * units),
 247            "D" or "DAY" or "DAYS" => TimeSpan.FromDays(units),
 248            "H" or "HOUR" or "HOURS" => TimeSpan.FromHours(units),
 249            "MINUTE" or "MINUTES" => TimeSpan.FromMinutes(units),
 250            "S" or "SECOND" or "SECONDS" => TimeSpan.FromSeconds(units),
 251            "MS" or "MILLISECOND" or "MILLISECONDS" => TimeSpan.FromMilliseconds(units),
 252            _ => TimeSpan.FromTicks((long)units),
 253        };
 54    }
 55    /// <summary>
 56    /// Gets a short string representing the specified timespan.
 57    /// </summary>
 58    /// <param name="duration">The <see cref="TimeSpan"/> whose string representation is to be generated</param>
 59    /// <returns>An easily human-readable string representing the time span with a postfix character indicating the unit
 60    public static string ToShortHumanReadableString(this TimeSpan duration)
 61    {
 62        string sign;
 63        TimeSpan absTimeSpan;
 264        if (duration.Ticks < 0)
 65        {
 266            sign = "-";
 267            absTimeSpan = new TimeSpan(-duration.Ticks);
 68        }
 69        else
 70        {
 271            sign = "";
 272            absTimeSpan = new TimeSpan(duration.Ticks);
 73        }
 274        if (MatchUnits(absTimeSpan.TotalDays, 731))
 75        {
 276            return UnitString(sign, absTimeSpan.TotalDays / 365.25, "Y");
 77        }
 278        if (MatchUnits(absTimeSpan.TotalDays, 61))
 79        {
 280            return UnitString(sign, absTimeSpan.TotalDays / 30.4375, "M");
 81        }
 282        if (MatchUnits(absTimeSpan.TotalHours, 48))
 83        {
 284            return UnitString(sign, absTimeSpan.TotalDays, "D");
 85        }
 286        if (MatchUnits(absTimeSpan.TotalMinutes, 120))
 87        {
 288            return UnitString(sign, absTimeSpan.TotalHours, "h");
 89        }
 290        if (MatchUnits(absTimeSpan.TotalSeconds, 120))
 91        {
 292            return UnitString(sign, absTimeSpan.TotalMinutes, "m");
 93        }
 294        if (MatchUnits(absTimeSpan.TotalMilliseconds, 2000))
 95        {
 296            return UnitString(sign, absTimeSpan.TotalSeconds, "s");
 97        }
 298        return UnitString(sign, absTimeSpan.TotalMilliseconds, "ms");
 99    }
 100    /// <summary>
 101    /// Gets a long string representing the specified timespan.
 102    /// </summary>
 103    /// <param name="duration">The <see cref="TimeSpan"/> whose string is to be generated.</param>
 104    /// <returns>An easily human-readable string representing the time span with a postfix string indicating the units (
 105    public static string ToLongHumanReadableString(this TimeSpan duration)
 106    {
 107        string sign;
 108        TimeSpan absTimeSpan;
 2109        if (duration.Ticks < 0)
 110        {
 2111            sign = "-";
 2112            absTimeSpan = new TimeSpan(-duration.Ticks);
 113        }
 114        else
 115        {
 2116            sign = "";
 2117            absTimeSpan = new TimeSpan(duration.Ticks);
 118        }
 2119        if (MatchUnits(absTimeSpan.TotalDays, 731))
 120        {
 2121            return UnitStringWithPlural(sign, absTimeSpan.TotalDays / 365.25, " Year");
 122        }
 2123        if (MatchUnits(absTimeSpan.TotalDays, 61))
 124        {
 2125            return UnitStringWithPlural(sign, absTimeSpan.TotalDays / 30.4375, " Month");
 126        }
 2127        if (MatchUnits(absTimeSpan.TotalHours, 48))
 128        {
 2129            return UnitStringWithPlural(sign, absTimeSpan.TotalDays, " Day");
 130        }
 2131        if (MatchUnits(absTimeSpan.TotalMinutes, 120))
 132        {
 2133            return UnitStringWithPlural(sign, absTimeSpan.TotalHours, " hour");
 134        }
 2135        if (MatchUnits(absTimeSpan.TotalSeconds, 120))
 136        {
 2137            return UnitStringWithPlural(sign, absTimeSpan.TotalMinutes, " minute");
 138        }
 2139        if (MatchUnits(absTimeSpan.TotalMilliseconds, 2000))
 140        {
 2141            return UnitStringWithPlural(sign, absTimeSpan.TotalSeconds, " second");
 142        }
 2143        return UnitStringWithPlural(sign, absTimeSpan.TotalMilliseconds, " millisecond");
 144    }
 145    private static bool MatchUnits(double total, int count)
 146    {
 2147        if (total < 2.0)
 148        {
 2149            return false;
 150        }
 2151        return total >= count;
 152    }
 153    private static string UnitString(string prefix, double count, string postfix)
 154    {
 2155        int intPart = (int)count;
 2156        int firstDecimal = ((int)(count * 10.0) % 10);
 2157        if (intPart < 10 && firstDecimal != 0)
 158        {
 2159            return prefix + intPart.ToString(System.Globalization.CultureInfo.InvariantCulture) + "." + firstDecimal.ToS
 160        }
 2161        return prefix + intPart.ToString(System.Globalization.CultureInfo.InvariantCulture) + postfix;
 162    }
 163    private static string UnitStringWithPlural(string prefix, double count, string postfix)
 164    {
 2165        int intPart = (int)count;
 2166        int firstDecimal = ((int)(count * 10.0) % 10);
 2167        if (intPart < 10 && firstDecimal != 0)
 168        {
 2169            return prefix + intPart.ToString(System.Globalization.CultureInfo.InvariantCulture) + "." + firstDecimal.ToS
 170        }
 171        // else nothing past the decimal
 2172        if (intPart != 1) postfix += "s";
 2173        return prefix + intPart.ToString(System.Globalization.CultureInfo.InvariantCulture) + postfix;
 174    }
 175}