Laden...

Blazor: Anzeige DB-UTC-Zeiten in lokaler Benutzerzeit

Erstellt von TheShihan vor 2 Jahren Letzter Beitrag vor 2 Jahren 499 Views
TheShihan Themenstarter:in
170 Beiträge seit 2007
vor 2 Jahren
Blazor: Anzeige DB-UTC-Zeiten in lokaler Benutzerzeit

Ich habe ein Blazor (Server) Projekt, welches das Entity Framework verwendet.

Es ist möglich, gewisse Entitäten zu teilen, sprich mehrere Benutzer haben z.B Rechte auf ein "Entry". Ein Entry hat also nicht direkt ein Benutzermapping (Creator Mapping ist aber da).

Alle Dates (DateTime) möchte ich als UTC in der DB speichern. Denke das ist am sinnvollsten, z.B. für Funktionen wie: gebe mir die 10 zuletzt veröffentlichten Einträge aller Benutzer. Würde ich die Daten in der jeweiligen Benutzerzeit speichern ginge das natürlich nicht. (nur ein Beispiel)

Ich weiss auch wie ich grundsätzlich (mittels JavaScript Interop) die lokale Benutzerzeitzone (resp. das Offset) herausfinde und verwenden kann. Habe es aktuell nach folgendem Beispiel implementiert - das heisst ich habe einen TimeZoneService (registriert als _scoped _ Service im Startup.cs):
Convert DateTime to user's time zone with server-side Blazor - Meziantou's blog

Für die Anzeige/Bearbeitung der Entitäten verwende ich vielfach Radzen-Komponenten, ich glaube aber, dass es bezüglich der Frage keine Rolle spielt, da es ein allgemeines "Problem" darstellt. Die Komponente bindet an ein Property der Entität. Aktuelles Beispiel (siehe _RadzenDatePicker _-> @bind-Value):


<RadzenGridColumn TItem="Entry" Property="Date" Title="Date">
    <Template Context="entry">
        <div>@entry.DateLocal.ToString("g")</div>
    </Template>
    <EditTemplate Context="entry">
        <RadzenDatePicker TValue="DateTime" @bind-Value=@entry.DateLocal ShowTime="true" ShowSeconds="false" HoursStep="1" MinutesStep="1" SecondsStep="10"  DateFormat="g" />
    </EditTemplate>
</RadzenGridColumn>

Wenn die Daten in der DB als UTC gespeichert sind, dann wird sie dem Benutzer so natürlich auch als UTC angezeigt, respektive er editiert UTC Daten, würde diese auf seine lokale Zeitzone ausgerichtet anpassen und somit als (logisch) nicht mehr UTC zurückspeichern.

Ich suche nun nach einem eleganten, sauberen Weg, so dass die DB weiterhin die DateTimes als UTC speichert, der Benutzer aber die Daten in seiner Zeitzone sieht.

Ein Lösungsansatz war, dass ich ein zusätzliches _NotMapped _Property auf der Entität mache, dieses wandelt das Property mit dem UTC-Wert beim Lesen mit Hilfe des _TimeZoneServices _in die lokale Benutzer-Zeitzone um. Beim Schreiben verhält es sich umgekehrt.

Allerdings ist es erstens vermutlich nicht sauber, wenn man etwas in ein Entity injected und zweites war mir dies bisher noch gar nicht möglich. Da ich diesbezüglich den DB-Context erweitert habe (_TimeZoneService _Injection bereits dort, das es so in die Entitäten kommt). Allerdings crasht es dann, da ein Scoped Service nicht im DB-Context Injected werden kann.

Dann dachte ich, dass ich es so machen müsste, dass man diesen Service nicht in der Entität braucht. Dass die Zeitzone z.B. über eine Benutzerprofil-Seite auf dem _ApplicationUser _als Property gespeichert werden könnte. Allerdings bringt mir das nichts, da ja mehrere Benutzer eine Entität verwenden können und es hier keine direkte Beziehung gibt.

Im Internet findet ich merkwürdigerweise für diese Problemstellung keine Lösungen, obwohl ich annehme, dass dies doch ein häufiges Problem sein müsste, auch z.B. in ASP .NET.

Hat wer eine Idee wie man das lösen könnte?

Build something that's idiot proof, and they'll build a better idiot

P
441 Beiträge seit 2014
vor 2 Jahren

Speichere das Datum besser als DateTimeOffset in der Datenbank, dann benötigst du nicht so viele Konvertierungen.
[FAQ] DateTime vs. DateTimeOffset und der Umgang mit Zeiten in .NET

Mit DateTimeOffset.ToLocalTime (Api-Docs) bekommst du generell die lokale Uhrzeit. Ich bin mir nicht sicher, wie sich das bei Blazor Server auswirkt. Probier es am besten einfach aus.

16.840 Beiträge seit 2008
vor 2 Jahren

Alle Dates (DateTime) möchte ich als UTC in der DB speichern.

Das ist schon der Fehler, siehe [FAQ] DateTime vs. DateTimeOffset und der Umgang mit Zeiten in .NET
Auch in der Entity Framework Dokumentation kannst Du entnehmen, dass Du DateTimeOffset verwenden sollst, genauso in der Datenbank-Dokumentation.

Am Ende musst Du Dich um die Zeitzonen gar nicht mehr kümmern, weil das DateTimeOffset alles automatisch macht.
Du musst nur noch die Zeitzone des Benutzers haben, und das in der Formatierung mit übergeben.

