Ghidul definitiv pentru manipularea DateTime

Publicat: 2022-03-11

În calitate de dezvoltator de software, nu poți fugi de manipularea datelor. Aproape fiecare aplicație creată de un dezvoltator va avea o componentă în care data/ora trebuie să fie obținută de la utilizator, stocată într-o bază de date și afișată înapoi utilizatorului.

Întrebați orice programator despre experiența sa în gestionarea datelor și a fusurilor orare și probabil că va împărtăși câteva povești de război. Gestionarea câmpurilor de dată și oră nu este cu siguranță știință rachetă, dar poate fi adesea plictisitoare și predispusă la erori.

Există sute de articole pe această temă, cu toate acestea, cele mai multe sunt fie prea academice, concentrându-se pe detalii esențiale, fie sunt prea neregulate, oferind fragmente scurte de cod fără prea multe explicații care le însoțesc. Acest ghid aprofundat pentru manipularea DateTime ar trebui să vă ajute să înțelegeți conceptele de programare și cele mai bune practici relevante pentru oră și dată, fără a fi nevoie să răsfoiți o mare de informații despre acest subiect.

În acest articol, vă voi ajuta să vă gândiți clar la câmpurile de dată și oră și vă voi sugera câteva bune practici care vă pot ajuta să evitați iadul dată/ora. Aici vom explora câteva dintre conceptele cheie care sunt necesare pentru manipularea corectă a valorilor datei și orei, formate care sunt convenabile pentru stocarea valorilor DateTime și transferul lor prin API-uri și multe altele.

Pentru început, răspunsul corect pentru codul de producție este aproape întotdeauna să folosești o bibliotecă adecvată, mai degrabă decât să o faci pe propria ta. Dificultățile potențiale cu calculul DateTime discutate în acest articol sunt doar vârful aisbergului, dar sunt totuși utile de știut, cu sau fără bibliotecă.

Bibliotecile DateTime ajută dacă le înțelegeți corect

Bibliotecile de date vă ajută în multe feluri să vă ușureze viața. Ele simplifică foarte mult analizarea datei, operațiile aritmetice și logice ale datei și formatarea datei. Puteți găsi o bibliotecă de date de încredere atât pentru partea din față, cât și pentru partea din spate, pentru a face cea mai mare parte a sarcinilor grele pentru dvs.

Cu toate acestea, folosim adesea biblioteci de date fără să ne gândim la cum funcționează de fapt data/ora. Data/ora este un concept complex. Bug-urile care apar din cauza înțelegerii incorecte pot fi extrem de greu de înțeles și de remediat, chiar și cu ajutorul bibliotecilor de date. Ca programator, trebuie să înțelegeți elementele de bază și să puteți aprecia problemele pe care bibliotecile de date le rezolvă pentru a profita la maximum de ele.

De asemenea, bibliotecile de date/ora nu te pot duce decât atât de departe. Toate bibliotecile de date funcționează oferindu-vă acces la structuri de date convenabile pentru a reprezenta un DateTime. Dacă trimiteți și primiți date printr-un API REST, în cele din urmă va trebui să convertiți data într-un șir și invers, deoarece JSON nu are o structură de date nativă care să reprezinte DateTime. Conceptele pe care le-am subliniat aici vă vor ajuta să evitați unele dintre problemele comune care ar putea apărea atunci când faceți aceste transformări de la data la șir și de la șir la dată.

Notă: Chiar dacă am folosit JavaScript ca limbaj de programare discutat în acest articol, acestea sunt concepte generale care se aplică, în mare măsură, la aproape toate limbajele de programare și bibliotecile lor de date. Așa că, chiar dacă nu ați scris niciodată o linie de JavaScript înainte, nu ezitați să continuați să citiți, deoarece nu presupun nicio cunoștințe anterioare despre JavaScript în articol.

Standardizarea timpului

Un DateTime este un moment foarte specific în timp. Să ne gândim la asta. În timp ce scriu acest articol, ceasul de pe laptopul meu arată 21 iulie 13:29. Aceasta este ceea ce numim „ora locală”, ora pe care o văd pe ceasurile de perete din jurul meu și pe ceasul meu de mână.

