1

I have a Powershell script that get the current time and convert it in Unix Timestamp. I would like to use this value as string parameter to make an HTTP request:

$currentTime = [math]::Round([double](get-date -uformat %s ((get-date).AddMinutes(-5).ToUniversalTime()))*1000)

Invoke-RestMethod -Method Delete -Uri "http://something?unmodifiedSince=$currentTime"

On some Windows machine it works fine but on some other (different Region settings?), I get the current time converted with the scientific notation. For example

http://something?unmodifiedSince=1.53835531189786E+17

How to avoid this conversion?

henrycarteruk
  • 12,708
  • 2
  • 36
  • 40
nicolas
  • 489
  • 1
  • 3
  • 22
  • What does "on some other" mean? – Jacob Colvin Oct 04 '18 at 14:47
  • @JacobColvin: Note the parenthetical "different Region settings?" clause, and it is indeed regional settings that are the source of the problem. – mklement0 Oct 04 '18 at 16:48
  • The way it's phrased made me think maybe he was running powershell on linux or something. @mklement0 – Jacob Colvin Oct 04 '18 at 16:58
  • @JacobColvin: I see. In PowerShell Core, on all platforms, including macOS and Linux, the problem would actually _not_ arise (and the code could be simplified) - see the bottom section of my answer. – mklement0 Oct 04 '18 at 17:00
  • Check out this answer https://stackoverflow.com/questions/1555397/formatting-large-numbers-with-net – Geordie Jan 18 '21 at 22:55

3 Answers3

1

Update:

[datetimeoffset]::UtcNow.AddMinutes(-5).ToUnixTimeSeconds()

tl;dr:

To work around the problem with culture-specific number formatting, avoid a [double] cast and use [double]::Parse() instead, which by default parses in a culture-sensitive manner:

[Math]::Round(
  [double]::Parse(
     (Get-Date -Uformat %s ((Get-Date).ToUniversalTime().AddMinutes(-5)))
  ) * 1000,
  0,
  'AwayFromZero'
)

The AwayFromZero midpoint-rounding strategy ensures that .5 second values are always rounded up, whereas the default ToEven strategy would situationally round down, namely whenever the integer part of the number happens to be even.


Indeed, your problem stems from the fact that:

  • Get-Date -UFormat % outputs a string representation of the Unix timestamp
  • and uses culture-sensitive formatting for the underlying floating-point number[1], which means that in some cultures you'll get a string such as '1538651788,87456' (, as the decimal mark) rather than '1538651788.87456' as the output.

By contrast, PowerShell's casts always use the invariant culture, which only recognizes . as the decimal mark - and ignores ,, which is considered a thousands-grouping character.

PS> [double] '1538651788,87456'
153865178887456  # !! , was IGNORED

Because the decimal mark was ignored and there are 5 decimal places, the resulting number is too large by a factor of 10,000 in this case (though note that the number of decimal places can vary, because trailing zeros aren't displayed).

If you then multiply that result with 1000, you get a number so large that PowerShell defaults its string representation to the scientific format that you've experienced:

PS> [double] '1538651788,87456' * 1000
1.53865178887456E+17 # !! scientific notation.

[1] Optional reading: Get-Date -UFormat %s problems in Windows PowerShell vs. PowerShell Core (v6+):

  • Unix timestamps are integers, so Get-Date -UFormat %s shouldn't return a floating-point number to begin with. This problem has been corrected in PowerShell Core.

  • Unix timestamps are expressed in UTC, but Windows PowerShell only returns the correct value if you explicitly pass a UTC [datetime] instance. This problem has been corrected in PowerShell Core (v6+).

    • E.g., to get the current time's Unix timestamp in Windows PowerShell, using
      Get-Date -UFormat %s is not enough; use Get-Date -UFormat %s ([datetime]::UtcNow) instead.

In short: This question's problem wouldn't have arisen in PowerShell Core, because string representations of integers aren't culture-sensitive; additionally, the need for rounding goes away, as does the need to convert the input date to UTC, so that a PowerShell Core solution simplifies to:

# PowerShell *Core* only 
1000 * (Get-Date -UFormat %s ((Get-Date).AddMinutes(-5)))

Note: This technically returns a [double] instance, but one without decimal places; use [long] (...) if an integer type is expressly needed.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

I use this small function to get the current date and time as Unix Timestamp. It returns an int64 so you should not have any trouble adding it in the url:

function Get-CurrentUnixTimeStamp {
    [DateTime]$epoch = New-Object System.DateTime 1970, 1, 1, 0, 0, 0, 0, Utc
    [TimeSpan]$diff  = (Get-Date).ToUniversalTime() - $epoch
    return [int64][Math]::Floor($diff.TotalSeconds)
}

$currentTime = Get-CurrentUnixTimeStamp
Invoke-RestMethod  -Method Delete -Uri "http://something?unmodifiedSince=$currentTime"
Theo
  • 57,719
  • 8
  • 24
  • 41
-1

You could indicate explicitly $currentTime variable data type as [Decimal] instead of auto assigned [Double]. Like so:

[Decimal]$currentTime = [math]::Round([double](get-date -uformat %s ((get-date).AddMinutes(-5).ToUniversalTime()))*1000)
henrycarteruk
  • 12,708
  • 2
  • 36
  • 40
Kirill Pashkov
  • 3,118
  • 1
  • 15
  • 20
  • This won't help, because the real problem is that the `[double]` cast misinterprets the culture-sensitive `Get-Date -UFormat %s` result in cultures that use `,` (comma) as the decimal separator. If the cast worked correctly, using the rounded `[double]` result as-is for stringification would work fine. – mklement0 Oct 04 '18 at 16:46