Is there an easy way in PowerShell to format numbers and the like in another locale? I'm currently writing a few functions to ease SVG generation for me and SVG uses .
as a decimal separator, while PowerShell honors my locale settings (de-DE
) when converting floating-point numbers to strings.
Is there an easy way to set another locale for a function or so without sticking
.ToString((New-Object Globalization.CultureInfo ""))
after every double
variable?
Note: This is about the locale used for formatting, not the format string.
(Side question: Should I use the invariant culture in that case or rather en-US
?)
ETA: Well, what I'm trying here is something like the following:
function New-SvgWave([int]$HalfWaves, [double]$Amplitude, [switch]$Upwards) {
"<path d='M0,0q0.5,{0} 1,0{1}v1q-0.5,{2} -1,0{3}z'/>" -f (
$(if ($Upwards) {-$Amplitude} else {$Amplitude}),
("t1,0" * ($HalfWaves - 1)),
$(if ($Upwards -xor ($HalfWaves % 2 -eq 0)) {-$Amplitude} else {$Amplitude}),
("t-1,0" * ($HalfWaves - 1))
)
}
Just a little automation for stuff I tend to write all the time and the double values need to use the decimal point instead of a comma (which they use in my locale).
ETA2: Interesting trivia to add:
PS Home:> $d=1.23
PS Home:> $d
1,23
PS Home:> "$d"
1.23
By putting the variable into a string the set locale doesn't seem to apply, somehow.
If you already have the culture loaded in your environment,
it is possible to resolve this problem:
like this:
Of course you need to keep in mind that if other users run the script with a different locale setting, the results may not turn out as originally intended.
Nevertheless, this solution could come in useful. Have fun!
While Keith Hill's helpful answer shows you how to change a script's current culture on demand (more modern alternative as of PSv3+ and .NET framework v4.6+:
[cultureinfo]::CurrentCulture = [cultureinfo]::InvariantCulture
), there is no need to change the culture, because - as you've discovered in your second update to the question - PowerShell's string interpolation (as opposed to using the-f
operator) always uses the invariant rather than the current culture:In other words:
If you replace
'val: {0}' -f 1.2
with"val: $(1.2)"
, the literal1.2
is not formatted according to the rules of the current culture.You can verify in the console by running (on a single line; PSv3+, .NET framework v4.6+):
Background:
Surprisingly, PowerShell always applies the invariant rather than the current culture in the following string-related contexts, if the type at hand supports culture-specific conversion to and from strings:
As explained in this in-depth answer, PowerShell explicitly requests culture-invariant processing - by passing the
[cultureinfo]::InvariantCulture
instance - in the following scenarios:When string-interpolating: if the object's type implements the
IFormattable
interface; otherwise, PowerShell calls.psobject.ToString()
on the object.When casting:
[string]
-typed parameter: if the source type implements the[IFormattable]
interface; otherwise, PowerShell calls.psobject.ToString()
..Parse()
method has an overload with an[IFormatProvider]
-typed parameter (which is an interface implemented by[cultureinfo]
).When string-comparing (
-eq
,-lt
,-gt
) , using theString.Compare()
overload that accepts aCultureInfo
parameter.Others?
As for what the invariant culture is / is for:
Presumably, it is the stability across cultures that motivated PowerShell's designers to consistently use the invariant culture when implicitly converting to and from strings.
For instance, if you hard-code a date string such as
'7/21/2017'
into a script and later try to convert it to date with a[date]
cast, PowerShell's culture-invariant behavior ensures that the script doesn't break even when run while a culture other than US-English is in effect - fortunately, the invariant culture also recognizes ISO 8601-format date and time strings;e.g.,
[datetime] '2017-07-21'
works too.On the flip side, if you do want to convert to and from current-culture-appropriate strings, you must do so explicitly.
To summarize:
Converting to strings:
"..."
yields a culture-invariant representation ([double]
or[datetime]
are examples of such types)..ToString()
explicitly or use-f
, the formatting operator (possibly inside"..."
via an enclosing$(...)
).Converting from strings:
A direct cast (
[<type>] ...
) only ever recognizes culture-invariant string representations.To convert from a current-culture-appropriate string representation (or a specific culture's representation), use the target type's static
::Parse()
method explicitly (optionally with an explicit[cultureinfo]
instance to represent a specific culture).Culture-INVARIANT examples:
string interpolation and casts:
"$(1/10)"
and[string] 1/10
0.1
, with decimal mark.
, irrespective of the current culture.Similarly, casts from strings are culture-invariant; e.g.,
[double] '1.2'
.
is always recognized as the decimal mark, irrespective of the current culture.[double] 1.2
is not translated to the culture-sensitive-by-default method overload[double]::Parse('1.2')
, but to the culture-invariant[double]::Parse('1.2', [cultureinfo]::InvariantCulture)
string comparison (assume that
[cultureinfo]::CurrentCulture='tr-TR'
is in effect - Turkish, wherei
is NOT a lowercase representation ofI
)[string]::Equals('i', 'I', 'CurrentCultureIgnoreCase')
$false
with the Turkish culture in effect.'i'.ToUpper()
shows that in the Turkish culture the uppercase isİ
, notI
.'i' -eq 'I'
$true
, because the invariant culture is applied.[string]::Equals('i', 'I', 'InvariantCultureIgnoreCase')
Culture-SENSITIVE examples:
The current culture IS respected in the following cases:
With
-f
, the string-formatting operator (as noted above):[cultureinfo]::currentculture = 'de-DE'; '{0}' -f 1.2
yields1,2
-f
must be enclosed in(...)
in order to be recognized as such:'{0}' -f 1/10
is evaluated as if('{0}' -f 1) / 10
had been specified;use
'{0}' -f (1/10)
instead.Default output to the console:
e.g.,
[cultureinfo]::CurrentCulture = 'de-DE'; 1.2
yields1,2
The same applies to output from cmdlets; e.g.,
[cultureinfo]::CurrentCulture = 'de-DE'; Get-Date '2017-01-01'
yieldsSonntag, 1. Januar 2017 00:00:00
Caveat: There appears to be a bug as of Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.5: in certain scenarios, literals passed to a script block as unconstrained parameters can result in culture-invariant default output - see this GitHub issue
When writing to a file with
Set-Content
/Add-Content
orOut-File
/>
/>>
:[cultureinfo]::CurrentCulture = 'de-DE'; 1.2 > tmp.txt; Get-Content tmp.txt
yields1,2
When using the static
::Parse()
/::TryParse()
methods on number types such as[double]
while passing only the string to parse; e.g., with culturefr-FR
in effect (where,
is the decimal mark),[double]::Parse('1,2')
returns double1.2
(i.e.,1 + 2/10
).0
is also accepted; e.g., in theen-US
culture (where,
is the thousands separator),[double]::Parse('0,18')
perhaps surprisingly succeeds and yields18
.[double]::Parse('0,18', 'Float')
, via theNumberStyles
parameterUnintentional culture-sensitivity that won't be corrected to preserve backward compatibility:
-as
operator - see this GitHub issue.[hashtable]
key lookups - see this answer and this GitHub issue.Others?
This is a PowerShell function I use for testing script in other cultures. I believe it could be used for what you are after:
I was thinking about how to make it easy and came up with accelerators:
I hope it will help.