DateTimeFormatInfo

本文对此 API 的参考文档进行补充说明。

DateTimeFormatInfo 类的属性包含用于格式化或解析日期和时间值的区域性特定信息,如下所示:

  • 用于格式化日期值的模式。
  • 用于格式化时间值的模式。
  • 一周中某天的名称。
  • 一年中某月的名称。
  • 时间值中使用的上午和下午指示符。
  • 表示日期的日历。

实例化 DateTimeFormatInfo 对象

DateTimeFormatInfo 对象可以代表固定区域性、特定区域性、非特定区域性或当前区域性的格式化约定。 本节讨论如何实例化每种类型的 DateTimeFormatInfo 对象。

实例化固定区域性的 DateTimeFormatInfo 对象

固定区域性代表对区域性不敏感的区域性。 它基于英语,但不基于任何特定的英语国家/地区。 尽管特定区域性的数据可以是动态的,并且可以更改以反映新的区域约定或用户偏好,但固定区域性的数据不会更改。 可以通过以下方式实例化代表固定区域性格式化约定的 DateTimeFormatInfo 对象:

以下示例使用其中每个方法来实例化代表固定区域性的 DateTimeFormatInfo 对象。 然后,它将指示对象是否是只读的。

System.Globalization.DateTimeFormatInfo dtfi;

dtfi = System.Globalization.DateTimeFormatInfo.InvariantInfo;
Console.WriteLine(dtfi.IsReadOnly);

dtfi = new System.Globalization.DateTimeFormatInfo();
Console.WriteLine(dtfi.IsReadOnly);

dtfi = System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat;
Console.WriteLine(dtfi.IsReadOnly);
// The example displays the following output:
//       True
//       False
//       True

实例化特定区域性的 DateTimeFormatInfo 对象

特定区域性代表特定国家/地区使用的语言。 例如,en-US 是代表在美国使用的英语的特定区域性,en-CA 是代表在加拿大使用的英语的特定区域性。 可以通过以下方式实例化代表特定区域性格式化约定的 DateTimeFormatInfo 对象:

以下示例描述了实例化 DateTimeFormatInfo 对象的每种方法,并指示生成的对象是否为只读。

System.Globalization.CultureInfo ci = null;
System.Globalization.DateTimeFormatInfo dtfi = null;

// Instantiate a culture using CreateSpecificCulture.
ci = System.Globalization.CultureInfo.CreateSpecificCulture("en-US");
dtfi = ci.DateTimeFormat;
Console.WriteLine("{0} from CreateSpecificCulture: {1}", ci.Name, dtfi.IsReadOnly);

// Instantiate a culture using the CultureInfo constructor.
ci = new System.Globalization.CultureInfo("en-CA");
dtfi = ci.DateTimeFormat;
Console.WriteLine("{0} from CultureInfo constructor: {1}", ci.Name, dtfi.IsReadOnly);

// Retrieve a culture by calling the GetCultureInfo method.
ci = System.Globalization.CultureInfo.GetCultureInfo("en-AU");
dtfi = ci.DateTimeFormat;
Console.WriteLine("{0} from GetCultureInfo: {1}", ci.Name, dtfi.IsReadOnly);

// Instantiate a DateTimeFormatInfo object by calling DateTimeFormatInfo.GetInstance.
ci = System.Globalization.CultureInfo.CreateSpecificCulture("en-GB");
dtfi = System.Globalization.DateTimeFormatInfo.GetInstance(ci);
Console.WriteLine("{0} from GetInstance: {1}", ci.Name, dtfi.IsReadOnly);

// The example displays the following output:
//      en-US from CreateSpecificCulture: False
//      en-CA from CultureInfo constructor: False
//      en-AU from GetCultureInfo: True
//      en-GB from GetInstance: False

实例化非特定区域性的 DateTimeFormatInfo 对象

非特定区域性代表独立于某个国家/地区的区域性或语言;它通常是一种或多种特定区域性的父级。 例如,Fr 是法语的非特定区域性,也是 fr-FR 区域性的父级。 可以实例化代表非特定区域性格式化约定的 DateTimeFormatInfo 对象,方式与创建代表特定区域性格式化约定的 DateTimeFormatInfo 对象相同。 此外,还可以通过从特定区域性的 CultureInfo.Parent 属性中检索非特定区域性,并检索其 CultureInfo.DateTimeFormat 属性返回的 DateTimeFormatInfo 对象来检索非特定区域性的 DateTimeFormatInfo 对象。 除非父区域性代表固定区域性,否则返回 DateTimeFormatInfo 的对象可读取/写入。 以下示例描述了实例化代表非特定区域性的 DateTimeFormatInfo 对象的这些方法。

System.Globalization.CultureInfo specific, neutral;
System.Globalization.DateTimeFormatInfo dtfi;

// Instantiate a culture by creating a specific culture and using its Parent property.
specific = System.Globalization.CultureInfo.GetCultureInfo("fr-FR");
neutral = specific.Parent;
dtfi = neutral.DateTimeFormat;
Console.WriteLine("{0} from Parent property: {1}", neutral.Name, dtfi.IsReadOnly);

dtfi = System.Globalization.CultureInfo.GetCultureInfo("fr-FR").Parent.DateTimeFormat;
Console.WriteLine("{0} from Parent property: {1}", neutral.Name, dtfi.IsReadOnly);

// Instantiate a neutral culture using the CultureInfo constructor.
neutral = new System.Globalization.CultureInfo("fr");
dtfi = neutral.DateTimeFormat;
Console.WriteLine("{0} from CultureInfo constructor: {1}", neutral.Name, dtfi.IsReadOnly);

// Instantiate a culture using CreateSpecificCulture.
neutral = System.Globalization.CultureInfo.CreateSpecificCulture("fr");
dtfi = neutral.DateTimeFormat;
Console.WriteLine("{0} from CreateSpecificCulture: {1}", neutral.Name, dtfi.IsReadOnly);