Dă-i sau ia-ți câteva minute, dacă îi rog pe prietena mea să mă întâlnească la o cafenea din apropiere la ora 15:00, mă pot aștepta să o văd acolo cam la acea oră. În mod similar, nu ar exista nicio confuzie dacă aș spune, de exemplu, „să ne întâlnim peste o oră și jumătate”. În mod obișnuit, vorbim despre timp în acest fel cu oameni care locuiesc în același oraș sau fus orar.

Să ne gândim la un scenariu diferit: vreau să-i spun unui prieten care locuiește în Uppsala, Suedia, că vreau să vorbesc cu el la ora 17:00. Îi trimit un mesaj: „Hei Anton, hai să vorbim la 5 PM.” Primesc imediat răspunsul „Ora ta sau ora mea?”

Anton îmi spune că locuiește în fusul orar central european, care este UTC+01:00. Locuiesc în UTC+05:45. Aceasta înseamnă că, atunci când sunt 5 PM unde locuiesc, este 5 PM - 05:45 = 11:15 AM UTC, ceea ce se traduce în 11:15 AM UTC + 01:00 = 12:15 PM în Uppsala, perfect pentru ambele dintre noi.

De asemenea, fiți conștienți de diferența dintre fusul orar (Ora Europei Centrale) și decalajul fusului orar (UTC+05:45). Țările pot decide să-și schimbe decalajele fusului orar pentru ora de vară și din motive politice. Aproape în fiecare an, există o modificare a regulilor în cel puțin o țară, ceea ce înseamnă că orice cod cu aceste reguli trebuie menținut la zi - merită să luați în considerare de ce depinde baza de cod pentru fiecare nivel al aplicației dvs.

Acesta este un alt motiv bun pentru care vă vom recomanda ca doar front-end-ul să se ocupe de fusurile orare în majoritatea cazurilor. Când nu se întâmplă, ce se întâmplă atunci când regulile pe care le folosește motorul de bază de date nu se potrivesc cu cele ale front-end sau back-end?

Această problemă de a gestiona două versiuni diferite ale timpului, relativ la utilizator și față de un standard universal acceptat, este dificilă, cu atât mai mult în lumea programării unde precizia este cheia și chiar și o secundă poate face o diferență uriașă. Primul pas către rezolvarea acestor probleme este stocarea DateTime în UTC.

Standardizarea formatului

Standardizarea orei este minunată deoarece trebuie doar să stochez ora UTC și atâta timp cât cunosc fusul orar al utilizatorului, pot oricând să convertesc la ora lui. În schimb, dacă cunosc ora locală a unui utilizator și îi cunosc fusul orar, o pot converti în UTC.

Dar datele și orele pot fi specificate în multe formate diferite. Pentru dată, puteți scrie „30 iulie” sau „30 iulie” sau „30/7” (sau 30/7, în funcție de locul în care locuiți). Pentru moment, puteți scrie „9:30 PM” sau „2130”.

Oamenii de știință din întreaga lume s-au reunit pentru a aborda această problemă și au decis asupra unui format pentru a descrie timpul pe care programatorii le place foarte mult, deoarece este scurt și precis. Ne place să-l numim „format de dată ISO”, care este o versiune simplificată a formatului extins ISO-8601 și arată astfel:

O imagine care arată o versiune simplificată a formatului extins ISO-8601 numit format de dată ISO.

Pentru 00:00 sau UTC, folosim în schimb „Z”, ceea ce înseamnă ora Zulu, un alt nume pentru UTC.

Manipularea datei și aritmetica în JavaScript

Înainte de a începe cu cele mai bune practici, vom afla despre manipularea datei folosind JavaScript pentru a înțelege sintaxa și conceptele generale. Deși folosim JavaScript, puteți adapta cu ușurință aceste informații la limbajul de programare preferat.

Vom folosi aritmetica datei pentru a rezolva problemele obișnuite legate de dată cu care se confruntă majoritatea dezvoltatorilor.

Scopul meu este să vă fac confortabil să creați un obiect data dintr-un șir și să extrageți componente dintr-unul. Este ceva cu care o bibliotecă de date vă poate ajuta, dar este întotdeauna mai bine să înțelegeți cum se face în culise.

Odată ce ne-am murdarit mâinile cu data/ora, este mai ușor să ne gândim la problemele cu care ne confruntăm, să extragem cele mai bune practici și să mergem mai departe. Dacă doriți să treceți la cele mai bune practici, nu ezitați să faceți acest lucru, dar v-aș recomanda cu căldură să parcurgeți cel puțin secțiunea de aritmetică a datei de mai jos.

Obiectul JavaScript Data

Limbajele de programare conțin constructe utile pentru a ne ușura viața. Obiectul JavaScript Date este un astfel de lucru. Oferă metode convenabile pentru a obține data și ora curente, pentru a stoca o dată într-o variabilă, pentru a efectua aritmetica date și pentru a formata data în funcție de localitatea utilizatorului.

Datorită diferențelor dintre implementările browserului și gestionarea incorectă a orei de vară (DST), în funcție de obiectul Date pentru aplicațiile critice nu este recomandat și probabil ar trebui să utilizați o bibliotecă DateTime precum Luxon, date-fns sau dayjs. (Orice ați folosi, evitați odată popularul Moment.js - adesea numit pur și simplu moment , așa cum apare în cod - deoarece acum este depreciat.)

Dar, în scopuri educaționale, vom folosi metodele pe care obiectul Date() le oferă pentru a afla cum se ocupă JavaScript de DateTime.

Obținerea datei curente

 const currentDate = new Date();

Dacă nu transmiteți nimic constructorului Date, obiectul dată returnat conține data și ora curente.

Apoi îl puteți formata pentru a extrage doar partea dată după cum urmează:

 const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"

Notă: Capcana „Ianuarie este 0” este comună, dar nu universală. Merită să verificați documentația oricărei limbi (sau format de configurare: de exemplu, cron este în special bazat pe 1) înainte de a începe să îl utilizați.

Obținerea ștampilei de oră curentă

Dacă în schimb doriți să obțineți marca temporală curentă, puteți crea un nou obiect Date și puteți utiliza metoda getTime().

 const currentDate = new Date(); const timestamp = currentDate.getTime();

În JavaScript, un marcaj de timp este numărul de milisecunde care au trecut de la 1 ianuarie 1970.

Dacă nu intenționați să acceptați <IE8, puteți utiliza Date.now() pentru a obține direct marca temporală, fără a fi nevoie să creați un nou obiect Date.

Analizarea unei date

Convertirea unui șir într-un obiect data JavaScript se face în moduri diferite.

Constructorul obiectului Date acceptă o mare varietate de formate de dată:

 const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");

Rețineți că nu trebuie să includeți ziua săptămânii, deoarece JS poate determina ziua săptămânii pentru orice dată.

De asemenea, puteți trece anul, luna, ziua, orele, minutele și secundele ca argumente separate:

 const date = new Date(2016, 6, 27, 13, 30, 0);

Desigur, puteți utiliza oricând formatul de dată ISO:

 const date = new Date("2016-07-27T07:45:00Z");

Cu toate acestea, puteți avea probleme atunci când nu furnizați fusul orar în mod explicit!

 const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");

Oricare dintre acestea vă va oferi 25 iulie 2016 ora locală 00:00:00.

Dacă utilizați formatul ISO, chiar dacă dați doar data și nu ora și fusul orar, acesta va accepta automat fusul orar ca UTC.

Aceasta înseamnă că:

 new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()

Formatarea unei date

Din fericire, JavaScript modern are câteva funcții convenabile de internaționalizare încorporate în spațiul de nume standard Intl , care fac formatarea datei o operație simplă.

Pentru aceasta vom avea nevoie de două obiecte: un Date și un Intl.DateTimeFormat , inițializate cu preferințele noastre de ieșire. Presupunând că am dori să folosim formatul american (M/D/AAAA), acesta ar arăta astfel:

 const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020

Dacă în schimb am dori formatul olandez (D/M/AAAA), am transmite un cod de cultură diferit constructorului DateTimeFormat :

 const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020

Sau o formă mai lungă a formatului american, cu numele lunii scris:

 const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020

Acum, dacă dorim un format ordinal adecvat pentru ziua lunii – adică „14” în loc de doar „14” – acest lucru, din păcate, are nevoie de o mică soluție, deoarece singurele valori valide ale day la momentul scrierii acestui articol sunt "numeric" sau "2-digit" . Împrumutând versiunea lui Flavio Copes a codului lui Mathias Bynens pentru a folosi o altă parte a Intl pentru aceasta, putem personaliza ieșirea zilei lunii prin formatToParts() :

 const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020

