Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>

<PropertyGroup>
<LangVersion>10</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand All @@ -12,7 +12,6 @@

<ItemGroup>
<PackageReference Include="Roslynator.Analyzers" Version="4.12.10" PrivateAssets="All" />
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="All" />
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polyfill" Version="8.9.0" PrivateAssets="All" />
</ItemGroup>

</Project>
4 changes: 0 additions & 4 deletions src/TimeZoneConverter.DataBuilder/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ public static void Main(string[] args)
// Remove the alias for "Etc/Unknown Factory" as it's not a valid zone.
aliases.Remove("Etc/Unknown,Factory");

// Add missing Rails mappings where they make sense
railsMapping.Remove("Arizona,America/Phoenix");
railsMapping.Add("Arizona,America/Phoenix America/Whitehorse");

// Add mappings for ISO country codes that aren't used in CLDR
mapping.Add("Romance Standard Time,EA,Africa/Ceuta");
mapping.Add("GMT Standard Time,IC,Atlantic/Canary");
Expand Down
Binary file modified src/TimeZoneConverter/Data/RailsMapping.csv.gz
Binary file not shown.
40 changes: 36 additions & 4 deletions src/TimeZoneConverter/TZConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static class TZConvert
private static readonly Dictionary<string, IList<string>> InverseRailsMap = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, string> Links = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, TimeZoneInfo> SystemTimeZones;

private static readonly IDictionary<string, IList<string>> IanaTerritoryZones =
new Dictionary<string, IList<string>>(StringComparer.OrdinalIgnoreCase);

Expand Down Expand Up @@ -78,7 +78,7 @@ public static IReadOnlyDictionary<string, IReadOnlyCollection<string>> GetIanaTi
return new ReadOnlyDictionary<string, IReadOnlyCollection<string>>(
IanaTerritoryZones.ToDictionary(
x => x.Key,
x => (IReadOnlyCollection<string>) x.Value
x => (IReadOnlyCollection<string>)x.Value
.OrderBy(zone => zone)
.ToList().AsReadOnly()));
}
Expand All @@ -87,7 +87,7 @@ public static IReadOnlyDictionary<string, IReadOnlyCollection<string>> GetIanaTi
return new ReadOnlyDictionary<string, IReadOnlyCollection<string>>(
IanaTerritoryZones.ToDictionary(
x => x.Key,
x => (IReadOnlyCollection<string>) x.Value
x => (IReadOnlyCollection<string>)x.Value
.Select(zone => TryIanaToWindows(zone, out var winId)
? WindowsToIana(winId, x.Key)
: zone)
Expand Down Expand Up @@ -184,7 +184,7 @@ public static string WindowsToIana(string windowsTimeZoneId, string territoryCod
throw new InvalidTimeZoneException(
$"\"{windowsTimeZoneId}\" was not recognized as a valid Windows time zone ID.");
}

/// <summary>
/// Attempts to convert a Windows time zone ID to an equivalent IANA time zone name.
/// Uses the "golden zone" - the one that is the most prevalent.
Expand Down Expand Up @@ -395,6 +395,23 @@ public static IList<string> IanaToRails(string ianaTimeZoneName)
/// <returns><c>true</c> if successful, <c>false</c> otherwise.</returns>
public static bool TryIanaToRails(string ianaTimeZoneName, out IList<string> railsTimeZoneNames)
{

// in the case of an Etc/GMT+/-n zone, use the Rails fixed-offset zone. For example, `Etc/GMT-6` -> `+06:00` or `Etc/GMT+3` -> `-03:00`
if (ianaTimeZoneName.StartsWith("Etc/GMT", StringComparison.OrdinalIgnoreCase))
{
var offsetPart = ianaTimeZoneName[7..];
if (offsetPart.Length > 2)
{
var sign = offsetPart[0];
if ((sign == '+' || sign == '-') && int.TryParse(offsetPart[1..], out var hours) && hours is >= 0 and <= 14)
{
var offsetString = $"{(sign == '+' ? '-' : '+')}{hours:00}:00"; // note the inverted sign
railsTimeZoneNames = [offsetString];
return true;
}
}
}

// try directly first
if (InverseRailsMap.TryGetValue(ianaTimeZoneName, out railsTimeZoneNames!))
{
Expand All @@ -409,6 +426,14 @@ public static bool TryIanaToRails(string ianaTimeZoneName, out IList<string> rai
return true;
}

// If it's a valid IANA zone, then just return it since Rails will accept it as-is.
if (KnownIanaTimeZoneNames.Contains(ianaTimeZoneName))
{
railsTimeZoneNames = [ianaTimeZoneName];
return true;
}

// not found
railsTimeZoneNames = Array.Empty<string>();
return false;
}
Expand Down Expand Up @@ -531,6 +556,13 @@ public static bool TryWindowsToRails(string windowsTimeZoneId, out IList<string>
public static bool TryWindowsToRails(string windowsTimeZoneId, string territoryCode,
out IList<string> railsTimeZoneNames)
{
// edge case "Dateline Standard Time" -> "International Date Line West"
if (string.Equals(windowsTimeZoneId, "Dateline Standard Time", StringComparison.OrdinalIgnoreCase))
{
railsTimeZoneNames = ["International Date Line West"];
return true;
}

if (TryWindowsToIana(windowsTimeZoneId, territoryCode, out var ianaTimeZoneName) &&
TryIanaToRails(ianaTimeZoneName, out railsTimeZoneNames))
{
Expand Down
1 change: 1 addition & 0 deletions src/TimeZoneConverter/TimeZoneConverter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading