【问题标题】:Format TimeSpan greater than 24 hour格式化 TimeSpan 大于 24 小时
【发布时间】:2011-03-31 03:11:21
【问题描述】:

假设我将一些秒转换为 TimeSpan 对象,如下所示:

Dim sec = 1254234568
Dim t As TimeSpan = TimeSpan.FromSeconds(sec)

如何将 TimeSpan 对象格式化为如下格式:

>105hr 56mn 47sec

有内置函数还是需要写自定义函数?

【问题讨论】:

    标签: c# vb.net formatting timespan


    【解决方案1】:

    嗯,最简单的做法是自己格式化,例如

    return string.Format("{0}hr {1}mn {2}sec",
                         (int) span.TotalHours,
                         span.Minutes,
                         span.Seconds);
    

    在 VB 中:

    Public Shared Function FormatTimeSpan(span As TimeSpan) As String
        Return String.Format("{0}hr {1}mn {2}sec", _
                             CInt(Math.Truncate(span.TotalHours)), _
                             span.Minutes, _
                             span.Seconds)
    End Function
    

    我不知道 .NET 4 中的任何 TimeSpan 格式是否会使这更简单。

    【讨论】:

    • 不过,您可能想提供 VB 代码,因为问题中的代码看起来像这样 :-)
    • 我错过了什么吗?当我的回答与 Jon Skeet 不一致时,我会感到紧张 :-)
    • ((int) span.TotalMinutes) % 60 可以替换为span.Minutes。秒也是一样。
    • 哎呀,是的,在 TotalHours 之后被带走了 - 这是必需的。
    • @JeffBridgman:如果时间跨度是 -30 分钟,您不希望它显示为 -1 小时,对吗?如果没有 CInt,我会预计它会显示为“1.0”而不是“1”,但我怀疑我没有检查它。我不确定您所说的“我认为这种情况还没有”是什么意思——但是是的,.NET 4 中的自定义格式确实会使这变得更简单。
    【解决方案2】:

    string.Format("{0}hr {1}mn {2}sec", (int) t.TotalHours, t.Minutes, t.Seconds);

    【讨论】:

    • 这仅适用于 TimeSpan
    • 你应该把它改成{0:f0} ..., t.TotalHours, ...
    • 不过,您需要调整 (int) t.TotalHours,请参阅我对 Jon Skeet 关于舍入的帖子的评论。
    【解决方案3】:

    您可能需要计算小时数。 TimeSpan.ToString 中小时的范围只有 0-23。

    您需要做的最糟糕的事情是对 la Jon Skeet 进行原始字符串格式化。

    【讨论】:

      【解决方案4】:

      试试这个功能:

      Public Shared Function GetTimeSpanString(ByVal ts As TimeSpan) As String
              Dim output As New StringBuilder()
      
              Dim needsComma As Boolean = False
      
              If ts = Nothing Then
      
                  Return "00:00:00"
      
              End If
      
              If ts.TotalHours >= 1 Then
                  output.AppendFormat("{0} hr", Math.Truncate(ts.TotalHours))
                  If ts.TotalHours > 1 Then
                      output.Append("s")
                  End If
                  needsComma = True
              End If
      
              If ts.Minutes > 0 Then
                  If needsComma Then
                      output.Append(", ")
                  End If
                  output.AppendFormat("{0} m", ts.Minutes)
                  'If ts.Minutes > 1 Then
                  '    output.Append("s")
                  'End If
                  needsComma = True
              End If
      
              Return output.ToString()
      
       End Function       
      

      Convert A Timespan To Hours And Minutes

      【讨论】:

        【解决方案5】:

        2018 年 10 月编辑:C# 6/VB 14 引入了interpolated strings,它可能比我的原始答案的第一个代码段更简单,也可能不简单。值得庆幸的是,两种语言的插值语法是相同的:前面的 $

        C# 6

        TimeSpan t = new TimeSpan(105, 56, 47);
        Console.WriteLine($"{(int)t.TotalHours}h {t:mm}mn {t:ss}sec");
        

        Visual Basic 14

        dim t As New TimeSpan(105, 56, 47)
        Console.WriteLine($"{CInt(Math.Truncate(t.TotalHours))}h {t:mm}mn {t:ss}sec")
        

        2021 年 11 月编辑:以上答案仅适用于正数 TimeSpans 和小于或等于 -1 小时的负数。如果您在(-1, 0]hr 范围内有一个负数TimeSpan,您需要自己手动插入负数。请注意,原始答案也是如此。

        TimeSpan t = TimeSpan.FromSeconds(-30 * 60 - 10); // -(30mn 10 sec)
        Console.WriteLine($"{(ts.Ticks < 0 && (int)ts.TotalHours == 0 ? "-" : "")}{(int)t.TotalHours}h {t:mm}mn {t:ss}sec");
        

        由于这很麻烦,我建议创建一个辅助函数。

        string Neg(TimeSpan ts)
        {
          return ts.Ticks < 0 && (int)ts.TotalHours == 0 ? "-" : "";
        }
        
        TimeSpan t = TimeSpan.FromSeconds(-30 * 60 - 10); // -(30mn 10 sec)
        Console.WriteLine($"{Neg(ts)}{(int)t.TotalHours}h {t:mm}mn {t:ss}sec");
        

        我不太了解 VB,无法编写等效版本。

        查看 C# here 的快速示例,包括 C# 7 中引入的 ValueTuples 功能。唉,我能找到的唯一 C#7 在线编译器运行 .NET Core,这对于小示例来说非常麻烦,但休息一下确保它在 .NET Framework 项目中的工作方式完全相同。


        原答案

        Microsoft(当前)没有为此提供简单的格式字符串快捷方式。已经分享了最简单的选项。

        C#

        string.Format("{0}hr {1:mm}mn {1:ss}sec", (int)t.TotalHours, t);
        

        VB

        String.Format("{0}hr {1:mm}mn {1:ss}sec", _
                      CInt(Math.Truncate(t.TotalHours)), _
                      t)
        

        但是,一个过于彻底的选择是为TimeSpan 实现您自己的ICustomFormatter。我不会推荐它,除非你经常使用它,从长远来看它会节省你的时间。但是,有时您确实编写了一个适合自己编写ICustomFormatter 的类,所以我写了这个作为示例。

        /// <summary>
        /// Custom string formatter for TimeSpan that allows easy retrieval of Total segments.
        /// </summary>
        /// <example>
        /// TimeSpan myTimeSpan = new TimeSpan(27, 13, 5);
        /// string.Format("{0:th,###}h {0:mm}m {0:ss}s", myTimeSpan) -> "27h 13m 05s"
        /// string.Format("{0:TH}", myTimeSpan) -> "27.2180555555556"
        /// 
        /// NOTE: myTimeSpan.ToString("TH") does not work.  See Remarks.
        /// </example>
        /// <remarks>
        /// Due to a quirk of .NET Framework (up through version 4.5.1), 
        /// <code>TimeSpan.ToString(format, new TimeSpanFormatter())</code> will not work; it will always call 
        /// TimeSpanFormat.FormatCustomized() which takes a DateTimeFormatInfo rather than an 
        /// IFormatProvider/ICustomFormatter.  DateTimeFormatInfo, unfortunately, is a sealed class.
        /// </remarks>
        public class TimeSpanFormatter : IFormatProvider, ICustomFormatter
        {
            /// <summary>
            /// Used to create a wrapper format string with the specified format.
            /// </summary>
            private const string DefaultFormat = "{{0:{0}}}";
        
            /// <remarks>
            /// IFormatProvider.GetFormat implementation. 
            /// </remarks>
            public object GetFormat(Type formatType)
            {
                // Determine whether custom formatting object is requested. 
                if (formatType == typeof(ICustomFormatter))
                {
                    return this;
                }
        
                return null;
            }
        
            /// <summary>
            /// Determines whether the specified format is looking for a total, and formats it accordingly.
            /// If not, returns the default format for the given <para>format</para> of a TimeSpan.
            /// </summary>
            /// <returns>
            /// The formatted string for the given TimeSpan.
            /// </returns>
            /// <remarks>
            /// ICustomFormatter.Format implementation.
            /// </remarks>
            public string Format(string format, object arg, IFormatProvider formatProvider)
            {
                // only apply our format if there is a format and if the argument is a TimeSpan
                if (string.IsNullOrWhiteSpace(format) ||
                    formatProvider != this || // this should always be true, but just in case...
                    !(arg is TimeSpan) ||
                    arg == null)
                {
                    // return the default for whatever our format and argument are
                    return GetDefault(format, arg);
                }
        
                TimeSpan span = (TimeSpan)arg;
        
                string[] formatSegments = format.Split(new char[] { ',' }, 2);
                string tsFormat = formatSegments[0];
        
                // Get inner formatting which will be applied to the int or double value of the requested total.
                // Default number format is just to return the number plainly.
                string numberFormat = "{0}";
                if (formatSegments.Length > 1)
                {
                    numberFormat = string.Format(DefaultFormat, formatSegments[1]);
                }
        
                // We only handle two-character formats, and only when those characters' capitalization match
                // (e.g. 'TH' and 'th', but not 'tH').  Feel free to change this to suit your needs.
                if (tsFormat.Length != 2 ||
                    char.IsUpper(tsFormat[0]) != char.IsUpper(tsFormat[1]))
                {
                    return GetDefault(format, arg);
                }
        
                // get the specified time segment from the TimeSpan as a double
                double valAsDouble;
                switch (char.ToLower(tsFormat[1]))
                {
                    case 'd':
                        valAsDouble = span.TotalDays;
                        break;
                    case 'h':
                        valAsDouble = span.TotalHours;
                        break;
                    case 'm':
                        valAsDouble = span.TotalMinutes;
                        break;
                    case 's':
                        valAsDouble = span.TotalSeconds;
                        break;
                    case 'f':
                        valAsDouble = span.TotalMilliseconds;
                        break;
                    default:
                        return GetDefault(format, arg);
                }
        
                // figure out if we want a double or an integer
                switch (tsFormat[0])
                {
                    case 'T':
                        // format Total as double
                        return string.Format(numberFormat, valAsDouble);
        
                    case 't':
                        // format Total as int (rounded down)
                        return string.Format(numberFormat, (int)valAsDouble);
        
                    default:
                        return GetDefault(format, arg);
                }
            }
        
            /// <summary>
            /// Returns the formatted value when we don't know what to do with their specified format.
            /// </summary>
            private string GetDefault(string format, object arg)
            {
                return string.Format(string.Format(DefaultFormat, format), arg);
            }
        }
        

        注意,正如代码中的注释,TimeSpan.ToString(format, myTimeSpanFormatter) 由于 .NET Framework 的怪癖而无法工作,因此您始终必须使用 string.Format(format, myTimeSpanFormatter) 来使用此类。见How to create and use a custom IFormatProvider for DateTime?

        编辑: 如果你真的,我的意思是真的,希望它与TimeSpan.ToString(string, TimeSpanFormatter)一起工作,你可以在上面的TimeSpanFormatter类中添加以下内容:

        /// <remarks>
        /// Update this as needed.
        /// </remarks>
        internal static string[] GetRecognizedFormats()
        {
            return new string[] { "td", "th", "tm", "ts", "tf", "TD", "TH", "TM", "TS", "TF" };
        }
        

        并在同一命名空间的某处添加以下类:

        public static class TimeSpanFormatterExtensions
        {
            private static readonly string CustomFormatsRegex = string.Format(@"([^\\])?({0})(?:,{{([^(\\}})]+)}})?", string.Join("|", TimeSpanFormatter.GetRecognizedFormats()));
        
            public static string ToString(this TimeSpan timeSpan, string format, ICustomFormatter formatter)
            {
                if (formatter == null)
                {
                    throw new ArgumentNullException();
                }
        
                TimeSpanFormatter tsFormatter = (TimeSpanFormatter)formatter;
        
                format = Regex.Replace(format, CustomFormatsRegex, new MatchEvaluator(m => MatchReplacer(m, timeSpan, tsFormatter)));
                return timeSpan.ToString(format);
            }
        
            private static string MatchReplacer(Match m, TimeSpan timeSpan, TimeSpanFormatter formatter)
            {
                // the matched non-'\' char before the stuff we actually care about
                string firstChar = m.Groups[1].Success ? m.Groups[1].Value : string.Empty;
                
                string input;
                if (m.Groups[3].Success)
                {
                    // has additional formatting
                    input = string.Format("{0},{1}", m.Groups[2].Value, m.Groups[3].Value);
                }
                else
                {
                    input = m.Groups[2].Value;
                }
        
                string replacement = formatter.Format(input, timeSpan, formatter);
                if (string.IsNullOrEmpty(replacement))
                {
                    return firstChar;
                }
        
                return string.Format("{0}\\{1}", firstChar, string.Join("\\", replacement.ToCharArray()));
            }
        }
        

        在此之后,您可以使用

        ICustomFormatter formatter = new TimeSpanFormatter();
        string myStr = myTimeSpan.ToString(@"TH,{000.00}h\:tm\m\:ss\s", formatter);
        

        {000.00} 是您想要格式化 TotalHours int 或 double 的地方。注意括号,它不应该出现在 string.Format() 的情况下。另请注意,formatter 必须声明(或强制转换)为 ICustomFormatter 而不是 TimeSpanFormatter

        过度?是的。惊人的?呃……

        【讨论】:

        • 您的简短而甜蜜的答案考虑到了前导零,漂亮!被“47:33:4”之类的东西卡住了一段时间......比Skeet本人更好!
        • 我真的很喜欢这个解决方案。不幸的是,它不能用于双向绑定场景,因为没有TimeSpan.Parse 的实现会采用ICustomFormatter 的实例。
        • @modiX 你能发布一个你如何使用它的例子吗?我也许能够找到一个解决方案,并且我想开始使用正确的上下文。在扩展方法和DateTime.ParseExact(input, format, formatProvder)之间,或许有解决办法。
        【解决方案6】:

        您不妨考虑使用Noda TimeDuration 类型。

        例如:

        Duration d = Duration.FromSeconds(sec);
        

        或者

        Duration d = Duration.FromTimeSpan(ts);
        

        然后您可以简单地将其格式化为字符串,如下所示:

        string result = d.ToString("H'hr' m'mn' s'sec'", CultureInfo.InvariantCulture);
        

        或者,您可以改用pattern-based API

        DurationPattern p = DurationPattern.CreateWithInvariantCulture("H'hr' m'mn' s'sec'");
        string result = p.Format(d);
        

        模式 API 的优点是您只需要创建一次模式。如果您有很多值要解析或格式化,则可以显着提高性能。

        【讨论】:

          【解决方案7】:

          我的解决办法是:

          string text = Math.Floor(timeUsed.TotalHours) + "h " + ((int)timeUsed.TotalMinutes) % 60 + "min";
          

          【讨论】:

            【解决方案8】:

            MS Excel 具有不同于 .NET 的其他格式。

            查看此链接http://www.paragon-inc.com/resources/blogs-posts/easy_excel_interaction_pt8

            我创建了一个简单的函数,可以将 DateTime 中的 TimeSpan 转换为 MS Excel 格式

                public static DateTime MyApproach(TimeSpan time)
                {
                    return new DateTime(1900, 1, 1).Add(time).AddDays(-2);
                }
            

            你需要像这样格式化单元格:

            col.Style.Numberformat.Format = "[H]:mm:ss";
            

            【讨论】:

              【解决方案9】:

              你可以试试这个:

              TimeSpan ts = TimeSpan.FromSeconds(1254234568);
              Console.WriteLine($"{((int)ts.TotalHours).ToString("d2")}hr {ts.Minutes.ToString("d2")}mm {ts.Seconds.ToString("d2")}sec");
              

              【讨论】:

              • toString("d2") 与已接受答案的连接获得最佳结果
              【解决方案10】:

              根据 (https://msdn.microsoft.com/en-us/library/1ecy8h51(v=vs.110).aspx),TimeSpan 对象的默认 ToString() 方法使用“c”格式,这意味着默认情况下,超过 24 小时的时间跨度类似于“1.03:14:56”当输出到剃刀视图时。这引起了我的一些不理解“1”的客户的困惑。代表一天。

              因此,如果您可以使用插值字符串 (C#6+),我想出的一种简单方法是尽可能保留默认格式,同时使用 TotalHours 而不是 Days+Hours 是提供 get 属性将时间输出为格式化字符串,如下所示:

              public TimeSpan SystemTime { get; set; }
              public string SystemTimeAsString
              {
                  get
                  {
                      // Note: ignoring fractional seconds.
                      return $"{(int)SystemTime.TotalHours}:SystemTime.Minutes.ToString("00")}:SystemTime.Seconds.ToString("00")}";
                  }
              }
              

              使用与上述相同时间的结果将是“27:14:56”。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2017-08-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-01-20
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多