Ajan käsitteet haastavat ohjelmoijan

Hetket, kellonajat, aikavyöhykkeet ym. puuroutuvat ohjelmoijalla helposti yhdeksi “ajan” käsitteeksi.

Hetket, kellonajat, aikavyöhykkeet ym. puuroutuvat ohjelmoijalla helposti yhdeksi “ajan” käsitteeksi. Monien ohjelmointikielten standardikirjastoissakin kaikki aika-asiat hoidetaan luokalla tai parilla. Lisäksi kirjastot yrittävät arvailla, mitä ohjelmoija milloinkin tarkoittaa, ja tekevät sitten piilossa kaikenlaisia “helpottavia” muunnoksia. Lopputuloksena aikaa käsittelevässä koodissa on usein tavallista enemmän virheitä. Niitä voi myös olla vaikea analysoida, sillä useampi virhe saattaa kumota toisensa tai koodi saattaa toimia hyvin melkein aina.

Tässä kirjoituksessa pyritään erottelemaan joitain aikaan liittyviä käsitteitä toisistaan. Toivottavasti tämä saa lukijan ajattelemaan käsitteitä selkeämmin ja tekemään muutaman aikabugin vähemmän.

En ole aiheen asiantuntija, vaan käytin kirjoittamista pohdiskeluun ja omienkin ajatusteni selventämiseen.

Hetkien käsittely on vielä helppoa

Hetki on piste aikasuoralla. Kuvittele, että joku käynnistää sekuntikellon. Käynnistyshetki määrää aikasuoran origon eli epokin, jonka jälkeen kaikki muut hetket voidaan ilmaista sekunteina ennen tai jälkeen epokin. Kaikkialla maailmassa samaan hetkeen viitataan samalla sekuntimäärällä. Jos Suomessa on 73 sekuntia epokin jälkeen, niin samalla hetkellä myös Japanissa on 73 sekuntia epokin jälkeen, koska kaikki viittaavat samaan sekuntikelloon.

Tunnetuimpia epokkeja on Unix-epokki. Juuri tällä hetkellä “Unix-sekuntikello” on käynyt 1 653 470 633 sekuntia. Windowseissa taas käytetään NT-epokkia ja samainen hetki on 13 297 944 233 sekuntia NT-epokin jälkeen. Sekuntien sijaan voidaan toki käyttää myös vaikkapa millisekunteja tai mikrosekunnin kymmenesosia kuten Windows tekee. Nämä ovat kuitenkin yksityiskohtia ja yleensä kannattaa pyrkiä siihen, että hetkiä pystyisi käsittelemään abstrakteina aikasuoran pisteinä.

Hetkiä on suhteellisen helppo käsitellä. Jos hetkeen lisätään 7 sekuntia, saadaan hetki, joka on 7 sekuntia ensimmäisen hetken jälkeen. Sekuntimäärien erotus kertoo, paljonko aikaa hetkien välillä on, ja sekuntimääriä vertailemalla nähdään, kumpi hetki on aikaisempi.

Tähän helpot asiat sitten oikeastaan loppuvatkin.

Kellonajat ja päivämäärät pistävät jo usealla pakan sekaisin

Tässä kirjoituksessa kellonaika tarkoittaa kellonajan ja päivämäärän yhdistelmää, esimerkiksi 2022-05-25 12:23:53. Kellonaika ei yksin viittaa mihinkään hetkeen, vaan tulkinta riippuu aina näkökulmasta. Minun näkökulmastani (Suomen aika) äskeinen kellonaika viittaa Unix-hetkeen 1 653 470 633 s, mutta vaikkapa ruotsalaisten näkökulmasta se onkin Unix-hetki 1 653 474 233 s. Kellonaika ei myöskään yksinään tarkoita “paikallista aikaa”, koska se viittaisi heti tulkintaan jossain aikavyöhykkeessä.

Kellonaika on siis vain liuta numeroita tai muita määreitä, joilla ilmaistaan vuosia, kuukausia jne., ja jotka noudattavat tiettyjä sääntöjä. Esimerkiksi päivän on oltava 1–31, missä yläraja riippuu kuukaudesta ja vuodesta.

Kuten arvata saattaa, kellonaikojen käsitteleminen on monimutkaisempaa kuin hetkien. Yllättävää on ehkä se, kuinka paljon hankalampaa se voi olla. Mitä on esimerkiksi kalenterikuukausi lisää päivämäärään 2019-02-28? Onko se 2019-03-28 vai 2019-03-31? Tai paljonko on kalenterivuosi ja yksi kalenteripäivä lisää samaan päivämäärään? 2020-03-01 vai 2020-02-29? Tällaiset pitää miettiä tapauskohtaisesti ja ensimmäinen askel on ymmärtää, minkälaisia ongelmia ylipäätään voi tulla vastaan.

Homma karkaa käsistä viimeistään siinä vaiheessa, kun hetket ja kellonajat menevät sekaisin. Sanotaan vaikka, että kellonaikaan 2022-10-30 00:00 pitäisi lisätä yksi päivä. Asiakas ajatteli yhtä kalenteripäivää (vastaus 2022-10-31 00:00), mutta ohjelmoija yhtä “sekuntikellopäivää” eli 86400 sekuntia sekuntikellolla mitattuna ja sai vastaukseksi 2022-10-30 23:00, koska satuttiin olemaan Suomessa ja siirryttiin talviaikaan.

Kellonaikojen vertailu voi myös olla ongelmallista. Jos kysytään, kumpi kahdesta kellonajasta on aikaisempi, vastaus on periaatteessa helppo. Ongelma onkin se, että oikeasti kysymys usein tarkoittaa, kumpi annetuista paikallisista kellonajoista on aikaisempi, ja siihen taas ei aina voi antaa yksikäsitteistä vastausta. Esimerkiksi talviaikaan siirryttäessä kellonaikoja toistetaan, mutta ensimmäiset niistä vastaavat aikaisempia hetkiä kuin toiset.

Aikavyöhykkeet sekä kesä- ja talviajan siirtymät tuovat omat pulmansa

Aikavyöhyke on liuta sääntöjä, joiden avulla hetki voidaan muuntaa kellonajaksi. Keskeisin aikavyöhyke on UTC, jossa on käytännössä kaksi sääntöä:

  1. Unix-epokki vastaa kellonaikaa 1970-01-01 00:00:00.
  2. Muut hetket muunnetaan kellonajoiksi laskemalla tästä sekunteja ylös tai alas kalenterilaskusääntöjen mukaan.

Periaatteessa lisähaasteita tulee karkaussekunneista, mutta ne voi yleensä aika hyvin unohtaa (kuuluisia viimeisiä sanoja?).

Muut aikavyöhykkeet ilmaistaan yleensä UTC:n avulla. Esimerkiksi Suomen kesäaika on UTC+3 (tuntia) eli hetki voidaan ilmaista Suomen kesäajassa muuntamalla se ensin UTC-aikaan ja lisäämällä tähän 3 kalenterituntia. Aikavyöhyke ei kuitenkaan ole pelkkä aikavyöhykesiirtymä kuten 3 tuntia, vaan se saattaa olla paljon monimutkaisempi. Suomen aikavyöhykkeessäkin on seuraavanlaisia sääntöjä:

  • Ennen vuotta 1981 Suomen aika oli aina UTC+2, paitsi vuonna 1945, jolloin 3.4.–4.10. se oli UTC+3.
  • Vuodesta 1981 tähän päivään Suomen aika on ollut UTC+2 paitsi kesäisin (maaliskuun viimeisestä sunnuntaista lokakuun viimeiseen sunnuntaihin), jolloin se on UTC+3.

Suomessa on myös välillä ollut puhetta kesä- tai talviajasta luopumisesta. Jos tämä joskus toteutuu, niin aikavyöhykesäännöt muuttuvat!

Suomen aikavyöhyke ei siis ole “UTC+2 (kesäisin UTC+3)” tai “EET/EEST”, koska se on historiallisesti ollut monimutkaisempi ja saattaa tulevaisuudessa muuttua. Sen sijaan suomen aikavyöhyke on Europe/Helsinki (zoneinfo-tunnus), joka viittaa koko dynaamiseen sääntöpakettiin.

Paikallinen aika ja sen laskeminen

Jos hetki ja aikavyöhyke tiedetään, voidaan laskea paikallinen aika. Esimerkiksi Unix-hetki 1 653 470 633 s on 2022-05-25 12:23:53 Suomen paikallista aikaa. Paikallinen aika kertoo, missä ollaan ja mitä kello siellä näyttää.

Toisin päin muunnos ei aina onnistu. Esimerkiksi “2022-03-27 03:30:00 Suomen aikaa” on virheellinen, sillä kyseinen kellonaika ohitettiin kesäaikaan siirryttäessä, eikä se siis vastaa mitään hetkeä. Toisaalta “2022-10-30 03:30:00 Suomen aikaa” ei ole yksikäsitteinen, koska tämä kellonaika tulee esiintymään kahdesti talviaikaan siirryttäessä, jos ei sitten talviajasta ennen sitä luovuta…

Sovelluksia ajan tallentamiseen

Tästä kaikesta voi ainakin johtaa pari nyrkkisääntöä ajan tallentamiseen. Sovellettava harkiten:

Menneet ajat kannattaa yleensä tallentaa hetkinä. Jos ne tallentaa pelkkinä paikallisina kellonaikoina, niin niitä ei voi enää asettaa aikajärjestykseen, ei vaikka aikavyöhykekin tunnettaisiin. Niitä ei liioin voi muuntaa hetkiksi tai toisen aikavyöhykkeen paikalliseen aikaan eikä selvittää, paljonko aikaa kahden kellonajan välillä on kulunut. Jos paikallinen aika pitää pystyä selvittämään, kannattaa luultavasti vain tallentaa sekä hetki että aikavyöhyke.

Tulevat ajat ovat vielä hankalampia. Mietitään muistutusohjelmaa. Kello on nyt 14 ja käyttäjä asettaa muistutuksen kello 17. Sitten käyttäjä lentää pikaisesti toiseen aikavyöhykkeeseen. Jos muistutus tallennettiin hetkenä, se hälyttää 3 tuntia asettamisen jälkeen kellonajasta riippumatta. Jos se tallennettiin kellonaikana, se hälyttää kello 17 riippumatta siitä, paljonko aikaa asettamisen ja hälytyksen välillä kuluu. Perussääntö voisi olla, että jos käyttäjä syöttää kellonajan, se tallennetaan kellonaikana, koska vastaavaa hetkeä ei pysty luotettavasti selvittämään. Jos taas käyttäjä syöttää ajan, joka pitää odottaa, niin tallennetaan hetki, koska vastaavaa kellonaikaa ei pysty laskemaan.

Linkkejä

Miksi .NET:in DateTime-luokka on ongelmallinen https://blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.html

Usein, tai ainakin joskus, tehtyjä vääriä olettamuksia ajasta  https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time

 

Blogin kirjoittaja Kristian Setälä toimii Trinerialla Senior Software Developerina.

Jaa:

Lisää artikkeleita