NodaTime – time offsets C#

Karen Payne - Aug 17 - - Dev Community

Introduction

NodaTime is an NuGet package devoted to working with dates and times. In this article a cursory look at working with time zones is done. Yes, .NET Framework handles time zone while NodaTime does a more precise handling of time zones.

With power and precision of NodaTime comes with it documentation not geared to developers who rarely read documentation and developers who just do not care to read documentation. For these developers it is a must to read NodaTime documentation and experiment.

Here code will be presented in a class project and ran in a console project to keep things simple and always the class project to be used in other project types while keeping things simple as possible but it’s the job of the reader to take time to study the code, not just use the class project without studying the code.

Source code

The provided code was written using NET8 Core while NodaTime support classic .NET Framework there are some aspects of the code provided which will not work with older frameworks yet all NodaTime code works across classic and core frameworks.

Class project

Console project

Core code

Regardless of the time zone, the following class provides core functionality which accepts a time zone identifier e.g. US/Eastern for east coast of the United States or Africa/Nairob for Nairob Africa.

See the full list here for time zone identifiers.

using NodaTime;
using NodaTime.TimeZones;

namespace NodaTimeLibrary.Classes;
/// <summary>
///
/// Patterns for Offset values
/// https://nodatime.org/3.1.x/userguide/offset-patterns
///
/// Time zone list
/// https://nodatime.org/TimeZones
/// https://gist.github.com/jrolstad/5ca7d78dbfe182d7c1be
/// </summary>
internal static class TimeOperations
{
    /// <summary>
    /// Gets the time zone offset and whether daylight savings is in effect for the specified time zone and UTC date and time.
    /// </summary>
    /// <param name="timeZoneId">The identifier of the time zone.</param>
    /// <param name="dateTimeUtc">The UTC date and time.</param>
    /// <returns>A tuple containing the time zone offset as a string and a boolean indicating whether daylight savings is in effect.</returns>
    public static (string offset, bool daylightSavings) TimeZoneOffset(string timeZoneId, DateTime dateTimeUtc)
    {

        Instant instant = Instant.FromDateTimeUtc(dateTimeUtc);
        DateTimeZone zone = DateTimeZoneProviders.Tzdb[timeZoneId];

        ZonedDateTime dateTime = new(instant, zone);
        var daylightSavings = dateTime.IsDaylightSavingsTime();

        Offset offset = daylightSavings ? zone.MinOffset : zone.MaxOffset;

        return (offset.ToString("m", null), daylightSavings);
    }

    /// <summary>
    /// Determines whether the specified date and time is during daylight saving time in the given time zone.
    /// </summary>
    /// <param name="zonedDateTime">The date and time to check.</param>
    /// <returns>True if the specified date and time is during daylight saving time; otherwise, false.</returns>
    public static bool IsDaylightSavingsTime(this ZonedDateTime zonedDateTime)
    {
        var instant = zonedDateTime.ToInstant();
        var zoneInterval = zonedDateTime.Zone.GetZoneInterval(instant);
        return zoneInterval.Savings != Offset.Zero;
    }

    /// <summary>
    /// Gets the current system date and time in the specified time zone.
    /// </summary>
    /// <param name="zone">The time zone identifier. Default is "US/Pacific".</param>
    /// <returns>The current system date and time in the specified time zone.</returns>
    public static DateTime GetSysDateTimeNow(string zone = "US/Pacific")
    {
        Instant now = SystemClock.Instance.GetCurrentInstant();
        return now.InZone(DateTimeZoneProviders.Tzdb[zone]).ToDateTimeUnspecified();
    }

    /// <summary>
    /// Gets all the time zones for a given country code.
    /// </summary>
    /// <param name="countryCode">The country code. Default is "US".</param>
    /// <returns>A list of TzdbZoneLocation objects representing the time zones.</returns>
    public static List<TzdbZoneLocation> GetAllTimeZones(string countryCode = "US") =>
        (TzdbDateTimeZoneSource.Default.ZoneLocations ?? Enumerable.Empty<TzdbZoneLocation>().ToList())
        .Where(x => x.CountryCode == countryCode)
        .ToList();
}
Enter fullscreen mode Exit fullscreen mode

United States Each/West coast sample

using NodaTimeLibrary.Classes;

namespace NodaTimeLibrary.Models;

/// <summary>
/// Represents the East Coast time zone.
/// </summary>
public class EastCoast
{
    private string _timeZoneId => "US/Eastern";

