Показать полную графическую версию : Посчитать время с учётом рабочих часов
Всем привет!
Стоит задача посчитать сколько было затрачено времени в текущей задаче. В целом всё просто, использовал командлет 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 буднего дня, а оставшееся выкидывать
Для будних дней:
$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
ещё нужно как-то учесть производственный календарь... »
Для выходных и праздничных дней надо будет добавить условный блок с коэффициентом, учитывающим выходные... с праздничными - сложнее, придется составлять календарь с праздничными днями, учитывать год, день недели и если праздничный выпадает на будний день, то вычитать 24 часа... в общем, по аналогии составляйте алгоритм и реализуйте его в код... ничего сложного :)
где $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 понимал, что это уже стоп... :(
причём вот, есть живой пример сейчас: время начинается с 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, только надо описать это в условиях...
В общем, остальные возможные комбинации, предлагаю обдумывать самостоятельно...
Njem,
В общем, накидал тут пример реализации логики для будней:
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
Выходные и праздники не учитываются... попробуйте их реализовать, отталкиваясь от логики будней...
В общем, накидал тут пример реализации логики для будней: »
ВООУУ, ничего себе!!! господи, спасибо Вам большое!!
а я тут сижу второй день пытаясь математически всё это рассчитать) пока что логика тяжело даётся) думаю с этим намного проще будет!)
Выходные и праздники не учитываются... попробуйте их реализовать, отталкиваясь от логики будней... »
да я думаю выходные как-нить попытаюсь сделать, а-ля в какой нить csv запихнуть дни (выходные все), где он туда будет ссылаться с проверкой, мол если день есть, то -24 часа делать сходу... как-нить по такому принципу думал
сейчас всё посмотрю, спасибо ещё раз!!!) работа с числами всегда тяжело даётся для меня :(
а-ля в какой нить csv запихнуть дни (выходные все), где он туда будет ссылаться с проверкой, мол если день есть, то -24 часа делать сходу... как-нить по такому принципу думал »
В csv для выходных не надо, там есть начало реализации... надо учитывать номер дня недели и если старт/стоп выпадает на выходные, то формула будет изменяться, а также при переходе через выходные, т.е. для ситуации:
($x -gt 0 -and $x -lt 7) -and ($sdw -gt $edw) -and ($sdw -in $w) -and ($edw -in $w)
- выполнение условия будет означать переход через выходные, кроме того и старт, и стоп были осуществлены в будний день... только при реализации, надо учесть все нюансы (один или два выходных, момент старта и стопа и т.д.), в общем поле для размышлений есть...
А вот для праздников, возможно, придется использовать какой-либо список, тот же csv, либо прямо в скрипте его организовать...
выполнение условия будет означать переход через выходные, кроме того и старт, и стоп были осуществлены в будний день... только при реализации, надо учесть все нюансы (один или два выходных, момент старта и стопа и т.д.), в общем поле для размышлений есть... »
о как, понял, хорошо, тогда так и сделаю, спасибо ещё раз за помощь)
А вот для праздников, возможно, придется использовать какой-либо список, тот же csv, либо прямо в скрипте его организовать... »
а тут проще, в инете есть производственный календарь как раз на 2023 год. Его можно как csv, так и json формата скачать)
Njem, небольшая поправка
неправильно был указан номер воскресенья... должен был быть 0, т.е. 6 - суббота, 0 - воскресенье
в скрипте (http://forum.oszone.net/post-2999493.html#post2999493) исправил...
Ещё вариант, в котором поправлены ошибки и добавлен учет выходных:
# Пример работы со временем. Учет времени только в рабочее время (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
Ну и всё упрощает работа с производственным календарем... единственный минус - необходимо следить за наличием и содержанием самих календарей, собственно.
# Пример работы со временем. Учет времени только в рабочее время (например 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
Ещё вариант, в котором поправлены ошибки и добавлен учет выходных: »
ох.. ёмаё, спасибо!!!
помотрю, проверю
господи, ты просто бомба!)
© OSzone.net 2001-2012
vBulletin v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.