// Retrieve a culture by calling the GetCultureInfo method.
neutral = System.Globalization.CultureInfo.GetCultureInfo("fr");
dtfi = neutral.DateTimeFormat;
Console.WriteLine("{0} from GetCultureInfo: {1}", neutral.Name, dtfi.IsReadOnly);

// Instantiate a DateTimeFormatInfo object by calling GetInstance.
neutral = System.Globalization.CultureInfo.CreateSpecificCulture("fr");
dtfi = System.Globalization.DateTimeFormatInfo.GetInstance(neutral);
Console.WriteLine("{0} from GetInstance: {1}", neutral.Name, dtfi.IsReadOnly);

// The example displays the following output:
//       fr from Parent property: False
//       fr from Parent property: False
//       fr from CultureInfo constructor: False
//       fr-FR from CreateSpecificCulture: False
//       fr from GetCultureInfo: True
//       fr-FR from GetInstance: False

但是,非特定区域性缺少特定于区域性的格式化信息,因为它独立于特定国家/地区。 .NET 不使用泛型值填充 DateTimeFormatInfo 对象,而是返回一个 DateTimeFormatInfo 对象,该对象反映作为非特定区域性子项的特定区域性的格式化约定。 例如,非特定 en 区域性的 DateTimeFormatInfo 对象反映 en-US 区域性的格式化约定,fr 区域性的 DateTimeFormatInfo 对象反映 fr-FR 区域性的格式化约定。

可以使用如下代码来确定非特定区域性所代表的特定区域性的格式化约定。 该示例使用反映将非特定区域性的 DateTimeFormatInfo 属性与特定子区域性的属性进行比较。 如果两个日历的日历类型相同,并且对于公历,如果它们的 GregorianCalendar.CalendarType 属性具有相同的值,则认为两个日历等效。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

public class InstantiateEx6
{
    public static void Main()
    {
        // Get all the neutral cultures
        List<String> names = new List<String>();
        Array.ForEach(CultureInfo.GetCultures(CultureTypes.NeutralCultures),
                      culture => names.Add(culture.Name));
        names.Sort();
        foreach (var name in names)
        {
            // Ignore the invariant culture.
            if (name == "") continue;

            ListSimilarChildCultures(name);
        }
    }

    private static void ListSimilarChildCultures(String name)
    {
        // Create the neutral DateTimeFormatInfo object.
        DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(name).DateTimeFormat;
        // Retrieve all specific cultures of the neutral culture.
        CultureInfo[] cultures = Array.FindAll(CultureInfo.GetCultures(CultureTypes.SpecificCultures),
                                 culture => culture.Name.StartsWith(name + "-", StringComparison.OrdinalIgnoreCase));
        // Create an array of DateTimeFormatInfo properties
        PropertyInfo[] properties = typeof(DateTimeFormatInfo).GetProperties(BindingFlags.Instance | BindingFlags.Public);
        bool hasOneMatch = false;

        foreach (var ci in cultures)
        {
            bool match = true;
            // Get the DateTimeFormatInfo for a specific culture.
            DateTimeFormatInfo specificDtfi = ci.DateTimeFormat;
            // Compare the property values of the two.
            foreach (var prop in properties)
            {
                // We're not interested in the value of IsReadOnly.
                if (prop.Name == "IsReadOnly") continue;

                // For arrays, iterate the individual elements to see if they are the same.
                if (prop.PropertyType.IsArray)
                {
                    IList nList = (IList)prop.GetValue(dtfi, null);
                    IList sList = (IList)prop.GetValue(specificDtfi, null);
                    if (nList.Count != sList.Count)
                    {
                        match = false;
                        Console.WriteLine("   Different n in {2} array for {0} and {1}", name, ci.Name, prop.Name);
                        break;
                    }

                    for (int ctr = 0; ctr < nList.Count; ctr++)
                    {
                        if (!nList[ctr].Equals(sList[ctr]))
                        {
                            match = false;
                            Console.WriteLine("   {0} value different for {1} and {2}", prop.Name, name, ci.Name);
                            break;
                        }
                    }

                    if (!match) break;
                }
                // Get non-array values.
                else
                {
                    Object specificValue = prop.GetValue(specificDtfi);
                    Object neutralValue = prop.GetValue(dtfi);

                    // Handle comparison of Calendar objects.
                    if (prop.Name == "Calendar")
                    {
                        // The cultures have a different calendar type.
                        if (specificValue.ToString() != neutralValue.ToString())
                        {
                            Console.WriteLine("   Different calendar types for {0} and {1}", name, ci.Name);
                            match = false;
                            break;
                        }

                        if (specificValue is GregorianCalendar)
                        {
                            if (((GregorianCalendar)specificValue).CalendarType != ((GregorianCalendar)neutralValue).CalendarType)
                            {
                                Console.WriteLine("   Different Gregorian calendar types for {0} and {1}", name, ci.Name);
                                match = false;
                                break;
                            }
                        }
                    }
                    else if (!specificValue.Equals(neutralValue))
                    {
                        match = false;
                        Console.WriteLine("   Different {0} values for {1} and {2}", prop.Name, name, ci.Name);
                        break;
                    }
                }
            }
            if (match)
            {
                Console.WriteLine("DateTimeFormatInfo object for '{0}' matches '{1}'",
                                  name, ci.Name);
                hasOneMatch = true;
            }
        }
        if (!hasOneMatch)
            Console.WriteLine("DateTimeFormatInfo object for '{0}' --> No Match", name);

        Console.WriteLine();
    }
}

实例化当前区域性的 DateTimeFormatInfo 对象

可以通过以下方式实例化代表当前区域性格式化约定的 DateTimeFormatInfo 对象:

以下示例将使用上述每种方法来实例化表示当前区域性格式化约定的 DateTimeFormatInfo 对象。 然后,它将指示对象是否是只读的。

DateTimeFormatInfo dtfi;

dtfi = DateTimeFormatInfo.CurrentInfo;
Console.WriteLine(dtfi.IsReadOnly);

dtfi = CultureInfo.CurrentCulture.DateTimeFormat;
Console.WriteLine(dtfi.IsReadOnly);

dtfi = DateTimeFormatInfo.GetInstance(CultureInfo.CurrentCulture);
Console.WriteLine(dtfi.IsReadOnly);
// The example displays the following output:
//     True
//     True
//     True

可以通过以下方式之一来创建代表当前区域性约定的可写入 DateTimeFormatInfo 对象:

以下示例描述了实例化读/写 DateTimeFormatInfo 对象的每种方法,并显示其 IsReadOnly 属性的值。

using System;
using System.Globalization;

public class InstantiateEx1
{
    public static void Main()
    {
        DateTimeFormatInfo current1 = DateTimeFormatInfo.CurrentInfo;
        current1 = (DateTimeFormatInfo)current1.Clone();
        Console.WriteLine(current1.IsReadOnly);

        CultureInfo culture2 = CultureInfo.CreateSpecificCulture(CultureInfo.CurrentCulture.Name);
        DateTimeFormatInfo current2 = culture2.DateTimeFormat;
        Console.WriteLine(current2.IsReadOnly);
    }
}
// The example displays the following output:
//       False
//       False

在 Windows 中,用户可以通过控制面板中的“区域和语言”应用程序覆盖格式化和解析操作中使用的某些 DateTimeFormatInfo 属性值。 例如,区域性为英语(美国)的用户可能会选择使用 24 小时制(格式为 HH:mm:ss)而不是默认的 12 小时制(格式为 h:mm:ss tt)来显示长时间值。 以前文讨论的方式检索的 DateTimeFormatInfo 对象都反映了这些用户替代。 如果你不希望出现这种情况,则可以通过调用 CultureInfo.CultureInfo(String, Boolean) 构造函数并为 useUserOverride 自变量提供 false 值来创建不反映用户替代的 NumberFormatInfo 对象(也是读/写,而非只读)。 以下示例描述了当前区域性为英语(美国)且长时间模式已从默认的 h:mm:ss tt 更改为 HH:mm:ss 的系统。

using System;
using System.Globalization;

public class InstantiateEx3
{
    public static void Main()
    {
        CultureInfo culture;
        DateTimeFormatInfo dtfi;

        culture = CultureInfo.CurrentCulture;
        dtfi = culture.DateTimeFormat;
        Console.WriteLine("Culture Name:      {0}", culture.Name);
        Console.WriteLine("User Overrides:    {0}", culture.UseUserOverride);
        Console.WriteLine("Long Time Pattern: {0}\n", culture.DateTimeFormat.LongTimePattern);

        culture = new CultureInfo(CultureInfo.CurrentCulture.Name, false);
        Console.WriteLine("Culture Name:      {0}", culture.Name);
        Console.WriteLine("User Overrides:    {0}", culture.UseUserOverride);
        Console.WriteLine("Long Time Pattern: {0}\n", culture.DateTimeFormat.LongTimePattern);
    }
}
// The example displays the following output:
//       Culture Name:      en-US
//       User Overrides:    True
//       Long Time Pattern: HH:mm:ss
//
//       Culture Name:      en-US
//       User Overrides:    False
//       Long Time Pattern: h:mm:ss tt

DateTimeFormatInfo 和动态数据

DateTimeFormatInfo 类提供的用于格式化日期和时间值的区域性特定数据是动态的,与 CultureInfo 类提供的区域性数据类似。 不应对与特定 CultureInfo 对象关联的 DateTimeFormatInfo 对象值的稳定性做出任何假设。 只有固定区域性及其关联的 DateTimeFormatInfo 对象提供的数据是稳定的。 其他数据可能会在应用程序会话之间,甚至在应用程序运行时更改。 有四个主要的更改来源:

  • 系统更新。 区域性首选项(例如首选日历或自定义日期和时间格式)会随着时间的推移而更改。 发生这种情况时,Windows Update 会包含对特定区域性的 DateTimeFormatInfo 属性值的更改。

  • 替换区域性。 CultureAndRegionInfoBuilder 类可用于替换现有区域性的数据。

  • 对属性值的级联更改。 许多与区域性相关的属性可能会在运行时更改,从而导致 DateTimeFormatInfo 数据发生更改。 例如,可以通过编程方式或通过用户操作来更改当前区域性。 发生这种情况时,CurrentInfo 属性返回的 DateTimeFormatInfo 对象将更改为与当前区域性关联的对象。 同样,区域性的日历可能会更改,这可能会导致许多 DateTimeFormatInfo 属性值发生更改。

  • 用户首选项。 应用程序的用户可能会选择通过控制面板中的区域和语言选项替代与当前系统区域性相关的一些值。 例如,用户可能会选择以不同的格式显示日期。 如果 CultureInfo.UseUserOverride 属性设置为 true,则还会从用户设置中检索 DateTimeFormatInfo 对象的属性。 如果用户设置与 CultureInfo 对象的关联区域性不兼容(例如,如果所选日历不是 OptionalCalendars 属性指示的日历之一),则方法的结果和属性的值未定义。

为了最大限度地降低数据不一致的可能性,DateTimeFormatInfo 对象的所有用户可替代属性都会在创建对象时进行初始化。 仍然存在不一致的可能性,因为对象创建和用户替代过程都不是原子的,并且相关值可以在对象创建期间更改。 但是,这种情况应该极其罕见。

可以控制用户替代是否反映在代表与系统区域性相同区域性的 DateTimeFormatInfo 对象中。 下表列出了检索 DateTimeFormatInfo 对象的方式,并指示生成的对象是否反映用户替代。

CultureInfo 和 DateTimeFormatInfo 对象的源 反映用户替代
CultureInfo.CurrentCulture.DateTimeFormat 属性
DateTimeFormatInfo.CurrentInfo 属性
CultureInfo.CreateSpecificCulture 方法
CultureInfo.GetCultureInfo 方法
CultureInfo.CultureInfo(String) 构造函数
CultureInfo.CultureInfo(String, Boolean) 构造函数 取决于 useUserOverride 参数的值

除非有令人信服的理由采取其他做法,否则在客户端应用程序中使用 DateTimeFormatInfo 对象来格式化和解析用户输入或显示数据时,应该遵循用户替代。 对于服务器应用程序或无人值守的应用程序,不应采取其他做法。 但是,如果你显式或隐式使用 DateTimeFormatInfo 对象以便以字符串形式保留日期和时间数据,则应使用反映固定区域性格式化约定的 DateTimeFormatInfo 对象,或者无论区域性如何,都应指定自定义所使用的日期和时间格式的字符串。

格式化日期和时间

DateTimeFormatInfo 对象在所有日期和时间格式化操作中隐式或显式使用。 其中包括对以下方法的调用:

所有日期和时间格式化操作均使用 IFormatProvider 实施。 IFormatProvider 接口包括单个方法,即 IFormatProvider.GetFormat(Type)。 此回调方法会传递一个 Type 对象,该对象表示提供格式信息所需的类型。 该方法将返回该类型的实例,如果无法提供该类型的实例,则返回 null。 .NET 包含两个用于日期和时间格式化的 IFormatProvider 实施:

如果未向格式化方法显式提供 IFormatProvider 实施,则使用由 CultureInfo.CurrentCulture 属性返回的表示当前区域性的 CultureInfo 对象。

以下示例描述了格式化操作中 IFormatProvider 接口和 DateTimeFormatInfo 类之间的关系。 它定义了一个自定义 IFormatProvider 实施,其 GetFormat 方法显示格式化操作所请求的对象的类型。 如果请求 DateTimeFormatInfo 对象,该方法会提供当前区域性的 DateTimeFormatInfo 对象。 如示例的输出所示,Decimal.ToString(IFormatProvider) 方法请求 DateTimeFormatInfo 对象以提供格式化信息,而 String.Format(IFormatProvider, String, Object[]) 方法则请求 NumberFormatInfoDateTimeFormatInfo 对象以及 ICustomFormatter 实施。

using System;
using System.Globalization;

public class CurrentCultureFormatProvider : IFormatProvider
{
    public Object GetFormat(Type formatType)
    {
        Console.WriteLine("Requesting an object of type {0}",
                          formatType.Name);
        if (formatType == typeof(NumberFormatInfo))
            return NumberFormatInfo.CurrentInfo;
        else if (formatType == typeof(DateTimeFormatInfo))
            return DateTimeFormatInfo.CurrentInfo;
        else
            return null;
    }
}

public class FormatProviderEx1
{
    public static void Main()
    {
        DateTime dateValue = new DateTime(2013, 5, 28, 13, 30, 0);
        string value = dateValue.ToString("F", new CurrentCultureFormatProvider());
        Console.WriteLine(value);
        Console.WriteLine();
        string composite = String.Format(new CurrentCultureFormatProvider(),
                                         "Date: {0:d}   Amount: {1:C}   Description: {2}",
                                         dateValue, 1264.03m, "Service Charge");
        Console.WriteLine(composite);
        Console.WriteLine();
    }
}
// The example displays output like the following:
//       Requesting an object of type DateTimeFormatInfo
//       Tuesday, May 28, 2013 1:30:00 PM
//
//       Requesting an object of type ICustomFormatter
//       Requesting an object of type DateTimeFormatInfo
//       Requesting an object of type NumberFormatInfo
//       Date: 5/28/2013   Amount: $1,264.03   Description: Service Charge

格式化字符串和 DateTimeFormatInfo 属性

DateTimeFormatInfo 对象包括三种用于日期和时间值格式化操作的属性:

标准日期和时间格式字符串(例如“d”、“D”、“f”和“F”)是与特定 DateTimeFormatInfo 格式模式属性相对应的别名。 大多数自定义日期和时间格式字符串与格式化操作插入到结果流中的字符串或子字符串相关。 下表列出了标准和自定义日期和时间格式说明符及其关联的 DateTimeFormatInfo 属性。 有关如何使用这些格式说明符的详细信息,请参阅标准日期和时间格式字符串自定义日期和时间格式字符串。 请注意,每个标准格式字符串都对应一个 DateTimeFormatInfo 属性,其值为自定义日期和时间格式字符串。 此自定义格式字符串中的各个说明符依次对应于其他 DateTimeFormatInfo 属性。 该表仅列出了标准格式字符串为其别名的 DateTimeFormatInfo 属性,并未列出可通过分配给这些别名属性的自定义格式字符串访问的属性。 此外,该表仅列出与 DateTimeFormatInfo 属性对应的自定义格式说明符。

格式说明符 关联的属性
“d”(短日期;标准格式字符串) ShortDatePattern,用于定义结果字符串的总体格式。
“D”(长日期;标准格式字符串) LongDatePattern,用于定义结果字符串的总体格式。
“f”(完整日期/短时间;标准格式字符串) LongDatePattern,用于定义结果字符串中日期部分的格式。

ShortTimePattern,用于定义结果字符串中时间部分的格式。
“F”(完整日期/长时间;标准格式字符串) LongDatePattern,用于定义结果字符串中日期部分的格式。

LongTimePattern,用于定义结果字符串中时间部分的格式。
“g”(常规日期/短时间;标准格式字符串) ShortDatePattern,用于定义结果字符串中日期部分的格式。

ShortTimePattern,用于定义结果字符串中时间部分的格式。
“G”(常规日期/长时间;标准格式字符串) ShortDatePattern,用于定义结果字符串中日期部分的格式。

