Určení intervalu mezi dvěma časy v rozsahu pracovní doby

Z PostgreSQL
Skočit na navigaci Skočit na vyhledávání

Jednou z úloh s časem je určení reakční doby. Pokud je reakční doba počítaná bez omezení, tak se jedná o jednoduchou úlohu. Komplikace nastávají, když do reakční doby počítáme pouze čas omezený pracovní dobou. Pro výpočet mohu použít vzorec rc = start - konec - pocet_prac_dnu(start, konec)*abs(zacatek_prac_doby - konec_prac_doby) - pocet_svatku_a_dnu_prac_volna*24. Určení počtu pracovních dnů je řešeno v článku PL/pgSQL - funkce generující tabulky.

-- vraci prvni pracovni okamzik po zadanem okammziku vcetne                                     
CREATE OR REPLACE FUNCTION FirstWTimestamp(ts timestamp, start_tm time)
RETURNS timestamp AS $$
DECLARE
  holidays date[];
  aux_day date;
BEGIN
  aux_day := CAST(ts AS date);
  holidays := ARRAY(SELECT termin FROM StatniSvatky(aux_day, aux_day + 10));
  IF NOT (isodow(aux_day) IN (6,7) OR aux_day = ANY(holidays)) THEN
    RETURN ts; -- nedochazi k posunu, vraci puvodni cas                                   
  END IF;
  LOOP
    aux_day := aux_day + 1;
    EXIT WHEN NOT (isodow(aux_day) IN (6,7) OR aux_day = ANY(holidays));
  END LOOP;
  RETURN aux_day + start_tm;
END;
$$ LANGUAGE plpgsql STABLE STRICT;

-- vraci posledni pracovni okamzik pred zadanym okamzikem vcetne                          
CREATE OR REPLACE FUNCTION LastWTimestamp(ts timestamp, end_tm time)
RETURNS timestamp AS $$
DECLARE
  holidays date[];
  aux_day date;
BEGIN
  aux_day := CAST(ts AS date);
  holidays := ARRAY(SELECT termin FROM StatniSvatky(aux_day, aux_day + 10));
  IF NOT (isodow(aux_day) IN (6,7) OR aux_day = ANY(holidays)) THEN
    RETURN ts; -- nedochazi k posunu, vraci puvodni cas                                   
  END IF;
  LOOP
    aux_day := aux_day - 1;
    EXIT WHEN NOT (isodow(aux_day) IN (6,7) OR aux_day = ANY(holidays));
  END LOOP;
  RETURN aux_day + end_tm;
END;
$$ LANGUAGE plpgsql STABLE STRICT;

CREATE OR REPLACE FUNCTION DiffWTime(start_ts timestamp, end_ts timestamp,
                                              start_wt time, end_wt time)
RETURNS interval AS $$
DECLARE
  nwh interval := justify_interval(start_wt - end_wt + interval '1day');
  _start_ts timestamp := FirstWTimestamp(start_ts, start_wt);
  _end_ts timestamp := LastWTimestamp(end_ts, end_wt);
  svatky date[]; _start_d date; _end_d date;
  result interval;
BEGIN
  -- jednoduchy algoritmus, nepredpokladam prilis velky pocet cyklu                       
  _start_d := CAST(_start_ts AS date); _end_d := CAST(_end_ts AS date);
  svatky := ARRAY(SELECT termin FROM StatniSvatky(_start_d, _end_d));
  result := _end_ts - _start_ts;
  WHILE _start_d <> _end_d LOOP
    IF isodow(_start_d) IN (6,7) OR _start_d = ANY(svatky) THEN
      result := result - interval '24hours';
    ELSE
      result := result - nwh;
    END IF;
    _start_d := _start_d + 1;
  END LOOP;
  -- prepocet na hodiny                                                                   
  RETURN EXTRACT (epoch FROM justify_interval(result)) / 3600.0 * interval '1hour';
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT; 

Použití:

postgres=# select diffwtime(current_timestamp::timestamp+interval '2hours', current_timestamp::timestamp + interval '1day', '8:00:00','16:30:00');
 diffwtime
-----------
 06:30:00
(1 row)

V praxi se mi osvědčilo pro hodnoty související s pracovní dobou používat vždy timestamp a nikoliv date - právě z důvodu korektního a jednoduchého výpočtu reakční doby.