在 Microsoft .NET Framework 4 中,TimeSpan 結構通過增加格式設置和分析支持得到改進,這種支 持可與 DateTime 值的格式設置和分析支持相媲美。在本文中,我將探討新增的格式設置和分析功能,並 提供 TimeSpan 值使用方面的一些實用提示。
.NET Framework 3.5 及更早版本中的格式設置
在 Microsoft .NET Framework 3.5 及更早版本中,用於時間間隔的唯一格式設置方法是無參數 TimeSpan.ToString 方法。返回字符串的具體格式取決於 TimeSpan 值。其中至少包含 TimeSpan 值的小 時、分鐘和秒組成部分。如果是非零值,還包含日組成部分。此外,如果存在小數秒組成部分,則還將包 含計時周期組成部分的所有七位數字。句點(“.”)用作日與小時之間以及秒與小數秒之間的分隔符。
.NET Framework 4 中的擴展格式設置支持
在 .NET Framework 4 中,默認 TimeSpan.ToString 方法的行為沒有變化,但現在新增了兩個重載。 第一個重載只有一個參數,該參數可以是標准或自定義格式字符串,用於定義結果字符串的格式。第二個 重載有兩個參數:一個標准或自定義格式字符串和一個 IFormatProvider 實現,後者表示提供格式設置 信息的區域性。順便說一下,此方法為 TimeSpan 結構提供了 IFormattable 實現,它使 TimeSpan 值可 用於支持復合格式設置的 String.Format 等方法。
除了包含標准或自定義格式字符串以及提供 IFormattable 實現之外,現在設置了格式的字符串還可 以區分區域性。兩個標准格式字符串“g”(一般短格式說明符)和“G”(一般長格式說明符)在結果字 符串中使用當前區域性或特定區域性的格式設置約定。圖 1 中的示例格式對此進行了演示,該示例顯示 的時間間隔結果字符串使用“G”格式字符串以及 en-US 和 fr-FR 區域性設置了格式。
圖 1 使用“G”格式字符串設置格式的時間間隔 (VB)
Visual Basic
Imports System.Globalization
Module Example
Public Sub Main()
Dim interval As New TimeSpan(1, 12, 42, 30, 566)
Dim cultures() As CultureInfo = { New CultureInfo("en-US"),
New CultureInfo("fr-FR") }
For Each culture As CultureInfo In cultures
Console.WriteLine("{0}: {1}", culture, interval.ToString(
"G", culture))
Next
End Sub
End Module
圖 1 使用“G”格式字符串設置格式的時間間隔 (C#)
using System;
using System.Globalization;
public class Example
{
public static void Main()
{
TimeSpan interval = new TimeSpan(1, 12, 42, 30, 566);
CultureInfo[] cultures = { new CultureInfo("en-US"),
new CultureInfo(“"fr-FR") };
foreach (CultureInfo culture in cultures)
Console.WriteLine("{0}: {1}", culture, interval.ToString( _
"G", culture));
}
}
圖 1 中的示例顯示以下輸出:
en-US:1:12:42:30.5660000
fr-FR:1:12:42:30,5660000
.NET Framework 3.5 及更早版本中的分析
在 .NET Framework 3.5 及更早版本中,由靜態 System.TimeSpan.Parse 和 System.TimeSpan.TryParse 方法處理對時間間隔的分析支持,這兩個方法支持數量有限的固定格式。圖 2 中的示例分析某一時間間隔的字符串表示形式,該時間間隔采用了前述方法可識別的每種格式。
圖 2 分析多種格式的時間間隔字符串 (VB)
Module Example
Public Sub Main()
Dim values() As String = {"12", "12.16:07", "12.16:07:32", _
"12.16:07:32.449", "12.16:07:32.4491522",
_
"16:07", "16:07:32", "16:07:32.449" }
For Each value In values
Try
Console.WriteLine("Converted {0} to {1}", _
value, TimeSpan.Parse(value))
Catch e As OverflowException
Console.WriteLine("Overflow: {0}", value)
Catch e As FormatException
Console.WriteLine("Bad Format: {0}", value)
End Try
Next
End Sub
圖 2 分析多種格式的時間間隔字符串 (C#)
using System;
public class Example
{
public static void Main()
{
string[] values = { "12", "12.16:07", "12.16:07:32",
"12.16:07:32.449", "12.16:07:32.4491522",
"16:07", "16:07:32", "16:07:32.449" };
foreach (var value in values)
try {
Console.WriteLine("Converted {0} to {1}",
value, TimeSpan.Parse(value));}
catch (OverflowException) {
Console.WriteLine("Overflow: {0}", value); }
catch (FormatException) {
Console.WriteLine("Bad Format: {0}", value);
}
}
}
圖 2 中的示例顯示以下輸出:
Converted 12 to 12.00:00:00
Converted 12.16:07 to 12.16:07:00
Converted 12.16:07:32 to 12.16:07:32
Converted 12.16:07:32.449 to 12.16:07:32.4490000
Converted 12.16:07:32.4491522 to 12.16:07:32.4491522
Converted 16:07 to 16:07:00
Converted 16:07:32 to 16:07:32
Converted 16:07:32.449 to 16:07:32.4490000
如輸出所示,該方法可以分析單個整數,將該整數解釋為時間間隔中的天數(稍後將對此進行詳細的 說明)。若非單個整數,則要分析的字符串至少須包含小時值和分鐘值。
.NET Framework 4 中的擴展分析支持
在 .NET Framework 4 和 Silverlight 4 中,對時間間隔字符串表示形式的分析支持已得到增強,現 可與日期和時間字符串的分析支持相媲美。TimeSpan 結構現在為 Parse 和 TryParse 方法提供了新的重 載,並提供了全新的 ParseExact 和 TryParseExact 方法,後兩個方法各有四個重載。這些分析方法支 持標准和自定義格式字符串,並為區分區域性的格式設置提供一定的支持。兩個標准格式字符串(“g” 和 “G”)是區分區域性的,而其余的標准格式字符串(“c”、“t”和“T”)以及所有自定義格式字 符串都是固定的。在 .NET Framework 的未來版本中,將會進一步增強對時間間隔的分析和格式設置支持 。
圖 3 中的示例演示了在 .NET Framework 4 中如何使用 ParseExact 方法分析時間間隔數據。它定義 了一個數組,其中包含七個自定義格式字符串;如果要分析的時間間隔字符串表示形式與其中所有格式都 不相符,該方法就會失敗並引發異常。
圖 3 使用 ParseExact 方法分析時間間隔數據 (VB)
Module modMain
Public Sub Main()
Dim formats() As String = { "hh", "%h", "h\:mm", "hh\:mm",
"d\.hh\:mm\:ss", "fffff", "hhmm" }
Dim values() As String = { '16", "1", "16:03", "1:12",
"1.13:34:15", "41237", "0609" }
Dim interval As TimeSpan
For Each value In values
Try
interval = TimeSpan.ParseExact(value, formats, Nothing)
Console.WriteLine("Converted '{0}' to {1}",
value, interval)
Catch e As FormatException
Console.WriteLine("Invalid format: {0}", value)
Catch e As OverflowException
Console.WriteLine("Overflow: {0}", value)
Catch e As ArgumentNullException
Console.WriteLine("No string to parse")
End Try
Next
End Sub
End Module
圖 3 使用 ParseExact 方法分析時間間隔數據 (C#)
using System;
public class Example
{
public static void Main()
{
string[] formats = { "hh", "%h", @"h\:mm", @"hh\:mm",
@"d\.hh\:mm\:ss", "fffff", "hhmm" };
string[] values = { "16", "1", "16:03", '1:12',
"1.13:34:15", "41237", "0609" };
TimeSpan interval;
foreach (var value in values)
{
try {
interval = TimeSpan.ParseExact(value, formats, null);
Console.WriteLine("Converted '{0}' to {1}", value,
interval); }
catch (FormatException) {
Console.WriteLine("Invalid format: {0}", value); }
catch (OverflowException) {
Console.WriteLine("Overflow: {0}", value); }
catch (ArgumentNullException) {
Console.WriteLine("No string to parse");
}
}
}
}
圖 3 中的示例顯示以下輸出:
Converted ‘16’ to 16:00:00
Converted ‘1’ to 01:00:00
Converted ‘16:03’ to 16:03:00
Converted ‘1:12’ to 01:12:00
Converted ‘1.13:34:15’ to 1.13:34:15
Converted ‘41237’ to 00:00:00.4123700
Converted ‘0609’ to 06:09:00
使用單個數值實例化 TimeSpan
有趣的是,如果在 .NET Framework 的任何版本中向 TimeSpan.Parse(String) 方法傳遞這七個時間 間隔字符串,則這些字符串的分析將全部成功,但其中四個字符串會返回不同的結果。對這些字符串調用 TimeSpan.Parse(String) 將產生以下輸出:
Converted ‘16’ to 16.00:00:00
Converted ‘1’ to 1.00:00:00
Converted ‘16:03’ to 16:03:00
Converted ‘1:12’ to 01:12:00
Converted ‘1.13:34:15’ to 1.13:34:15
Converted ‘41237’ to 41237.00:00:00
Converted ‘0609’ to 609.00:00:00
TimeSpan.Parse(String) 與 TimeSpan.ParseExact(String, String[], IFormatProvider) 方法調用 的主要區別在於對表示整數值的字符串的處理方式。TimeSpan.Parse(String) 方法將此類字符串解釋為 日。TimeSpan.ParseExact(String, String[], IFormatProvider) 方法對整數的解釋取決於在字符串數 組參數中提供的自定義格式字符串。在此示例中,只有一個或兩個整數位的字符串將解釋為小時數,具有 四個整數位的字符串將解釋為小時數和分鐘數,而具有五個整數位的字符串將解釋為秒的小數。
在許多情況下,.NET Framework 應用程序以任意格式接收包含時間間隔數據的字符串(如表示毫秒數 的整數或表示小時數的整數)。在以前的 .NET Framework 版本中,須將這樣的數據處理成可接受的格式 ,然後才能傳遞給 TimeSpan.Parse 方法。在 .NET Framework 4 中,可以使用自定義格式字符串定義對 只包含整數的時間間隔字符串的解釋,而無需預先處理字符串數據。圖 4 中的示例為一到五位的整數提 供不同的表示形式,從而演示這一點。
圖 4 1 到 5 位整數表示形式 (VB)
Module Example
Public Sub Main()
Dim formats() As String = { "%h", "hh", "fff", "ffff', 'fffff' }
Dim values() As String = { "3", "17", "192", "3451",
"79123", "01233" }
For Each value In values
Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, formats, Nothing, interval) Then
Console.WriteLine("Converted '{0}' to {1}",
value, interval.ToString())
Else
Console.WriteLine("Unable to parse {0}.", value)
End If
Next
End Sub
End Module
圖 4 1 到 5 位整數表示形式 (C#)
using System;
public class Example
{
public static void Main()
{
string[] formats = { "%h", "hh", "fff", "ffff", "fffff" };
string[] values = { "3", "17", "192", "3451", "79123", "01233" };
foreach (var value in values)
{
TimeSpan interval;
if (TimeSpan.TryParseExact(value, formats, null, out interval))
Console.WriteLine("Converted '{0}' to {1}",
value, interval.ToString());
else
Console.WriteLine("Unable to parse {0}.", value);
}
}
}
圖 4 中的示例顯示以下輸出:
Converted ‘3’ to 03:00:00
Converted ‘17’ to 17:00:00
Converted ‘192’ to 00:00:00.1920000
Converted ‘3451’ to 00:00:00.3451000
Converted ‘79123’ to 00:00:00.7912300
Converted ‘01233’ to 00:00:00.0123300
處理分析時間間隔時產生的 OverflowException
.NET Framework 4 中所引入的這些新 TimeSpan 格式設置和分析功能保留了一個可能會對某些用戶造 成不便的行為。為了向後兼容,在以下情況下 TimeSpan parsing 方法會引發 OverflowException:
如果小時組成部分的值超過 23。
如果分鐘組成部分的值超過 59。
如果秒組成部分的值超過 59。
可以通過多種方式來處理此異常。可以使用 Int32.Parse 方法(而不是調用 TimeSpan.Parse 方法) 將各字符串組成部分轉換為整數值,然後將其傳遞給某一 TimeSpan 類構造函數。與 TimeSpan 分析方法 不同,TimeSpan 構造函數不會在傳遞給該構造函數的小時、分鐘或秒值超出范圍時引發 OverflowException。
這是一種可接受的解決方法,但它也存在一個限制:它要求在調用 TimeSpan 構造函數之前分析所有 字符串並將其轉換為整數。如果要分析的大部分數據在分析操作期間都不會溢出,則此解決方法會導致不 必要的處理。
另一種解決方法是先嘗試分析數據,然後再處理在時間間隔的各組成部分超出范圍時引發的 OverflowException。這也是一種可接受的解決方法,但應用程序中的不必要異常處理可能會產生昂貴的 開銷。
最佳解決方法是先使用 TimeSpan.TryParse 方法分析數據,然後僅當該方法返回 false 時再處理時 間間隔的各組成部分。如果分析操作失敗,則可以使用 String.Split 方法將時間間隔的字符串表示形式 拆分為各組成部分,然後將其傳遞給 TimeSpan(Int32, Int32, Int32, Int32, Int32) 構造函數。圖 5 中的示例提供了一個簡單實現:
圖 5 處理非標准時間間隔字符串 (VB)
Module Example
Public Sub Main()
Dim values() As String = { "37:16:45.33", "0:128:16.324",
"120:08" }
Dim interval As TimeSpan
For Each value In values
Try
interval = ParseIntervalWithoutOverflow(value)
Console.WriteLine("'{0}' --> {1}", value, interval)
Catch e As FormatException
Console.WriteLine("Unable to parse {0}.", value)
End Try
Next
End Sub
Private Function ParseIntervalWithoutOverflow(value As String)
As TimeSpan
Dim interval As TimeSpan
If Not TimeSpan.TryParse(value, interval) Then
Try
‘ Handle failure by breaking string into components.
Dim components() As String = value.Split( {"."c, ":"c } )
Dim offset As Integer = 0
Dim days, hours, minutes, seconds, milliseconds As Integer
‘ Test whether days are present.
If value.IndexOf(".") >= 0 AndAlso
value.IndexOf(".") < value.IndexOf(":") Then
offset = 1
days = Int32.Parse(components(0))
End If
‘ Call TryParse to parse values so no exceptions result.
hours = Int32.Parse(components(offset))
minutes = Int32.Parse(components(offset + 1))
If components.Length >= offset + 3 Then
seconds = Int32.Parse(components(offset + 2))
End If
If components.Length >= offset + 4 Then
milliseconds = Int32.Parse(components(offset + 3))
End If
‘ Call constructor.
interval = New TimeSpan(days, hours, minutes,
seconds, milliseconds)
Catch e As FormatException
Throw New FormatException(
String.Format("Unable to parse '{0}'"), e)
Catch e As ArgumentOutOfRangeException
Throw New FormatException(
String.Format("Unable to parse '{0}'"), e)
Catch e As OverflowException
Throw New FormatException(
String.Format("Unable to parse '{0}'"), e)
Catch e As ArgumentNullException
Throw New ArgumentNullException("value cannot be null.",
e)
End Try
End If
Return interval
End Function
End Module
圖 5 處理非標准時間間隔字符串 (C#)
using System;
public class Example
{
public static void Main()
{
string[] values = { "37:16:45.33", "0:128:16.324", "120:08" };
TimeSpan interval;
foreach (var value in values)
{
try {
interval = ParseIntervalWithoutOverflow(value);
Console.WriteLine("'{0}' --> {1}", value, interval);
}
catch (FormatException) {
Console.WriteLine("Unable to parse {0}.", value);
}
}
}
private static TimeSpan ParseIntervalWithoutOverflow(string value)
{
TimeSpan interval;
if (! TimeSpan.TryParse(value, out interval))
{
try {
// Handle failure by breaking string into components.
string[] components = value.Split(
new Char[] {'.', ':' } );
int offset = 0;
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
int milliseconds = 0;
// Test whether days are present.
if (value.IndexOf(".") >= 0 &&
value.IndexOf(".") < value.IndexOf(":"))
{
offset = 1;
days = Int32.Parse(components[0]);
}
// Call TryParse to parse values so no exceptions result.
hours = Int32.Parse(components[offset]);
minutes = Int32.Parse(components[offset + 1]);
if (components.Length >= offset + 3)
seconds = Int32.Parse(components[offset + 2]);
if (components.Length >= offset + 4)
milliseconds = Int32.Parse(components[offset + 3]);
// Call constructor.
interval = new TimeSpan(days, hours, minutes,
seconds, milliseconds);
}
catch (FormatException e) {
throw new FormatException(
String.Format("Unable to parse '{0}'"), e);
}
catch (ArgumentOutOfRangeException e) {
throw new FormatException(
String.Format("Unable to parse '{0}'"), e);
}
catch (OverflowException e)
{
throw new FormatException(
String.Format("Unable to parse '{0}'"), e);
}
catch (ArgumentNullException e)
{
throw new ArgumentNullException("value cannot be null.",
e);
}
}
return interval;
}
}
如以下輸出所示,圖 5 中的示例成功處理了四個大於 23 的值,以及大於 59 的分鐘值和秒值:
‘37:16:45.33’ --> 1.13:16:45.0330000
‘0:128:16.324’ --> 02:08:16.3240000
‘120:08’ --> 5.00:08:00
應用程序兼容性
矛盾的是,在 .NET Framework 4 中對 TimeSpan 值的增強格式設置支持破壞了在 .NET Framework 早期版本中對 TimeSpan 值做過格式設置的一些應用程序。例如,下面的代碼在 .NET Framework 3.5 中 可正常執行,但在 .NET Framework 4 中執行時會引發 FormatException:
string result = String.Format("{0:r}", new TimeSpan(4, 23, 17));
為了對參數列表中的每個參數進行格式設置,String.Format 方法將確定該對象是否實現了 IFormattable。如果實現,則會調用該對象的 IFormattable.ToString 實現。如果未實現,則會放棄索 引項中提供的任何格式字符串並調用該對象的無參數 ToString 方法。
在 .NET Framework 3.5 及更早版本中,TimeSpan 未實現 IFormattable,也不支持格式字符串。因 此,將忽略“r”格式字符串,並將調用無參數 TimeSpan.ToString 方法。而在 .NET Framework 4 中, 將調用 TimeSpan.ToString(String, IFormatProvider) 並傳遞不受支持的格式字符串,從而導致該異常 。
如果可能,應修改此代碼使之調用無參數 TimeSpan.ToString 方法,或向格式設置方法傳遞有效的格 式字符串。但是,如果這樣做不可行,可向應用程序的配置文件中添加一個 <TimeSpan_LegacyFormatMode> 元素,類似如下所示:
<?xml version ="1.0"?>
<configuration>
<runtime>
<TimeSpan_LegacyFormatMode enabled="true"/>
</runtime>
</configuration>
通過將其 enabled 特性設置為 true,可確保 TimeSpan 使用舊式格式設置行為。