How to convert DateTime to string using Region Short Date
Asked Answered
S

2

4

I would like to output a DateTime as a string in the format specified in the operating system preferences. I don't want to provide an explicit format pattern, but instead want to use whatever the user has defined in the Region panel.

For example on my Windows 10 system, formats are defined under Control Panel > Region > Formats as follows:

  • Short date: "yyyy-MM-dd"
  • Short time: "HH:mm"

When files and folders are displayed in Windows Explorer, they appear in the format: "yyyy-MM-dd HH:mm".

I should point out that I'm using Unity 2019.4.4. -- As far as I know, it's not doing anything strange by overriding the CurrentCulture or anything like that. I've tagged this question with the "unity3d" tag, but it may not be specifically related to Unity, unless Unity is doing strange things with the culture.

I've tried the following .NET Fiddle, and saw the same results as in Unity:

using System;
using System.Globalization;

public class Program
{
    public static void Main()
    {
        long dateTimeTicks = 637314588165245627;
        DateTime dateTime = new DateTime(dateTimeTicks);

        // I thought the following methods might work, since the Region panel allow configuring the
        // short and long formats for date and time.

        Console.WriteLine(dateTime.ToShortDateString());  // 7/27/2020
        Console.WriteLine(dateTime.ToLongDateString());   // Monday, July 27, 2020
        Console.WriteLine(dateTime.ToShortTimeString());  // 3:00 PM
        Console.WriteLine(dateTime.ToLongTimeString());   // 3:00:16 PM

        // None of the standard date and time format strings work. The "o", "s", and "u" are close to
        // what I'm looking for, but I don't want to force this format, I want to use whatever the
        // user has specified in the Region preferences.

        Console.WriteLine(dateTime.ToString());                                // 7/27/2020 3:00:16 PM
        Console.WriteLine(dateTime.ToString(CultureInfo.CurrentCulture));      // 7/27/2020 3:00:16 PM
        Console.WriteLine(dateTime.ToString(CultureInfo.InvariantCulture));    // 07/27/2020 15:00:16
        Console.WriteLine(dateTime.ToString(CultureInfo.InstalledUICulture));  // 7/27/2020 3:00:16 PM

        Console.WriteLine(dateTime.ToString("d"));  // 7/27/2020
        Console.WriteLine(dateTime.ToString("D"));  // Monday, July 27, 2020
        Console.WriteLine(dateTime.ToString("f"));  // Monday, July 27, 2020 3:00 PM
        Console.WriteLine(dateTime.ToString("F"));  // Monday, July 27, 2020 3:00:16 PM
        Console.WriteLine(dateTime.ToString("g"));  // 7/27/2020 3:00 PM
        Console.WriteLine(dateTime.ToString("G"));  // 7/27/2020 3:00:16 PM
        Console.WriteLine(dateTime.ToString("m"));  // July 27
        Console.WriteLine(dateTime.ToString("o"));  // 2020-07-27T15:00:16.5245627
        Console.WriteLine(dateTime.ToString("r"));  // Mon, 27 Jul 2020 15:00:16 GMT
        Console.WriteLine(dateTime.ToString("s"));  // 2020-07-27T15:00:16
        Console.WriteLine(dateTime.ToString("t"));  // 3:00 PM
        Console.WriteLine(dateTime.ToString("T"));  // 3:00:16 PM
        Console.WriteLine(dateTime.ToString("u"));  // 2020-07-27 15:00:16Z
        Console.WriteLine(dateTime.ToString("U"));  // Monday, July 27, 2020 3:00:16 PM
        Console.WriteLine(dateTime.ToString("y"));  // July 2020

        // I considered using CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern and
        // CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern together, but they also
        // don't return what is specified in Region preferences.

        // 7/27/2020
        Console.WriteLine(dateTime.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern));
    }
}

I'm assuming I need to somehow access the Culture information that's defined in the OS Regions panel and use that to format the string, but that's where I'm lost.

The CurrentCulture seems to be using a ShortDate of "M/d/yyyy" and a ShortTime of "h:mm tt", which is not what I have set in my Region panel.

How can I output the DateTime as a string in the format specified by the user and stored in the Region panel on Windows without knowing what that pattern is ahead of time?

Ultimately, I would like the string to be in the same format used by Windows Explorer, which would be something like "ShortDate ShortTime".

Strom answered 27/7, 2020 at 23:55 Comment(5)
The followings are called from .NET app (non-Unity) running on my PC. On My Region panel right now it use the non-standard "ddd d-MMM-yy" for date and the standard "hh:mm:ss tt" for time, they're used in Windows Explorer and the clock widget on left-bottom corner of the screen, and both CurrentUICulture and CurrentCulture use them too, DateTime.Now.ToString(CultureInfo.CurrentCulture) returns "Tue 28-Jul-20 7:13:20 AM". Calling CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern and CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern also returns their correct values.Ala
Since Fiddle won't be aware of your PC regional setting, it seems the problem is in Unity side, their forum intermittently mentions problems with regional formatting that should be fixed in later version, if you can reliably reproduce CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern ignoring your custom format set on Region panel, fill a bugAla
Ah. Okay. I wrongly assumed that .NET Fiddle would be magically using my system settings, but it makes sense that it wouldn't have access to that information. It does seem likely this is a Unity issue. Thanks!Strom
"Funny" that this still wasn't fixed ... it is a very old known issue and there exists a workaroundWarhorse
@Warhorse I tried that work around earlier, but it resulted in the same behavior as Unity in that the short date and time were the defaults for "en-US" ("M/d/yyyy" and "h:mm tt") and not the values I have set in Regions ("yyyy-MM-dd" and "HH:mm"). Of course, the kernel32.dll method call pointed me in the right direction, as there's another method included in the DLL that gives me exactly what I was looking for.Strom
S
2

