Lucru corect cu data și ora. Eliminarea decalajului de fus orar de pe serverul SQL DateTimeOffset

Aproape toate proiectele se confruntă cu probleme cauzate de procesarea și stocarea necorespunzătoare a datelor și orelor. Chiar dacă proiectul este utilizat într-un singur fus orar, puteți primi surprize neplăcute după trecerea la ora de iarnă / vară. În același timp, puțini oameni sunt nedumeriți de implementarea mecanismului corect de la început, deoarece se pare că nu pot fi probleme cu acest lucru, deoarece totul este banal. Din păcate, realitatea ulterioară arată că nu este cazul.

În mod logic, se pot distinge următoarele tipuri de valori legate de dată și oră:


Luați în considerare fiecare articol separat, fără a uita.

data si ora

Să presupunem că laboratorul care a colectat materialul pentru analiză se află în fusul orar +2, iar ramura centrală, care monitorizează finalizarea la timp a analizelor, se află în fusul orar +1. Timpul dat în exemplu a fost notat când materialul a fost colectat de primul laborator. Apare întrebarea - ce cifră de timp ar trebui să vadă biroul central? Evident software-ul birou central ar trebui să apară 15 ianuarie 2014 12:17:15 - cu o oră mai puțin, deoarece, conform ceasului lor, evenimentul a avut loc chiar în acel moment.

Luați în considerare unul dintre posibilele lanțuri de acțiuni prin care datele trec de la client la server și invers, ceea ce vă permite să afișați întotdeauna corect data/ora în funcție de fusul orar actual al clientului:

  1. Valoarea este creată pe client, de exemplu 2 martie 2016 15 :13:36, clientul este în fusul orar +2.
  2. Valoarea este convertită într-o reprezentare șir pentru transmiterea către server - „2016-03-02T 15 :13:36+02:00”.
  3. Datele serializate sunt trimise la server.
  4. Serverul deserializează ora într-un obiect dată/oră, transformându-l în fusul orar curent. De exemplu, dacă serverul rulează la +1, atunci obiectul va conține 2 martie 2016 14 :13:36.
  5. Serverul salvează date în baza de date, în timp ce nu conține nicio informație despre fusul orar - tipurile de dată / oră cele mai frecvent utilizate pur și simplu nu știu nimic despre acesta. Astfel, baza de date va fi salvată pe 2 martie 2016 14 :13:36 intr-un fus orar "necunoscut".
  6. Serverul citește datele din baza de date și creează obiectul corespunzător cu valoarea 2 martie 2016 14 :13:36. Și deoarece serverul funcționează în fusul orar +1, această valoare va fi interpretată și în același fus orar.
  7. Valoarea este convertită într-o reprezentare șir pentru transmitere către client - „2016-03-02T 14 :13:36+01:00”.
  8. Datele serializate sunt trimise clientului.
  9. Clientul deserializează valoarea primită într-un obiect dată/oră, transformând-o în fusul orar curent. De exemplu, dacă este -5, atunci valoarea afișată ar trebui să fie 2 martie 2016 09 :13:36.
Totul pare a fi holistic, dar să ne gândim la ce poate merge prost în acest proces. De fapt, problemele aici pot apărea aproape la fiecare pas.
  • Ora de pe client poate fi formată fără un fus orar deloc - de exemplu, tipul DateTime în .NET cu DateTimeKind.Unspecified.
  • Motorul de serializare poate folosi un format care nu include o compensare a fusului orar.
  • La deserializarea unui obiect, offset-ul fusului orar poate fi ignorat, în special în deserializatoarele „de casă” - atât pe server, cât și pe client.
  • Când citiți dintr-o bază de date, un obiect dată/oră poate fi format fără un fus orar deloc - de exemplu, tipul DateTime în .NET cu DateTimeKind.Unspecified. Mai mult, cu DateTime în .NET, în practică, exact asta se întâmplă dacă nu specificați în mod explicit un alt DateTimeKind imediat după scădere.
  • Dacă serverele de aplicații care lucrează cu o bază de date comună se află în fusuri orare diferite, va exista o confuzie serioasă în decalajele de timp. Valoarea dată/ora scrisă în baza de date de serverul A și citită de serverul B va fi vizibil diferită de aceeași valoare inițială scrisă de serverul B și citită de serverul A.
  • Transferarea serverelor de aplicații dintr-o zonă în alta va interpreta greșit valorile date/ora deja stocate.
