Kirjautuminen

Haku

Tehtävät

Koodivinkit: PHP: Aikavälien päällekkäisyyden laskenta

Kirjoittaja: Metabolix; viimeksi muokattu 30.07.2017.

Tagit: laskenta

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.

Funktiot

// 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);
	}
}

Esimerkki: työvuoron yötuntien laskeminen

// 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";

Esimerkki: kellonaikojen päällekkäisyyden laskeminen

// 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";

Esimerkki: sunnuntaituntien tarkastus

// 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";

Kommentit

juplin [30.07.2017 20:12:04]

Lainaa #

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.

Metabolix [30.07.2017 21:25:35]

Lainaa #

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);
	}
}

juplin [31.07.2017 16:34:16]

Lainaa #

Vapautit minut juuri useiden päivien pään seinään hakkaamiselta. Loistavaa matskua :)

juplin [31.07.2017 20:40:00]

Lainaa #

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.

jalski [31.07.2017 21:32:21]

Lainaa #

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.

juplin [31.07.2017 21:48:02]

Lainaa #

Jees, eiköhän tämä tästä lutviudu. Kiitos paljon :)

Kirjoita kommentti

Muista lukea keskustelun ohjeet.
Tietoa sivustosta