2

I'm working with an API which I know requires a timestamp that can be successfully produced with the following line of python: str(int(1000000 * datetime.datetime.utcnow().timestamp()))

This looks like it takes a Unix epoch time (with fractions of a second in the decimal place) and multiplies it by 1000000.

My problem in powershell is that I dont know how to get the same 6 decimal places returned by Python:

datetime.datetime.utcnow().timestamp()
1643941678.401823

In PowerShell I can do

Get-Date   -UFormat %s 
1643920137  

But, this only gives me the epoch with no decimal places.

Is there any way to get the same value with decimals as returned by the Python code in PowerShell?

Also it looks like the values returned by powershell are even more off, I ran those commands within seconds and they are off by 20,000 seconds? Anyone have any idea why theres such a big time difference?

dcom-launch
  • 147
  • 6

1 Answers1

3

To get the current point in time's epoch time as a seconds-with-fractions value based on the full resolution that the .NET [datetime] type offers (100-nanosecond intervals):

[decimal] (
    [datetime]::UtcNow - [datetime]::new(1970, 1, 1, 0, 0, 0, 0, 'Utc')
  ).Ticks / 1e7

Or, a bit more succinctly, using the [datetimeoffset] type instead:

[decimal] ([datetimeoffset]::UtcNow - [datetimeoffset] '1970-01-01Z').Ticks / 1e7

Tip of the hat to Theo for his help - see the bottom section.

In PowerShell (Core) 7+ (.NET (Core) 2.1+), you can simplify to (also works with [datetimeoffset]):

[decimal] ([datetime]::UtcNow - [datetime]::UnixEpoch).Ticks / 1e7

The result is a [decimal] instance with 6 decimal places; e.g., 1643939471.006042

This should match the output from your Python (v3+) command, assuming you fix it by replacing .utcnow() with .now():

# Note: Use .now(), not .utcnow() - the latter seemingly causes
#       .timestamp() to misinterpret the UTC point in time as a *local* one.
python3 -c "import datetime; print(str(datetime.datetime.now().timestamp()))"

If millisecond resolution is sufficient, a simpler solution, via the [datetimeoffset] type, is possible:

[decimal] [datetimeoffset]::UtcNow.ToUnixTimeMilliseconds() / 1e3

An aside re Get-Date -UFormat %s:

  • Since the purpose of Get-Date's -Format / -UFormat parameters is to produce a formatted string representation, the return value is indeed a string rather than a number. And, as shown in your question, in PowerShell (Core) 7+, with -UFormat %s this string represents an integer (whole-seconds) values.

  • In Windows PowerShell (the legacy PowerShell edition whose latest and last version is 5.1) the value represented by the output string actually is a fractional value, but it is flawed in two respects:

    • The value returned is only correct if you pass a [datetime] instance that represents a UTC point in time explicitly (i.e. an instance whose .Kind property is Utc, such as obtained with [datetime]::UtcNow).

    • The string representation is culture-sensitive, so that in certain cultures , rather than . is used as the decimal mark.

    • See this answer for details.


An aside re subtracting [datetime] instances in order to get a time span ([timespan] instance):

Subtracting two [datetime] instances (as shown in the solutions above) only works robustly if their .Kind property values are the same, as the instances' .Ticks values are seemingly blindly subtracted, even if their reference point - UTC (Utc) vs. local time (Local) - differs (note that the third possible .Kind value, Unspecified, is by design ambiguous - the reference point isn't specified):

# !! Unless your local time zone happens to coincide with UTC, 
# !! this does NOT yield 0 - even though both operands
# !! unambiguously reference the same point in time: one as .Kind
# !! Utc, the other as .Kind Local
$utcNow = [datetime]::UtcNow
($utcNow - $utcNow.ToLocalTime()).TotalHours

E.g., in the US EST time zone, the above yields 5, i.e. the UTC offset in hours between the local time and UTC.

While it may be tempting to construct the timestamp for the start of the Unix epoch more succinctly with [datetime] '1970-01-01Z' (as opposed to the cumbersome [datetime]::new(...) call above), the problem is that this yields an instance whose .Kind value is Local, not Utc; that is, the UTC string representation is implicitly translated into the equivalent local timestamp.

Using the generally preferable [datetimeoffset] class avoids this problem:

[datetimeoffset] instances represent unambiguous points in time with a specifiable UTC offset (a timespan stored in the .Offset property, as implied by the local time zone, for instance). Calculations involving two instances work robustly, because they are based on the offset-independent .UtcTicks property, i.e. the unambiguous number of 100-nanosecond intervals since midnight of 1 Jan 0001 UTC.

When subtracting, it is sufficient for the LHS operand to be of type [datetimeoffset]:

# OK - always yields 0, because both operands are recognized
# as the same point in time, thanks to the LHS type
# being of type [datetimeoffset] (converted from [datetime]).
$utcNow = [datetime]::UtcNow
([datetimeoffset] $utcNow - $utcNow.ToLocalTime()).TotalHours

Of course, it also works as expected when both operands are [datetimeoffset] instances:

# OK - always yields 0, despite the different UTC offsets.
# (The [int] cast ignores the slight difference that comes from
#  asking for "now" in two successive calls.)
[int] ([datetimeoffset]::UtcNow - [datetimeoffset]::Now).TotalHours
mklement0
  • 382,024
  • 64
  • 607
  • 775