Din păcate, formatToParts nu este acceptat deloc de Internet Explorer (IE) la momentul scrierii acestui articol, dar toate celelalte tehnologii desktop, mobile și back-end (adică Node.js) au suport. Pentru cei care au nevoie să accepte IE și au absolut nevoie de ordinale, nota laterală de mai jos (sau mai bine, o bibliotecă de date adecvată) oferă un răspuns.

Dacă trebuie să acceptați browsere mai vechi, cum ar fi IE, înainte de versiunea 11, formatarea datei în JavaScript este mai dificilă, deoarece nu existau funcții standard de formatare a datei, cum ar fi strftime în Python sau PHP.

În PHP, de exemplu, funcția strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99)) vă oferă Today is Dec 30 1999 05:10:00 .

Puteți utiliza o combinație diferită de litere precedate de % pentru a obține data în diferite formate. (Atenție, nu toate limbile atribuie același înțeles fiecărei litere – în special, „M” și „m” pot fi schimbate pentru minute și luni.)

Dacă sunteți sigur de formatul pe care doriți să-l utilizați, cel mai bine este să extrageți biți individuali folosind funcțiile JavaScript descrise mai sus și să creați singur un șir.

 var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();

Putem obține data în format LL/ZZ/AAAA ca

 var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;

Problema cu această soluție este că poate da o lungime inconsecventă datelor deoarece unele luni și zile ale lunii sunt de o singură cifră, iar altele de două cifre. Acest lucru poate fi problematic, de exemplu, dacă afișați data într-o coloană de tabel, deoarece datele nu se aliniază.

Putem rezolva acest lucru folosind o funcție „pad” care adaugă un 0 inițial.

 function pad ( n ) { return n< 10 ? '0' +n : n; }

Acum, obținem data corectă în format LL/ZZ/AAAA folosind:

 var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;

Dacă vrem în schimb ZZ-LL-AAAA, procesul este similar:

 var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;

Să creștem ansa și să încercăm să tipărim data în format „Lună, dată, an”. Vom avea nevoie de o mapare a indicilor lunilor la nume:

 var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;

Unii oameni le place să afișeze data ca 1 ianuarie 2013. Nicio problemă, tot ce avem nevoie este o funcție de ajutor ordinal care returnează 1 pentru 1, 12 pentru 12 și 103 pentru 103 etc., iar restul este simplu:

 var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;

Este ușor să determinați ziua săptămânii din obiectul dată, așa că să adăugăm asta în:

 var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;

Ideea mai importantă aici este că, odată ce ați extras numerele din dată, formatarea este în mare parte legată de șiruri.

Modificarea formatului datei

Odată ce știți cum să analizați o dată și să o formatați, schimbarea unei date dintr-un format în altul este doar o chestiune de a combina cele două.

De exemplu, dacă aveți o dată în formatul 21 iulie 2013 și doriți să schimbați formatul în 21-07-2013, se poate realiza astfel:

 const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"

Utilizarea funcțiilor de localizare a obiectului Data JavaScript

Metodele de formatare a datei pe care le-am discutat mai sus ar trebui să funcționeze în majoritatea aplicațiilor, dar dacă doriți cu adevărat să localizați formatarea datei, vă sugerez să utilizați metoda toLocaleDateString() a obiectului Date :

 const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', });

… ne oferă ceva de genul 26 Jul 2016 .

Schimbarea localizării la „en-US” dă în schimb „26 iulie 2016”. Observați cum s-a schimbat formatarea, dar opțiunile de afișare au fost încă păstrate la fel - o caracteristică foarte utilă. După cum se arată în secțiunea anterioară, noua tehnică bazată pe Intl.DateTimeFormat funcționează foarte similar cu aceasta, dar vă permite să reutilizați un obiect de formatare, astfel încât să aveți nevoie să setați opțiunile o singură dată.

Cu toLocaleDateString() , este un obicei bun să treceți întotdeauna opțiunile de formatare, chiar dacă rezultatul arată bine pe computer. Acest lucru poate proteja interfața de utilizare împotriva spargerii în localități neașteptate cu nume de luni foarte lungi sau arătând ciudat din cauza celor scurte.

Dacă am vrut în schimb luna completă „iulie”, tot ce fac este să schimb parametrul lunii din opțiuni la „lung”. JavaScript se ocupă de totul pentru mine. Pentru en-US, primesc acum 26 iulie 2016.

