Как преобразовать длинную строку в строку без научного представления

У меня есть сценарий 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

Как избежать этого преобразования?


person nicolas    schedule 04.10.2018    source источник
comment
Что значит на каком-то другом?   -  person Jacob Colvin    schedule 04.10.2018
comment
@JacobColvin: Обратите внимание на разные настройки региона в скобках? пункт, и действительно региональные параметры являются источником проблемы.   -  person mklement0    schedule 04.10.2018
comment
То, как это сформулировано, заставило меня подумать, что, возможно, он запускал PowerShell на Linux или что-то в этом роде. @ mklement0   -  person Jacob Colvin    schedule 04.10.2018
comment
@JacobColvin: Понятно. В PowerShell Core на всех платформах, включая macOS и Linux, проблема на самом деле не возникнет (и код можно упростить) - см. Нижнюю часть моего ответа.   -  person mklement0    schedule 04.10.2018


Ответы (3)


tl; dr:

Чтобы обойти проблему с форматированием чисел, зависящим от языка и региональных параметров, используйте замену строки в Get-Date -UFormat результате, чтобы десятичный разделитель был ., что требуется для преобразования [double]:

[math]::Round([double] (
  (get-date -uformat %s ((get-date).AddMinutes(-5).ToUniversalTime())) -replace
    '[^\d]', '.') * 1000
 )

-replace '[^\d]', '.' заменяет нецифровые символы. с ., с единственным нецифровым символом. в выходных данных Get-Date -UFormat %s - десятичный знак, зависящий от языка и региональных параметров.


Действительно, ваша проблема связана с тем, что:

  • Get-Date -UFormat % выводит строковое представление временной метки Unix.
  • и использует форматирование с учетом языка и региональных параметров для базового числа с плавающей запятой [1], что означает, что в некоторых культурах вы получите строку, например '1538651788,87456' (, в качестве десятичный знак), а не '1538651788.87456' в качестве вывода.

Напротив, приведения PowerShell всегда используют инвариантный язык и региональные параметры, который только распознает . как десятичный знак и игнорирует ,, который считается символом группировки тысяч.

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

Поскольку десятичная метка была проигнорирована и имеется 5 десятичных знаков, результирующее число в этом случае будет слишком большим в 10 000 раз (хотя обратите внимание, что количество десятичных знаков может варьироваться, поскольку конечные нули не отображаются).

Если вы затем умножите этот результат на 1000, вы получите настолько большое число, что PowerShell по умолчанию использует его строковое представление в научном формате, который вы испытали:

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

[1] Дополнительная литература: Get-Date -UFormat %s проблемы в Windows PowerShell и PowerShell Core:

  • Метки времени Unix - это целые числа, поэтому Get-Date -UFormat %s не должен возвращать число с плавающей запятой для начала. Эта проблема исправлена ​​в PowerShell Core.

  • Временные метки Unix выражаются в формате UTC, но Windows PowerShell возвращает правильное значение, только если вы явно передаете экземпляр UTC [datetime]. Эта проблема исправлена ​​в PowerShell Core.

    • 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.

Вкратце: проблема этого вопроса не возникла бы в PowerShell Core, потому что строковое представление целых чисел не зависит от языка и региональных параметров; Кроме того, отпадает необходимость в округлении, как и в преобразовании даты ввода в UTC, так что решение PowerShell Core упрощается до:

# PowerShell *Core* only
[double](get-date -uformat %s ((get-date).AddMinutes(-5))) * 1000
person mklement0    schedule 04.10.2018

Я использую эту небольшую функцию, чтобы получить текущую дату и время как временную метку Unix. Он возвращает int64, поэтому у вас не должно возникнуть проблем с добавлением его в 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"
person Theo    schedule 04.10.2018

Вы можете явно указать $currentTime тип данных переменной как [Decimal] вместо автоматически назначенного [Double]. Вот так:

[Decimal]$currentTime = [math]::Round([double](get-date -uformat %s ((get-date).AddMinutes(-5).ToUniversalTime()))*1000)
person Kirill Pashkov    schedule 04.10.2018
comment
Это не поможет, потому что настоящая проблема заключается в том, что приведение [double] неверно интерпретирует результат, зависящий от языка и региональных параметров Get-Date -UFormat %s, в культурах, в которых используется , (запятая) в качестве десятичного разделителя. Если приведение сработало правильно, использование округленного [double] результата как есть для стрингификации будет работать нормально. - person mklement0; 04.10.2018