有谁知道将 TimeSpan 对象转换为“友好”字符串的好库(或代码片段),例如:
- 两年三个月零四天
- 一星期零两天
(适用于文件到期系统,到期时间可能是几天到几十年不等)
澄清一下,假设我有一个 7 天的 TimeSpan,应该打印“1 周”、14 天“2 周”、366 天“1 年零 1 天”等。
我只是偶然发现了这个问题,因为我想做类似的事情。经过一番谷歌搜索后,我仍然没有找到我想要的:以一种“圆形”方式显示时间跨度。我的意思是:当某个事件需要几天时间时,显示毫秒并不总是有意义的。但是,当它需要几分钟时,它可能会。在这种情况下,我不希望显示 0 天和 0 小时。所以,我想参数化要显示的相关时间跨度部分的数量。这导致了这段代码:
public static class TimeSpanExtensions
{
private enum TimeSpanElement
{
Millisecond,
Second,
Minute,
Hour,
Day
}
public static string ToFriendlyDisplay(this TimeSpan timeSpan, int maxNrOfElements)
{
maxNrOfElements = Math.Max(Math.Min(maxNrOfElements, 5), 1);
var parts = new[]
{
Tuple.Create(TimeSpanElement.Day, timeSpan.Days),
Tuple.Create(TimeSpanElement.Hour, timeSpan.Hours),
Tuple.Create(TimeSpanElement.Minute, timeSpan.Minutes),
Tuple.Create(TimeSpanElement.Second, timeSpan.Seconds),
Tuple.Create(TimeSpanElement.Millisecond, timeSpan.Milliseconds)
}
.SkipWhile(i => i.Item2 <= 0)
.Take(maxNrOfElements);
return string.Join(", ", parts.Select(p => string.Format("{0} {1}{2}", p.Item2, p.Item1, p.Item2 > 1 ? "s" : string.Empty)));
}
}
示例(LinqPad):
new TimeSpan(1,2,3,4,5).ToFriendlyDisplay(3).Dump();
new TimeSpan(0,5,3,4,5).ToFriendlyDisplay(3).Dump();
显示:
1 Day, 2 Hours, 3 Minutes
5 Hours, 3 Minutes, 4 Seconds
适合我,看看是否适合你。
不是一个功能齐全的实现,但它应该让你足够接近。
DateTime dtNow = DateTime.Now;
DateTime dtYesterday = DateTime.Now.AddDays(-435.0);
TimeSpan ts = dtNow.Subtract(dtYesterday);
int years = ts.Days / 365; //no leap year accounting
int months = (ts.Days % 365) / 30; //naive guess at month size
int weeks = ((ts.Days % 365) % 30) / 7;
int days = (((ts.Days % 365) % 30) % 7);
StringBuilder sb = new StringBuilder();
if(years > 0)
{
sb.Append(years.ToString() + " years, ");
}
if(months > 0)
{
sb.Append(months.ToString() + " months, ");
}
if(weeks > 0)
{
sb.Append(weeks.ToString() + " weeks, ");
}
if(days > 0)
{
sb.Append(days.ToString() + " days.");
}
string FormattedTimeSpan = sb.ToString();
最后,您真的需要让某人知道文件将在 1 年、5 个月、2 周和 3 天后到期吗?你不能告诉他们文件将在 1 年后或 5 个月后到期吗?只需取最大的单位并说出该单位的 n 即可。
现在还有一个看起来非常有趣的Humanizer 项目,它可以做到这一点,甚至更多。
我知道这很旧,但我想用一个很棒的 nuget 包来回答。
Install-Package Humanizer
https://www.nuget.org/packages/Humanizer
https://github.com/MehdiK/Humanizer
他们的 readme.md 中的示例
TimeSpan.FromMilliseconds(1299630020).Humanize(4) => "2 weeks, 1 day, 1 hour, 30 seconds"
@ian-becker 需要功劳
TimeSpan 对象具有 Days、Hours、Minutes 和 Seconds 属性,因此制作将这些值格式化为友好字符串的代码段并不难。
不幸的是,天是最大的价值。比这更长的时间,你将不得不开始担心每年一个月中的几天......等等。在我看来,你最好停在几天(额外的努力似乎不值得获得)。
更新
...我想我会从我自己的评论中提出这个:
可以理解,但是“本文档将在 10 年 3 个月 21 天 2 小时 30 分钟后到期”真的更有帮助还是更愚蠢?如果由我决定,因为这两种表示似乎都不是很有用......
我会保留到期的时间跨度,直到日期合理接近(如果您担心更新文档,可能需要 30 或 60 天)。
对我来说似乎是一个更好的用户体验选择。
这是我的解决方案。它基于此线程中的其他答案,并增加了对原始问题中要求的年份和月份的支持(这是我需要的)。
至于讨论这是否有意义,我会说在某些情况下确实如此。在我的例子中,我们想展示在某些情况下只有几天,而在其他情况下几年的协议期限。
测试;
[Test]
public void ToFriendlyDuration_produces_expected_result()
{
new DateTime(2019, 5, 28).ToFriendlyDuration(null).Should().Be("Until further notice");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2020, 5, 28)).Should().Be("1 year");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2021, 5, 28)).Should().Be("2 years");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2021, 8, 28)).Should().Be("2 years, 3 months");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2019, 8, 28)).Should().Be("3 months");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2019, 8, 31)).Should().Be("3 months, 3 days");
new DateTime(2019, 5, 1).ToFriendlyDuration(new DateTime(2019, 5, 31)).Should().Be("30 days");
new DateTime(2010, 5, 28).ToFriendlyDuration(new DateTime(2020, 8, 28)).Should().Be("10 years, 3 months");
new DateTime(2010, 5, 28).ToFriendlyDuration(new DateTime(2020, 5, 29)).Should().Be("10 years, 1 day");
}
执行;
private class TermAndValue
{
public TermAndValue(string singular, string plural, int value)
{
Singular = singular;
Plural = plural;
Value = value;
}
public string Singular { get; }
public string Plural { get; }
public int Value { get; }
public string Term => Value > 1 ? Plural : Singular;
}
public static string ToFriendlyDuration(this DateTime value, DateTime? endDate, int maxNrOfElements = 2)
{
if (!endDate.HasValue)
return "Until further notice";
var extendedTimeSpan = new TimeSpanWithYearAndMonth(value, endDate.Value);
maxNrOfElements = Math.Max(Math.Min(maxNrOfElements, 5), 1);
var termsAndValues = new[]
{
new TermAndValue("year", "years", extendedTimeSpan.Years),
new TermAndValue("month", "months", extendedTimeSpan.Months),
new TermAndValue("day", "days", extendedTimeSpan.Days),
new TermAndValue("hour", "hours", extendedTimeSpan.Hours),
new TermAndValue("minute", "minutes", extendedTimeSpan.Minutes)
};
var parts = termsAndValues.Where(i => i.Value != 0).Take(maxNrOfElements);
return string.Join(", ", parts.Select(p => $"{p.Value} {p.Term}"));
}
internal class TimeSpanWithYearAndMonth
{
internal TimeSpanWithYearAndMonth(DateTime startDate, DateTime endDate)
{
var span = endDate - startDate;
Months = 12 * (endDate.Year - startDate.Year) + (endDate.Month - startDate.Month);
Years = Months / 12;
Months -= Years * 12;
if (Months == 0 && Years == 0)
{
Days = span.Days;
}
else
{
var startDateExceptYearsAndMonths = startDate.AddYears(Years);
startDateExceptYearsAndMonths = startDateExceptYearsAndMonths.AddMonths(Months);
Days = (endDate - startDateExceptYearsAndMonths).Days;
}
Hours = span.Hours;
Minutes = span.Minutes;
}
public int Minutes { get; }
public int Hours { get; }
public int Days { get; }
public int Years { get; }
public int Months { get; }
}
它可能无法满足您的所有需求,但在v4IFormattable
TimeSpan
中,Microsoft 将在.
要格式化任何超过 1 天的时间段(即月/年/十年等),Timespan 对象是不够的。
假设您的时间跨度是 35 天,那么从 4 月 1 日起您将获得 1 个月零 5 天,而从 12 月 1 日起您将获得 1 个月零 4 天。
是的!我需要同样的很多次,然后现在我创建了我的 onw 包并将其发布在 Nuget 中。欢迎您使用它。包名称是EstecheAssemblies
很容易实现:
using EstecheAssemblies;
var date = new DateTime("2019-08-08 01:03:21");
var text = date.ToFriendlyTimeSpan();
参见how-do-i-calculate-relative-time ,在一年前(由用户号 1 )问 SO 还年轻且不公开的时候。它(可能)是 SO 问题和答案当前年龄显示的基础。