11

My understanding of PowerShell's string embedding syntax "$($object)" has always been that $object is cast to [System.String], which invokes $object.ToString(). However, I've noticed this curious behavior with the [DateTime] class using PowerShell 4.0 on Windows 8.1.

PS> $x = Get-Date

PS> $x.GetType() | select -ExpandProperty Name
DateTime

PS> $x.ToString()
2015-05-29 13:36:06

PS> [String]$x
05/29/2015 13:36:06

PS> "$($x)"
05/29/2015 13:36:06

It seems that "$($object)" gives the same behavior as casting to string, but is clearly producing a different result from $object.ToString(). $x.ToString() is consistent with the short date format set in intl.cpl (yyyy-MM-dd). [String]$x appears to use the en-US default.

It is possible this is simply a bug in the DateTime class, but I'm more surprised that the different methods of converting an object to a string produce different results. What are the rules for casting an object to a string, if not calling ToString()? Is the DateTime class simply a special case because of its overloading of ToString(String)?

Ryan Bemrose
  • 9,018
  • 1
  • 41
  • 54
  • 1
    I don't have enough for an answer right now, but I have definitely noticed that `.ToString()` does not always produce the same result as embedding an object in a string (I haven't tested if embedding is always the same as casting to `[String]`). So all I can say is it's not limited to `[DateTime]`. – briantist May 29 '15 at 21:12
  • 1
    `.Tostring()` accepts a format argument, so if you require consistency, always `.ToString()` and provide your desired format. Meanwhile, agreed that `cast` should just call `.ToString()` and accept whatever it's default format is. Not sure why they would implement it any other way. – johnjps111 May 29 '15 at 21:26
  • 2
    [Probably related](http://stackoverflow.com/q/14359053/1630171). I would suspect that casting uses en-US as the default culture either way. `ToString()` OTOH uses the current culture, as [documented](https://msdn.microsoft.com/en-us/library/k494fzbf%28v=vs.110%29.aspx). – Ansgar Wiechers May 29 '15 at 22:30
  • 1
    What is the output of `Get-Culture` on your machine? – Matt Johnson-Pint May 30 '15 at 00:56
  • 1
    Hmmm.. I get the `mm/dd/yyyy` format with `$x.ToString()`. Though I also get 12-hour time, and with the `[String]$x` I get 24 hour time. Also worth noting - the cast from `DateTime` to `[String]` must be something supported by Powershell, as it is not valid for C# or other .NET languages. – Matt Johnson-Pint May 30 '15 at 01:03
  • Culture is 1033 en-US, but I've used the control panel to update the date (yyyy-MM-dd) and time (HH:mm:ss) formats. – Ryan Bemrose May 30 '15 at 01:11

2 Answers2

9

If an object implements the IFormattable interface, then PowerShell will call IFormattable.ToString instead of Object.ToString for the cast operation. A similar thing happens for static Parse method: if there is an overload with IFormatProvider parameter, then it would be called.

Add-Type -TypeDefinition @'
    using System;
    using System.Globalization;
    public class MyClass:IFormattable {
        public static MyClass Parse(string str) {
            return new MyClass{String=str};
        }
        public static MyClass Parse(string str,IFormatProvider fp) {
            return new MyClass{String=str,FormatProvider=((CultureInfo)fp).DisplayName};
        }
        public string String {get;private set;}
        public string FormatProvider {get;private set;}
        public override string ToString() {
            return "Object.ToString()";
        }
        string IFormattable.ToString(string format,IFormatProvider fp) {
            return string.Format("IFormattable.ToString({0},{1})",format,((CultureInfo)fp).DisplayName);
        }
    }
'@
[String](New-Object MyClass) #Call IFormattable.ToString(null,CultureInfo.InvariantCulture)
[MyClass]'Test'              #Call MyClass.Parse("Test",CultureInfo.InvariantCulture)
mklement0
  • 382,024
  • 64
  • 607
  • 775
user4003407
  • 21,204
  • 4
  • 50
  • 60
4

Your question is not PowerShell question, but a .NET question. PowerShell scripts can use the .NET structure [datetime] as it is, i.e., PowerShell does not change its behavior.

What are the rules for casting an object to a string, if not calling ToString()?

Casting uses culture invariant definitions. The ToString() method can contain culture dependent implementations, because it is overridable.

Is the DateTime class simply a special case because of its overloading of ToString(String)?

First, DateTime is not a class; it's a structure. Second, there's no ToString() method overloading; in this case, the correct denomination is overriding (it is overriding the Object.ToString() method).

To understand better what I'm meaning, have fun with these pretty funny date and time printing in different cultures (copy, paste and run):

function f{
    $x=get-date
    [CultureInfo]$currentCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('en-US')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('ar-IQ')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('de-DE')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('ru-RU')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('fr-FR')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('zh-CN')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('zh-HK')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('zh-TW')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('hu-HU')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('ko-KR')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('ja-JP')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('ka-GE')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=[CultureInfo]::CreateSpecificCulture('pt-BR')
    $x.ToString()
    [System.Threading.Thread]::CurrentThread.CurrentCulture=$currentCulture
}

f

Notice that the code above will produce different printing if run either in ISE or in non-ISE versions of PowerShell.

  • I used "Overloading" in this context to distinguish DateTime's `ToString()` from `ToString(formatString)`. I'm not entirely familiar with the difference between struct and class in .NET. Is it relevant here? – Ryan Bemrose May 30 '15 at 03:59
  • Yes, it's confusing. `ToString()` method is the `Object.ToString()` method, which is overridable. But, DateTime **also defines** overloadings to this method, but `ToString()` method is an overriding, and `ToString(something)` method is an **overloading**. Classes and structures are quite different, really. –  May 30 '15 at 04:08