【问题标题】:TimeSpan to friendly string library (C#)TimeSpan 到友好的字符串库 (C#)
【发布时间】:2009-07-16 16:19:35
【问题描述】:

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

  • 两年三个月零四天
  • 一周零两天

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

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

【问题讨论】:

标签: timespan c#


【解决方案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

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

【讨论】:

  • 有趣的想法,我会尝试一下(迟来的):)
  • @Gert,我只是在尝试您的扩展方法。作为 TimeSpan 过去了 8 小时,结果是“8 小时,0 分钟,0 秒,0 毫秒”。不应该像您在帖子中所说的那样不显示那些零吗?提前致谢!
  • @Yustme 我的意思是前导 0 部分。如果您不想显示 any 0 部分,可以在 Take 之后添加 .Where(i =&gt; i.Item2 != 0
  • 啊,好吧,我的错。这正是我想要的,谢谢!
  • @Yustme / @Gert-Arnold 不会用 .Where(i =&gt; i.Item2 &gt; 0) 替换 .SkipWhile(i =&gt; i.Item2 &lt;= 0) >要显示的任何 0部分)?另请参阅我对 Humanizer 项目的回答。
【解决方案2】:

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

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 个。

【讨论】:

  • 粉碎!为此干杯。是的,我认为您将值变得更加模糊是正确的,但这是我需要在各种值上对其进行测试的事情之一,看看什么是太少和太少之间的明智中间立场太多细节了。
  • 感谢帕特里克麦克唐纳。我完全错过了我的复制粘贴错误。
  • @Andy_Vulhop 通过以传统方式连接您放入其中的字符串,您正在失去 StringBuilder 的一些好处。你应该使用.Append 两次(仅供参考,它链接。即sb.Append(years.ToString()).Append(" years, ");)。
  • 或者我喜欢 sb.AppendFormat("{0} years, ", years);
【解决方案3】:

现在还有看起来很有趣的Humanizer project 可以做到这一点,甚至更多。

【讨论】:

    【解决方案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 需要功劳

    【讨论】:

      【解决方案5】:

      TimeSpan 对象具有 Days、Hours、Minutes 和 Seconds 属性,因此创建一个将这些值格式化为友好字符串的 sn-p 并不难。

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

      更新

      ...我想我会根据自己的评论提出这个问题:

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

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

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

      【讨论】:

      • Days 可以说“此文档将在 7 天后过期”,但当它是“此文档将在 3892 天后过期”时,这看起来有点愚蠢(而且没有帮助)。我可以看到如何使用模数来做到这一点,但我很懒,不想花时间重新发明轮子:)
      • 可以理解,但是“本文档将在 10 年 3 个月 21 天 2 小时 30 分钟后到期”真的更有帮助还是更愚蠢?如果由我决定,因为这两种表示似乎都不是很有用......我会保留到期的时间跨度,直到日期合理接近(如果您担心更新文档,可能需要 30 或 60 天)。对我来说似乎是一个更好的用户体验选择。
      【解决方案6】:

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

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

      测试;

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

      【讨论】:

        【解决方案7】:

        它可能无法满足您的所有需求,但在 v4 中,Microsoft 将是 implementing IFormattable on TimeSpan

        【讨论】:

        【解决方案8】:

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

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

        【讨论】:

          【解决方案9】:

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

          很容易实现:

          using EstecheAssemblies;  
          
          var date = new DateTime("2019-08-08 01:03:21");  
          var text = date.ToFriendlyTimeSpan();
          

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              猜你喜欢
              • 2010-12-25
              • 2020-06-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-12-06
              • 2011-04-04
              • 1970-01-01
              相关资源
              最近更新 更多