├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── publishnuget.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── TimeZoneMapper.Help └── TimeZoneMapperHelp.shfbproj ├── TimeZoneMapper.Tests ├── TimeZoneMapper.Tests.csproj ├── TimezoneMapTests.cs └── testfiles │ ├── duplicatekey.xml │ ├── nonexisting.xml │ └── testcldr.xml ├── TimeZoneMapper.sln └── TimeZoneMapper ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs └── Resources.resx ├── README.txt ├── ResourceFiles ├── TimezoneMapper.ico ├── TimezoneMapper.png └── windowsZones.xml ├── TZMappers ├── BaseTZMapper.cs ├── CustomTZMapper.cs ├── DefaultValuesTZMapper.cs ├── ITZMapper.cs └── OnlineValuesTZMapper.cs ├── TimeZoneMap.cs └── TimeZoneMapper.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [RobThree] 2 | custom: ["https://paypal.me/robiii"] 3 | -------------------------------------------------------------------------------- /.github/workflows/publishnuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish Nuget Package 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | dotnet-version: [ '8.0.x' ] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup .NET ${{ matrix.dotnet-version }} 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: ${{ matrix.dotnet-version }} 23 | 24 | - name: Setup NuGet 25 | uses: NuGet/setup-nuget@v2 26 | 27 | - name: Restore dependencies 28 | run: dotnet restore 29 | 30 | - name: Build 31 | run: dotnet build -c Release --no-restore /p:Version="${{ github.event.release.tag_name }}" 32 | 33 | - name: Run tests 34 | run: dotnet test -c Release --no-restore --no-build 35 | 36 | - name: Create packages 37 | run: dotnet pack ${{ github.event.repository.name }} -c Release --no-restore --no-build -p:Version="${{ github.event.release.tag_name }}" 38 | 39 | - name: Publish 40 | run: dotnet nuget push **\*.nupkg -s 'https://api.nuget.org/v3/index.json' -k ${{secrets.NUGET_API_KEY}} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push 5 | 6 | jobs: 7 | build: 8 | 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | dotnet-version: [ '8.0.x' ] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Setup .NET ${{ matrix.dotnet-version }} 18 | uses: actions/setup-dotnet@v4 19 | with: 20 | dotnet-version: ${{ matrix.dotnet-version }} 21 | 22 | - name: Setup NuGet 23 | uses: NuGet/setup-nuget@v2 24 | 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | 28 | - name: Run tests 29 | run: dotnet test --no-restore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | 158 | # Documentation files 159 | *.chm 160 | *.shfbproj_* 161 | 162 | # NuGet artifacts 163 | *.nupkg 164 | 165 | # VS files 166 | .vs/ 167 | 168 | # Rider files 169 | .idea/ 170 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2023 Rob Janssen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Logo](https://raw.githubusercontent.com/RobThree/TimeZoneMapper/master/TimeZoneMapper/ResourceFiles/TimezoneMapper.png) TimeZoneMapper 2 | 3 | ![Build Status](https://img.shields.io/github/actions/workflow/status/RobThree/TimeZoneMapper/test.yml?branch=master&style=flat-square) [![Nuget version](https://img.shields.io/nuget/v/TimeZoneMapper.svg?style=flat-square)](https://www.nuget.org/packages/TimeZoneMapper/) 4 | 5 | 6 | Library for mapping \*N\*X TimeZone ID's (e.g. `Europe/Amsterdam`) to .Net's [TimeZoneInfo](http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx) classes. This mapping is one-way since, for example, `Europe/Amsterdam` maps to `W. Europe Standard Time` but `W. Europe Standard Time` could map to `Europe/Stockholm` or `Arctic/Longyearbyen` just as easily. 7 | 8 | The library provides a simple static `TimeZoneMap` object that exposes 3 types of mappers, each described below under [usage](#usage). The project is kept up-to-date with the latest mapping information as much as I can, but TimeZoneMapper can use the latest mapping information available online completely transparently. 9 | 10 | TimeZoneMapper is available as a [NuGet package](https://www.nuget.org/packages/TimeZoneMapper/) and comes with (basic) documentation in the form of a Windows Helpfile (.chm). 11 | 12 | # Usage 13 | 14 | The most basic example is as follows: 15 | ```c# 16 | TimeZoneInfo tzi = TimeZoneMap.DefaultValuesTZMapper.MapTZID("Europe/Amsterdam"); 17 | ```` 18 | 19 | This uses the static TimeZoneMap.DefaultValuesTZMapper to map the string to the specific [TimeZoneInfo](http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx). The DefaultValuesTZMapper object uses a built-in resource containing the mapping information. If you want more up-to-date mapping information, you can use the `OnlineValuesTZMapper`. 20 | ```c# 21 | TimeZoneInfo tzi = TimeZoneMap.OnlineValuesTZMapper.MapTZID("Europe/Amsterdam"); 22 | ```` 23 | 24 | This will retrieve the [information](https://unicode-org.github.io/cldr-staging/charts/) from the [Unicode Consortium](http://unicode.org/)'s latest [CLDR data](https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml). There is a catch though: what if, for some reason, this information is not available (for example an outbound HTTP request is blocked, the data is not available (HTTP status 404 for example) or the data is corrupt (invalid XML for some reason))? Well, simple, we just use the `OnlineWithFallbackValuesTZMapper`! 25 | ```c# 26 | TimeZoneInfo tzi = TimeZoneMap.OnlineWithFallbackValuesTZMapper.MapTZID("Europe/Amsterdam"); 27 | ```` 28 | 29 | This will try to download the CLDR data from the Unicode Consortium and when that, for some reason fails, it uses the built-in values as fallback. 30 | 31 | Note that an HTTP request will be made only once for as long as the TimeZoneMapper is around (usually the lifetime of the application). Also note that the TimeZoneMapper is **case-*in*sensitive**; the TimeZone ID `Europe/Amsterdam` works just as well as `EUROPE/AMSTERDAM` or `eUrOpE/AmStErDaM`. 32 | 33 | Finally, when you want control over the actual CLDR data and where it is stored, how you cache it etc. you can use the `CustomValuesTZMapper`. Be sure to add the `TimeZoneMapper.TZMappers` namespace if you want to use this class. This class' constructor has 3 overloads demonstrated below: 34 | 35 | ```c# 36 | // Overload 1: CustomValuesTZMapper(string, Encoding) 37 | 38 | // Load XML from file 39 | var mapper = new CustomValuesTZMapper("myfile.xml", Encoding.UTF8); 40 | TimeZoneInfo tzi = mapper.MapTZID("Europe/Amsterdam"); 41 | ```` 42 | ```c# 43 | // Overload 2: CustomValuesTZMapper(string) 44 | 45 | // Get XML from database, cache, online resource, file, etc. or, in this case, "hard-coded": 46 | string cldrdata = "..."; 47 | var mapper = new CustomValuesTZMapper(cldrdata); 48 | TimeZoneInfo tzi = mapper.MapTZID("Europe/Amsterdam"); 49 | ```` 50 | ```c# 51 | // Overload 3: CustomValuesTZMapper(Stream) 52 | 53 | // Use a Stream 54 | using (var mystream = new GZipStream(File.OpenRead("myfile.gz"), CompressionMode.Decompress)) 55 | { 56 | var mapper = new CustomValuesTZMapper(mystream); 57 | TimeZoneInfo tzi = mapper.MapTZID("Europe/Amsterdam"); 58 | } 59 | ```` 60 | All you need to do is ensure the data you supply to the CustomValuesTZMapper is valid CLDR data (see [this example](TimeZoneMapper/ResourceFiles/windowsZones.xml)) 61 | 62 | # Future 63 | 64 | I will try to update the built-in resource every now-and-then. 65 | 66 | --- 67 | 68 | [Icon](https://www.deviantart.com/deviantdark/art/Oxygen-Refit-70199755) made by [deviantdark](https://www.deviantart.com/deviantdark), licensed by [Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)](https://creativecommons.org/licenses/by-nc-sa/3.0/) ([Archived page](http://riii.me/jcgob)). 69 | -------------------------------------------------------------------------------- /TimeZoneMapper.Help/TimeZoneMapperHelp.shfbproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | Debug 8 | AnyCPU 9 | 2.0 10 | {d6a78785-3434-4544-a495-b201afbd0964} 11 | 2017.9.26.0 12 | 14 | Documentation 15 | Documentation 16 | Documentation 17 | 18 | ..\Documentation\ 19 | TimeZoneMapper 20 | en-US 21 | http://devcorner.nl 22 | %28C%29 2013 - 2024 Devcorner.nl 23 | rob%40devcorner.nl 24 | Rob Janssen 25 | TimeZoneMapper documentation 26 | True 27 | TimeZoneMapper documentation 28 | False 29 | False 30 | 31 | 32 | 33 | 34 | The TimeZoneMapper namespace provides a simple programming interface for mapping TimeZoneID's (known from many *N*X implementations) to .Net's <see cref="T:System.TimeZoneInfo"/> class. 35 | The TimeZoneMapper.TZMappers namespace provides concrete classes that implement the <see cref="T:TimeZoneMapper.TZMappers.ITZMapper"/> interface as well as the interface itself and a static <see cref="T:TimeZoneMapper.TimeZoneMap"/> class that allows for easy access to these <see cref="T:TimeZoneMapper.TZMappers.ITZMapper"/>s. 36 | 37 | &lt%3bpara&gt%3b 38 | Provides %2aN%2aX TimeZone ID to .NET&#39%3bs TimeZoneInfo mappings. Information is based on &lt%3ba href=&quot%3bhttp://www.unicode.org/cldr/charts/latest/supplemental/zone_tzid.html&quot%3b&gt%3bThe Zone-Tzid table&lt%3b/a&gt%3b and &lt%3ba href=&quot%3bhttp://unicode.org/cldr/data/common/supplemental/windowsZones.xml&quot%3b&gt%3bwindowsZones.xml&lt%3b/a&gt%3b. 39 | &lt%3b/para&gt%3b 40 | &lt%3bpara&gt%3b 41 | &lt%3bstrong&gt%3bCOPYRIGHT AND PERMISSION NOTICE&lt%3b/strong&gt%3b 42 | &lt%3b/para&gt%3b 43 | &lt%3bpara&gt%3b 44 | Copyright &#169%3b 1991-2013 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in &lt%3ba href=&quot%3bhttp://www.unicode.org/copyright.html&quot%3b&gt%3bhttp://www.unicode.org/copyright.html&lt%3b/a&gt%3b. 45 | &lt%3b/para&gt%3b 46 | &lt%3bpara&gt%3b 47 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation %28the &quot%3bData Files&quot%3b%29 or Unicode software and any associated documentation %28the &quot%3bSoftware&quot%3b%29 to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that %28a%29 the above copyright notice%28s%29 and this permission notice appear with all copies of the Data Files or Software, %28b%29 both the above copyright notice%28s%29 and this permission notice appear in associated documentation, and %28c%29 there is clear notice in each modified Data File or in the Software as well as in the documentation associated with the Data File%28s%29 or Software that the data or software has been modified. 48 | &lt%3b/para&gt%3b 49 | &lt%3bpara&gt%3b 50 | THE DATA FILES AND SOFTWARE ARE PROVIDED &quot%3bAS IS&quot%3b, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. 51 | &lt%3b/para&gt%3b 52 | &lt%3bpara&gt%3b 53 | Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. 54 | &lt%3b/para&gt%3b 55 | 56 | 57 | 1.3.1.0 58 | Standard 59 | Blank 60 | VS2013 61 | Guid 62 | AboveNamespaces 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ms.vsipcc+, ms.vsexpresscc+ 73 | Hierarchical 74 | Msdn 75 | True 76 | 2 77 | False 78 | 79 | HtmlHelp1 80 | True 81 | True 82 | False 83 | False 84 | OnlyWarningsAndErrors 85 | 100 86 | v4.8 87 | .NET Core/.NET Standard/.NET 5.0+ 88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | bin\ReleaseWithDocumentation\ 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /TimeZoneMapper.Tests/TimeZoneMapper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | false 6 | Debug;Release;ReleaseWithDocumentation 7 | latest 8 | enable 9 | true 10 | latest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /TimeZoneMapper.Tests/TimezoneMapTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml; 8 | using TimeZoneMapper.TZMappers; 9 | 10 | namespace TimeZoneMapper.Tests; 11 | 12 | [TestClass] 13 | public class TimeZoneMapTests 14 | { 15 | [TestMethod] 16 | public void DefaultValuesMapper_ReturnsCorrectTimeZoneInfo() 17 | { 18 | var mapper = TimeZoneMap.DefaultValuesTZMapper; 19 | var actual = mapper.MapTZID("Europe/Amsterdam"); 20 | var expected = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); 21 | 22 | Assert.AreEqual(expected, actual); 23 | } 24 | 25 | [TestMethod] 26 | public void OnlineValuesMapper_ReturnsCorrectTimeZoneInfo() 27 | { 28 | var mapper = TimeZoneMap.OnlineValuesTZMapper; 29 | var actual = mapper.MapTZID("Europe/Amsterdam"); 30 | var expected = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); 31 | 32 | Assert.AreEqual(expected, actual); 33 | } 34 | 35 | [TestMethod] 36 | public void OnlineWithFallbackValuesMapper_ReturnsCorrectTimeZoneInfo() 37 | { 38 | var mapper = TimeZoneMap.OnlineWithFallbackValuesTZMapper; 39 | var actual = mapper.MapTZID("Europe/Amsterdam"); 40 | var expected = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); 41 | 42 | Assert.AreEqual(expected, actual); 43 | } 44 | 45 | [TestMethod] 46 | public void OnlineWithSpecificFallbackValuesMapper_ReturnsCorrectFallbackMapper() 47 | { 48 | var mapper = TimeZoneMap.CreateOnlineWithSpecificFallbackValuesTZMapper(new Uri("http://example.com/test.xml"), new CustomValuesTZMapper("testfiles/testcldr.xml", Encoding.UTF8)); 49 | Assert.AreEqual("zyx.xyz", mapper.Version); 50 | } 51 | 52 | [TestMethod] 53 | public void DefaultValuesMapper_ReturnsUTCTimeZoneInfo() 54 | { 55 | var mapper = TimeZoneMap.DefaultValuesTZMapper; 56 | var actual = mapper.MapTZID("Etc/GMT"); 57 | Assert.AreEqual(TimeZoneInfo.Utc, actual); 58 | } 59 | 60 | [TestMethod] 61 | public void OnlineValuesMapper_ReturnsUTCTimeZoneInfo() 62 | { 63 | var mapper = TimeZoneMap.OnlineValuesTZMapper; 64 | var actual = mapper.MapTZID("Etc/GMT"); 65 | Assert.AreEqual(TimeZoneInfo.Utc, actual); 66 | } 67 | 68 | [TestMethod] 69 | public void GetAvailableTZIDsContainsAtLeastEtcGMT() 70 | { 71 | Assert.IsTrue(TimeZoneMap.DefaultValuesTZMapper.GetAvailableTZIDs().Contains("Etc/GMT", StringComparer.OrdinalIgnoreCase)); 72 | Assert.IsTrue(TimeZoneMap.OnlineValuesTZMapper.GetAvailableTZIDs().Contains("Etc/GMT", StringComparer.OrdinalIgnoreCase)); 73 | } 74 | 75 | [TestMethod] 76 | public void GetAvailableTimeZonesContainsAtLeastCET() 77 | { 78 | Assert.IsTrue(TimeZoneMap.DefaultValuesTZMapper.GetAvailableTimeZones().Select(t => t.Id).Contains("W. Europe Standard Time", StringComparer.OrdinalIgnoreCase)); 79 | Assert.IsTrue(TimeZoneMap.OnlineValuesTZMapper.GetAvailableTimeZones().Select(t => t.Id).Contains("W. Europe Standard Time", StringComparer.OrdinalIgnoreCase)); 80 | } 81 | 82 | [TestMethod] 83 | public void MapperIsNotCaseSensitive() 84 | { 85 | var mapper = TimeZoneMap.DefaultValuesTZMapper; 86 | var expected = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); 87 | 88 | Assert.AreEqual(expected, mapper.MapTZID("europe/amsterdam")); 89 | Assert.AreEqual(expected, mapper.MapTZID("EUROPE/AMSTERDAM")); 90 | Assert.AreEqual(expected, mapper.MapTZID("Europe/Amsterdam")); 91 | } 92 | 93 | [TestMethod] 94 | public void TryMapTZIDReturnsExpectedValues() 95 | { 96 | var mapper = TimeZoneMap.DefaultValuesTZMapper; 97 | var expected = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); 98 | 99 | Assert.IsTrue(mapper.TryMapTZID("Europe/Amsterdam", out var actual)); 100 | Assert.AreEqual(expected, actual); 101 | 102 | Assert.IsFalse(mapper.TryMapTZID("Foo/Bar", out actual)); 103 | Assert.IsNull(actual); 104 | } 105 | 106 | [TestMethod] 107 | public void CustomTZMapperStringConstructorPassingXML() 108 | { 109 | var mapper = new CustomValuesTZMapper(File.ReadAllText("testfiles/testcldr.xml")); 110 | 111 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("UTC"), mapper.MapTZID("Test/A")); 112 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"), mapper.MapTZID("Test/B")); 113 | } 114 | 115 | [TestMethod] 116 | public void SpaceSeparatedTimeZonesAreParsedCorrectly() 117 | { 118 | var mapper = new CustomValuesTZMapper(File.ReadAllText("testfiles/testcldr.xml")); 119 | 120 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("Alaskan Standard Time"), mapper.MapTZID("America/Anchorage")); 121 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("Alaskan Standard Time"), mapper.MapTZID("America/Juneau")); 122 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("Alaskan Standard Time"), mapper.MapTZID("America/Nome")); 123 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("Alaskan Standard Time"), mapper.MapTZID("America/Sitka")); 124 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("Alaskan Standard Time"), mapper.MapTZID("America/Yakutat")); 125 | } 126 | 127 | [TestMethod] 128 | public void CustomTZMapperStringConstructorPassingPath() 129 | { 130 | var mapper = new CustomValuesTZMapper("testfiles/testcldr.xml", Encoding.UTF8); 131 | 132 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("UTC"), mapper.MapTZID("Test/A")); 133 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"), mapper.MapTZID("Test/B")); 134 | } 135 | 136 | [TestMethod] 137 | public void CustomTZMapperStringConstructorPassingStream() 138 | { 139 | using var stream = File.Open("testfiles/testcldr.xml", FileMode.Open, FileAccess.Read, FileShare.Read); 140 | var mapper = new CustomValuesTZMapper(stream); 141 | 142 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("UTC"), mapper.MapTZID("Test/A")); 143 | Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"), mapper.MapTZID("Test/B")); 144 | } 145 | 146 | [TestMethod] 147 | [ExpectedException(typeof(KeyNotFoundException), "A non-existing TZID should throw")] 148 | public void CustomTZMapperThrowsOnNonExistingTZID() 149 | { 150 | var mapper = new CustomValuesTZMapper("testfiles/testcldr.xml", Encoding.UTF8); 151 | mapper.MapTZID("XXX"); 152 | } 153 | 154 | [TestMethod] 155 | [ExpectedException(typeof(KeyNotFoundException), "A TZID in the CLDR that doesn't map to an existing TimeZone should throw since it shouldn't be in TZMapper")] 156 | public void CustomTZMapperDoesntContainNonExistingTimeZones() 157 | { 158 | var mapper = new CustomValuesTZMapper("testfiles/testcldr.xml", Encoding.UTF8); 159 | mapper.MapTZID("Test/C"); 160 | } 161 | 162 | [TestMethod] 163 | [ExpectedException(typeof(FileNotFoundException), "When a non-existing path is specified the CustomTZMapper should throw")] 164 | public void CustomTZMapperThrowsOnNonExistingFile() => _ = new CustomValuesTZMapper("XXX.xml", Encoding.UTF8); 165 | 166 | [TestMethod] 167 | [ExpectedException(typeof(XmlException), "When invalid XML is specified the CustomTZMapper should throw")] 168 | public void CustomTZMapperThrowsOnInvalidXML() => _ = new CustomValuesTZMapper(""); 169 | 170 | [TestMethod] 171 | [ExpectedException(typeof(InvalidOperationException), "Valid XML, but not valid CLDR data should throw")] 172 | public void CustomTZMapperThrowsOnInvalidCLDR() => _ = new CustomValuesTZMapper(""); 173 | 174 | [TestMethod] 175 | public void CustomTZMapperLoadsEmptyCLDRCorrectly() => _ = new CustomValuesTZMapper(""); 176 | 177 | [TestMethod] 178 | public void VersionAttributesAreParsedCorrectly() 179 | { 180 | var mapper = new CustomValuesTZMapper("testfiles/testcldr.xml", Encoding.UTF8); 181 | 182 | Assert.AreEqual("zyx", mapper.TZIDVersion); 183 | Assert.AreEqual("xyz", mapper.TZVersion); 184 | Assert.AreEqual("zyx.xyz", mapper.Version); 185 | } 186 | 187 | [TestMethod] 188 | [ExpectedException(typeof(KeyNotFoundException), "A non-existing TZID should throw")] 189 | public void DefaultValuesTZMapperThrowsOnNonExistingTZID() => _ = TimeZoneMap.DefaultValuesTZMapper.MapTZID("XXX"); 190 | 191 | [TestMethod] 192 | public void EnsureResourceFileIsValid() => 193 | // We take the ACTUAL resourcefile and load it in the StrictTestMapper which will throw on duplicate 194 | // keys and/or non-existing timezone ID's. This should NOT throw any exceptions. 195 | _ = new StrictTestMapper( 196 | xmldata: File.ReadAllText("../../../../TimeZoneMapper/ResourceFiles/windowsZones.xml"), 197 | throwOnDuplicateKey: true, 198 | throwOnNonExisting: true 199 | ); 200 | 201 | [TestMethod] 202 | [ExpectedException(typeof(TimeZoneNotFoundException))] 203 | public void ConstructorThrowsOnNonExistingTimeZoneIdWhenSpecified() => 204 | // We take a test resource file with a non-existing TimeZoneId (i.e. .Net won't recognize the TimeZoneId) 205 | // which should then throw a TimeZoneNotFoundException. 206 | _ = new StrictTestMapper( 207 | xmldata: File.ReadAllText("testfiles/nonexisting.xml"), 208 | throwOnDuplicateKey: true, 209 | throwOnNonExisting: true 210 | ); 211 | 212 | [TestMethod] 213 | public void ConstructorDoesNotThrowOnNonExistingTimeZoneIdWhenSpecified() => 214 | // We take a test resource file with a non-existing TimeZoneId (i.e. .Net won't recognize the TimeZoneId) 215 | // which should NOT throw a TimeZoneNotFoundException because we set throwOnNonExisting to false. 216 | _ = new StrictTestMapper( 217 | xmldata: File.ReadAllText("testfiles/nonexisting.xml"), 218 | throwOnDuplicateKey: true, 219 | throwOnNonExisting: false 220 | ); 221 | 222 | [TestMethod] 223 | [ExpectedException(typeof(ArgumentException))] 224 | public void ConstructorThrowsOnDuplicateKeyWhenSpecified() => 225 | // We take a test resource file with a duplicate TimeZoneId which should then throw an ArgumentException. 226 | _ = new StrictTestMapper( 227 | xmldata: File.ReadAllText("testfiles/duplicatekey.xml"), 228 | throwOnDuplicateKey: true, 229 | throwOnNonExisting: true 230 | ); 231 | 232 | [TestMethod] 233 | public void ConstructorDoesNotThrowOnDuplicateKeyWhenSpecified() => 234 | // We take a test resource file with a duplicate TimeZoneId which should NOT throw an ArgumentException 235 | // because we set throwOnDuplicateKey to false. 236 | _ = new StrictTestMapper( 237 | xmldata: File.ReadAllText("testfiles/duplicatekey.xml"), 238 | throwOnDuplicateKey: false, 239 | throwOnNonExisting: true 240 | ); 241 | } 242 | 243 | public class StrictTestMapper : BaseTZMapper, ITZMapper 244 | { 245 | public StrictTestMapper(string xmldata, bool throwOnDuplicateKey, bool throwOnNonExisting) 246 | : base(xmldata, throwOnDuplicateKey, throwOnNonExisting) { } 247 | } 248 | -------------------------------------------------------------------------------- /TimeZoneMapper.Tests/testfiles/duplicatekey.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TimeZoneMapper.Tests/testfiles/nonexisting.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /TimeZoneMapper.Tests/testfiles/testcldr.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /TimeZoneMapper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34601.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DC815F93-760E-45F3-9413-BE9A4BEEB01F}" 7 | ProjectSection(SolutionItems) = preProject 8 | LICENSE = LICENSE 9 | README.md = README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimeZoneMapper.Tests", "TimeZoneMapper.Tests\TimeZoneMapper.Tests.csproj", "{7354DF79-574F-4133-96BF-FFB49099E242}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimeZoneMapper", "TimeZoneMapper\TimeZoneMapper.csproj", "{38D70D34-7DDD-45A0-B898-BF3FA82246EB}" 15 | EndProject 16 | Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "TimeZoneMapperHelp", "TimeZoneMapper.Help\TimeZoneMapperHelp.shfbproj", "{D6A78785-3434-4544-A495-B201AFBD0964}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | ReleaseWithDocumentation|Any CPU = ReleaseWithDocumentation|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {7354DF79-574F-4133-96BF-FFB49099E242}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {7354DF79-574F-4133-96BF-FFB49099E242}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {7354DF79-574F-4133-96BF-FFB49099E242}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {7354DF79-574F-4133-96BF-FFB49099E242}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {7354DF79-574F-4133-96BF-FFB49099E242}.ReleaseWithDocumentation|Any CPU.ActiveCfg = ReleaseWithDocumentation|Any CPU 30 | {7354DF79-574F-4133-96BF-FFB49099E242}.ReleaseWithDocumentation|Any CPU.Build.0 = ReleaseWithDocumentation|Any CPU 31 | {38D70D34-7DDD-45A0-B898-BF3FA82246EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {38D70D34-7DDD-45A0-B898-BF3FA82246EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {38D70D34-7DDD-45A0-B898-BF3FA82246EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {38D70D34-7DDD-45A0-B898-BF3FA82246EB}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {38D70D34-7DDD-45A0-B898-BF3FA82246EB}.ReleaseWithDocumentation|Any CPU.ActiveCfg = ReleaseWithDocumentation|Any CPU 36 | {38D70D34-7DDD-45A0-B898-BF3FA82246EB}.ReleaseWithDocumentation|Any CPU.Build.0 = ReleaseWithDocumentation|Any CPU 37 | {D6A78785-3434-4544-A495-B201AFBD0964}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {D6A78785-3434-4544-A495-B201AFBD0964}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {D6A78785-3434-4544-A495-B201AFBD0964}.ReleaseWithDocumentation|Any CPU.ActiveCfg = ReleaseWithDocumentation|Any CPU 40 | {D6A78785-3434-4544-A495-B201AFBD0964}.ReleaseWithDocumentation|Any CPU.Build.0 = ReleaseWithDocumentation|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {799D4255-0A90-40BE-8D54-36C16BF9C4B4} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /TimeZoneMapper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyCulture("")] 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible 12 | // to COM components. If you need to access a type in this assembly from 13 | // COM, set the ComVisible attribute to true on that type. 14 | [assembly: ComVisible(false)] 15 | 16 | // The following GUID is for the ID of the typelib if this project is exposed to COM 17 | [assembly: Guid("59a3c36f-61c0-4c85-8128-4b56f002a713")] 18 | 19 | 20 | [assembly: InternalsVisibleTo("TimeZoneMapper.Tests")] -------------------------------------------------------------------------------- /TimeZoneMapper/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TimeZoneMapper.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TimeZoneMapper.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" ?> 65 | ///<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd"> 66 | ///<!-- 67 | ///Copyright © 1991-2013 Unicode, Inc. 68 | ///CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) 69 | ///For terms of use, see http://www.unicode.org/copyright.html 70 | ///--> 71 | /// 72 | ///<supplementalData> 73 | /// <version number="$Revision$"/> 74 | /// <windowsZones> 75 | /// <mapTimezones otherVersion="7e00600" typeVersion="2017a"> 76 | /// 77 | /// <!-- (UTC-12:00) International Date Line Wes [rest of string was truncated]";. 78 | /// 79 | internal static string windowsZones { 80 | get { 81 | return ResourceManager.GetString("windowsZones", resourceCulture); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /TimeZoneMapper/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\ResourceFiles\windowsZones.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | -------------------------------------------------------------------------------- /TimeZoneMapper/README.txt: -------------------------------------------------------------------------------- 1 |  2 | COPYRIGHT AND PERMISSION NOTICE 3 | 4 | Copyright © 1991-2013 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in 5 | http://www.unicode.org/copyright.html. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any 8 | associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to 9 | deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, 10 | merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data 11 | Files or Software are furnished to do so, provided that (a) the above copyright notice(s) and this permission notice 12 | appear with all copies of the Data Files or Software, (b) both the above copyright notice(s) and this permission notice 13 | appear in associated documentation, and (c) there is clear notice in each modified Data File or in the Software as well 14 | as in the documentation associated with the Data File(s) or Software that the data or software has been modified. 15 | 16 | THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 17 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY 18 | RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY 19 | SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 20 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 21 | PERFORMANCE OF THE DATA FILES OR SOFTWARE. 22 | 23 | Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to 24 | promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the 25 | copyright holder. 26 | 27 | 28 | More info: 29 | Data source : https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml 30 | Information : https://unicode-org.github.io/cldr-staging/charts/ 31 | Icon : https://www.iconfinder.com/icons/23607/stock_timezone_icon#size=32 32 | -------------------------------------------------------------------------------- /TimeZoneMapper/ResourceFiles/TimezoneMapper.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobThree/TimeZoneMapper/1265e41fe7bbecb6e248ea971f1b8ded1a5ee561/TimeZoneMapper/ResourceFiles/TimezoneMapper.ico -------------------------------------------------------------------------------- /TimeZoneMapper/ResourceFiles/TimezoneMapper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobThree/TimeZoneMapper/1265e41fe7bbecb6e248ea971f1b8ded1a5ee561/TimeZoneMapper/ResourceFiles/TimezoneMapper.png -------------------------------------------------------------------------------- /TimeZoneMapper/ResourceFiles/windowsZones.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | -------------------------------------------------------------------------------- /TimeZoneMapper/TZMappers/BaseTZMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml.Linq; 5 | 6 | namespace TimeZoneMapper.TZMappers; 7 | 8 | /// 9 | /// Provides a base class for TimeZoneMapper objects. 10 | /// 11 | public abstract class BaseTZMapper : ITZMapper 12 | { 13 | private readonly Dictionary _mappings; 14 | 15 | /// 16 | /// Gets the TimeZoneID version part of the resource currently in use. 17 | /// 18 | /// This value corresponds to the "typeVersion" attribute of the resource data. 19 | public string? TZIDVersion { get; private set; } 20 | 21 | /// 22 | /// Gets the TimeZoneInfo version part of the resource currently in use. 23 | /// 24 | /// This value corresponds to the "otherVersion" attribute of the resource data. 25 | public string? TZVersion { get; private set; } 26 | 27 | /// 28 | /// Gets the version of the resource currently in use. 29 | /// 30 | /// This value is a composite of ".". 31 | public string Version { get; private set; } 32 | 33 | /// 34 | /// Baseclass for s. 35 | /// 36 | /// 37 | /// 38 | /// When true, an exception will be thrown when the XML data contains duplicate timezones. When false, 39 | /// duplicates are ignored and only the first entry in the XML data will be used. 40 | /// 41 | /// 42 | /// When true, an exception will be thrown when the XML data contains non-existing timezone ID's. When false, 43 | /// non-existing timezone ID's are ignored. 44 | /// 45 | /// 46 | /// Thrown when a timezone is found that cannot be mapped to a windows timezone in the xmldata and 47 | /// is true. 48 | /// 49 | /// 50 | /// Thrown when the time zone identifier was found, but the registry data is corrupted and 51 | /// is true. 52 | /// 53 | /// 54 | /// Thrown when a duplicate timezone is found in the xmldata and is true. 55 | /// 56 | internal BaseTZMapper(string xmldata, bool throwOnDuplicateKey = true, bool throwOnNonExisting = true) 57 | { 58 | var root = XDocument.Parse(xmldata).Descendants("mapTimezones").First(); 59 | _mappings = root.Descendants("mapZone") 60 | .Where(n => !n.Attribute("territory").Value.Equals("001")) 61 | .SelectMany(n => n.Attribute("type").Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries), (n, t) => new { TZID = t, TZ = TryGetTimeZone(n.Attribute("other").Value, throwOnNonExisting) }) 62 | .Where(n => n.TZ != null) //Filter out "not found" TimeZones (only happens when throwOnNonExisting is false) 63 | .OrderBy(n => n.TZID) 64 | .ToDictionarySafe(n => n.TZID, v => v.TZ!, StringComparer.OrdinalIgnoreCase, throwOnDuplicateKey); 65 | 66 | TZIDVersion = root.Attribute("typeVersion").GetSafeValue(); 67 | TZVersion = root.Attribute("otherVersion").GetSafeValue(); 68 | Version = string.Format("{0}.{1}", TZIDVersion, TZVersion); 69 | } 70 | 71 | /// 72 | /// Retrieves a TimeZone by it's Id, handling exceptions and returning null instead for invalid / not found Id's. 73 | /// 74 | /// The time zone identifier, which corresponds to the Id property. 75 | /// Throws an exception when the timezone Id cannot be found. 76 | /// Returns the TimeZone when found, null otherwise. 77 | private static TimeZoneInfo? TryGetTimeZone(string id, bool throwOnNonExisting) 78 | { 79 | if (throwOnNonExisting) 80 | { 81 | return TimeZoneInfo.FindSystemTimeZoneById(id); 82 | } 83 | 84 | try 85 | { 86 | return TimeZoneInfo.FindSystemTimeZoneById(id); 87 | } 88 | catch (TimeZoneNotFoundException) { } 89 | catch (InvalidTimeZoneException) { } 90 | return null; 91 | } 92 | 93 | /// 94 | /// Maps a TimeZone ID (e.g. "Europe/Amsterdam") to a corresponding TimeZoneInfo object. 95 | /// 96 | /// The TimeZone ID (e.g. "Europe/Amsterdam"). 97 | /// Returns a .Net BCL object corresponding to the TimeZone ID. 98 | /// Thrown when the specified TimeZone ID is not found. 99 | /// Thrown when the specified TimeZone ID is null. 100 | public TimeZoneInfo MapTZID(string tzid) 101 | => _mappings[tzid]; 102 | 103 | /// 104 | /// Maps a TimeZone ID (e.g. "Europe/Amsterdam") to a corresponding TimeZoneInfo object. 105 | /// 106 | /// The TimeZone ID (e.g. "Europe/Amsterdam"). 107 | /// 108 | /// When this method returns, contains the value associated with the specified TimeZone ID, if the timezone is 109 | /// found; otherwise, null. 110 | /// 111 | /// true if the contains an element with the specified timezone; otherwise, false. 112 | public bool TryMapTZID(string tzid, out TimeZoneInfo timeZoneInfo) 113 | => _mappings.TryGetValue(tzid, out timeZoneInfo); 114 | 115 | /// 116 | /// Builds an array of available TimeZone ID's and returns these as an array. 117 | /// 118 | /// Returns an array of all available ('known') TimeZone ID's. 119 | public string[] GetAvailableTZIDs() 120 | => _mappings.Keys.ToArray(); 121 | 122 | /// 123 | /// Builds an array of available objects that the mapper can return. 124 | /// 125 | /// Returns an array of available objects that the mapper can return. 126 | public TimeZoneInfo[] GetAvailableTimeZones() 127 | => _mappings.Values.Distinct().ToArray(); 128 | } 129 | 130 | internal static class LinqExtensions 131 | { 132 | public static Dictionary ToDictionarySafe(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer, bool throwOnDuplicateKey) 133 | { 134 | var ret = new Dictionary(comparer ?? EqualityComparer.Default); 135 | foreach (var item in source) 136 | { 137 | var key = keySelector(item); 138 | if (!ret.ContainsKey(key)) 139 | { 140 | ret.Add(key, elementSelector(item)); 141 | } 142 | else 143 | { 144 | // if throwOnDuplicateKey then we throw, if not we disregard any duplicate keys and only store the first we encounter 145 | if (throwOnDuplicateKey) 146 | { 147 | throw new ArgumentException(string.Format("Key '{0}' already exists", key)); 148 | } 149 | } 150 | } 151 | return ret; 152 | } 153 | } 154 | 155 | internal static class XAttributeExtensions 156 | { 157 | public static string? GetSafeValue(this XAttribute att, string? defaultValue = null) 158 | => att == null ? defaultValue : att.Value; 159 | } -------------------------------------------------------------------------------- /TimeZoneMapper/TZMappers/CustomTZMapper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace TimeZoneMapper.TZMappers; 5 | 6 | /// 7 | /// Provides TimeZoneID mapping based on custom values provided by a CLDR xml file. 8 | /// 9 | public sealed class CustomValuesTZMapper : BaseTZMapper, ITZMapper 10 | { 11 | /// 12 | /// Initializes a CustomValuesTZMapper with custom CLDR data in XML format. 13 | /// 14 | /// A string containing an XML document with CLDR data. 15 | /// 16 | /// When true, an exception will be thrown when the XML data contains duplicate timezones. When false, 17 | /// duplicates are ignored and only the first entry in the XML data will be used. 18 | /// 19 | /// 20 | /// When true, an exception will be thrown when the XML data contains non-existing timezone ID's. When false, 21 | /// non-existing timezone ID's are ignored. 22 | /// 23 | public CustomValuesTZMapper(string cldrxml, bool throwOnDuplicateKey = false, bool throwOnNonExisting = false) 24 | : base(cldrxml, throwOnDuplicateKey, throwOnNonExisting) { } 25 | 26 | /// 27 | /// Initializes a CustomValuesTZMapper with custom CLDR data in XML format. 28 | /// 29 | /// Path to an XML file containing CLDR data. 30 | /// The encoding applied to the contents of the file. 31 | /// /// 32 | /// When true, an exception will be thrown when the XML data contains duplicate timezones. When false, 33 | /// duplicates are ignored and only the first entry in the XML data will be used. 34 | /// 35 | /// 36 | /// When true, an exception will be thrown when the XML data contains non-existing timezone ID's. When false, 37 | /// non-existing timezone ID's are ignored. 38 | /// 39 | public CustomValuesTZMapper(string path, Encoding encoding, bool throwOnDuplicateKey = false, bool throwOnNonExisting = false) 40 | : base(File.ReadAllText(path, encoding), throwOnDuplicateKey, throwOnNonExisting) { } 41 | 42 | /// 43 | /// Initializes a CustomValuesTZMapper with custom CLDR data in XML format. 44 | /// 45 | /// The stream to be read. 46 | /// 47 | /// When true, an exception will be thrown when the XML data contains duplicate timezones. When false, 48 | /// duplicates are ignored and only the first entry in the XML data will be used. 49 | /// 50 | /// 51 | /// When true, an exception will be thrown when the XML data contains non-existing timezone ID's. When false, 52 | /// non-existing timezone ID's are ignored. 53 | /// 54 | public CustomValuesTZMapper(Stream cldrstream, bool throwOnDuplicateKey = false, bool throwOnNonExisting = false) 55 | : base(new StreamReader(cldrstream).ReadToEnd(), throwOnDuplicateKey, throwOnNonExisting) { } 56 | } 57 | -------------------------------------------------------------------------------- /TimeZoneMapper/TZMappers/DefaultValuesTZMapper.cs: -------------------------------------------------------------------------------- 1 | namespace TimeZoneMapper.TZMappers; 2 | 3 | /// 4 | /// Provides TimeZoneID mapping based on a built-in ("static") resource. 5 | /// 6 | public sealed class DefaultValuesTZMapper : BaseTZMapper, ITZMapper 7 | { 8 | /// 9 | /// Provides TimeZoneID mapping based on a built-in ("static") resource. 10 | /// 11 | /// 12 | /// When true, an exception will be thrown when the XML data contains duplicate timezones. When false, 13 | /// duplicates are ignored and only the first entry in the XML data will be used. 14 | /// 15 | /// 16 | /// When true, an exception will be thrown when the XML data contains non-existing timezone ID's. When false, 17 | /// non-existing timezone ID's are ignored. 18 | /// 19 | public DefaultValuesTZMapper(bool throwOnDuplicateKey = true, bool throwOnNonExisting = true) 20 | : base(Properties.Resources.windowsZones, throwOnDuplicateKey, throwOnNonExisting) { } 21 | } -------------------------------------------------------------------------------- /TimeZoneMapper/TZMappers/ITZMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TimeZoneMapper.TZMappers; 4 | 5 | /// 6 | /// Provides the base interface for implementation of a TimeZoneMapper class. 7 | /// 8 | public interface ITZMapper 9 | { 10 | /// 11 | /// Returns an array of available objects that the mapper can return. 12 | /// 13 | /// Returns an array of available objects that the mapper can return. 14 | TimeZoneInfo[] GetAvailableTimeZones(); 15 | 16 | /// 17 | /// Returns an array of available TimeZone ID's and returns these as an array. 18 | /// 19 | /// Returns an array of all available ('known') TimeZone ID's. 20 | string[] GetAvailableTZIDs(); 21 | 22 | /// 23 | /// Maps a TimeZone ID (e.g. "Europe/Amsterdam") to a corresponding TimeZoneInfo object. 24 | /// 25 | /// The TimeZone ID (e.g. "Europe/Amsterdam"). 26 | /// Returns a .Net BCL object corresponding to the TimeZone ID. 27 | TimeZoneInfo MapTZID(string tzid); 28 | 29 | /// 30 | /// Maps a TimeZone ID (e.g. "Europe/Amsterdam") to a corresponding TimeZoneInfo object. 31 | /// 32 | /// The TimeZone ID (e.g. "Europe/Amsterdam"). 33 | /// 34 | /// When this method returns, contains the value associated with the specified TimeZone ID, if the timezone is 35 | /// found; otherwise, null. 36 | /// 37 | /// true if the contains an element with the specified timezone; otherwise, false. 38 | bool TryMapTZID(string tzid, out TimeZoneInfo timeZoneInfo); 39 | 40 | /// 41 | /// Gets the TimeZoneID version part of the resource currently in use. 42 | /// 43 | /// This value corresponds to the "typeVersion" attribute of the resource data. 44 | string? TZIDVersion { get; } 45 | 46 | /// 47 | /// Gets the TimeZoneInfo version part of the resource currently in use. 48 | /// 49 | /// This value corresponds to the "otherVersion" attribute of the resource data. 50 | string? TZVersion { get; } 51 | 52 | /// 53 | /// Gets the version of the resource currently in use. 54 | /// 55 | /// This value is a composite of ".". 56 | string Version { get; } 57 | } 58 | -------------------------------------------------------------------------------- /TimeZoneMapper/TZMappers/OnlineValuesTZMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Cache; 5 | 6 | namespace TimeZoneMapper.TZMappers; 7 | 8 | /// 9 | /// Provides TimeZoneID mapping based on a current ("dynamic") resource. 10 | /// 11 | public sealed class OnlineValuesTZMapper : BaseTZMapper, ITZMapper 12 | { 13 | /// 14 | /// Default URL used for 15 | /// 16 | public const string DEFAULTRESOURCEURL = "https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml"; 17 | 18 | /// 19 | /// Default timeout for HTTP requests. 20 | /// 21 | public const int DEFAULTTIMEOUTMS = 5000; 22 | 23 | /// 24 | /// Default cache TTL time. 25 | /// 26 | public static readonly TimeSpan DEFAULTCACHETTL = TimeSpan.FromHours(24); 27 | 28 | /// 29 | /// Initializes a new instance of an with default timeout of 5 seconds and 30 | /// as resourceURL. 31 | /// 32 | /// 33 | /// By default, the data retrieved is cached for the default cache TTL time 34 | /// in the user's temporary folder retrieved from . 35 | /// 36 | public OnlineValuesTZMapper() 37 | : this(TimeSpan.FromMilliseconds(DEFAULTTIMEOUTMS)) { } 38 | 39 | /// 40 | /// Initializes a new instance of an with the specified timeout and 41 | /// as resourceURL. 42 | /// 43 | /// The length of time, in milliseconds, before the request times out. 44 | /// 45 | /// By default, the data retrieved is cached for the default cache TTL time 46 | /// in the user's temporary folder retrieved from . 47 | /// 48 | public OnlineValuesTZMapper(int timeout) 49 | : this(TimeSpan.FromMilliseconds(timeout)) { } 50 | 51 | /// 52 | /// Initializes a new instance of an with the specified timeout and 53 | /// as resourceURL. 54 | /// 55 | /// The length of time before the request times out. 56 | /// 57 | /// By default, the data retrieved is cached for the default cache TTL time 58 | /// in the user's temporary folder retrieved from . 59 | /// 60 | public OnlineValuesTZMapper(TimeSpan timeout) 61 | : this(timeout, DEFAULTRESOURCEURL) { } 62 | 63 | /// 64 | /// Initializes a new instance of an with the specified timeout and 65 | /// resourceURL. 66 | /// 67 | /// The length of time, in milliseconds, before the request times out. 68 | /// The URL to use when retrieving CLDR data. 69 | /// 70 | /// By default, the data retrieved is cached for the default cache TTL time 71 | /// in the user's temporary folder retrieved from . 72 | /// 73 | public OnlineValuesTZMapper(int timeout, string resourceurl) 74 | : this(TimeSpan.FromMilliseconds(timeout), resourceurl) { } 75 | 76 | /// 77 | /// Initializes a new instance of an with the specified timeout and 78 | /// resourceURL. 79 | /// 80 | /// The length of time before the request times out. 81 | /// The URL to use when retrieving CLDR data. 82 | /// 83 | /// By default, the data retrieved is cached for the default cache TTL time 84 | /// in the user's temporary folder retrieved from . 85 | /// 86 | public OnlineValuesTZMapper(TimeSpan timeout, string resourceurl) 87 | : this(timeout, new Uri(resourceurl, UriKind.Absolute)) { } 88 | 89 | /// 90 | /// Initializes a new instance of an with the specified timeout and 91 | /// resourceURI. 92 | /// 93 | /// The length of time, in milliseconds, before the request times out. 94 | /// The URI to use when retrieving CLDR data. 95 | /// 96 | /// By default, the data retrieved is cached for the default cache TTL time 97 | /// in the user's temporary folder retrieved from . 98 | /// 99 | public OnlineValuesTZMapper(int timeout, Uri resourceuri) 100 | : this(TimeSpan.FromMilliseconds(timeout), resourceuri) { } 101 | 102 | /// 103 | /// Initializes a new instance of an with the specified timeout and 104 | /// resourceURI. 105 | /// 106 | /// The length of time before the request times out. 107 | /// The URI to use when retrieving CLDR data. 108 | /// 109 | /// By default, the data retrieved is cached for the default cache TTL time 110 | /// in the user's temporary folder retrieved from . 111 | /// 112 | public OnlineValuesTZMapper(TimeSpan timeout, Uri resourceuri) 113 | : this(timeout, resourceuri, DEFAULTCACHETTL) { } 114 | 115 | /// 116 | /// Initializes a new instance of an with the specified timeout and 117 | /// resourceURI. 118 | /// 119 | /// The length of time, in milliseconds, before the request times out. 120 | /// The URI to use when retrieving CLDR data. 121 | /// 122 | /// Expiry time for downloaded data; unless this TTL has expired a cached version will be used. 123 | /// 124 | /// 125 | /// The default cache directory used is retrieved from . 126 | /// 127 | public OnlineValuesTZMapper(int timeout, Uri resourceuri, TimeSpan cachettl) 128 | : this(TimeSpan.FromMilliseconds(timeout), resourceuri, cachettl) { } 129 | 130 | /// 131 | /// Initializes a new instance of an with the specified timeout and 132 | /// resourceURI. 133 | /// 134 | /// The length of time before the request times out. 135 | /// The URI to use when retrieving CLDR data. 136 | /// 137 | /// Expiry time for downloaded data; unless this TTL has expired a cached version will be used. 138 | /// 139 | /// 140 | /// The default cache directory used is retrieved from . 141 | /// 142 | public OnlineValuesTZMapper(TimeSpan timeout, Uri resourceuri, TimeSpan cachettl) 143 | : this(timeout, resourceuri, cachettl, Path.GetTempPath()) { } 144 | 145 | /// 146 | /// Initializes a new instance of an with the specified timeout and 147 | /// resourceURI. 148 | /// 149 | /// The length of time before the request times out. 150 | /// The URI to use when retrieving CLDR data. 151 | /// 152 | /// Expiry time for downloaded data; unless this TTL has expired a cached version will be used. 153 | /// 154 | /// The directory to use to store a cached version of the data. 155 | public OnlineValuesTZMapper(int timeout, Uri resourceuri, TimeSpan cachettl, string cachedirectory) 156 | : this(TimeSpan.FromMilliseconds(timeout), resourceuri, cachettl, cachedirectory) { } 157 | 158 | /// 159 | /// Initializes a new instance of an with the specified timeout and 160 | /// resourceURI. 161 | /// 162 | /// The length of time, in milliseconds, before the request times out. 163 | /// The URI to use when retrieving CLDR data. 164 | /// 165 | /// Expiry time for downloaded data; unless this TTL has expired a cached version will be used. 166 | /// 167 | /// The directory to use to store a cached version of the data. 168 | /// 169 | /// When true, an exception will be thrown when the XML data contains duplicate timezones. When false, 170 | /// duplicates are ignored and only the first entry in the XML data will be used. 171 | /// 172 | /// 173 | /// When true, an exception will be thrown when the XML data contains non-existing timezone ID's. When false, 174 | /// non-existing timezone ID's are ignored. 175 | /// 176 | public OnlineValuesTZMapper(TimeSpan timeout, Uri resourceuri, TimeSpan cachettl, string cachedirectory, bool throwOnDuplicateKey = false, bool throwOnNonExisting = false) 177 | : base(new TimedWebClient(timeout, cachettl, cachedirectory).RetrieveCachedString(resourceuri), throwOnDuplicateKey, throwOnNonExisting) { } 178 | 179 | /// 180 | /// Simple "wrapper class" providing timeouts. 181 | /// 182 | private class TimedWebClient : WebClient 183 | { 184 | public int Timeout { get; set; } 185 | 186 | public TimeSpan DefaultTTL { get; set; } 187 | 188 | public string CacheDirectory { get; set; } 189 | 190 | public TimedWebClient(TimeSpan timeout, TimeSpan ttl, string cachedirectory) 191 | { 192 | Timeout = (int)Math.Max(0, timeout.TotalMilliseconds); 193 | DefaultTTL = ttl; 194 | CacheDirectory = cachedirectory; 195 | } 196 | 197 | protected override WebRequest GetWebRequest(Uri address) 198 | { 199 | var wr = base.GetWebRequest(address); 200 | wr.Timeout = Timeout; 201 | return wr; 202 | } 203 | 204 | public string RetrieveCachedString(Uri uri) 205 | { 206 | var filename = Path.GetFileName(uri.AbsolutePath); 207 | if (string.IsNullOrEmpty(filename)) 208 | { 209 | filename = "windowsZones.xml"; 210 | } 211 | 212 | var dest = Path.Combine(CacheDirectory, filename); 213 | if (IsFileExpired(dest, DefaultTTL)) 214 | { 215 | CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); 216 | DownloadFile(uri, dest); 217 | } 218 | 219 | using var f = File.OpenRead(dest); 220 | using var fr = new StreamReader(f); 221 | return fr.ReadToEnd(); 222 | } 223 | 224 | private static bool IsFileExpired(string path, TimeSpan ttl) 225 | { 226 | var x = DateTime.UtcNow - new FileInfo(path).LastWriteTimeUtc; 227 | return !File.Exists(path) || x > ttl; 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /TimeZoneMapper/TimeZoneMap.cs: -------------------------------------------------------------------------------- 1 | namespace TimeZoneMapper 2 | { 3 | using System; 4 | using TimeZoneMapper.TZMappers; 5 | 6 | /// 7 | /// Provides easy access to different "built-in" types of s. 8 | /// 9 | /// 10 | /// The static properties and/or methods on this class are mostly convenience methods/properties; if you need 11 | /// more control (such as using a specific uri, cache TTL or timeout value for the 12 | /// or other options not provided by the "built-in" TZMappers/resources 13 | /// returned here) then you will need to instantiate your own instance (and maybe even implement your own 14 | /// ). 15 | /// 16 | public static class TimeZoneMap 17 | { 18 | /// 19 | /// Gets a that uses a built-in (and thus, possibly outdated) resource. 20 | /// 21 | /// 22 | /// 23 | /// The mappings are based on the built-in data. The specific version of the resource can be retrieved 24 | /// by examining the and (or 25 | /// ) 26 | /// properties. 27 | /// 28 | /// 29 | /// when a mapped zone cannot be found in the current OS. It may occur on outdated Windows versions. 30 | public static ITZMapper DefaultValuesTZMapper { get { return _defaultvaluesmapper.Value; } } 31 | 32 | /// 33 | /// Gets a that uses a online (and thus, possibly 'unreachable') resource. 34 | /// 35 | /// 36 | /// 37 | /// The mappings are retrieved from the online resource a single time (upon first usage) and used from 38 | /// there on. Consider that, between different runs of the application, different values may be 39 | /// returned when the online resource changes. The specific version of the resource can be retrieved by 40 | /// examining the and (or 41 | /// ) 42 | /// properties. 43 | /// 44 | /// 45 | public static ITZMapper OnlineValuesTZMapper { get { return _onlinevaluesmapper.Value; } } 46 | 47 | /// 48 | /// Gets a that tries to use the online resource and, when unreachable or otherwise 49 | /// problematic, uses the built-in resource as fallback. 50 | /// 51 | /// 52 | /// 53 | /// Consider that, between different runs of the application, different values may be returned when the 54 | /// online resource changes or the online resource is out of sync with the current built-in resource 55 | /// and unreachable. 56 | /// 57 | /// 58 | /// 59 | public static ITZMapper OnlineWithFallbackValuesTZMapper { get { return _onlinewithfallbackvaluesmapper.Value; } } 60 | 61 | private readonly static Lazy _defaultvaluesmapper; 62 | private readonly static Lazy _onlinevaluesmapper; 63 | private readonly static Lazy _onlinewithfallbackvaluesmapper; 64 | 65 | static TimeZoneMap() 66 | { 67 | _defaultvaluesmapper = new Lazy(() => new DefaultValuesTZMapper()); 68 | _onlinevaluesmapper = new Lazy(() => new OnlineValuesTZMapper()); 69 | _onlinewithfallbackvaluesmapper = new Lazy(() => { try { return OnlineValuesTZMapper; } catch { return DefaultValuesTZMapper; } }); 70 | } 71 | 72 | /// 73 | /// Creates a that tries to use the online resource and, when unreachable or otherwise 74 | /// problematic, uses the specified as fallback. 75 | /// 76 | /// 77 | /// The to use when the default fails for any 78 | /// reason. 79 | /// 80 | /// 81 | /// Returns the default unless it experiences any trouble; in that case the 82 | /// specified fallback will be returned. 83 | /// 84 | /// 85 | public static ITZMapper CreateOnlineWithSpecificFallbackValuesTZMapper(ITZMapper fallbacktzmapper) 86 | { 87 | try { return OnlineValuesTZMapper; } 88 | catch { return fallbacktzmapper; } 89 | } 90 | 91 | /// 92 | /// Creates a that tries to use the online resource and, when unreachable or otherwise 93 | /// problematic, uses the specified as fallback. 94 | /// 95 | /// The URI to use when retrieving CLDR data. 96 | /// 97 | /// The to use when the default fails for any 98 | /// reason. 99 | /// 100 | /// 101 | /// Returns the default unless it experiences any trouble; in that case the 102 | /// specified fallback will be returned. 103 | /// 104 | /// 105 | public static ITZMapper CreateOnlineWithSpecificFallbackValuesTZMapper(Uri resourceuri, ITZMapper fallbacktzmapper) 106 | { 107 | try { return new OnlineValuesTZMapper(TZMappers.OnlineValuesTZMapper.DEFAULTTIMEOUTMS, resourceuri); } 108 | catch { return fallbacktzmapper; } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /TimeZoneMapper/TimeZoneMapper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | ResourceFiles\TimezoneMapper.ico 6 | False 7 | RobIII 8 | Devcorner.nl 9 | Library for mapping *N*X TimeZone ID's to windows TimeZoneInfo classes. Information is based on Unicode Inc's CLDR data files. 10 | https://github.com/RobThree/TimeZoneMapper 11 | 12 | TimeZoneMapper 13 | TimeZoneMapper 14 | timezone;cldr 15 | Copyright © Devcorner.nl 2013 - 2024 16 | https://github.com/RobThree/TimeZoneMapper 17 | TimezoneMapper.png 18 | Debug;Release;ReleaseWithDocumentation 19 | latest 20 | enable 21 | true 22 | latest 23 | README.md 24 | en 25 | git 26 | LICENSE 27 | 28 | 29 | 30 | bin\Release\net40\TimeZoneMapper.xml 31 | 32 | false 33 | 34 | 35 | 36 | 37 | bin\Release\net40\TimeZoneMapper.xml 38 | 39 | false 40 | 41 | 42 | 43 | 44 | bin\Release\netstandard2.0\TimeZoneMapper.xml 45 | 46 | 47 | 48 | bin\Release\netstandard2.0\TimeZoneMapper.xml 49 | 50 | 51 | 52 | 53 | 54 | True 55 | 56 | 57 | 58 | True 59 | \ 60 | 61 | 62 | True 63 | 64 | 65 | 66 | 67 | 68 | 69 | PreserveNewest 70 | 71 | 72 | 73 | 74 | 75 | True 76 | True 77 | Resources.resx 78 | 79 | 80 | 81 | 82 | 83 | ResXFileCodeGenerator 84 | Resources.Designer.cs 85 | 86 | 87 | 88 | --------------------------------------------------------------------------------