我有一个
TimeSpan
代表客户端连接到我的服务器的时间。我想向用户显示 TimeSpan
。但我不想过于冗长地显示该信息(例如:2hr 3min 32.2345sec = 太详细了!)
例如:如果连接时间是...
> 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;
}
要摆脱复杂的 if 和 switch 结构,您可以使用字典查找基于 TotalSeconds 的正确格式字符串,并使用 CustomFormatter 来相应地格式化提供的时间跨度。
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;
}
}
为什么不简单地像这样呢?
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";
}
}
如果您更喜欢两个时间单位(例如分钟加秒),添加起来会非常简单。
恢复旧帖子,但是...
尝试使用 Humanizer 库,它可以非常轻松地做到这一点:
TimeSpan.FromMilliseconds(1).Humanize() => "1 millisecond"
TimeSpan.FromMilliseconds(2).Humanize() => "2 milliseconds"
TimeSpan.FromDays(1).Humanize() => "1 day"
TimeSpan.FromDays(16).Humanize() => "2 weeks"
默认情况下,它会为您提供时间跨度的四舍五入的整数值。但您也可以要求更精确:
TimeSpan.FromDays(16).Humanize(2) => "2 weeks, 2 days"
我根据 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}";
}
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
这是我的看法 - 比公认的答案简单一点,你不觉得吗?另外,没有字符串分割/解析。
var components = new List<Tuple<int, string>> {
Tuple.Create((int)span.TotalDays, "day"),
Tuple.Create(span.Hours, "hour"),
Tuple.Create(span.Minutes, "minute"),
Tuple.Create(span.Seconds, "second"),
};
while(components.Any() && components[0].Item1 == 0)
{
components.RemoveAt(0);
}
var result = string.Join(", ", components.Select(t => t.Item1 + " " + t.Item2 + (t.Item1 != 1 ? "s" : string.Empty)));
我们已经创建了它的实现,它还显示年、月和周。
最多显示 2 个单位。
由于一个月的天数并不总是相同,因此可能会存在误差。
示例:
代码:
/// <summary>
/// Formats a <see cref="TimeSpan" /> to a human-readable string.
/// </summary>
public static class TimeSpanHumanReadable
{
/// <summary>
/// Formats the given <paramref name="timeSpan" /> to a human-readable format.
/// </summary>
/// <remarks>
/// This method intelligently formats the given <paramref name="timeSpan" /> to produce a result
/// that combines years, months, weeks, days, hours, minutes, seconds, and milliseconds, ensuring
/// the output is both accurate and intuitive for a wide range of durations.<br />
/// </remarks>
/// <param name="timeSpan">The value to format.</param>
/// <returns>A human-readable <see cref="TimeSpan" />. (See Example)</returns>
/// <example>
/// Example outputs for various <paramref name="timeSpan" /> values:
/// <ul>
/// <li>1 day</li>
/// <li>2 weeks</li>
/// <li>1 week 3 days</li>
/// <li>1 year 1 month</li>
/// <li>1 year 1 week</li>
/// <li>1 year 1 day</li>
/// <li>1 hour</li>
/// <li>1 hour 15 min</li>
/// <li>1 min 15 sec</li>
/// <li><c>TimeSpan.FromDays(400)</c>: 1 year 1 month</li>
/// <li><c>TimeSpan.FromHours(30)</c>: 1 day 6 hours</li>
/// <li><c>TimeSpan.FromMilliseconds(12345)</c>: 12 sec</li>
/// <li><c>TimeSpan.FromMinutes(90)</c>: 1 hour 30 min</li>
/// </ul>
/// </example>
[Pure]
public static string ToHumanReadableString(this TimeSpan timeSpan)
{
TimeValueClass timeValue = new(timeSpan);
StringBuilder builder = new();
ProcessTimeValue(builder, timeValue);
return builder.ToString();
}
// ReSharper disable once CognitiveComplexity
private static void ProcessTimeValue(this StringBuilder builder, TimeValueClass timeValue)
{
if (timeValue.Years is not 0)
{
// 1 year
builder.AddYears(timeValue);
if (timeValue.Months is not 0)
{
// 1 year 1 month
builder.AddSpace()
.AddMonths(timeValue);
}
else if (timeValue.Weeks is not 0)
{
// 1 year 1 week
builder.AddSpace()
.AddWeeks(timeValue);
}
else if (timeValue.Days is not 0)
{
// 1 year 1 day
builder.AddSpace()
.AddDays(timeValue);
}
return;
}
if (timeValue.Months is not 0)
{
// 1 month
builder.AddMonths(timeValue);
if (timeValue.Weeks is not 0)
{
// 1 month 1 week
builder.AddSpace()
.AddWeeks(timeValue);
}
else if (timeValue.Days is >= 3 and <= 6)
{
// 1 month 3 days
builder.AddSpace()
.AddDays(timeValue);
}
return;
}
if (timeValue.Weeks is not 0)
{
builder.AddWeeks(timeValue);
if (timeValue.Days is not 0)
{
builder.AddSpace()
.AddDays(timeValue);
}
return;
}
if (timeValue.Days is not 0)
{
builder.AddDays(timeValue);
if (timeValue.Hours is not 0)
{
builder.AddSpace()
.AddHours(timeValue);
}
return;
}
if (timeValue.Hours is not 0)
{
builder.AddHours(timeValue);
if (timeValue.Minutes is not 0)
{
builder.AddSpace()
.AddMinutes(timeValue);
}
return;
}
if (timeValue.Minutes is not 0)
{
builder.AddMinutes(timeValue);
if (timeValue.Seconds is not 0)
{
builder.AddSpace()
.AddSeconds(timeValue);
}
return;
}
if (timeValue.Seconds is not 0)
{
builder.AddSeconds(timeValue);
return;
}
if (timeValue.Milliseconds is not 0)
{
builder.AddMilliseconds(timeValue);
return;
}
builder.Append("000 ms");
}
private static StringBuilder AddSpace(this StringBuilder builder)
{
return builder.Append(' ');
}
private static StringBuilder AddYears(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Years, " year", " years");
private static StringBuilder AddMonths(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Months, " month", " months");
private static StringBuilder AddWeeks(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Weeks, " week", " weeks");
private static StringBuilder AddDays(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Days, " day", " days");
private static StringBuilder AddHours(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Hours, " hour", " hours");
private static StringBuilder AppendValueAndUnit(this StringBuilder builder, int value, string singular, string plural)
{
return builder.Append(value)
.Append(value is 1 or -1 ? singular : plural);
}
private static StringBuilder AddMinutes(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Minutes, " min");
private static StringBuilder AddSeconds(this StringBuilder builder, TimeValueClass timeValue)
=> builder.AppendValueAndUnit(timeValue.Seconds, " sec");
private static StringBuilder AppendValueAndUnit(this StringBuilder builder, int value, string unit)
{
return builder.Append(value)
.Append(unit);
}
private static StringBuilder AddMilliseconds(this StringBuilder builder, TimeValueClass timeValue)
{
// We show ms with leading zeros. So we have to add the '-' here.
if (timeValue.Milliseconds < 0)
{
builder.Append('-');
}
builder.Append(Math.Abs(timeValue.Milliseconds).ToString().PadLeft(3, '0'));
builder.Append(" ms");
return builder;
}
/// <remarks>
/// With help from https://stackoverflow.com/a/21260317/1847143
/// </remarks>
private class TimeValueClass
{
private const double DaysPerMonth = 30.4375; // Average days per month.
private const double DaysPerWeek = 7;
private const double DaysPerYear = 365;
public int Days { get; }
public int Hours { get; }
public int Milliseconds { get; }
public int Minutes { get; }
public int Months { get; }
public int Seconds { get; }
public int Weeks { get; }
public int Years { get; }
public TimeValueClass(TimeSpan timeSpan)
{
// Calculate the span in days
int days = timeSpan.Days;
// 362 days == 11 months and 4 weeks. 4 weeks => 1 month and 12 months => 1 year. So we have to exclude this value
bool has362Days = days % 362 == 0;
// Calculate years
int years = (int)(days / DaysPerYear);
// Decrease the remaining days
days -= (int)(years * DaysPerYear);
// Calculate months
int months = (int)(days / DaysPerMonth);
// Decrease the remaining days
days -= (int)(months * DaysPerMonth);
// Calculate weeks
int weeks = (int)(days / DaysPerWeek);
// Decrease the remaining days
days -= (int)(weeks * DaysPerWeek);
// 4 weeks is 1 month
if (weeks is 4 && has362Days is false)
{
weeks = 0;
months++;
}
else if (weeks is -4 && has362Days is false)
{
weeks = 0;
months--;
}
// 12 months is 1 year
if (months == 12)
{
months = 0;
years++;
}
Years = years;
Months = months;
Weeks = weeks;
Days = days;
Hours = timeSpan.Hours;
Minutes = timeSpan.Minutes;
Seconds = timeSpan.Seconds;
Milliseconds = timeSpan.Milliseconds;
}
}
}
单元测试:
/// <summary>
/// Test class for <see cref="Utils.Data.TimeSpanHumanReadable.ToHumanReadableString" />
/// </summary>
[NoReorder]
[TestFixture]
public class TimeSpanHumanReadableTests : AbstractTestBase
{
[TestCase(-1, "-1 day")]
[TestCase(1, "1 day")]
[TestCase(2, "2 days")]
[TestCase(3, "3 days")]
[TestCase(4, "4 days")]
[TestCase(5, "5 days")]
[TestCase(6, "6 days")]
[TestCase(7, "1 week")]
[TestCase(-7, "-1 week")]
public void ToHumanReadableString_DayValues_ReturnsHumanReadableString(int days, string expected)
{
TimeSpan timeSpan = new(days, 0, 0, 0, 0);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
[TestCase(28, "1 month")]
[TestCase(-28, "-1 month")]
[TestCase(29, "1 month")]
[TestCase(-29, "-1 month")]
[TestCase(30, "1 month")]
[TestCase(-30, "-1 month")]
[TestCase(31, "1 month")]
[TestCase(-31, "-1 month")]
[TestCase(32, "1 month")]
[TestCase(-32, "-1 month")]
public void ToHumanReadableString_DaysFor1Month_ReturnsHumanReadableString(int days, string expected)
{
TimeSpan timeSpan = new(days, 0, 0, 0, 0);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
[TestCase(-58, "-2 months")]
[TestCase(58, "2 months")]
[TestCase(59, "2 months")]
[TestCase(60, "2 months")]
[TestCase(61, "2 months")]
[TestCase(62, "2 months")]
public void ToHumanReadableString_DaysFor2Months_ReturnsHumanReadableString(int days, string expected)
{
TimeSpan timeSpan = new(days, 0, 0, 0, 0);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
[TestCase(-8, "-1 week -1 day")]
[TestCase(8, "1 week 1 day")]
[TestCase(16, "2 weeks 2 days")]
public void ToHumanReadableString_DaysForWeeks_ReturnsHumanReadableString(int days, string expected)
{
TimeSpan timeSpan = new(days, 0, 0, 0, 0);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
[TestCase(30, "1 month")]
[TestCase(30 + 1, "1 month")]
[TestCase(30 + 2, "1 month")]
[TestCase(30 + 3, "1 month 3 days")]
[TestCase(30 + 4, "1 month 4 days")]
[TestCase(30 + 5, "1 month 5 days")]
[TestCase(30 + 6, "1 month 6 days")]
[TestCase(30 + 7, "1 month 1 week")]
[TestCase(30 + 7 + 1, "1 month 1 week")]
[TestCase(32 + 7 + 2, "1 month 1 week")]
[TestCase(32 + 7 + 3, "1 month 1 week")]
public void ToHumanReadableString_DaysForMonths_ReturnsHumanReadableString(int days, string expected)
{
TimeSpan timeSpan = new(days, 0, 0, 0, 0);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
[TestCase(365, "1 year")]
[TestCase(365 + 1, "1 year 1 day")]
[TestCase(365 + 2, "1 year 2 days")]
[TestCase(365 + 3, "1 year 3 days")]
[TestCase(365 + 4, "1 year 4 days")]
[TestCase(365 + 5, "1 year 5 days")]
[TestCase(365 + 6, "1 year 6 days")]
[TestCase(365 + 7, "1 year 1 week")]
[TestCase(365 + 7 + 1, "1 year 1 week")]
[TestCase(365 + 7 + 2, "1 year 1 week")]
[TestCase(365 + 7 + 3, "1 year 1 week")]
[TestCase(365 + 7 + 4, "1 year 1 week")]
[TestCase(365 + 7 + 5, "1 year 1 week")]
[TestCase(365 + 7 + 6, "1 year 1 week")]
[TestCase(365 + 14, "1 year 2 weeks")]
[TestCase(365 + 30, "1 year 1 month")]
[TestCase(-365 - 30, "-1 year -1 month")]
[TestCase(365 + 60, "1 year 2 months")]
[TestCase(-365 - 60, "-1 year -2 months")]
public void ToHumanReadableString_DaysForYears_ReturnsHumanReadableString(int days, string expected)
{
TimeSpan timeSpan = new(days, 0, 0, 0, 0);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
[TestCase(1, 0, 0, 0, 0, "1 day")]
[TestCase(-1, 0, 0, 0, 0, "-1 day")]
[TestCase(0, 1, 0, 0, 0, "1 hour")]
[TestCase(0, -1, 0, 0, 0, "-1 hour")]
[TestCase(0, 0, 1, 0, 0, "1 min")]
[TestCase(0, 0, -1, 0, 0, "-1 min")]
[TestCase(0, 0, 0, 1, 0, "1 sec")]
[TestCase(0, 0, 0, -1, 0, "-1 sec")]
[TestCase(0, 0, 0, 0, 1, "001 ms")]
[TestCase(0, 0, 0, 0, -1, "-001 ms")]
[TestCase(0, 15, 0, 0, 0, "15 hours")]
[TestCase(0, -15, 0, 0, 0, "-15 hours")]
[TestCase(0, 0, 15, 0, 0, "15 min")]
[TestCase(0, 0, -15, 0, 0, "-15 min")]
[TestCase(0, 0, 0, 15, 0, "15 sec")]
[TestCase(0, 0, 0, -15, 0, "-15 sec")]
[TestCase(0, 0, 0, 0, 15, "015 ms")]
[TestCase(0, 0, 0, 0, -15, "-015 ms")]
[TestCase(1, 1, 0, 0, 0, "1 day 1 hour")]
[TestCase(2, 2, 0, 0, 0, "2 days 2 hours")]
[TestCase(5, 5, 5, 5, 5, "5 days 5 hours")]
[TestCase(0, 1, 1, 0, 0, "1 hour 1 min")]
[TestCase(0, 2, 2, 0, 0, "2 hours 2 min")]
[TestCase(0, 0, 1, 1, 0, "1 min 1 sec")]
[TestCase(0, 0, 2, 2, 0, "2 min 2 sec")]
[TestCase(0, 0, 0, 1, 1, "1 sec")] // With ms
[TestCase(0, 0, 0, 2, 2, "2 sec")] // With ms
[TestCase(0, 0, 0, 0, 0, "000 ms")] // With ms
[TestCase(400, 0, 0, 0, 0, "1 year 1 month")]
[TestCase(0, 30, 0, 0, 0, "1 day 6 hours")]
[TestCase(0, 0, 0, 0, 12345, "12 sec")]
[TestCase(0, 0, 90, 0, 0, "1 hour 30 min")]
[TestCase(5000, 0, 90, 0, 0, "13 years 8 months")]
public void ToHumanReadableString_TimeValues_ReturnsHumanReadableString(int days, int hours, int minutes, int seconds, int milliseconds, string expected)
{
TimeSpan timeSpan = new(days, hours, minutes, seconds, milliseconds);
Assert.That(TimeSpanHumanReadable.ToHumanReadableString(timeSpan), Is.EqualTo(expected), timeSpan.ToString());
}
}
我更喜欢这样的东西,我认为它更“可读”:
public string GetReadableTimeSpan(TimeSpan value)
{
string duration = "";
var totalDays = (int)value.TotalDays;
if (totalDays >= 1)
{
duration = totalDays + " day" + (totalDays > 1 ? "s" : string.Empty);
value = value.Add(TimeSpan.FromDays(-1 * totalDays));
}
var totalHours = (int)value.TotalHours;
if (totalHours >= 1)
{
if (totalDays >= 1)
{
duration += ", ";
}
duration += totalHours + " hour" + (totalHours > 1 ? "s" : string.Empty);
value = value.Add(TimeSpan.FromHours(-1 * totalHours));
}
var totalMinutes = (int)value.TotalMinutes;
if (totalMinutes >= 1)
{
if (totalHours >= 1)
{
duration += ", ";
}
duration += totalMinutes + " minute" + (totalMinutes > 1 ? "s" : string.Empty);
}
return duration;
}
另一种方法(德语)
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;
}
这是我的,非常简单-
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";
我更喜欢抛弃细节——例如,一旦你达到了几个月,那么秒数就无关紧要了。因此,我使用自然切换点来达到下一个级别,并丢弃所有小数值。当然,这也完全消除了单复数问题。
private static string LastFetched(TimeSpan ago)
{
string lastFetched = "last fetched ";
if (ago.TotalDays >= 90)
lastFetched += $"{(int)ago.TotalDays / 30} months ago";
else if (ago.TotalDays >= 14)
lastFetched += $"{(int)ago.TotalDays / 7} weeks ago";
else if (ago.TotalDays >= 2)
lastFetched += $"{(int)ago.TotalDays} days ago";
else if (ago.TotalHours >= 2)
lastFetched += $"{(int)ago.TotalHours} hours ago";
else if (ago.TotalMinutes >= 2)
lastFetched += $"{(int)ago.TotalMinutes} minutes ago";
else if (ago.TotalSeconds >= 10)
lastFetched += $"{(int)ago.TotalSeconds} seconds ago";
else
lastFetched += $"just now";
return lastFetched;
}
再次尝试这一点。 更连贯地处理单位的复数(并省略零单位):
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;
}
还有另一种选择。
测试用例:
var testCases = new List<HumanReadableTimeStringTestCase>
{
new HumanReadableTimeStringTestCase
{
ExpectedShort = "1.88s",
ExpectedLong = "1.88 seconds",
Span = TimeSpan.FromMilliseconds(1880)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "90s",
ExpectedLong = "90 seconds",
Span = TimeSpan.FromSeconds(90.4)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "90s", // No rounding for seconds
ExpectedLong = "90 seconds",
Span = TimeSpan.FromSeconds(90.7)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "90m",
ExpectedLong = "90 minutes",
Span = TimeSpan.FromMinutes(90.4)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "119m",
ExpectedLong = "119 minutes",
Span = TimeSpan.FromMinutes(119.4)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "2h",
ExpectedLong = "2 hours, 0 minutes",
Span = TimeSpan.FromMinutes(120)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "3h", // rounded
ExpectedLong = "2 hours, 55 minutes",
Span = TimeSpan.FromMinutes(120 + 55)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "24h",
ExpectedLong = "24 hours, 3 minutes",
Span = new TimeSpan(days: 1, hours: 0, minutes: 3, seconds: 0)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "26h",
ExpectedLong = "26 hours, 3 minutes",
Span = new TimeSpan(days: 1, hours: 2, minutes: 3, seconds: 0)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "27h",
ExpectedLong = "26 hours, 31 minutes",
Span = new TimeSpan(days: 1, hours: 2, minutes: 31, seconds: 0)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "24h",
ExpectedLong = "24 hours, 3 minutes",
Span = new TimeSpan(days: 1, hours: 0, minutes: 3, seconds: 0)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "2d,0h",
ExpectedLong = "2 days, 0 hours",
Span = new TimeSpan(days: 2, hours: 0, minutes: 3, seconds: 0)
},
new HumanReadableTimeStringTestCase
{
ExpectedShort = "2d,4h",
ExpectedLong = "2 days, 4 hours",
Span = new TimeSpan(days: 2, hours: 4, minutes: 3, seconds: 0)
},
};
代码:
public static string ToHumanReadableString(TimeSpan t)
{
if (t.TotalSeconds < 2)
{
return $@"{t.TotalSeconds:.##} seconds";
}
if (t.TotalMinutes < 2)
{
return $@"{(int)t.TotalSeconds} seconds";
}
if (t.TotalHours < 2)
{
return $@"{(int)Math.Round(t.TotalMinutes, MidpointRounding.AwayFromZero)} minutes";
}
if (t.TotalDays < 2)
{
return $@"{(int)(t.TotalMinutes / 60)} hours, {t:%m} minutes";
}
return $@"{t:%d} days, {t:%h} hours";
}
public static string ToHumanReadableStringShort(TimeSpan t)
{
if (t.TotalSeconds < 2)
{
return $@"{t.TotalSeconds:0.##}s";
}
if (t.TotalMinutes < 2)
{
return $@"{(int)t.TotalSeconds}s";
}
if (t.TotalHours < 2)
{
return $@"{(int)Math.Round(t.TotalMinutes, MidpointRounding.AwayFromZero)}m";
}
if (t.TotalDays < 2)
{
return $@"{(int)Math.Round(t.TotalHours, MidpointRounding.AwayFromZero)}h";
}
return $@"{t:%d}d,{t:%h}h";
}