Powershell: Add -AsLocal switch to Get-Date

Created on 30 Jan 2020  路  20Comments  路  Source: PowerShell/PowerShell

Similar to the recent PR that added the -AsUTC switch to Get-Date, we should also have the ability to set Local DateTimeKind as well.

Examples of how this would work:

Get-Date "1/12/2020 4:00pm" -Format "MM/dd/yyyy HH:mm K"
# The DateTimeKind here is Unspecified, so result is 01/12/2020 16:00

Get-Date ((Get-Date "1/12/2020 4:00pm").ToUniversalTime()) -Format "MM/dd/yyyy HH:mm K"
Get-Date "1/12/2020 4:00pm" -AsUTC -Format "MM/dd/yyyy HH:mm K"
# The DateTimeKind here is Utc, so result is 01/12/2020 22:00 Z

Get-Date ((Get-Date "1/12/2020 4:00pm").ToLocalTime()) -Format "MM/dd/yyyy HH:mm K"
Get-Date "1/12/2020 4:00pm" -AsLocal -Format "MM/dd/yyyy HH:mm K"
# The DateTimeKind here is Local, so result is 01/12/2020 10:00 -06:00
Area-Cmdlets-Utility Issue-Enhancement

All 20 comments

Great idea.
For the sake of completeness, -AsUnspecified would be helpful too, if you're expressly looking for an abstract point in time.

I assume the intent of the -As* switches is to either _directly output_ a [datetime] instance with the indicated .Kind value (UTC, Local, Unspecified), or to make such an instance _the basis for applying the -Format / -UFormat format_.

We have to be careful about the logic of interpreting the _input_, however:

From what I can tell, only Get-Date -Date with either a _string_ argument (except strings explicitly indicating UTC or containing a time-zone offset) or a [datetime] instance whose .Kind is Unspecified currently results in the _output_ [datetime] instance having .Kind Unspecified; otherwise, it is Local (or will be Utc with -AsUtc).

The tricky part is that Unspecified instances are treated _situationally_ either as representing UTC _or_ as representing LOCAL time:

  • .ToUniversalTime() interprets an Unspecified instance as Local
  • .ToLocalTime() interprets an Unspecified instance as Utc(!)

Therefore, as also implied by your last example command:

# NOTE: Interprets the given date *as UTC*
PS> (Get-Date "1/12/2020 4:00pm").ToLocalTime()

Sunday, January 12, 2020 11:00:00 AM   # e.g., in the US Eastern time zone

This is .NET's behavior, over which we have no control, but I wonder if surfacing this behavior via
-AsLocal would cause confusion:

# !! If we just call .ToLocalTime() behind the scenes, we get the same behavior as above:
# !! The given date is interpreted *as UTC* and then *converted to a local date*.
Get-Date "1/12/2020 4:00pm" -AsLocal

My expectation would be that the nominal date given (in text form) would be interpreted _as-is_ as a local date; which is the equivalent of:

PS> [datetime]::new((Get-Date "1/12/2020 4:00pm").Ticks, 'Local') | Select DateTime, Kind

DateTime                             Kind
--------                             ----
Sunday, January 12, 2020 4:00:00 PM Local

Note how the unspecified input date string was now interpreted _as_ a local time, as reflected in the output [datetime] instance, which has .Kind Local, as requested.

+1 @mklement0. I would expect it to use:

[datetime]::Parse(
    '4pm',
    [cultureinfo]::CurrentCulture,
    [Globalization.DateTimeStyles]::AssumeLocal)

Thanks, @SeeminglyScience, but note that since the -Date parameter is [datetime]-typed, I think it is the _parameter binder_ that performs the parsing up front (culture-_sensitively_, due to being a _cmdlet_ argument, as opposed to the culture-_invariant_ parsing that happens with _casts_ or _advanced functions_, which is a won't-fix inconsistency; see #6989)

Therefore, I think Get-Date already sees the parsed result, which is a [datetime] instance with a .Kind value of Unspecified - hence my suggestion to use [datetime]::new(<ticks>, <kind>).

@mklement0 and @SeeminglyScience Thanks for joining in!

If we are including all three DateTimeKind options, do you think we need a parameter more like
-AsDateTimeKind Utc
-AsDateTimeKind Local
-AsDateTimeKind Unspecified
?

-AsUTC hasn't been part of a formal release yet, so now would be the time to change it if the PowerShell team was going to?

I'd say, given that there's only _3_ enumeration values whose number is unlikely to grow, it's easier on users to use distinct switches, -AsUtc, -AsLocal, -AsUnspecified.

@mklement0 Fair point. I can accept that ;-)

