UNIX Time - Predstavljanje datuma i vremena na računarima
Uvod
Posle prethodnog članka u kome smo se bavili problematikom predstavljanja znakova u računarskim sistemima, reklo bi se da je diskusija o formatima za predstavljanje datuma i vremena, sledeći prirodan korak.
Sa računarskim kalendarima (a pogotovo sa časovnicima), susrećemo se svakodnevno, i pri tom deluje da računarski kalendari i časovnici funkcionišu vrlo slično kao kalendari i časovnici iz spoljnjeg sveta, ali, uz podrobnije udubljivanje u problematiku, nije teško uvideti da predstavljanje vremena na računarima nije 'baš skroz trivijalan' zadatak ....
Osnovna problematika
Ako se usmerimo na 'uobičajeni' zapis vremenskog trenutka, u odnosu na početak Nove ere ....
07.08.2021.
18:12:46
.... jasno se mogu zapaziti dva podatka:
- datum (i)
- vreme
.... pri čemu su u pitanju složeni podaci, od kojih se svaki, naizgled, može dalje raščlaniti na nekoliko celobrojnih vrednosti (datum - na dan, mesec i godinu; vreme - na sat, minut i sekund).
Isti podaci se mogu predstaviti i drugačije ....
07. avgust 2021.
18:12:46
.... a ako bismo izabrali format koji bi operativni sistem tipično koristio da nije preveden na srpski jezik, prikaz bi ponovo bio drugačiji:
Sunday August 07 2021
06:12:46PM
Osnovni podatak (vremenski trenutak), uvek je isti, ali, u širem kontekstu, ako onaj ko tumači podatke nije upoznat sa formatom zapisa, vrlo lako može doći do zabune.
Zabuna najčešće nastaje sa mesecima i danima, kada je redni broj dana manji od 12, ali (u praktičnom smislu), nije previše teško uočiti razlike:
07.08.- dan je na prvom mestu, mesec na drugom; za razdvajanje se koristi znak.08/07- na prvom mestu je mesec, na drugom mestu je dan; za razdvajanje se koristi znak/
U svakom slučaju (tj. u oba primera) - datum je "sedmi avgust".
Međutim, usled navika - pogotovo kada se podaci unose i obrađuju 'ručno' i 'brzinski' (a posebno u situacijama kada je količina podataka za obradu velika pa u svemu i zamor postane faktor) - neretko dolazi do grešaka ako korisnik naiđe na format na koji nije naviknut.
Nedoumice bi se (naravno) mogle rešiti upotrebom međunarodnog standarda koji nalaže da se u zapisu datuma redom navode: godina, mesec i (na kraju) dan, kao i to da se sati beleže isključivo u formatu od 0 do 23 ....
2021-08-07
18:12:46
.... ali, praksa (očekivano) pokazuje da se različite države (odnosno, različite kulture), ne odriču tek tako navika stečenih tokom (doslovno) više stotina godina.
Ipak, ono što smo naveli do sada, nije jedini (pa čak ni osnovni) problem koji se tiče 'tehnikalija'.
Kako god da treba formatirati podatke u ispisu, klasa za beleženje datuma i vremena koja bi bila definisana u sledećem obliku ....
public class DatumVreme
{
public Int32 Dan, Mesec, Godina,
Sat, Minut, Sekund;
public DatumVreme(Int32 Godina, Int32 Mesec, Int32 Dan,
Int32 Sat, Int32 Minut, Int32 Sekund)
{
this.Godina = Godina;
this.Mesec = Mesec;
this.Dan = Dan;
this.Sat = Sat;
this.Minut = Minut;
this.Sekund = Sekund;
}
public String FormatiranjeDatum()
{
return Dan.ToString() + "-" +
Mesec.ToString() + "-" +
Godina.ToString() + "\r\n";
}
public String FormatiranjeVreme()
{
return Sat.ToString("00") + ":" +
Minut.ToString("00") + "-" +
Sekund.ToString("00") + "\r\n";
}
}
class DatumVreme
{
public:
int Dan, Mesec, Godina,
Sat, Minut, Sekund;
DatumVreme(int Godina, int Mesec, int Dan,
int Sat, int Minut, int Sekund)
{
this.Godina = Godina;
this.Mesec = Mesec;
this.Dan = Dan;
this.Sat = Sat;
this.Minut = Minut;
this.Sekund = Sekund;
}
string intUString(int n, int p)
{
string s = "", t = "";
int b = 0;
while (n > 0) {
int c = n % 10 + 48;
n /= 10;
s = (char)c + s;
b++;
}
while (b < p) {
t += "0";
p--;
}
return t + s;
}
string FormatiranjeDatum()
{
return intUString(Dan, 2) + "." +
intUString(Mesec, 2) + "," +
intUString(Godina, 0) + "\r\n";
}
string FormatiranjeVreme()
{
return intUString(Sat, 2) + "." +
intUString(Minut, 2) + "," +
intUString(Sekund, 2) + "\r\n";
}
}
public class DatumVreme {
public int Dan, Mesec, Godina,
Sat, Minut, Sekund;
public DatumVreme(int Godina, int Mesec, int Dan,
int Sat, int Minut, int Sekund)
{
this.Godina = Godina;
this.Mesec = Mesec;
this.Dan = Dan;
this.Sat = Sat;
this.Minut = Minut;
this.Sekund = Sekund;
}
String FormatiranjeDatum() {
return String
.format("%2d-%2d-%d", Dan, Mesec, Godina)
.replace(" ", "0");
}
String FormatiranjeVreme() {
return String
.format("%2d:%2d:%2d", Sat, Minut, Sekund)
.replace(" ", "0");
}
}
# U primerima u ovom članku, objekte u Python-u
# razmenjivaćemo bez posredničkih klasa
// U primerima u ovom članku, objekte u JavaScript-u
// razmenjivaćemo bez posredničkih klasa
.... predstavljala bi adekvatan i univerzalan način da se podaci čuvaju i naknadno ispisuju u bilo kom željenom formatu (mi smo izabrali format sa prve slike), međutim, ovakav 'prirodni' ('ljudski'/'kalendarski') način predstavljanja vremenskih trenutaka, ne omogućava jednostavno poređenje, sabiranje i oduzimanje datuma i vremena, a to su operacije koje se na računarima širom sveta obavljaju više stotina miliona puta - u toku svakog dana (procena učestalosti je više nego konzervativna).
Sa druge strane, ako se datum i vreme svedu na broj sekundi koje su protekle od .... "nekog trenutka", različiti datumi se lako mogu porediti / sabirati / oduzimati ....
I jedan i drugi pristup imaju: i prednosti, i nedostatke, međutim (u praktičnom smislu), kao osnovni format koristi se "broj sekundi", ali, beleže se i ostali podaci. *
U najosnovnijem smislu, beleženje (i interpretacija) datuma, funkcioniše na sledeći način ....
S obzirom na to da jedan minut predstavlja 60 sekundi, vreme 01:30 može se zapisati kao 90 (sekundi), dok se vreme 01:01:30 može zapisati kao 3690 (sekundi), pri čemu smo računali da jedan sat predstavlja 60 minuta, odnosno, 3600 sekundi.
Sve dok je broj sekundi relativno mali (pogotovo ako je osetno manji od 86400, što predstavlja broj sekundi u jednom danu), neće biti problema sa poređenjem, dodavanjem, oduzimanjem i pretvaranjem intervala iz oblika "celobrojne promenljive koja predstavlja ukupan broj sekundi", u zapis sati, minuta i sekundi.
Međutim, ako je broj sekundi znatno veći (recimo - pet miliona), postavlja se pitanje šta takav podatak zapravo predstavlja?
Prethodno navedena vrednost, uvek će biti: 57 dana, 20 sati, 53 minuta i 20 sekundi, ali, ako data vrednost treba da predstavlja konkretan kalendarski datum, mora se precizirati šta u 'odbrojavanju sekundi' znači vrednost 0.
Još koja reč o prednostima i nedostacima različitih formata
Pre nego što pređemo na detaljnu diskusiju o osnovnom formatu za zapis datuma i vremena, koji se tipično koristi na računarima, osvrnimo se još jednom (ukratko) na prednosti i nedostatke različitih formata.
Osnovni nedostatak zapisa vremenskog trenutka u obliku (mili)sekundi koje su protekle od nekog (manje-više proizvoljno izabranog) datuma, tiče se "neprirodnosti" takvog postupka, dok je osnovni nedostatak zapisa datuma i vremena preko šest nezavisnih podataka (GGGG-MM-DD SS-Mi-ss) - otežano obavljanje operacija poređenja, sabiranja, oduzimanja i sl.
Prednosti formata "dan-mesec-godina sat-minut-sekund" su: preglednost i prirodnost (s tim da je najverovatnije samo u pitanju stvar navike).
Što se tiče prednosti formata koji podrazumeva 'odbrojavanje sekundi' (ili milisekundi), ne moramo tražiti bolji primer od pretrage u bazama podataka: ako su datumi u bazi zapisani na "prirodan"/"ljudski" način (preko više nezavisnih celobrojnih vrednosti), pretraga slogova koji su vezani za datume pre ili posle određenog datuma, neće biti trivijalan zadatak, dok - ako su vremena i datumi zapisani preko jedinstvene celobrojne vrednosti, nije teško urediti 'indeks' - strukturu podataka koja omogućava da se slogovi efikasno pretražuju preko 'vremenskih trenutaka', uz upotrebu hash mapa ili dobro poznate binarne pretrage.
Kao što smo već nagovestili, u praksi (u klasama za zapis vremenskih trenutaka), tipično se definišu svi navedeni podaci, s tim da je timestamp (broj sekundi proteklih od početka epohe) * - glavni podatak, a ostala polja se popunjavaju preko funkcija za pretvaranje timestamp-a u format koji je prilagođen ljudima. **
U prethodnom primeru, ako bi se podatak 5 x 106 pojavio samostalno (pri čemu bi takav podatak "nekako" trebalo da predstavlja datum - a dodatnih informacija nema), može se pretpostaviti da odbrojavanje počinje od 01. januara u ponoć, i stoga nije teško ustanoviti da se broj sekundi 5 x 106 poklapa sa 27. februarom (što takođe predstavlja 58. dan u godini), međutim - i dalje nije poznato:
- kojoj godini pripada navedeni datum
- šta bi (inače) bilo sa (npr) 78. danom u godini - da li bi datum bio 18.03. ili 19.03. (jer nije poznato da li je godina prestupna)
UNIX timestamp
U računarskim sistemima, termin timestamp (u prevodu sa engleskog - "vremenski žig"), predstavlja broj sekundi između "početka epohe" i određenog (proizvoljnog) trenutka, iz čega praktično proizilazi da se problematika predstavljanja proizvoljnog datuma i vremena u obliku koji je razumljiv ljudima (i prilagođen određenoj kulturi, u smislu dan.mesec.godina., ili mesec/dan/godina", sa ili bez vodećih nula i sl), svodi se na beleženje i interpretaciju timestamp-a.
U prethodno navedenom kontekstu, početak epohe (timestamp 0), može označavati različite datume, pri čemu u računarskoj tehnici postoji izvestan broj karakterističnih datuma koji se koriste u različitim sistemima:
- 01.01.AD - .NET
- 01.01.1601. - NTFS, COBOL, Windows
- 01.01.1900. - Network Time Protocol
- 01.01.1970. - UNIX time (takođe se koristi i u GNU/Linux distribucijama, operativnim sistemima koji su zasnovani na BSD-u, kao i u većini programskih jezika)
Kao veliki poštovaoci Linux-a (a takođe i programskih jezika :)), za upoznavanje sa formatom za predstavljanje datuma i vremena na računarima, izabrali smo upravo UNIX timestamp.
Predstavljanje datuma posle 01.01.1970.
Prethodno je navedeno da UNIX epoha počinje u ponoć 01.01.1970. po UTC-u, * što praktično znači da timestamp koji smo koristili (5000000), odgovara datumu 27.02.1970. i vremenu 20:53:20.
Za početak smo razmotrili timestamp koji se intuitivno može pretvoriti u poznati format koji koristimo svakodnevno, ali, da bismo se što bolje upoznali sa postupkom, u nastavku ćemo koristiti timestamp-ove 50 x 106 i 150 x 106 ("malo više nula i jedinica" i, što je važnije (zarad boljeg razumevanja tematike), u pitanju su timestamp-ovi koji se ne uklapaju "u istu godinu sa timestamp-om 0").
Osnovne postavke algoritma objasnićemo preko timestamp-a 50 x 106 (direktno u nastavku), timestamp 150 x 106 će poslužiti kao primer pri određivanju godine, nakon čega ćemo se vratiti na timestamp (50 x 106), koji će poslužiti i kao primer pri određivanju datuma ....
U svakom slučaju, prvo treba podeliti timestamp sa 86400 * - i potom treba dodati 1. **
Umesto dodavanja jedinice, u implementaciji se prosto može napisati:
Int32 BrojDana = Math.Ceiling(timestamp / 86400);
#include<cmath>
int brojDana = ceil(timestamp / 86400);
int brojDana = Math.ceil(timestamp / 86400);
import math
brojDana = math.ceil(timestamp / 86400)
let brojDana = Math.ceil(timestamp / 86400);
Preko gornje formule, pravilno smo odredili koliko je dana proteklo od 01.01.1970 u ponoć. **
Za određivanje tačnog datuma, moguće je koristiti jednu od dve opcije:
- pomoćne tabele
- prilično komplikovanu aritmetiku ***
(Za početak ćemo se držati jednostavnijeg algoritma koji podrazumeva korišćenje pomoćnih tabela.)
Kada se 50 miliona podeli sa 86400, dobija se 579 (u smislu: 578 + 1) - što predstavlja broj 'započetih dana', posmatrano u odnosu na početak 1970.
Kada se od broja 579 oduzme 365 (1970. nije bila prestupna godina), dobija se 214, a budući da smo (samo) jednom oduzimali broj dana u godini (pri čemu je preostala razlika koja je manja od broja dana u godini), može se zaključiti da timestamp odgovara godini - 1971.
Sa jedne strane, godinu smo odredili prilično proizvoljno/"odokativno", ali (sa druge strane), pretpostavljamo da čitaocima ne bi bilo teško da samostalno osmisle i univerzalni algoritam za "oduzimanje godina", međutim (kao što smo nagovestili), svakako ćemo o navedenom algoritmu detaljnije prodiskutovati.
Određivanje godine
U svojstvu primera (pri određivanju godine), privremeno ćemo koristiti timestamp 150 x 106, kome odgovara godina koja se (očigledno) ne poklapa, ni sa 1970, ni sa 1971.
Kada se timestamp podeli sa 86400 (što u novom primeru daje rezultat 1737 (tj. 1736 dana + 1 dan)), potrebno je redom oduzimati 365 ili 366 (dana), za svaku godinu, sve dok broj dana ne postane manji ili jednak ("mogućem") broju dana u godini:
- 1970: 1737 - 365 = 1372 -> prelazak u 1971.
- 1971: 1372 - 365 = 1007 -> prelazak u 1972.
- 1972: 1007 - 366 * = 641 -> prelazak u 1973.
- 1973: 641 - 365 = 276 -> prelazak u 1974.
- 1974: 276 je manje od 365 (i stoga nema daljeg oduzimanja) -> godina 'ostaje' 1974.
Zarad prikaza procedure za određivanje datuma (koja sledi), vraćamo se na glavni primer, timestamp 50 x 106.
Prethodno smo ustanovili da navedenom timestamp-u odgovara godina 1971, ustanovili smo takođe da je preostalo još 214 dana, i potrebno je ustanoviti datum (tj. dan i mesec).
Određivanje datuma
Opšti postupak preko koga se može ustanoviti sa kojim datumom se poklapa određeni dan u godini, sastoji se iz sledećih koraka:
- za svaki mesec (redom, počevši od januara), potrebno je proveriti da li je ukupan broj 'preostalih' dana, veći od zbira: dana u trenutnom mesecu, i ukupnog broja dana u prethodnim mesecima
- ako je ukupan broj dana veći od zbira, prelazi se u sledeći mesec, a algoritam se vraća na prethodni korak (ispitivanje uslova)
- ako je ukupan broj dana manji ili jednak, mesec se određuje shodno tome koliko puta je algoritam 'prešao u sledeći mesec', a dan se računa kao razlika ukupnog broja dana i ukupnog broja dana za prvih n meseci, pri čemu važi:
n = pronađeni_mesec - 1
Postupak se lakše može razumeti preko primera (koji već koristimo):
- ukupan broj dana (214), veći je od 31 (broj dana u 1. mesecu), što znači da traženi datum nije u januaru
- ukupan broj dana (214), veći je i od 59 (28 dana u 2. mesecu (s obzirom na to da godina nije prestupna) + broj dana u prethodnim mesecima (31)), što znači da traženi datum nije ni u februaru
- ukupan broj dana (214), veći je i od 90 (31 dan u 3. mesecu + broj dana u prethodnim mesecima (59)), što znači da traženi datum nije ni u martu ....
Preskočićemo određeni raspon meseci (da primer ne bi postao 'zamoran' za praćenje) i, na kraju, pošto je postupak 'prelaženja u sledeći mesec' ponovljen 7 puta, ustanovili smo sledeće:
- 214. dan 1971. pripada avgustu
- u pitanju je 2. dan u mesecu *
Kao što smo već pomenuli, postupak se može uprostiti preko pomoćne tabele koja odgovara mesecima u godini (u ovom slučaju, 1970) ....
/* ----- 1970 ----- */
// 0 1 2 3 4 5 6 7 8 9 10 11 12
// 0 31 28 31 30 31 30 31 31 30 31 30 31
[ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]
- prvi red (komentar), sadrži redni broj meseca, tj. indekse niza
- drugi red (takođe komentar), sadrži broj dana u svakom mesecu
- treći red sadrži broj celih dana između početka godine i kraja određenog meseca
Pažljivom pretragom tabele, lako se pronalaze neophodne informacije:
- mesec se nalazi na prvom indeksu koji odgovara prvoj vrednosti iz trećeg reda, koja je veća od broja dana (u primeru: 243 je prva vrednost veća od 214, i poklapa se sa 8. mesecom)
- dan u mesecu dobija se oduzimanjem broja dana (214) i vrednosti iz prethodne kolone (212).
Preko (idejno) slične tabele, može se uprostiti i postupak pronalaženja godine:
// 0 (1970) 1 (1971) 2 (1972) 3 (1973) 4 (1974) ....
// 365 365 366 365 365 ....
[ 365, 730, 1096, 1461, 1826 .... ]
Naravno, da bi postupak imao pravi smisao, pretraga mora biti efikasna (tj. nikako ne treba koristiti linearnu pretragu i sl).
Pogledajmo usput i nekoliko zanimljivih kombinacija timestamp-a i datuma:
31536000 - 01.01.1971. 00:00:00
946684800 - 01.01.2000. 00:00:00
1000000000 - 09.11.2001. 03:46:40
1234567890 - 14.02.2009. 00:31:30
Predstavljanje datuma pre 01.01.1970.
Uz napomenu da razumemo, da predstavljanje datuma pre početka UNIX epohe (koje odlikuju negativne vrednosti timestamp-a), može delovati pomalo neintuitivno pri prvom susretu, primetićemo da je postupak i dalje veoma sličan u idejnom smislu, ali, obavlja se "u obrnutom smeru".
Postupak ćemo objasniti na primeru timestampa -35 x 106 (-35000000):
- ovoga puta, pri deljenju sa
86400, ne dodaje se 1. - bilo koja vrednost manja od 0, automatski znači da se obavezno prelazi unazad bar u 1969. godinu, a budući da je dobijeni količnik
405veći od 365, znači da smo se vratili u 1968. - kada se od
405oduzme365, preostaje40dana. - budući da decembar ima 31 dan, posle oduzimanja (40 - 31), dobija se razlika
9i može se zaključiti da je mesec koji odgovara datumu -novembar. - dan u mesecu, dobija se kada se od ukupnog broja dana u pronađenom mesecu (30), oduzme preostali broj dana (9), i stoga je traženi datum:
21.11.1968.
Zanimljivi istorijski fenomeni vezani za predstavljanje vremena na računarima
Pre nego što pređemo na implementaciju algoritama koje smo prethodno opisivali, osvrnućemo se usput na dva zanimljiva fenomena koji se tiču zapisa vremena na računarima ....
Problem 2000 ("Milenijumska buba"/Y2K)
Nekada čuvena i ne-baš-malo zabrinjavajuća "milenijumska buba"/"Y2K" (potencijalna katastrofa koja je mogla nastati na samom početku 2000. godine zbog neadekvatnog formata podataka u stari(ji)m programima), predstavlja jedan od zanimljivijih fenomena u računarskoj istoriji (koga se stariji čitaoci, verujemo, sećaju - iako je sve donekle zaboravljeno u međuvremenu), ali, u pitanju je situacija koja nije imala toliko veze sa UNIX timestamp-om, ili nekim drugim, koliko sa opštim merama uštede pri zapisivanju datuma.
Problematika je (ukratko) sledeća: usled izrazito velikih cena memorije u ranom periodu eksploatacije računara, programeri su bili prinuđeni da se 'dovijaju' na razne načine kako bi što bolje iskoristili skromne memorijske kapacitete, i - u kontekstu "praktično neizbežnih mera", * "stradao" je (između ostalog) zapis datuma, odnosno (da budemo precizni), zarad uštede je odlučeno da pri beleženju datuma neće biti korišćen četvorocifreni zapis godina ("1958"), već dvocifreni ("58"), pri čemu će se prve dve cifre ("19") - "podrazumevati".
Verujemo da bi mnogi čitaoci mogli pomisliti da je u prethodno navedenim okolnostima jedno od (naj)češće postavljanih pitanja bilo: "šta će se desiti kada godina dođe do '00'", međutim, zanimljivo je primetiti da takvom pitanju naizgled nije pridavan veći značaj (tokom jednog dužeg vremenskog perioda).
U prvo vreme, datumi su beleženi samo zarad ažurnosti i nije se očekivalo da će tako zabeleženi datumi učestvovati u bilo kakvom "odlučivanju"; na primer: u obračunavanju kamate na bankovnim računima shodno datumu, u pokretanju industrijskih procesa u određeno vreme ili drugim sličnim poduhvatima, ali (kao što verovatno već očekujete da ćemo navesti), nedugo posle prvih godina, računari su počeli da se koriste za kontrolu procesa u najrazličitijim oblastima ljudske delatnosti (ekonomija, industrija, energetika, zdravstvo, saobraćaj i manje-više sve ostalo što nekome može pasti na pamet), zabeleženi datumi su postali "faktor odlučivanja", a problem je (bio) - u tome što format zapisa datuma nije promenjen.
Zarad preciznosti, navedimo šta je tačno problem u tehničkom smislu: iako su u međuvremenu mnogi od starih programa povučeni iz upotrebe (doduše, mnogi nisu, i postoji procena da je krajem veka u opticaju ostalo više desetina (pa čak i stotina) hiljada starih programa "na bitnim mestima"), podaci u bazama podataka - i dalje su bili zapisani onako kako smo već naveli - sa godinom koja je definisana preko dve cifre (umesto sve četiri).
U praktičnom smislu, moramo (bar donekle) razumeti ljude sa kraja pedesetih i početka šezdesetih godina 20. veka. Programeri i drugi stručnjaci svakako su ('još onda') primetili da postoji potencijalni problem koji bi se mogao manifestovati dolaskom 2000. godine, ali, na raspolaganju nije bilo dovoljno memorije (ni iz daleka), očekivalo se da prvobitni programi neće dugo ostati u opticaju, ili - ukoliko programi 'eventualno' ostanu u opticaju - prilično iskreno se verovalo u to da će buduće generacije programera biti u stanju da reše sve probleme bez iole veće muke, koristeći se "superiornim tehnologijama budućnosti".
Da se izrazimo malo jednostavnije: usled kombinacije iskrenog, ali ne-baš-preterano-utemeljenog optimizma, i (naravno) lenjosti - koja je često "pritajeni faktor" u mnogim velikim poduhvatima - računalo se da za sve "ima vremena".
Međutim, mnogi od prvobitnih programa iz početnog perioda ostali su (tj. 'opstali su') u upotrebi mnogo duže nego što se očekivalo, kraj veka se približavao i, u drugoj polovini devedesetih, "već" se uveliko razmišljalo o svemu.
Šta je zapravo moglo da se desi dolaskom 2000?
U pravom smislu reči, odgovor na prethodno pitanje - niko ne zna, jer u svemu ima previše faktora (tj. previše "varijabli").
I dalje ostaju samo nagađanja, ali (u osnovnom smislu), pitanje je bilo: kako će računari odreagovati na godinu sa "dve nule na kraju" (da li će godina biti shvaćena kao "2000", ili kao "1900")?!
Dvocifreni zapis godina nije problematičan kada je (na primer), u bazi podataka, za određenu osobu kao godina rođenja upisano "68" a kao trenutna godina očitava se "87" - i vreme je da osoba čiji se podaci razmatraju krene na fakultet, ali, šta ako "premotamo film" dvadeset godina unapred, dospemo u godinu "07" (namerno nećemo reći "2007"), i vreme je da se pozove nova generacija đaka u 1. razred?!
Što se računarskih sistema tiče, i dalje "nema problema": u bazama podataka će biti pronađene sve osobe čija je godina rođenja zabeležena kao "00", i svoj deci rođenoj 2000. uredno će biti prosleđeni pozivi za upis u osnovnu školu.
Ostaje samo pitanje - da li će takvim pozivom biti obuhvaćene i osobe koje su rođene 1900 (budući da osobe rođene 1900. takođe imaju godinu rođenja zabeleženu kao "00")?!
Šalimo se (donekle) i znamo naravno da se pomenuti događaji nisu odigrali u praksi, ali, scenario jeste bio moguć i slična razmatranja bila su omiljena zabava mnogih analitičara i novinara krajem devedesetih godina (a bilo je i karikatura koje prikazuju bake i deke kako zajedno sa dečicom polaze u školu, muške bebe sa sedim bradama i sl).
Za još malo 'istorijskog konteksta', napomenimo da je već sredinom osamdesetih bilo 'glasova' koji su (u nešto većoj meri) skretali pažnju na to da bi ipak trebalo "preduzeti nešto po pitanju 2000", * međutim, tek je u drugoj polovini devedesetih situacija za prave "dobila na značaju", a u poslednjih nekoliko godina koje "počinju sa 19", ** zabrinutost je bila legitimna i bilo je brojnih pojedinaca koji civilizaciji, koja (već veoma ozbiljno) zavisi od računara, nisu predviđali svetlu budućnost.
U 'poslednjim godinama', bilo je čak i pojedinaca koji su se spremali na "scenario kataklizme" (gomilanje namirnica, opremanje atomskih skloništa i sl), mediji su situaciju koristili da preko senzacionalističkih naslova povećaju tiraž, ali, na kraju se sve završilo - ne samo bez kataklizmičkih posledica (i/ili "slanja starijih sugrađana u školske klupe" i sl), već - bez gotovo ikakvih iole ozbiljnijih primećenih incidenata.
Naravno, problemi se nisu rešili "sami od sebe".
"Iza zavese", brojni programeri širom sveta bavili su se saniranjem zastarelih kodova, radilo se vredno, u sve je uloženo i mnogo novca (procenjuje se da je u pitanju manji broj stotina milijardi dolara), šteta je (ipak) predupređena, i čovečanstvo nije doživelo predviđenu kataklizmu (bar ne onu koja se ticala kraha računarskih sistema).
Problem 2038.
Kao što je 'milenijumska buba' pre početka 2000. ponešto zadavala glavobolje korisnicima računara i stručnjacima koji su učestvovali u rešavanju problema - i izazivala znatiželju, za 2038. godinu i UNIX timestamp vezuje se donekle slična, ali, ipak mnogo manje dramatična situacija.
U četvrtak, 19. januara 2038, u 03:14:07 po UTC-u, doći će do resetovanja 32-bitnih brojača koji su zaduženi za UNIX timestamp.
U pomenutom trenutku, brojači će dostići vrednost 2147483647, što predstavlja najveću vrednost koja se može dodeliti označenoj 32-bitnoj celobrojnoj promenljivoj.
I ovoga puta, za očekivati je da sve kritične (i ostale iole bitne) implementacije, budu "zakrpljene", što u većini slučajeva podrazumeva prebacivanje na 64-bitne brojače (mnogo toga već jeste urađeno, a vremena za sada ima još uvek više nego dovoljno).
Prelaskom na 64-bitne brojače, onaj deo čovečanstva koji (u smislu računarskih potreba) ima veze sa UNIX timestamp-om, moći će ipak da 'odahne' (usudićemo se da kažemo da je tako). :)
Naime, 64-bitni brojači će omogućiti inkrementaciju timestamp-ova u sledeće ~292 milijarde godina!
Računica koja se (u opštem smislu) tiče implementacije timestamp brojača preko različitih veličina celobrojnih promenljivih, vrlo je zanimljiva:
- 16-bitni timestamp ne omogućava odbrojavanje sekundi ni u trajanju od jednog dana (65535 < 86400)!
- preko 32-bitnog timestampa, moguće je odbrojavati sekunde u trajanju od ~68 godina
- preko 64-bitnog timestampa, moguće je odbrojavati sekunde u trajanju od .... tolikom da većina astronoma predviđa kraj Sunčevog sistema i ljudske civilizacije, mnogo (mnogo!) pre nego što 64-bitni brojači dođu do kraja (konkretna vrednost je: prethodno pomenutih ~292 x 109 godina)
Ako do sada niste razumeli razliku u smeštajnom kapacitetu 16-bitnih, 32-bitnih i 64-bitnih promenljivih, verujemo da je sada jasnije. 🙂
Algoritmi
Kao što smo ranije nagovestili, u nastavku ćemo se pozabaviti nekolicinom popularnih algoritama koji se tiču obrade datuma i vremena na računarima (pri čemu ćemo najviše pažnje posvetiti, na kraju poglavlja, implementaciji algoritma za pretvaranje timestamp-a u strukturu podataka koja prikazuje datum i vreme).
Algoritam #1 - Kreiranje tajmera
Za početak ćemo razmotriti algoritam preko koga se lako može uvideti očigledna prednost upotrebe timestamp-a pri beleženju vremenskih trenutaka.
Pretpostavićemo da je potrebno napraviti tajmer koji prikazuje koliko je sati : minuta : sekundi proteklo od trenutka kada je tajmer pokrenut.
Zadatak se rešava na jednostavan način ....
Preko promenljive t1, očitava se timestamp koji odgovara početnom trenutku ....
DateTime t1 = DateTime.Now;
#include<ctime>
time_t t1, = time(NULL);
LocalDateTime t1 = LocalDateTime.now();
t1 = datetime.datetime.now()
let t1 = Date.now();
// Prvo očitavanje
// (JavaScript dodaje i milisekunde na timestamp)
.... i potom, svaki put kada je potrebno prikazati proteklo vreme, očitava se novi timestamp:
DateTime t2 = DateTime.Now; // novo očitavanje
time_t t2 = time(NULL);
LocalDateTime t2 = LocalDateTime.now(); // novo očitavanje
t2 = datetime.datetime.now() # novo očitavanje
let t2 = Date.now(); // novo očitavanje
Prostim oduzimanjem dve vrednosti, dobija se vreme u sekundama (koje je proteklo između dva očitavanja), a preko pomoćne funkcije, izračunata vrednost se lako može predstaviti u obliku sati, minuta i sekundi:
TimeSpan r = t2 - t1;
// Struktura se može direktno ispisati,
// ali, može se koristiti i sledeći kod:
Int32 r = t2.Second - t1.Second;
DatumVreme FormatiranjeTajmer(Int32 t)
{
Int32 sat = t / 3600;
Int32 min = t % 3600 / 60;
Int32 sec = t % 60;
return new DatumVreme(0, 0, 0, sat, minut, sekund);
}
double r = difftime(t2, t1);
DatumVreme FormatiranjeTajmer(int t)
{
int sat = t / 3600;
int min = t % 3600 / 60;
int sec = t % 60;
return DatumVreme(0, 0, 0, sat, minut, sekund);
}
int r = t2.getSecond() - t1.getSecond();
DatumVreme formatiranjeTajmer(int t) {
int sat = t / 3600;
int min = t % 3600 / 60;
int sec = t % 60;
return new DatumVreme(0, 0, 0, sat, minut, sekund);
}
r = t2 - t1;
# Rezultat se može ispisati direktno
let r = t2 - t1;
function formatiranjeTajmer(t) {
t /= 1000;
let sat = parseInt(t / 3600);
let min = parseInt(t % 3600) / 60;
let sec = t % 60;
return {
Sat: sat,
Minut: min,
Sekund: sec
}
}
Algoritam #2 - pretvaranje timestamp-a u format sat:minut:sekund
Iako smo se najviše bavili određivanjem datuma, prikazaćemo i kako se može izračunati vremenski trenutak u formatu sat:minut:sekund, ukoliko je timestamp manji od 86400:
sat = timestamp / 3600;
minut = (timestamp % 3600) / 60;
sekund = timestamp % 60;
sat = timestamp / 3600;
minut = (timestamp % 3600) / 60;
sekund = timestamp % 60;
sat = timestamp / 3600;
minut = (timestamp % 3600) / 60;
sekund = timestamp % 60;
sat = timestamp / 3600
minut = (timestamp % 3600) / 60
sekund = timestamp % 60
sat = timestamp / 3600;
minut = (timestamp % 3600) / 60;
sekund = timestamp % 60;
Algoritam #3 - Računanje starosti osobe
Razmotrićemo i jedan veoma uobičajen algoritam u kome direktna upotreba timestamp-a nije od prevelike pomoći, već je praktičnije operisati nad strukturom "godina-mesec-dan".
Kada je u pitanju određivanje starosti osobe, prostim oduzimanjem timestamp-a koji odgovara trenutku rođenja, od trenutnog timestamp-a, može se dobiti 'broj dana', ali, pitanje je šta sa takvom vrednošću dalje raditi?!
Ako se broj dana koji je prethodno izračunat 'nalepi' na početak UNIX epohe, ni u kom slučaju nećemo dobiti korektan rezultat, a ako ostavimo dobijenu razliku tako da nije povezana sa rasporedom prestupnih i neprestupnih godina - rezultat takođe neće biti korektan.
Umesto svega što smo naveli, praktičnije je da se prvo dva timestamp-a pretvore u strukturu datuma koja se koristi svakodnevno ....
/* ----- trenutni datum ----- */
07.08.2021.
/* ----- datum rođenja ------ */
18.08.1979.
.... nakon čega sledi postupak koji je 'prirodan i uobičajen'.
Broj godina lako se dobija prostim oduzimanjem godine rođenja od trenutne godine (2021 - 1979 = 42), posle čega samo još preostaje pitanje, da li dobijenu vrednost treba umanjiti za 1 (ili ne treba).
Dobijeni rezultat umanjuje se za 1 u sledećim okolnostima:
- ako je trenutni mesec manji od meseca rođenja (ili)
- ako je trenutni mesec isti kao mesec rođenja - i pri tom je dan manji od dana rođenja
Pošto smo razmotrili sve bitne detalje algoritma, možemo implementirati funkciju:
Int32 racunanjeStarosti(DatumVreme d1, DatumVreme d2) {
Int32 s = d2.Godina - d1.Godina;
if (d2.Mesec > d1.Mesec) return s;
if (d2.Mesec < d1.Mesec) s--;
if (d1.Dan < d2.Dan) s--;
return s;
}
int racunanjeStarosti(DatumVreme d1, DatumVreme d2)
{
int s = d2.Godina - d1.Godina;
if (d2.Mesec > d1.Mesec) return s;
if (d2.Mesec < d1.Mesec) s--;
if (d1.Dan < d2.Dan) s--;
return s;
}
int racunanjeStarosti(DatumVreme d1, DatumVreme d2) {
int s = d2.Godina - d2.godina;
if (d2.Mesec > d1.Mesec) return s;
if (d2.Mesec < d1.Mesec) s--;
if (d1.Dan < d2.Dan) s--;
return s;
}
def racunanjeStarosti(d1, d2):
s = d2.godina - d1.godina
if d2.mesec > d1.mesec return s
if d2.mesec < d1.mesec: s = s - 1
if d1.dan < d2.dan: s = s - 1
return s
function racunanjeStarosti(d1, d2) {
let s = d2.godina - d1.godina;
if (d2.mesec > d1.mesec) return s;
if (d2.mesec < d1.mesec) s--;
if (d1.dan < d2.dan) s--;
return s;
}
Na kraju, pogledajmo i algoritam za pretvaranje timestamp-a u datum (koji je u celokupnoj diskusiji najbitniji).
Algoritam #4 - pretvaranje UNIX timestamp-a u datum
Iako smo se sa algoritmom za pretvaranje timestamp-a u datum već upoznali (u najvećoj meri), ipak ćemo se ponovo osvrnuti na osnovne ideje pre nego što pređemo na implementaciju:
- timestamp se prvo deli sa 86400, čime se dobija broj 'započetih dana' (posmatrano u odnosu na 1.1.1970. u ponoć)
- računanje godine obavlja se tako što se od prethodno dobijene vrednosti oduzima 365 ili 366, * jednom ili više puta - sve dok je preostali broj dana i dalje veći od broja dana u godini:
- ukoliko preostali broj dana dozvoljava oduzimanje (u određenom koraku), dolazi do oduzimanja i pri tom se godina uvećava za 1
- kada preostali broj dana postane manji od broja dana u godini, godina je izračunata kao:
1970 + broj_prelazaka, nakon čega sledi određivanje meseca
-
za određivanje meseca, primenjuje se idejno sličan postupak koji podrazumeva da se, u svakom koraku, od preostalog broja dana oduzima broj dana koji odgovara mesecu do koga je algoritam došao (31 dan za januar, 28 ili 29 za februar, 31 za mart i sl):
- ukoliko preostali broj dana dozvoljava oduzimanje (u određenom koraku), dolazi do oduzimanja i pri tom se mesec uvećava za 1
- kada nastane situacija u kojoj preostali broj dana ne omogućava dalje oduzimanje, mesec je izračunat kao:
1 + broj_prelazaka, nakon čega preostaje da se odredi dan u mesecu
- dan u mesecu, poklapa se sa preostalom vrednošću
Ceo postupak može se (naravno) ubrzati, korišćenjem pomoćnih tabela koje se pretražuju metodom binarne pretrage, ** nakon čega algoritam postaje sasvim efikasan.
(Kao što rekosmo, postoji i nešto efikasnije rešenje, *** koje nije baš jednostavno za razumevanje, ali, u članku ćemo razmatrati algoritam koji smo prethodno opisali.)
U primerima koje prikazujemo, tabele će biti popunjene ručno (zarad boljeg razumevanja), i potom mapirane automatski, s tim da se tabela koja predstavlja broj dana po godinama, lako može i popuniti automatski (prikazaćemo postupak u nastavku):
static Int32 sekundeUMinutu = 60;
static Int32 sekundeUSatu = sekundeUMinutu * 60; // 3600
static Int32 sekundeUDanu = sekundeUSatu * 24; // 86400
static Int32 sekundeUGodiniN = sekundeUDanu * 365; // 31536000
static Int32 sekundeUGodiniP = sekundeUDanu * 366; // 31622400
static Int32[] daniUGodini = {
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1970 - 1979
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 1980 - 1989
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1990 - 1999
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2000 - 2010
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 2010 - 2019
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2020 - 2029
};
static Int32[] meseciN = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static Int32[] meseciP = {
0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int sekundeUMinutu = 60;
int sekundeUSatu = sekundeUMinutu * 60; // 3600
int sekundeUDanu = sekundeUSatu * 24; // 86400
int sekundeUGodiniN = sekundeUDanu * 365; // 31536000
int sekundeUGodiniP = sekundeUDanu * 366; // 31622400
int daniUGodini[] = {
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1970 - 1979
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 1980 - 1989
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1990 - 1999
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2000 - 2010
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 2010 - 2019
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2020 - 2029
};
int meseciN[] = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int meseciP[] = {
0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static int sekundeUMinutu = 60;
static int sekundeUSatu = sekundeUMinutu * 60; // 3600
static int sekundeUDanu = sekundeUSatu * 24; // 86400
static int sekundeUGodiniN = sekundeUDanu * 365; // 31536000
static int sekundeUGodiniP = sekundeUDanu * 366; // 31622400
static int[] daniUGodini = {
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1970 - 1979
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 1980 - 1989
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1990 - 1999
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2000 - 2010
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 2010 - 2019
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2020 - 2029
};
static int[] meseciN = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static int[] meseciP = {
0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
import math
sekundeUMinutu = 60
sekundeUSatu = sekundeUMinutu * 60; # 360
sekundeUDanu = sekundeUSatu * 24; # 8640
sekundeUGodiniN = sekundeUDanu * 365; # 3153600
sekundeUGodiniP = sekundeUDanu * 366; # 3162240
daniUGodini = [
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, # 1970 - 1979
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, # 1980 - 1989
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, # 1990 - 1999
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, # 2000 - 2010
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, # 2010 - 2019
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, # 2020 - 2029
]
meseciN = [
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
]
meseciP = [
0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
]
let sekundeUMinutu = 60;
let sekundeUSatu = sekundeUMinutu * 60; // 3600
let sekundeUDanu = sekundeUSatu * 24; // 86400
let sekundeUGodiniN = sekundeUDanu * 365; // 31536000
let sekundeUGodiniP = sekundeUDanu * 366; // 31622400
let daniUGodini = [
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1970 - 1979
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 1980 - 1989
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 1990 - 1999
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2000 - 2010
365, 365, 366, 365, 365, 365, 366, 365, 365, 365, // 2010 - 2019
366, 365, 365, 365, 366, 365, 365, 365, 366, 365, // 2020 - 2029
];
let meseciN = [
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
]
let meseciP = [
0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
]
Vidimo da smo svakoj godini, odnosno, svakom mesecu, za početak dodelili pripadajući broj dana ....
// 1970 1971 1972 1973 ....
daniUGodini [ 365, 365, 366, 365 ....]
// 0 1 2 3 4 5 6 7 8 9 10 11 12
meseciN [ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
meseciP [ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
.... ali, nizovi će biti mapirani tako da na kraju svaki element predstavlja broj proteklih dana:
- u slučaju niza
daniUGodini, u pitanju je broj celih dana između početka UNIX epohe i kraja određene godine - u slučaju nizova
meseciNimeseciP, u pitanju je broj celih dana između početka godine i kraja određenog meseca
// 1970 1971 1972 1973 ....
daniUGodini [ 365, 730, 1096, 1461 ....]
// 0 1 2 3 4 5 6 7 8 9 10 11 12
meseciN [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]
meseciP [ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ]
U svemu je bitno razumeti sledeće: ako datum pripada (na primer) 1973. godini, zarad oduzimanja treba očitati vrednost koja se poklapa sa godinom 1972 (vrednost 1096), a ako je datum u septembru, treba očitati vrednost koja se poklapa sa 8. mesecom (243 ili 244, u zavisnosti od toga da li je godina prestupna ili nije).
Za mapiranje nizova možemo koristiti jednostavnu funkciju koja prolazi kroz sve elemente, od drugog do poslednjeg, i dodaje prethodni zbir (sa pozicije i - 1), na vrednost elementa na poziciji i.
Za pretragu, možemo koristiti dobro poznati algoritam binarne pretrage.
static void inicijalizacijaNiza(Int32[] niz) {
for (int i = 1; i < niz.Length; i++) {
niz[i] = niz[i] + niz[i - 1];
}
}
static Int32 BinarnaPretraga(Int32 a, Int32[] niz) {
Int32 le = 0, de = niz.Length - 1;
Int32 ind = (Int32)Math.Floor((Double)(le + de) * 0.5);
while (le < de) {
if (a == niz[ind]) return ind;
if (a < niz[ind]) {
de = ind - 1;
}
else {
le = ind + 1;
}
ind = (Int32)Math.Floor((Double)(le + de) * 0.5);
}
return ind;
}
void inicijalizacijaNiza(int niz[], int n) {
for (int i = 1; i < n; i++) {
niz[i] = niz[i] + niz[i - 1];
}
}
void ispisNiza(int niz[], int n) {
for (int i = 0; i < n; i++) {
cout << niz[i] << " ";
}
cout << endl;
}
int binarnaPretraga(int a, int niz[], int n) {
int le = 0, de = n - 1;
int ind = floor((le + de) * 0.5);
while (le < de) {
if (a == niz[ind]) return ind;
if (a < niz[ind]) {
de = ind - 1;
}
else {
le = ind + 1;
}
ind = floor((le + de) * 0.5) ;
}
return ind;
}
static void inicijalizacijaNiza(int[] niz) {
for (int i = 1; i < niz.length; i++) {
niz[i] = niz[i] + niz[i - 1];
}
}
static int BinarnaPretraga(int a, int[] niz) {
int le = 0, de = niz.length - 1;
int ind = (int)Math.floor((double)(le + de) * 0.5);
while (le < de) {
if (a == niz[ind]) return ind;
if (a < niz[ind]) {
de = ind - 1;
}
else {
le = ind + 1;
}
ind = (int)Math.floor((double)(le + de) * 0.5);
}
return ind;
}
def inicijalizacijaNiza(niz):
r = range(1, len(niz))
for i in r:
niz[i] = niz[i] + niz[i - 1]
inicijalizacijaNiza(daniUGodini)
inicijalizacijaNiza(meseciN)
inicijalizacijaNiza(meseciP)
def binarnaPretraga(n, niz):
le = 0
de = len(niz) - 1;
ind = math.floor((le + de) * 0.5)
while (le < de):
if n == niz[ind]: return ind
if (n < niz[ind]):
de = ind - 1
else:
le = ind + 1
ind = math.floor((le + de) * 0.5)
return ind;
function inicijalizacijaNiza(niz) {
for (let i = 1; i < niz.length; i++) {
niz[i] = niz[i] + niz[i - 1];
}
}
function binarnaPretraga(n, niz) {
let le = 0, de = niz.length - 1;
let ind = Math.floor((le + de) * 0.5);
while (le < de) {
if (n == niz[ind]) return ind;
if (n < niz[ind]) {
de = ind - 1;
}
else {
le = ind + 1;
}
ind = Math.floor((le + de) * 0.5) ;
}
return ind;
}
Naravno, za razliku od nizova meseciN i meseciP (u kojima postoji idiosinkratičan i nepravilan sled elemenata), niz daniUGodini može se kreirati automatski:
Int32 poslednjaGodina = 2040;
Int32 epoha = 1970;
Int32 brojElemenata = poslednjaGodina - epoha;
Int32[] daniUGodini = new Int32[brojElemenata];
/* ----- ver #1 ----- */
for (Int32 i = 0; i < brojElemenata; i++) {
niz[i] = (i % 4 == 2)? 366 : 365;
}
/* ----- ver #2 ----- */
Int32 i;
for (i = 0; i < brojElemenata; i++) {
niz[i] = 365;
}
i = 2;
while (i < brojElemenata) {
niz[i] = 366;
i += 4;
}
int poslednjaGodina = 2040;
int epoha = 1970;
int brojElemenata = poslednjaGodina - epoha;
int daniUGodini[brojElemenata];
/* ----- ver #1 ----- */
for (int i = 0; i < brojElemenata; i++) {
niz[i] = (i % 4 == 2)? 366 : 365;
}
/* ----- ver #2 ----- */
int i;
for (i = 0; i < brojElemenata; i++) {
niz[i] = 365;
}
i = 2;
while (i < brojElemenata) {
niz[i] = 366;
i += 4;
}
int poslednjaGodina = 2040;
int epoha = 1970;
int brojElemenata = poslednjaGodina - epoha;
int daniUGodini[] = new int[brojElemenata];
/* ----- ver #1 ----- */
for (int i = 0; i < brojElemenata; i++) {
niz[i] = (i % 4 == 2)? 366 : 365;
}
/* ----- ver #2 ----- */
int i;
for (i = 0; i < brojElemenata; i++) {
niz[i] = 365;
}
i = 2;
while (i < brojElemenata) {
niz[i] = 366;
i += 4;
}
poslednja_godina = 2040
epoha = 1970
broj_elemenata = poslednja_godina - epoha
daniUGodini = [ ]
# ----- ver #1 ----- #
for i in range(0, broj_elemenata):
if i % 4 == 2:
niz.append(366)
else:
niz.append(365)
}
# ----- ver #2 ----- #
for i in range(0, broj_elemenata):
niz.append(365)
i = 2
while i < broj_elemenata:
niz[i] = 366
i = i + 4
let poslednjaGodina = 2040;
let epoha = 1970;
let brojElemenata = poslednjaGodina - epoha;
let daniUGodini = [];
/* ----- ver #1 ----- */
for (let i = 0; i < brojElemenata; i++) {
niz.push((i % 4 == 2)? 366 : 365);
}
/* ----- ver #2 ----- */
let i;
for (i = 0; i < brojElemenata; i++) {
niz.push(365);
}
i = 2;
while (i < brojElemenata) {
niz[i] = 366;
i += 4;
}
Dodaćemo još i pomoćnu funkciju koja proverava da li je godina prestupna ....
static Boolean DaLiJePrestupna(Int32 godina) {
if (godina % 4 == 0) {
if (godina % 100 == 0) {
if (godina % 400 == 0) return true;
return false;
}
else {
return true;
}
}
return false;
}
bool daLiJePrestupna(int godina) {
if (godina % 4 == 0) {
if (godina % 100 == 0) {
if (godina % 400 == 0) return true;
return false;
}
else {
return true;
}
}
return false;
}
static Boolean DaLiJePrestupna(int godina) {
if (godina % 4 == 0) {
if (godina % 100 == 0) {
if (godina % 400 == 0) return true;
return false;
}
else {
return true;
}
}
return false;
}
def daLiJePrestupna(godina):
if godina % 4 == 0:
if godina % 100 == 0:
if godina % 400 == 0: return True
return False
else:
return True
return False
function daLiJePrestupna(godina) {
if (godina % 4 == 0) {
if (godina % 100 == 0) {
if (godina % 400 == 0) return true;
return false;
}
else {
return true;
}
}
return false;
}
.... kao i dve pomoćne klase (u C#-u i Javi), odnosno, pomoćnu strukturu u C++ - u * ....
public class Datum {
public int Godina, Mesec, Dan;
public String Ispis()
{
return Godina.ToString() + "-" +
Mesec.ToString() + "-" +
Dan.ToString() + "\r\n";
}
}
public class Rezultat {
public int Rez, Korekcija;
}
struct Datum {
int godina, mesec, dan;
};
struct Rezultat {
int rez, korekcija;
};
public class Datum {
public int Godina, Mesec, Dan;
public String Ispis() {
return String.format("%d-%d-%d", Godina, Mesec, Dan);
}
}
public class Rezultat {
public int Rez, Korekcija;
}
# U primerima u ovom članku, objekte u Python-u
# razmenjivaćemo bez posredničkih klasa
// U primerima u ovom članku, objekte u JavaScript-u
// razmenjivaćemo bez posredničkih klasa
.... i onda možemo početi.
Za početak, timestamp se deli sa 86400 (odnosno, sa brojem sekundi u jednom danu) ....
daniUGodini = floor(timestamp / brojSekundiUDanu)
.... i potom se dobijena vrednost predaje funkciji PronalazenjeGodine, koja (preko tabele dani) ....
static Rezultat PronalazenjeGodine(Int32 dan, Int32[] niz, Int32 epoha) {
Rezultat rezultat = new Rezultat();
if (dan <= 365) {
rezultat.Rez = epoha;
rezultat.Korekcija = 0;
return rezultat;
}
int ind = BinarnaPretraga(dan, niz);
rezultat.Rez = epoha + ind;
rezultat.Korekcija = niz[ind - 1];
return rezultat;
}
struct Rezultat pronalazenjeGodine(int dan, int niz[], int epoha) {
struct Rezultat rezultat;
if (dan <= 365) {
rezultat.rez = epoha;
rezultat.korekcija = 0;
return rezultat;
}
int ind = binarnaPretraga(dan, niz, 100);
rezultat.rez = epoha + ind;
rezultat.korekcija = niz[ind - 1];
return rezultat;
}
static Rezultat PronalazenjeGodine(int dan, int[] niz, int epoha) {
Rezultat rezultat = new Rezultat();
if (dan <= 365) {
rezultat.Rez = epoha;
rezultat.Korekcija = 0;
return rezultat;
}
int ind = BinarnaPretraga(dan, niz);
rezultat.Rez = epoha + ind;
rezultat.Korekcija = niz[ind - 1];
return rezultat;
}
def pronalazenjeGodine(dan, niz, epoha):
if dan <= 365:
return {
'godina': epoha,
'korekcija': 0
}
ind = binarnaPretraga(dan, niz)
return {
'godina': epoha + ind,
'korekcija': niz[ind - 1]
}
function pronalazenjeGodine(dan, niz, epoha) {
if (dan <= 365) {
return {
godina: epoha,
korekcija: 0
}
}
let ind = binarnaPretraga(dan, niz);
return {
godina: epoha + ind,
korekcija: niz[ind - 1]
}
}
.... pronalazi godinu koja odgovara unetom timestamp-u.
Ali, ne samo to: vrednost promenljive daniUGodini takođe će biti umanjena za vrednost iz tabele, koja odgovara pronađenoj godini (s tim - da ponovimo - da se vrednost koja se preuzima iz tabele, nalazi jedno mesto ulevo, odnosno, 'ispod' godine koja je za jedan manja od pronađene).
danUGodini -= rez.korekcija
Na sličan način, preko odgovarajuće funkcije ....
static Rezultat pronalazenjeMeseca(Int32 dan, Boolean prestupna, Int32[] nizN, Int32[] nizP) {
Int32[] niz;
if (prestupna) {
niz = nizP;
}
else {
niz = nizN;
}
int ind = BinarnaPretraga(dan, niz);
Rezultat rezultat = new Rezultat();
rezultat.Rez = ind;
rezultat.Korekcija = niz[ind - 1];
return rezultat;
}
struct Rezultat pronalazenjeMeseca(int dan, bool prestupna, int nizN[], int nizP[]) {
int* niz;
if (prestupna) {
niz = nizP;
}
else {
niz = nizN;
}
int ind = binarnaPretraga(dan, niz, 13);
struct Rezultat rezultat;
rezultat.rez = ind;
rezultat.korekcija = niz[ind - 1];
return rezultat;
}
static Rezultat pronalazenjeMeseca(int dan, Boolean prestupna, int[] nizN, int[] nizP) {
int[] niz;
if (prestupna) {
niz = nizP;
}
else {
niz = nizN;
}
int ind = BinarnaPretraga(dan, niz);
Rezultat rezultat = new Rezultat();
rezultat.Rez = ind;
rezultat.Korekcija = niz[ind - 1];
return rezultat;
}
def pronalazenjeMeseca(dan, prestupna, nizN, nizP):
if prestupna == True:
niz = nizP
else:
niz = nizN
ind = binarnaPretraga(dan, niz)
return {
'mesec': ind,
'korekcija': niz[ind - 1]
}
function pronalazenjeMeseca(dan, prestupna, nizN, nizP) {
if (prestupna) {
niz = nizP
}
else {
niz = nizN;
}
let ind = binarnaPretraga(dan, niz);
return {
mesec: ind,
korekcija: niz[ind - 1]
}
}
.... može se pronaći i mesec koji odgovara datumu, posle čega nije teško uneti odgovarajuću korekciju, i time dobiti redni broj dana.
Sada možemo sagledati i glavnu funkciju:
static Datum timestampUDatum(Int32 t) {
Double stamp;
Rezultat rezultat = new Rezultat();
Datum datum = new Datum();
stamp = (Double)t / sekundeUDanu;
stamp = Math.Ceiling(stamp);
rezultat = PronalazenjeGodine((Int32)stamp, daniUGodini, 1970);
int godina = rezultat.Rez;
bool prestupna = DaLiJePrestupna(godina);
int danUGodini = (Int32)stamp - rezultat.Korekcija;
rezultat = pronalazenjeMeseca(danUGodini, prestupna, meseciN, meseciP);
int mesec = rezultat.Rez;
int dan = danUGodini - rezultat.Korekcija;
datum.Godina = godina;
datum.Mesec = mesec;
datum.Dan = dan;
return datum;
}
struct Datum timestampUDatum(int t) {
double stamp;
struct Rezultat rezultat;
struct Datum datum;
stamp = (double)t / sekundeUDanu;
stamp = ceil(stamp);
rezultat = pronalazenjeGodine(stamp, daniUGodini, 1970);
int godina = rezultat.rez;
bool prestupna = daLiJePrestupna(godina);
int danUGodini = stamp - rezultat.korekcija;
rezultat = pronalazenjeMeseca(danUGodini, prestupna, meseciN, meseciP);
int mesec = rezultat.rez;
int dan = danUGodini - rezultat.korekcija;
datum.godina = godina;
datum.mesec = mesec;
datum.dan = dan;
return datum;
}
static Datum timestampUDatum(int t) {
double stamp;
Rezultat rezultat = new Rezultat();
Datum datum = new Datum();
stamp = (double)t / sekundeUDanu;
stamp = Math.ceil(stamp);
rezultat = PronalazenjeGodine((int)stamp, daniUGodini, 1970);
int godina = rezultat.Rez;
Boolean prestupna = DaLiJePrestupna(godina);
int danUGodini = (int)stamp - rezultat.Korekcija;
rezultat = pronalazenjeMeseca(danUGodini, prestupna, meseciN, meseciP);
int mesec = rezultat.Rez;
int dan = danUGodini - rezultat.Korekcija;
datum.Godina = godina;
datum.Mesec = mesec;
datum.Dan = dan;
return datum;
}
def timestampUDatum(t):
stamp = t / sekundeUDanu
stamp = math.ceil(stamp)
rez = pronalazenjeGodine(stamp, daniUGodini, 1970)
godina = rez['godina']
prestupna = daLiJePrestupna(godina)
danUGodini = stamp - rez['korekcija']
rez = pronalazenjeMeseca(danUGodini, prestupna, meseciN, meseciP)
mesec = rez['mesec']
dan = danUGodini - rez['korekcija']
return {
'godina': godina,
'mesec': mesec,
'dan': dan
}
function timestampUDatum(t) {
let stamp, rez;
stamp = t / sekundeUDanu;
stamp = Math.ceil(stamp);
rez = pronalazenjeGodine(stamp, daniUGodini, 1970);
let godina = rez.godina;
let prestupna = daLiJePrestupna(godina);
let danUGodini = stamp - rez.korekcija;
rez = pronalazenjeMeseca(danUGodini, prestupna, meseciN, meseciP);
let mesec = rez.mesec;
let dan = danUGodini - rez.korekcija;
return {
godina: godina,
mesec: mesec,
dan: dan
}
}
Za kraj, preko jednostavnog formulara, možete videti kako sve funkcioniše u praksi.
Formular - UNIX timecode / (datum-vreme)
Donji formular takođe prikazuje i vreme (sate, minute, sekunde) ....
UNIX time stamp
Datum / vreme
.... međutim, implementacija ovakvog formulara, ipak je nešto što ćemo ostaviti našim cenjenim čitaocima koji žele da samostalno isprobaju dodatne opcije.