Say I convert some seconds into the TimeSpan object like this:
Dim sec = 1254234568
Dim t As TimeSpan = TimeSpan.FromSeconds(sec)
How do I format the TimeSpan object into a format like the following:
>105hr 56mn 47sec
Is there a built-in function or do I need to write a custom function?
Well, the simplest thing to do is to format this yourself, e.g.
return string.Format("{0}hr {1}mn {2}sec",
(int) span.TotalHours,
span.Minutes,
span.Seconds);
In 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
I don't know whether any of the TimeSpan
formatting in .NET 4 would make this simpler.
Edit: C# 6/VB 14 introduced interpolated strings which may or may not be simpler than the first code segment of my original answer. Thankfully, the syntax for interpolation is identical: a preceding $
.
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")
See a quick example of C# here including the ValueTuple
s feature introduced in C# 7. Alas, the only C#7 online compiler I could find runs .NET Core, which is super cumbersome for small examples, but rest assured it works exactly the same in a .NET Framework project.
Original Answer
Microsoft doesn't (currently) have a simple format string shortcut for this. The easiest options have already been shared.
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)
However, an overly-thorough option is to implement your own ICustomFormatter
for TimeSpan
. I wouldn't recommend it unless you use this so often that it would save you time in the long run. However, there are times where you DO write a class where writing your own ICustomFormatter
is appropriate, so I wrote this one as an example.
/// <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);
}
}
Note, as in the remarks in the code, TimeSpan.ToString(format, myTimeSpanFormatter)
will not work due to a quirk of the .NET Framework, so you'll always have to use string.Format(format, myTimeSpanFormatter) to use this class. See How to create and use a custom IFormatProvider for DateTime?.
EDIT:
If you really, and I mean really, want this to work with TimeSpan.ToString(string, TimeSpanFormatter)
, you can add the following to the above TimeSpanFormatter
class:
/// <remarks>
/// Update this as needed.
/// </remarks>
internal static string[] GetRecognizedFormats()
{
return new string[] { "td", "th", "tm", "ts", "tf", "TD", "TH", "TM", "TS", "TF" };
}
And add the following class somewhere in the same namespace:
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()));
}
}
After this, you may use
ICustomFormatter formatter = new TimeSpanFormatter();
string myStr = myTimeSpan.ToString(@"TH,{000.00}h\:tm\m\:ss\s", formatter);
where {000.00}
is however you want the TotalHours int or double to be formatted. Note the enclosing braces, which should not be there in the string.Format() case. Also note, formatter
must be declared (or cast) as ICustomFormatter
rather than TimeSpanFormatter
.
Excessive? Yes. Awesome? Uhhh....
string.Format("{0}hr {1}mn {2}sec", (int) t.TotalHours, t.Minutes, t.Seconds);
Try this Function:
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
You may wish to consider using Noda Time's Duration
type.
For example:
Duration d = Duration.FromSeconds(sec);
Or
Duration d = Duration.FromTimeSpan(ts);
You can then simply format it as a string, like this:
string result = d.ToString("H'hr' m'mn' s'sec'", CultureInfo.InvariantCulture);
Alternatively, you can use the pattern-based API instead:
DurationPattern p = DurationPattern.CreateWithInvariantCulture("H'hr' m'mn' s'sec'");
string result = p.Format(d);
The advantage with the pattern API is that you only need to create the pattern once. If you have a lot of values to parse or format, there can be a significant performance benefit.
MS Excel has other format different to .NET.
Check this Link http://www.paragon-inc.com/resources/blogs-posts/easy_excel_interaction_pt8
I create a simple function that convert a TimeSpan in a DateTime with the MS Excel format
public static DateTime MyApproach(TimeSpan time)
{
return new DateTime(1900, 1, 1).Add(time).AddDays(-2);
}
and you need to format the cell like this:
col.Style.Numberformat.Format = "[H]:mm:ss";
As per (https://msdn.microsoft.com/en-us/library/1ecy8h51(v=vs.110).aspx), the default ToString() method for a TimeSpan object uses the "c" formatting, which means that by default, a timespan longer than 24 hours looks something like "1.03:14:56" when output to a razor view. This caused some confusion with my customers who do not understand that the "1." represents one day.
So, if you can use interpolated strings (C#6+), an easy way that I came up with to preserve the default formatting as much as possible, while using TotalHours instead of Days+Hours is to provide a get property to output the time as a formatted string in, like so:
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")}";
}
}
The result of this using the same time as above will be "27:14:56".