Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: PHP: Yötuntien tunnistus?

juplin [29.07.2017 09:51:22]

#

Tarkoitus olisi laskea vuoron aloitus- ja lopetus ajan mukaisesti yötunnit yhteensä. Seuraava koodi hoitaa asian sinällään oikein mikäli aloitus ja lopetus ajat vain ovat esimerkin mukaiset:

$tes_yolisa_alkaa = '22:00';
$tes_yolisa_paattyy = '07:00';

$vuoron_aloitus_01 = strtotime("22:00");
$vuoron_lopetus_01 = strtotime("07:00");

function vuoron_lisat($vuoron_aloitus_tunnit, $vuoron_lopetus_tunnit, $vuoron_aloitus_lisat, $vuoron_lopetus_lisat, $vuorokausi) {
if ($vuoron_lopetus_tunnit < $vuoron_aloitus_lisat) {
return 0;
}
if ($vuoron_aloitus_tunnit > $vuoron_lopetus_lisat) {
return 0;
}
if ($vuoron_aloitus_tunnit < $vuoron_aloitus_lisat) {
$vuoron_aloitus_tunnit = $vuoron_aloitus_lisat;
}
if ($vuoron_lopetus_tunnit > $vuoron_lopetus_lisat) {
$vuoron_lopetus_tunnit = $vuoron_lopetus_lisat;
}
return $vuoron_lopetus_tunnit - $vuoron_aloitus_tunnit;
}

$vuoron_aloitus_lisat_01 = $vuoron_aloitus_01 /* Jos 00:00 tai yli, lisää + 3600 * 24 */;
$vuoron_lopetus_lisat_01 = $vuoron_lopetus_01 + 3600 * 24 /* Jos 00:00 tai yli, lisää + 3600 * 24 */;

$yolisa_alkaa_01  = strtotime($tes_yolisa_alkaa);
$yolisa_paattyy_01 = strtotime($tes_yolisa_paattyy) + 3600 * 24;

echo "Yö: " . (vuoron_lisat($vuoron_aloitus_lisat_01, $vuoron_lopetus_lisat_01, $yolisa_alkaa_01, $yolisa_paattyy_01, 3600 * 24)) / 3600;

Ongelma syntyy ja tunnit lasketaan miten sattuu mikäli aloitus ja lopetusajat ovatkin erilaiset. Esimerkiksi:

21:00 - 23:00

tai

00:00 - 07:00

Olen yrittänyt ratkaista ongelmaa seuraavan kaltaisella tavalla, eli lisäämällä "+ 3600 * 24" mikäli aloitus tai lopetus tapahtuu puolenyön aikaan tai sen jälkeen:

if ($vuoron_aloitus_01 >= strtotime('00:00') && $vuoron_aloitus_01 <= strtotime('07:00')) {
$vuoron_aloitus_lisat_01 = $vuoron_aloitus_01 + 3600 * 24;
}
else {
$vuoron_aloitus_lisat_01 = $vuoron_aloitus_01;
}

ja

if ($vuoron_lopetus_01 >= strtotime('00:00') && $vuoron_lopetus_01 <= strtotime('07:00')) {
$vuoron_lopetus_lisat_01 = $vuoron_lopetus_01 + 3600 * 24;
}
else {
$vuoron_lopetus_lisat_01 = $vuoron_lopetus_01;
}

Tiedostan, että esimerkin kaltainen if-lause ei itsessään riitä, mutta en yrityksistä huolimatta ole onnistunut laatimaan sellaista kokonaisuutta, mikä toimisi riippumatta siitä, mikä aloitus tai lopetus aika on.

Löytyisikö tähän apua joltakin kokeneemmalta PHP-tuntijalta?

Metabolix [29.07.2017 11:20:18]

#

Ensinnäkin heitä pois muuttujat vuoron_aloitus_lisat_01 ja vuoron_lopetus_lisat_01 ja laske alkuperäisiin muuttujiin todelliset työvuoron ajat, koska vuoron aloitus- ja lopetusajat ovat samat riippumatta lisistä. Nimeä loputkin muuttujat oikein: _tunnit on selvästi väärä pääte muuttujalle, joka sisältää oikeasti aikaleiman sekunteina.

Eikö sinulla ole tiedossa, milloin työvuoro on tehty? Kaikkein helpointa olisi laskea ihan oikeat aikaleimat kokonaisilla päivämäärillä:

$alku = strtotime("2017-07-28 19:00");
$loppu = strtotime("2017-07-29 10:00");

Jos tähän ei ole mahdollisuutta, järkevä logiikka olisi siirtää vuoron loppu seuraavalle päivälle silloin, kun se muuten näyttää olevan ennen alkua (eli työvuoron kesto olisi negatiivinen).

$alku = strtotime("19:00");
$loppu = strtotime("10:00");
if ($loppu <= $alku) {
	$loppu += 86400;
}

