diff --git a/src/dev/impl/DevToys/DevToys.csproj b/src/dev/impl/DevToys/DevToys.csproj index 7bb7d87c87..ad1a8c0b42 100644 --- a/src/dev/impl/DevToys/DevToys.csproj +++ b/src/dev/impl/DevToys/DevToys.csproj @@ -36,6 +36,7 @@ + diff --git a/src/dev/impl/DevToys/Helpers/TimestampToolHelper.cs b/src/dev/impl/DevToys/Helpers/TimestampToolHelper.cs new file mode 100644 index 0000000000..ceaff138dd --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/TimestampToolHelper.cs @@ -0,0 +1,106 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text.RegularExpressions; + +namespace DevToys.Helpers +{ + internal static class TimestampToolHelper + { + internal static class ZoneInfo + { + private static string _utcDisplayName = ""; + private static string _localDisplayName = ""; + private static readonly IReadOnlyCollection _systemTimeZone = TimeZoneInfo.GetSystemTimeZones(); + private static readonly IReadOnlyDictionary _timeZoneCollection = InitTimeZoneCollection(); + + internal static string UtcDisplayName => _utcDisplayName; + + internal static string LocalDisplayName => _localDisplayName; + + internal static IReadOnlyList DisplayNames => _timeZoneCollection.Keys.ToList(); + + internal static IReadOnlyDictionary TimeZones => _timeZoneCollection; + + private static IReadOnlyDictionary InitTimeZoneCollection() + { + Dictionary timeZoneCollection = new(); + if (!Regex.IsMatch(_systemTimeZone.ElementAt(0).DisplayName, @"^\(UTC.*\).+$")) + { + // version < .Net6 + // This implementation mitigates the changes in the strings + // that are obtained when optimized in release builds, + // as the target of external tools is .net6 or earlier. + // zone.DisplayName : "(UTC+09:00) 大阪、札幌、東京"( >= .net6) or "東京 (標準時)"( < .net6) + foreach (TimeZoneInfo zone in _systemTimeZone) + { + string displayName = $"(UTC{zone.BaseUtcOffset.Hours:+00;-00;}:{zone.BaseUtcOffset.Minutes:00;00;}) " + zone.DisplayName; + if (zone.Id == TimeZoneInfo.Utc.Id) + { + displayName = "(UTC) " + zone.DisplayName; + _utcDisplayName = "(UTC) " + zone.DisplayName; + } + if (zone.Id == TimeZoneInfo.Local.Id) + { + _localDisplayName = displayName; + } + timeZoneCollection.Add(displayName, zone.Id); + } + } + else + { + // version >= .Net6 + foreach (TimeZoneInfo zone in _systemTimeZone) + { + timeZoneCollection.Add(zone.DisplayName, zone.Id); + } + _utcDisplayName = TimeZoneInfo.Utc.DisplayName; + _localDisplayName = TimeZoneInfo.Local.DisplayName; + } + return timeZoneCollection; + } + } + + internal static class TimeZone + { + internal static DateTimeOffset SafeMinValue(TimeZoneInfo timezone) + { + if (timezone is null) + { + timezone = TimeZoneInfo.Utc; + } + DateTimeOffset t1 = TimeZoneInfo.ConvertTime( + new DateTimeOffset(10, 1, 1, 0, 0, 0, TimeZoneInfo.Utc.BaseUtcOffset), + timezone); + DateTimeOffset minValue = DateTimeOffset.MinValue; + if (t1.Year < 10) + { + minValue = minValue.Subtract(t1.Offset); + } + return TimeZoneInfo.ConvertTime(minValue, timezone); + } + + internal static DateTimeOffset SafeMaxValue(TimeZoneInfo timezone) + { + if (timezone is null) + { + timezone = TimeZoneInfo.Utc; + } + DateTimeOffset t1 = TimeZoneInfo.ConvertTime( + new DateTimeOffset(9990, 12, 31, 23, 59, 59, TimeZoneInfo.Utc.BaseUtcOffset), + timezone); + DateTimeOffset maxValue = DateTimeOffset.MaxValue; + if (t1.Year > 9990) + { + maxValue = maxValue.Subtract(t1.Offset); + } + return TimeZoneInfo.ConvertTime(maxValue, timezone); + } + + } + + } +} diff --git a/src/dev/impl/DevToys/LanguageManager.cs b/src/dev/impl/DevToys/LanguageManager.cs index 4c25863518..5ff95fa2e8 100644 --- a/src/dev/impl/DevToys/LanguageManager.cs +++ b/src/dev/impl/DevToys/LanguageManager.cs @@ -2731,6 +2731,16 @@ public class TimestampStrings : ObservableObject /// public string AccessibleName => _resources.GetString("AccessibleName"); + /// + /// Gets the resource TimeZoneTitle. + /// + public string TimeZoneTitle => _resources.GetString("TimeZoneTitle"); + + /// + /// Gets the resource DaylightSavingTime. + /// + public string DaylightSavingTime => _resources.GetString("DaylightSavingTime"); + /// /// Gets the resource DayTitle. /// @@ -2741,6 +2751,16 @@ public class TimestampStrings : ObservableObject /// public string Description => _resources.GetString("Description"); + /// + /// Gets the resource DisabledDaylightSavingTime. + /// + public string DisabledDaylightSavingTime => _resources.GetString("DisabledDaylightSavingTime"); + + /// + /// Gets the resource DSTAmbiguousTime. + /// + public string DSTAmbiguousTime => _resources.GetString("DSTAmbiguousTime"); + /// /// Gets the resource HourTitle. /// @@ -2771,6 +2791,11 @@ public class TimestampStrings : ObservableObject /// public string MonthTitle => _resources.GetString("MonthTitle"); + /// + /// Gets the resource OffsetTitle. + /// + public string OffsetTitle => _resources.GetString("OffsetTitle"); + /// /// Gets the resource SearchDisplayName. /// @@ -2786,6 +2811,11 @@ public class TimestampStrings : ObservableObject /// public string SecondsTitle => _resources.GetString("SecondsTitle"); + /// + /// Gets the resource SupportsDaylightSavingTime. + /// + public string SupportsDaylightSavingTime => _resources.GetString("SupportsDaylightSavingTime"); + /// /// Gets the resource TimestampTitle. /// @@ -2796,6 +2826,11 @@ public class TimestampStrings : ObservableObject /// public string UTCDateTime => _resources.GetString("UTCDateTime"); + /// + /// Gets the resource UtcTicksTitle. + /// + public string UtcTicksTitle => _resources.GetString("UtcTicksTitle"); + /// /// Gets the resource YearTitle. /// diff --git a/src/dev/impl/DevToys/Strings/cs-CZ/Timestamp.resw b/src/dev/impl/DevToys/Strings/cs-CZ/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/cs-CZ/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/cs-CZ/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/de-DE/Timestamp.resw b/src/dev/impl/DevToys/Strings/de-DE/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/de-DE/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/de-DE/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/en-US/Timestamp.resw b/src/dev/impl/DevToys/Strings/en-US/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/en-US/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/en-US/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/es-AR/Timestamp.resw b/src/dev/impl/DevToys/Strings/es-AR/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/es-AR/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/es-AR/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/es-ES/Timestamp.resw b/src/dev/impl/DevToys/Strings/es-ES/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/es-ES/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/es-ES/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/fr-FR/Timestamp.resw b/src/dev/impl/DevToys/Strings/fr-FR/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/fr-FR/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/fr-FR/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/hu-HU/Timestamp.resw b/src/dev/impl/DevToys/Strings/hu-HU/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/hu-HU/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/hu-HU/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/id-ID/Timestamp.resw b/src/dev/impl/DevToys/Strings/id-ID/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/id-ID/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/id-ID/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/it-IT/Timestamp.resw b/src/dev/impl/DevToys/Strings/it-IT/Timestamp.resw index 604d293416..db2b942079 100644 --- a/src/dev/impl/DevToys/Strings/it-IT/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/it-IT/Timestamp.resw @@ -120,12 +120,24 @@ Convertitore Unix Timestamp + + Time zone + + + Daylight saving time. + Giorno Converti date di tipo timestamp in date accessibili all'uomo e viceversa + + There is no daylight saving time. + + + DST Ambiguous time. + Ora (24 ore) @@ -144,6 +156,9 @@ Mese + + Offset + Convertitore Unix Timestap @@ -154,12 +169,18 @@ Secondi + + There is daylight saving time. + Timestamp Data e ora UTC + + UtcTicks + Anno diff --git a/src/dev/impl/DevToys/Strings/ja-JP/Timestamp.resw b/src/dev/impl/DevToys/Strings/ja-JP/Timestamp.resw index 93d8f1bea3..1243f1132e 100644 --- a/src/dev/impl/DevToys/Strings/ja-JP/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/ja-JP/Timestamp.resw @@ -120,17 +120,29 @@ Unix タイムスタンプ変換ツール + + タイムゾーン + + + 夏時間 (サマータイム) 適用期間です + - Unix 時間やUTCなどの日時を変換 + Unix 時間や UTC などの日時を変換 + + + 夏時間 (サマータイム) 規則はありません + + + 夏時間の切り替えによる重複が発生しています 時 (24 時間) - 使用できない文字が含まれています + 変換できる範囲を超えています ローカル日時 @@ -144,6 +156,9 @@ + + オフセット + Unix タイムスタンプ変換 @@ -154,12 +169,18 @@ + + 夏時間 (サマータイム) 規則があります + タイムスタンプ UTC (協定世界時) + + UtcTicks 値 + diff --git a/src/dev/impl/DevToys/Strings/ko-KR/Timestamp.resw b/src/dev/impl/DevToys/Strings/ko-KR/Timestamp.resw index c4edfd6831..9004511660 100644 --- a/src/dev/impl/DevToys/Strings/ko-KR/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/ko-KR/Timestamp.resw @@ -120,12 +120,24 @@ 타임스탬프 변환기 + + Time zone + + + Daylight saving time. + 타임스탬프를 읽기 쉬운 날짜로 변환하거나 반대로 변환합니다. + + There is no daylight saving time. + + + DST Ambiguous time. + 시 (24 시간제) @@ -144,6 +156,9 @@ + + Offset + Unix 타임스탬프 변환기 @@ -154,12 +169,18 @@ + + There is daylight saving time. + 타임스탬프 UTC 날짜 및 시간 + + UtcTicks + diff --git a/src/dev/impl/DevToys/Strings/pl-PL/Timestamp.resw b/src/dev/impl/DevToys/Strings/pl-PL/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/pl-PL/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/pl-PL/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/pt-BR/Timestamp.resw b/src/dev/impl/DevToys/Strings/pt-BR/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/pt-BR/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/pt-BR/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/pt-PT/Timestamp.resw b/src/dev/impl/DevToys/Strings/pt-PT/Timestamp.resw index 404df2c798..74966d64ee 100644 --- a/src/dev/impl/DevToys/Strings/pt-PT/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/pt-PT/Timestamp.resw @@ -120,12 +120,24 @@ Ferramenta de Conversão do Carimbo Temporal + + Time zone + + + Daylight saving time. + Dia Converte o carimbo temporal em data legível pelo ser humano e vice-versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hora (24 horas) @@ -144,6 +156,9 @@ Mês + + Offset + Conversor Carimbo Temporal Unix @@ -154,12 +169,18 @@ Segundos + + There is daylight saving time. + Carimbo Temporal Data e Hora UTC + + UtcTicks + Ano diff --git a/src/dev/impl/DevToys/Strings/ru-RU/Timestamp.resw b/src/dev/impl/DevToys/Strings/ru-RU/Timestamp.resw index 1df1790420..ded72ad0b8 100644 --- a/src/dev/impl/DevToys/Strings/ru-RU/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/ru-RU/Timestamp.resw @@ -120,12 +120,24 @@ Timestamp converter tool + + Time zone + + + Daylight saving time. + Day Convert timestamp to human-readable date and vice versa + + There is no daylight saving time. + + + DST Ambiguous time. + Hour (24 hour) @@ -144,6 +156,9 @@ Month + + Offset + Unix Timestamp Converter @@ -154,12 +169,18 @@ Seconds + + There is daylight saving time. + Timestamp UTC Date and Time + + UtcTicks + Year diff --git a/src/dev/impl/DevToys/Strings/zh-Hans/Timestamp.resw b/src/dev/impl/DevToys/Strings/zh-Hans/Timestamp.resw index 0307fed2ad..7e12211376 100644 --- a/src/dev/impl/DevToys/Strings/zh-Hans/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/zh-Hans/Timestamp.resw @@ -120,12 +120,24 @@ 时间戳转换工具 + + Time zone + + + Daylight saving time. + 将时间戳转换为人类可读的日期,反之亦然 + + There is no daylight saving time. + + + DST Ambiguous time. + 小时(24小时制) @@ -144,6 +156,9 @@ + + Offset + Unix 时间戳转换工具 @@ -154,12 +169,18 @@ + + There is daylight saving time. + 时间戳 世界标准时间(UTC) + + UtcTicks + diff --git a/src/dev/impl/DevToys/Strings/zh-Hant/Timestamp.resw b/src/dev/impl/DevToys/Strings/zh-Hant/Timestamp.resw index 1e0471bad0..f81f2ef0e8 100644 --- a/src/dev/impl/DevToys/Strings/zh-Hant/Timestamp.resw +++ b/src/dev/impl/DevToys/Strings/zh-Hant/Timestamp.resw @@ -120,12 +120,24 @@ 時間戳轉換工具 + + Time zone + + + Daylight saving time. + 在時間戳和易讀格式間轉換 + + There is no daylight saving time. + + + DST Ambiguous time. + 時 (24 小時制) @@ -144,6 +156,9 @@ + + Offset + Unix 時間戳轉換工具 @@ -154,12 +169,18 @@ + + There is daylight saving time. + 時間戳 UTC 日期及時間 + + UtcTicks + diff --git a/src/dev/impl/DevToys/ViewModels/Tools/Converters/Timestamp/TimestampToolViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/Converters/Timestamp/TimestampToolViewModel.cs index 69cc58dfba..2517b7736c 100644 --- a/src/dev/impl/DevToys/ViewModels/Tools/Converters/Timestamp/TimestampToolViewModel.cs +++ b/src/dev/impl/DevToys/ViewModels/Tools/Converters/Timestamp/TimestampToolViewModel.cs @@ -1,8 +1,10 @@ #nullable enable using System; +using System.Collections.Generic; using System.Composition; using DevToys.Api.Tools; +using DevToys.Helpers; using DevToys.Views.Tools.Timestamp; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; @@ -14,289 +16,275 @@ namespace DevToys.ViewModels.Tools.Timestamp public sealed class TimestampToolViewModel : ObservableRecipient, IToolViewModel { private bool _isInputInvalid; + + internal IReadOnlyList TimeZoneDisplayNameCollection = TimestampToolHelper.ZoneInfo.DisplayNames; + private readonly IReadOnlyDictionary _timeZoneCollection = TimestampToolHelper.ZoneInfo.TimeZones; + private TimeZoneInfo _currentTimeZone = TimeZoneInfo.Utc; + private string _currentTimeZoneDisplayName = TimestampToolHelper.ZoneInfo.UtcDisplayName; private double _timestamp; - private DateTime _utcDateTime; - private Lazy _localDateTime = null!; + private DateTimeOffset _utcDateTime; + private DateTimeOffset _zoneOffsetDateTime; + private long _minimumZoneOffsetTimestamp = -62135596800; + private long _maximumZoneOffsetTimestamp = 253402300799; + + private string _dstInfoDSTMessage = ""; + private string _dstInfoOffset = ""; + private string _dstInfoLocalDateTime = ""; + private string _dstInfoUtcDateTime = ""; + private string _dstInfoUtcTicks = ""; public Type View => typeof(TimestampToolPage); internal TimestampStrings Strings => LanguageManager.Instance.Timestamp; + /// + /// Gets or sets true if the DateTimeOffset structure may exceed the settable range. + /// internal bool IsInputInvalid { get => _isInputInvalid; set => SetProperty(ref _isInputInvalid, value); } - internal double Timestamp + /// + /// Daylight saving time display in DSTInfo block. + /// Gets or sets text that displays whether it is + /// daylight saving time support, in daylight saving time, or ambiguous time + /// for a given time zone. + /// + internal string DSTInfoMessage { - get => _timestamp; - set - { - if (double.IsNaN(value)) - { - _timestamp = 0; - } - else - { - _timestamp = value; - } - - ResetUtcDateTime(); - ResetLocalDateTime(); - } + get => _dstInfoDSTMessage; + set => SetProperty(ref _dstInfoDSTMessage, value); } - internal int UtcYear + /// + /// Local date and time value in DSTInfo block. + /// Get or set the date and time converted to the time zone on the PC. + /// (e.g. "2026/05/13 09:12:34") + /// + internal string DSTInfoLocalDateTime { - get => _utcDateTime.Year; - set - { - if (value < 1) // empty = -2147483648 - { - return; - } - - if (UtcDay > DateTime.DaysInMonth(value, UtcMonth)) - { - return; - } - if (!UpdateUtcDateTime(value, UtcMonth, UtcDay, UtcHour, UtcMinute, UtcSecond)) - { - return; - } - ResetLocalDateTime(); - ResetTimestamp(); - } + get => _dstInfoLocalDateTime; + set => SetProperty(ref _dstInfoLocalDateTime, value); } - internal int UtcMonth + /// + /// Time zone offset value for DSTInfo block. + /// Gets or sets the offset value that changes with the date and time in the specified time zone. + /// (e.g. "+09:00") + /// + internal string DSTInfoOffsetValue { - get => _utcDateTime.Month; - set - { - if (value < 1) // empty = -2147483648 - { - return; - } - - if (UtcDay > DateTime.DaysInMonth(UtcYear, value)) - { - return; - } - - if (!UpdateUtcDateTime(UtcYear, value, UtcDay, UtcHour, UtcMinute, UtcSecond)) - { - return; - } - ResetLocalDateTime(); - ResetTimestamp(); - } + get => _dstInfoOffset; + set => SetProperty(ref _dstInfoOffset, value); } - internal int UtcDay + /// + /// UTC date and time value in DSTInfo block. + /// Gets or sets the string of the specified date and time converted to UTC(+00:00). + /// (e.g. "2026/05/13 01:23:45") + /// + internal string DSTInfoUtcDateTime { - get => _utcDateTime.Day; - set - { - if (value < 1) // empty = -2147483648 - { - return; - } + get => _dstInfoUtcDateTime; + set => SetProperty(ref _dstInfoUtcDateTime, value); + } - if (value > DateTime.DaysInMonth(UtcYear, UtcMonth)) - { - return; - } - if (!UpdateUtcDateTime(UtcYear, UtcMonth, value, UtcHour, UtcMinute, UtcSecond)) - { - return; - } - ResetLocalDateTime(); - ResetTimestamp(); - } + /// + /// UTCTicks value in DSTInfo block. + /// Gets or sets the string of the specified date and time converted to UTCTicks. + /// + internal string DSTInfoUtcTicks + { + get => _dstInfoUtcTicks; + set => SetProperty(ref _dstInfoUtcTicks, value); } - internal int UtcHour + private void DSTInfo() { - get => _utcDateTime.Hour; - set + if (_currentTimeZone.IsAmbiguousTime(_zoneOffsetDateTime)) { - if (value < 0) // empty = -2147483648 - { - return; - } - if(!UpdateUtcDateTime(UtcYear, UtcMonth, UtcDay, value, UtcMinute, UtcSecond)) - { - return; - } - ResetLocalDateTime(); - ResetTimestamp(); + DSTInfoMessage = Strings.DSTAmbiguousTime; + } + else if (_currentTimeZone.IsDaylightSavingTime(_zoneOffsetDateTime)) + { + DSTInfoMessage = Strings.DaylightSavingTime; + } + else if (_currentTimeZone.SupportsDaylightSavingTime) + { + DSTInfoMessage = Strings.SupportsDaylightSavingTime; + } + else + { + DSTInfoMessage = Strings.DisabledDaylightSavingTime; } + DSTInfoOffsetValue = _zoneOffsetDateTime.ToString("zzz"); + DSTInfoLocalDateTime = _zoneOffsetDateTime.LocalDateTime.ToString("yyyy/MM/dd HH:mm:ss"); + DSTInfoUtcDateTime = _zoneOffsetDateTime.UtcDateTime.ToString("yyyy/MM/dd HH:mm:ss"); + DSTInfoUtcTicks = _zoneOffsetDateTime.UtcTicks.ToString(); } - internal int UtcMinute + /// + /// Gets or sets the time zone name. + /// This value is essentially the value of TimeZoneInfo.(zone).DisplayName (e.g. "(UTC) Coordinated Universal Time"), + /// which is used to reverse lookup the time zone ID supported by the OS(e.g. TimeZoneInfo.Utc.Id -> "UTC"). + /// + internal string CurrentTimeZoneDisplayName { - get => _utcDateTime.Minute; + get => _currentTimeZoneDisplayName; set { - if (value < 0) // empty = -2147483648 - { - return; - } - if (!UpdateUtcDateTime(UtcYear, UtcMonth, UtcDay, UtcHour, value, UtcSecond)) + if (_timeZoneCollection.TryGetValue(value, out string timeZoneID)) { - return; + _currentTimeZoneDisplayName = value; + _currentTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneID); + _minimumZoneOffsetTimestamp = TimestampToolHelper.TimeZone.SafeMinValue(_currentTimeZone) + .ToUnixTimeSeconds(); + _maximumZoneOffsetTimestamp = TimestampToolHelper.TimeZone.SafeMaxValue(_currentTimeZone) + .ToUnixTimeSeconds(); + UpdateZoneOffsetTimestamp(_timestamp); } - ResetLocalDateTime(); - ResetTimestamp(); } } - internal int UtcSecond + /// + /// Gets or sets the Unix time. + /// -62135596800 (0001-01-01T00:00:00Z) to 253402300799 (9999-12-31T23:59:59Z) integer value. + /// + internal double Timestamp { - get => _utcDateTime.Second; + get => _timestamp; set { - if (value < 0) // empty = -2147483648 - { - return; - } - if (!UpdateUtcDateTime(UtcYear, UtcMonth, UtcDay, UtcHour, UtcMinute, value)) - { - return; - } - ResetLocalDateTime(); - ResetTimestamp(); + IsInputInvalid = false; + UpdateZoneOffsetTimestamp(value); } } - internal int LocalYear + /// + /// Gets or sets the year value. + /// + internal int ZoneOffsetYear { - get => _localDateTime.Value.Year; + get => _zoneOffsetDateTime.Year; set { if (value < 1) // empty = -2147483648 { return; } - - if (LocalDay > DateTime.DaysInMonth(value, LocalMonth)) - { - return; - } - if (!UpdateLocalDateTime(value, LocalMonth, LocalDay, LocalHour, LocalMinute, LocalSecond)) + if (!IsValidDateTime(value, ZoneOffsetMonth, ZoneOffsetDay, ZoneOffsetHour, ZoneOffsetMinute, ZoneOffsetSecond)) { + IsInputInvalid = true; return; } - ResetUtcDateTime(); - ResetLocalDateTime(); - ResetTimestamp(); + Timestamp = _utcDateTime.AddYears(value - _zoneOffsetDateTime.Year).ToUnixTimeSeconds(); } } - internal int LocalMonth + /// + /// Gets or sets the month value. + /// + internal int ZoneOffsetMonth { - get => _localDateTime.Value.Month; + get => _zoneOffsetDateTime.Month; set { - if (value < 1) // empty = -2147483648 - { - return; - } - - if (LocalDay > DateTime.DaysInMonth(LocalYear, value)) + if (value < 0) // empty = -2147483648 { return; } - if (!UpdateLocalDateTime(LocalYear, value, LocalDay, LocalHour, LocalMinute, LocalSecond)) + if (!IsValidDateTime(ZoneOffsetYear, value, ZoneOffsetDay, ZoneOffsetHour, ZoneOffsetMinute, ZoneOffsetSecond)) { + IsInputInvalid = true; return; } - ResetUtcDateTime(); - ResetLocalDateTime(); - ResetTimestamp(); + Timestamp = _utcDateTime.AddMonths(value - _zoneOffsetDateTime.Month).ToUnixTimeSeconds(); } } - internal int LocalDay + /// + /// Gets or sets the day value. + /// + internal int ZoneOffsetDay { - get => _localDateTime.Value.Day; + get => _zoneOffsetDateTime.Day; set { - if (value < 1) // empty = -2147483648 - { - return; - } - - if (value > DateTime.DaysInMonth(LocalYear, LocalMonth)) + if (value < 0) // empty = -2147483648 { return; } - if (!UpdateLocalDateTime(LocalYear, LocalMonth, value, LocalHour, LocalMinute, LocalSecond)) + if (!IsValidDateTime(ZoneOffsetYear, ZoneOffsetMonth, value, ZoneOffsetHour, ZoneOffsetMinute, ZoneOffsetSecond)) { + IsInputInvalid = true; return; } - ResetUtcDateTime(); - ResetLocalDateTime(); - ResetTimestamp(); + Timestamp = _utcDateTime.AddDays(value - _zoneOffsetDateTime.Day).ToUnixTimeSeconds(); } } - internal int LocalHour + /// + /// Gets or sets the hour value. + /// + internal int ZoneOffsetHour { - get => _localDateTime.Value.Hour; + get => _zoneOffsetDateTime.Hour; set { - if (value < 0) // empty = -2147483648 + if (value < -1) // empty = -2147483648 { return; } - if (!UpdateLocalDateTime(LocalYear, LocalMonth, LocalDay, value, LocalMinute, LocalSecond)) + if (!IsValidDateTime(ZoneOffsetYear, ZoneOffsetMonth, ZoneOffsetDay, value, ZoneOffsetMinute, ZoneOffsetSecond)) { + IsInputInvalid = true; return; } - ResetUtcDateTime(); - ResetLocalDateTime(); - ResetTimestamp(); + Timestamp = _utcDateTime.AddHours(value - _zoneOffsetDateTime.Hour).ToUnixTimeSeconds(); } } - internal int LocalMinute + /// + /// Gets or sets the minute value. + /// + internal int ZoneOffsetMinute { - get => _localDateTime.Value.Minute; + get => _zoneOffsetDateTime.Minute; set { - if (value < 0) // empty = -2147483648 + if (value < -1) // empty = -2147483648 { return; } - if (!UpdateLocalDateTime(LocalYear, LocalMonth, LocalDay, LocalHour, value, LocalSecond)) + if (!IsValidDateTime(ZoneOffsetYear, ZoneOffsetMonth, ZoneOffsetDay, ZoneOffsetHour, value, ZoneOffsetSecond)) { + IsInputInvalid = true; return; } - ResetUtcDateTime(); - ResetLocalDateTime(); - ResetTimestamp(); + Timestamp = _utcDateTime.AddMinutes(value - _zoneOffsetDateTime.Minute).ToUnixTimeSeconds(); } } - internal int LocalSecond + /// + /// Gets or sets the second value. + /// + internal int ZoneOffsetSecond { - get => _localDateTime.Value.Second; + get => _zoneOffsetDateTime.Second; set { - if (value < 0) // empty = -2147483648 + if (value < -1) // empty = -2147483648 { return; } - if(!UpdateLocalDateTime(LocalYear, LocalMonth, LocalDay, LocalHour, LocalMinute, value)) + if (!IsValidDateTime(ZoneOffsetYear, ZoneOffsetMonth, ZoneOffsetDay, ZoneOffsetHour, ZoneOffsetMinute, value)) { + IsInputInvalid = true; return; } - ResetUtcDateTime(); - ResetLocalDateTime(); - ResetTimestamp(); + Timestamp = _utcDateTime.AddSeconds(value - _zoneOffsetDateTime.Second).ToUnixTimeSeconds(); } } @@ -307,7 +295,8 @@ public TimestampToolViewModel() NowCommand = new RelayCommand(ExecuteNowCommand); // Set to the current epoch time. - Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); + CurrentTimeZoneDisplayName = TimestampToolHelper.ZoneInfo.LocalDisplayName; } #region PasteCommand @@ -369,102 +358,137 @@ private void ExecuteCopyCommand() private void ExecuteNowCommand() { - Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - ResetUtcDateTime(); - ResetLocalDateTime(); + Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); } #endregion - private void ResetUtcDateTime() + private DateTimeOffset TimestampToUtcDateTime(double value) { - try + return DateTimeOffset.FromUnixTimeSeconds((long)value).UtcDateTime; + } + + private void UpdateZoneOffsetTimestamp(double value) + { + if (double.IsNaN(value)) { - _utcDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds((long)Timestamp); - IsInputInvalid = false; + value = 0; } - catch + if (!IsValidTimestamp((long)value)) { IsInputInvalid = true; - } + if (value < 0) + { + value = _minimumZoneOffsetTimestamp; + } + else + { + value = _maximumZoneOffsetTimestamp; + } - OnPropertyChanged(nameof(UtcYear)); - OnPropertyChanged(nameof(UtcMonth)); - OnPropertyChanged(nameof(UtcDay)); - OnPropertyChanged(nameof(UtcHour)); - OnPropertyChanged(nameof(UtcMinute)); - OnPropertyChanged(nameof(UtcSecond)); + } + _timestamp = value; + _utcDateTime = TimestampToUtcDateTime(value); + _zoneOffsetDateTime = TimeZoneInfo.ConvertTime(_utcDateTime, _currentTimeZone); + DSTInfo(); + ResetZoneOffsetTimestamp(); + ResetZoneOffsetDateTime(); } - private void ResetLocalDateTime() + private bool IsValidTimestamp(long value) { - _localDateTime = new Lazy(() => _utcDateTime.ToLocalTime()); - OnPropertyChanged(nameof(LocalYear)); - OnPropertyChanged(nameof(LocalMonth)); - OnPropertyChanged(nameof(LocalDay)); - OnPropertyChanged(nameof(LocalHour)); - OnPropertyChanged(nameof(LocalMinute)); - OnPropertyChanged(nameof(LocalSecond)); + if (value < _minimumZoneOffsetTimestamp || value > _maximumZoneOffsetTimestamp) + { + return false; + } + return true; } - private void ResetTimestamp() + private bool IsValidDateTime(int Year, int Month, int Day, int Hour, int Minute, int Second) { - _timestamp = new DateTimeOffset(_utcDateTime).ToUnixTimeSeconds(); - OnPropertyChanged(nameof(Timestamp)); - } + if (Year is < 1 or > 9999) + { + return false; + } - private bool UpdateUtcDateTime(int UtcYear, int UtcMonth, int UtcDay, int UtcHour, int UtcMinute, int UtcSecond) - { + DateTimeOffset calcDateTime = TimeZoneInfo.ConvertTime(_zoneOffsetDateTime, TimeZoneInfo.Utc); + calcDateTime = calcDateTime.AddYears(1970 - calcDateTime.Year); try { - var utcDateTime = new DateTime(UtcYear, UtcMonth, UtcDay, UtcHour, UtcMinute, UtcSecond, DateTimeKind.Utc); - long timestamp = new DateTimeOffset(utcDateTime).ToUnixTimeSeconds(); - // TODO: UTC 9999/12/31 23:59:59 + LocalTime(+0030...) -> invalid LocalTime - // TODO: UTC 0001/01/01 00:00:00 + LocalTime(-0030...) -> invalid LocalTime - /* - if (timestamp is < (-62135596800) or > 253402300799) - { - IsInputInvalid = true; - return false; - } - */ - DateTime localDateTime = utcDateTime.ToLocalTime(); - _utcDateTime = utcDateTime; - IsInputInvalid = false; + calcDateTime = calcDateTime.AddMonths(Month - _zoneOffsetDateTime.Month); + calcDateTime = calcDateTime.AddDays(Day - _zoneOffsetDateTime.Day); + calcDateTime = calcDateTime.AddHours(Hour - _zoneOffsetDateTime.Hour); + calcDateTime = calcDateTime.AddMinutes(Minute - _zoneOffsetDateTime.Minute); + calcDateTime = calcDateTime.AddSeconds(Second - _zoneOffsetDateTime.Second); } catch { - IsInputInvalid = true; return false; } - return true; - } - - private bool UpdateLocalDateTime(int LocalYear, int LocalMonth, int LocalDay, int LocalHour, int LocalMinute, int LocalSecond) - { - try + calcDateTime = TimeZoneInfo.ConvertTime(calcDateTime, _currentTimeZone); + TimeSpan offset = calcDateTime.Offset; + + if (Year + calcDateTime.Year - 1970 is > 1 and < 9999) { - var localDateTime = new DateTime(LocalYear, LocalMonth, LocalDay, LocalHour, LocalMinute, LocalSecond, DateTimeKind.Local); - long timestamp = new DateTimeOffset(localDateTime.ToUniversalTime()).ToUnixTimeSeconds(); - // TODO: UTC 9999/12/31 23:59:59 + LocalTime(+0030...) -> invalid LocalTime - // TODO: UTC 0001/01/01 00:00:00 + LocalTime(-0030...) -> invalid LocalTime - /* - if (timestamp is < (-62135596800) or > 253402300799) + // 0002 ... 9998 + return true; + } + + if (offset.TotalSeconds >= 0) + { + //UTC+ + //example +01:00 Valid -> 0001/01/01 01:00:00 - 9999/12/31 23:59:59 + if (Year == 1) { - IsInputInvalid = true; - return false; + if (calcDateTime.UtcDateTime.Year < 1970) + { + return false; + } + } + else //Year == 9999 + { + if (calcDateTime.Year > 1970) + { + return false; + } } - */ - DateTime utcDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp); - _timestamp = timestamp; - IsInputInvalid = false; } - catch + else { - IsInputInvalid = true; - return false; + //UTC- + //example -01:00 Valid -> 0001/01/01 00:00:00 - 9999/12/31 22:59:59 + if (Year == 1) + { + if (calcDateTime.Year < 1970) + { + return false; + } + } + else //Year == 9999 + { + if (calcDateTime.UtcDateTime.Year > 1970) + { + return false; + } + } } return true; } + + private void ResetZoneOffsetDateTime() + { + OnPropertyChanged(nameof(ZoneOffsetYear)); + OnPropertyChanged(nameof(ZoneOffsetMonth)); + OnPropertyChanged(nameof(ZoneOffsetDay)); + OnPropertyChanged(nameof(ZoneOffsetHour)); + OnPropertyChanged(nameof(ZoneOffsetMinute)); + OnPropertyChanged(nameof(ZoneOffsetSecond)); + } + + private void ResetZoneOffsetTimestamp() + { + OnPropertyChanged(nameof(Timestamp)); + } + } } diff --git a/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml b/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml index d01aa51e2e..545f621989 100644 --- a/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml +++ b/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml @@ -9,26 +9,183 @@ mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="600"> - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + IsClosable="False" /> + - + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VerticalAlignment="Bottom" /> + - - + + + - - - - - - - - - - - - - - - - + + Minimum="{x:Bind local:TimestampToolPage.MinimumTimestamp}" + Maximum="{x:Bind local:TimestampToolPage.MaximumTimestamp}" + AutomationProperties.LabeledBy="{Binding ElementName=TimestampHeaderTextBlock}" /> - - - - + + - - - - + + + + + + + + + - - - - + + + + + + + - - - - + + + + - - - - - + + + + - - - - - - - - - - + + + + - - - - + + + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + - - - - + diff --git a/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml.cs b/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml.cs index 065ff501a2..b59db5a3ce 100644 --- a/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml.cs +++ b/src/dev/impl/DevToys/Views/Tools/Converters/Timestamp/TimestampToolPage.xaml.cs @@ -12,8 +12,8 @@ namespace DevToys.Views.Tools.Timestamp { public sealed partial class TimestampToolPage : Page { - public readonly static double MinimumTimestamp = (double)long.MinValue; - public readonly static double MaximumTimestamp = (double)long.MaxValue; + public static readonly double MinimumTimestamp = -62135596800; + public static readonly double MaximumTimestamp = 253402300799; public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( @@ -48,15 +48,16 @@ protected override void OnNavigatedTo(NavigationEventArgs e) DataContext = ViewModel; } + // Smart detection if (!string.IsNullOrWhiteSpace(parameters.ClipBoardContent)) { if (long.TryParse(parameters.ClipBoardContent, out long timestamp)) { ViewModel.Timestamp = timestamp; } - else if (DateTime.TryParse(parameters.ClipBoardContent, out DateTime localDateTime)) + else if (DateTimeOffset.TryParse(parameters.ClipBoardContent, out DateTimeOffset clipboardDateTime)) { - ViewModel.Timestamp = new DateTimeOffset(localDateTime.ToUniversalTime()).ToUnixTimeSeconds(); + ViewModel.Timestamp = clipboardDateTime.ToUnixTimeSeconds(); } }