Then again, for invocation with programmatically constructed parameters something like -AsKind Utc|Local|Unspecified is preferable...

Having our cake and eating it too (providing -AsUtc as an effective alias of -AsKind Utc, ...) is also an option, but might get confusing / be a pain to implement (many parameter sets).

;-) I have only started posting suggestions for PowerShell in the last few days and I hate to ask for the moon, so I am trying to be semi-conservative in my asks. I will take it either way ;-)

Since we're adding multiple options here for the kind it should be an enum not switch parameter. Right now if we add -AsUtc and -AsLocal and -AsUnspecified those three switch parameters will need to be in separate parameter sets since you can't have multiple passed at the same time.

As stated before, for _programmatic_ construction of arguments -AsKind <kind> is preferable; that doesn't necessary preclude _also_ providing _convenience aliases_ (-AsUtc for -AsKind Utc, ...; they wouldn't be the first of their kind), but I get that this (a) could be a paint to _implement_ (proliferation of parameter sets) and (b) the duplication could cause _conceptual confusion_.

In short: I'm personally fine with _only_ implementing -AsKind <kind>, if that's the consensus.

However, we should then act quickly, before 7.0 GA ships with the recently implemented -AsUtc switch.

Do you have any examples of a alias switch parameter mapping to another parameter?

The one that readily comes to mind is Get-Location -Stack for Get-Location -StackName '', but, if memory serves, there are more; they're not easy to search for.

More relatable examples:

  • In Get-ChildItem: -Directory / for Attributes -Directory, -File for -Attributes !Directory, -Hidden for -Attributes Hidden, -ReadOnly for -Attributes ReadOnly, -System for Attributes System.

  • In Import-Module: -Global for -Scope Global ("The Global parameter is equivalent to the Scope parameter with a value of Global." - help).

I'm curious, though, @ThomasNieto: Are you asking not just because you were curious / weren't convinced that there is such a thing, or are you philosophically opposed to the concept?

@mklement0 I was unaware of them or had used them without knowing. The concern I have with them is that you can get into scenarios like this where you can pass conflicting parameters. Normally tab completion will prevent a user from passing parameters that cannot be used together. In the first example -Global and -Scope can't be used together but is allowed by tab completion since they're in the same parameter set. The second example is where both can be used at the same time if all attributes are met.

To sum it up, I think convenience parameters have a place where they can be used in conjunction with the other parameter. PowerShell has done a good job with parameter sets protecting the user from passing parameters that cannot be used together through tab completion and help.

C:\> Import-Module AssignedAccess -Global -Scope Local
Import-Module: The 'Global' and 'Scope' parameters cannot be specified together. Remove one of these parameters, and then try running the command again.
C:\> Get-ChildItem -Attributes Directory -Hidden


    Directory: C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d--hs           12/4/2019 10:42 PM                $Recycle.Bin
l--hs           12/4/2019 11:35 PM                Documents and Settings -> C:\Users
d--h-            2/3/2020 10:09 AM                ProgramData
d--hs           12/4/2019 11:35 PM                Recovery
d--hs            2/5/2020  6:09 PM                System Volume Information

or had used them without knowing.

That's a testament to their usefulness.

To sum it up, I think convenience parameters have a place where they can be used in conjunction with the other parameter

While you _can_ squeeze combinations out of -File/ -Directory, ... vs. -Attributes, it's awkward and potentially self-defeating:

# Allowed, but nonsensical
Get-ChildItem -File -Attributes directory

# The more intuitive way to write your example, using shortcut aliases only
Get-ChildItem -Directory -Hidden

The far more typical case is to use one _or_ the other; if there's something you cannot model with combining the shortcut aliases alone, you're better off using _just_ -Attributes.

With _single-choice_ enumerations - as in the case of -Global vs. -Scope Global and in our case, -AsUtc, ... vs. -AsKind Utc - conceptual confusion doesn't even arise, because the shortcut aliases _mustn't_ be combined with the verbose parameter.

As such, these single-choice scenarios strike me as the _ideal_ use case for shortcut aliases.

That Import-Module currently implements this exclusion awkwardly is not an argument against the concept.

Similarly, the fact that _implementing_ such mutual exclusion is currently cumbersome is not an argument - it just means that we need to make implementing this easier, as has been attempted before, in this since-withdrawn RFC: https://github.com/PowerShell/PowerShell-RFC/blob/master/X-Withdrawn/RFC0001-Mutual-Exclusion-Parameters-and-Properties.md

GitHub
RFC (Request for Comments) documents for community feedback on design changes and improvements to PowerShell ecosystem - PowerShell/PowerShell-RFC

To continue the discussion from https://github.com/PowerShell/PowerShell/pull/13084#discussion_r454955330, where @iSazonov wrote:

I guess the DateTimeKind was added to mitigate Datetime type limitations (today we could use DateTimeOffset). I do not think that we should use DateTimeKind broadly.

Agreed that [datetimeoffset] is always the better choice, butGet-Date is currently a wrapper for [datetime] only and for reasons of backward compatibility it will have to remain that way, at least by default.

Therefore, it makes sense to support it more fully, and to also offer -AsLocal and -AsUnspecified / -AsKind <kind>.

A possible alternative is to eliminate the Unspecified kind from Get-Date as follows, in which case _only_ implementing -AsUtc would be sufficient:

  • Unless -AsUtc is passed, _always_ return a Local date.

This would mean that commands such as Get-Date 2020-1-1, which currently return an Unspecified date, would then return a Local date, which may be more in line with what users expect.

Technically, though, it is a breaking change.

AsUTC was added in Preview only (#11611) and we can replace it with general parameter like -AsKind <kind> or -DateTimeKind <kind>. No needs exist for other parameters.

/cc @brendandburns

I think it would be complete to take TimeZoneInfo (or its Id) instead of DateTimeKind (in which case the output would be DateTimeOffset).
As a special case of TimeZoneInfo, it supports UTC and Local.


For example, I may want to know what time '10:00 AM PDT' is in Japanese time (this format is often used as the start time for some event).
So, I think it would be useful if I could convert the time zone as follows.

Get-Date -Date '2020-07-25 10:00' -TimeZone PDT -AsTimeZone JST

But, time zone abbreviations such as 'PDT' and 'JST' are not specified in ISO 8601 and are not official.
Also, it is unclear whether 'JST' refers to 'Japan Standard Time' or 'Jerusalem Standard Time'.
Thus, I am not sure if we should accept these notations as arguments.

This command will accept the following formats.
But, if the command only accepts values in the following form, I don't think this specification is so useful.
Because before I can use the command, I have to find out the official name of the time zone or its offset.

Get-Date -Date '2020-07-25 10:00' -TimeZone 'America/Los_Angeles' -AsTimeZone 'Asia/Tokyo'
Get-Date -Date '2020-07-25 10:00' -Offset -7 -AsOffset +9

I think we should be cautious about using SwitchParameter and ParameterSetName together, as they don't work well together.
For example, not setting -AsUTC is equivalent to specifying -AsUTC:$false.
However, if -AsUTC and -AsLocal are mutually exclusive by parameter set, then if -AsUTC is not set, -AsLocal can be set, but if -AsUTC:$false is specified, -AsLocal cannot be set.

@aetos382 RE: parameter sets - yes, that's how it's designed, you're not supposed to be able to use them together. Switches supporting a -Switch:$false option doesn't change that, really, I don't think that allowing both for that reason is very clear or sensible. It's much clearer to show users via parameter sets (which are directly reflected in the syntax diagrams in the Get-Help output) which switches can and can't be used in tandem. It would be pointless and confusing to display a syntax diagram that _seems_ to allow both, when in practice only one can be used.

As for the time zone suggestion, it would be best if you requested that in a new issue as it is an entirely separate request IMO.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alx9r picture alx9r  路  3Comments

JohnLBevan picture JohnLBevan  路  3Comments

andschwa picture andschwa  路  3Comments

SteveL-MSFT picture SteveL-MSFT  路  3Comments

Michal-Ziemba picture Michal-Ziemba  路  3Comments