Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Скриптовые языки администрирования Windows (http://forum.oszone.net/forumdisplay.php?f=102)
-   -   Посчитать время с учётом рабочих часов (http://forum.oszone.net/showthread.php?t=352634)

Njem 21-12-2022 12:22 2999391

Посчитать время с учётом рабочих часов
 
Вложений: 1
Всем привет!

Стоит задача посчитать сколько было затрачено времени в текущей задаче. В целом всё просто, использовал командлет Timespan, взял время старта, время конца, сложил и получил ответ:

Код:

$FIRST_RESPONSE_TIME = New-TimeSpan -Start "$START_TIME_TICKET" -End "$END_TIME_TICKET"
Во вложении вывод.

Однако, есть нюанс, который заключается в SLA. Время останавливается на нерабочих часах. То есть должно на 19:00 время остановиться и в 9:00 следующего дня утром снова возобновиться. По итогу вывод должен быть не на 2 часа 17 минут, а просто на 1 час 32 минуты. Следовательно, если время будет начинаться, скажем с 20:00 часов, то считать он должен с 9:00 и до времени, когда будет $END_TIME_TICKET (например конечная дата 12:00 и того 3 часа итог, а не 16 часов, как он посчитается всё время).

И вот как посчитать время с учётом рабочих часов, вообще никаких идей нет, может у кого будут мысли как это сделать.

Заранее спасибо!!!

p.s. а и да, ещё нужно как-то учесть производственный календарь... в общем время должно считать строго в пороге от 9:00 до 19:00 буднего дня, а оставшееся выкидывать

YuS_2 21-12-2022 13:12 2999395

Для будних дней:
Код:

$k = new-timespan -h 14
if (($x = $END_TIME_TICKET.day - $START_TIME_TICKET.day)){
    $END_TIME_TICKET - $START_TIME_TICKET - $x*$k
} else {
    $END_TIME_TICKET - $START_TIME_TICKET
}

где $x - разница дней, $k - период с 19:00 до 9:00

Цитата:

Цитата Njem
ещё нужно как-то учесть производственный календарь... »

Для выходных и праздничных дней надо будет добавить условный блок с коэффициентом, учитывающим выходные... с праздничными - сложнее, придется составлять календарь с праздничными днями, учитывать год, день недели и если праздничный выпадает на будний день, то вычитать 24 часа... в общем, по аналогии составляйте алгоритм и реализуйте его в код... ничего сложного :)

Njem 21-12-2022 13:47 2999401

Цитата:

Цитата YuS_2
где $x - разница дней, $k - период с 19:00 до 9:00 »

хм... то есть получается $k - период, который, он не явно с 19:00 до 9:00, а просто делается всегда отнимание 14 часов, если $END_TIME_TICKET +1 день от $START_TIME_TICKET

а если время будет начинаться с 7:00 утра, он ведь всё равно посчитает его с 7:00, а не с 9:00 как должен((

p.s. причём вот, есть живой пример сейчас: время начинается с 16:37, а заканчивается на следующий день в 19:37. Считает 13 часов, так как отнимает 14 часов, НО, 37 минут же идут вне рабочего временни, значит должно быть 12 часов 23 минуты. И вот как сделать, чтобы он когда достигал 19:00 понимал, что это уже стоп... :(

YuS_2 21-12-2022 17:12 2999425

Цитата:

Цитата Njem
причём вот, есть живой пример сейчас: время начинается с 16:37, а заканчивается на следующий день в 19:37. Считает 13 часов, так как отнимает 14 часов, НО, 37 минут же идут вне рабочего временни, значит должно быть 12 часов 23 минуты. И вот как сделать, чтобы он когда достигал 19:00 понимал, что это уже стоп... »

Научитесь составлять словесный алгоритм, в котором будут учитываться все возможные ситуации, после этого реализуйте его в код...
Пример для одного дня, без перехода на новую дату:
Код:

$start = new-timespan -h 9
$end = new-timespan -h 19
$k = new-timespan -h 14
$time = new-timespan

if (!($x = $END_TIME_TICKET.day - $START_TIME_TICKET.day)){
        if($END_TIME_TICKET.timeofday.totalhours -ge $end.totalhours -and $START_TIME_TICKET.timeofday.totalhours -lt $start.totalhours){
                $time = $end - $start
        } elseif ($END_TIME_TICKET.timeofday.totalhours -ge $end.totalhours -and $START_TIME_TICKET.timeofday.totalhours -ge $start.totalhours){
                $time = $end - $START_TIME_TICKET.timeofday
        } elseif ($END_TIME_TICKET.timeofday.totalhours -le $end.totalhours -and $START_TIME_TICKET.timeofday.totalhours -lt $start.totalhours){
                $time = $END_TIME_TICKET.timeofday - $start
        } elseif ($END_TIME_TICKET.timeofday.totalhours -le $end.totalhours -and $START_TIME_TICKET.timeofday.totalhours -ge $start.totalhours) {
                $time = $END_TIME_TICKET.timeofday - $START_TIME_TICKET.timeofday
        }
}
$time

- Здесь не учтена ситуация, когда и старт, и стоп, до начала рабочего дня, либо оба после окончания, но в этом случае, $time будет 0, только надо описать это в условиях...
В общем, остальные возможные комбинации, предлагаю обдумывать самостоятельно...

YuS_2 22-12-2022 13:03 2999493

Вложений: 1
Njem,
В общем, накидал тут пример реализации логики для будней:
script.ps1
Код:

param (
        [datetime]$stime = $(get-date("21.12.2022 8:00")),
        [datetime]$etime = $(get-date("22.12.2022 8:00")),
        [timespan]$startwork = $(new-timespan -h 9),
        [timespan]$endwork = $(new-timespan -h 19),
        [timespan]$fullwork = $(new-timespan -h 10)
)
function get-timework {
        param (
                [datetime]$stt, # старт
                [datetime]$ett, # стоп
                [timespan]$start, # начало рабочего периода
                [timespan]$end , # конец рабочего периода
                [timespan]$m # полный рабочий период
        )
        $x = $ett.day - $stt.day # количество дней между стартом и стопом
        $w, $h = @(1,2,3,4,5), @(6,0) # будни, выходные
        $sdw,$edw = $stt.dayofweek.value__,$ett.dayofweek.value__ # номер дня недели для старт и стоп
        # укорачиваем код до названия переменных:
        $rs,$re = $stt.timeofday.totalminutes,$ett.timeofday.totalminutes
        $s,$e = $start.totalminutes,$end.totalminutes
        $minutes = $m.totalminutes
        # Реализация логики подсчета для будней:
        if (!$x -and $re -le $rs) {
                write-host Ошибка! Проверьте данные '$stt - старт/ $ett - стоп' -for red
                break
        }
        if (!$x -and $sdw -notin $h -and $edw -notin $h -and $re -gt $rs){
                if($rs -lt $s -and $re -gt $e){
                        $time = new-timespan -min ($minutes)
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -gt $e){
                        $time = new-timespan -min ($e - $rs)
                } elseif (($rs -gt $e -and $re -gt $e) -or ($rs -lt $s -and $re -lt $s)){
                        $time = new-timespan
                } elseif ($rs -lt $s -and ($re -ge $s -and $re -le $e)) {
                        $time = new-timespan -min ($re - $s)
                } elseif (($rs -ge $s -and $rs -le $e) -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $rs)
                }
        } elseif ($x -and $sdw -notin $h -and $edw -notin $h) {
                if ($rs -lt $s -and $re -gt $e){
                        $time = new-timespan -min ($minutes * ($x+1))
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -gt $e){
                        $time = new-timespan -min ($e - $rs + $minutes*$x)
                } elseif (($rs -gt $e -and $re -gt $e) -or ($rs -lt $s -and $re -lt $s)){
                        $time = new-timespan -min ($minutes * $x)
                } elseif ($rs -lt $s -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $s + $minutes*$x)
                } elseif (($rs -ge $s -and $rs -le $e) -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $s + $e - $rs + $minutes*($x-1))
                } elseif ($rs -gt $e -and ($re -ge $s -and $re -le $e)) {
                        $time = new-timespan -min ($re - $s + $minutes*($x-1))
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -lt $s){
                        $time = new-timespan -min ($e - $rs + $minutes*($x-1))
                } elseif ($rs -gt $e -and $re -lt $s) {
                        $time = new-timespan -min ($minutes*($x-1))
                }
        } else {
                write-host Условия не сработали... для будущей разработки. -for cyan
        }
        return $time
}

get-timework -stt $stime -ett $etime -start $startwork -end $endwork -m $fullwork



Пример запуска:
Код:

.\script.ps1 -stime $(get-date("21.12.2022 19:50")) -etime $(get-date("22.12.2022 17:00"))
Для ориентирования, как реализуется логика, приведу табличку:
Файл 167728

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

Njem 22-12-2022 13:19 2999495

Цитата:

Цитата YuS_2
В общем, накидал тут пример реализации логики для будней: »

ВООУУ, ничего себе!!! господи, спасибо Вам большое!!
а я тут сижу второй день пытаясь математически всё это рассчитать) пока что логика тяжело даётся) думаю с этим намного проще будет!)

Цитата:

Цитата YuS_2
Выходные и праздники не учитываются... попробуйте их реализовать, отталкиваясь от логики будней... »

да я думаю выходные как-нить попытаюсь сделать, а-ля в какой нить csv запихнуть дни (выходные все), где он туда будет ссылаться с проверкой, мол если день есть, то -24 часа делать сходу... как-нить по такому принципу думал

сейчас всё посмотрю, спасибо ещё раз!!!) работа с числами всегда тяжело даётся для меня :(

YuS_2 22-12-2022 13:36 2999497

Цитата:

Цитата Njem
а-ля в какой нить csv запихнуть дни (выходные все), где он туда будет ссылаться с проверкой, мол если день есть, то -24 часа делать сходу... как-нить по такому принципу думал »

В csv для выходных не надо, там есть начало реализации... надо учитывать номер дня недели и если старт/стоп выпадает на выходные, то формула будет изменяться, а также при переходе через выходные, т.е. для ситуации:
Код:

($x -gt 0 -and $x -lt 7) -and ($sdw -gt $edw) -and ($sdw -in $w) -and ($edw -in $w)
- выполнение условия будет означать переход через выходные, кроме того и старт, и стоп были осуществлены в будний день... только при реализации, надо учесть все нюансы (один или два выходных, момент старта и стопа и т.д.), в общем поле для размышлений есть...
А вот для праздников, возможно, придется использовать какой-либо список, тот же csv, либо прямо в скрипте его организовать...

Njem 22-12-2022 13:40 2999500

Цитата:

Цитата YuS_2
выполнение условия будет означать переход через выходные, кроме того и старт, и стоп были осуществлены в будний день... только при реализации, надо учесть все нюансы (один или два выходных, момент старта и стопа и т.д.), в общем поле для размышлений есть... »

о как, понял, хорошо, тогда так и сделаю, спасибо ещё раз за помощь)

