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