From 1253ec4a19dda8a1577b1eb054b167697d29d9bb Mon Sep 17 00:00:00 2001 From: axunonb Date: Tue, 1 Oct 2024 09:30:00 +0200 Subject: [PATCH] Enhance time zone handling and add unit tests (#194) * Updated the `IZonedTimeInfo` and `ZonedTime` classes to clarify the documentation for the `BaseUtcOffset` property, specifying its relation to UTC. * Added a new test class `TimeZoneConverterTests` with multiple test methods to verify various functionalities of the `TimeZoneConverter`, including conversions between UTC and time zones, handling of `DateTime` kinds, and mapping to IANA time zones. --- .../DateAndTime/TimeZoneConverterTests.cs | 202 ++++++++++++++++++ Axuno.Tools/DateAndTime/IZonedTimeInfo.cs | 2 +- Axuno.Tools/DateAndTime/TimeZoneConverter.cs | 4 +- Axuno.Tools/DateAndTime/ZonedTime.cs | 2 +- 4 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 Axuno.Tools.Tests/DateAndTime/TimeZoneConverterTests.cs diff --git a/Axuno.Tools.Tests/DateAndTime/TimeZoneConverterTests.cs b/Axuno.Tools.Tests/DateAndTime/TimeZoneConverterTests.cs new file mode 100644 index 00000000..24e94368 --- /dev/null +++ b/Axuno.Tools.Tests/DateAndTime/TimeZoneConverterTests.cs @@ -0,0 +1,202 @@ +using System.Globalization; +using NUnit.Framework; +using NodaTime; + +namespace Axuno.Tools.Tests.DateAndTime; + +[TestFixture] +public class TimeZoneConverterTests +{ + private static Axuno.Tools.DateAndTime.TimeZoneConverter GetTimeZoneConverter(string culture) + { + var tzc = new Axuno.Tools.DateAndTime.TimeZoneConverter( + new NodaTime.TimeZones.DateTimeZoneCache(NodaTime.TimeZones.TzdbDateTimeZoneSource.Default), + "Europe/Berlin", + CultureInfo.GetCultureInfo(culture), + NodaTime.TimeZones.Resolvers.LenientResolver); + return tzc; + } + + [Test] + public void ConvertUtcToTimeZoneStandard_ShouldReturnCorrectDateTime() + { + // Arrange + var utcDateTime = new DateTime(2022, 1, 1, 12, 0, 0, DateTimeKind.Utc); + var expectedDateTime = new DateTime(2022, 1, 1, 13, 0, 0, DateTimeKind.Local); + + // Act + var convertedDateTime = GetTimeZoneConverter("de-DE").ToZonedTime(utcDateTime)!; + + // Assert + Assert.Multiple(() => + { + Assert.That(convertedDateTime.DateTimeOffset.DateTime, Is.EqualTo(expectedDateTime)); + Assert.That(convertedDateTime.TimeZoneId, Is.EqualTo("Europe/Berlin")); + Assert.That(convertedDateTime.CultureInfo.TwoLetterISOLanguageName, Is.EqualTo("de")); + Assert.That(convertedDateTime.GenericName, Is.EqualTo("Mitteleuropäische Zeit")); + Assert.That(convertedDateTime.GenericAbbreviation, Is.EqualTo("MEZ")); + Assert.That(convertedDateTime.DisplayName, Is.EqualTo("(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien")); + Assert.That(convertedDateTime.Name, Is.EqualTo("Mitteleuropäische Normalzeit")); + Assert.That(convertedDateTime.Abbreviation, Is.EqualTo("MEZ")); + Assert.That(convertedDateTime.IsDaylightSavingTime, Is.False); + Assert.That(convertedDateTime.DateTimeOffset.Offset, Is.EqualTo(new TimeSpan(0, 1, 0, 0))); + Assert.That(convertedDateTime.BaseUtcOffset, Is.EqualTo(new TimeSpan(0,1,0,0))); + }); + } + + [Test] + public void ConvertUtcToTimeZoneDaylight_ShouldReturnCorrectDateTime() + { + // Arrange + var utcDateTime = new DateTime(2022, 7, 1, 12, 0, 0, DateTimeKind.Utc); + var expectedDateTime = new DateTime(2022, 7, 1, 14, 0, 0, DateTimeKind.Local); + + // Act + var convertedDateTime = GetTimeZoneConverter("de-DE").ToZonedTime(utcDateTime)!; + + // Assert + Assert.Multiple(() => + { + Assert.That(convertedDateTime.DateTimeOffset.DateTime, Is.EqualTo(expectedDateTime)); + Assert.That(convertedDateTime.Name, Is.EqualTo("Mitteleuropäische Sommerzeit")); + Assert.That(convertedDateTime.Abbreviation, Is.EqualTo("MESZ")); + Assert.That(convertedDateTime.IsDaylightSavingTime, Is.True); + Assert.That(convertedDateTime.DateTimeOffset.Offset, Is.EqualTo(new TimeSpan(0, 2, 0, 0))); + Assert.That(convertedDateTime.BaseUtcOffset, Is.EqualTo(new TimeSpan(0, 1, 0, 0))); + }); + } + + [Test] + public void ConvertUnspecifiedKindToTimeZone_ShouldReturnCorrectDateTime() + { + // Arrange + var unspecifiedDateTime = new DateTime(2022, 1, 1, 12, 0, 0, DateTimeKind.Unspecified); + var expectedDateTime = new DateTime(2022, 1, 1, 13, 0, 0, DateTimeKind.Local); + + // Act + var convertedDateTime = GetTimeZoneConverter("de-DE").ToZonedTime(unspecifiedDateTime)!; + + // Assert + Assert.That(convertedDateTime.DateTimeOffset.DateTime, Is.EqualTo(expectedDateTime)); + } + + [Test] + public void ConvertKindLocalToTimeZone_ShouldReturnCorrectDateTime() + { + // Arrange + var localDateTime = new DateTime(2022, 1, 1, 12, 0, 0, DateTimeKind.Local); + var expectedDateTime = new DateTime(2022, 1, 1, 12, 0, 0, DateTimeKind.Local); + + // Act + var convertedDateTime = GetTimeZoneConverter("de-DE").ToZonedTime(localDateTime)!; + + // Assert + Assert.That(convertedDateTime.DateTimeOffset.DateTime, Is.EqualTo(expectedDateTime)); + } + + [Test] + public void ConvertTimeZoneToUtc_ShouldReturnCorrectDateTime() + { + // Arrange + var localDateTime = new DateTime(2022, 1, 1, 8, 0, 0, DateTimeKind.Local); + var expectedDateTime = new DateTime(2022, 1, 1, 7, 0, 0, DateTimeKind.Utc); + + // Act + var convertedDateTime = GetTimeZoneConverter("en-US").ToUtc(localDateTime); + + // Assert + Assert.That(convertedDateTime, Is.EqualTo(expectedDateTime)); + } + + [Test] + public void ConvertTimeWithZoneIdToUtc_ShouldReturnCorrectDateTime() + { + // Arrange + var localDateTime = new DateTime(2022, 1, 1, 8, 0, 0, DateTimeKind.Local); + var expectedDateTime = new DateTime(2022, 1, 1, 7, 0, 0, DateTimeKind.Utc); + + // Act + var convertedDateTime = GetTimeZoneConverter("en-US").ToUtc(localDateTime, "Europe/Berlin"); + + // Assert + Assert.That(convertedDateTime, Is.EqualTo(expectedDateTime)); + } + + [Test] + public void ConvertUtcToTimeZone_ShouldReturnNull_WhenUtcDateTimeIsNull() + { + // Arrange + DateTime? utcDateTime = null; + + // Act + var convertedDateTime = GetTimeZoneConverter("de-DE").ToZonedTime(utcDateTime); + + // Assert + Assert.That(convertedDateTime, Is.Null); + } + + [Test] + public void ConvertTimeZoneToUtc_ShouldReturnNull_WhenLocalDateTimeIsNull() + { + // Arrange + DateTime? localDateTime = null; + + // Act + var convertedDateTime = GetTimeZoneConverter("en-US").ToUtc(localDateTime); + + // Assert + Assert.That(convertedDateTime, Is.Null); + } + + [Test] + public void ConvertUtcToTimeZone_ShouldReturnNull_WhenDateTimeOfAnyKindIsNull() + { + // Arrange + DateTime? dateTimeOfAnyKind = null; + + // Act + var convertedDateTime = GetTimeZoneConverter("en-US").ToZonedTime(dateTimeOfAnyKind); + + // Assert + Assert.That(convertedDateTime, Is.Null); + } + + [Test] + public void ConvertTimeZoneToUtc_ShouldReturnNull_WhenDateTimeOfAnyKindIsNull() + { + // Arrange + DateTime? dateTimeOfAnyKind = null; + + // Act + var convertedDateTime = GetTimeZoneConverter("en-US").ToUtc(dateTimeOfAnyKind, "Europe/Berlin"); + + // Assert + Assert.That(convertedDateTime, Is.Null); + } + [Test] + public void GetTimeZoneList_ShouldReturnTimeZoneList_WhenTimeZoneProviderIsNull() + { + // Arrange + IDateTimeZoneProvider? timeZoneProvider = null; + + // Act + var timeZoneList = Axuno.Tools.DateAndTime.TimeZoneConverter.GetTimeZoneList(timeZoneProvider); + + // Assert + Assert.That(timeZoneList, Is.InstanceOf>()); + } + + [Test] + public void CanMapToIanaTimeZone_ShouldReturnTrue_WhenTimeZoneInfoIsValid() + { + // Arrange + var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"); + + // Act + var canMap = Axuno.Tools.DateAndTime.TimeZoneConverter.CanMapToIanaTimeZone(timeZoneInfo); + + // Assert + Assert.That(canMap, Is.True); + } +} + diff --git a/Axuno.Tools/DateAndTime/IZonedTimeInfo.cs b/Axuno.Tools/DateAndTime/IZonedTimeInfo.cs index 49bae040..a4097f9f 100644 --- a/Axuno.Tools/DateAndTime/IZonedTimeInfo.cs +++ b/Axuno.Tools/DateAndTime/IZonedTimeInfo.cs @@ -48,7 +48,7 @@ public interface IZonedTimeInfo bool IsDaylightSavingTime { get; } /// - /// Gets the base UTC offset of the timezone related to the . + /// Gets the time difference between the current time zone's standard time and Coordinated Universal Time (UTC). /// TimeSpan BaseUtcOffset { get; } } diff --git a/Axuno.Tools/DateAndTime/TimeZoneConverter.cs b/Axuno.Tools/DateAndTime/TimeZoneConverter.cs index 4b509e85..54596da7 100644 --- a/Axuno.Tools/DateAndTime/TimeZoneConverter.cs +++ b/Axuno.Tools/DateAndTime/TimeZoneConverter.cs @@ -73,7 +73,7 @@ public TimeZoneConverter(IDateTimeZoneProvider dateTimeZoneProvider, string iana /// /// Converts the of any to a of . /// - /// A in the the timezone specified with the timezone ID given when creating this converter instance. + /// A in the timezone specified with the timezone ID given when creating this converter instance. /// Returns the converted with or null, if the parameter is null. public DateTime? ToUtc(DateTime? zoneDateTime) { @@ -83,7 +83,7 @@ public TimeZoneConverter(IDateTimeZoneProvider dateTimeZoneProvider, string iana /// /// Converts the of any to a of . /// - /// A in the the timezone specified with the timezone ID given when creating this converter instance. + /// A in the timezone specified with the timezone ID given when creating this converter instance. /// Returns the converted with . public DateTime ToUtc(DateTime zoneDateTime) { diff --git a/Axuno.Tools/DateAndTime/ZonedTime.cs b/Axuno.Tools/DateAndTime/ZonedTime.cs index cc803ef5..0a580f3f 100644 --- a/Axuno.Tools/DateAndTime/ZonedTime.cs +++ b/Axuno.Tools/DateAndTime/ZonedTime.cs @@ -57,7 +57,7 @@ internal ZonedTime() public bool IsDaylightSavingTime { get; internal set; } /// - /// Gets the base UTC offset of the timezone related to the . + /// Gets the time difference between the current time zone's standard time and Coordinated Universal Time (UTC). /// public TimeSpan BaseUtcOffset { get; internal set; } }