As Martheen stated in the comment, the Unity editor appears to be ignoring or overriding the CurrentCulture as provided by the OS.


UPDATE:

I found a solution that does return the data that I'm looking for, which I'll include below. First I'll describe the problem in more detail.

On my Windows 10 system, my Region settings are customized as follows:

  • Format: English (United States)
  • Short date: "yyyy-MM-dd"
  • Short time: "HH:mm"
  • Long time: "HH:mm:ss"

The default "en-US" settings for those properties are:

  • Format: English (United States)
  • Short date: "M/d/yyyy"
  • Short time: "h:mm tt"
  • Long time: "h:mm:ss tt"

I didn't customize the "Long date" format or any other settings.

Inside Unity, when accessing Thread.CurrentThread.CurrentCulture, it does seem to return the proper culture, but in its default state. It's as if when Unity loads, it sees that "en-US" is being used, but it then overwrites the culture with default values. Something like this:

var culture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture.Name);

Later, when trying to get the short date and time formats, they return the default values instead of the values I've specified in Region settings.

var culture = Thread.CurrentThread.CurrentCulture;
string shortDatePattern = culture.DateTimeFormat.ShortDatePattern; // "M/d/yyyy"
string shortTimePattern = culture.DateTimeFormat.ShortTimePattern; // "h:mm tt"

By invoking the GetLocaleInfoEx() method included in kernel32.dll and passing certain parameters, I can access the actual Region settings active on my system. The following methods can return the format pattern for the short date, short time, and long time. And GetDateTimeFormat() returns for me the combined short date and long time for the current culture: "yyyy-MM-dd HH:mm:ss".

public class LocaleFunctions
{
   public enum LCTYPE : uint
   {
      // There are 150+ values than can be passed to GetLocaleInfoEx(),
      // but most were excluded for brevity.
      //
      // See the following header file for defines with the "LOCALE_" prefix:
      //    https://github.com/wine-mirror/wine/blob/master/include/winnls.h
      //
      LOCALE_SSHORTDATE = 0x001F,
      LOCALE_STIMEFORMAT = 0x1003,
      LOCALE_SSHORTTIME = 0x0079
   }

   public static string GetDateTimeFormat()
   {
      var locale = Thread.CurrentThread.CurrentCulture.Name;
      return GetShortDateFormat(locale) + " " + GetLongTimeFormat(locale);
   }

   public static string GetLongTimeFormat(string locale, int maxLength = 32)
   {
      var data = new StringBuilder(maxLength);
      GetLocaleInfoEx(locale, LCTYPE.LOCALE_STIMEFORMAT, data, maxLength);
      return data.ToString();
   }

   public static string GetShortDateFormat(string locale, int maxLength = 32)
   {
      var data = new StringBuilder(maxLength);
      GetLocaleInfoEx(locale, LCTYPE.LOCALE_SSHORTDATE, data, maxLength);
      return data.ToString();
   }

   public static string GetShortTimeFormat(string locale, int maxLength = 32)
   {
      var data = new StringBuilder(maxLength);
      GetLocaleInfoEx(locale, LCTYPE.LOCALE_SSHORTTIME, data, maxLength);
      return data.ToString();
   }

   // pInvoke declarations
   [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
   private static extern int GetLocaleInfoEx(
      string lpLocaleName,
      LCTYPE lcType,
      StringBuilder lpLCData,
      int cchData);
}
Strom answered 28/7, 2020 at 0:41 Comment(2)
this seems like a windows only solutionApheliotropic
Yeah. This solution would only work for Windows. I haven't tested how Unity handles the CurrentCulture on Mac and whether it's reset like it appears to be under Windows. So I'm not sure if my original issue is even reproducible on Mac.Strom
J
0

It seems that Unity can't get the values directly from the Operating System, but you can still formatting your DateTime as shown below, with all the system options:

    System.DateTime currentTime = System.DateTime.Now;

    void Start()
    {
        //Short date
        Debug.Log(currentTime.ToString("M/d/yyyy"));
        Debug.Log(currentTime.ToString("M/d/yy"));
        Debug.Log(currentTime.ToString("MM/dd/yy"));
        Debug.Log(currentTime.ToString("MM/dd/yyyy"));
        Debug.Log(currentTime.ToString("yy/MM/dd"));
        Debug.Log(currentTime.ToString("yyyy-MM-dd"));
        Debug.Log(currentTime.ToString("dd-MMM-yy"));

        //Long date
        Debug.Log(currentTime.ToString("dddd, MMMM d, yyyy"));
        Debug.Log(currentTime.ToString("MMMM d, yyyy"));
        Debug.Log(currentTime.ToString("dddd, d MMMM, yyyy"));
        Debug.Log(currentTime.ToString("d MMMM, yyyy"));

        //Short time
        Debug.Log(currentTime.ToString("h:mm tt"));
        Debug.Log(currentTime.ToString("hh:mm tt"));
        Debug.Log(currentTime.ToString("H:mm"));
        Debug.Log(currentTime.ToString("HH:mm"));

        //Long time
        Debug.Log(currentTime.ToString("h:mm:ss tt"));
        Debug.Log(currentTime.ToString("hh:mm:ss t"));
        Debug.Log(currentTime.ToString("H:mm:ss"));
        Debug.Log(currentTime.ToString("HH:mm:ss"));
    }
Jonis answered 28/7, 2020 at 9:39 Comment(1)
Thanks. I was originally looking for a solution would get the settings defined by the user in the operating system, and I had settled on using a string literal like you included until I discovered a way to get the actual values I was looking for.Strom

© 2022 - 2024 — McMap. All rights reserved.