Search code examples
powershellcultureinfo

Get-Culture DateTimeFormat doesn't respect local setting in console, DOES in ISE


This will produce the correct European formatting on my VM configured for Netherlands, but only in the ISE.

(Get-Date).toString((Get-Culture).DateTimeFormat.ShortDatePattern)
(Get-Date).toString((Get-Culture).DateTimeFormat.ShortTimePattern)

24/01/2025
09:19

When I run it in the console via shortcut it reverts to US standard.

1/24/2025
9:24 AM

Is there something I am missing to ensure correct behavior in the console? Or just one more way that Microsoft's inconsistency makes me pull my hair out?

EDIT: Changed the title to better reflect the fact that the code ONLY works in the deprecated ISE, and fails in the console where one would expect it to work.

EDIT: An update for anyone else who runs into this mess. From the sounds of it I should NOT be seeing this behavior, but I am, which means I need to work around it because I have no idea if a customer machine will have the same problem. So, the solution is

$culture = (Get-ItemPropertyValue 'HKCU:\Control Panel\International' LocaleName)
[cultureinfo]::CurrentCulture = $culture
$startTime.ToString('d')
$startTime.ToString('t')

Where $startTime is a DateTime. So easy, and yet it seems to me that I shouldn't even need to concern myself with Culture. .ToString() should just return the correct format based on OS settings, and if I want to be a $^%# and ignore the user's settings I should have to work harder to force 'EN-US' on someone. /RANT


Solution

  • Preface:

    • The tl;dr is:

      • Your problem isn't reproducible.
      • However, pitfalls do exist with respect to effective culture settings, as discussed below.
    • None of the pitfalls discussed below apply to PowerShell (Core) 7, irrespective of its host application; by definition it can not run in the Windows PowerShell ISE, which is but one of the reasons to avoid the latter (see next point).

    • Let me complement the advice to avoid the ISE in sirtao's answer with the following standard advice:

    • .NET has both an effective culture ([cultureinfo]::CurrentCulture) and a UI culture ([cultureinfo]::CurrentUICulture) per thread that can be set independently of one another and serve distinct purposes:

      • .CurrentCulture contains "information includes the names for the culture, the writing system, the calendar used, the sort order of strings, and formatting for dates and numbers."

        • As such, only .CurrentCulture is the one that matters in the case at hand.

        • On Windows, PowerShell's Set-Culture cmdlet can be used to persistently change the current culture for future sessions.

      • .CurrentUICulture is the "culture used by the Resource Manager to look up culture-specific resources at run time."

        • PowerShell has no Set-UICulture cmdlet analogous to Set-Culture.

    While there are many subtle and treacherous differences between the ISE and a regular Windows PowerShell console window / WT (Windows Terminal) tab, there is no appreciable difference with respect to culture handling.[1]

    However, there are culture-handling pitfalls that affect both environments:

    • Interactively setting [cultureinfo]::CurrentCulture (applies analogously to [cultureinfo]::CurrentUICulture) takes effect only for the given command submission.

      • Specifically, after submitting a command that involves changing [cultureinfo]::CurrentCulture, the original, persistently configured culture is reverted to immediately afterwards.
        This questionable behavior has been fixed in PowerShell (Core) 7.

      • E.g., in Windows PowerShell (whether in the ISE or not):

        • First, submit: & { [cultureinfo]::CurrentCulture = 'fr-FR'; [cultureinfo]::CurrentCulture.DisplayName }, and you'll see that the change did take effect in the context of the command submitted.

        • Then submit [cultureinfo]::CurrentCulture.DisplayName alone, and you'll see that the original, persistently configured culture was reverted to (Note: if your persistently configured culture happens to be fr-FR, substitute a different culture above.)

      • By contrast, fortunately, in a script (file or block), the change does stay in effect until the script is exited.

    • While a dynamically changed culture is in effect in Windows PowerShell (whether in the ISE or not), it is not reflected in Get-Culture's output, only in [cultureinfo]::CurrentCulture's.

      • That is, in Windows PowerShell Get-Culture reports the original, persistently configured culture throughout the entire session, irrespective of in-session changes.
        This unexpected behavior has been fixed in PowerShell (Core) 7.

      • Building on the example above, the following shows this discrepancy:
        & { [cultureinfo]::CurrentCulture = 'fr-FR'; [cultureinfo]::CurrentCulture.DisplayName; (Get-Culture).DisplayName }


    The upshot for your use case:

    Using Get-Culture isn't robust, as it isn't guaranteed to refer to the culture that is truly in effect if an in-session change was made.

    Therefore:

    • Don't use (Get-Culture).DateTimeFormat.ShortDatePattern, use
      [cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern, for instance.

    • More simply, you can take advantage of .NET's standard date and time formats in the context of the [datetime] type's .ToString method; e.g.:

    & {
      [cultureinfo]::CurrentCulture = 'nl-NL'
      (Get-Date).ToString('d')  # 'd' == [cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern
      (Get-Date).ToString('t')  # 't' == [cultureinfo]::CurrentCulture.DateTimeFormat.ShortTimePattern
    }
    

    Output:

    25-1-2025
    11:10
    

    [1] If you have a reproducible case, do tell us.
    There is a minor technical difference between these two environments, as iRon points out, but it doesn't seem to have any behavioral impact: in the ISE, Get-Culture returns an instance of a type that is derived from the expected [cultureinfo] type, namely [Microsoft.Windows.PowerShell.Gui.Internal.ISECultureInfo], which, as noted, can be discovered with (Get-Culture).pstypenames