ABIS Infor - 2020-12

1970 - 2020: 50 jaar sinds de "Unix Epoch"

Peter Vanroose (ABIS) - 14 januari 2020

Samenvatting

1 januari 2020: een verjaardag, zelfs een gouden jubileum, voor de Unix Epoch! De "jaartelling" van Unix-systemen begint inderdaad te tellen vanaf 1970. Een korte schets hoe de vork precies in de steel zit.

De Unix Epoch - nooit van gehoord?

Unix-besturingssystemen (inclusief Linux) slaan tijdstippen intern op m.b.v. de zogenaamde Unix-tijd. Dit gebeurt b.v. voor het bijhouden van het aanmaak-moment van een bestand, of het tijdstip van een gebeurtenis zoals iemand die zich aanmeldt op het systeem, of een shutdown-bericht, of de tijdsaanduiding in een e-mail. Maar wat is die Unix-tijd precies?

Ik parafraseer Wikipedia ( https://nl.wikipedia.org/wiki/Unix_Epoch) die hiervoor een beknopte en nauwkeurige beschrijving heeft:

"De Unix Epoch is de datum en tijd die correspondeert met de waarde 0 van de klok en de timestamp van Unix. Die epoch is gelijk aan 00:00:00 UTC op 1 januari 1970. De Unix-tijd is dan het aantal seconden na de Unix Epoch."

De Unix Epoch is dus middernacht op 1 januari 1970, wat betekent dat een paar dagen geleden, nl. om middernacht op Nieuwjaarsdag, de "Unix Epoch" zijn 50ste verjaardag vierde! Op dat ogenblik was de waarde van de Unix-tijd precies (365 * 38 + 366 * 12) * 24 * 60 * 60 = 1577836800 (seconden).

Om preciezer te zijn: de Epoch is middernacht UTC van 1 januari 1970. Eigenlijk werd UTC of "Universal Time Coordinated" pas uitgevonden in 1972, maar voor alle praktische doeleinden is UTC identiek aan GMT of "Greenwich Mean Time", ook wel "Zulu Time" (tijdszone Zero) genoemd, die wel al bestond in 1970: het is de (winter)tijd van Greenwich, of wat dat betreft ook van Dublin, Reykjavik, Lissabon of Tenerife. Voor ons in de Benelux is dat één uur vroeger dan onze lokale tijd in de winter, en twee uur vroeger dan onze lokale tijd in de zomer.

De Unix Epoch - het vieren waard?

Ja zeker! Het hele idee van (1) het weergeven van de "huidige tijd" onafhankelijk van de lokale tijd(szone) van je computer, (2) altijd oplopend te zijn, d.w.z.: niet hoeven één keer per jaar een uur terug te springen vanwege de zomertijd, en (3) het gebruik van één enkele teller voor zowel de dag als het tijdstip binnen een dag, waren destijds (d.w.z. in de beginjaren 70, toen Unix werd "uitgevonden") revolutionaire beslissingen. Vroeger, en eigenlijk ook (veel) later op andere besturingssystemen (b.v. z/OS of MS-Windows), is de interne klok van een computer altijd ingesteld op lokale tijd. En dat is dus b.v. zomertijd tijdens de zomermaanden.

In de huidige context van wereldwijde onmiddellijke communicatie en gegevensuitwisseling lijkt deze computeronafhankelijke definitie van "huidige tijd" evident, maar dat was het zeker niet in de jaren 70, toen het TCP/IP "internet" nog niet bestond, toen e-mails met enkele uren vertraging konden afgeleverd worden, en er geen GPS-satellieten waren en computers essentieel stand-alone systemen waren. Dus het was zeker een visionaire beslissing van de Unix-pioniers om intern geen lokale tijd te gebruiken voor b.v. tijdstippen van bestanden.

Er zijn eigenlijk twee onmiddellijke voordelen van een "UTC"-keuze versus een "lokale tijd"-keuze voor creatie- of wijzigingstijdstip van een bestand:
(1) bij het uitwisselen van bestanden tussen externe computers, kan behalve de inhoud ook de metadata worden gecommuniceerd en correct worden geïnterpreteerd bij ontvangst; en

(2) meer recentelijk aangemaakte of gewijzigde bestanden zullen nooit een ouder tijdstip hebben dan eerder aangemaakte of gewijzigde bestanden, zelfs niet tussen 2 uur 's nachts en 3 uur 's nachts bij overschakelen van zomertijd naar wintertijd.

En daar bovenop is er ook een belangrijk voordeel bij het gebruik van "seconden sinds een bepaald tijdstip" in plaats van twee afzonderlijke gegevens nl. "datum" en "tijd": de "leeftijd" van een bestand is gewoon het verschil tussen twee gehele waarden (of desnoods decimale waarden, voor wie een hogere precisie dan een seconde wil).

De Unix Epoch - en andere epochs

De term "epoch" betekent in het algemeen "een moment in de tijd dat als referentie wordt gebruikt" (startpunt of nulpunt dus) voor het meten van een tijdsverloop. Verschillende epochs (of "tijdperken") zijn in gebruik of zijn in gebruik geweest: b.v. het feit dat we nu in jaar 2020 zijn, betekent dat de "epoch" van onze huidige kalender ongeveer 2020 jaar geleden was. In de astronomie is de epoch januari 2000 op de middag. De Juliaanse datum heeft zijn epoch ongeveer 6733 jaar geleden, op 1 januari 4713 v.Chr. En je weet waarschijnlijk dat sommige culturen of religies een andere kalender gebruiken dan de onze, wat altijd ook betekent: een andere epoch, dat wil zeggen hun definitie voor "het jaar 1".

Hoewel men in principe vanaf een epoch kan "terugtellen", dat wil zeggen: negatieve tijdstippen gebruiken, werd heel vaak de epoch zodanig gekozen dat men - voor alle praktische doeleinden - zichzelf kon beperken tot positieve tijdstippen, dus tijdstippen na de epoch.

Meer specifiek zijn, in de context van computersystemen, de volgende epochs het vermelden waard:

  • Software zoals Excel en Mathematica en besturingssystemen zoals VME en CICS gebruiken het begin van 1900 als hun "referentie". Samen met een tweecijferige voorstelling voor het jaar veroorzaakte dat trouwens het beruchte "Y2K" (oftewel "jaar 2000") -probleem!
  • VMS en DVB gebruiken 17 november 1858 als hun epoch. Waarom? Wel, dit is de Juliaanse datum met waarde 2400000 (dagen), dus het bespaart hen twee beduidende cijfers: het zorgt ervoor dat de "24" aan het begin van een dagnummer niet hoeft herhaald te worden. Ze zullen wel een 6de cijfer nodig hebben (of de epoch moeten herdefiniëren) na het jaar 2131 ...
  • COBOL, MS-Windows (sinds NT) en NTFS gebruiken 1 januari 1601 als hun epoch. Dit is het eerste jaar van de 400-jarige cyclus van onze Gregoriaanse kalender van exact 400 * 365 + 97 = 146097 dagen = 20871 weken (omdat het 97 schrikkeljaren bevat: alle 4-tallen behalve de niet-400-voudige veelvouden van 100). Een kalender van exact 400 jaar geleden kan men dus opnieuw gebruiken.
  • SAS gebruikt 1 januari 1960 als epoch.
  • DOS, alle FAT-varianten en de PC-BIOS gebruiken 1 januari 1980. Opmerkelijk is dat het GPS-systeem 6 januari 1980 als zijn epoch gebruikt: vermits GPS de week als tijdseenheid gebruikt (in plaats van een dag of een seconde), en hun weken op zondag beginnen, hebben ze hun epoch op de eerste zondag van 1980 gezet.

Tijdseenheden sinds de epoch

Het hele punt van het definiëren van een epoch, in de context van een computersysteem, is de mogelijkheid om een éénduidig tijdstip te kunnen weergeven met één enkel getal, of het nu een geheel getal of een decimale waarde is. Zoals al uit bovenstaande voorbeelden blijkt, tellen de meeste systemen (of het nu een besturingssysteem of een bestandssysteem of een compiler is) in een combinatie van jaren en/of dagen, terwijl sommige in seconden tellen. De Unix-tijd telt in seconden sinds de Unix Epoch.

Nadat de keuze voor de epoch en voor de tijdseenheid is gemaakt, is de volgende beslissing de (interne) weergave van de tijdstip-waarde in deze eenheden. Afhankelijk van die keuze, en meer specifiek het aantal cijfers of bits van die weergave, zullen we vroeg of laat een soort "Y2K"-probleem krijgen, nl. wanneer die teller "terugspringt naar nul".

Aanvankelijk koos Unix voor een weergave als gehele getallen met 32 bits of 4 bytes (datatype time_t): het gebruik van de registergrootte (het datatype int, dat wil zeggen 16 bit op 16-bits processoren) zou absurd zijn geweest, vermits het "Y2K"-probleem zich dan al op 4 januari 1970 zou hebben voorgedaan! Dus gingen ze voor een 2-register representatie op 16-bit computers (en een 1-register representatie op 32-bit computers). Dit lijkt het "Y2K"-probleem te verplaatsen naar ... 7 februari 2104. Dus we zijn nog een paar decennia veilig?

Dit is eigenlijk niet helemaal correct, omdat die 32-bits gehele getallen als getallen met teken ("signed") moeten geïnterpreteerd worden. (time_t is een signed integer datatype.) Er zijn dus slechts 31 bits beschikbaar voor positieve waarden. Dit plaatst de "einddatum" op ... 19 januari 2038 om 03:14:08 UTC! Dus let op voor een nieuw "Y2K"-probleem over een paar jaar ... (Een interessant artikel hierover is https://en.wikipedia.org/wiki/Year_2038_problem.)

Anderzijds, door een mogelijk negatieve interpretatie van die 32-bits waarde te gebruiken, laten we de Unix-tijd toe, tijdstippen vóór 1970 voor te stellen, nl. tot ... 13 december 1901! Hoe dan ook, sommige (oudere) Unix (C)-programma's hebben dus een ingebouwde tijdbom: in de ochtend van 19 januari 2038 zal het plots december 1901 zijn ...

Wikimedia-Y2038p.gif

Merk op dat alle op Unix gebaseerde systemen mogelijk kwetsbaar zijn voor dit probleem. Dit omvat b.v. Linux, macOS en alle Android-varianten (tegenwoordig vaak gebruikt in embedded systemen).

Ondertussen hebben de meest recente versies van de meeste van Unix afgeleide besturingssystemen time_t als een 64-bits geheel getal gedefinieerd, zodat nu tijdstippen tot ongeveer 293 miljard jaar kunnen voorgesteld worden (of binnenkort zullen zijn). Dit is meer dan twintig keer de leeftijd van het heelal. Verder opschalen lijkt dus niet meer nodig.

Unix-tijd in de praktijk

Als je toegang hebt tot een Unix- of Linux-systeem, heb je zeker al het ls-commando gebruikt. Of zeer waarschijnlijk zelfs ls -l, z'n "lange" variant. De uitvoer bevat meestal meerdere regels die er ongeveer zo uitzien:

-rw-r--r-- 1 peter abis      510 Jun 21  2019 old_test.txt
-rw-r--r-- 1 peter abis      699 Jan 14 11:12 test.txt

Op Linux wordt dit nog iets gedetailleerder door de optie "--full-time" toe te voegen:

-rw-r--r-- 1 peter abis      512 2019-06-21 19:07:43.568495200 +0200 old_test.txt
-rw-r--r-- 1 peter abis      699 2020-01-14 11:12:02.195578700 +0100 test.txt

Dit geeft het "volledige" tijdstip van laatste wijziging van de bestanden weer, zoals opgeslagen in het bestandssysteem. Of beter gezegd: wat wordt opgeslagen is natuurlijk alleen het "aantal seconden sinds de Epoch", en het commando ls converteert dat naar een voor ons leesbaar formaat, nl. de lokale tijd op het moment dat het bestand werd aangemaakt: bemerk de twee verschillende tijdzones +0200 (zomertijd) en +0100 (wintertijd)!

Een eenvoudiger manier om aan het "volledige" tijdstip van laatste wijziging van een bestand te komen, is met behulp van het commando stat dat op elk Unix-achtig besturingssysteem aanwezig is:

  File: old_test.txt
  Size: 510                     Blocks: 8               IO Block: 4096     regular file
Device: c63826bbh/3325568699d   Inode: 971290           Links: 1
Access: (0644/-rw-r--r--)       Uid: ( 48979/   peter)  Gid: (  500/    abis)
Access: 2020-01-14 13:33:51.966689300 +0100
Modify: 2019-06-21 19:07:43.568495200 +0200
Change: 2019-06-21 19:07:43.568495200 +0200

Blijkbaar worden timestamps op dit bestandssysteem in fracties van een seconde bijgehouden. Maar in elk geval worden ze intern opgeslagen als "seconden sinds de Epoch". Alleen kunnen we die interne waarden niet zien, omdat we Unix-opdrachten (zoals ls en stat) moeten gebruiken die automatisch de vertaling doen naar een voor ons leesbare vorm. Maar wacht ... Linux is een open-source besturingssysteem, dus de broncode van een GNU-commando zoals stat is beschikbaar, b.v. via https://ftp.gnu.org/gnu/coreutils/

Bij het bekijken van de broncode (geschreven in C), zien we inderdaad de conversie (die rechttoe-rechtaan is, maar redelijk omslachtig, b.v. om rekening te houden met schrikkeljaren enz.) Maar we zien ook waar de interne timestamp-waarde vandaan komt: de implementaties van zowel ls als stat voeren een zogenaamde system call uit naar de kernel-functie stat, die een struct met meerdere velden teruggeeft, inclusief een veld met de naam st_mtime dat van datatype time_t is. Als je zelf een beetje wilt experimenteren, en je hebt toegang tot een C-compiler (bijv. gcc), dan kun je zelf een klein programma schrijven dat de stat system call uitvoert op een bestand naar keuze, en je kunt dan eenvoudig verifiëren dat je inderdaad de gehele waarde 1561136863 terugkrijgt voor 2019-06-21 19:07:43 +0200. Ik laat de berekeningen voor het verifiëren van deze conversie als oefening voor de ijverige lezer.

Een ander interessant Unix-commando dat een "seconden sinds de Epoch"-antwoord krijgt van de kernel, en het converteert naar een leesbare vorm, is het commando date: zonder argumenten toont dit het huidige tijdstip. Intern gebruikt het de system call time. Dit is een nog eenvoudiger C-programma om zelf te schrijven, te compileren en uit te voeren. En het laat toe, alles wat tot nu toe is gezegd te verifiëren:

#include <time.h>   /* voor de declaraties van time() en time_t */
#include <stdio.h>  /* voor de declaratie van printf() */
int main() {
  time_t t = time(0); /* "nu" */
  printf("%ld\n", t);
}

Er zijn nog Unix-commands die op één of andere manier timestamps gebruiken. Er is b.v. het commando touch waarmee het "laatst gewijzigd" tijdstip van een bestand kan ingesteld worden; het converteert een tijdstip in een voor ons leesbare vorm, meegegeven op de opdrachtregel, naar een Unix-tijd-waarde (seconden sinds de Epoch) die het doorgeeft aan de system call utimes.

Er is ook het tar-commando dat een archiver is: het verpakt een aantal bestanden in één enkel archiefbestand. Interessant in de context van dit artikel is het feit dat tar ook de bestands-metadata samen met de bestandsgegevens in dat archief opslaat. D.w.z.: eigenaar/groep, bestandsrechten, en natuurlijk het tijdstip van laatste wijziging. Uiteraard slaat het die timestamp op als het aantal seconden sinds de Epoch. Verrassend misschien doet het dit in een tekstuele, octale vorm; dus zoek in een tar-bestand naar een 11-cijferig getal dat begint met 136, tenminste als het bronbestand een timestamp heeft tussen ongeveer 23 december 2019 en 3 juli 2020:

touch -t 202001141200.00 X.txt  #  maak een leeg bestand met timestamp 2020-01-14 12:00
tar cf X.tar X.txt              #  maak het tar-bestand X.tar aan, dat X.txt bevat
strings X.tar                   #  bekijk de "tekst-leesbare fragmenten" van dat bestand

Op de 6de uitvoerregel van de laatste opdracht verschijnt de octale tekenreeks 13607317460, wat decimaal 1578999600 is, of 1577836800+1162800. Aangezien 1577836800 overeenkomt met 1 januari 2020 middernacht UTC, en 1162800=13*24*60*60+11*60*60 = 13 dagen + 11 uur, komt dit inderdaad overeen met 14 januari 11:00 UTC of 12:00 in "onze" tijdszone +01:00.

Conclusie

Vijftig jaar is een lange tijd in IT. Heel lang. Dit maakt het des te opmerkelijk dat een keuze die bijna 50 jaar geleden werd gemaakt, nog steeds bestaat. En waarschijnlijk nog lang na het jaar 2038 zal bestaan ...

Maar wacht misschien niet zo lang om wat meer over Unix of Linux te leren: we hebben een aantal cursussen in ons aanbod om u hierbij op weg te helpen!