78

I would like to format some commands execution times in a human readable format, for example:

3 -> 3ms
1100 -> 1s 100ms
62000 -> 1m 2s
etc ..

Taking into account days, hours, minutes, seconds, ...

Is it possible using C#?

6
  • 7
    Basically you just have to use modulo and division.
    – STT LCU
    Commented Apr 3, 2012 at 13:07
  • 1
    how about new TimeSpan(30000).ToString()?
    – juergen d
    Commented Apr 3, 2012 at 13:09
  • Well, I'm asking because I have other things to do here, in my company, better than basic programming exercises ;-) Commented Apr 3, 2012 at 13:09
  • 3
    @DanielPeñalba: then, instead of asking whether it's possible using C# (yes, it is), you should have asked "is there a ready-made class which does this?" Commented Apr 3, 2012 at 13:12
  • 1
    So you're basically asking for a TimeSpan.Format custom string that doesn't result in "0 years 0 months 0 days 0 hours 0 min 1 sec 100 ms"?
    – Mr Lister
    Commented Apr 3, 2012 at 13:13

13 Answers 13

139

You can use TimeSpan class, something like this:

TimeSpan t = TimeSpan.FromMilliseconds(ms);
string answer = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", 
                        t.Hours, 
                        t.Minutes, 
                        t.Seconds, 
                        t.Milliseconds);

It's quite similar as this thread I've just found:

What is the best way to convert seconds into (Hour:Minutes:Seconds:Milliseconds) time?

2
  • 8
    This would produce 00h:00m:00s:003ms instead of 3ms for the input 3, so I don't think it's exactly what the OP wants ;)
    – Nuffin
    Commented Apr 3, 2012 at 13:17
  • 6
    @Nuffin, he can always alter the code to suit his needs ;) I've just provided an example...
    – walther
    Commented Apr 3, 2012 at 13:20
31

I know this is old, but I wanted to answer with a great nuget package.

Install-Package Humanizer

https://www.nuget.org/packages/Humanizer

https://github.com/MehdiK/Humanizer

Example from their readme.md

TimeSpan.FromMilliseconds(1299630020).Humanize(4) => "2 weeks, 1 day, 1 hour, 30 seconds"
0
18

What about this?

var ts = TimeSpan.FromMilliseconds(86300000 /*whatever */);
var parts = string
                .Format("{0:D2}d:{1:D2}h:{2:D2}m:{3:D2}s:{4:D3}ms",
                    ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds)
                .Split(':')
                .SkipWhile(s => Regex.Match(s, @"00\w").Success) // skip zero-valued components
                .ToArray();
var result = string.Join(" ", parts); // combine the result

Console.WriteLine(result);            // prints '23h 58m 20s 000ms'
1
  • 1
    Nice code but it fails for 100 ms as input. The fix is to use @"^00\w" as the Regex. Commented Dec 10, 2018 at 13:20
17

You could utilize the static TimeSpan.FromMilliseconds method as well as the resulting TimeSpan's Days, Hours, Minutes, Seconds and Milliseconds properties.

But I'm busy right now, so I'll leave the rest to you as an exercise.

9

.NET 4 accepts format in TimeSpan.Tostring().

For other you can implement extension method like

    public static string Format(this TimeSpan obj)
    {
        StringBuilder sb = new StringBuilder();
        if (obj.Hours != 0)
        {
            sb.Append(obj.Hours);
            sb.Append(" "); 
            sb.Append("hours");
            sb.Append(" ");
        }
        if (obj.Minutes != 0 || sb.Length != 0)
        {
            sb.Append(obj.Minutes);
            sb.Append(" "); 
            sb.Append("minutes");
            sb.Append(" ");
        }
        if (obj.Seconds != 0 || sb.Length != 0)
        {
            sb.Append(obj.Seconds);
            sb.Append(" "); 
            sb.Append("seconds");
            sb.Append(" ");
        }
        if (obj.Milliseconds != 0 || sb.Length != 0)
        {
            sb.Append(obj.Milliseconds);
            sb.Append(" "); 
            sb.Append("Milliseconds");
            sb.Append(" ");
        }
        if (sb.Length == 0)
        {
            sb.Append(0);
            sb.Append(" "); 
            sb.Append("Milliseconds");
        }
        return sb.ToString();
    }

and call as

foreach (TimeSpan span in spans)
{
    MessageBox.Show(string.Format("{0}",  span.Format()));
}
1
  • I've used this approach and I just wanted to note that the highest interval (in this case obj.Hours) should be obj.TotalHours,ToString("0"). .TotalHours so it can show values above 24h, and ,ToString("0") to convert it to a single digit instead of a comma separated value. Commented Aug 4, 2016 at 14:31
8
public static string ReadableTime(int milliseconds)
{
    var parts = new List<string>();
    Action<int, string> add = (val, unit) => { if (val > 0) parts.Add(val+unit); };
    var t = TimeSpan.FromMilliseconds(milliseconds);

    add(t.Days, "d");
    add(t.Hours, "h");
    add(t.Minutes, "m");
    add(t.Seconds, "s");
    add(t.Milliseconds, "ms");

    return string.Join(" ", parts);
}
2
  • This is a nice approach for longer time spans (for which t.TotalDays also works).
    – Travis J
    Commented Oct 19, 2016 at 23:15
  • Great approach, and for optional formatting. Action<int, string, int> add = (val, unit, zeroplaceholder) => {if (val > 0) parts.Add( string.Format( "{0:DZ}X".Replace("X", unit.ToString()) .Replace("Z",zeroplaceholder.ToString()) , val ); }; and call with add(t.Milliseconds, "ms", 4); to get 2m 37s 0456ms
    – Markus
    Commented Sep 26, 2017 at 14:50
6

Old question, new answer:

public static string GetReadableTimeByMs(long ms)
{
   TimeSpan t = TimeSpan.FromMilliseconds(ms);
   if (t.Hours > 0) return $"{t.Hours}h:{t.Minutes}m:{t.Seconds}s";
   else if (t.Minutes > 0) return $"{t.Minutes}m:{t.Seconds}s";
   else if (t.Seconds > 0) return $"{t.Seconds}s:{t.Milliseconds}ms";
   else return $"{t.Milliseconds}ms";
}
4

This probably has a slightly different output than requested, but the result is human readable - and it can be adapted to fit many other use cases.

private static List<double> _intervals = new List<double>
{
    1.0 / 1000 / 1000,
    1.0 / 1000,
    1,
    1000,
    60 * 1000,
    60 * 60 * 1000
};
private static List<string> _units = new List<string>
{
    "ns",
    "µs",
    "ms",
    "s",
    "min",
    "h"
};

public string FormatUnits(double milliseconds, string format = "#.#")
{
    var interval = _intervals.Last(i=>i<=milliseconds);
    var index = _intervals.IndexOf(interval);

    return string.Concat((milliseconds / interval).ToString(format) , " " , _units[index]);
}

Example calls...

Console.WriteLine(FormatUnits(1));
Console.WriteLine(FormatUnits(20));
Console.WriteLine(FormatUnits(300));
Console.WriteLine(FormatUnits(4000));
Console.WriteLine(FormatUnits(50000));
Console.WriteLine(FormatUnits(600000));
Console.WriteLine(FormatUnits(7000000));
Console.WriteLine(FormatUnits(80000000));

...and results:

1000 µs
20 ms
300 ms
4 s
50 s
10 min
1.9 h
22.2 h
2

For example to get 00:01:35.0090000 as 0 hours, 1 minutes, 35 seconds and 9 milliseconds you can use this:

Console.WriteLine("Time elapsed:" +TimeSpan.FromMilliseconds(numberOfMilliseconds).ToString());

Your output:

Time elapsed: 00:01:35.0090000
1

Maybe something like this?

DateTime.Now.ToString("%d 'd' %h 'h' %m 'm' %s 'seconds' %ms 'ms'")
1
  • The question is about TimeSpans, not DateTimes. With a DateTime, you'll always end up with at least one day, which wouldn't really match the input of 3 milliseconds.
    – Nuffin
    Commented Apr 3, 2012 at 13:18
1

You can use TimeSpan.FromMilliseconds function

var tspan = TimeSpan.FromMilliseconds(YOUR_MILLI_SECONDS);
int h = tspan.Hours;
int m = tspan.Minutes;
int s = tspan.Seconds;
0

Well i normally hate writing if statements but some times what you really have is a nail and need a hammer.

string time;
if (elapsedTime.TotalMinutes > 2)
    time = string.Format("{0:n2} minutes", elapsedTime.TotalMinutes);
else if (elapsedTime.TotalSeconds > 15)
    time = string.Format("{0:n2} seconds", elapsedTime.TotalSeconds);
else
    time = string.Format("{0:n0}ms", elapsedTime.TotalMilliseconds);
0

Here my code working for different input value (h, m, s, ms).

public static string FormatTime(this object inputTime, bool displayOriginalValue = true, string inputType = "ms")
    {
        string originalValue = $"{inputTime}{inputType}";
        try
        {
            //"ms", "s", "min", "h", "d"
            double ms = double.Parse(inputTime.ToString());
            switch (inputType.ToUpper())
            {
                case "S":
                    ms = ms * 1000;
                    break;
                case "MIN":
                case "M":
                    ms = ms * 1000 * 60;
                    break;
                case "H":
                    ms = ms * 1000 * 60 * 60;
                    break;
                case "D":
                    ms = ms * 1000 * 60 * 60 * 24;
                    break;
                default:
                    break;
            }


            TimeSpan ts = TimeSpan.FromMilliseconds(ms);
            var parts = string
                .Format("{0:D2}d:{1:D2}h:{2:D2}m:{3:D2}s:{4:D3}ms",
                    ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds)
                .Split(':')
                .SkipWhile(s => Regex.Match(s, @"00\w").Success) // skip zero-valued components
                .ToArray();
            var result = string.Join(" ", parts); // combine the result
            if (ms > 0)
                result = result.Replace(" 000ms", "");

            if (string.IsNullOrWhiteSpace(result))
                result = originalValue;

            return displayOriginalValue && result.ToUpper() != originalValue.ToUpper() ? $"{result} ({inputTime}{inputType})" : result;
        }
        catch (Exception ex)
        {
            return $"Original input: {originalValue}. Error: {ex.Message}";
        }
    }

Not the answer you're looking for? Browse other questions tagged or ask your own question.