Notă: Dacă doriți ca browserul să utilizeze automat localitatea utilizatorului, puteți trece „nedefinit” ca prim parametru.

Dacă doriți să afișați versiunea numerică a datei și nu doriți să vă agitați cu LL/ZZ/AAAA față de ZZ/LL/AAAA pentru diferite locații, vă sugerez următoarea soluție simplă:

 const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });

Pe computerul meu, iese 7/26/2016 . Dacă doriți să vă asigurați că luna și data au două cifre, trebuie doar să schimbați opțiunile:

 const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });

Acesta iese 07/26/2016 . Exact ce ne-am dorit!

De asemenea, puteți utiliza și alte funcții asociate pentru a localiza modul în care sunt afișate atât ora, cât și data:

Cod Ieșire Descriere
 now.toLocaleTimeString()
„4:21:38” Afișează versiunea localizată numai a orei
 now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });
„04:21:38” Afișează ora localizată pe baza opțiunilor oferite
 now.toLocaleString()
„22.07.2016, 04:21:38” Afișează data și ora pentru localitatea utilizatorului
 now.toLocaleString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', });
„22.07.2016, 04:21” Afișați data și ora localizate pe baza opțiunilor oferite

Calcularea datelor și orelor relative

Iată un exemplu de adăugare a 20 de zile la o dată JavaScript (adică stabilirea datei la 20 de zile după o dată cunoscută):

 const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();

Obiectul data inițial reprezintă acum o dată la 20 de zile după 20 iulie, iar newDate conține un șir localizat care reprezintă data respectivă. Pe browserul meu, newDate conține „8/9/2016, 3:00:00 PM”.

Pentru a calcula mărcile de timp relative cu o diferență mai precisă decât zile întregi, puteți utiliza Date.getTime() și Date.setTime() pentru a lucra cu numere întregi care reprezintă numărul de milisecunde de la o anumită epocă, și anume, 1 ianuarie 1970. Pentru De exemplu, dacă doriți să știți când sunt 17 ore după:

 const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);

Compararea Datelor

Ca și în orice altceva legat de dată, compararea datelor are propriile sale probleme.

În primul rând, trebuie să creăm obiecte date. Din fericire, <, >, <= și >= funcționează. Deci, compararea 19 iulie 2014 cu 18 iulie 2014 este la fel de simplă ca:

 const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }

Verificarea egalității este mai dificilă, deoarece două obiecte dată care reprezintă aceeași dată sunt încă două obiecte dată diferite și nu vor fi egale. Compararea șirurilor de date este o idee proastă, deoarece, de exemplu, „20 iulie 2014” și „20 iulie 2014” reprezintă aceeași dată, dar au reprezentări diferite. Fragmentul de mai jos ilustrează primul punct:

 const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot);

Acest lucru not equal .

Acest caz particular poate fi rezolvat prin compararea echivalentelor întregi ale datelor (marcajele de timp ale acestora), după cum urmează:

 date1.getTime() == date2.getTime()

Am văzut acest exemplu în multe locuri, dar nu-mi place pentru că de obicei nu creați un obiect dată dintr-un alt obiect dată. Deci, consider că exemplul este important doar din punct de vedere academic. De asemenea, acest lucru necesită ca ambele obiecte Date să se refere la exact aceeași secundă, în timp ce ați putea dori să știți doar dacă se referă la aceeași zi, oră sau minut.

Să ne uităm la un exemplu mai practic. Încercați să comparați dacă ziua de naștere pe care a introdus-o utilizatorul este aceeași cu data norocoasă pe care o primiți de la un API.

 const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }

Ambele au reprezentat aceeași dată, dar, din păcate, utilizatorul dvs. nu va primi milionul de dolari.

Iată problema: JavaScript presupune întotdeauna că fusul orar este cel pe care browser-ul îl oferă, dacă nu se specifică în mod explicit altfel.

Aceasta înseamnă, pentru mine, new Date ("12/20/1989") va crea o dată 1989-12-20T00:00:00+5:45 sau 1989-12-19T18:15:00Z care nu este aceeași cu 1989-12-20T00:00:00Z în ceea ce privește marca temporală.

Nu este posibil să se schimbe doar fusul orar al unui obiect data existent, așa că ținta noastră este acum să creăm un nou obiect dată, dar cu UTC în loc de fusul orar local.

