2

I have TimeSpan object that tracks calculation times. This TimeSpan can range from seconds to days. I currently have a quite verbose method in place to give me a string from the TimeSpan that represents the TimeSpan in a long form; for example:

3 Days 13 Hours 1 Minute 52 Seconds

When the TimeSpan is less than 1 second, I display the Milliseconds property instead. The code for this is simplistic but is quite verbose as I stated above. Is there a concise way to achieve this similar to DateTime.Now.ToString("ddMMyyyy hhmmssffff")? Below is my current code:

public static string GetLongTime(TimeSpan t) {
    string result = string.Empty;
    if (t.Days > 0) {
        if (t.Days == 1) result = "1 Day";
        else result = $"{t.Days} Days";
    } else if (t.TotalMilliseconds < 1000) {
        if (t.Milliseconds > 0)
            result = $"{t.Milliseconds}ms";
    } else {
        if (t.Hours > 0) {
            if (t.Hours == 1) result += "1 Hour ";
            else result += $"{t.Hours} Hours ";
        }
        if (t.Minutes > 0) {
            if (t.Minutes == 1) result += "1 Minute ";
            else result += $"{t.Minutes} Minutes ";
        }
        if (t.Seconds > 0) {
            if (t.Seconds == 1) result += "1 Second ";
            else result += $"{t.Seconds } Seconds ";
        }
    }
    return result;
}

Just as with DateTime, the TimeSpan object has a custom format list; however, their example list displays strings with the good ol' fashioned (s) appendage:

  TimeSpan duration = new TimeSpan(1, 12, 23, 62);
  Console.WriteLine("Time of Travel: {0:%d} day(s)", duration);
  Console.WriteLine("Time of Travel: {0:dd\\.hh\\:mm\\:ss} days", duration);

Time of Travel: 1 day(s)

Time of Travel: 01.12:24:02 days

This isn't really what I'm looking for. I've also looked into some related posts here on StackOverflow, and as one user pointed out in a comment, they are good resources. Overall, these aren't the solutions I'm looking for either as I am mostly in high hopes of something incredibly concise such as:

return t.ToString("d Day?s");

Granted the above is just pseudo and wouldn't actually do more than:

3 Day?s

Some of the related posts have promise to refactor my code; for example, this post that recommends the conditional assignment operator:

string result = string.Empty;
if (t.Days > 0)
    return $"{t.Days} {(t.Days > 1 ? "Days " : "Day ")}";
else if (t.TotalMilliseconds < 1000) {
    if (t.Milliseconds > 0)
        return $"{t.Milliseconds}ms";
} else {
    if (t.Hours > 0)
        result += $"{t.Hours} {(t.Hours > 1 ? "Hours " : "Hour ")}";
    if (t.Minutes > 0)
        result += $"{t.Minutes} {(t.Minutes > 1 ? "Minutes " : "Minute ")}";
    if (t.Seconds > 0)
        result += $"{t.Seconds} {(t.Seconds > 1 ? "Seconds " : "Second ")}";
}
return result;

Though this shortens the code, it doesn't exactly make it look the cleanest either (especially to new developers). Which is also in line with this post that recommends using a SortedList of the proper formats; however, I agree with the OP on that post; Your code does take some time to follow. That's the only thing I'm not in love with..

Is there a way to achieve conditional pluralization within the format string; or, is there a more simplistic and concise approach compared to what I have above?

