13

有谁知道将 TimeSpan 对象转换为“友好”字符串的好库(或代码片段),例如:

  • 两年三个月零四天
  • 一星期零两天

(适用于文件到期系统,到期时间可能是几天到几十年不等)

澄清一下,假设我有一个 7 天的 TimeSpan,应该打印“1 周”、14 天“2 周”、366 天“1 年零 1 天”等。

4

10 回答 10

17

我只是偶然发现了这个问题,因为我想做类似的事情。经过一番谷歌搜索后,我仍然没有找到我想要的:以一种“圆形”方式显示时间跨度。我的意思是:当某个事件需要几天时间时,显示毫秒并不总是有意义的。但是,当它需要几分钟时,它可能会。在这种情况下,我不希望显示 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

适合我,看看是否适合你。

于 2011-08-26T11:38:06.783 回答
14

不是一个功能齐全的实现,但它应该让你足够接近。

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 即可。

于 2009-07-16T16:44:43.097 回答
4

现在还有一个看起来非常有趣的Humanizer 项目,它可以做到这一点,甚至更多。

于 2015-05-25T22:21:41.207 回答
4

我知道这很旧,但我想用一个很棒的 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 需要功劳

于 2020-05-15T07:49:26.690 回答
3

TimeSpan 对象具有 Days、Hours、Minutes 和 Seconds 属性,因此制作将这些值格式化为友好字符串的代码段并不难。

不幸的是,天是最大的价值。比这更长的时间,你将不得不开始担心每年一个月中的几天......等等。在我看来,你最好停在几天(额外的努力似乎不值得获得)。

更新

...我想我会从我自己的评论中提出这个:

可以理解,但是“本文档将在 10 年 3 个月 21 天 2 小时 30 分钟后到期”真的更有帮助还是更愚蠢?如果由我决定,因为这两种表示似乎都不是很有用......

我会保留到期的时间跨度,直到日期合理接近(如果您担心更新文档,可能需要 30 或 60 天)。

对我来说似乎是一个更好的用户体验选择。

于 2009-07-16T16:29:32.363 回答
3

这是我的解决方案。它基于此线程中的其他答案,并增加了对原始问题中要求的年份和月份的支持(这是我需要的)。

至于讨论这是否有意义,我会说在某些情况下确实如此。在我的例子中,我们想展示在某些情况下只有几天,而在其他情况下几年的协议期限。

测试;

[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; }
}
于 2019-05-28T12:02:42.713 回答
1

它可能无法满足您的所有需求,但在v4IFormattableTimeSpan中,Microsoft 将.

于 2009-07-16T16:22:15.063 回答
0

要格式化任何超过 1 天的时间段(即月/年/十年等),Timespan 对象是不够的。

假设您的时间跨度是 35 天,那么从 4 月 1 日起您将获得 1 个月零 5 天,而从 12 月 1 日起您将获得 1 个月零 4 天。

于 2009-07-16T16:37:23.347 回答
0

是的!我需要同样的很多次,然后现在我创建了我的 onw 包并将其发布在 Nuget 中。欢迎您使用它。包名称是EstecheAssemblies

很容易实现:

using EstecheAssemblies;  

var date = new DateTime("2019-08-08 01:03:21");  
var text = date.ToFriendlyTimeSpan();
于 2019-09-28T20:01:45.457 回答
-1

参见how-do-i-calculate-relative-time ,在一年前(由用户号 1 )问 SO 还年轻且不公开的时候。它(可能)是 SO 问题和答案当前年龄显示的基础。

于 2009-07-16T16:46:19.943 回答