Vom ignora fusul orar al utilizatorului și vom folosi UTC în timp ce creăm obiectul dată. Există două moduri de a face acest lucru:

  1. Creați un șir de dată formatat ISO din data introdusă de utilizator și utilizați-l pentru a crea un obiect Date. Utilizarea unui format de dată ISO valid pentru a crea un obiect Date în timp ce intenția UTC vs local este foarte clară.
 const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true

Acest lucru funcționează și dacă nu specificați ora, deoarece aceasta va fi implicit la miezul nopții (adică, 00:00:00Z):

 const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true

Rețineți: dacă constructorului de dată i se transmite un șir în formatul corect de dată ISO AAAA-LL-ZZ, acesta presupune automat UTC.

  1. JavaScript oferă o funcție ordonată Date.UTC() pe care o puteți utiliza pentru a obține marcajul de timp UTC al unei date. Extragem componentele din dată și le trecem în funcție.
 const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...

Găsirea diferenței între două întâlniri

Un scenariu comun pe care îl veți întâlni este să găsiți diferența dintre două întâlniri.

Discutăm două cazuri de utilizare:

Găsirea numărului de zile dintre două date

Convertiți ambele date în marcaj de timp UTC, găsiți diferența în milisecunde și găsiți zilele echivalente.

 const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);

Găsirea vârstei utilizatorului de la data nașterii

 const birthDateFromAPI = "12/10/1989";

Notă: avem un format non-standard. Citiți documentul API pentru a determina dacă aceasta înseamnă 12 octombrie sau 10 decembrie. Schimbați în mod corespunzător formatul ISO.

 const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }

Știu că există modalități mai concise de a scrie acest cod, dar îmi place să-l scriu în acest fel din cauza clarității absolute a logicii.

Sugestii pentru a evita iadul întâlnirilor

Acum că ne simțim confortabil cu aritmetica datei, suntem în măsură să înțelegem cele mai bune practici de urmat și motivele pentru care le urmăm.

Obținerea DateTime de la utilizator

Dacă primiți data și ora de la utilizator, cel mai probabil sunteți în căutarea lui DateTime locală. Am văzut în secțiunea de aritmetică a datei că constructorul Date poate accepta data în mai multe moduri diferite.

Pentru a elimina orice confuzie, sugerez întotdeauna să creați o dată folosind new Date(year, month, day, hours, minutes, seconds, milliseconds) , chiar dacă aveți deja data într-un format analizabil valid. Dacă toți programatorii din echipa ta urmează această regulă simplă, va fi extrem de ușor să menții codul pe termen lung, deoarece este cât se poate de explicit cu constructorul Date .

Partea tare este că puteți folosi variațiile care vă permit să omiteți oricare dintre ultimii patru parametri dacă sunt zero; adică, new Date(2012, 10, 12) este aceeași cu new Date(2012, 10, 12, 0, 0, 0, 0) deoarece parametrii nespecificați sunt implicit la zero.

De exemplu, dacă utilizați un selector de dată și oră care vă oferă data 2012-10-12 și ora 12:30, puteți extrage părțile și crea un nou obiect Data după cum urmează:

 const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);

Încercați să evitați crearea unei date dintr-un șir decât dacă este în format de dată ISO. Utilizați în schimb metoda Data (an, lună, dată, ore, minute, secunde, microsecunde).

Obține doar data

Dacă obțineți doar data, data de naștere a unui utilizator, de exemplu, cel mai bine este să convertiți formatul într-un format de dată ISO valid pentru a elimina orice informații despre fusul orar care pot determina deplasarea datei înainte sau înapoi atunci când este convertită în UTC. De exemplu:

 const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString();

În cazul în care ați uitat, dacă creați un obiect Date cu intrare în format de dată ISO valid (AAAA-LL-ZZ), acesta va fi implicit UTC în loc să fie implicit fusul orar al browserului.

Stocarea Datei

Stocați întotdeauna DateTime în UTC. Trimiteți întotdeauna un șir de dată ISO sau o ștampilă de timp în partea din spate.

Generații de programatori de computere au realizat acest adevăr simplu după experiențe amare în care au încercat să arate utilizatorului ora locală corectă. Stocarea orei locale în back-end este o idee proastă, este mai bine să lăsați browserul să se ocupe de conversia la ora locală în front-end.

