如何生成“人类可读”的字符串来表示TimeSpan

时间:2013-05-22 10:30:21

标签: c# .net string-formatting timespan

我有TimeSpan表示客户端连接到我的服务器的时间。我想向用户显示TimeSpan。但是我不想过于冗长地显示这些信息(例如:2小时3分钟32.2345秒=太详细了!)

例如:如果连接时间是......

> 0 seconds and < 1 minute   ----->  0 Seconds
> 1 minute  and < 1 hour     ----->  0 Minutes, 0 Seconds
> 1 hour    and < 1 day      ----->  0 Hours, 0 Minutes
> 1 day                      ----->  0 Days, 0 Hours

当然,在数字为1(例如:1秒,1分钟,1小时,1天)的情况下,我想使文本单数(例如:1秒,1分钟,1小时, 1天)。

在没有一大堆if / else子句的情况下,是否可以轻松实现此功能?这就是我目前正在做的事情。

public string GetReadableTimeSpan(TimeSpan value)
{
    string duration;

    if (value.TotalMinutes < 1)
        duration = value.Seconds + " Seconds";
    else if (value.TotalHours < 1)
        duration = value.Minutes + " Minutes, " + value.Seconds + " Seconds";
    else if (value.TotalDays < 1)
        duration = value.Hours + " Hours, " + value.Minutes + " Minutes";
    else
        duration = value.Days + " Days, " + value.Hours + " Hours";

    if (duration.StartsWith("1 Seconds") || duration.EndsWith(" 1 Seconds"))
        duration = duration.Replace("1 Seconds", "1 Second");

    if (duration.StartsWith("1 Minutes") || duration.EndsWith(" 1 Minutes"))
        duration = duration.Replace("1 Minutes", "1 Minute");

    if (duration.StartsWith("1 Hours") || duration.EndsWith(" 1 Hours"))
        duration = duration.Replace("1 Hours", "1 Hour");

    if (duration.StartsWith("1 Days"))
        duration = duration.Replace("1 Days", "1 Day");

    return duration;
}

8 个答案:

答案 0 :(得分:25)

要摆脱复杂的if和switch结构,可以使用Dictionary查找基于TotalSeconds的正确格式字符串和CustomFormatter来相应地格式化提供的Timespan。

public string GetReadableTimespan(TimeSpan ts)
{
     // formats and its cutoffs based on totalseconds
     var cutoff = new SortedList<long, string> { 
       {59, "{3:S}" }, 
       {60, "{2:M}" },
       {60*60-1, "{2:M}, {3:S}"},
       {60*60, "{1:H}"},
       {24*60*60-1, "{1:H}, {2:M}"},
       {24*60*60, "{0:D}"},
       {Int64.MaxValue , "{0:D}, {1:H}"}
     };

     // find nearest best match
     var find = cutoff.Keys.ToList()
                   .BinarySearch((long)ts.TotalSeconds);
     // negative values indicate a nearest match
     var near = find<0?Math.Abs(find)-1:find;
     // use custom formatter to get the string
     return String.Format(
         new HMSFormatter(), 
         cutoff[cutoff.Keys[near]], 
         ts.Days, 
         ts.Hours, 
         ts.Minutes, 
         ts.Seconds);
}

// formatter for forms of
// seconds/hours/day
public class HMSFormatter:ICustomFormatter, IFormatProvider
{
    // list of Formats, with a P customformat for pluralization
    static Dictionary<string, string> timeformats = new Dictionary<string, string> {
        {"S", "{0:P:Seconds:Second}"},
        {"M", "{0:P:Minutes:Minute}"},
        {"H","{0:P:Hours:Hour}"},
        {"D", "{0:P:Days:Day}"}
    };

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        return String.Format(new PluralFormatter(),timeformats[format], arg);
    }

    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter)?this:null;
    }   
}

// formats a numeric value based on a format P:Plural:Singular
public class PluralFormatter:ICustomFormatter, IFormatProvider
{

   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
     if (arg !=null)
     {
         var parts = format.Split(':'); // ["P", "Plural", "Singular"]

         if (parts[0] == "P") // correct format?
         {
            // which index postion to use
            int partIndex = (arg.ToString() == "1")?2:1;
            // pick string (safe guard for array bounds) and format
            return String.Format("{0} {1}", arg, (parts.Length>partIndex?parts[partIndex]:""));               
         }
     }
     return String.Format(format, arg);
   }

   public object GetFormat(Type formatType)
   {
       return formatType == typeof(ICustomFormatter)?this:null;
   }   
}

答案 1 :(得分:6)

为什么不这样呢?

public static class TimespanExtensions
{
    public static string ToHumanReadableString (this TimeSpan t)
    {
        if (t.TotalSeconds <= 1) {
            return $@"{t:s\.ff} seconds";
        }
        if (t.TotalMinutes <= 1) {
            return $@"{t:%s} seconds";
        }
        if (t.TotalHours <= 1) {
            return $@"{t:%m} minutes";
        }
        if (t.TotalDays <= 1) {
            return $@"{t:%h} hours";
        }

        return $@"{t:%d} days";
    }
}

如果您更喜欢两个时间单位(例如分钟加秒),那么添加起来非常简单。

答案 2 :(得分:4)