Dar cel mai serios dezavantaj din lanțul descris mai sus este utilizarea fusului orar local pe server. Dacă nu are ora de vară, atunci nu vor fi probleme suplimentare. Dar în rest, puteți obține o mulțime de surprize neplăcute.

Regulile de trecere la ora de vara/iarna sunt, strict vorbind, un lucru variabil. Diferite țări își pot schimba regulile din când în când, iar aceste modificări ar trebui să fie încorporate în actualizările de sistem cu mult timp înainte. În practică, au existat numeroase situații de funcționare incorectă a acestui mecanism, care, ca urmare, au fost rezolvate prin instalarea de remedieri rapide sau sistem de operare, sau biblioteci terță parte utilizate. Probabilitatea de a repeta aceleași probleme nu este zero, așa că este mai bine să aveți o modalitate de a le evita cu garanție.

Având în vedere considerentele descrise mai sus, să formulăm cea mai fiabilă și simplă abordare a transferului și stocării timpului: pe server și în baza de date, toate valorile trebuie convertite în fusul orar UTC.

Luați în considerare ce ne oferă această regulă:

  • Când trimite date către server, clientul trebuie să treacă decalajul fusului orar, astfel încât serverul să poată converti corect ora în UTC. O variantă alternativă este de a forța clientul să facă această transformare, dar prima opțiune este mai flexibilă. Când primește date înapoi de la server, clientul va traduce data și ora în fusul orar local, știind că va ajunge oricum în UTC.
  • În UTC nu există tranziții la ora de vară și, respectiv, de iarnă, problemele asociate cu aceasta vor fi irelevante.
  • Pe server, când citiți din baza de date, nu trebuie să convertiți valorile de timp, este suficient să indicați în mod explicit că corespunde UTC. În .NET, de exemplu, acest lucru poate fi realizat prin setarea DateTimeKind a obiectului de timp la DateTimeKind.Utc.
  • Diferența de fus orar dintre serverele care lucrează cu o bază de date comună, precum și transferul de servere dintr-o zonă în alta, nu vor afecta corectitudinea datelor primite.
Pentru a implementa o astfel de regulă, este suficient să aveți grijă de trei lucruri:
  1. Faceți mecanismul de serializare și deserializare astfel încât valorile date/ora să fie traduse corect din UTC în fusul orar local și invers.
  2. Asigurați-vă că deserializatorul din partea serverului creează obiecte dată/oră în UTC.
  3. Faceți astfel încât, atunci când scădeți din baza de date, obiectele date/ora să fie create în UTC. Acest articol este uneori furnizat fără modificări de cod - doar fusul orar al sistemului de pe toate serverele este setat la UTC.
Considerațiile și recomandările de mai sus funcționează excelent cu o combinație a două condiții:
  • În cerințele de sistem nu este nevoie să afișați ora locală și/sau decalajul fusului orar exact așa cum a fost stocat. De exemplu, în biletele de avion, ora de plecare și de sosire trebuie tipărită în fusul orar corespunzător locației aeroportului. Sau dacă serverul tipărește facturi create în țări diferite, fiecare ar trebui să ajungă la ora locală, nu convertită în fusul orar al serverului.
  • Toate valorile datei și orei din sistem sunt „absolute”, adică. descrieți un moment în timp în viitor sau trecut care are o singură valoare în UTC. De exemplu, „lansarea vehiculului de lansare a avut loc la ora 23:00, ora Kievului”, sau „întâlnirea se va desfășura între 13:30 și 14:30, ora Minsk”. În diferite fusuri orare, numerele pentru aceste evenimente vor fi diferite, dar vor descrie același moment în timp. Dar se poate întâmpla ca cerințele pentru software implică ora locală „relativă” pentru unele cazuri. De exemplu, „această emisiune TV se va difuza între orele 9:00 și 10:00 în fiecare țară în care există o sucursală a canalului TV”. Se pare că spectacolul programului nu este un eveniment, ci mai multe și, potențial, toate pot avea loc în diferite perioade de timp, în funcție de scara „absolută”.
