Skip to content
Author: ytianle

Time⚓︎

Let's covers the core time-related types in .NET: TimeZoneInfo, DateTime, and DateTimeOffset.

TimeZoneInfo⚓︎

TimeZoneInfo represents time zones and their rules (standard time and daylight saving time). It is used to convert between time zones and apply historical/future rules.

Key points

  • Backed by the system time zone database (IDs differ between Windows and Linux/macOS).
  • Always specify source and destination time zones to avoid accidental local-time assumptions.
TimeZoneInfo in .NET
using System;

class Demo
{
    static void Main()
    {
        string tzId = OperatingSystem.IsWindows() ? "China Standard Time" : "Asia/Shanghai";
        var shanghai = TimeZoneInfo.FindSystemTimeZoneById(tzId);
        var utc = TimeZoneInfo.Utc;

        Console.WriteLine($"Zone: {shanghai.Id}");
        Console.WriteLine($"UTC: {utc.Id}");

        DateTime localTime = new DateTime(2024, 1, 20, 10, 0, 0, DateTimeKind.Unspecified);
        DateTime utcFromZone = TimeZoneInfo.ConvertTimeToUtc(localTime, shanghai);
        DateTime zoneFromUtc = TimeZoneInfo.ConvertTimeFromUtc(new DateTime(2024, 1, 20, 2, 0, 0, DateTimeKind.Utc), shanghai);

        Console.WriteLine($"Zone Time: {localTime:o}");
        Console.WriteLine($"UTC From Zone: {utcFromZone:o}");
        Console.WriteLine($"Zone From UTC: {zoneFromUtc:o}");
    }
}

// >>> Zone: China Standard Time
// >>> UTC: UTC
// >>> Zone Time: 2024-01-20T10:00:00.0000000
// >>> UTC From Zone: 2024-01-20T02:00:00.0000000Z
// >>> Zone From UTC: 2024-01-20T10:00:00.0000000+08:00
TimeZoneInfo conversion example
using System;

class Demo
{
    static void Main()
    {
        // Windows: "China Standard Time"
        // Linux/macOS: "Asia/Shanghai"
        string tzId = OperatingSystem.IsWindows() ? "China Standard Time" : "Asia/Shanghai";
        var shanghai = TimeZoneInfo.FindSystemTimeZoneById(tzId);

        DateTime utc = new DateTime(2024, 1, 20, 2, 0, 0, DateTimeKind.Utc);
        DateTime shanghaiTime = TimeZoneInfo.ConvertTimeFromUtc(utc, shanghai);

        Console.WriteLine($"UTC: {utc:o}");
        Console.WriteLine($"Shanghai: {shanghaiTime:o}");
    }
}

// >>> UTC: 2024-01-20T02:00:00.0000000Z
// >>> Shanghai: 2024-01-20T10:00:00.0000000+08:00

DateTime⚓︎

DateTime represents a date and time without a specific time zone. The "kind" is indicated by DateTime.Kind: Local, Utc, or Unspecified.

Key points

  • Storing local time with DateTime can be risky across systems or time zones.
  • Prefer DateTime.UtcNow for time points to avoid DST ambiguity.
  • DateTime.Kind is just a flag; it does not convert values.
DateTime in .NET
using System;

class Demo
{
    static void Main()
    {
        string tzId = OperatingSystem.IsWindows() ? "China Standard Time" : "Asia/Shanghai";
        var shanghai = TimeZoneInfo.FindSystemTimeZoneById(tzId);

        DateTime utc = new DateTime(2024, 1, 20, 2, 0, 0, DateTimeKind.Utc);
        DateTime unspecified = new DateTime(2024, 1, 20, 10, 0, 0, DateTimeKind.Unspecified);
        DateTime localInZone = TimeZoneInfo.ConvertTimeFromUtc(utc, shanghai);

        Console.WriteLine($"UTC: {utc:o} Kind={utc.Kind}");
        Console.WriteLine($"Unspecified: {unspecified:o} Kind={unspecified.Kind}");
        Console.WriteLine($"Zone Time: {localInZone:o} Kind={localInZone.Kind}");

        DateTime zoneToUtc = TimeZoneInfo.ConvertTimeToUtc(unspecified, shanghai);
        DateTime utcToZone = TimeZoneInfo.ConvertTimeFromUtc(utc, shanghai);

        Console.WriteLine($"Zone->UTC: {zoneToUtc:o}");
        Console.WriteLine($"UTC->Zone: {utcToZone:o}");
    }
}

// >>> UTC: 2024-01-20T02:00:00.0000000Z Kind=Utc
// >>> Unspecified: 2024-01-20T10:00:00.0000000 Kind=Unspecified
// >>> Zone Time: 2024-01-20T10:00:00.0000000+08:00 Kind=Local
// >>> Zone->UTC: 2024-01-20T02:00:00.0000000Z
// >>> UTC->Zone: 2024-01-20T10:00:00.0000000+08:00
Parsing and serializing DateTime
using System;
using System.Globalization;

class Demo
{
    static void Main()
    {
        // ISO 8601 string
        string iso = "2024-01-20T15:30:00Z";
        DateTime parsed = DateTime.Parse(iso, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
        Console.WriteLine($"Parsed: {parsed:o} Kind={parsed.Kind}");

        // Serialize as UTC
        DateTime utc = new DateTime(2024, 1, 20, 2, 0, 0, DateTimeKind.Utc);
        string serialized = utc.ToString("o");
        Console.WriteLine($"Serialized: {serialized}");
    }
}

// >>> Parsed: 2024-01-20T15:30:00.0000000Z Kind=Utc
// >>> Serialized: 2024-01-20T02:00:00.0000000Z

DateTimeOffset⚓︎

DateTimeOffset represents an absolute point in time as local time plus an offset. It explicitly carries the UTC offset and is better for cross-time-zone scenarios.

Key points

  • The offset is a numeric value and does not include time zone rules (e.g., DST changes).
  • Prefer DateTimeOffset for storing time points, especially across regions.
UTC offset and Kind

When assigning values, 2024-01-20T10:00:00.0000000+00:00 will be treated below: - As DateTimeOffset, it means 2024-01-20 10:00 at UTC+0 offset. - As DateTime, it means 2024-01-20 10:00 with Kind=Unspecified unless parsed/constructed differently. Some of the system will treat it as UTC, some as local time.

DateTimeOffset in .NET
using System;

class Demo
{
    static void Main()
    {
        DateTimeOffset fixedLocal = new DateTimeOffset(2024, 1, 20, 10, 0, 0, TimeSpan.FromHours(8));
        DateTimeOffset fixedUtc = fixedLocal.ToUniversalTime();

        Console.WriteLine($"Local: {fixedLocal:o}");
        Console.WriteLine($"UTC: {fixedUtc:o}");

        // Convert to a specific offset
        DateTimeOffset toOffset = fixedLocal.ToOffset(TimeSpan.FromHours(8));
        Console.WriteLine($"+08:00: {toOffset:o}");
    }
}

// >>> Local: 2024-01-20T10:00:00.0000000+08:00
// >>> UTC: 2024-01-20T02:00:00.0000000+00:00
// >>> +08:00: 2024-01-20T10:00:00.0000000+08:00
Convert to/from DateTime
using System;

class Demo
{
    static void Main()
    {
        DateTimeOffset dto = new DateTimeOffset(2024, 1, 20, 2, 0, 0, TimeSpan.Zero);
        DateTime utc = dto.UtcDateTime;
        DateTime local = dto.ToOffset(TimeSpan.FromHours(8)).DateTime;

        Console.WriteLine($"DTO: {dto:o}");
        Console.WriteLine($"UTC DateTime: {utc:o} Kind={utc.Kind}");
        Console.WriteLine($"Local DateTime: {local:o} Kind={DateTimeKind.Unspecified}");
    }
}

// >>> DTO: 2024-01-20T02:00:00.0000000+00:00
// >>> UTC DateTime: 2024-01-20T02:00:00.0000000Z Kind=Utc
// >>> Local DateTime: 2024-01-20T10:00:00.0000000 Kind=Unspecified

Recommendation⚓︎

  • If you need a time point across time zones, prefer DateTimeOffset.
  • If you only use local time within a single environment, DateTime can be acceptable; still prefer UtcNow.
  • For time zone rules and conversions, use TimeZoneInfo.