Редактировать : введено 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
.
Чрезмерная? Да. Потрясающие? Уххх ....