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.
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.
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ö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öä:
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ä:
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.
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…
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.
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.
Trineria on kehittänyt oman ketterästi skaalautuvan teollisen internetin alustan, Trineria Industrial Platformin.
Nadia Sabour on liittynyt Trinerian tiimiin myynti- ja markkinointijohtajana (CSMO).
Anna-Leena Poukkanen on aloittanut HR-Managerina Trinerialla.
Trineria Oy:n uusi laatujärjestelmä täyttää ISO 9001:2015 -standardin vaatimukset.
Digitaalinen partiolaisaloite on työkalu kehittämisaloitteiden keräämiseen ja käsittelyyn.
Ohjelmistokehityksen hankinta onnistuu helpommin, kun huomioi muutamia asioita heti alussa. Kokosimme kuusi käytännönläheistä askelta yrityksen ostoprosessia helpottamaan.
IteWikin juttusarja esittelee ohjelmistoalan yrityksiä ja ihmisiä niiden takana. Räätälöityjä teollisen Internetin ohjelmistoja kehittävä Trineria tavoittelee 40-60 % vuotuista kasvua.
Otimme Trinerialla OKR-mallin käyttöön, koska halusimme jalkauttaa yrityksen uuden strategian jokapäiväisen työn tasolle.
Justus Sydänmaa on aloittanut Trinerialla Software Developer Traineena.