LongTimePattern,用于定义结果字符串中时间部分的格式。
“M”、“m”(月/日;标准格式字符串) MonthDayPattern,用于定义结果字符串的总体格式。
“O”、“o”(往返日期/时间;标准格式字符串) 无。
“R”、“r”(RFC1123;标准格式字符串) RFC1123Pattern,用于定义符合 RFC 1123 标准的结果字符串。 属性为只读属性。
“s”(可排序日期/时间;标准格式字符串) SortableDateTimePattern,用于定义符合 ISO 8601 标准的结果字符串。 属性为只读属性。
“t”(短时间;标准格式字符串) ShortTimePattern,用于定义结果字符串的总体格式。
“T”(长时间;标准格式字符串) LongTimePattern,用于定义结果字符串的总体格式。
“u”(通用可排序日期/时间;标准格式字符串) UniversalSortableDateTimePattern,用于定义符合适用于协调世界时的 ISO 8601 标准的结果字符串。 属性为只读属性。
“U”(通用完整日期/时间;标准格式字符串) FullDateTimePattern,用于定义结果字符串的总体格式。
“Y”、“y”(年月;标准格式字符串) YearMonthPattern,用于定义结果字符串的总体格式。
“ddd”(自定义格式说明符) AbbreviatedDayNames,用于在结果字符串中包含星期几的缩写名称。
“g”、“gg”(自定义格式说明符) 调用 GetEraName 方法以在结果字符串中插入纪元名称。
“MMM”(自定义格式说明符) AbbreviatedMonthNames,用于在结果字符串中包含缩写的月份名称。
“MMMM”(自定义格式说明符) MonthNamesMonthGenitiveNames,用于在结果字符串中包含完整的月份名称。
“t”(自定义格式说明符) AMDesignatorPMDesignator,用于在结果字符串中包含 AM/PM 指示符的第一个字符。
“tt”(自定义格式说明符) AMDesignatorPMDesignator,用于在结果字符串中包含完整的 AM/PM 指示符。
“:”(自定义格式说明符) TimeSeparator,用于在结果字符串中包含时间分隔符。
“/”(自定义格式说明符) DateSeparator,用于在结果字符串中包含日期分隔符。

修改 DateTimeFormatInfo 属性

可以通过修改可写 DateTimeFormatInfo 对象的关联属性来更改日期和时间格式字符串生成的结果字符串。 若要确定对象 DateTimeFormatInfo 是否可写,请使用 IsReadOnly 属性。 以如下方式自定义 DateTimeFormatInfo 对象:

  1. 创建要修改其格式化约定的 DateTimeFormatInfo 对象的读/写副本。

  2. 修改用于生成所需结果字符串的一个或多个属性。 (有关格式化方法如何使用 DateTimeFormatInfo 属性定义结果字符串的信息,请参阅上一节:格式化字符串和 DateTimeFormatInfo 属性的。)

  3. 在调用格式化方法时使用创建的自定义 DateTimeFormatInfo 对象作为 IFormatProvider 自变量。

还有另外两种方法可以更改结果字符串的格式:

  • 可以使用 CultureAndRegionInfoBuilder 类来定义自定义区域性(具有唯一名称,用于补充现有区域性的区域性)或替换区域性(用于代替特定区域性的区域性)。 可以通过编程方式保存和访问该区域性,就像处理 .NET 支持的任何 CultureInfo 对象一样。

  • 如果结果字符串不区分区域性且不遵循预定义的格式,则可以使用自定义日期和时间格式字符串。 例如,如果你要序列化格式为 YYYYMMDDHHmmss 的日期和时间数据,则可以通过将自定义格式字符串传递给 DateTime.ToString(String) 方法来生成结果字符串,并且可以通过调用 DateTime.ParseExact 方法将结果字符串转换回 DateTime 值。

更改短日期模式

以下示例更改由“d”(短日期)标准格式字符串生成的结果字符串的格式。 它将 en-US 或英语(美国)区域性的关联 ShortDatePattern 属性从默认值“M/d/yyyy”更改为“yyyy”-“MM”-“dd”,并使用“d”标准格式字符串以显示 ShortDatePattern 属性更改之前和之后的日期。

using System;
using System.Globalization;

public class Example1
{
    public static void Main()
    {
        DateTime dateValue = new DateTime(2013, 8, 18);
        CultureInfo enUS = CultureInfo.CreateSpecificCulture("en-US");
        DateTimeFormatInfo dtfi = enUS.DateTimeFormat;

        Console.WriteLine("Before modifying DateTimeFormatInfo object: ");
        Console.WriteLine("{0}: {1}\n", dtfi.ShortDatePattern,
                                      dateValue.ToString("d", enUS));

        // Modify the short date pattern.
        dtfi.ShortDatePattern = "yyyy-MM-dd";
        Console.WriteLine("After modifying DateTimeFormatInfo object: ");
        Console.WriteLine("{0}: {1}", dtfi.ShortDatePattern,
                                      dateValue.ToString("d", enUS));
    }
}
// The example displays the following output:
//       Before modifying DateTimeFormatInfo object:
//       M/d/yyyy: 8/18/2013
//
//       After modifying DateTimeFormatInfo object:
//       yyyy-MM-dd: 2013-08-18

更改日期分隔符

以下示例更改表示 fr-FR 区域性的格式化约定的 DateTimeFormatInfo 对象中的日期分隔符。 该示例使用“g”标准格式字符串显示属性更改前后的 DateSeparator 日期。

using System;
using System.Globalization;

public class Example3
{
    public static void Main()
    {
        DateTime dateValue = new DateTime(2013, 08, 28);
        CultureInfo frFR = CultureInfo.CreateSpecificCulture("fr-FR");
        DateTimeFormatInfo dtfi = frFR.DateTimeFormat;

        Console.WriteLine("Before modifying DateSeparator property: {0}",
                          dateValue.ToString("g", frFR));

        // Modify the date separator.
        dtfi.DateSeparator = "-";
        Console.WriteLine("After modifying the DateSeparator property: {0}",
                          dateValue.ToString("g", frFR));
    }
}
// The example displays the following output:
//       Before modifying DateSeparator property: 28/08/2013 00:00
//       After modifying the DateSeparator property: 28-08-2013 00:00

更改日期名称缩写和长日期模式

在某些情况下,长日期模式(通常显示完整的日期和月份名称以及月份和年份的天数)可能太长。 以下示例缩短了 en-US 区域性的长日期模式,以返回一个字符或两个字符的日期名称缩写,后跟日期编号、月份名称缩写和年份。 它通过向 AbbreviatedDayNames 数组分配较短的日期名称缩写,以及修改分配给 LongDatePattern 属性的自定义格式字符串来执行此操作。 这会影响“D”和“f”标准格式字符串返回的结果字符串。

using System;
using System.Globalization;

public class Example2
{
    public static void Main()
    {
        DateTime value = new DateTime(2013, 7, 9);
        CultureInfo enUS = CultureInfo.CreateSpecificCulture("en-US");
        DateTimeFormatInfo dtfi = enUS.DateTimeFormat;
        String[] formats = { "D", "F", "f" };

        // Display date before modifying properties.
        foreach (var fmt in formats)
            Console.WriteLine("{0}: {1}", fmt, value.ToString(fmt, dtfi));

        Console.WriteLine();

        // We don't want to change the FullDateTimePattern, so we need to save it.
        String originalFullDateTimePattern = dtfi.FullDateTimePattern;

        // Modify day name abbreviations and long date pattern.
        dtfi.AbbreviatedDayNames = new String[] { "Su", "M", "Tu", "W", "Th", "F", "Sa" };
        dtfi.LongDatePattern = "ddd dd-MMM-yyyy";
        dtfi.FullDateTimePattern = originalFullDateTimePattern;
        foreach (var fmt in formats)
            Console.WriteLine("{0}: {1}", fmt, value.ToString(fmt, dtfi));
    }
}
// The example displays the following output:
//       D: Tuesday, July 9, 2013
//       F: Tuesday, July 9, 2013 12:00:00 AM
//       f: Tuesday, July 9, 2013 12:00 AM
//
//       D: Tu 09-Jul-2013
//       F: Tuesday, July 9, 2013 12:00:00 AM
//       f: Tu 09-Jul-2013 12:00 AM

通常,对 LongDatePattern 属性的更改也会影响 FullDateTimePattern 属性,后者又定义“F”标准格式字符串返回的结果字符串。 为了保留原始的完整日期和时间模式,该示例会在修改 LongDatePattern 属性后重新分配已分配给 FullDateTimePattern 属性的原始自定义格式字符串。

从 12 小时制更改为 24 小时制

对于 .NET 中的许多区域性,时间使用 12 小时制以及 AM/PM 指示符来表示。 以下示例定义了一个 ReplaceWith24HourClock 方法,该方法将任何使用 12 小时制的时间格式替换为使用 24 小时制的格式。

using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class Example5
{
    public static void Main()
    {
        CultureInfo enUS = CultureInfo.CreateSpecificCulture("en-US");
        DateTimeFormatInfo dtfi = enUS.DateTimeFormat;

        Console.WriteLine("Original Property Values:");
        Console.WriteLine("ShortTimePattern: " + dtfi.ShortTimePattern);
        Console.WriteLine("LongTimePattern: " + dtfi.LongTimePattern);
        Console.WriteLine("FullDateTimePattern: " + dtfi.FullDateTimePattern);
        Console.WriteLine();

        dtfi.LongTimePattern = ReplaceWith24HourClock(dtfi.LongTimePattern);
        dtfi.ShortTimePattern = ReplaceWith24HourClock(dtfi.ShortTimePattern);

        Console.WriteLine("Modififed Property Values:");
        Console.WriteLine("ShortTimePattern: " + dtfi.ShortTimePattern);
        Console.WriteLine("LongTimePattern: " + dtfi.LongTimePattern);
        Console.WriteLine("FullDateTimePattern: " + dtfi.FullDateTimePattern);
    }

    private static string ReplaceWith24HourClock(string fmt)
    {
        string pattern = @"^(?<openAMPM>\s*t+\s*)? " +
                         @"(?(openAMPM) h+(?<nonHours>[^ht]+)$ " +
                         @"| \s*h+(?<nonHours>[^ht]+)\s*t+)";
        return Regex.Replace(fmt, pattern, "HH${nonHours}",
                             RegexOptions.IgnorePatternWhitespace);
    }
}
// The example displays the following output:
//       Original Property Values:
//       ShortTimePattern: h:mm tt
//       LongTimePattern: h:mm:ss tt
//       FullDateTimePattern: dddd, MMMM dd, yyyy h:mm:ss tt
//
//       Modififed Property Values:
//       ShortTimePattern: HH:mm
//       LongTimePattern: HH:mm:ss
//       FullDateTimePattern: dddd, MMMM dd, yyyy HH:mm:ss

该示例使用正则表达式修改格式字符串。 正则表达式模式 @"^(?<openAMPM>\s*t+\s*)? (?(openAMPM) h+(?<nonHours>[^ht]+)$ | \s*h+(?<nonHours>[^ht]+)\s*t+) 按以下方式定义:

模式 描述
^ 从字符串开头开始匹配。
(?<openAMPM>\s*t+\s*)? 匹配零个或多个空白字符出现零次或一次,然后匹配字母“t”出现一次或多次,再匹配零个或多个空白字符。 此捕获组命名为 openAMPM
(?(openAMPM) h+(?<nonHours>[^ht]+)$ 如果 openAMPM 组有匹配项,则匹配字母“h”出现一次或多次,然后匹配一个或多个既不是“h”也不是“t”的字符。 在字符串的结尾结束匹配。 “h”之后捕获的所有字符都包含在名为 nonHours 的捕获组中。
&#124; \s*h+(?<nonHours>[^ht]+)\s*t+) 如果 openAMPM 组没有匹配项,则匹配字母“h”出现一次或多次,然后匹配一个或多个既不是“h”也不是“t”的字符,再匹配零个或多个空白字符。 最后,匹配字母“t”出现一次或多次。 在“h”之后、空格和“t”之前捕获的所有字符都包含在名为 nonHours 的捕获组中。

nonHours 捕获组包含分钟,可能还包含自定义日期和时间格式字符串的第二个组成部分,以及任何时间分隔符。 替换模式 HH${nonHours} 会在这些元素前面添加子字符串“HH”。

显示和更改日期中的纪元

以下示例将“g”自定义格式说明符添加到表示 en-US 区域性格式约定的对象的 LongDatePattern 属性。 此添加会影响以下三个标准格式字符串:

  • “D”(长日期)标准格式字符串,直接映射到 LongDatePattern 属性。

  • “f”(完整日期/短时间)标准格式字符串,它将生成一个结果字符串,该结果字符串连接由 LongDatePatternShortTimePattern 属性生成的子字符串。

  • “F”(完整日期/长时间)标准格式字符串,该字符串直接映射到 FullDateTimePattern 属性。 由于我们没有显式设置此属性值,因此它是通过连接 LongDatePatternLongTimePattern 属性动态生成的。

该示例还介绍了如何更改日历具有单个纪元的区域性的纪元名称。 在本例中,en-US 区域性使用公历,由 GregorianCalendar 对象表示。 GregorianCalendar 类支持单个纪元,将其命名为 A.D.(Anno Domini)。 该示例将纪元名称更改为 C.E.(Common Era),方法是将分配给 FullDateTimePattern 属性的格式字符串中的“g”自定义格式说明符替换为文字字符串。 使用文本字符串是必要的,因为纪元名称通常由 GetEraName 方法从 .NET 或操作系统提供的区域性表中的私有数据返回。

using System;
using System.Globalization;

public class Example4
{
    public static void Main()
    {
        DateTime dateValue = new DateTime(2013, 5, 18, 13, 30, 0);
        String[] formats = { "D", "f", "F" };

        CultureInfo enUS = CultureInfo.CreateSpecificCulture("en-US");
        DateTimeFormatInfo dtfi = enUS.DateTimeFormat;
        String originalLongDatePattern = dtfi.LongDatePattern;

        // Display the default form of three long date formats.
        foreach (var fmt in formats)
            Console.WriteLine(dateValue.ToString(fmt, dtfi));

        Console.WriteLine();

        // Modify the long date pattern.
        dtfi.LongDatePattern = originalLongDatePattern + " g";
        foreach (var fmt in formats)
            Console.WriteLine(dateValue.ToString(fmt, dtfi));

        Console.WriteLine();

        // Change A.D. to C.E. (for Common Era)
        dtfi.LongDatePattern = originalLongDatePattern + @" 'C.E.'";
        foreach (var fmt in formats)
            Console.WriteLine(dateValue.ToString(fmt, dtfi));
    }
}
// The example displays the following output:
//       Saturday, May 18, 2013
//       Saturday, May 18, 2013 1:30 PM
//       Saturday, May 18, 2013 1:30:00 PM
//
//       Saturday, May 18, 2013 A.D.
//       Saturday, May 18, 2013 A.D. 1:30 PM
//       Saturday, May 18, 2013 A.D. 1:30:00 PM
//
//       Saturday, May 18, 2013 C.E.
//       Saturday, May 18, 2013 C.E. 1:30 PM
//       Saturday, May 18, 2013 C.E. 1:30:00 PM

分析日期和时间字符串

解析涉及将日期和时间的字符串表示形式转换为 DateTimeDateTimeOffset 值。 这两种类型都包含支持解析操作的 ParseTryParseParseExactTryParseExact 方法。 ParseTryParse 方法转换可以具有多种格式的字符串,ParseExactTryParseExact 则要求字符串具有定义的格式。 如果解析操作失败,ParseParseExact 会引发异常,TryParseTryParseExact 则会返回 false

解析方法隐式或显式使用 DateTimeStyles 枚举值来确定要解析的字符串中可以存在哪些样式元素(例如前导、尾随或内部空格),以及如何解释已解析的字符串或任何缺失元素。 如果在调用 ParseTryParse 方法时未提供 DateTimeStyles 值,则默认值为 DateTimeStyles.AllowWhiteSpaces,它是包含 DateTimeStyles.AllowLeadingWhiteDateTimeStyles.AllowTrailingWhiteDateTimeStyles.AllowInnerWhite 标志的复合样式。 对于 ParseExactTryParseExact 方法,默认值为 DateTimeStyles.None;输入字符串必须与特定的自定义日期和时间格式字符串精确对应。

解析方法还隐式或显式使用 DateTimeFormatInfo 对象,该对象定义要解析的字符串中可能出现的特定符号和模式。 如果未提供 DateTimeFormatInfo 对象,则默认使用当前区域性的 DateTimeFormatInfo 对象。 有关解析日期和时间字符串的详细信息,请参阅各个解析方法,例如 DateTime.ParseDateTime.TryParseDateTimeOffset.ParseExactDateTimeOffset.TryParseExact

以下示例描述了解析日期和时间字符串的区域性敏感性质。 它尝试使用 en-US、en-GB、fr-FR 和 fi-FI 区域性的约定来解析两个日期字符串。 在 en-US 区域性中解释为 8/18/2014 的日期在其他三种区域性中会引发 FormatException 异常,因为 18 被解释为月份数字。 1/2/2015 在 en-US 区域性中被解析为第一个月的第二天,但在其他区域性中被解析为第二个月的第一天。

using System;
using System.Globalization;

public class ParseEx1
{
    public static void Main()
    {
        string[] dateStrings = { "08/18/2014", "01/02/2015" };
        string[] cultureNames = { "en-US", "en-GB", "fr-FR", "fi-FI" };

        foreach (var cultureName in cultureNames)
        {
            CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureName);
            Console.WriteLine("Parsing strings using the {0} culture.",
                              culture.Name);
            foreach (var dateStr in dateStrings)
            {
                try
                {
                    Console.WriteLine(String.Format(culture,
                                      "   '{0}' --> {1:D}", dateStr,
                                      DateTime.Parse(dateStr, culture)));
                }
                catch (FormatException)
                {
                    Console.WriteLine("   Unable to parse '{0}'", dateStr);
                }
            }
        }
    }
}
// The example displays the following output:
//       Parsing strings using the en-US culture.
//          '08/18/2014' --> Monday, August 18, 2014
//          '01/02/2015' --> Friday, January 02, 2015
//       Parsing strings using the en-GB culture.
//          Unable to parse '08/18/2014'
//          '01/02/2015' --> 01 February 2015
//       Parsing strings using the fr-FR culture.
//          Unable to parse '08/18/2014'
//          '01/02/2015' --> dimanche 1 février 2015
//       Parsing strings using the fi-FI culture.
//          Unable to parse '08/18/2014'
//          '01/02/2015' --> 1. helmikuuta 2015