Im Internet findet ich merkwürdigerweise für diese Problemstellung keine Lösungen, obwohl ich annehme, dass dies doch ein häufiges Problem sein müsste, auch z.B. in ASP .NET.

Einfach die offizielle Doku anschauen, denn die beschreibt das sehr deutlich (ist verlinkt in der DateTime FAQ).

Damit Du es einfacher hast kannst Dir einfach einen TagHelper oder eine Komponente schreiben, die das alles automatisch macht.
Machen wir hier im Forum auch.


<mycs-user-time value="@(builtOn)" format="dd.MM.yyyy - HH:mm" />

TheShihan Themenstarter:in
170 Beiträge seit 2007
vor 2 Jahren

OK, danke für die Antworten. Ich habe mich nun in die Thematik bezüglich DateTimeOffset eingelesen und etwas damit herumexperimentiert.

Allerdings denke ich, gibt es noch ein Problem.

Grundsätzlich möchte ich ja die Zeit für den Benutzer nach dem auslesen aus der DB wieder in seiner Zeit anzeigen und zwar wo er aktuell ist (gemäss seinem aktuellen Time Zone Offset). Das heisst, wenn er ein Element z.B. in den USA erstellt hat, nun aber in UK ist, soll es im die Zeit entsprechend umgerechnet für UK anzeigen.

So wie ich es verstehe, kann man das ja mit DateTimeOffset.ToLocalTime(), es gibt auch noch das Property DateTimeOffset.LocalDateTime - der Unterschied erschliesst sich mir noch nicht.

Wenn ich das in Blazor (Server) teste, erhalte ich in beiden Fällen die gleiche korrekte Uhrzeit:


<div>
    <h2>DateTimeOffset Testing</h2>
    <p>
        UTC Now to Local Time (Method): @DateTimeOffset.UtcNow.ToLocalTime().ToString("g")
        <br />
        UTC Now to Local Time (Property): @DateTimeOffset.UtcNow.LocalDateTime.ToString("g")
    </p>
</div>

Allerdings ist das erst einmal lokal getestet, ich weiss noch nicht wie sich das auf einem Server in einer anderen Zeitzone verhält.

Das heisst, ich könnte annehmen, dass ich auf das Property "LocalDateTime" binden könnte in meinem Control. Doch kann das wirklich funktionieren? Meine Vermutung ist, dass es dann von UTC einfach in die Zeitzone des Servers konvertiert wird, nicht die des Benutzers. Oder wie war folgender Satz gemeint:
"Du musst nur noch die Zeitzone des Benutzers haben, und das in der Formatierung mit übergeben."
?

Falls ich die Zeitzone des Benutzers nehmen muss und dann z.B. mittels des entsprechenden Offsets das neue DateTime(Offset) erstellen müsste, dann hätte ich ja wieder das ursprüngliche Problem, dass ich irgendwie den _TimeZoneService _in meine Entität bringen müsste.

Mache ich einen Überlegungsfehler?

Build something that's idiot proof, and they'll build a better idiot

16.840 Beiträge seit 2008
vor 2 Jahren

So wie ich es verstehe, kann man das ja mit DateTimeOffset.ToLocalTime(), es gibt auch noch das Property DateTimeOffset.LocalDateTime - der Unterschied erschliesst sich mir noch nicht.

Einfach in die Doku schauen:

  • ToLocalTime liefert ein DateTimeOffset (Api-Docs) Objekt MIT der aktuellen Ortszeit
  • LocalDateTime ist ein DateTime (Api-Docs) Objekt und bekommt die Kind-Eigenschaften der Zeitdarstellung des aktuellen DateTimeOffset Objekt

LocalDateTime verwendet man vor allem dann, wenn man eine API verwendet, die leider DateTime verwendet und man keinen Einfluss hat, das zu korrigieren.

Das heisst, wenn er ein Element z.B. in den USA erstellt hat, nun aber in UK ist, soll es im die Zeit entsprechend umgerechnet für UK anzeigen.

DateTimeOffset ist immer eine eindeutige Zeitdarstellung, egal wo Du es verwendest - im Gegensatz zu DateTime (Api-Docs).
Es ist daher für die Zeit völlig egal, wo die Entität erzeugt wird.

Zum Anzeigen brauchst Du daher nur zwei Infos:

Danach kannst Du zwei Wege gehen:

  • Du erzeugst ein neues DateTimeOffset anhand des DB-DateTimeOffset Objekts und gibst die Ziel-Zeitzonemit
  • Du erzeugst ein neues DateTimeOffset anhand des DB-DateTimeOffset Objekts und gibst die Ziel-++Offset ++mit

Beides geht dann über den Konstruktor oder TimeZoneInfo.ConvertTime (Api-Docs)

Die Zeitzone des Users kannst Du entweder automatisch ermitteln (auf dem Client) oder Du speicherst sie mit dem User in seinen Eigenschaften ab.
Wenn Du DateTimeOffset verwendest musst Du das immer nur in der UI machen; Du musst niemals die Zeit auf dem Server / DB anpassen oder Dir um UTC-Referenzen sorgen machen.
Du brauchst daher auch niemals den TimeZoneService in der Entität.

Willst Du Dinge auf dem Server generieren und brauchst dazu die Uhrzeit des Users (zB ein PDF), dann brauchst die Info natürlich auch auf dem Server, zB. durch das User Setting.