Neu in .NET 6 [9]: Datentypen DateOnly und TimeOnly

Der Dotnet-Doktor Holger Schwichtenberg  –  21 Kommentare

Der Wunsch der .NET-Entwicklergemeinde, neben dem gemischten Typ DateTime auch noch Datentypen zu bekommen, in denen man ein Datum beispielsweise für Geburtstage oder Einstellungstermine beziehungsweise eine Uhrzeit getrennt abspeichern kann, ist schon uralt – gerade in Verbindung mit Datenbankmanagementsystemen, die dafür häufig getrennte Datentypen besitzen.

Nun in .NET 6.0 gibt es diese zusätzlichen Basisdatentypen:

  • System.DateOnly: speichert nur ein Datum
  • System.TimeOnly: speichert nur eine Zeit

Die Namensgebung gefällt nicht allen .NET-Entwickler; einige hätten sich einfach Date und Time ohne den Zusatz "Only" gewünscht (siehe auch die Diskussion auf GitHub). Aber es hätte Konflikte gegeben mit dem eingebauten Datentyp Date in Visual Basic .NET und es gab auch weitere Argumente dagegen.

DateOnly und TimeOnly sind wie DateTime als Strukturen (struct) deklariert mit Wertesemantik.

Größe und Wertebereiche kann man sich von den beiden Klassen selbst liefern lassen

unsafe
{
Console.WriteLine($"DateTime: {sizeof(DateTime)} Bytes");
Console.WriteLine(DateTime.MinValue + "->" + DateTime.MaxValue);
Console.WriteLine($"DateOnly: {sizeof(DateOnly)} Bytes");
Console.WriteLine(DateOnly.MinValue + "->" + DateOnly.MaxValue);
Console.WriteLine($"TimeOnly: {sizeof(TimeOnly)} Bytes");
Console.WriteLine(TimeOnly.MinValue + "->" + TimeOnly.MaxValue);
}

Dieser Code gibt aus:

DateTime: 8 Bytes
01.01.0001 00:00:00->31.12.9999 23:59:59
DateOnly: 4 Bytes
01.01.0001->31.12.9999
TimeOnly: 8 Bytes
00:00->23:59

Beide neuen Datentypen haben Konstruktorparameter zum Setzen des initialen Wertes:

DateOnly d1 = new DateOnly(2021, 11, 9); // 9.11.2021
TimeOnly t1 = new TimeOnly(11, 59, 20, 10); // 11:59:20.10

Genau wie bei DateTime gibt es eine statische TryParse()-Methode zur Umwandlung von Zeichenketten:

DateOnly d2;
if (DateOnly.TryParse("09.11.2021", out d2))
{ Console.WriteLine(".NET 6 erscheint am " +d2); }
TimeOnly t2;
if (TimeOnly.TryParse("11:59:20.10", out t2))
{ Console.WriteLine(t2);

Beide Datentypen erlauben die Konvertierung von und nach DateTime:

// DateOnly und TimeOnly aus DateTime
DateOnly d2 = DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine($"Heute ist der {d1}.");

TimeOnly t2 = TimeOnly.FromDateTime(DateTime.Now);
Console.WriteLine($"Es ist jetzt {t2}.");

// DateOnly und TimeOnly zu DateTime zusammensetzen
DateTime d3 = d2.ToDateTime(t2);

Die Werte lassen sich mit Methoden verändern:

// Datum und Zeit verändern
var d6 = new DateOnly(2021, 11, 9);
Console.WriteLine("Eine Woche später: " + d6.AddDays(7));
TimeOnly t6 = new TimeOnly(11, 59, 20, 10); // 11:59:20.10
Console.WriteLine("15 Minuten zuvor: " + t6.AddMinutes(-15));

Man kann mit TimeOnly eine Zeitspanne errechnen:

// Mit Zeit rechnen
var t5 = new TimeOnly(17, 0, 0);
TimeSpan restarbeitszeit = t5 - TimeOnly.FromDateTime(DateTime.Now);
Console.WriteLine(restarbeitszeit + " bis zum Feierabend");

Das geht aber leider nicht mit DateOnly:

// Mit Datum rechnen
var d5 = new DateOnly(2021, 11, 9);
TimeSpan tage = d5 - DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine(tage + " bis zum Erscheinen von .NET 6");

Hier ist der Minus-Operator nicht überladen: Operator '-' cannot be applied to operands of type 'DateOnly' and 'DateOnly'

Bei TimeOnly gibt es eine Methode IsBetween(), die prüft, ob eine bestimmte Zeit in einem Zeitfenster liegt:

var t9 = new TimeOnly(9, 0);
var t17 = new TimeOnly(17, 0);
var now = TimeOnly.FromDateTime(DateTime.Now);
if (now.IsBetween(t9,t17)) {
Console.WriteLine("Während der regulären Arbeitszeit!");
}
else {
Console.WriteLine("Außerhalb der regulären Arbeitszeit!");
}

Microsoft hat aber nun noch einige Arbeit, diese neuen Datentypen in die verschiedenen .NET-Bibliotheken zu implementieren.

Im OR-Mapper Entity Framework Core werden die Datentypen bisher (Stand .NET 6 RC1) nur im Provider für SQLite, aber nicht im Provider für Microsoft SQL Server unterstützt, denn der Basis ADO.NET-Treiber für SQL Server (System.Data.SqlClient) kann das noch nicht.

Der JSON-Serializer System.Text.Json (abgekürzt: STJ) will auch die beiden Typen nicht serialisieren: Serialization and deserialization of 'System.DateOnly' instances are not supported and should be avoided since they can lead to security issues. beziehungsweise Serialization and deserialization of 'System.TimeOnly' instances are not supported and should be avoided since they can lead to security issues. Dazu findet man die Diskussion auf GitHub im Issue #53539.