    /// <summary>
    /// Gets the offset of the East Coast time zone.
    /// </summary>
    /// <returns>The offset of the East Coast time zone.</returns>
    public string Offset =>
        TimeOperations.TimeZoneOffset(_timeZoneId, 
            DateTime.SpecifyKind(TimeOperations.GetSysDateTimeNow(_timeZoneId), 
                DateTimeKind.Utc)).offset;

    /// <summary>
    /// Determines whether the East Coast time zone is currently in daylight saving time.
    /// </summary>
    /// <returns>True if the East Coast time zone is currently in daylight saving time; otherwise, false.</returns>
    public bool DaylightSavingsSupported =>
        TimeOperations.TimeZoneOffset(_timeZoneId, 
            DateTime.SpecifyKind(TimeOperations.GetSysDateTimeNow(_timeZoneId), 
                DateTimeKind.Utc)).daylightSavings;
}

using NodaTimeLibrary.Classes;

namespace NodaTimeLibrary.Models;

public class WestCoast
{
    private string _timeZoneId => "US/Pacific";

    /// <summary>
    /// Gets the offset of the West Coast time zone.
    /// </summary>
    /// <returns>The offset of the West Coast time zone.</returns>
    public string Offset =>
        TimeOperations.TimeZoneOffset(_timeZoneId, 
            DateTime.SpecifyKind(TimeOperations.GetSysDateTimeNow(), 
                DateTimeKind.Utc)).offset;

    /// <summary>
    /// Determines whether the West Coast time zone is currently in daylight saving time.
    /// </summary>
    /// <returns>True if the West Coast time zone is currently in daylight saving time; otherwise, false.</returns>
    public bool DaylightSavingsSupported =>
        TimeOperations.TimeZoneOffset(_timeZoneId, 
            DateTime.SpecifyKind(TimeOperations.GetSysDateTimeNow(_timeZoneId), 
                DateTimeKind.Utc)).daylightSavings;
}
Enter fullscreen mode Exit fullscreen mode

Main class

The main class, UnitedStates has two properties, EastCoast and WestCoast. The key to each coast class is the time zone identifier which is passed to the Offset and DaylightSavingsSupported.

namespace NodaTimeLibrary.Models;

/// <summary>
/// Represents the United States with its East Coast and West Coast time zones.
/// </summary>
public class UnitedStates
{
    /// <summary>
    /// Gets or sets the East Coast time zone.
    /// </summary>  
    public EastCoast EastCoast { get; init; } = new();

    /// <summary>
    /// Gets or sets the West Coast time zone.
    /// </summary>
    public WestCoast WestCoast { get; init; } = new();
}
Enter fullscreen mode Exit fullscreen mode

Now we can get the offset for east and west code using. AnsiConsole is from Spectre.Console NuGet package.

UnitedStates us = new();
AnsiConsole.MarkupLine($"[yellow]United States[/]");
AnsiConsole.MarkupLine($"[cyan]  Eastern offset[/]: [b]{us.EastCoast.Offset}[/]");
AnsiConsole.MarkupLine($"[cyan]Daylight savings:[/] [b]{us.EastCoast.DaylightSavingsSupported.ToYesNo()}[/]");

AnsiConsole.MarkupLine($"[cyan]  Western offset[/]: [b]{us.WestCoast.Offset}[/]");
AnsiConsole.MarkupLine($"[cyan]Daylight savings:[/] [b]{us.WestCoast.DaylightSavingsSupported.ToYesNo()}[/]");
Enter fullscreen mode Exit fullscreen mode

Output from above code

That's it.

Other

To peak interest in other aspects of NodaTime, let’s look at checking how old a person is. See provided source to try the method out.

using NodaTime;

namespace NodaTimeLibrary.Classes;
public class PeriodOperations
{
    /// <summary>
    /// Calculates the period between a birthdate and a given date in years, months, and days.
    /// </summary>
    /// <param name="birthDate">The birthdate.</param>
    /// <param name="date">The date to calculate the period to.</param>
    /// <returns>The period between the birthdate and the given date.</returns>
    public static Period YearsOld(DateOnly birthDate, DateOnly date) =>
        Period.Between(LocalDate.FromDateOnly(birthDate), LocalDate.FromDateOnly(date),
            PeriodUnits.Years |
            PeriodUnits.Months |
            PeriodUnits.Days);
}
Enter fullscreen mode Exit fullscreen mode


`

Summary

NodaTime is a powerful library for working with dates and time, here only the tip of the iceberg has been explored. If dates and time are important to your project check out NodaTime.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .