web-dev-qa-db-fra.com

Format TimeSpan supérieur à 24 heures

Disons que je convertis quelques secondes en objet TimeSpan comme ceci:

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

Comment formater l'objet TimeSpan dans un format tel que celui-ci:

>105hr 56mn 47sec

Existe-t-il une fonction intégrée ou dois-je écrire une fonction personnalisée?

32
Maxd

Eh bien, la chose la plus simple à faire est de formater cela vous-même, par exemple.

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

En 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

Je ne sais pas si un des formats TimeSpan dans .NET 4 simplifierait les choses.

51
Jon Skeet

Edit: C # 6/VB 14 introduit chaînes interpolées qui peuvent ou non être plus simples que le premier segment de code de ma réponse originale. Heureusement, la syntaxe pour l'interpolation est identique: un $ précédent.

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

Voir un exemple rapide de C # ici incluant la ValueTuples fonctionnalité introduite en C # 7. Hélas, le seul compilateur en ligne C # 7 que je pouvais trouver fonctionnait .NET Core, ce qui est fastidieux pour de petits exemples, soyez assurés que cela fonctionne exactement de la même manière dans un projet .NET Framework.


Réponse originale

Microsoft n'a pas (actuellement) un simple raccourci de chaîne de format pour cela. Les options les plus faciles ont déjà été partagées.

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)

Cependant, une option trop approfondie consiste à implémenter votre propre ICustomFormatter pour TimeSpan. Je ne le recommanderais pas, à moins que vous n'utilisiez ceci souvent, cela vous ferait gagner du temps à long terme. Cependant, il y a des moments où vous écrivez une classe dans laquelle écrire votre propre variable ICustomFormatter est approprié, aussi ai-je écrit celui-ci à titre d'exemple.

/// <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);
    }
}

Notez que, comme dans les remarques dans le code, TimeSpan.ToString(format, myTimeSpanFormatter) ne fonctionnera pas en raison d'une bizarrerie du .NET Framework, vous devrez donc toujours utiliser string.Format (format, myTimeSpanFormatter) pour utiliser cette classe. Voir Comment créer et utiliser un IFormatProvider personnalisé pour DateTime? .


EDIT: Si vous voulez vraiment, et je veux dire, vraiment, voulez que cela fonctionne avec TimeSpan.ToString(string, TimeSpanFormatter), vous pouvez ajouter ce qui suit à la classe TimeSpanFormatter ci-dessus:

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

Et ajoutez la classe suivante quelque part dans le même espace de noms:

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()));
    }
}

Après cela, vous pouvez utiliser

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

{000.00} est cependant vous voulez que TotalHours int ou double soit formaté. Notez les accolades englobantes, qui ne devraient pas être là dans le cas string.Format (). Notez également que formatterdoit être déclaré (ou converti) sous la forme ICustomFormatter plutôt que TimeSpanFormatter.

Excessif? Oui. Impressionnant? Uhhh ....

7
dx_over_dt

Vous pouvez essayer ceci:

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

Vous devrez peut-être calculer les heures. La plage d'heures dans TimeSpan.ToString est uniquement comprise entre 0 et 23.

Le pire dont vous aurez besoin est de formater la chaîne brute à la Jon Skeet.

1
John

Vous voudrez peut-être envisager d'utiliser le type Duration de Noda Time .

Par exemple:

Duration d = Duration.FromSeconds(sec);

Ou

Duration d = Duration.FromTimeSpan(ts);

Vous pouvez alors simplement le formater en chaîne, comme ceci:

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

Vous pouvez également utiliser l'API basée sur pattern à la place:

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

L’avantage de l’API de modèle est qu’il n’est nécessaire de créer le modèle qu’une seule fois. Si vous avez beaucoup de valeurs à analyser ou à formater, les performances peuvent être considérablement améliorées.

0
Matt Johnson

Conformément à ( https://msdn.Microsoft.com/en-us/library/1ecy8h51(v=vs.110).aspx ), la méthode ToString () par défaut d'un objet TimeSpan utilise le "c". le formatage, ce qui signifie que par défaut, une durée supérieure à 24 heures ressemble à "1.03: 14: 56" lors de la sortie dans une vue rasoir. Cela a créé une certaine confusion avec mes clients qui ne comprennent pas que le "1" représente un jour.

Donc, si vous pouvez utiliser des chaînes interpolées (C # 6 +), un moyen simple de conserver le formatage par défaut autant que possible, tout en utilisant TotalHours au lieu de Days + Hours, est de fournir une propriété get pour afficher le temps comme une chaîne formatée dans, comme suit:

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

Le résultat de cette utilisation en utilisant le même temps que ci-dessus sera "27:14:56". 

0
Michael Villere

Ma solution est:

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

MS Excel a un autre format différent de .NET. 

Vérifiez ce lien http://www.paragon-inc.com/resources/blogs-posts/easy_Excel_interaction_pt8

Je crée une fonction simple qui convertit TimeSpan en DateTime au format MS Excel

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

et vous devez formater la cellule comme ceci:

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

Essayez cette fonction:

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       

Convertir une période en heures et minutes

0
Angkor Wat