日期和时间字符串通常出于两个原因进行解析:

  • 将用户输入转换为日期和时间值。
  • 解析为往返日期和时间值;也就是说,用于反序列化之前序列化为字符串的日期和时间值。

以下各节将对这两项操作进行更详细的讨论。

解析用户字符串

解析用户输入的日期和时间字符串时,应始终实例化一个反映用户区域性设置(包括用户可能进行的任何自定义设置)的 DateTimeFormatInfo 对象。 否则,日期和时间对象可能会具有不正确的值。 有关如何实例化反映用户区域性自定义的 DateTimeFormatInfo 对象的信息,请参阅 DateTimeFormatInfo 和动态数据一节。

以下示例描述了反映用户区域性设置的解析操作与不反映用户区域性设置的解析操作之间的差异。 在本例中,默认系统区域性为 en-US,但用户已使用控制面板、区域和语言将短日期模式从默认的“M/d/yyyy”更改为“yy/MM/dd”。 当用户输入反映用户设置的字符串,并且该字符串由也反映用户设置(覆盖)的 DateTimeFormatInfo 对象进行解析时,解析操作会返回正确的结果。 但是,当字符串由反映标准 en-US 区域性设置的 DateTimeFormatInfo 对象解析时,解析方法会引发 FormatException 异常,因为它将 14 解释为月份数字,而不是年份的最后两位数字。

using System;
using System.Globalization;

public class ParseEx2
{
    public static void Main()
    {
        string inputDate = "14/05/10";

        CultureInfo[] cultures = { CultureInfo.GetCultureInfo("en-US"),
                                 CultureInfo.CreateSpecificCulture("en-US") };

        foreach (var culture in cultures)
        {
            try
            {
                Console.WriteLine("{0} culture reflects user overrides: {1}",
                                  culture.Name, culture.UseUserOverride);
                DateTime occasion = DateTime.Parse(inputDate, culture);
                Console.WriteLine("'{0}' --> {1}", inputDate,
                                  occasion.ToString("D", CultureInfo.InvariantCulture));
            }
            catch (FormatException)
            {
                Console.WriteLine("Unable to parse '{0}'", inputDate);
            }
            Console.WriteLine();
        }
    }
}
// The example displays the following output:
//       en-US culture reflects user overrides: False
//       Unable to parse '14/05/10'
//
//       en-US culture reflects user overrides: True
//       '14/05/10' --> Saturday, 10 May 2014

序列化和反序列化日期和时间数据

序列化的日期和时间数据预计是往返的;也就是说,所有序列化和反序列化的值应该相同。 如果日期和时间值表示单个时间点,则反序列化值应表示同一时间点,无论恢复该值的系统的区域性或时区如何。 要成功往返日期和时间数据,必须使用固定区域性(由 InvariantInfo 属性返回)的约定来生成和解析数据。 格式化和解析操作永远不应该反映默认区域性的约定。 如果使用默认区域性设置,数据的可移植性会受到严格限制;它只能在其区域性特定设置与序列化线程的区域性特定设置相同的线程上成功反序列化。 在某些情况下,这意味着数据甚至无法在同一系统上成功序列化和反序列化。

如果日期和时间值的时间部分很重要,则还应将其转换为 UTC 并使用“o”或“r”标准格式字符串进行序列化。 然后,可以通过调用解析方法并向其传递适当的格式字符串以及固定区域性作为 provider 自变量来恢复时间数据。

以下示例描述了往返日期和时间值的过程。 它在观察美国太平洋时间且当前区域性是 en-US 的系统上序列化日期和时间。

using System;
using System.Globalization;
using System.IO;

public class SerializeEx1
{
    public static void Main()
    {
        StreamWriter sw = new StreamWriter(@".\DateData.dat");
        // Define a date and time to serialize.
        DateTime originalDate = new DateTime(2014, 08, 18, 08, 16, 35);
        // Display information on the date and time.
        Console.WriteLine("Date to serialize: {0:F}", originalDate);
        Console.WriteLine("Current Culture:   {0}",
                          CultureInfo.CurrentCulture.Name);
        Console.WriteLine("Time Zone:         {0}",
                          TimeZoneInfo.Local.DisplayName);
        // Convert the date value to UTC.
        DateTime utcDate = originalDate.ToUniversalTime();
        // Serialize the UTC value.
        sw.Write(utcDate.ToString("o", DateTimeFormatInfo.InvariantInfo));
        sw.Close();
    }
}
// The example displays the following output:
//       Date to serialize: Monday, August 18, 2014 8:16:35 AM
//       Current Culture:   en-US
//       Time Zone:         (UTC-08:00) Pacific Time (US & Canada)

它反序列化位于布鲁塞尔、哥本哈根、马德里和巴黎时区且当前区域性为 fr-FR 的系统上的数据。 还原的日期比原始日期晚 9 小时,这反映了时区从比 UTC 晚 8 小时调整为比 UTC 早 1 小时。 原始日期和还原的日期都表示相同的时间。

using System;
using System.Globalization;
using System.IO;

public class SerializeEx2
{
    public static void Main()
    {
        // Open the file and retrieve the date string.
        StreamReader sr = new StreamReader(@".\DateData.dat");
        String dateValue = sr.ReadToEnd();

        // Parse the date.
        DateTime parsedDate = DateTime.ParseExact(dateValue, "o",
                              DateTimeFormatInfo.InvariantInfo);
        // Convert it to local time.
        DateTime restoredDate = parsedDate.ToLocalTime();
        // Display information on the date and time.
        Console.WriteLine("Deserialized date: {0:F}", restoredDate);
        Console.WriteLine("Current Culture:   {0}",
                          CultureInfo.CurrentCulture.Name);
        Console.WriteLine("Time Zone:         {0}",
                          TimeZoneInfo.Local.DisplayName);
    }
}
// The example displays the following output:
//    Deserialized date: lundi 18 août 2014 17:16:35
//    Current Culture:   fr-FR
//    Time Zone:         (UTC+01:00) Brussels, Copenhagen, Madrid, Paris