C#idiomatic C#
how-to-round-trip-date-and-time-valuesstandard/base-types

Core library

How to: Round-trip Date and time values

Learn how to round-trip date and time values

  • page standard/base-types/how-to-round-trip-date-and-time-values
  • updated 11/15/2022

In many applications, a date and time value is intended to unambiguously identify a single point in time. This article shows how to save and restore a System.DateTime value, a System.DateTimeOffset value, and a date and time value with time zone information so that the restored value identifies the same time as the saved value.

Round-trip a DateTime value

  1. Convert the System.DateTime value to its string representation by calling the System.DateTime.ToString%28System.String%29 method with the "o" format specifier.

  2. Save the string representation of the System.DateTime value to a file, or pass it across a process, application domain, or machine boundary.

  3. Retrieve the string that represents the System.DateTime value.

  4. Call the System.DateTime.Parse%28System.String%2CSystem.IFormatProvider%2CSystem.Globalization.DateTimeStyles%29 method, and pass System.Globalization.DateTimeStyles.RoundtripKind as the value of the styles parameter.

The following example illustrates how to round-trip a System.DateTime value.

sample · csharpsamples/snippets/csharp/VS_Snippets_CLR/Formatting.HowTo.RoundTrip/cs/RoundTrip.cs1

csharp
using System;
using System.Globalization;
using System.IO;

public class Class1
{
    public static void Main()
    {
        RoundTripDateTime();
        Console.WriteLine();
        RoundTripDateTimeOffset();
        Console.WriteLine();
    }

    private static void RoundTripDateTime()
    {
        // <Snippet1>
        const string fileName = @".\DateFile.txt";

        StreamWriter outFile = new StreamWriter(fileName);

        // Save DateTime value.
        DateTime dateToSave = DateTime.SpecifyKind(new DateTime(2008, 6, 12, 18, 45, 15),
                                                   DateTimeKind.Local);
        string? dateString = dateToSave.ToString("o");
        Console.WriteLine($"Converted {dateToSave.ToString()} ({dateToSave.Kind.ToString()}) to {dateString}.");
        outFile.WriteLine(dateString);
        Console.WriteLine($"Wrote {dateString} to {fileName}.");
        outFile.Close();

        // Restore DateTime value.
        DateTime restoredDate;

        using StreamReader inFile = new StreamReader(fileName);
        dateString = inFile.ReadLine();

        if (dateString is not null)
        {
            restoredDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
            Console.WriteLine($"Read {restoredDate.ToString()} ({restoredDate.Kind.ToString()}) from {fileName}.");
        }

        // The example displays the following output:
        //    Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
        //    Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
        //    Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.
        // </Snippet1>
    }

    private static void RoundTripDateTimeOffset()
    {
        // <Snippet2>
        const string fileName = @".\DateOff.txt";

        StreamWriter outFile = new StreamWriter(fileName);

        // Save DateTime value.
        DateTimeOffset dateToSave = new DateTimeOffset(2008, 6, 12, 18, 45, 15,
                                                       new TimeSpan(7, 0, 0));
        string? dateString = dateToSave.ToString("o");
        Console.WriteLine($"Converted {dateToSave.ToString()} to {dateString}.");
        outFile.WriteLine(dateString);
        Console.WriteLine($"Wrote {dateString} to {fileName}.");
        outFile.Close();

        // Restore DateTime value.
        DateTimeOffset restoredDateOff;

        using StreamReader inFile = new StreamReader(fileName);
        dateString = inFile.ReadLine();

        if (dateString is not null)
        {
            restoredDateOff = DateTimeOffset.Parse(dateString, null,
                                                   DateTimeStyles.RoundtripKind);
            Console.WriteLine($"Read {restoredDateOff.ToString()} from {fileName}.");
        }

        // The example displays the following output:
        //    Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
        //    Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
        //    Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.
        // </Snippet2>
    }
}

// <Snippet3>
[Serializable]
public class DateInTimeZone
{
    private TimeZoneInfo? tz;
    private DateTimeOffset thisDate;

    public DateInTimeZone() { }

    public DateInTimeZone(DateTimeOffset date, TimeZoneInfo timeZone)
    {
        if (timeZone is null)
            throw new ArgumentNullException("timeZone", "The time zone cannot be null.");

        this.thisDate = date;
        this.tz = timeZone;
    }

    public DateTimeOffset DateAndTime
    {
        get
        {
            return thisDate;
        }
        set
        {
            if ((tz is not null) && (value.Offset != tz.GetUtcOffset(value)))
                thisDate = TimeZoneInfo.ConvertTime(value, tz);
            else
                thisDate = value;
        }
    }

    public TimeZoneInfo? TimeZone => tz;
}
// </Snippet3>

sample · vbsamples/snippets/visualbasic/VS_Snippets_CLR/Formatting.HowTo.RoundTrip/vb/RoundTrip.vb1

vb
' Visual Basic .NET Document
Option Strict On

Imports System.Globalization
Imports System.IO

Module modMain

    Public Sub Main()
        RoundTripDateTime()
        Console.WriteLine()
        RoundTripDateTimeOffset()
        Console.WriteLine()
    End Sub

    Private Sub RoundTripDateTime()
        ' <Snippet1>
        Const fileName As String = ".\DateFile.txt"

        Dim outFile As New StreamWriter(fileName)

        ' Save DateTime value.
        Dim dateToSave As Date = DateTime.SpecifyKind(#06/12/2008 6:45:15 PM#, _
                                                      DateTimeKind.Local)
        Dim dateString As String = dateToSave.ToString("o")
        Console.WriteLine("Converted {0} ({1}) to {2}.", dateToSave.ToString(), _
                          dateToSave.Kind.ToString(), dateString)
        outFile.WriteLine(dateString)
        Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
        outFile.Close()

        ' Restore DateTime value.
        Dim restoredDate As Date

        Dim inFile As New StreamReader(fileName)
        dateString = inFile.ReadLine()
        inFile.Close()
        restoredDate = DateTime.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
        Console.WriteLine("Read {0} ({2}) from {1}.", restoredDate.ToString(), _
                          fileName, restoredDAte.Kind.ToString())
        ' The example displays the following output:
        '    Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
        '    Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
        '    Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.
        ' </Snippet1>
    End Sub

    Private Sub RoundTripDateTimeOffset()
        ' <Snippet2>
        Const fileName As String = ".\DateOff.txt"

        Dim outFile As New StreamWriter(fileName)

        ' Save DateTime value.
        Dim dateToSave As New DateTimeOffset(2008, 6, 12, 18, 45, 15, _
                                             New TimeSpan(7, 0, 0))
        Dim dateString As String = dateToSave.ToString("o")
        Console.WriteLine("Converted {0} to {1}.", dateToSave.ToString(), dateString)
        outFile.WriteLine(dateString)
        Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
        outFile.Close()

        ' Restore DateTime value.
        Dim restoredDateOff As DateTimeOffset

        Dim inFile As New StreamReader(fileName)
        dateString = inFile.ReadLine()
        inFile.Close()
        restoredDateOff = DateTimeOffset.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
        Console.WriteLine("Read {0} from {1}.", restoredDateOff.ToString(), fileName)
        ' The example displays the following output:
        '    Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
        '    Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
        '    Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.
        ' </Snippet2>
    End Sub
End Module

' <Snippet3>
<Serializable> Public Class DateInTimeZone
    Private tz As TimeZoneInfo
    Private thisDate As DateTimeOffset

    Public Sub New()
    End Sub

    Public Sub New(date1 As DateTimeOffset, timeZone As TimeZoneInfo)
        If timeZone Is Nothing Then
            Throw New ArgumentNullException("timeZone", "The time zone cannot be null.")
        End If
        Me.thisDate = date1
        Me.tz = timeZone
    End Sub

    Public Property DateAndTime As DateTimeOffset
        Get
            Return Me.thisDate
        End Get
        Set
            If Value.Offset <> Me.tz.GetUtcOffset(Value) Then
                Me.thisDate = TimeZoneInfo.ConvertTime(Value, tz)
            Else
                Me.thisDate = Value
            End If
        End Set
    End Property

    Public ReadOnly Property TimeZone As TimeZoneInfo
        Get
            Return tz
        End Get
    End Property
End Class
' </Snippet3>

When round-tripping a System.DateTime value, this technique successfully preserves the time for all local and universal times. For example, if a local System.DateTime value is saved on a system in the U.S. Pacific Standard Time zone and is restored on a system in the U.S. Central Standard Time zone, the restored date and time will be two hours later than the original time, which reflects the time difference between the two time zones. However, this technique is not necessarily accurate for unspecified times. All System.DateTime values whose System.DateTime.Kind property is System.DateTimeKind.Unspecified are treated as if they are local times. If it's not a local time, the System.DateTime doesn't successfully identify the correct point in time. The workaround for this limitation is to tightly couple a date and time value with its time zone for the save and restore operation.

Round-trip a DateTimeOffset value

  1. Convert the System.DateTimeOffset value to its string representation by calling the System.DateTimeOffset.ToString%28System.String%29 method with the "o" format specifier.

  2. Save the string representation of the System.DateTimeOffset value to a file, or pass it across a process, application domain, or machine boundary.

  3. Retrieve the string that represents the System.DateTimeOffset value.

  4. Call the System.DateTimeOffset.Parse%28System.String%2CSystem.IFormatProvider%2CSystem.Globalization.DateTimeStyles%29 method, and pass System.Globalization.DateTimeStyles.RoundtripKind as the value of the styles parameter.

The following example illustrates how to round-trip a System.DateTimeOffset value.

sample · csharpsamples/snippets/csharp/VS_Snippets_CLR/Formatting.HowTo.RoundTrip/cs/RoundTrip.cs2

csharp
using System;
using System.Globalization;
using System.IO;

public class Class1
{
    public static void Main()
    {
        RoundTripDateTime();
        Console.WriteLine();
        RoundTripDateTimeOffset();
        Console.WriteLine();
    }

    private static void RoundTripDateTime()
    {
        // <Snippet1>
        const string fileName = @".\DateFile.txt";

        StreamWriter outFile = new StreamWriter(fileName);

        // Save DateTime value.
        DateTime dateToSave = DateTime.SpecifyKind(new DateTime(2008, 6, 12, 18, 45, 15),
                                                   DateTimeKind.Local);
        string? dateString = dateToSave.ToString("o");
        Console.WriteLine($"Converted {dateToSave.ToString()} ({dateToSave.Kind.ToString()}) to {dateString}.");
        outFile.WriteLine(dateString);
        Console.WriteLine($"Wrote {dateString} to {fileName}.");
        outFile.Close();

        // Restore DateTime value.
        DateTime restoredDate;

        using StreamReader inFile = new StreamReader(fileName);
        dateString = inFile.ReadLine();

        if (dateString is not null)
        {
            restoredDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
            Console.WriteLine($"Read {restoredDate.ToString()} ({restoredDate.Kind.ToString()}) from {fileName}.");
        }

        // The example displays the following output:
        //    Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
        //    Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
        //    Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.
        // </Snippet1>
    }

    private static void RoundTripDateTimeOffset()
    {
        // <Snippet2>
        const string fileName = @".\DateOff.txt";

        StreamWriter outFile = new StreamWriter(fileName);

        // Save DateTime value.
        DateTimeOffset dateToSave = new DateTimeOffset(2008, 6, 12, 18, 45, 15,
                                                       new TimeSpan(7, 0, 0));
        string? dateString = dateToSave.ToString("o");
        Console.WriteLine($"Converted {dateToSave.ToString()} to {dateString}.");
        outFile.WriteLine(dateString);
        Console.WriteLine($"Wrote {dateString} to {fileName}.");
        outFile.Close();

        // Restore DateTime value.
        DateTimeOffset restoredDateOff;

        using StreamReader inFile = new StreamReader(fileName);
        dateString = inFile.ReadLine();

        if (dateString is not null)
        {
            restoredDateOff = DateTimeOffset.Parse(dateString, null,
                                                   DateTimeStyles.RoundtripKind);
            Console.WriteLine($"Read {restoredDateOff.ToString()} from {fileName}.");
        }

        // The example displays the following output:
        //    Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
        //    Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
        //    Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.
        // </Snippet2>
    }
}

// <Snippet3>
[Serializable]
public class DateInTimeZone
{
    private TimeZoneInfo? tz;
    private DateTimeOffset thisDate;

    public DateInTimeZone() { }

    public DateInTimeZone(DateTimeOffset date, TimeZoneInfo timeZone)
    {
        if (timeZone is null)
            throw new ArgumentNullException("timeZone", "The time zone cannot be null.");

        this.thisDate = date;
        this.tz = timeZone;
    }

    public DateTimeOffset DateAndTime
    {
        get
        {
            return thisDate;
        }
        set
        {
            if ((tz is not null) && (value.Offset != tz.GetUtcOffset(value)))
                thisDate = TimeZoneInfo.ConvertTime(value, tz);
            else
                thisDate = value;
        }
    }

    public TimeZoneInfo? TimeZone => tz;
}
// </Snippet3>

sample · vbsamples/snippets/visualbasic/VS_Snippets_CLR/Formatting.HowTo.RoundTrip/vb/RoundTrip.vb2

vb
' Visual Basic .NET Document
Option Strict On

Imports System.Globalization
Imports System.IO

Module modMain

    Public Sub Main()
        RoundTripDateTime()
        Console.WriteLine()
        RoundTripDateTimeOffset()
        Console.WriteLine()
    End Sub

    Private Sub RoundTripDateTime()
        ' <Snippet1>
        Const fileName As String = ".\DateFile.txt"

        Dim outFile As New StreamWriter(fileName)

        ' Save DateTime value.
        Dim dateToSave As Date = DateTime.SpecifyKind(#06/12/2008 6:45:15 PM#, _
                                                      DateTimeKind.Local)
        Dim dateString As String = dateToSave.ToString("o")
        Console.WriteLine("Converted {0} ({1}) to {2}.", dateToSave.ToString(), _
                          dateToSave.Kind.ToString(), dateString)
        outFile.WriteLine(dateString)
        Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
        outFile.Close()

        ' Restore DateTime value.
        Dim restoredDate As Date

        Dim inFile As New StreamReader(fileName)
        dateString = inFile.ReadLine()
        inFile.Close()
        restoredDate = DateTime.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
        Console.WriteLine("Read {0} ({2}) from {1}.", restoredDate.ToString(), _
                          fileName, restoredDAte.Kind.ToString())
        ' The example displays the following output:
        '    Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
        '    Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
        '    Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.
        ' </Snippet1>
    End Sub

    Private Sub RoundTripDateTimeOffset()
        ' <Snippet2>
        Const fileName As String = ".\DateOff.txt"

        Dim outFile As New StreamWriter(fileName)

        ' Save DateTime value.
        Dim dateToSave As New DateTimeOffset(2008, 6, 12, 18, 45, 15, _
                                             New TimeSpan(7, 0, 0))
        Dim dateString As String = dateToSave.ToString("o")
        Console.WriteLine("Converted {0} to {1}.", dateToSave.ToString(), dateString)
        outFile.WriteLine(dateString)
        Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
        outFile.Close()

        ' Restore DateTime value.
        Dim restoredDateOff As DateTimeOffset

        Dim inFile As New StreamReader(fileName)
        dateString = inFile.ReadLine()
        inFile.Close()
        restoredDateOff = DateTimeOffset.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
        Console.WriteLine("Read {0} from {1}.", restoredDateOff.ToString(), fileName)
        ' The example displays the following output:
        '    Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
        '    Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
        '    Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.
        ' </Snippet2>
    End Sub
End Module

' <Snippet3>
<Serializable> Public Class DateInTimeZone
    Private tz As TimeZoneInfo
    Private thisDate As DateTimeOffset

    Public Sub New()
    End Sub

    Public Sub New(date1 As DateTimeOffset, timeZone As TimeZoneInfo)
        If timeZone Is Nothing Then
            Throw New ArgumentNullException("timeZone", "The time zone cannot be null.")
        End If
        Me.thisDate = date1
        Me.tz = timeZone
    End Sub

    Public Property DateAndTime As DateTimeOffset
        Get
            Return Me.thisDate
        End Get
        Set
            If Value.Offset <> Me.tz.GetUtcOffset(Value) Then
                Me.thisDate = TimeZoneInfo.ConvertTime(Value, tz)
            Else
                Me.thisDate = Value
            End If
        End Set
    End Property

    Public ReadOnly Property TimeZone As TimeZoneInfo
        Get
            Return tz
        End Get
    End Property
End Class
' </Snippet3>

This technique always unambiguously identifies a System.DateTimeOffset value as a single point in time. The value can then be converted to Coordinated Universal Time (UTC) by calling the System.DateTimeOffset.ToUniversalTime method, or it can be converted to the time in a particular time zone by calling the System.DateTimeOffset.ToOffset or System.TimeZoneInfo.ConvertTime%28System.DateTimeOffset%2CSystem.TimeZoneInfo%29 method. The major limitation of this technique is that date and time arithmetic, when performed on a System.DateTimeOffset value that represents the time in a particular time zone, may not produce accurate results for that time zone. This is because when a System.DateTimeOffset value is instantiated, it is disassociated from its time zone. Therefore, that time zone's adjustment rules can no longer be applied when you perform date and time calculations. You can work around this problem by defining a custom type that includes both a date and time value and its accompanying time zone.

Compile the code

These examples require that the following namespaces be imported with C# using directives or Visual Basic Imports statements:

See also