Цитата:

Цитата YuS_2
А вот для праздников, возможно, придется использовать какой-либо список, тот же csv, либо прямо в скрипте его организовать... »

а тут проще, в инете есть производственный календарь как раз на 2023 год. Его можно как csv, так и json формата скачать)

YuS_2 22-12-2022 14:44 2999513

Njem, небольшая поправка
неправильно был указан номер воскресенья... должен был быть 0, т.е. 6 - суббота, 0 - воскресенье
в скрипте исправил...

YuS_2 23-12-2022 18:58 2999636

Ещё вариант, в котором поправлены ошибки и добавлен учет выходных:
script.ps1
Код:

# Пример работы со временем. Учет времени только в рабочее время (9:00 - 19:00),
# исключая выходные (суб., вск.).
# .\script.ps1 -stime $(get-date("21.12.2022 19:50")) -etime $(get-date("22.12.2022 17:00"))
param (
        [datetime]$stime = $(get-date("21.12.2022 8:00")),
        [datetime]$etime = $(get-date("22.12.2022 8:00")),
        [timespan]$startwork = $(new-timespan -h 9 -min 0),
        [timespan]$endwork = $(new-timespan -h 19 -min 0)
)
function get-timework {
        param (
                [datetime]$stt, # старт
                [datetime]$ett, # стоп
                [timespan]$start, # начало рабочего периода
                [timespan]$end # конец рабочего периода
        )
        function get-weekend {
                param (
                        [datetime]$sd,
                        [datetime]$ed,
                        [int[]]$weekend = @(6,0)
                )
                $sub = ($ed.date - $sd.date).days
                $arr = @()
                for ($i = 0; $i -le $sub;$i++){
                        #формируем массив выходных за период, для дальнейшего подсчета пар
                        if ($sd.adddays($i).dayofweek.value__ -in $weekend){
                                $arr += $sd.adddays($i).dayofweek.value__
                        }
                }
                if ($arr){
                        $out = ($arr.where({$_ -eq $weekend[0]},5)|%{$_.count}|sort -d)[0]
                } else {$out = 0}
                return $out
        }
        if ($ett -le $stt) {
                write-host Ошибка! Проверьте данные '$stt - старт/ $ett - стоп' -for red
                break
        }
        $m = new-timespan -h ($end - $start).totalhours
        $x = ($ett.date - $stt.date).days # количество дней между стартом и стопом
        $w, $h = @(1,2,3,4,5), @(6,0) # будни, выходные (суббота и воскресенье)
        # укорачиваем код до названия переменных:
        $sdw,$edw = $stt.dayofweek.value__,$ett.dayofweek.value__ # номер дня недели для старт и стоп
        $rs,$re = $stt.timeofday.totalminutes,$ett.timeofday.totalminutes
        $s,$e = $start.totalminutes,$end.totalminutes
        $minutes = $m.totalminutes

        # Для выходных:
        if ($edw -in $h){
                # Если стоп в выходной день (перенос стопа на пон. 0:00)
                if (!$edw){$ds = 1} else {$ds = 2}
                $ett = $ett.addminutes(-$ett.timeofday.totalminutes)
                $ett = $ett.adddays($ds)
                $edw,$re = $ett.dayofweek.value__,$ett.timeofday.totalminutes
                $x = ($ett.date - $stt.date).days
        }
        if ($sdw -in $h){
                # Если старт в выходной день (перенос старта на пон. 0:00)
                if (!$sdw){$ds = 1} else {$ds = 2}
                $stt = $stt.addminutes(-$stt.timeofday.totalminutes)
                $stt = $stt.adddays($ds)
                $sdw,$rs = $stt.dayofweek.value__,$stt.timeofday.totalminutes
                $x = ($ett.date - $stt.date).days
        }
        $kw = get-weekend $stt $ett # количество пар выходных между стартом и стопом
        if ($stt.dayofweek.value__ -in $h -and $kw){$kw--}
        if ($x -and $sdw -in $w -and $edw -in $w){
                # Если есть полный переход через выходные,
                # старт/стоп в рабочие дни, вычитаем выходные полностью:
                $x = $x - 2*$kw
        }

        # Реализация логики подсчета для будней:
        if (!$x -and $sdw -in $w -and $edw -in $w -and $re -ge $rs){
                # Если и старт, и стоп в один день
                if($rs -lt $s -and $re -gt $e){
                        $time = new-timespan -min ($minutes)
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -gt $e){
                        $time = new-timespan -min ($e - $rs)
                } elseif (($rs -gt $e -and $re -gt $e) -or ($rs -lt $s -and $re -lt $s)){
                        $time = new-timespan
                } elseif ($rs -lt $s -and ($re -ge $s -and $re -le $e)) {
                        $time = new-timespan -min ($re - $s)
                } elseif (($rs -ge $s -and $rs -le $e) -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $rs)
                }
        } elseif ($x -and $sdw -in $w -and $edw -in $w) {
                # Если старт и стоп в разные дни,
                if ($rs -lt $s -and $re -gt $e){
                        $time = new-timespan -min ($minutes * ($x+1))
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -gt $e){
                        $time = new-timespan -min ($e - $rs + $minutes*$x)
                } elseif (($rs -gt $e -and $re -gt $e) -or ($rs -lt $s -and $re -lt $s)){
                        $time = new-timespan -min ($minutes * $x)
                } elseif ($rs -lt $s -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $s + $minutes*$x)
                } elseif (($rs -ge $s -and $rs -le $e) -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $s + $e - $rs + $minutes*($x-1))
                } elseif ($rs -gt $e -and ($re -ge $s -and $re -le $e)) {
                        $time = new-timespan -min ($re - $s + $minutes*($x-1))
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -lt $s){
                        $time = new-timespan -min ($e - $rs + $minutes*($x-1))
                } elseif ($rs -gt $e -and $re -lt $s) {
                        $time = new-timespan -min ($minutes*($x-1))
                }
        } else {
                write-host Условия не сработали... для будущей разработки. -for cyan
        }
        return $time
}

