Kirjoittaja: Metabolix
Kirjoitettu: 30.07.2017 – 30.07.2017
Tagit: yhteiskunta, koodi näytille, vinkki
Seuraava koodi esittelee laskutavan sille, paljonko jostakin aikavälistä on tiettyjen kellonaikojen välissä, esimerkiksi paljonko työvuorosta on yötyötä. Koodissa käytetään PHP:n funktiota strtotime, jolla voidaan helposti lisätä aikaleimaan päiviä niin, että jopa kesäajan vaihtuessa kellonaika pysyy samana. Näin on helppo käydä läpi silmukassa kaikki tutkittavalle aikavälille kuuluvat päivämäärät ja siis määrätyt kellonajat kaikkina päivinä.
Jos tiedetään vain kellonajat, voidaan valita laskuja varten mielivaltainen päivämäärä. Pelkkien kellonaikojen käyttö tosielämän tilanteissa on ongelmallista, koska kesäajan vaihtumista ei voida huomioida ja laskuihin tulee siis silloin tunnin virhe. Siksi oikeissa ohjelmissa kannattaakin pyrkiä mahdollisimman tarkkoihin tietoihin päivämäärineen.
Mukana on funktio myös vain tietyn viikonpäivän tarkastamiseen. Tämä toimii samalla idealla kuin kellonaikojen tarkastus muutenkin, mutta käytännössä edetäänkin viikko kerrallaan.
Monimutkaisemmat ehdot on usein helpointa tehdä jakamalla tarkastus osiin. Esimerkiksi lauantain ja sunnuntain välisen yötyön määrän voi laskea siten, että laskee erikseen lauantain ja sunnuntain puolella olevan osan työstä.
Arkipyhien ja muiden yksittäisten päivien tutkimiseen kannattaa laatia selvä lista näistä päivistä (päivämäärineen) ja käydä kyseinen lista läpi silmukassa.
// Funktio laskee, paljonko kahdesta lukuvälistä menee päällekkäin. // Otetaan siis aikaisemman loppukohdan ja myöhäisemmän alkukohdan erotus, // ja jos tulos on negatiivinen, se muutetaan nollaksi. function päällekkäin_int($a_alku, $a_loppu, $b_alku, $b_loppu) { return max(0, min($b_loppu, $a_loppu) - max($a_alku, $b_alku)); } // Funktio laskee, paljonko aikavälistä on tiettyjen kellonaikojen välissä. // Aikaväli tulee antaa UNIX-aikaleimoina (time, strtotime, mktime). // Kellonajat tulee antaa muodossa HH:MM (tai HH:MM:SS). function päällekkäin_int_klo($a_alku, $a_loppu, $b_alku_klo, $b_loppu_klo) { // Jos alkuaika on myöhempi kuin loppuaika (esim. klo 22 – klo 07), // siirretään alku aiemmalle vuorokaudelle (ikään kuin klo -2 – klo 7). if ($b_alku_klo >= $b_loppu_klo) { $b_alku_klo .= " - 1 day"; } // Käydään silmukassa aikaväliin kuuluvat vuorokaudet läpi. $pvm = date("Y-m-d", $a_alku); $päiviä = intval(($a_loppu - $a_alku) / 86400 + 1 + 1/24); $summa = 0; for ($i = 0; $i <= $päiviä; ++$i) { // Lasketaan $i. vuorokauden tutkittavat kellonajat. $i_alku = strtotime("{$pvm} {$b_alku_klo} + {$i} day"); $i_loppu = strtotime("{$pvm} {$b_loppu_klo} + {$i} day"); // Lisätään summaan tämän vuorokauden päällekkäinen aika. $summa += päällekkäin_int($a_alku, $a_loppu, $i_alku, $i_loppu); } return $summa; } // Funktio laskee, paljonko annetut kellonaikavälit osuvat päällekkäin. // Kellonajat tulee antaa muodossa HH:MM (tai HH:MM:SS). // HUOMIO! Koska päivämäärää ei tiedetä, kesäajan alkua ja loppua ei huomioida. function päällekkäin_klo_klo($a_alku_klo, $a_loppu_klo, $b_alku_klo, $b_loppu_klo) { // Valitaan jokin päivämäärä, jona kesäaikaan siirtyminen ei sekoita. $päivämäärä = "2017-05-01"; // Kellonajoista ja päivämäärästä lasketaan varsinaiset ajat. $a_alku = strtotime("{$päivämäärä} {$a_alku_klo}"); $a_loppu = strtotime("{$päivämäärä} {$a_loppu_klo}"); // Jos kellonajan mukaan alku on ennen loppua (kuten klo 22 – klo 07), // siirretään loppu seuraavan vuorokauden puolelle. if ($a_alku >= $a_loppu) { $a_loppu = strtotime("{$päivämäärä} {$a_loppu_klo} + 1 day"); } // Voidaan laskea loput edellisellä funktiolla. return päällekkäin_int_klo($a_alku, $a_loppu, $b_alku_klo, $b_loppu_klo); } // Funktio laskee, paljonko aikavälistä on tietyllä viikonpäivällä. // Aikaväli annetaan UNIX-aikaleimoina (strtotime tai vastaava). // Viikonpäivä annetaan englanniksi. // Kellonajatkin voi antaa (HH:MM); oletuksena on koko vuorokausi (klo 00–24). function päällekkäin_int_viikonpäivä($a_alku, $a_loppu, $viikonpäivä, $b_alku_klo = "00:00", $b_loppu_klo = "24:00") { // Tarkastetaan, että viikonpäivä on oikein kirjoitettu. if (!in_array($viikonpäivä, array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'))) { throw new Exception("Viikonpäivä {$viikonpäivä} ei kelpaa!"); } // Aloitetetaan eilisestä. $b_alku = strtotime("yesterday", $a_alku); $summa = 0; while (true) { // Etsitään seuraava sopiva viikonpäivä ja kellonaika. $b_alku = strtotime("next {$viikonpäivä}", $b_alku); $b_alku = strtotime($b_alku_klo, $b_alku); // Lopetetaan, jos viikonpäivä on aikavälin ulkopuolella. if ($b_alku > $a_loppu) { return $summa; } // Etsitään loppuaika. $b_loppu = strtotime($b_loppu_klo, $b_alku); $summa += päällekkäin_int($a_alku, $a_loppu, $b_alku, $b_loppu); } }
// Yön määritelmä: klo 22 – klo 07. $yö_alku_klo = "22:00"; $yö_loppu_klo = "07:00"; // Työvuoron ajankohta. $vuoro_alku = strtotime("2017-07-28 19:00"); $vuoro_loppu = strtotime("2017-07-29 06:00"); // Laskeminen ja tuloksen näyttö: $vuoro_yö = päällekkäin_int_klo($vuoro_alku, $vuoro_loppu, $yö_alku_klo, $yö_loppu_klo); echo "Vuoron alku: ", date("Y-m-d H:i:s", $vuoro_alku), "\n"; echo "Vuoron loppu: ", date("Y-m-d H:i:s", $vuoro_loppu), "\n"; echo "Yötunteja: ", ($vuoro_yö / 3600), "\n";
// Kuvitellaan työvuoro klo 19–06. Paljonko on yötyötä (22–07)? $vuoro_yö = päällekkäin_klo_klo("19:00", "06:00", "22:00", "07:00"); echo "Yötunteja: ", ($vuoro_yö / 3600), "\n";
// Montako sunnuntaituntia oli maaliskuussa 2017? // (Vastaus: 95, eli 4 vuorokautta miinus kesäaikaan siirtymisen tunti.) $alku = strtotime("2017-03-01 00:00"); $loppu = strtotime("2017-03-31 24:00"); echo "sunnuntaitunnit vuoden 2017 maaliskuussa:\n"; echo päällekkäin_int_viikonpäivä($alku, $loppu, "sunday") / 3600, " tuntia\n"; echo päällekkäin_int_viikonpäivä($alku, $loppu, "sunday", "02:00", "03:00") / 3600, " tuntia klo 02–03\n"; echo päällekkäin_int_viikonpäivä($alku, $loppu, "sunday", "03:00", "04:00") / 3600, " tuntia klo 03–04\n";
Hyvin näyttää koodisi toimivan ja ainakin omaan tarpeeseeni vaikuttaa sopivalta. Tein omaan versiooni koodistasi muutaman muutoksen ja se laskee nyt myös täydet tunnit ja iltalisät erikseen.
Ei sinulla sattuisi olemaan vielä vinkkiä tai jaksamista tehdä esimerkkiä, miten omaan koodiisi lisäämällä saisi mahdollisuuden laskea lisäksi lauantai- ja sunnuntaitunnit? Arkipyhät varmaankin on sitten ihan oma lukunsa.
juplin kirjoitti:
Miten omaan koodiisi lisäämällä saisi mahdollisuuden laskea lisäksi lauantai- ja sunnuntaitunnit?
Lisäsin vinkkiin funktion tietyn viikonpäivän tarkastamiseen.
Monimutkaisemmat ehdot on usein helpointa tehdä jakamalla tarkastus osiin. Esimerkiksi lauantain ja sunnuntain välisen yötyön määrän voi laskea siten, että laskee erikseen lauantain (klo 22–24) ja sunnuntain (klo 00–07).
juplin kirjoitti:
Arkipyhät varmaankin on sitten ihan oma lukunsa.
Arkipyhien ja muiden yksittäisten päivien tutkimiseen kannattaa laatia selvä lista näistä päivistä (päivämäärineen) ja käydä kyseinen lista läpi silmukassa.
$summa = 0; $päivät = array("2017-01-01", "2017-12-06"); // Ja mitä näitä nyt on... foreach ($päivät as $päivä) { $p_alku = strtotime("{$päivä} 00:00"); $p_loppu = strtotime("{$päivä} 24:00"); if ($p_alku == strtotime("this sunday", $p_alku)) { // skip, oli pyhäpäivä muutenkin } else { $summa += päällekkäin_int($vuoro_alku, $vuoro_loppu, $p_alku, $p_loppu); } }
Vapautit minut juuri useiden päivien pään seinään hakkaamiselta. Loistavaa matskua :)
Nyt kun tätä tarkemmin olen tutkaillut, koodi ei taida laskea sunnuntain yölisiä pilkkomatta aikoja ensin osiin, esim 00:00-07:00 ja 22:00-24:00 ja sitten laskemalla ne yhteen.
Vuorokauden vaihdokset lauantain puolelta olisi myös hyvä huomioida, koska esimerkiksi ainakin KVTES taitaa määritellä pyhälisät alkamaan jo lauantaina klo 18:00 jälkeen, mikä kikkailematta lienee koodilla melko haastavaa laskea. Myös arkipyhät tuossa kohtaa tuottavat vastaavan ongelman pyhätuntien aloitukseen.
Myös kokonaistuntien laskussa on jotain outoa. Jos aloitus on 29.07.2017 klo 22:00 ja lopetus on 30.07.2017 klo 22:00 on tulos 24 tuntia, mutta jos aloitus on 29.07.2017 klo 22:00 ja lopetus on 30.07.2017 klo 23:00 on tulos 2 tuntia.
juplin kirjoitti:
Myös kokonaistuntien laskussa on jotain outoa:
Jos aloitus on 29.07.2017 22:00 ja lopetus on 30.07.2017 22:00 on tulos 24 tuntia, mutta jos aloitus on 29.07.2017 22:00 ja lopetus on 30.07.2017 23:00 on tulos 2 tuntia.
Itse käsittelisin aina jokaisen vuorokauden tunnit omanaan, eli pätkäiset tarvittaessa puolenyön kohdalta ja käsittelet erikseen.
Pyhälisien laskemisenkaan ei pitäisi tuottaa ongelmia.
Jees, eiköhän tämä tästä lutviudu. Kiitos paljon :)