powershellbatch-filelocale

Localization affecting the output of Powershell Get-Date command


I am trying to call Powershell Get-Date command within the batch script. Here is my batch script:

@echo off
setlocal EnableDelayedExpansion
set myFormat=dd/MM
set locale_independent_get_date=powershell -NonInteractive -Command "Get-Date -Format %myFormat%"
for /F "tokens=1,2 delims=/" %%i IN ('%locale_independent_get_date%') DO (
    set dd=%%i
    set mm=%%j
)
echo Day:[%dd%]
echo Month:[%mm%]

Because I explicitly specify the format with -Format switch, I was expecting to have the same output with every different locale settings.

But I see that, this is not true. Above Powershell command is not working as I expected with some different settings.

Isn't specifying the format explicitly with -Format enough to ensure locale independence? Clearly it is not, but I am wondering why.

Also, Instead of -Format, I tried -UFormat switch. In this case, command is working as expected in problematic cases too. Here what I did :

set myFormat=%%d/%%m
set locale_independent_get_date=powershell -NonInteractive -Command "Get-Date -UFormat %myFormat%"

But as we have many different possible settings, I cannot try all of them. So, How can I be certain that this command is locale-independent? Is there any documentation ? Or Do you know an easy way to test this code with all possible locale settings ?

Thank you !


Solution

  • Escape the / char:

    set myFormat=dd\/MM
    

    Doing so ensures that .NET, which PowerShell is built on, uses the / literally in the output string, whereas unescaped use treats / as a placeholder, namely for the culture-appropriate date-component separator; e.g., with the de-DE (German) culture in effect, / turns into . in the output string.

    The same applies analogously to the time-component separator, :

    (As an alternative to \-escaping, you may use embedded '...' or "..." quoting, e.g. set myFormat=dd'/'MM, but to avoid quote character-escaping headaches when calling from outside PowerShell it is simpler to use the \ escape).

    For more information, see the .NET docs:

    Caveat:


    To provide a succinct demonstration in PowerShell code:

    & {
    
      # Save the culture currently in effect.
      $prevCulture = [cultureinfo]::CurrentCulture
    
      # Loop over all given cultures.
      $args[0] | ForEach-Object {
        [cultureinfo]::CurrentCulture = $_
    
        # Contrast escaped and unescaped use.
        [pscustomobject] @{
          Culture = $_
          UnescapedSlash = Get-Date -Format dd/MM
          EscapedSlash = Get-Date -Format dd\/MM # Note the \
        }
    
      }
    
      # Restore the previous culture
      [cultureinfo]::CurrentCulture = $prevCulture
    
    } 'en-US', 'de-DE'
    

    The above prints:

    Culture UnescapedSlash EscapedSlash
    ------- -------------- ------------
    en-US   26/11          26/11
    de-DE   26.11          26/11
    

    Note how, for culture de-DE, the unescaped / in the format string turned into the culture-appropriate . separator, whereas the escaped form was retained verbatim.


    Additionally ensuring use of the Gregorian calendar:

    As noted, the month and day numbers are expressed in the current culture's calendar, as reflected in [cultureinfo]::CurrentCulture.Calendar.GetType().Name.

    Thus, with a culture that uses a calendar other than the Gregorian one in effect, the day and month number can differ (e.g., 26 November 2024 will yield '26/11' in cultures using the Gregorian Calendar, but '24/05' in ar-SA (Saudi Arabian Arabic, which uses the UmAlQuraCalendar calendar).

    To ensure use of the Gregorian calendar, temporarily switch to the invariant culture, [cultureinfo]::InvariantCulture, as part of the PowerShell CLI call:

    @echo off
    set myFormat=dd\/MM
    set locale_independent_get_date=powershell -NonInteractive -Command "[cultureinfo]::CurrentCulture=[cultureinfo]::InvariantCulture; Get-Date -Format %myFormat%"
    for /F "tokens=1,2 delims=/" %%i IN ('%locale_independent_get_date%') DO (
        set dd=%%i
        set mm=%%j
    )
    echo Day:[%dd%]
    echo Month:[%mm%]