De asemenea, ar trebui să fie evident că nu ar trebui să trimiteți niciodată un șir DateTime precum „20 iulie 1989 12:10” la final. Chiar dacă trimiteți și fusul orar, creșteți efortul pentru alți programatori de a vă înțelege intențiile și de a analiza și stoca corect data.

Utilizați toISOString() sau toJSON() ale obiectului Date pentru a converti DateTime local în UTC.

 const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)

Afișarea datei și orei

  1. Obțineți marca temporală sau data formatată ISO dintr-un API REST.
  2. Creați un obiect Date .
  3. Utilizați toLocaleString() sau toLocaleDateString() și toLocaleTimeString() sau o bibliotecă de date pentru a afișa ora locală.
 const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });

Când ar trebui să stocați și ora locală?

„Uneori este important să cunoaștem fusul orar în care a avut loc un eveniment, iar conversia într-un singur fus orar șterge irevocabil aceste informații.

„Dacă faci o promovare de marketing și vrei să știi ce clienți au plasat comenzi în jurul prânzului, o comandă care pare să fi fost plasată la prânz GMT nu este foarte utilă când a fost plasată de fapt la micul dejun în New York.”

Dacă întâlniți acest tip de situație, ar fi mai înțelept să economisiți și ora locală. Ca de obicei, am dori să creăm data în format ISO, dar mai întâi trebuie să găsim compensarea fusului orar.

Funcția getTimeZoneOffset() a obiectului Date ne indică numărul de minute care, atunci când sunt adăugate la o oră locală dată, dă ora UTC echivalentă. Vă sugerez să îl convertiți în formatul (+-)hh:mm, deoarece face mai evident că este o compensare a fusului orar.

 const now = new Date(); const tz = now.gettime zoneOffset();

Pentru fusul meu orar +05:45, primesc -345, acesta nu este doar semnul opus, dar un număr ca -345 ar putea fi complet derutant pentru un dezvoltator back-end. Așa că convertim acest lucru în +05:45.

 const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;

Acum obținem restul valorilor și creăm un șir ISO valid care reprezintă DateTime local.

 const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());

Dacă doriți, puteți include datele UTC și datele locale într-un obiect.

 const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, }

Acum, în ultimul rând, dacă doriți să aflați dacă evenimentul a avut loc înainte de ora locală amiază, puteți analiza data și pur și simplu utilizați funcția getHours() .

 const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); }

Nu am folosit tzOffset aici, dar îl stocăm în continuare pentru că s-ar putea să avem nevoie de el în viitor în scopuri de depanare. De fapt, puteți trimite doar compensarea fusului orar și doar ora UTC. Dar îmi place să stochez și ora locală, deoarece în cele din urmă va trebui să stocați data într-o bază de date, iar ora locală stocată separat vă permite să interogați direct pe baza unui câmp, mai degrabă decât să trebuiască să efectuați calcule pentru a obține data locală.

Uneori, chiar și cu fusul orar local stocat, veți dori să afișați datele într-un anumit fus orar. De exemplu, orele pentru evenimente ar putea avea mai mult sens în fusul orar al utilizatorului actual, dacă acestea sunt virtuale, sau în fusul orar în care vor avea loc fizic, dacă nu sunt. În orice caz, merită să priviți în prealabil soluțiile consacrate pentru formatarea cu nume explicite de fus orar.

Configurare server și baze de date

Configurați întotdeauna serverele și bazele de date pentru a utiliza fusul orar UTC. (Rețineți că UTC și GMT nu sunt același lucru - GMT, de exemplu, ar putea implica o trecere la BST în timpul verii, în timp ce UTC nu va face niciodată.)

Am văzut deja cât de dureroase pot fi conversiile fusului orar, mai ales când sunt neintenționate. Trimiterea întotdeauna UTC DateTime și configurarea serverelor pentru a fi în fusul orar UTC vă poate face viața mai ușoară. Codul dvs. de back-end va fi mult mai simplu și mai curat, deoarece nu trebuie să facă nicio conversie de fus orar. DateTime data coming in from servers across the world can be compared and sorted effortlessly.

Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.

It's Time for Better Date Handling

Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.

The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.

Related: Buggy JavaScript Code: The 10 Most Common Mistakes JavaScript Developers Make