Warum kann ein DateTime nicht in ein TimeSpan (und umgekehrt) konvertiert werden?

  • Allgemein

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von petaod.

    Warum kann ein DateTime nicht in ein TimeSpan (und umgekehrt) konvertiert werden?

    Die Frage in der Überschrift kann wohl etwas missverstanden werden. Es geht mir nicht darum, wie sowas implementiert werden kann, sondern vielmehr um eine "philosophische" Betrachtung des Themas.

    Aufgekommen ist es bei mir, als ich im Rahmen einer Anwendung TimeSpan-Werte - also Objekte, die eine Dauer wiedergeben - in einer Datenbank speichern wollte, die den Timespan-Datentyp nicht unterstützt, wohl aber den Typ Datum bzw. DateTime.

    Der Versuch, ein TimeSpan-Objekt mithilfe der im .NET-Framework zur Verfügung gestellten Umwandlungsmöglichkeiten - z.B. CType, DirectCast, System.Convert - in ein DateTime-Objekt zu überführen, schlug jedoch fehl. Auf der Suche nach einer Begründung, warum man das eine nicht in das andere umsetzen können soll, stieß ich in diversen Foren immer wieder auf das Totschlagargument, ein DateTime sei nunmal ein festgelegter Zeitpunkt und ein TimeSpan nur eine Zeitspanne, mit der man ohne einen Referenz-(Zeit-)Punkt keinen DateTime-Zeitpunkt bestimmen kann. Oder anders: TimeSpan ist was relatives, DateTime was absolutes.

    Auf den ersten Blick leuchtet das ein, auf den zweiten Blick aber ergibt das keinen Sinn, vor allem wenn man auch mal andere Teile des .NET-Frameworks oder andere weitverbreitete Bibliotheken betrachtet.

    Exkurs: DirectX

    In Direct3D wird viel mit Punkten im 3D-Raum und aber auch mit Vektoren im 3D-Raum herumhantiert. Der Unterschied zwischen Punkt und Vektor ist tatsächlich mit dem Unterschied zwischen DateTime und TimeSpan vergleichbar. Der Punkt ist eine feste (absolute) Position, während der Vektor nur eine relative Position ohne festen Bezugspunkt angibt. Erst durch die Anwendung des Vektors auf einen Bezugspunkt (z.B. den Ursprung) erhalten wir einen definierten Punkt im Raum.

    Wo auch immer jedoch in der DirectX-API ein Punkt (also eine absolute Position, z.B. die Kameraposition) definiert wird, kommt ein Vector3D zum Einsatz. Es gibt in DirectX einfach keine separate Point-Klasse. Wird die Definition eines Punkts erwartet, so ist der Typ des Parameters immer ein Vektor bei dem einfach implizit definiert wird, dass sein Bezugspunkt der Ursprung des Koordinatensystems ist.

    Betrachtet man nun, wie ein Datum intern gehandhabt wird, sieht es hier doch genauso aus. Denn ein DateTime-Objekt speichert intern doch im Grunde nichts anderes als "die Zeitspanne, die seit dem 1.1.0001 0:00 Uhr vergangen ist" (siehe MSDN), womit auch ein DateTime tatsächlich nur eine TimeSpan mit einem festen Bezugspunkt - dem Zeitpunkt 1.1.0001 0:00 Uhr - darstellt.
    Die kleinste Einheit darin ist 1 "Tick", wobei 1 Tick 100 Nanosekunden, also 1/10 Millisekunde entspricht. Über die Anzahl der Ticks seit dem 1.1.0001 n.Chr. wird also ein DateTime definiert. Der Unterschied zum TimeSpan? Keiner, denn auch ein TimeSpan definiert nur die Anzahl Ticks, die eine Zeitspanne dauert.

    Wenn also DirectX einen Vektor als Punkt betrachten will und dabei einfach den Bezugspunkt als Ursprung des Koordinatensystem impliziert, wieso kann dann nicht auch ein TimeSpan als DateTime betrachtet werden, indem man eben das Ursprungsdatum 1.1.0001 0:00 Uhr als Bezugspunkt impliziert?

    Zugegeben, als "streng-gläubiger" OOPler muss es einem in der Seele wehtun, solch blasphemische Betrachtungen zu lesen. Ein DateTime ist ein DateTime und ein TimeSpan ist ein TimeSpan - fertig.

    Aus "praktischer Sicht" heraus sehe ich keinen Grund, warum man eine Zeitspanne von "20 Minuten" nicht in ein Datum "1.1.0001 0:20 Uhr" umwandeln können soll, um daraus wieder die Zeitspanne "20 Minuten" herzuleiten.

    Im Grunde bietet das .NET-Framework sogar indirekt die Möglichkeit dieser Umwandlung. Sowohl DateTime als auch TimeSpan bieten mit der identisch benannten Eigenschaft "Ticks" die Möglichkeit, die 1/10 Millisekunden Dauer bzw. 1/10 Millisekunden, die zwischen dem 1.1.0001 0:00 Uhr und dem Datum liegen zu bestimmen. Und sowohl die DateTime wie auch die TimeSpan-Klasse verfügen über einen Konstruktor, mit dem man jeweils ein Objekt mit Hilfe dieser Anzahl an 1/10 Millisekunden instanzieren kann. So lässt sich aus einem TimeSpan-Objekt relativ einfach mit new DateTime(timeSpan.Ticks) ein DateTime-Objekt herleiten, mit dem es einem z.B. möglich wird, den Wert auch in solchen Datenbanken zu speichern, die nur DateTime-Typen, aber keine TimeSpan-Typen unterstützen (SQLite wäre so ein Beispiel). Anders herum kann solche ein DateTime-Objekt wieder mit new TimeSpan(dateTime.Ticks) in ein TimeSpan-Objekt überführt werden.

    Warum mich das ganze beschäftigt und ich es als störend empfinde, dass diese Form der Konvertierung nicht auch durch entsprechende Cast-Operatoren oder die System.Convert-Klasse unterstützt wird, ist der Umstand, dass ich in Umgebungen, in denen solche Konvertierungen typ-unabhängig funktionieren sollen, so zum Beispiel ein O/R-Mapper für Datenbanken, nun mehr oder weniger gezwungen bin, einen zu speichernden Typ separat darauf zu testen, ob er einem TimeSpan entspricht und das Ziel aber nur DateTime akzeptiert (oder umgekehrt), damit ich hier an System.Convert vorbei den Mechanismus über die Ticks-Konstruktoren implementieren kann.

    /discuss :D
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.
    Wenn die Frage ist, warum sowas in MSDN nicht implementiert wurde, dann ist hier die Antwort:
    Ich denke, es hat schlichtweg niemand daran gedacht, dass es notwendig sein wird, die relative Zeit (TimeSpan) in eine absolute Zeit (DateTime) zu verwandeln. Wie ich es verstehe, funktioniert DirectCast über das Objekt selbst, d.h. das zu wandelnde Objekt muss die Wandlung irgendwo im Hintergrund implementiert haben oder? Und die Entwickler von TimeSpan haben nicht daran gedacht, es zu implementieren, dass es gleichzeitig ein DateTime-Objekt werden kann, der als "TimeSpan"-Dauer nach dem 1.1.0001 ist.
    @Arby:: Sieh es mal einfach so:
    Was ist {4.12.2013 12:00:00} MINUS {4.12.2012 12:00:00} ? ==> 1 Jahr. Nicht aber Das Jahr 0001.
    Was ist {4.12.2013 12:00:00} PLUS {4.12.2012 12:00:00} ? Das ist nicht definiert.
    aber
    Was ist {4.12.2013 12:00:00} PLUS | MINUS { 1 Jahr } ? {4.12.2014 12:00:00} | {4.12.2012 12:00:00}.

    Analog dazu:

    Was ist { 19 °C } MINUS { 17 °C } ? ==> 2 K.
    Was ist { 19 °C } PLUIS { 17 °C } ? ==> Das ist nicht definiert.
    aber
    Was ist { 19 °C } PLUS | MINUS { 1 K } ? {20 °C} | {18 °C}.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    siehe auch:

    diesen Post

    @RodFromGermany
    Dein Temperatur-Beispiel hinkt!

    Es gibt einmal verschiedene Temperaturskalen für absolute Temperaturen: z.B. Kelvin und Celsius
    Zum anderen gibt es Temperaturdifferenzen. Eine Temperaturdifferenz kann man aber sowohl in Kelvin als auch in Celsius angeben.
    Wenn du auf ne absolute Temperatur eine Temperaturdifferenz addierst dann kannst du schreiben:
    0K + 1K
    oder
    0K + 1 °C
    oder
    0°C + 1°C
    oder
    0°C + 1 K
    das ist NICHT analog zu timespan/datetime bzw. location/size sondern nur ein bisschen ähnlich.

    Timespan und Size sind für sich genommen immer als Differenzen zu sehen. Das kann man aber nicht mit dem Temperaturbeispiel vergleichen, bei dem bei der einen Skala nur der Nullpunkt verschoben ist.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „markus.obi“ ()

    markus.obi schrieb:

    Eine Temperaturdifferenz kann man aber sowohl in Kelvin als auch in Celsius angeben.
    ist leider falsch, die Einheit der Temperaturdifferenz ist das Kelvin (K).

    markus.obi schrieb:

    0°C + 1°C
    Was bitte soll da rauskommen?
    Bitte rechne mir das vor.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    0°C + 1°C

    Was bitte soll da rauskommen?
    Bitte rechne mir das vor.

    0 und 1 sind die Skalare, °C ist die Einheit. Beim Addieren solcher Werte werden die Skalare addiert und die Einheit übernommen. Ergebnis wäre also 1°C.
    Im übrigen wird auch im Sprachgebrauch gesagt "es ist x Grad heißer" (oder kälter), damit sind sicher nicht Kelvin gemeint, denn "Grad Kelvin" gibt es nicht. Und Fahrenheit ist auch nicht gemeint. Nähme man es so genau wie du, müsste man korrekterweise sagen "es ist x Kelvin heißer" - aber selbst das habe ich bisher nicht einmal von den penetrantesten Erbsenzählern gehört.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Arby schrieb:

    Ergebnis wäre also 1°C.
    LOL.
    C = K - 273.15

    0 °C = 273,15 K (richtig)
    1 °C = 274,15 K (richtig)

    0 °C + 1 °C ==> 1 °C (Schwachsinn)
    // Umrechnung in Kelvin
    273,15 K + 274,15 K = 547,3 K (richtig)

    // Rückrechnung in °C
    547,3 K = 274,15 °C (richtig)

    Womit Du bewiesen hättest, dass 1 °C = 274,15 °C wäre. LOL.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    sonne75 schrieb:

    sondern auch das falsch sein:
    Das ist aber richtig, da die Einheit der Temperaturdifferenz ebenfalls das Kelvin (K) ist.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Arby schrieb:

    Warum mich das ganze beschäftigt und ich es als störend empfinde, dass diese Form der Konvertierung nicht auch durch entsprechende Cast-Operatoren oder die System.Convert-Klasse unterstützt wird, ist der Umstand, dass ich in Umgebungen, in denen solche Konvertierungen typ-unabhängig funktionieren sollen, so zum Beispiel ein O/R-Mapper für Datenbanken, nun mehr oder weniger gezwungen bin, einen zu speichernden Typ separat darauf zu testen, ob er einem TimeSpan entspricht und das Ziel aber nur DateTime akzeptiert (oder umgekehrt), damit ich hier an System.Convert vorbei den Mechanismus über die Ticks-Konstruktoren implementieren kann.
    Das scheint mir eher eine Beschränkung der Umgebung zu sein, konkret: der Datenbank-Systeme.
    Ist mir auch schon aufgefallen, dass bisher nur SqlServer einen Datentyp für TimeSpan bereithält.

    Logisch gesehen ist jedenfalls die strikte Unterscheidung von Zeitpunkt und Zeitspanne korrekt. Eine generelle Konvertierung würde einen fixen Nullpunkt vorraussetzen - ok - Date.MinValue ist der 1.1.0001, aber das ist eigentlich nur im christlichen Kulturkreis plausibel.

    Mit den Punkten und Vektoren in DirectX ists bisserl anners, weil da ist immer iein Koordinatensystem vorrausgesetzt, und das wird schon einen Nullpunkt haben.
    Also ein Raum-Punkt ist immer relativ, während ein Zeitpunkt eine global gültige Größe sein müsste.

    Also beim Mappen von Timespan auf DB-Datentypen wird man immer (ausser bei SqlServer) quasi "Ausweichmanöver" fahren müssen, aber das gilt ja nicht nur für Timespan.

    Etwa der Char-Datentyp wird von gar keiner DB unterstützt.
    Denkbares "Ausweichmanöver" wäre hier, auf einen nvarChar mit fester Länge 1 auszuweichen.
    Ähnliches Theater mit SByte, UInt etc.

    Allerdings, beim Rechnen mit Zeitwerten ist das Problem am dringendsten, und da ist Sql gradezu peinlich, weil das kanns nicht. Nichtmal die Expressions vom Dataset können mit Zeitwerten rechnen - vlt. für einen OR-Mapper wäre es besser, Zeitpunkte und Zeitspannen von vornherein in Int64 zu übersetzen - das gscheit gerechnet werden kann.
    Wenn man diese Zahl als Anzahl der Millisekunden ab dem 1.1.0001 auffasst, so ergibt sich immer noch ein Zeit-Bereich von +-292471208 Jahren - nach menschlichem Ermessen sicher ausreichend ;)
    Physiker... :thumbdown:

    /E: @ErfinderDesRades: Ich sehe, du verstehst mich ;)
    An der Millisekunden-als-Int64-Geschichte stört mich, dass man dann in der Datenbank selbst nur noch nicht-aussagekräftige Zahlenwerte stehen hat, wohingegen Datums-Angaben im Bereich "MinValue + 24 Stunden" von den meisten DB-GUIs automatisch nur noch als Zeit angezeigt werden und somit der Effekt erreicht wäre, der angedacht ist, nämlich dass in der Spalte tatsächlich sowas wie "1:20:00" steht.

    Wie gesagt: Ich brauche eigentlich keine Lösung für das "Problem", ich hab es bei meinem Mapper so gelöst, dass er den eingehenden Typ mit dem ausgehenden Typ (also was die DB erwartet/liefert einerseits und was der System.Type meiner typisierten Klasse ist anderseits vergleicht und im Sonderfall der Paarung TimeSpan/DateTime die Umwandlung auf Basis der Ticks vornimmt.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Arby“ ()

    Leute, bitte versucht beim Thema zu bleiben. Es ist für die Fragestellung nicht wirklich relevant, wie sich verschiedene Temperaturskalen o.ä. miteinander verrechnen lassen. Das kann man entweder in einem anderen Thread im DETAIL klären, oder sonstwo. Bitte versucht etwas präziser und nicht mehr so abschweifend zu agieren - auch wenn die Themen vielleicht interessant sind ;) Beispiele sind immer super und schön, aber muss man, wenn es Streitigkeiten über ihre 'Richtigkeit' gibt, die nicht so ausbreiten, dass der eigentlich Thread-Sinn fast verloren geht :)

    ErfinderDesRades schrieb:

    Eine generelle Konvertierung würde einen fixen Nullpunkt vorraussetzen - ok - Date.MinValue ist der 1.1.0001, aber das ist eigentlich nur im christlichen Kulturkreis plausibel.
    Und selbst in diesem Kreis braut jedes System sein eigenes Süppchen.
    .Net DateTime.MinValue; 1.1.0001
    MySQL: 1.1.1000
    Excel: 1.1.1900
    Unix: 1.1.1970
    SQL-Server DateTime: 1.1.1900 (Select CAST(0 as datetime))
    SQL-Server DateTime2: 1.1.0001
    Und irgendwann gab's mal den 1.1.1753 als kleinstmögliches Datum in SQL-Server, damit die Falschumrechnungen zwischen julianischem und gregorianischem Datum nicht auffallen.
    (entspricht -53690, was die kleinste erlaubte Zahl für Select CAST(-53690 as datetime) ist)

    Von daher lässt sich einfach kein allgemeingültiger Nullpunkt festlegen
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „petaod“ ()