Добавить в корзинуПозвонить
Найти в Дзене

О разных тиках замолвите слово или как не получить ошибку в Powershell при работе с Get-Date

Утащил сюда статью из своего профиля на Хабре про один момент с Powershell.
Я на момент написания той статьи, заметил одну странность с лишним тактом выполнения команд в процессоре. Итак дело о лишнем тике:
В одной обработке для удобства выбора нужно было число дней до конца месяца.
Вычислялось это одной строкой такого вида: ((Get-date -Day 1).AddMonths(1)-(get-date)).days-1 Что характерно ее вычисление может давать разные результаты.
Сначала я подумал, что с кодом что-то не то, или версией Powershell. Проверил на нескольких машинках и понял, что ситуация воспроизводима. Поэтому сел дебажить и писать функцию. У меня получилось такое: Function Get-DaysToEndOfMonths([int]$Month=1)
{If($Month -lt 1){[int]$Month = 1}
$CurrentDate = get-date #Получаем текущую дату
$CurrentDay = $CurrentDate.day #И какой это день
$FirstDayCurrentMonths = (Get-date -Day 1) #Первый день текущего месяца
#$FirstDayNextMonths = $FirstDayCurrentMonths.AddMonths($Month) #Первый день иском

Утащил сюда статью из своего профиля на Хабре про один момент с Powershell.
Я на момент написания той статьи, заметил одну странность с лишним тактом выполнения команд в процессоре. Итак дело о лишнем тике:

В одной обработке для удобства выбора нужно было число дней до конца месяца.
Вычислялось это одной строкой такого вида:

((Get-date -Day 1).AddMonths(1)-(get-date)).days-1
так 28 или 29 ?
так 28 или 29 ?

Что характерно ее вычисление может давать разные результаты.
Сначала я подумал, что с кодом что-то не то, или версией Powershell. Проверил на нескольких машинках и понял, что ситуация воспроизводима. Поэтому сел дебажить и писать функцию. У меня получилось такое:

Function Get-DaysToEndOfMonths([int]$Month=1)
{
If($Month -lt 1){[int]$Month = 1}
$CurrentDate = get-date #Получаем текущую дату
$CurrentDay = $CurrentDate.day #И какой это день
$FirstDayCurrentMonths = (Get-date -Day 1) #Первый день текущего месяца
#$FirstDayNextMonths = $FirstDayCurrentMonths.AddMonths($Month) #Первый день искомого месяца
#Ага, так можно сразу отнять один день, чтобы получить последний день до искомого месяца
$LastDay = $FirstDayCurrentMonths.AddMonths($Month).AddDays(-1) #Последний день искомого месяца
$DaysToEndOfMonths = $($LastDay - $CurrentDate).Days #Сколько дней до искомого месяца
#Или через New-TimeSpan
#$NewTimeSpan = New-TimeSpan -Start $CurrentDate -End $LastDay
#$DaysToEndOfMonths = $NewTimeSpan.Days #Сколько дней
Write-debug "$DaysToEndOfMonths days to the end of the next $Month month"
}
Get-DaysToEndOfMonths(1)

Ну и после того как написал, сел еще подумать и пришел к такой строке:

[int]((((Get-Date -day 01).AddMonths(1)).AddDays(-1)).Day-(Get-date).Day)

Она уже не давала ошибку, но может, так как: Оказывается, что мы не учитываем час\минуты\секунды, а вернее тактовые тики процессора. Заметим, что, Get-date выдаёт значение в миллисекундах. Но если при выполнении вычислений первый и второй вызов Get-date пришёлся на один тик, то будут такие значения:

(Get-date -day 1).AddMonth(1) = 1.12.2019 15:33:00:500 Get-date = 1.11.2019 15:33:00:500

Вычтем и получим 30 00:00:00:000 Но если вызов второго Get-date выпадает на следующий тик, то его значение будет =>

1.11.2019 15:33:00:501

И тогда мы получим значение в

29 23:59:59:999

Теперь, когда проблема найдена, мы можем сделать так:

#Число дней до конца месяца ((((Get-Date -day 01).AddMonths(1)).AddDays(-1))-(Get-date -Hour 0 -Minute 0 -Second 0 -Millisecond 0)).Days

Или еще лучше вариант:

[datetime]::DaysInMonth((get-date).Year,(Get-Date).Month) - (get-date).day


И у нас всегда будет одинаковое значение.