DateTime.ToString("T")和DateTime.ToString("G")存在Bug吗?

20

Microsoft指定:时间分隔符,但似乎至少有两个时间分隔符:一个在小时和分钟之间,另一个在分钟和秒之间。

控制面板中的长时间格式设置
(来源: wellisolutions.de)

Windows时钟显示的长时间格式
(来源: wellisolutions.de)

有没有一种方法可以获取特定的时间分隔符?我需要小时和分钟之间的一个分隔符,以及分钟和秒钟之间的另一个分隔符。
我也不介意以其他方式构建我的DateTime字符串,例如使用标准格式字符串 TG,但它们都不起作用。
mydate.ToString("T");
// Output: 20-29-46
// Expected output: 20-29:46 (as shown by Windows clock)

mydate.ToString("G");
// Output: 09/03-2014 20-29-46
// Expected output: 09/03-2014 20-29:46

4
可以考虑在 connect.microsoft.com 提交报告,我认为标准格式字符串应该遵循操作系统设置。 - Greg D
1
考虑到内置的格式化机制似乎不能按预期工作,在最坏的情况下,您可以始终使用Interop自己调用底层操作系统API来执行正确的格式化。 - Greg D
13
我听到了一个微软程序员对着那个错误报告尖叫。 - Hans Passant
你尝试检查了CurrentCulture并查看DateTimeFormat吗? CultureInfo c2 = CultureInfo.CurrentCulture; Console.WriteLine(c2.DateTimeFormat.ToString()); 您将看到属性“LongTimePattern”。然后,也许您可以使用正则表达式检查并提取分隔符... - blfuentes
我也遇到了同样的问题。控制面板区域和语言设置可以让你设置任意多个分隔符和参数,但是 .net CultureInfo DateTimeFormat 只假设用户是理智的。因此,即使像 DateTime.Parse(DateTime.Now.ToString()) 这样的往返操作也行不通。 - user1249190
显示剩余2条评论
2个回答

4

只需在.NET中按您喜欢的格式设置即可。例如:

var clonedProvider = (CultureInfo)CultureInfo.CurrentCulture.Clone();

clonedProvider.DateTimeFormat.LongTimePattern = "HH-mm':'ss";
clonedProvider.DateTimeFormat.ShortDatePattern = "dd'/'MM-yyyy";

然后:

mydate.ToString("T", clonedProvider);
mydate.ToString("G", clonedProvider);

请注意,我将冒号:和斜杠/放在单引号(撇号')中,以防止它们被翻译成你所在文化环境中的任何分隔符。我只想要字面上的冒号和斜杠。
如果您不想在每个地方都写clonedProvider,可以永久更改当前线程上的文化环境。
Thread.CurrentThread.CurrentCulture = CultureInfo.ReadOnly(clonedProvider);

您的应用程序中有很多线程吗?


在评论后编辑:

如果您想查看操作系统设置如何影响您的.NET格式提供程序对象,只需检查字符串即可:

DateTimeFormatInfo.CurrentInfo.ShortDatePattern
DateTimeFormatInfo.CurrentInfo.LongTimePattern

等等。我假设你当前的格式提供程序已将UseUserOverride设置为true,因此Windows中的用户设置将可见。

用户输入的“分隔符”数量没有限制。例如,有人可能使用"ddd;dd-MM,yyyy"。所以那里有三个分隔符。因此,您需要自己检查字符串,看看有多少“分隔符”和“组件”,以及用户在每个位置使用哪些字符作为分隔符。


仔细阅读您的问题,并与您的示例相关联,我发现您在Windows设置中键入了HH-mm:ss。这有一个问题。当转换为.NET语法时,第一个分隔符-成为时间分隔符。然后,在.NET中,下一个分隔符(冒号)是一个“通配符”,意思是“用时间分隔符替换”。因此,该冒号也被转换为破折号。

您应该在Windows设置中键入:

HH-mm':'ss

再次提醒,您需要用单引号(撇号)保护冒号。

假设您的用户首先使用非标准分隔符,然后在不使用单引号引用后面的标准分隔符:(或/),那么在这种情况下,您是正确的,Windows和.NET行为之间存在差异。显然,用户不应该输入这样的格式。您可以称其为 Bug。


1
这并不是按照Windows时间设置中指定的方式获取特定的时间分隔符的方法。 - Rawling
我不想为每个客户编译我的代码,以反映他的操作系统设置。 - Thomas Weller
1
@Rawling 或许我误解了。请看我的编辑。不能保证分隔符的数量恰好为两个。例如,字符串中可能没有、一个或三个分隔符。因此,您应该自行检查字符串。一些病态的人可能使用 d.yyyy,仅表示月中的天和年份,而不包括月份。依赖用户设置时,请勿做出任何假设。 - Jeppe Stig Nielsen
@ThomasW,请查看我的最终编辑。在.NET中,冒号:字符是特殊的。您应该使用单引号'来防止这种特殊的解释。 - Jeppe Stig Nielsen
+1 关于多个分隔符的观点。我认为这甚至回答了我所写的唯一问题:没有办法获取“第二个分隔符”,因为可能会有多个分隔符。 - Thomas Weller
显示剩余2条评论

2

获取分隔符

正如Jeppe Stig Nielson所说(也许可以给他点赞),没有一个好方法可以得到第二个时间或日期的分隔符,因为在格式字符串中

HH-mm/HH:mm-HH/mm

它们可以有多个,甚至具有相同的语义(例如在小时和分钟之间)。

微软错误报告

我已经在Microsoft Connect上注册,并将错误报告为DateTime.ToString("T") and DateTime.ToString("G")。如果您有Microsoft Connect帐户,则可以投票是否可以重现该错误。

可重现该错误的SSCCE

using System;
using System.Globalization;

namespace DateTimeToString
{
    class Program
    {
        // Tested on
        // Microsoft Windows 7 Enterprise x64 Version 6.1.7601 Service Pack 1 Build 7601
        // I should have all official updates installed at the time of writing (2014-03-11)
        //
        // Microsoft Visual Studio Premium 2012 Version 11.0.61030.00 Update 4
        // Microsoft .NET Framework Version 4.5.50938
        //
        // Type: Console application x86
        // Target framework: .NET 4 Client Profile
        static void Main()
        {
            if (DateTimeFormatInfo.CurrentInfo.LongTimePattern != "HH-mm:ss" ||
                DateTimeFormatInfo.CurrentInfo.ShortDatePattern != "dd.MM/yyyy")
            {
                Console.WriteLine("Configure long time format to MM-mm:ss to reproduce the time bug.");
                Console.WriteLine("Configure short date format to dd.MM/yyyy to reproduce the date bug.");
                Console.WriteLine("Control Panel/Region and Language/Additional settings");
                return;
            }

            var dateTime = DateTime.Now;
            Console.WriteLine("Expected: " + dateTime.ToString("HH'-'mm':'ss"));
            Console.WriteLine("Actual  : " + dateTime.ToString("T"));
            Console.WriteLine();

            Console.WriteLine("Expected: " + dateTime.ToString("dd'.'MM'/'yyyy HH'-'mm':'ss"));
            Console.WriteLine("Actual  : " + dateTime.ToString("G"));
            Console.WriteLine();

            Console.WriteLine("Expected: " + dateTime.ToString("HH'-'mm':'ss"));
            Console.WriteLine("Actual  : " + dateTime.ToLongTimeString());
            Console.WriteLine();

            Console.WriteLine("Expected: " + dateTime.ToString("dd'.'MM'/'yyyy"));
            Console.WriteLine("Actual  : " + dateTime.ToShortDateString());
            Console.ReadLine();
        }
    }
}

解决方法

作为一种解决方法,我们可以使用本地方法GetTimeFormatGetDateFormat

static class Program
{
    static void Main()
    {
        var systemTime = new SystemTime(DateTime.Now);

        Console.WriteLine("ShortDatePattern (as reported by .NET): " + DateTimeFormatInfo.CurrentInfo.ShortDatePattern);
        var sbDate = new StringBuilder();
        GetDateFormat(0, 0, ref systemTime, null, sbDate, sbDate.Capacity);
        Console.WriteLine("Date string (as reported by kernel32) : " + sbDate);
        Console.WriteLine();

        Console.WriteLine("LongTimePattern (as reported by .NET) : " + DateTimeFormatInfo.CurrentInfo.LongTimePattern);
        var sbTime = new StringBuilder();
        GetTimeFormat(0, 0, ref systemTime, null, sbTime, sbTime.Capacity);
        Console.WriteLine("Time string (as reported by kernel32) : " + sbTime);

        Console.ReadKey();
    }

    [DllImport("kernel32.dll")]
    private static extern int GetDateFormat(int locale, uint dwFlags, ref SystemTime sysTime,
        string lpFormat, StringBuilder lpDateStr, int cchDate);

    [DllImport("kernel32.dll")]
    private static extern int GetTimeFormat(uint locale, uint dwFlags, ref SystemTime time, 
        string format, StringBuilder sb, int sbSize);


    [StructLayout(LayoutKind.Sequential)]
    private struct SystemTime
    {
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Year;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Month;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort DayOfWeek;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Day;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Hour;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Minute;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Second;
        [MarshalAs(UnmanagedType.U2)] private readonly ushort Milliseconds;

        public SystemTime(DateTime dateTime)
        {
            Year = (ushort) dateTime.Year;
            Month = (ushort) dateTime.Month;
            DayOfWeek = (ushort) dateTime.DayOfWeek;
            Day = (ushort) dateTime.Day;
            Hour = (ushort) dateTime.Hour;
            Minute = (ushort) dateTime.Minute;
            Second = (ushort) dateTime.Second;
            Milliseconds = (ushort) dateTime.Millisecond;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接