О разных тиках замолвите слово или как не получить ошибку в 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) #Первый день искомого месяца
       #Ага, так можно сразу отнять один день, чтобы получить последний день до искомого месяца
        $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

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

    Будьте внимательны и хороших выходных!

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 8

      +3
      Каждый раз, когда вы запрашиваете текущую дату (Get-Date, DateTime.Now, etc), вы получите дату на момент выполнения команды. А если у вас таких команд Get-Date много разбросано по скрипту, то, конечно, рано или поздно произойдет так, что они выдадут разные даты. Поэтому есть простой best practice при работе с датами: сохраните текущее значение Get-Date в переменную и потом работайте только с этой переменной.
        0
        Собственно в функции это и сделал. А код из которого получилась статья получился по принципу, добавить быстро, для удобства. Но сегодня что-то зацепило, и решил подумать.
        Просто не сразу осознается, что запрос даты на коротких дистанциях в коде может привести к таким большим расхождениям. )
        0
        тут уже была статья на тему как работать с датами. Суть та же — берите текущую дату один раз и с ней работайте
          0
          Статья не только о работе с датами.
          И как итог есть решение, где можно брать дату более одного раза, но с занулением всех значений, во избежание ошибок.
            0

            какой-то совсем костыль получается

          +1
          Да, согласен — с датами и временем в ПоШе надо быть аккуратными. Очень.

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

            А то. Ведь если код будет выполняться в 23:59:59.999, то Get-Date могут попасть на разные даты. Так рождаются глюки.

            0
            Get-Date берет день только 1 раз.
            Вот если, последний день месяца и 23:59:59.999… Тут да, некрасиво получится — ожидается получить 0, а код выдаст или 30 или 29…

            Тогда просто сохраняем в переменную и работаем!

            Only users with full accounts can post comments. Log in, please.