Hazel へいぜる
  • 2,751
  • 1
  • 12
  • 44
  • The common code could be put into a method `string Pluralize(int amount, string unit)` – juharr Aug 13 '18 at 16:49
  • Have you thought about `$"{t.Days} Day{(t.Days>1?"s":"")}"` ? – Fildor Aug 13 '18 at 16:49
  • 1
    See this [answer](https://stackoverflow.com/a/840177/8061994). Something along these lines may be what you need. It is similar to what @Fildor suggested above. –  Aug 13 '18 at 16:49
  • I've thought about slimming it down with the conditional assignment operator `?:`, definitely a good point. I'll make sure I add the posts I've looked at here to my post so that future readers understand all of the options I have looked into. – Hazel へいぜる Aug 13 '18 at 18:00
  • It's not clear for me what format you're looking for: *Is there a concise way to achieve this similar to* `DateTime.Now.ToString("ddMMyyyy hhmmssffff")`. This is quite different from `3 Days 13 Hours 1 Minute 52 Seconds`. The language should be considered (`ddMMyyyy` is not the *Invariant* format)? If you're just looking for a "shorter" code, I can post a *sample* and you can see if it fits. – Jimi Aug 13 '18 at 18:01
  • @Jimi The quote of `DateTime.Now.ToString("ddMMyyyy hhmmssffff")` was a basic example of the way I would ***like*** to be able to format the string. I'll be more than happy to look at your example. Shorter code is a bonus, but readability will always prevail. – Hazel へいぜる Aug 13 '18 at 18:18

2 Answers2

1

There is no built-in TimeSpan format specifier in .NET which would make this possible.

However, I've implemented this functionality as I needed it before. It's available in my free and open-source utility library. It's published under MIT license so you're allowed to copy the code or include the library in your project.

Usage (when choosing the latter option)

Install the NuGet package. Type the following in the Package Manager Console:

Install-Package Karambolo.Common

Then you can convert a TimeSpan value using the ToTimeReference extension method like this.

using System;
using Karambolo.Common;
using Karambolo.Common.Localization;

class Program
{
    static void Main(string[] args)
    {
        var ts = new TimeSpan(3, 13, 1, 52);
        Console.WriteLine(ts.ToTimeReference(4, "{0}", "now", "{0}", DefaultTextLocalizer.Instance));
    }
}

The code above prints:

3 days 13 hours 1 minute 52 seconds

The ToTimeReference overload I used in the sample code gives you full control over the formatting of the output. The last argument is a delegate which is responsible for specifying the format of the time interval strings (like "day", "hour", etc.) E.g. it can be used to localize the output of the function.

Adam Simon
  • 2,762
  • 16
  • 22
  • Though this is useful and indeed a good answer, I am unable to utilize this as I am unable to use any external libraries. I've thought about adding this to all of my posts, maybe I should start doing that. Good answer none the less. – Hazel へいぜる Aug 13 '18 at 18:46
  • @davisj1691 You are free to copy and modify the necessary parts of the source code if you don't want to include the whole package. – Adam Simon Aug 13 '18 at 18:48
1

This is simply a shorter method. Readability here is a matter of perspective :)

Some examples, to see if this meets the requirements.

TimeSpan ts = new TimeSpan(3, 4, 10, 25);
=> 3 Days 4 Hours 10 Minutes 25 Seconds

TimeSpan ts = new TimeSpan(0, 0, 0, 0, 700);
=> 700ms

TimeSpan ts = new TimeSpan(0, 2, 0, 1, 700);
=> 2 Hours 1 Second

TimeSpan ts = new TimeSpan(1, 1, 1, 1);
=> 1 Day 1 Hour 1 Minute 1 Second


TimeSpan ts = new TimeSpan([Some time span]);

string[] TFormat = new[] {
    (ts.Days > 1 ? ts.Days + " Days" : (ts.Days == 1 ? "1 Day" : "")),
    (ts.Hours > 1 ? " "+ ts.Hours + " Hours" : (ts.Hours == 1 ? " 1 Hour" : "")),
    (ts.Minutes > 1 ? " " + ts.Minutes + " Minutes" : (ts.Minutes == 1 ? " 1 Minute" : "")),
    (ts.Seconds > 1 ? " " + ts.Seconds + " Seconds" : (ts.Seconds == 1 ? " 1 Second" : "")),
    (ts.TotalMilliseconds < 1000 ? $"{ts.TotalMilliseconds }ms" : "")
};

Console.WriteLine($"{TFormat[0]}{TFormat[1]}{TFormat[2]}{TFormat[3]}{TFormat[4]}".TrimStart());
Jimi
  • 29,621
  • 8
  • 43
  • 61