Ilman päivämäärää strtotime olettaa tämän päivämäärän, joten edellä kuvattu laskutapa toimii väärin kesäajan vaihtuessa. Tähän ei ole muuta varmaa ratkaisua kuin se, että käytetään todellisia työvuoron päivämääriä.

Nyt $alku ja $loppu ovat järkevät, mutta ne saattavat olla joko tätä vuorokautta (esim. 01–04) tai seuraavan vuorokauden vaihteessa (esim. 22–05 eli tavallaan 22–29). Onhan mahdollista myös, että vuoro osuu kahdelle eri yölle (esim. 03–23). Näiden takia lisät pitää nyt laskea kahdelta eri väliltä: vuorokauden alusta 00–07 ja seuraavasta vaihteesta 22–07.

Tästä yötuntien ym. laskemisesta on väännetty sivustolla jo monta kertaa, ja on nähty mitä ihmeellisimpiä ratkaisuja, joten laitanpa nyt vihdoin tällaisen aivan tavallisen koodin malliksi:

<?php

// Funktio laskee, paljonko kahdesta lukuvälistä menee päällekkäin.
// Otetaan siis aikaisemman loppukohdan ja myöhäisemmän alkukohdan ero,
// ja negatiivinen tulos muutetaan nollaksi.
function päällekkäin($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_klo($alku, $loppu, $alku_klo, $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 ($alku_klo >= $loppu_klo) {
		$alku_klo .= "- 1 day";
	}

	// Käydään silmukassa aikaväliin kuuluvat vuorokaudet läpi.
	$pvm = date("Y-m-d", $alku);
	$n = intval(($loppu - $alku) / 86400 + 1 + 1/24);
	$päällekkäin = 0;
	for ($i = 0; $i <= $n; ++$i) {
		// Lasketaan $i. vuorokauden tutkittavat kellonajat.
		$i_alku = strtotime("{$pvm} {$alku_klo} + {$i} day");
		$i_loppu = strtotime("{$pvm} {$loppu_klo} + {$i} day");
		// Lisätään summaan tämän vuorokauden päällekkäinen aika.
		$päällekkäin += päällekkäin($alku, $loppu, $i_alku, $i_loppu);
	}
	return $päällekkäin;
}

// 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övuoro:
if (true) {
	// Jos työn ajankohta tiedetään tarkasti, kannattaa käyttää sitä.
	$vuoro_alku = strtotime("2017-07-28 19:00");
	$vuoro_loppu = strtotime("2017-07-29 06:00");
} else {
	// Jos työn päivämäärää ei tiedetä, valitaan jokin sopiva päivämäärä,
	// johon ei liity kesäaikaan siirtymistä tai muuta laskuja sekoittavaa.
	$työpäivä = "2017-07-28";

	// Työvuorojen kellonaika pitää silti tietää...
	$vuoro_alku_klo = "19:00";
	$vuoro_loppu_klo = "06:00";

	// Kellonajoista ja päivämäärästä lasketaan varsinaiset ajat.
	$vuoro_alku = strtotime("{$työpäivä} {$vuoro_alku_klo}");
	$vuoro_loppu = strtotime("{$työpäivä} {$vuoro_loppu_klo}");
	// Jos kellonajan mukaan alku on ennen loppua (kuten klo 22 – klo 07),
	// siirretään loppu seuraavan vuorokauden puolelle.
	if ($vuoro_alku >= $vuoro_loppu) {
		$vuoro_loppu = strtotime("{$työpäivä} {$vuoro_loppu_klo} + 1 day");
	}
}

$lisät = päällekkäin_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:    ", ($lisät / 3600), "\n";

Täytynee laittaa tuosta ihan koodivinkki... Edit: vinkki on julkaistu ja siinä koodia vielä vähän siistitty.

juplin [29.07.2017 11:39:32]

#

Rakennan yksinkertaistettua sovellusta ja tarkoitus oli, että se ei olisi sidoksissa varsinaisesti päivämääriin, mutta kokeilen esimerkkisi avulla.

Kiitos paljon vastauksesta :) Perehdyn koodiin illemmalla.

Metabolix [29.07.2017 11:44:20]

#

Valitsemillasi funktioilla et voi tehdä sovellusta, joka ei olisi jotenkin sidoksissa päivämääriin. Eli valitse jokin harmiton päivämäärä laskuihisi, tai muuten laskut voivat toimia yhtenä päivänä eri tavalla kuin toisena (koska strtotime valitsee kuitenkin jonkin päivämäärän, vaikkei sitä näkyisi). Tällöin kuitenkin jää edelleen ongelmaksi se, että jos työvuoro on oikeasti kestänyt kesäajan vaihtumisen yli, koodi antaa siitä väärän tuloksen (puuttuu tunti tai on ylimääräinen tunti).

juplin [29.07.2017 11:52:48]

#

Loistava ohjeistus, kiitos toistamiseen :)

juplin [30.07.2017 00:39:49]

#

Koodisi toimii hienosti. Tästä oli suuri apu :)

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta