DateOnly & TimeOnly

DateOnly and TimeOnly

Introducción

Las estructuras de datos DateOnly y TimeOnly representan un dato de tipo fecha sin la hora o una hora del día sin especificar el día. Estas estructuras de datos están disponibles a partir de la versión 6 de .NET Core.

DateOnly

Representa una fecha sin hora, por lo que corresponde a un día completo. Se le puede establecer los valores del 01/01/0001 al 31/12/9999. En su constructor se le puede indicar el calendario a usar (aunque siempre se convertirá al gregoriano):

    var hebrewCalendar = new System.Globalization.HebrewCalendar();
    var theDate = new DateOnly(5776, 2, 8, hebrewCalendar); // 8 Cheshvan 5776

    Console.WriteLine(theDate);

    /* Este ejemplo produce el siguiente valor:
    * 10/21/2015
    */

Ventajas

  • El valor de la fecha no puede ser modificada por la zona horaria, lo que si ocurría con el tipo de datos DateTime.

  • Al serializar un valor de tipo DateOnly serializa menos datos que el tipo DateTime.

  • Este valor coincide mejor con el tipo de base de datos Date de SqlServer.

Ejemplos

  • Convertir DateTime a DateOnly

    var today = DateOnly.FromDateTime(DateTime.Now);
    Console.WriteLine($"Today is {today}");
    
    /* Este ejemplo produce el siguiente valor:
    * Today is 03/08/2024
    */
  • Agregar o restar días, meses o años

    var theDate = new DateOnly(2015, 10, 21);
    
    var nextDay = theDate.AddDays(1);
    var previousDay = theDate.AddDays(-1);
    var decadeLater = theDate.AddYears(10);
    var lastMonth = theDate.AddMonths(-1);
    
    Console.WriteLine($"Date: {theDate}");
    Console.WriteLine($" Next day: {nextDay}");
    Console.WriteLine($" Previous day: {previousDay}");
    Console.WriteLine($" Decade later: {decadeLater}");
    Console.WriteLine($" Last month: {lastMonth}");
    
    /* Este ejemplo produce el siguiente valor:
    * Date: 10/21/2015
    *  Next day: 10/22/2015
    *  Previous day: 10/20/2015
    *  Decade later: 10/21/2025
    *  Last month: 9/21/2015
    */
  • Analizar DateOnly y darle formato

    var theDate = DateOnly.ParseExact("21 Oct 2015", "dd MMM yyyy", CultureInfo.InvariantCulture);  // Formato personalizado
    var theDate2 = DateOnly.Parse("October 21, 2015", CultureInfo.InvariantCulture);
    
    Console.WriteLine(theDate.ToString("m", CultureInfo.InvariantCulture));     // Patrón mes día
    Console.WriteLine(theDate2.ToString("o", CultureInfo.InvariantCulture));    // Formato ISO 8601
    Console.WriteLine(theDate2.ToLongDateString());
    
    /* Este ejemplo produce el siguiente valor:
    * October 21
    * 2015-10-21
    * Wednesday, October 21, 2015
    */
  • Comparar DateOnly

    var theDate = DateOnly.ParseExact("21 Oct 2015", "dd MMM yyyy", CultureInfo.InvariantCulture);  // Formato personalizado
    var theDate2 = DateOnly.Parse("October 21, 2015", CultureInfo.InvariantCulture);
    var dateLater = theDate.AddMonths(6);
    var dateBefore = theDate.AddDays(-10);
    
    Console.WriteLine($"Consider {theDate}...");
    Console.WriteLine($" Is '{nameof(theDate2)}' equal? {theDate == theDate2}");
    Console.WriteLine($" Is {dateLater} after? {dateLater > theDate} ");
    Console.WriteLine($" Is {dateLater} before? {dateLater < theDate} ");
    Console.WriteLine($" Is {dateBefore} after? {dateBefore > theDate} ");
    Console.WriteLine($" Is {dateBefore} before? {dateBefore < theDate} ");
    
    /* Este ejemplo produce el siguiente valor:
    * Consider 10/21/2015
    *  Is 'theDate2' equal? True
    *  Is 4/21/2016 after? True
    *  Is 4/21/2016 before? False
    *  Is 10/11/2015 after? False
    *  Is 10/11/2015 before? True
    */

TimeOnly

Representa una hora del día sin fecha, cuyo valor va del 00:00:00.0000000 al 23:59:59.9999999. Antes de crearse esta estructura de datos, se solía usar TimeSpan.

Ventajas

  • En un TimeSpan el rango superior puede superar los 29 000 años o tener valores negativos, cosa que no sucede con TimeOnly.

  • Se puede establecer un valor fuera del día en el TimeSpan pero no en el TimeOnly.

  • Si se usa DateTime para establecer una hora, se suele aplicar como día DateTime.MinValue y manipulando el valor puede hacer que lance una excepción del tipo OutOfRange.

  • Con TimeOnly serializa menos datos.

Ejemplos

  • Convertir DateTime a DateOnly

    var now = TimeOnly.FromDateTime(DateTime.Now);
    Console.WriteLine($"It is {now} right now");
    
    /* Este ejemplo produce el siguiente valor:
    * It is 2:01 PM right now
    */
  • Agregar o restar tiempo

    var theTime = new TimeOnly(7, 23, 11);
    
    var hourLater = theTime.AddHours(1);
    var minutesBefore = theTime.AddMinutes(-12);
    var secondsAfter = theTime.Add(TimeSpan.FromSeconds(10));
    var daysLater = theTime.Add(new TimeSpan(hours: 21, minutes: 200, seconds: 83), out int wrappedDays);
    var daysBehind = theTime.AddHours(-222, out int wrappedDaysFromHours);
    
    Console.WriteLine($"Time: {theTime}");
    Console.WriteLine($" Hours later: {hourLater}");
    Console.WriteLine($" Minutes before: {minutesBefore}");
    Console.WriteLine($" Seconds after: {secondsAfter}");
    Console.WriteLine($" {daysLater} is the time, which is {wrappedDays} days later");
    Console.WriteLine($" {daysBehind} is the time, which is {wrappedDaysFromHours} days prior");
    
    /* Este ejemplo produce el siguiente valor:
    * Time: 7:23 AM
    *  Hours later: 8:23 AM
    *  Minutes before: 7:11 AM
    *  Seconds after: 7:23 AM
    *  7:44 AM is the time, which is 1 days later 
    *  1:23 AM is the time, which is -9 days prior
    */
  • Analizar TimeOnly y darle formato

    var theTime = TimeOnly.ParseExact("5:00 pm", "h:mm tt", CultureInfo.InvariantCulture);  // Formato personalizado
    var theTime2 = TimeOnly.Parse("17:30:25", CultureInfo.InvariantCulture);
    
    Console.WriteLine(theTime.ToString("o", CultureInfo.InvariantCulture));     // Formato largo
    Console.WriteLine(theTime2.ToString("t", CultureInfo.InvariantCulture));    // Formato corto
    Console.WriteLine(theTime2.ToLongTimeString());
    
    /* Este ejemplo produce el siguiente valor:
    * 17:00:00.0000000
    * 17:30
    * 5:30:25 PM
    */

Serializar DateOnly y TimeOnly

Con la versión .NET 7 y posteriores, se admite la serialización de estas estructuras de datos.

    sealed file record Appointment(
            Guid Id,
            string Description,
            DateOnly Date,
            TimeOnly StartTime,
            TimeOnly EndTime);

    Appointment originalAppointment = new(
            Id: Guid.NewGuid(),
            Description: "Take dog to veterinarian.",
            Date: new DateOnly(2002, 1, 13),
            StartTime: new TimeOnly(5,15),
            EndTime: new TimeOnly(5, 45));
    string serialized = JsonSerializer.Serialize(originalAppointment);

    Console.WriteLine($"Resulting JSON: {serialized}");

    Appointment deserializedAppointment = JsonSerializer.Deserialize<Appointment>(serialized)!;

    bool valuesAreTheSame = originalAppointment == deserializedAppointment;
    Console.WriteLine($"""Original record has the same values as the deserialized record: {valuesAreTheSame}""");

Trabajar con TimeOnly y DateTime

TimeOnly puede crearse a partir de un TimeSpan y convertirse en ese valor. También puede utilizarse un DateTim para crear un TimeOnly.

    var theTime = TimeOnly.FromTimeSpan(new TimeSpan(23, 59, 59));
    var theTimeSpan = theTime.ToTimeSpan();

    Console.WriteLine($"Variable '{nameof(theTime)}' is {theTime}");
    Console.WriteLine($"Variable '{nameof(theTimeSpan)}' is {theTimeSpan}");

    /* Este ejemplo produce el siguiente valor:
    * Variable 'theTime' is 11:59 PM
    * Variable 'theTimeSpan' is 23:59:59
    */

Creación de un DateTime a través de un TimeOnly

    var theTime = new TimeOnly(11, 25, 46);   // 11:25 AM and 46 seconds
    var theDate = new DateOnly(2015, 10, 21); // October 21, 2015
    var theDateTime = theDate.ToDateTime(theTime);
    var reverseTime = TimeOnly.FromDateTime(theDateTime);

    Console.WriteLine($"Date only is {theDate}");
    Console.WriteLine($"Time only is {theTime}");
    Console.WriteLine();
    Console.WriteLine($"Combined to a DateTime type, the value is {theDateTime}");
    Console.WriteLine($"Converted back from DateTime, the time is {reverseTime}");

    /* Este ejemplo produce el siguiente valor:
    * Date only is 10/21/2015
    * Time only is 11:25 AM
    * 
    * Combined to a DateTime type, the value is 10/21/2015 11:25:46 AM
    * Converted back from DateTime, the time is 11:25 AM
    */

Operadores aritméticos y comparación de TimeOnly

    var start = new TimeOnly(10, 12, 01); // 10:12:01 AM
    var end = new TimeOnly(14, 00, 53); // 02:00:53 PM

    var outside = start.AddMinutes(-3);
    var inside = start.AddMinutes(120);

    Console.WriteLine($"Time starts at {start} and ends at {end}");
    Console.WriteLine($" Is {outside} between the start and end? {outside.IsBetween(start, end)}");
    Console.WriteLine($" Is {inside} between the start and end? {inside.IsBetween(start, end)}");
    Console.WriteLine($" Is {start} less than {end}? {start < end}");
    Console.WriteLine($" Is {start} greater than {end}? {start > end}");
    Console.WriteLine($" Does {start} equal {end}? {start == end}");
    Console.WriteLine($" The time between {start} and {end} is {end - start}");

    /* Este ejemplo produce el siguiente valor:
    * Time starts at 10:12 AM and ends at 2:00 PM
    *  Is 10:09 AM between the start and end? False
    *  Is 12:12 PM between the start and end? True
    *  Is 10:12 AM less than 2:00 PM? True
    *  Is 10:12 AM greater than 2:00 PM? False
    *  Does 10:12 AM equal 2:00 PM? False
    *  The time between 10:12 AM and 2:00 PM is 03:48:52
    */

Referencias

Microsoft Learn