首页 > 解决方案 > 如何在没有科学记数法的情况下将长整数转换为字符串

问题描述

我有一个 Powershell 脚本,它获取当前时间并将其转换为 Unix 时间戳。我想将此值用作字符串参数来发出 HTTP 请求:

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

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

在某些 Windows 机器上它工作正常,但在其他一些(不同的区域设置?)上,我用科学记数法转换了当前时间。例如

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

如何避免这种转换?

标签: powershellunix-timestamp

解决方案


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.


推荐阅读