Pentru cazurile în care prima condiție este încălcată, problema poate fi rezolvată folosind tipuri de date care conțin fusul orar - atât pe server, cât și în baza de date. Mai jos este o mică listă de exemple pentru diferite platforme și DBMS.
.NET datetimeoffset
Java org.joda.time.DateTime, java.time.ZonedDateTime
MS SQL datetimeoffset
Oracle, PostgreSQL TIMESTAMP CU FUS ORAR
MySQL

Încălcarea celei de-a doua condiții este un caz mai complicat. Dacă acest timp „relativ” trebuie stocat pur și simplu pentru afișare și nu există nicio sarcină pentru a determina momentul „absolut” în timp în care evenimentul a avut loc sau va avea loc pentru un anumit fus orar, este suficient să dezactivați pur și simplu conversia timpului. . De exemplu, utilizatorul a intrat la începutul transmisiei pentru toate sucursalele companiei de televiziune în data de 25 martie 2016 la ora 9:00, iar aceasta va fi transmisă, stocată și afișată în acest formular. Dar se poate întâmpla ca un planificator să efectueze automat acțiuni speciale cu o oră înainte de începerea fiecărei transmisii (trimite notificări sau verifică prezența unor date în baza de date a radiodifuzorului). Implementarea fiabilă a unui astfel de planificator nu este o sarcină banală. Să presupunem că programatorul știe în ce fus orar se află fiecare dintre filiale. Și una dintre țările în care există o sucursală decide să schimbe fusul orar după un timp. Cazul nu este atât de rar pe cât ar părea - în acest și în cei doi ani anteriori, am numărat mai mult de 10 astfel de evenimente (http://www.timeanddate.com/news/time/). Se pare că fie utilizatorii trebuie să păstreze legăturile la fusurile orare la zi, fie programatorul trebuie să preia automat aceste informații din surse globale, cum ar fi Hărți Google API Timezone. Nu mă angajez să ofer o soluție universală pentru astfel de cazuri, remarc pur și simplu că astfel de situații necesită un studiu serios.

După cum se poate observa din cele de mai sus, nu există o abordare unică care să acopere 100% din cazuri. Prin urmare, mai întâi trebuie să înțelegeți clar din cerințe care dintre situațiile menționate mai sus vor fi în sistemul dumneavoastră. Cu o probabilitate mare, totul se va limita la prima abordare propusă cu stocare în UTC. Ei bine, situațiile excepționale descrise nu o anulează, ci pur și simplu adaugă alte soluții pentru cazuri speciale.

data fara ora

Să presupunem că ne-am dat seama de afișarea corectă a datei și orei, ținând cont de fusul orar al clientului. Să trecem la date fără timp și la exemplul dat pentru acest caz la început – „noul contract intră în vigoare la 2 februarie 2016”. Ce se întâmplă dacă folosim aceleași tipuri și același mecanism pentru astfel de valori ca și pentru datele „obișnuite” cu timpul?

Nu toate platformele, limbile și SGBD-urile au tipuri de date. De exemplu, în .NET există doar tipul DateTime, nu există „doar Data” separat. Chiar dacă la crearea unui astfel de obiect a fost specificată doar o dată, ora este încă prezentă și este egală cu 00:00:00. Dacă traducem valoarea „2 februarie 2016 00:00:00” din zona cu un offset de +2 în +1, atunci obținem „1 februarie 2016 23:00:00”. Pentru exemplul de mai sus, acest lucru ar fi echivalent cu noul contract care începe pe 2 februarie într-un fus orar și pe 1 februarie în altul. Din punct de vedere juridic, acest lucru este absurd și, desigur, nu ar trebui să fie așa. Regula generala pentru datele „pure”, este extrem de simplu - astfel de valori nu ar trebui convertite la nici un pas de salvare și citire.

Există mai multe modalități de a evita conversia pentru date:

  • Dacă platforma acceptă un tip care reprezintă o dată fără oră, atunci acesta ar trebui utilizat.
  • Adăugați un flag special la metadatele obiectului care va spune serializatorului că fusul orar trebuie ignorat pentru valoarea dată.
  • Treceți data de la client și înapoi ca șir și stocați-o ca dată. Această abordare este incomodă dacă trebuie nu numai să afișați data pe client, ci și să efectuați unele operații asupra acesteia: comparație, scădere etc.
  • Treceți și stocați ca șir și convertiți la o dată doar pentru formatare, ținând cont de setările regionale ale clientului. Are chiar mai multe dezavantaje decât versiunea anterioară - de exemplu, dacă părțile de dată din șirul stocat nu sunt în ordinea „an, lună, zi”, atunci va fi imposibil să se facă o căutare eficientă indexată după intervalul de date.
Puteți, desigur, să încercați să dați un contraexemplu și să spuneți că contractul are sens numai în țara în care este încheiat, țara se află în același fus orar și, prin urmare, este posibil să se determine fără ambiguitate momentul intrării sale. in forta. Dar chiar și în acest caz, utilizatorii din alte fusuri orare nu vor fi interesați de ce moment din ora lor locală va avea loc acest eveniment. Și chiar dacă ar fi necesar să se arate acest moment în timp, atunci ar fi necesar să se afișeze nu numai data, ci și ora, ceea ce contrazice condiția inițială.

Interval de timp

Odată cu stocarea și procesarea intervalelor de timp, totul este simplu: valoarea acestora nu depinde de fusul orar, așa că nu există recomandări speciale aici. Ele pot fi stocate și transmise ca un număr de unități de timp (întreg sau virgulă mobilă, în funcție de precizia necesară). Dacă precizia secundă este importantă - atunci ca număr de secunde, dacă milisecunde - atunci ca număr de milisecunde etc.

Dar calculul intervalului poate avea capcane. Să presupunem că avem un cod C# generic care numără intervalul de timp dintre două evenimente:

DateTime start = DateTime.Now; //... DateTime end = DateTime.Now; ore duble = (sfârşit - început).TotalHours;
La prima vedere, nu există probleme aici, dar nu este. În primul rând, pot exista probleme cu testarea unitară a unui astfel de cod, dar vom vorbi despre asta puțin mai târziu. În al doilea rând, să ne imaginăm că ora de începere este ora de iarnă, iar ora de încheiere este ora de vară (de exemplu, numărul de ore de lucru este măsurat în acest fel, iar muncitorii au tură de noapte).

Să presupunem că codul rulează într-un fus orar în care ora de vară în 2016 are loc în noaptea de 27 martie și simulați situația descrisă mai sus:

DateTime start = DateTime.Parse("2016-03-26T20:00:15+02"); DateTime end = DateTime.Parse("2016-03-27T05:00:15+03"); ore duble = (sfârşit - început).TotalHours;
Acest cod va avea ca rezultat 9 ore, chiar dacă între aceste ore au trecut efectiv 8 ore. Acest lucru poate fi verificat cu ușurință prin schimbarea codului astfel:

DateTime start = DateTime.Parse("2016-03-26T20:00:15+02").ToUniversalTime(); DateTime end = DateTime.Parse("2016-03-27T05:00:15+03").ToUniversalTime(); ore duble = (sfârşit - început).TotalHours;
De aici concluzia - orice operații aritmetice cu data și ora trebuie efectuate folosind fie valori UTC, fie tipuri care stochează informații despre fusul orar. Și apoi înapoi pentru a traduce în local, dacă este necesar. Din acest punct de vedere, exemplul original este ușor de remediat prin schimbarea DateTime.Now în DateTime.UtcNow.

Această nuanță nu depinde de o anumită platformă sau limbaj. Iată un cod Java similar cu același dezavantaj:

LocalDateTime start = LocalDateTime.now(); //... LocalDateTime end = LocalDateTime.now(); ore lungi = ChronoUnit.HOURS.between(start, end);
De asemenea, este ușor de remediat - de exemplu, folosind ZonedDateTime în loc de LocalDateTime.

Programul evenimentelor programate

Programarea evenimentelor programate este o situație mai complexă. Nu există niciun tip generic care să vă permită să stocați programe în bibliotecile standard. Dar o astfel de sarcină nu este atât de rară, așa că soluții gata făcute pot fi găsite fără probleme. Un bun exemplu este formatul cron scheduler, care este folosit într-o formă sau alta de alte soluții, precum Quartz: http://quartz-scheduler.org/api/2.2.0/org/quartz/CronExpression.html . Acesta acoperă aproape toate nevoile de programare, inclusiv opțiunile „a doua vineri a lunii”.

În cele mai multe cazuri, scrierea propriului programator nu are sens, deoarece există soluții flexibile testate în timp, dar dacă dintr-un motiv oarecare este nevoie să vă creați propriul mecanism, atunci cel puțin formatul de programare poate fi împrumutat de la cron.

Pe lângă recomandările descrise mai sus cu privire la stocarea și procesarea diferitelor tipuri de valori de timp, există câteva altele despre care aș vrea să vorbesc și eu.

În primul rând, despre utilizarea membrilor clasei statice pentru a obține ora curentă - DateTime.UtcNow, ZonedDateTime.now(), etc. După cum am menționat, utilizarea lor direct în cod poate complica serios testarea unitară, deoarece fără cadre speciale simulate nu va fi posibilă modificarea orei curente. Prin urmare, dacă intenționați să scrieți teste unitare, ar trebui să aveți grijă ca implementarea unor astfel de metode să poată fi înlocuită. Există cel puțin două moduri de a rezolva această problemă:

  • Dedicați interfața IDateTimeProvider cu o singură metodă care returnează ora curentă. Apoi adăugați o dependență la această interfață în toate unitățile de cod în care trebuie să obțineți ora curentă. În timpul execuției normale a programului, implementarea implicită va fi injectată în toate aceste locuri, ceea ce returnează timpul real actual, iar în testele unitare - orice altă implementare necesară. Această metodă este cea mai flexibilă în ceea ce privește testarea.
  • Creați propria clasă statică cu o metodă pentru obținerea orei curente și capacitatea de a seta orice implementare a acestei metode din exterior. De exemplu, în cazul codului C#, această clasă poate expune proprietatea UtcNow și SetImplementation(Func impl). Utilizarea unei proprietăți sau metode statice pentru a obține ora curentă elimină necesitatea de a specifica în mod explicit dependența de o interfață suplimentară peste tot, dar din punctul de vedere al principiilor OOP, aceasta nu este o soluție ideală. Cu toate acestea, dacă dintr-un motiv oarecare opțiunea anterioară nu este potrivită, atunci o puteți folosi pe aceasta.
O problemă suplimentară care trebuie rezolvată atunci când migrați la propria implementare a furnizorului de timp actual este să vă asigurați că nimeni „moda veche” nu continuă să folosească clasele standard. Această sarcină este ușor de rezolvat în majoritatea sistemelor de control al calității codului. În esență, se rezumă la căutarea subșirului „nedorit” în toate fișierele, cu excepția celor în care este declarată implementarea „implicit”.

A doua nuanță cu obținerea orei curente este aceea clientul nu poate fi de încredere. Ora curentă pe computerele utilizatorilor poate fi foarte diferită de cea reală, iar dacă există o logică legată de aceasta, atunci această diferență poate strica totul. Toate locurile în care este nevoie să obțineți ora curentă ar trebui, dacă este posibil, să fie efectuate pe partea serverului. Și, așa cum am menționat mai devreme, toate operațiile aritmetice cu timpul trebuie efectuate fie în valori UTC, fie folosind tipuri care stochează offset-ul fusului orar.

Și încă un lucru pe care am vrut să-l menționez este standardul ISO 8601, care descrie formatul de dată și oră pentru schimbul de informații. În special, reprezentarea în șir a unei date și ore utilizate în serializare trebuie să se conformeze acestui standard pentru a preveni potențiale probleme de compatibilitate. În practică, este extrem de rar să fii nevoit să implementezi singur formatarea, astfel încât standardul în sine poate fi util în principal în scopuri informaționale.

Etichete: Adăugați etichete

Opțiune umană ([trebuie să vă înregistrați pentru a vedea acest link]).

Esența problemei este .. Dacă ați implementat accidental baza de date pe serverul SQL cu un „offset de dată” de 0, atunci a existat o problemă atunci când în baza de date este găsit un atribut cu tipul TIME, adică acest atribut este setat până la 01.01.0001 10:30:00 sau data înscrierii este goală 01.01.0001 00:00:00. Când un astfel de atribut este înregistrat, nu este înregistrat.
Pe Internet, ei sugerează crearea unei noi baze de date cu un offset de 2000.
Dar nu am vrut cu adevărat să creez o bază nouă. Și schimbați calea către baza de date pentru toți utilizatorii.
Apoi am mers pe cale, dar unde este această valoare stocată în SQL. Găsit și schimbat în 2000 și totul a fost ok..
Și acum, pas cu pas, unde să schimbi.

Dă afară toți utilizatorii.

ATENŢIE!!!

1. Mai întâi fă backup prin intermediul 1C i.e. încărcați-l pe *.dt
Acest lucru trebuie făcut înainte de a schimba „offset”
Dacă acest lucru nu se face, atunci în întreaga dvs. bază de date, ref., doc etc.
unde există recuzită data va fi să spunem 02.10.0009
CE NU ESTE PERMIS….
Deci ați făcut o încărcare pe *.dt

2. Accesați SQL Server Management Studio
Vă găsim baza în listă, apăsați semnul plus.
Găsiți folderul „Tabele” acolo și deschideți-l.
Se vor deschide o grămadă de mese, mergeți în partea de jos, găsiți masa
_YearOffset, stăm pe el și facem clic dreapta pe elementul „Open table”, vezi Fig. 1
Modificați valoarea de la 0 la 2000
Închideți SQL Server Management Studio

3. Accesați configuratorul și încărcați baza de date salvată anterior.

Dacă acest lucru nu se face, atunci toate datele vor fi cu anul 0009.
După ce baza de date s-a încărcat... Puteți merge la 1C și vă asigurați că datele sunt normale.
Ca urmare, am schimbat „data de compensare de la 0 la 2000”

Uneori se întâmplă ca această opțiune să nu poată fi folosită dintr-un motiv sau altul. Apoi există o versiune mai hardcore ([Trebuie să vă înregistrați pentru a vedea acest link]):

Declara cursorul TablesAndFields pentru

SELECTAȚI objects.name ca Tablename, columns.name ca coloană
DE LA dbo.sysobjects la fel de obiecte
stânga alăturați dbo.syscolumns ca coloane pe objects.id = columns.id
unde objects.xtype = „U” și columns.xtype = 61

deschide TablesAndFields

ÎN CAZUL @@FETCH_STATUS = 0
BEGIN Exec(" Actualizați"+ @TableName + "
a stabilit" + @ColumnName + " = ""2000- 01- 01 00:00:00" "
Unde" + @ColumnName + " > ""3999- 12- 31 23:59:59" "")

Aceasta se execută atâta timp cât preluarea anterioară reușește.
FETCH NEXT FROM TablesAndFields în @TableName, @ColumnName
Sfârşit

închideți TablesAndFields
dealocați TablesAndFields
merge

Înainte de orice manipulare, nu uitați să faceți copii ale bazelor de date!

Problema nu are nimic de-a face cu baza de date. Dacă setați un punct de întrerupere sau introduceți o ieșire undeva, ar trebui să puteți vedea decalajul tăiat la scurt timp după acest cod:

TestDateAndTime = testDateAndTime.DateTime.Date;

Să o descompunem:

  • Ați început cu o valoare DateTimeOffset de 2008-05-01T08:06:32+01:00
  • Apoi ați apelat .DateTime, ceea ce a dus la o valoare DateTime de 2008-05-01T08:06:32 cu DateTimeKind.Unspecified.
  • Apoi ați apelat .Date , ceea ce a dus la o valoare DateTime de 2008-05-01T00:00:00 cu DateTimeKind.Unspecified .
  • Alocați rezultatul testDateAndTime , care este de tip DateTimeOffset . Aceasta apelează o distribuție implicită de la DateTime la DateTimeOffset - care se aplica local Fus orar. În cazul dvs., offset-ul pentru acea valoare în fusul orar local pare să fie -04:00 , deci valoarea rezultată este un DateTimeOffset de 2008-05-01T00:00:00-04:00, așa cum ați descris.

Ai spus:

Scopul final este de a avea pur și simplu o dată fără decalaj orar sau fus orar.

Ei bine, există în prezent tip de date C# non-nativ, care este doar o dată fără oră. Există un tip de date pur Timpul sistemului pachet în corefxlab , dar nu este încă pregătit pentru o aplicație tipică de producție. Există un LocalDate în biblioteca Noda Time pe care îl puteți folosi astăzi, dar trebuie totuși să convertiți înapoi la un tip nativ înainte de a salva în baza de date. Deci, între timp, cel mai bun lucru pe care îl puteți face este:

  • Schimbați-vă SQL Server pentru a utiliza tipul de dată pe câmp.
  • În codul dvs. .NET, utilizați DateTime cu ora 00:00:00 și DateTimeKind.Unspecified . Trebuie să vă amintiți să ignorați partea de timp (deoarece există date valabile fără miezul nopții locale în anumite fusuri orare).
  • Schimbați elementele de recuzită de testare pentru a fi DateTime , nu DateTimeOffset .

În general, în timp ce DateTimeOffset este potrivit pentru un număr mare de scenarii (de exemplu, marcajele de timp evenimente), nu se potrivește bine pentru valorile numai pentru date.

Vreau data curenta cu decalaj zero.

daca tu chiar vreau este ca DateTimeOffset, ai putea face:

TestDateAndTime = nou DateTimeOffset(testDateAndTime.Date, TimeSpan.Zero);

Cu toate acestea, nu recomand să faceți acest lucru. Făcând asta, iei local data valorii inițiale și susțin că este în UTC. Dacă offset-ul inițial este altceva decât zero, aceasta va fi o afirmație falsă. Ulterior, va duce la erori diferite, deoarece de fapt vorbiți despre un alt moment în timp (cu potențial o dată diferită) decât cel pe care l-ați creat.

În ceea ce privește întrebarea suplimentară pusă în forumul dvs. Specificarea DateTimeKind.Utc modifică comportamentul castului implicit. În loc să utilizați fusul orar local, se folosește ora UTC, care are întotdeauna un offset zero. Rezultatul este același cu aspectul mai explicit pe care l-am dat mai sus. Recomand în continuare împotriva ei din aceleași motive.

Luați în considerare un exemplu care începe cu 2016-12-31T22:00:00-04:00 . Conform abordării dvs., ar trebui să stocați 2016-12-31T00:00:00+00:00 în baza de date. Cu toate acestea, acestea sunt două timpuri diferite. Primul, normalizat la UTC, va fi 2017-01-01T02:00:00+00:00 , iar al doilea, convertit într-un alt fus orar, va fi 2016-12-30T20:00:00-04:00 . Fiți atenți la schimbarea datei în conversie. Acesta nu este probabil comportamentul pe care ați dori să îl utilizați în aplicația dvs.

DateTimeOffset testDateAndTime = new DateTimeOffset(2008, 5, 1, 8, 6, 32, new TimeSpan(1, 0, 0)); //CURĂȚARE ORA ȘI DATA testDateAndTime = testDateAndTime.DateTime.Date; var datesTableEntry = db.DatesTable.First(dt => dt.Id == someTestId); datesTableEntry.test=testDateAndTime; db.SaveChangesAsync();

REZULTAT ÎN BAZĂ DE DATE: 2008-05-01 00:00:00.0000000 -04:00

Cum se transformă -4:00 în +00:00 (din cod înainte de salvare)?

Am încercat:

Sarcina publică SetTimeZoneOffsetToZero(DateTimeOffset dateTimeOffSetObj) ( TimeSpan zeroOffsetTimeSpan = nou TimeSpan(0, 0, 0, 0, 0); return dateTimeOffSetObj.ToOffset(zeroOffsetTimeSpan); )

El nu face nimic.

Scopul final este doar de a avea o dată fără decalaj orar sau fus orar. NU vreau să convertesc ora într-un alt fus orar (adică 00:00:00.0000000 Nu vreau să scadă 4 ore din 00:00:00.0000000 timp și 00:00:00.0000000 să compenseze ora setată cu +00: 00, vreau doar să seteze offset-ul la +00:00). Vreau data curentă cu zero offset.

Editați | ×:

Iată ce puteți oferi în altă parte:

DateTimeOffset testDateAndTime = new DateTimeOffset(2008, 5, 1, 8, 6, 32, new TimeSpan(1, 0, 0)); testDateAndTime = testDateAndTime.DateTime.Date; //Zero out time porțiune testDateAndTime = DateTime.SpecifyKind(testDateAndTime.Date, DateTimeKind.Utc); //Porțiunea offset „Zero out”.

Eram sigur că SpecifyKind va fi SpecifyKind al dateTimeOffset-ului meu, schimba AMBĂ offset-ul orar și al fusului orar, de exemplu, dar în testare pare să schimbe doar offset-ul fusului orar, ceea ce vreau. Există vreo problemă cu asta?

1 raspuns

Problema nu are nimic de-a face cu baza de date. Dacă ați setat un punct de întrerupere sau ați înregistrat o ieșire undeva, ar trebui să vedeți decalajul atașat la scurt timp după acest cod:

TestDateAndTime = testDateAndTime.DateTime.Date;

Să o descompunem:

  • Ați început cu DateTimeOffset 2008-05-01T08:06:32+01:00
  • Apoi ați apelat .DateTime, rezultând o valoare DateTime de 2008-05-01T08:06:32 cu DateTimeKind.Unspecified.
  • Apoi ați apelat .Date , ceea ce a dus la o valoare DateTime de 2008-05-01T00:00:00 cu DateTimeKind.Unspecified .
  • Întoarceți rezultatul în testDateAndTime , care este de tip DateTimeOffset . Acest lucru determină o conversie implicită de la DateTime la DateTimeOffset care utilizează fusul orar local. În cazul dvs., se pare că offset-ul pentru acea valoare în fusul dvs. orar local este -04:00 , deci valoarea rezultată este DateTimeOffset 2008-05-01T00:00:00-04:00 așa cum ați descris,

Ai spus:

Scopul final este doar de a avea o dată fără decalaj orar sau fus orar.

Ei bine, în prezent nu există un tip de date nativ C#, care este doar o dată fără oră. Există un tip pur Date în pachetul System.Time al corefxlab, dar acesta nu este destul de pregătit pentru o aplicație tipică de producție. Există un LocalDate în biblioteca de timp Noda pe care îl puteți folosi astăzi, dar trebuie totuși să convertiți înapoi la tipul nativ înainte de a o stoca în baza de date. Deci, între timp, cel mai bun lucru pe care îl puteți face este:

  • Schimbați-vă SQL Server pentru a utiliza tipul de dată în acest câmp.
  • În codul dvs. .NET, utilizați DateTime cu ora 00:00:00 și DateTimeKind.Unspecified . Trebuie să vă amintiți să ignorați partea de timp (deoarece există date valabile fără miezul nopții locale în anumite fusuri orare).
  • Schimbați alerta de testare să fie DateTime , nu DateTimeOffset .

În general, în timp ce DateTimeOffset este potrivit pentru un numar mare scenarii (de exemplu, evenimente de marcare temporală), nu este potrivit pentru valorile de date.

Vreau data curentă cu zero offset.

Dacă chiar îl doriți ca DateTimeOffset, ați face:

TestDateAndTime = nou DateTimeOffset(testDateAndTime.Date, TimeSpan.Zero);

Cu toate acestea, vă sfătuiesc împotriva ei. Procedând astfel, luați data locală a valorii inițiale și afirmați că este în UTC. Dacă offset-ul inițial este altceva decât zero, aceasta va fi o afirmație falsă. Ulterior, va duce la erori diferite, deoarece de fapt vorbiți despre un alt moment în timp (cu potențial o dată diferită) decât cel pe care l-ați creat.

În ceea ce privește întrebarea suplimentară adresată în editarea dvs. - specificarea DateTimeKind.Utc modifică comportamentul castului implicit. În loc să utilizați fusul orar local, se folosește ora UTC, care are întotdeauna un offset zero. Rezultatul este același cu aspectul mai explicit pe care l-am dat mai sus. Recomand în continuare împotriva ei din aceleași motive.

Luați în considerare exemplul de a începe din 2016-12-31T22:00:00-04:00 . Conform abordării dvs., ar trebui să stocați 2016-12-31T00:00:00+00:00 în baza de date. Cu toate acestea, acestea sunt două timpuri diferite. Primul, normalizat la UTC, va fi 2017-01-01T02:00:00+00:00 , iar al doilea, convertit într-un alt fus orar, va fi 2016-12-30T20:00:00-04:00 . Fiți atenți la schimbarea datei în conversie. Acesta nu este probabil comportamentul pe care ați dori să îl utilizați în aplicația dvs.