Формат TimeSpan больше 24 часов - PullRequest
       29

Формат TimeSpan больше 24 часов

34 голосов
/ 17 августа 2010

Скажем, я конвертирую несколько секунд в объект TimeSpan следующим образом:

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

Как мне отформатировать объект TimeSpan в следующий формат:

>105hr 56mn 47sec

Есть ли встроенная функция или мне нужно написать пользовательскую функцию?

Ответы [ 10 ]

54 голосов
/ 17 августа 2010

Ну, самое простое, что нужно сделать, это отформатировать это самостоятельно, например,

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

Я не знаю, есть ли какие-либо из TimeSpanформатирование в .NET 4 сделает это проще.

9 голосов
/ 15 июля 2014

Редактировать : введено C # 6 / VB 14 интерполированные строки , которые могут быть или не быть проще, чем первый сегмент кода моего исходного ответа . К счастью, синтаксис для интерполяции идентичен: предшествующий $.

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")

Смотрите краткий пример C # здесь , включая функцию ValueTuple s , представленную в C # 7. Увы, единственный компилятор 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)

Однако, слишком тщательным вариантом является реализация вашего ICustomFormatter для TimeSpan. Я не рекомендовал бы это, если вы не используете это так часто, что это сэкономит ваше время в долгосрочной перспективе. Однако бывают случаи, когда вы пишете класс, в котором уместно написать собственный 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) для использования этого класса. См. Как создать и использовать пользовательский IFormatProvider для DateTime? .


EDIT : Если вы действительно, и я имею в виду действительно , хотите, чтобы это работало с 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.

Чрезмерная? Да. Потрясающие? Уххх ....

4 голосов
/ 17 августа 2010

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

2 голосов
/ 05 апреля 2017

Вы можете попробовать это:

TimeSpan ts = TimeSpan.FromSeconds(1254234568);
Console.WriteLine($"{((int)ts.TotalHours).ToString("d2")}hr {ts.Minutes.ToString("d2")}mm {ts.Seconds.ToString("d2")}sec");
1 голос
/ 17 августа 2010

Возможно, вам понадобится рассчитать часы.Диапазон часов в TimeSpan.ToString составляет всего 0-23.

Самое худшее, что вам нужно, это выполнить форматирование необработанных строк в стиле Джона Скита.

0 голосов
/ 30 марта 2018

Согласно (https://msdn.microsoft.com/en-us/library/1ecy8h51(v=vs.110).aspx),), метод ToString () по умолчанию для объекта TimeSpan использует форматирование «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».

0 голосов
/ 20 ноября 2014

MS Excel имеет другой формат, отличный от .NET.

Проверьте эту ссылку http://www.paragon -inc.com / resources / blogs-posts / easy_excel_interaction_pt8

Я создаю простую функцию, которая конвертирует TimeSpan в DateTime в формате 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";
0 голосов
/ 18 ноября 2014

Мое решение:

string text = Math.Floor(timeUsed.TotalHours) + "h " + ((int)timeUsed.TotalMinutes) % 60 + "min";
0 голосов
/ 05 ноября 2014

Вы можете рассмотреть возможность использования Noda Time s Duration type.

Например:

Duration d = Duration.FromSeconds(sec);

Или

Duration d = Duration.FromTimeSpan(ts);

Затем вы можете просто отформатировать его как строку, например:

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

В качестве альтернативы вы можете использовать API на основе шаблонов вместо:

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

Преимущество шаблона API заключается в том, что вам нужно создать шаблон только один раз. Если у вас есть много значений для анализа или форматирования, это может значительно повысить производительность.

0 голосов
/ 17 августа 2010

Попробуйте эту функцию:

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       

Преобразование промежутка времени в часы и минуты

...