Tijdzone problemen

    • Tijdzone problemen

      Hallo,

      Ik ben bezig met een maffia game, en loop tegen een probleem met de tijdzones aan.
      Door het verzetten van de tijd ging er zojuist het èèn en ander verkeerd.

      Allereerst wat mijn instellingen zijn zover ik zie:
      Terminal zegt op dit moment met de command "date":

      Source Code

      1. Sun Oct 29 02:47:06 CET 2017


      PHP met de functie date_default_timezone_get() zegt:

      Source Code

      1. Europe/Berlin
      (Moet natuurlijk Amsterdam zijn, maar zou in principe niet moeten uitmaken? Maar zo aan te passen in php.ini

      MySQL met de volgende query "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)" zegt:

      Source Code

      1. 01:00:00


      En het probleem is dat als ze om 02:58 een misdaad doen, ze om 02:59 dit weer kunnen doen.
      Maar om 03:00 word de tijd weer 02:00, waardoor het systeem denkt dat er een uur gewacht moet worden.
      (Raar genoeg leek het tussen 01:00 en 02:00 te gebeuren op me spel)

      Nu is mijn vraag, hoe kan ik dit voorkomen? (Ik heb volledige beheer tot de server voor als dat belangrijk is)

      Alvast bedankt,
      Pekeltje
    • Gebruik overal UTC / GMT. En reken overal met UTC / GMT. De datums en -tijden die je overal toont kun je in lokale datum/tijd weergeven want functies als date() enzo nemen UTC al als uitgangspunt. Zorg ook dat je één bron hebt voor de tijd: ofwel in PHP, ofwel in MySQL, maar niet beide, want dan ben je mogelijk met twee maten aan het meten.

      Voorbeeld:

      PHP Source Code

      1. <?php
      2. header('Content-Type: text/html; charset=UTF-8');
      3. error_reporting(E_ALL);
      4. ini_set('display_errors', 'stdout');
      5. $now = time(); // huidige unix tijd
      6. // verander tijdszone naar Amsterdam, datum(formatteer)functies reageren hierop omdat deze altijd vanuit UTC werken
      7. date_default_timezone_set('Europe/Amsterdam');
      8. $AmsterdamTime = date('Y-m-d H:i:s', $now);
      9. // verander tijdszone naar LA
      10. date_default_timezone_set('America/Los_Angeles');
      11. $LATime = date('Y-m-d H:i:s', $now);
      12. ?><h2>Amsterdam: <?php echo $AmsterdamTime ?></h2>
      13. <h2>Los Angeles: <?php echo $LATime ?></h2>
      Laat alles zien

      Levert:

      Amsterdam: 2017-10-30 17:17:09

      Los Angeles: 2017-10-30 09:17:09



      Dus ook al verander je de tijdszone, je gebruikt in beide gevallen dezelfde timestamp als basis ($now). Je rekent op die manier dus altijd met één universele klok. Welke tijd je vervolgens toont hangt bijvoorbeeld af van een gebruikersinstelling (de tijdszone waarin deze persoon zit).
    • Bedankt FangorN,

      Het lijkt er dus op dat ik de tijdzone moet zetten (Staat nu op Berlin, ga ik straks gelijk veranderen in Amsterdam, zou verder geen invloed moeten hebben maar wel zo netjes)
      Ik gebruik nu vaak time(), en in de database current_timestamp.

      De current timestamp kan ik zo overal vervangen door je voorbeeld van AmsterdamTime mee te geven in de query, inplaats MySQL dit te laten regelen via current_timestamp.
      En time() blijkt al UTC te zijn (php.net/manual/en/function.time.php#100220)

      Dan zou het opgelost moeten zijn ?
      Ik ga het binnenkort proberen, alleen ben ik ivm tentamens en eindopdrachten weinig met dit project bezig.


      Jordy
    • Nee, dat zeg ik dus. Onder water wordt standaard al (zoals je zelf al zegt met bij o.a. de time() functie) met UTC gerekend. Hoe je dit weergeeft doet er niet toe. Je moet er ook voor zorgen dat je één bron voor je klok gebruikt. Dus niet zowel in MySQL als in PHP de "huidige tijd" genereren, maar doe dit altijd ofwel in PHP ofwel in MySQL. Stap dus van een van de twee af.

      CURRENT_TIMESTAMP() is een -geformatteerde- timestamp (yyyy-mm-dd hh:mm:ss) in lokale tijd. Dit is dus NIET (of in ieder geval NIET PER SE) het MySQL-equivalent van time() (ALTIJD UTC), maar hangt af van de op de databaseserver geconfigureerde tijdszone (MOGELIJK NIET UTC).

      Ik denk dat het volgende even moet bezinken. time() is altijd in UTC. Op het moment dat je, gegeven zo'n time() timestamp, hier een datum-formatteringsfunctie overheen gooit, zoals date(), dat wordt automatisch een conversie gedaan van UTC naar de ingestelde lokale tijd(szone). Dit kon je ook terugzien in mijn bovenstaande codevoorbeeld: ik verander niets aan de timestamp $now, maar als ik de tijdszones tussendoor verander spuugt dezelfde functie-aanroep (met precies dezelfde parameters) achtereenvolgens twee verschillende tijden uit.

      Dit is dus enkel een weergave-ding, onder de motorkap is (of zou dit) allemaal UTC (moeten zijn), dus OOK de timestamps die je in je database wegschrijft. Daarom is het onverstandig om CURRENT_TIMESTAMP() te gebruiken want dit vertelt je niets over de tijdszone waar je in zit. Het is dan vaak wel een zinnige aanname om, wanneer je met timestamps werkt, er vanuit te gaan dat dit ook UTC is, maar daar moet je dan wel zelf voor zorgen :p. En liefst ook documenteren.

      TL;DR
      gebruik één klok (PHP of MySQL) om te bepalen hoe laat het is (maar niet allebei, mogelijk staan beide klokken niet (helemaal) gelijk)
      sla alles in UTC op, en documenteer dit ook ergens
      geef tijden en datums in lokale tijdszone weer, doe dit middels standaard datum- en tijdfuncties (deze voeren de vertaling UTC -> lokale tijdszone vaak automatisch uit, maar controleer dit)

      In andere bewoordingen, waarin in feite hetzelfde wordt gezegd.

      (en het probleem is dus bij timestamps als yyyy-mm-dd hh:mm:ss dat je op geen enkele manier kunt afleiden op welke tijdszone deze betrekking heeft)

      Het TIMESTAMP kolomtype heeft trouwens dezelfde beperkingen als de unix timestamp (bereik tot 2038-01-19) en de DATETIME kolom heeft het nadeel dat deze niet automatisch geconverteerd wordt bij gebruikmaking van formatteringsfuncties, want het is niet gegarandeerd dat de timestamps om te beginnen UTC waren... Je zou er nog steeds voor kunnen kiezen om alles als UTC op te slaan in DATETIMEs en dan het rekenwerk naar jouw ingestelde lokale tijdszone over te hevelen naar PHP. Dit is in zekere zin een weergavekwestie die je apart kunt behandelen.

      Speerpunt hier misschien is ook het volgende: UTC heeft geen "DST" (Daylight Saving Time), oftewel, deze zal nooit een uur verspringen. Dit komt dan weer van pas om te berekenen of iemand al acties voor/na een bepaalde verstreken tijd mag uitvoeren want dat tijdsverschil is altijd exact want de klok verspringt nooit.

      Dit alles komt dus in feite neer op bewustwording van het fenomeen tijdszones en welke functies hier op acteren of afhankelijk van zijn, en welke niet. En vervolgens is het zaak dat je consequent bent bij het opvragen van de tijd, en het op één manier (in één tijdszone) opslaan van deze tijden.

      Post werd 17x aangepast, het laatst door FangorN ().

    • En zelfs als je je tijden fixt zul je rekening moeten houden met het volgende:

      Stel dat je een "crime" of wat dan ook wilt verwerken, dit valt dan vaak in twee of meer stappen uiteen:
      [controleer of actie X uitgevoerd mag worden]
      [voer na controle actie X uit]

      Wat je wilt is dat deze stappen als één ondeelbare (atomaire) actie worden uitgevoerd. Ook hier zul je zelf werk voor moeten verzetten, bijvoorbeeld door gebruik te maken van database-transacties (en enkel het gebruik maken van transacties is eigenlijk nooit genoeg). De reden waarom "crime games" of andere webgames vaak niet goed beveiligd zijn tegen valsspelen is omdat (onder andere) aan dit punt geen aandacht wordt besteed.

      Stel bijvoorbeeld dat iemand 3x tegelijkertijd zo'n actie op de webserver afvuurt, wanneer dan de bovenstaande actie niet ondeelbaar is, worden de scripts parallel uitgevoerd (dit gebeurt trouwens sowieso), bijvoorbeeld in de volgende volgorde:
      [controleer of actie X uitgevoerd mag worden #1]
      [controleer of actie X uitgevoerd mag worden #2]
      [voer na controle actie X uit #1]
      [voer na controle actie X uit #2]
      [controleer of actie X uitgevoerd mag worden #3]
      [voer na controle actie X uit #3]
      Grote kans dat actie #1 en #2 worden doorgelaten, en enkel #3 wordt geblokkeerd vanwege #1 of #2, maar zelfs dat is niet gegarandeerd en hangt af van wat deze acties doen / hoe snel de scripts uitgevoerd worden.

      Wat belangrijk is dat op het moment dat zo'n transactie start, alle betrokken resources worden vergrendeld zodat eenzelfde transactie moet wachten totdat de huidige transactie is afgerond. Dit is de enige manier om te garanderen dat op enig moment maximaal één transactie ten uitvoer wordt gebracht.

      In bovenstaand voorbeeld zou dit bijvoorbeeld kunnen resulteren in het uitvoeren van twee crimes in eenzelfde tijdsbestek, terwijl het eigenlijk de bedoeling was dat iemand dit maar één keer mag doen binnen een bepaalde timeout. Hierbij helpt het natuurlijk ook als er geen rekenfouten zitten in je tijdscalculaties. Als je die baseert op 1 klok, en daarmee dus ook meet met maar 1 maat, ben je al een eind op de goede weg, maar ben je nog bij lange na niet bij een (veilige) eindbestemming.