Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug in DateTime.ToString("T") and DateTime.ToString("G")?

Tags:

c#

.net

datetime

Microsoft specifies : as The time separator, but it seems there are at least two time separators: one between hours and minutes and another one between minutes and seconds.

Long time format settings in control panel
(source: wellisolutions.de)

Long time format shown by Windows clock
(source: wellisolutions.de)

Is there a way to get the specific time separators? I need the one between hours and minutes and the other one between minutes and seconds.

I would also not mind building my DateTime string in other ways, e.g. using the standard format string T or G, but they both don't work

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
like image 519
Thomas Weller Avatar asked Mar 09 '14 21:03

Thomas Weller


2 Answers

Just set the formats in .NET as you like. For example:

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

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

Then:

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

Note that I put the colon : and the slash / into single quotes (apostrophes ') to prevent them from being translated into whatever separator your culture has from the outset. I just want literal colon and slash.

If you don't want to write clonedProvider everywhere, change the culture permanently on your current thread:

Thread.CurrentThread.CurrentCulture = CultureInfo.ReadOnly(clonedProvider);

Do you have many threads in your application?


Edit after comment:

If you want to see how the OS settings has affected your .NET format provider object, just inspect the strings:

DateTimeFormatInfo.CurrentInfo.ShortDatePattern
DateTimeFormatInfo.CurrentInfo.LongTimePattern

and so on. I suppose your current format provider has UseUserOverride set to true, so the user settings from Windows will be visible.

There is no limit to the number of "separators" the user could have typed in. For example someone might use "ddd;dd-MM,yyyy". So there are three separators there. So you will have to examine the string yourself to see how many "separators" and "components" are there, and which characters the user uses as separator in each place.


Reading your question carefully, and relating to your example, I see that you typed HH-mm:ss in the Windows setting. That has got a problem with it. When translated to .NET syntax, the first separator - becomes the time separator. Then the next separator, the colon, in .NET is a "wildcard" meaning "substitute with time separator". So that colon is translated to a dash as well.

You should have typed, in Windows settings,

HH-mm':'ss

where again you protect the colon with single quotes (apostrophes).

Now what if one of your users uses a non-standard separator first, and then later uses the standard separator : (or /) without quoting the latter in single quotes? Well, in that case you are right, there is a difference between the behavior in Windows and that in .NET. Apparently users should not type formats like that. You could call this a bug.

like image 184
Jeppe Stig Nielsen Avatar answered Sep 20 '22 00:09

Jeppe Stig Nielsen


Getting separators

As stated by Jeppe Stig Nielson (maybe upvote for him), there is no good way to get the second time or date separator, because in a format string like

HH-mm/HH:mm-HH/mm

there can be multiple of them, even with the same semantics (e.g. between hours and minutes).

Microsoft bug report

I have registered at Microsoft Connect and filed the bug as DateTime.ToString("T") and DateTime.ToString("G"). If you have a Microsoft Connect account, you can vote whether or not you can reproduce the bug.

SSCCE to reproduce the bug

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();
        }
    }
}

Workaround

As a workaround, we can use the native methods GetTimeFormat and GetDateFormat.

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;
        }
    }
}
like image 30
Thomas Weller Avatar answered Sep 19 '22 00:09

Thomas Weller