diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..ef2b31c --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 57a1574..aa27072 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,12 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt + +# Libraries +lib/* + +# Local output +java.txt +joda.txt +nodatime.txt +zdump.txt diff --git a/.project b/.project new file mode 100644 index 0000000..4891e98 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + tzvalidate + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/csharp/TzValidate/MungeZdump/App.config b/csharp/TzValidate/MungeZdump/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/csharp/TzValidate/MungeZdump/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/csharp/TzValidate/MungeZdump/MungeZdump.csproj b/csharp/TzValidate/MungeZdump/MungeZdump.csproj new file mode 100644 index 0000000..37eb416 --- /dev/null +++ b/csharp/TzValidate/MungeZdump/MungeZdump.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {496D98F8-7E4D-4B9B-8C82-8DA643FA5FC0} + Exe + Properties + MungeZdump + MungeZdump + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NodaTime.1.3.1\lib\net35-Client\NodaTime.dll + True + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/TzValidate/MungeZdump/Program.cs b/csharp/TzValidate/MungeZdump/Program.cs new file mode 100644 index 0000000..6b3ffb1 --- /dev/null +++ b/csharp/TzValidate/MungeZdump/Program.cs @@ -0,0 +1,129 @@ +// Copyright 2015 Jon Skeet. All rights reserved. +// Use of this source code is governed by the Apache License 2.0, +// as found in the LICENSE.txt file. +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using NodaTime; +using NodaTime.Text; +using NodaTime.TimeZones; + +namespace MungeZdump +{ + + /// + /// Application to run zdump on each of the zones listed in a NodaTime nzd file, + /// + class Program + { + private static readonly Instant Start = Instant.FromUtc(1905, 1, 1, 0, 0); + private static readonly Instant End = Instant.FromUtc(2035, 1, 1, 0, 0); + private static readonly IPattern InstantPattern = NodaTime.Text.InstantPattern.GeneralPattern; + private static readonly IPattern OffsetPattern = NodaTime.Text.OffsetPattern.CreateWithInvariantCulture("l"); + private static readonly IPattern LocalPattern = LocalDateTimePattern.CreateWithInvariantCulture("ddd MMM d HH:mm:ss yyyy"); + // Sample line: + // Europe/Paris Sun Oct 27 00:59:59 2019 UTC = Sun Oct 27 02:59:59 2019 CEST isdst=1 + private static readonly Regex LineRegex = new Regex(@"^[^ ]* (?.*) UTC = (?.*\d\d\d\d) (?[^ ]*) isdst=(?.)$"); + + static void Main(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine("Usage: NodaDump "); + Console.WriteLine("The source may be a local nzd file, or a URL, e.g. http://nodatime.org/tzdb/tzdb2015e.nzd"); + return; + } + var sourceData = LoadSource(args[0]); + var source = TzdbDateTimeZoneSource.FromStream(new MemoryStream(sourceData)); + var provider = new DateTimeZoneCache(source); + + // Ids is already sorted + foreach (var id in provider.Ids) + { + DumpZone(id, args[1]); + Console.Write("\r\n"); + } + } + + private static void DumpZone(string id, string zdump) + { + Console.Write("{0}\r\n", id); + // There's a before/on pair for each transition. We only want the "on" line. + // Additionally, skip the first pair which just gives the earliest known transition. + var lines = RunZdump(id, zdump).Where((value, index) => index % 2 == 1).Skip(1).ToList(); + bool writtenAnything = false; + foreach (var line in lines) + { + var match = LineRegex.Match(line); + if (!match.Success) + { + throw new Exception("Invalid line: " + line); + } + var utc = LocalPattern.Parse(match.Groups["utc"].Value.Replace(" ", " ")).Value; + var local = LocalPattern.Parse(match.Groups["local"].Value.Replace(" ", " ")).Value; + var name = match.Groups["name"].Value; + var isStandard = match.Groups["isdst"].Value == "0"; + var offsetInMillis = Period.Between(utc, local, PeriodUnits.Milliseconds).Milliseconds; + var offset = Offset.FromMilliseconds((int) offsetInMillis); + + var utcInstant = utc.InUtc().ToInstant(); + if (utcInstant <= Start) + { + continue; + } + if (utcInstant >= End) + { + if (!writtenAnything) + { + Console.Write("Fixed: {0} {1}\r\n", + OffsetPattern.Format(offset), + name); + return; + } + return; + } + + writtenAnything = true; + Console.Write("{0} {1} {2} {3}\r\n", + InstantPattern.Format(utcInstant), + OffsetPattern.Format(offset), + isStandard ? "standard" : "daylight", + name); + } + } + + private static List RunZdump(string id, string zdump) + { + var info = new ProcessStartInfo + { + FileName = zdump, + Arguments = "-v " + id, + RedirectStandardOutput = true, + UseShellExecute = false + }; + var process = Process.Start(info); + return process.StandardOutput.ReadToEnd().Split('\n').ToList(); + } + + private static byte[] LoadSource(string source) + { + if (source.StartsWith("http://") || source.StartsWith("https://")) + { + using (var client = new WebClient()) + { + return client.DownloadData(source); + } + } + else + { + return File.ReadAllBytes(source); + } + } + } +} diff --git a/csharp/TzValidate/MungeZdump/Properties/AssemblyInfo.cs b/csharp/TzValidate/MungeZdump/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..975d2c4 --- /dev/null +++ b/csharp/TzValidate/MungeZdump/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MungeZdump")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MungeZdump")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("496d98f8-7e4d-4b9b-8c82-8da643fa5fc0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/csharp/TzValidate/MungeZdump/packages.config b/csharp/TzValidate/MungeZdump/packages.config new file mode 100644 index 0000000..9944db4 --- /dev/null +++ b/csharp/TzValidate/MungeZdump/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/csharp/TzValidate/NodaDump/App.config b/csharp/TzValidate/NodaDump/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/csharp/TzValidate/NodaDump/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/csharp/TzValidate/NodaDump/NodaDump.csproj b/csharp/TzValidate/NodaDump/NodaDump.csproj new file mode 100644 index 0000000..3ed3d3f --- /dev/null +++ b/csharp/TzValidate/NodaDump/NodaDump.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {93F5A54A-3AB7-427A-992B-B2F554BFD8FA} + Exe + Properties + NodaTime.TzValidate + NodaDump + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NodaTime.1.3.1\lib\net35-Client\NodaTime.dll + True + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/TzValidate/NodaDump/Program.cs b/csharp/TzValidate/NodaDump/Program.cs new file mode 100644 index 0000000..3299182 --- /dev/null +++ b/csharp/TzValidate/NodaDump/Program.cs @@ -0,0 +1,87 @@ +// Copyright 2015 Jon Skeet. All rights reserved. +// Use of this source code is governed by the Apache License 2.0, +// as found in the LICENSE.txt file. + +using System; +using System.IO; +using System.Net; +using NodaTime.Text; +using NodaTime.TimeZones; + +namespace NodaTime.TzValidate.NodaDump +{ + /// + /// Dump program for Noda Time. + /// + internal static class Program + { + private static readonly Instant Start = Instant.FromUtc(1905, 1, 1, 0, 0); + private static readonly Instant End = Instant.FromUtc(2035, 1, 1, 0, 0); + private static readonly IPattern InstantPattern = NodaTime.Text.InstantPattern.GeneralPattern; + private static readonly IPattern OffsetPattern = NodaTime.Text.OffsetPattern.CreateWithInvariantCulture("l"); + + static void Main(string[] args) + { + if (args.Length < 1 || args.Length > 2) + { + Console.WriteLine("Usage: NodaDump [zone]"); + Console.WriteLine("The source may be a local nzd file, or a URL, e.g. http://nodatime.org/tzdb/tzdb2015e.nzd"); + return; + } + var sourceData = LoadSource(args[0]); + var source = TzdbDateTimeZoneSource.FromStream(new MemoryStream(sourceData)); + var provider = new DateTimeZoneCache(source); + + if (args.Length == 2) + { + DumpZone(provider[args[1]]); + } + else + { + // Ids is already sorted + foreach (var id in provider.Ids) + { + DumpZone(provider[id]); + Console.Write("\r\n"); + } + } + } + + private static void DumpZone(DateTimeZone zone) + { + Console.Write("{0}\r\n", zone.Id); + var zoneInterval = zone.GetZoneInterval(Start); + if (zoneInterval.End >= End) + { + Console.Write("Fixed: {0} {1}\r\n", + OffsetPattern.Format(zoneInterval.WallOffset), + zoneInterval.Name); + return; + } + while (zoneInterval.End < End) + { + zoneInterval = zone.GetZoneInterval(zoneInterval.End); + Console.Write("{0} {1} {2} {3}\r\n", + InstantPattern.Format(zoneInterval.Start), + OffsetPattern.Format(zoneInterval.WallOffset), + zoneInterval.Savings == Offset.Zero ? "standard" : "daylight", + zoneInterval.Name); + } + } + + private static byte[] LoadSource(string source) + { + if (source.StartsWith("http://") || source.StartsWith("https://")) + { + using (var client = new WebClient()) + { + return client.DownloadData(source); + } + } + else + { + return File.ReadAllBytes(source); + } + } + } +} diff --git a/csharp/TzValidate/NodaDump/Properties/AssemblyInfo.cs b/csharp/TzValidate/NodaDump/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2ba545f --- /dev/null +++ b/csharp/TzValidate/NodaDump/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NodaDump")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NodaDump")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("93f5a54a-3ab7-427a-992b-b2f554bfd8fa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/csharp/TzValidate/NodaDump/packages.config b/csharp/TzValidate/NodaDump/packages.config new file mode 100644 index 0000000..9944db4 --- /dev/null +++ b/csharp/TzValidate/NodaDump/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/csharp/TzValidate/TzValidate.sln b/csharp/TzValidate/TzValidate.sln new file mode 100644 index 0000000..3289683 --- /dev/null +++ b/csharp/TzValidate/TzValidate.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.22823.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NodaDump", "NodaDump\NodaDump.csproj", "{93F5A54A-3AB7-427A-992B-B2F554BFD8FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MungeZdump", "MungeZdump\MungeZdump.csproj", "{496D98F8-7E4D-4B9B-8C82-8DA643FA5FC0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {93F5A54A-3AB7-427A-992B-B2F554BFD8FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93F5A54A-3AB7-427A-992B-B2F554BFD8FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93F5A54A-3AB7-427A-992B-B2F554BFD8FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93F5A54A-3AB7-427A-992B-B2F554BFD8FA}.Release|Any CPU.Build.0 = Release|Any CPU + {496D98F8-7E4D-4B9B-8C82-8DA643FA5FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {496D98F8-7E4D-4B9B-8C82-8DA643FA5FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {496D98F8-7E4D-4B9B-8C82-8DA643FA5FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {496D98F8-7E4D-4B9B-8C82-8DA643FA5FC0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/format.md b/format.md new file mode 100644 index 0000000..2d5702c --- /dev/null +++ b/format.md @@ -0,0 +1,91 @@ +The aim of the project is to produce tools for a variety of APIs/platforms, +where each tool will produce output in the same format. If two tools +produce the same output for the same version of time zone data, we +can be reasonably confident that they have interpreted the source +data in the same way - at least for the period of time covered by +the tool. + +Current format +---- + +The zones present are sorted using an ordinal comparison of UTF-16 +code units. (In practice, all zone IDs are currently ASCII, so this +is an ordinal comparison of ASCII values.) All known zones are included, +even if they aliases for other zones. + +The information for each zone consists of: + +- The zone ID (e.g. "London/Europe" +- A line per transition strictly after 1905-01-01T00:00:00Z and + strictly before 2035-01-01T00:00:00Z +- A blank line + +Each transition line consists of: + +- The instant of the transition, in the format `yyyy-MM-ddTHH:mm:ssZ` + (where both `T` and `Z` are literals) +- A space +- The offset from UTC after the transition, in the format "+hh:mm:ss" + (where the + is either '+' or '-', but present in either case; '+' + indicates an offset ahead of UTC, e.g. Eastern Europe) +- A space +- The string "daylight" or "standard" to indicate whether the + period after the transition is in daylight or standard time +- A space +- The abbreviated name of the zone at this time, e.g. "CST" or "CDT" + +Lines are separated by "\r\n" - the Windows default line +separator - until such time as that becomes annoying. (It's +convenient for me, Jon Skeet, as I mostly work on Windows...) + +If there are no transitions for the zone (within the range 1900-2050), a +single line is included below the zone ID, with a format of: + +- The literal "Fixed: " +- The offset, in the format above +- The name of the zone within the period 1900-2050 + +(It is possible that the zone isn't genuinely fixed for all time, of course - +only within the period 1905-2035.) + +Motivation for the format +==== + +- One line per transition makes for simple diffs. (A format of + "zone intervals" of constant name/offset instead of transitions + makes for simpler reading in some ways, but then a change in + transition affects multiple lines.) +- The only variable-width part of the transition format is the name of + the zone after the transition. This allows the data to be effectively + read as a table. +- The transition instant is expressed in UTC and in ISO-8601 extended + format to be as simple as possible to parse in code if desired. +- The range of 1905-2035 is just about within the bounds that zdump supports + +Ideal format +---- + +Currently, there's no indication of the split between "standard +offset" and "daylight savings"; only an indication of whether a +transition is into standard time or daylight time. Daylight savings +comprise 1 hour (forward) in almost all cases, but a few time zones +(e.g. Antarctica/Troll) do not follow this pattern. + +The current restriction is due to the output of `zdump` - which in +turn is due to the output of `zic` not including the amount of +daylight savings. If we regard `zic` as the canonical implementation, +we'd either need a new file format, a clone of the `zic` code, or +a modified version of `zic` which optionally wrote out a text dump +file as part of its operation. + +The ideal format would potentially include two offsets instead of an +offset and "standard" or "daylight". The two offsets to represent +could be any pair of: + +- Wall and standard +- Wall and daylight +- Standard and daylight + +The third value can easily be derived from the other two in any +case, but each makes a different use case simple. We could include +all three, at the cost of redundancy/verbosity. diff --git a/java/org/nodatime/tzvalidate/JavaDump.java b/java/org/nodatime/tzvalidate/JavaDump.java new file mode 100644 index 0000000..f0eb5ae --- /dev/null +++ b/java/org/nodatime/tzvalidate/JavaDump.java @@ -0,0 +1,95 @@ +// Copyright 2015 Jon Skeet. All rights reserved. +// Use of this source code is governed by the Apache License 2.0, +// as found in the LICENSE.txt file. + +package org.nodatime.tzvalidate; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.time.zone.ZoneRulesProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public final class JavaDump { + public static void main(String[] args) throws IOException { + if (args.length > 1) { + System.out.println("Usage: JavaDump [zone]"); + return; + } + Set zones = ZoneRulesProvider.getAvailableZoneIds(); + + if (args.length == 1) { + String id = args[0]; + ZoneId zone = ZoneId.of(id); + if (zone == null) { + System.out.println("Zone " + id + " does not exist"); + } else { + dumpZone(zone); + } + } else { + // Sort IDs in ordinal fashion + List ids = new ArrayList(zones); + Collections.sort(ids); // compareTo does ordinal comparisons + for (String id : ids) { + dumpZone(ZoneId.of(id)); + System.out.printf("\r\n"); + } + } + } + + private static final DateTimeFormatter INSTANT_FORMAT = DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US) + .withZone(ZoneOffset.UTC); + private static final DateTimeFormatter ZONE_NAME_FORMAT = DateTimeFormatter + .ofPattern("zzz", Locale.US); + private static final Instant START = ZonedDateTime.of(1905, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant(); + private static final Instant END = ZonedDateTime.of(2035, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant(); + + private static void dumpZone(ZoneId zone) { + System.out.printf("%s\r\n", zone.getId()); + ZoneRules rules = zone.getRules(); + ZoneOffsetTransition firstTransition = rules.nextTransition(START); + if (firstTransition == null || !firstTransition.getInstant().isBefore(END)) { + System.out.printf("Fixed: %s %s\r\n", + printOffset(rules.getOffset(START.plusMillis(1))), + ZONE_NAME_FORMAT.format(START.atZone(zone))); + return; + } + Instant now = firstTransition.getInstant(); + while (now.isBefore(END)) { + ZoneOffsetTransition transition = rules.nextTransition(now); + if (transition == null) { + return; + } + ZoneOffset offset = transition.getOffsetAfter(); + + // TODO: Name. Can't seem to find this... + System.out.printf("%s %s %s %s\r\n", + INSTANT_FORMAT.format(now), + printOffset(offset), + rules.getDaylightSavings(transition.getInstant()).isZero() ? "standard" : "daylight", + ZONE_NAME_FORMAT.format(now.atZone(zone))); + + now = transition.getInstant(); + } + } + + private static String printOffset(ZoneOffset offset) { + long seconds = offset.getTotalSeconds(); + String sign = seconds < 0 ? "-" : "+"; + if (seconds < 0) { + seconds = -seconds; + } + return String.format("%s%02d:%02d:%02d", sign, seconds / 3600, + (seconds / 60) % 60, seconds % 60); + } +} diff --git a/java/org/nodatime/tzvalidate/JodaDump.java b/java/org/nodatime/tzvalidate/JodaDump.java new file mode 100644 index 0000000..50e75e0 --- /dev/null +++ b/java/org/nodatime/tzvalidate/JodaDump.java @@ -0,0 +1,111 @@ +// Copyright 2015 Jon Skeet. All rights reserved. +// Use of this source code is governed by the Apache License 2.0, +// as found in the LICENSE.txt file. + +package org.nodatime.tzvalidate; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.joda.time.*; +import org.joda.time.format.*; +import org.joda.time.tz.*; + +public final class JodaDump { + + private static final String[] KNOWN_FILES = { "africa", "antarctica", + "asia", "australasia", "backward", "etcetera", "europe", + "northamerica", "pacificnew", "southamerica" }; + + public static void main(String[] args) throws IOException { + + if (args.length < 1 || args.length > 2) { + System.out.println("Usage: JodaDump [zone]"); + return; + } + File directory = new File(args[0]); + if (!directory.isDirectory()) { + System.out.println(directory + " is not a directory"); + return; + } + + File[] files = new File[KNOWN_FILES.length]; + for (int i = 0; i < files.length; i++) { + files[i] = new File(directory, KNOWN_FILES[i]); + } + + ZoneInfoCompiler compiler = new ZoneInfoCompiler(); + PrintStream out = System.out; + // Ignore standard output while compiling... + System.setOut(new PrintStream(new ByteArrayOutputStream())); + Map zones = compiler.compile(null, files); + System.setOut(out); + + if (args.length == 2) { + String id = args[1]; + DateTimeZone zone = zones.get(id); + if (zone == null) { + System.out.println("Zone " + id + " does not exist"); + } else { + dumpZone(id, zone); + } + } else { + // Sort IDs in ordinal fashion + List ids = new ArrayList(zones.keySet()); + Collections.sort(ids); // compareTo does ordinal comparisons + for (String id : ids) { + dumpZone(id, zones.get(id)); + System.out.printf("\r\n"); + } + } + } + + private static final DateTimeFormatter INSTANT_FORMAT = DateTimeFormat + .forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") + .withZone(DateTimeZone.UTC); + private static final Instant START = new DateTime(1905, 1, 1, 0, 0, DateTimeZone.UTC).toInstant(); + private static final Instant END = new DateTime(2035, 1, 1, 0, 0, DateTimeZone.UTC).toInstant(); + + private static void dumpZone(String id, DateTimeZone zone) { + System.out.printf("%s\r\n", id); + long now = zone.nextTransition(START.getMillis()); + if (now == START.getMillis() || now >= END.getMillis()) { + System.out.printf("Fixed: %s %s\r\n", + printOffset(zone.getOffset(START.plus(1L))), + zone.getNameKey(START.getMillis() + 1L)); + return; + } + while (now < END.getMillis()) { + int standardOffset = zone.getStandardOffset(now); + int wallOffset = zone.getOffset(now); + + System.out.printf("%s %s %s %s\r\n", + INSTANT_FORMAT.print(now), + printOffset(wallOffset), + standardOffset == wallOffset ? "standard" : "daylight", + zone.getNameKey(now)); + + long next = zone.nextTransition(now); + if (next <= now) { + break; + } + now = next; + } + } + + private static String printOffset(long millis) { + long seconds = Math.abs(millis) / 1000; + if (seconds < 0) { + seconds = -seconds; + } + String sign = millis < 0 ? "-" : "+"; + return String.format("%s%02d:%02d:%02d", sign, seconds / 3600, + (seconds / 60) % 60, seconds % 60); + } +} diff --git a/runall.bat b/runall.bat new file mode 100644 index 0000000..368dd06 --- /dev/null +++ b/runall.bat @@ -0,0 +1,6 @@ +REM Note: This script expects the layout on Jon's laptop, with all +REM libraries already downloaded... + +csharp\TzValidate\NodaDump\bin\Debug\NodaDump.exe ..\NodaTime\www\\tzdb\tzdb2015e.nzd > nodatime.txt +java -cp bin;lib\joda-time-2.8.1.jar org.nodatime.tzvalidate.JodaDump ..\NodaTime\data\tzdb\2015e > joda.txt +java -cp bin org.nodatime.tzvalidate.JavaDump > java.txt \ No newline at end of file