get-timework -stt $stime -ett $etime -start $startwork -end $endwork


YuS_2 24-12-2022 14:40 2999665

Ну и всё упрощает работа с производственным календарем... единственный минус - необходимо следить за наличием и содержанием самих календарей, собственно.
script_calendar.ps1
Код:

# Пример работы со временем. Учет времени только в рабочее время (например 9:00 - 19:00),
# исключая выходные и праздничные дни по производственному календарю.
# в данном варианте использовались календари в текстовом виде, со списком выходных и праздничных
# дней. (http://xmlcalendar.ru/data/ru/2023/calendar.txt) (http://xmlcalendar.ru/)
# .\script_calendar.ps1 -stime $(get-date("21.12.2022 19:50")) -etime $(get-date("22.12.2022 17:00"))
param (
        [datetime]$stime = $(get-date("21.12.2022 8:00")),
        [datetime]$etime = $(get-date("22.12.2022 8:00")),
        [timespan]$startwork = $(new-timespan -h 9 -min 0),
        [timespan]$endwork = $(new-timespan -h 19 -min 0),
        [string]$calendarpath = '.\Calendars\RUS'
)
function get-timework {
        param (
                [datetime]$stt, # старт
                [datetime]$ett, # стоп
                [timespan]$start, # начало рабочего периода
                [timespan]$end, # конец рабочего периода
                # Каталог календарей в виде текстового списка (*.txt) с датами формата YYYY.MM.DD
                [string]$pathcr
        )
        if ($ett -le $stt) {
                write-host Ошибка! Проверьте данные '$stt - старт/ $ett - стоп' -for red
                break
        }
        $x = ($ett.date - $stt.date).days # количество дней между стартом и стопом
        # Считываем производственные календари (списки выходных и праздничных дней)
        $cal = gc ($pathcr + '\' + '*.txt')|%{get-date($_)}
        if ($stt.year -notin $cal.year -or $ett.year -notin $cal.year){
                write-host Отсутствует производственный календарь на год начала или конца периода! -for red
                break
        }
        $m = new-timespan -h ($end - $start).totalhours # Вычисляем рабочее время за день
        # укорачиваем код до названия переменных:
        $rs,$re = $stt.timeofday.totalminutes,$ett.timeofday.totalminutes
        $s,$e = $start.totalminutes,$end.totalminutes
        $minutes = $m.totalminutes
        # Если старт в вых. или праздник, перенос на ближайший р.день в большую сторону
        # и время обнуляется
        if ($x -and $stt.date -in $cal){
                $stt = $stt.addminutes(-$stt.timeofday.totalminutes)
                while ($stt.date -in $cal){
                        $stt = $stt.adddays(1)
                }
        }
        # Если стоп в вых. или праздник, перенос на ближайший р.день в большую сторону
        # и время обнуляется
        if ($x -and $ett.date -in $cal){
                $ett = $ett.addminutes(-$ett.timeofday.totalminutes)
                while ($ett.date -in $cal){
                        $ett = $ett.adddays(1)
                }
        }
        # переинициализация старта и стопа
        $rs,$re = $stt.timeofday.totalminutes,$ett.timeofday.totalminutes
        $x = ($ett.date - $stt.date).days # пересчет дней между стартом и стопом
        $j = 0
        for ($i = 0; $i -le $x;$i++){
                #счетчик вых. и празд. по календарю
                if ($stt.adddays($i).date -in $cal){
                        $j++
                }
        }
        # Вычисляем окончательное количество рабочих дней
        $x = $x - $j
        # Реализация логики подсчета для будней:
        if (!$x -and $re -ge $rs){
                # Если и старт, и стоп в один день
                if($rs -lt $s -and $re -gt $e){
                        $time = new-timespan -min ($minutes)
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -gt $e){
                        $time = new-timespan -min ($e - $rs)
                } elseif (($rs -gt $e -and $re -gt $e) -or ($rs -lt $s -and $re -lt $s)){
                        $time = new-timespan
                } elseif ($rs -lt $s -and ($re -ge $s -and $re -le $e)) {
                        $time = new-timespan -min ($re - $s)
                } elseif (($rs -ge $s -and $rs -le $e) -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $rs)
                }
        } elseif ($x) {
                # Если старт и стоп в разные дни,
                if ($rs -lt $s -and $re -gt $e){
                        $time = new-timespan -min ($minutes * ($x+1))
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -gt $e){
                        $time = new-timespan -min ($e - $rs + $minutes*$x)
                } elseif (($rs -gt $e -and $re -gt $e) -or ($rs -lt $s -and $re -lt $s)){
                        $time = new-timespan -min ($minutes * $x)
                } elseif ($rs -lt $s -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $s + $minutes*$x)
                } elseif (($rs -ge $s -and $rs -le $e) -and ($re -ge $s -and $re -le $e)){
                        $time = new-timespan -min ($re - $s + $e - $rs + $minutes*($x-1))
                } elseif ($rs -gt $e -and ($re -ge $s -and $re -le $e)) {
                        $time = new-timespan -min ($re - $s + $minutes*($x-1))
                } elseif (($rs -ge $s -and $rs -le $e) -and $re -lt $s){
                        $time = new-timespan -min ($e - $rs + $minutes*($x-1))
                } elseif ($rs -gt $e -and $re -lt $s) {
                        $time = new-timespan -min ($minutes*($x-1))
                }
        } else {
                write-host Условия не сработали... для будущей разработки. -for cyan
        }
        return $time
}

get-timework -stt $stime -ett $etime -start $startwork -end $endwork -pathcr $calendarpath


Njem 27-12-2022 13:45 2999819

Цитата:

Цитата YuS_2
Ещё вариант, в котором поправлены ошибки и добавлен учет выходных: »

ох.. ёмаё, спасибо!!!
помотрю, проверю

господи, ты просто бомба!)


Время: 03:38.

Время: 03:38.
© OSzone.net 2001-