我建立在Bjorn的答案以满足我的需求,想分享以防其他人看到这个问题。可以节省他们的时间。接受的答案对我的需求来说有点重要。

    private static string FormatTimeSpan(TimeSpan timeSpan)
    {
        Func<Tuple<int,string>, string> tupleFormatter = t => $"{t.Item1} {t.Item2}{(t.Item1 == 1 ? string.Empty : "s")}";
        var components = new List<Tuple<int, string>>
        {
            Tuple.Create((int) timeSpan.TotalDays, "day"),
            Tuple.Create(timeSpan.Hours, "hour"),
            Tuple.Create(timeSpan.Minutes, "minute"),
            Tuple.Create(timeSpan.Seconds, "second"),
        };

        components.RemoveAll(i => i.Item1 == 0);

        string extra = "";

        if (components.Count > 1)
        {
            var finalComponent = components[components.Count - 1];
            components.RemoveAt(components.Count - 1);
            extra = $" and {tupleFormatter(finalComponent)}";
        }

        return $"{string.Join(", ", components.Select(tupleFormatter))}{extra}";
    }

答案 3 :(得分:2)

这是我的看法 - 比接受的答案简单一点,你觉得不是吗?此外,没有字符串拆分/解析。

'//table[@class="social_list"]/tbody/tr'

答案 4 :(得分:1)

另一种方法(德语)

public static string GetReadableTimeSpan(TimeSpan span)
{
    var formatted = string.Format("{0}{1}{2}{3}",
        span.Duration().Days > 0
            ? $"{span.Days:0} Tag{(span.Days == 1 ? string.Empty : "e")}, "
            : string.Empty,
        span.Duration().Hours > 0
            ? $"{span.Hours:0} Stunde{(span.Hours == 1 ? string.Empty : "n")}, "
            : string.Empty,
        span.Duration().Minutes > 0
            ? $"{span.Minutes:0} Minute{(span.Minutes == 1 ? string.Empty : "n")}, "
            : string.Empty,
        span.Duration().Seconds > 0
            ? $"{span.Seconds:0} Sekunde{(span.Seconds == 1 ? string.Empty : "n")}"
            : string.Empty);

    if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2);

    return string.IsNullOrEmpty(formatted) ? "0 Sekunden" : ReplaceLastOccurrence(formatted, ",", " und ").Replace("  ", " ");
}

private static string ReplaceLastOccurrence(string source, string find, string replace)
{
    var place = source.LastIndexOf(find, StringComparison.Ordinal);

    if (place == -1)
        return source;

    var result = source.Remove(place, find.Length).Insert(place, replace);
    return result;
}

答案 5 :(得分:1)

public string ToHumanDuration(TimeSpan? duration, bool displaySign = true)
    {
        if (duration == null) return null;

        var builder = new StringBuilder();
        if (displaySign)
        {
            builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+");
        }

        duration = duration.Value.Duration();

        if (duration.Value.Days > 0)
        {
            builder.Append($"{duration.Value.Days}d ");
        }

        if (duration.Value.Hours > 0)
        {
            builder.Append($"{duration.Value.Hours}h ");
        }

        if (duration.Value.Minutes > 0)
        {
            builder.Append($"{duration.Value.Minutes}m ");
        }

        if (duration.Value.TotalHours < 1)
        {
            if (duration.Value.Seconds > 0)
            {
                builder.Append(duration.Value.Seconds);
                if (duration.Value.Milliseconds > 0)
                {
                    builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}");
                }

                builder.Append("s ");
            }
            else
            {
                if (duration.Value.Milliseconds > 0)
                {
                    builder.Append($"{duration.Value.Milliseconds}ms ");
                }
            }
        }

        if (builder.Length <= 1)
        {
            builder.Append(" <1ms ");
        }

        builder.Remove(builder.Length - 1, 1);

        return builder.ToString();
    }

来源:https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.Core/Dashboard/HtmlHelper.cs

答案 6 :(得分:0)

另一个刺伤。 更加连贯地处理多个单位(并省略零单位):

private string GetValueWithPluralisedUnits(int value, string units, int prefix_value)
    {
        if (value != 0)
        {
            return (prefix_value == 0 ? "" : ", ") + value.ToString() + " " + units + (value == 1 ? "" : "s");
        }

        return "";
    }

    private string GetReadableTimeSpan(TimeSpan value)
    {
        string duration;

        if (value.TotalMinutes < 1)
        {
            if (value.Seconds > 0)
            {
                duration = GetValueWithPluralisedUnits(value.Seconds, "Second", 0);
            }
            else
            {
                duration = "";
            }
        }
        else if (value.TotalHours < 1)
        {
            duration = GetValueWithPluralisedUnits(value.Minutes, "Minute", 0) + GetValueWithPluralisedUnits(value.Seconds, "Second", value.Minutes);
        }
        else if (value.TotalDays < 1)
        {
            duration = GetValueWithPluralisedUnits(value.Hours, "Hour", 0) + GetValueWithPluralisedUnits(value.Minutes, "Minute", value.Hours);
        }
        else
        {
            int days_left = (int)value.TotalDays;
            int years = days_left / 365;
            days_left -= years * 365;
            int months = days_left / 12;
            days_left -= months * 12;

            duration = GetValueWithPluralisedUnits(years, "Year", 0) + GetValueWithPluralisedUnits(months, "Month", years) + GetValueWithPluralisedUnits(days_left, "Day", years + months);
        }

        return duration;
    }

答案 7 :(得分:0)

这是我的,很简单-

TimeSpan timeElapsed = DateTime.Now - referenceTime_;
string timeString = "";
if (timeElapsed.Hours > 0)
    timeString = timeElapsed.Hours.ToString() + " hour(s), " + timeElapsed.Minutes.ToString() + " minutes, " + timeElapsed.Seconds.ToString() + " seconds";
else if (timeElapsed.Minutes > 0)
    timeString = timeElapsed.Minutes.ToString() + " minutes, " + timeElapsed.Seconds.ToString() + " seconds";
else
    timeString = timeElapsed.Seconds.ToString() + " seconds";