有没有一种简洁的方法来实现 TimeSpan 格式的条件复数?

Is There a Concise Way To Achieve Conditional Pluralization of TimeSpan Format?

我有 TimeSpan 跟踪计算时间的对象。 TimeSpan 的范围可以从几秒到几天。我目前有一个相当冗长的方法来给我一个来自 TimeSpan 的字符串,它以长格式表示 TimeSpan;例如:

3 Days 13 Hours 1 Minute 52 Seconds

TimeSpan 小于 1 秒时,我显示 Milliseconds 属性。这个代码很简单,但正如我上面所说的那样非常冗长。有没有一种类似于DateTime.Now.ToString("ddMMyyyy hhmmssffff")的简洁方法来实现这一点?以下是我当前的代码:

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;
}

就像 DateTime 一样,TimeSpan 对象有一个 custom format list;然而,他们的示例列表显示带有良好 ol' 时尚 (s) 附加物的字符串:

  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

这不是我真正想要的。我还在 Whosebug 上查看了一些相关的 posts,正如一位用户在评论中指出的那样,它们是很好的资源。总的来说,这些都不是我正在寻找的解决方案,因为我主要寄希望于一些非常简洁的东西,例如:

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

当然以上只是伪造的,实际上不会做更多的事情:

3 Day?s

一些相关的 post 承诺重构我的代码;例如,this post 推荐条件赋值运算符:

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;

虽然这缩短了代码,但它并没有使它看起来最干净(尤其是对于新开发人员而言)。这也符合 post 上的 this post that recommends using a SortedList of the proper formats; however, I agree with the OP您的代码确实需要一些时间才能理解。这是我唯一不喜欢的东西。

有没有办法在格式字符串中实现条件复数;或者,与我上面的方法相比,是否有更简单和简洁的方法?

.NET 中没有使这成为可能的内置 TimeSpan 格式说明符。

但是,我已经根据需要实现了此功能。它在我的免费开源 utility library 中可用。它是根据 MIT 许可发布的,因此您可以复制代码或将库包含在您的项目中。

用法(选择后一个选项时)

安装 NuGet 包。在程序包管理器控制台中键入以下内容:

Install-Package Karambolo.Common

然后您可以像这样使用 ToTimeReference 扩展方法转换 TimeSpan 值。

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));
    }
}

上面的代码打印:

3 days 13 hours 1 minute 52 seconds

我在示例代码中使用的 ToTimeReference 重载使您可以完全控制输出的格式。最后一个参数是一个委托,负责指定时间间隔字符串的格式(如 "day"、"hour" 等)。它可用于本地化函数的输出。

这只是一个更短的方法。这里的可读性是一个视角问题:)

一些例子,看看是否符合要求。

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());