diff --git a/BUILD b/BUILD new file mode 100644 index 00000000..1b730a30 --- /dev/null +++ b/BUILD @@ -0,0 +1,166 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +### libraries + +cc_library( + name = "cctz", + srcs = [ + "src/time_zone_format.cc", + "src/time_zone_if.cc", + "src/time_zone_if.h", + "src/time_zone_impl.cc", + "src/time_zone_impl.h", + "src/time_zone_info.cc", + "src/time_zone_info.h", + "src/time_zone_libc.cc", + "src/time_zone_libc.h", + "src/time_zone_lookup.cc", + "src/time_zone_posix.cc", + "src/time_zone_posix.h", + "src/tzfile.h", + ], + hdrs = [ + "src/civil_time.h", + "src/time_zone.h", + ], + includes = ["src"], + linkopts = [ + "-lm", + "-lpthread", + ], + textual_hdrs = ["src/civil_time_detail.h"], + visibility = ["//visibility:public"], +) + +### tests + +# Builds the Google Test source that was fetched from another repository. +cc_library( + name = "gtest", + srcs = glob( + [ + "google*/src/*.cc", + ], + exclude = glob([ + "google*/src/*-all.cc", + "googlemock/src/gmock_main.cc", + ]), + ), + hdrs = glob(["*/include/**/*.h"]), + includes = [ + "googlemock/", + "googlemock/include", + "googletest/", + "googletest/include", + ], + linkopts = ["-pthread"], + textual_hdrs = ["googletest/src/gtest-internal-inl.h"], + visibility = ["//visibility:public"], +) + +cc_test( + name = "civil_time_test", + size = "small", + srcs = ["src/civil_time_test.cc"], + deps = [ + "@gtest//:gtest", + ":cctz", + ], +) + +cc_test( + name = "time_zone_format_test", + size = "small", + srcs = ["src/time_zone_format_test.cc"], + deps = [ + "@gtest//:gtest", + ":cctz", + ], +) + +cc_test( + name = "time_zone_lookup_test", + size = "small", + srcs = ["src/time_zone_lookup_test.cc"], + deps = [ + "@gtest//:gtest", + ":cctz", + ], +) + +### examples + +cc_binary( + name = "classic", + srcs = ["examples/classic.cc"], +) + +cc_binary( + name = "epoch_shift", + srcs = ["examples/epoch_shift.cc"], + deps = [ + ":cctz", + ], +) + +cc_binary( + name = "example1", + srcs = ["examples/example1.cc"], + deps = [ + ":cctz", + ], +) + +cc_binary( + name = "example2", + srcs = ["examples/example2.cc"], + deps = [ + ":cctz", + ], +) + +cc_binary( + name = "example3", + srcs = ["examples/example3.cc"], + deps = [ + ":cctz", + ], +) + +cc_binary( + name = "example4", + srcs = ["examples/example4.cc"], + deps = [ + ":cctz", + ], +) + +cc_binary( + name = "hello", + srcs = ["examples/hello.cc"], + deps = [ + ":cctz", + ], +) + +### binaries + +cc_binary( + name = "time_tool", + srcs = ["src/time_tool.cc"], + deps = [ + ":cctz", + ], +) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ba85392..94abbcec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,29 @@ -Want to contribute? Great! First, read this page (including the small print at the end). +Want to contribute? Great! First, read this page (including the small print at +the end). ### Before you contribute -Before we can use your code, you must sign the -[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) -(CLA), which you can do online. The CLA is necessary mainly because you own the -copyright to your changes, even after your contribution becomes part of our -codebase, so we need your permission to use and distribute your code. We also -need to be sure of various other things—for instance that you'll tell us if you -know that your code infringes on other people's patents. You don't have to sign -the CLA until after you've submitted your code for review and a member has -approved it, but you must do it before we can put your code into our codebase. -Before you start working on a larger contribution, you should get in touch with -us first through the issue tracker with your idea so that we can help out and -possibly guide you. Coordinating up front makes it much easier to avoid -frustration later on. + +Before we can use your code, you must sign the [Google Individual Contributor +License Agreement] +(https://developers.google.com/open-source/cla/individual?csw=1) (CLA), which +you can do online. The CLA is necessary mainly because you own the copyright to +your changes, even after your contribution becomes part of our codebase, so we +need your permission to use and distribute your code. We also need to be sure of +various other things—for instance that you'll tell us if you know that +your code infringes on other people's patents. You don't have to sign the CLA +until after you've submitted your code for review and a member has approved it, +but you must do it before we can put your code into our codebase. Before you +start working on a larger contribution, you should get in touch with us first +through the issue tracker with your idea so that we can help out and possibly +guide you. Coordinating up front makes it much easier to avoid frustration later +on. ### Code reviews + All submissions, including submissions by project members, require review. We use Github pull requests for this purpose. ### The small print -Contributions made by corporations are covered by a different agreement than -the one above, the Software Grant and Corporate Contributor License Agreement. + +Contributions made by corporations are covered by a different agreement than the +one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ef2498c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,90 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +CC = $(CXX) +OPT = -g +# TEST_FLAGS = +# TEST_LIBS = +CPPFLAGS = -Isrc $(TEST_FLAGS) -Wall -std=c++11 -pthread $(OPT) -fPIC +VPATH = examples:src +LDFLAGS = -pthread +LDLIBS = $(TEST_LIBS) -lm +ARFLAGS = rcs +PREFIX = /usr/local + +CCTZ_LIB = libcctz.a + +CCTZ_HDRS = \ + civil_time.h \ + civil_time_detail.h \ + time_zone.h + +CCTZ_OBJS = \ + time_zone_format.o \ + time_zone_if.o \ + time_zone_impl.o \ + time_zone_info.o \ + time_zone_libc.o \ + time_zone_lookup.o \ + time_zone_posix.o + +# TESTS = civil_time_test time_zone_lookup_test time_zone_format_test +TOOLS = time_tool +EXAMPLES = classic epoch_shift hello example1 example2 example3 example4 + +all: $(TESTS) $(TOOLS) $(EXAMPLES) + +$(TESTS) $(TOOLS) $(EXAMPLES): $(CCTZ_LIB) + +$(CCTZ_LIB): $(CCTZ_OBJS) + $(AR) $(ARFLAGS) $@ $(CCTZ_OBJS) + +install: $(CCTZ_HDRS) $(CCTZ_LIB) + sudo cp -p $(CCTZ_HDRS) $(PREFIX)/include + sudo cp -p $(CCTZ_LIB) $(PREFIX)/lib + +clean: + @$(RM) $(EXAMPLES:=.o) $(EXAMPLES) + @$(RM) $(TOOLS:=.o) $(TOOLS) + @$(RM) $(TESTS:=.o) $(TESTS) + @$(RM) $(CCTZ_OBJS) $(CCTZ_LIB) + +# dependencies + +time_zone_format.o: time_zone.h civil_time.h time_zone_if.h +time_zone_if.o: time_zone_if.h time_zone.h civil_time.h \ + time_zone_info.h time_zone_libc.h tzfile.h +time_zone_impl.o: time_zone_impl.h time_zone.h civil_time.h \ + time_zone_info.h time_zone_if.h tzfile.h +time_zone_info.o: time_zone_info.h time_zone.h civil_time.h \ + time_zone_posix.h time_zone_if.h tzfile.h +time_zone_libc.o: time_zone_libc.h time_zone.h civil_time.h \ + time_zone_if.h +time_zone_lookup.o: time_zone.h civil_time.h \ + time_zone_impl.h time_zone_info.h time_zone_if.h tzfile.h +time_zone_posix.o: time_zone_posix.h + +civil_time_test.o: civil_time.h +time_zone_lookup_test.o: time_zone.h civil_time.h +time_zone_format_test.o: time_zone.h civil_time.h + +time_tool.o: time_zone.h civil_time.h + +hello.o: time_zone.h civil_time.h +example1.o: time_zone.h civil_time.h +example2.o: time_zone.h civil_time.h +example3.o: time_zone.h civil_time.h +example4.o: time_zone.h civil_time.h + +civil_time.h: civil_time_detail.h diff --git a/README.md b/README.md index 6cb126e6..ddcadf4a 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,29 @@ This is not an official Google product. # Overview CCTZ (C++ Time Zone) is a library for translating between absolute times and -civil times (see the [Fundamental Concepts](#fundamental-concepts) section below for an explanation of -these terms) using the rules defined by a time zone. +civil times (see the [Fundamental Concepts](#fundamental-concepts) section below +for an explanation of these terms) using the rules defined by a time zone. -This library currently works on **Linux** and **Mac OS X**, -using the standard IANA time zone data -installed on the system in `/usr/share/zoneinfo`. +This library currently works on **Linux** and **Mac OS X**, using the standard +IANA time zone data installed on the system in `/usr/share/zoneinfo`. CCTZ is built using http://bazel.io and tested using https://github.com/google/googletest # Getting Started -1. Download/install Bazel http://bazel.io/docs/install.html -2. Get the cctz source: `git clone https://github.com/google/cctz.git` then `cd cctz` -3. Build cctz and run the tests: `bazel test ...` -4. See the CCTZ API, which is defined in the header [cctz.h](https://github.com/google/cctz/blob/master/src/cctz.h) -5. Look at the examples in https://github.com/google/cctz/tree/master/examples +1. Download/install Bazel http://bazel.io/docs/install.html +2. Get the cctz source: `git clone https://github.com/google/cctz.git` then `cd + cctz` +3. Build cctz and run the tests: `bazel test :all` +4. See the CCTZ API, which is defined in the header [time_zone.h] + (https://github.com/google/cctz/blob/master/src/time_zone.h) +5. Look at the examples in https://github.com/google/cctz/tree/master/examples # Fundamental Concepts -[ See also the [Time Programming Fundamentals](https://youtu.be/2rnIHsqABfM) talk from CppCon 2015 ([slides available here](http://goo.gl/ofof4N)) ] +[ See also the [Time Programming Fundamentals](https://youtu.be/2rnIHsqABfM) +talk from CppCon 2015 ([slides available here](http://goo.gl/ofof4N)) ] There are two ways to represent time: as an *Absolute Time*, and as a *Civil Time*. An absolute time uniquely and universally represents a specific instant @@ -42,10 +44,10 @@ the values shown on the clock hanging on your wall and the Dilbert calendar on your desk. Your friend living across the country may, at the same moment, have a different civil time showing on their Far Side calendar and clock. For example, if you lived in New York on July 20, 1969 you witnessed Neil Armstrong's small -step at 10:56 in the evening, whereas your friend in San Francisco saw the -same thing at 7:56, and your pen pal in Sydney saw it while eating lunch at -12:56 on July 21. You all would agree on the absolute time of the event, but -you'd disagree about the civil time. +step at 10:56 in the evening, whereas your friend in San Francisco saw the same +thing at 7:56, and your pen pal in Sydney saw it while eating lunch at 12:56 on +July 21. You all would agree on the absolute time of the event, but you'd +disagree about the civil time. Time zones are geo-political regions within which rules are shared to convert between absolute times and civil times. The geographical nature of time zones is @@ -59,8 +61,8 @@ complicated, which is why you should always let a time library do time-zone calculations for you. Time zones define the relationship between absolute and civil times. Given an -absolute or civil time and a time zone, you can compute the other, as shown -in the example below. +absolute or civil time and a time zone, you can compute the other, as shown in +the example below. ``` Civil Time = F(Absolute Time, Time Zone) @@ -76,8 +78,12 @@ relationships will still exist. # These concepts in CCTZ -* An *absolute* time is represented by any `std::chrono::time_point` defined on the `std::chrono::system_clock`. -* A *civil* time is represented by a `cctz::Breakdown`, or even separate integers. -* A *time zone* is represented by a `cctz::TimeZone`. +* An *absolute* time is represented by any `std::chrono::time_point` defined + on the `std::chrono::system_clock`. +* A *civil* time is represented by a `cctz::Breakdown`, or even separate + integers. +* A *time zone* is represented by a `cctz::TimeZone`. -For more information, see the full API and documentation are described in the header [cctz.h](https://github.com/google/cctz/blob/master/src/cctz.h). +For more information, see the full API and documentation are described in the +header [time_zone.h] +(https://github.com/google/cctz/blob/master/src/time_zone.h). diff --git a/WORKSPACE b/WORKSPACE index 03611002..953aa253 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,5 +2,5 @@ new_git_repository( name = "gtest", remote = "https://github.com/google/googletest.git", commit = "de411c3e80120f8dcc2a3f4f62f3ca692c0431d7", - build_file = "test/BUILD", + build_file = "BUILD", ) diff --git a/examples/BUILD b/examples/BUILD deleted file mode 100644 index 31d0ba05..00000000 --- a/examples/BUILD +++ /dev/null @@ -1,52 +0,0 @@ -cc_binary( - name = "classic", - srcs = ["classic.cc"], -) - -cc_binary( - name = "epoch_shift", - srcs = ["epoch_shift.cc"], - deps = [ - "//src:cctz", - ], -) - -cc_binary( - name = "hello", - srcs = ["hello.cc"], - deps = [ - "//src:cctz", - ], -) - -cc_binary( - name = "example1", - srcs = ["example1.cc"], - deps = [ - "//src:cctz", - ], -) - -cc_binary( - name = "example2", - srcs = ["example2.cc"], - deps = [ - "//src:cctz", - ], -) - -cc_binary( - name = "example3", - srcs = ["example3.cc"], - deps = [ - "//src:cctz", - ], -) - -cc_binary( - name = "example4", - srcs = ["example4.cc"], - deps = [ - "//src:cctz", - ], -) diff --git a/examples/classic.cc b/examples/classic.cc index b09b9bd0..986f1f67 100644 --- a/examples/classic.cc +++ b/examples/classic.cc @@ -1,17 +1,16 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include diff --git a/examples/epoch_shift.cc b/examples/epoch_shift.cc index c2c6a576..532df3f1 100644 --- a/examples/epoch_shift.cc +++ b/examples/epoch_shift.cc @@ -1,17 +1,16 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include diff --git a/examples/example1.cc b/examples/example1.cc index 12cc90d8..ac73e3d7 100644 --- a/examples/example1.cc +++ b/examples/example1.cc @@ -1,33 +1,33 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include -#include "src/cctz.h" +#include "civil_time.h" +#include "time_zone.h" int main() { - cctz::TimeZone lax; - LoadTimeZone("America/Los_Angeles", &lax); + cctz::time_zone lax; + load_time_zone("America/Los_Angeles", &lax); // Time Programming Fundamentals @cppcon - const auto tp = cctz::MakeTime(2015, 9, 22, 9, 0, 0, lax); + const auto tp = lax.lookup(cctz::civil_second(2015, 9, 22, 9, 0, 0)).pre; - cctz::TimeZone nyc; - LoadTimeZone("America/New_York", &nyc); + cctz::time_zone nyc; + load_time_zone("America/New_York", &nyc); - std::cout << cctz::Format("Talk starts at %T %z (%Z)\n", tp, lax); - std::cout << cctz::Format("Talk starts at %T %z (%Z)\n", tp, nyc); + std::cout << cctz::format("Talk starts at %T %z (%Z)\n", tp, lax); + std::cout << cctz::format("Talk starts at %T %z (%Z)\n", tp, nyc); } diff --git a/examples/example2.cc b/examples/example2.cc index 57b64c80..6f9196b4 100644 --- a/examples/example2.cc +++ b/examples/example2.cc @@ -1,31 +1,30 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include #include -#include "src/cctz.h" +#include "time_zone.h" int main() { const std::string civil_string = "2015-09-22 09:35:00"; - cctz::TimeZone lax; - LoadTimeZone("America/Los_Angeles", &lax); + cctz::time_zone lax; + load_time_zone("America/Los_Angeles", &lax); std::chrono::system_clock::time_point tp; - const bool ok = cctz::Parse("%Y-%m-%d %H:%M:%S", civil_string, lax, &tp); + const bool ok = cctz::parse("%Y-%m-%d %H:%M:%S", civil_string, lax, &tp); if (!ok) return -1; const auto now = std::chrono::system_clock::now(); diff --git a/examples/example3.cc b/examples/example3.cc index e627583b..7b112e50 100644 --- a/examples/example3.cc +++ b/examples/example3.cc @@ -1,33 +1,35 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include -#include "src/cctz.h" +#include "civil_time.h" +#include "time_zone.h" int main() { - cctz::TimeZone lax; - LoadTimeZone("America/Los_Angeles", &lax); + cctz::time_zone lax; + load_time_zone("America/Los_Angeles", &lax); const auto now = std::chrono::system_clock::now(); - const cctz::Breakdown bd = cctz::BreakTime(now, lax); + const cctz::time_zone::absolute_lookup bd = lax.lookup(now); // First day of month, 6 months from now. - const auto then = cctz::MakeTime(bd.year, bd.month + 6, 1, 0, 0, 0, lax); + const auto then = + lax.lookup(cctz::civil_second(bd.cs.year(), bd.cs.month() + 6, 1, + 0, 0, 0)).pre; - std::cout << cctz::Format("Now: %F %T %z\n", now, lax); - std::cout << cctz::Format("6mo: %F %T %z\n", then, lax); + std::cout << cctz::format("Now: %F %T %z\n", now, lax); + std::cout << cctz::format("6mo: %F %T %z\n", then, lax); } diff --git a/examples/example4.cc b/examples/example4.cc index 39dc59e1..fe4fb846 100644 --- a/examples/example4.cc +++ b/examples/example4.cc @@ -1,37 +1,38 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include -#include "src/cctz.h" +#include "civil_time.h" +#include "time_zone.h" template -cctz::time_point FloorDay(cctz::time_point tp, - cctz::TimeZone tz) { - const cctz::Breakdown bd = cctz::BreakTime(tp, tz); - const cctz::TimeInfo ti = - cctz::MakeTimeInfo(bd.year, bd.month, bd.day, 0, 0, 0, tz); - return ti.kind == cctz::TimeInfo::Kind::SKIPPED ? ti.trans : ti.pre; +cctz::time_point FloorDay(cctz::time_point tp, + cctz::time_zone tz) { + const cctz::time_zone::absolute_lookup bd = tz.lookup(tp); + const cctz::time_zone::civil_lookup ti = + tz.lookup(cctz::civil_second(bd.cs.year(), bd.cs.month(), bd.cs.day(), + 0, 0, 0)); + return ti.kind == cctz::time_zone::civil_lookup::SKIPPED ? ti.trans : ti.pre; } int main() { - cctz::TimeZone lax; - LoadTimeZone("America/Los_Angeles", &lax); + cctz::time_zone lax; + load_time_zone("America/Los_Angeles", &lax); const auto now = std::chrono::system_clock::now(); const auto day = FloorDay(now, lax); - std::cout << cctz::Format("Now: %F %T %z\n", now, lax); - std::cout << cctz::Format("Day: %F %T %z\n", day, lax); + std::cout << cctz::format("Now: %F %T %z\n", now, lax); + std::cout << cctz::format("Day: %F %T %z\n", day, lax); } diff --git a/examples/hello.cc b/examples/hello.cc index b913cef5..0b701982 100644 --- a/examples/hello.cc +++ b/examples/hello.cc @@ -1,37 +1,37 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #include #include #include -#include "src/cctz.h" +#include "civil_time.h" +#include "time_zone.h" int main() { - cctz::TimeZone syd; - if (!cctz::LoadTimeZone("Australia/Sydney", &syd)) return -1; + cctz::time_zone syd; + if (!cctz::load_time_zone("Australia/Sydney", &syd)) return -1; // Neil Armstrong first walks on the moon - const auto tp1 = cctz::MakeTime(1969, 7, 21, 12, 56, 0, syd); + const auto tp1 = syd.lookup(cctz::civil_second(1969, 7, 21, 12, 56, 0)).pre; - const std::string s = cctz::Format("%F %T %z", tp1, syd); + const std::string s = cctz::format("%F %T %z", tp1, syd); std::cout << s << "\n"; - cctz::TimeZone nyc; - cctz::LoadTimeZone("America/New_York", &nyc); + cctz::time_zone nyc; + cctz::load_time_zone("America/New_York", &nyc); - const auto tp2 = cctz::MakeTime(1969, 7, 20, 22, 56, 0, nyc); + const auto tp2 = nyc.lookup(cctz::civil_second(1969, 7, 20, 22, 56, 0)).pre; return tp2 == tp1 ? 0 : 1; } diff --git a/src/BUILD b/src/BUILD deleted file mode 100644 index fd46494c..00000000 --- a/src/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -cc_library( - name = "cctz", - srcs = [ - "cctz_cnv.cc", - "cctz_fmt.cc", - "cctz_if.cc", - "cctz_if.h", - "cctz_impl.cc", - "cctz_impl.h", - "cctz_info.cc", - "cctz_info.h", - "cctz_libc.cc", - "cctz_libc.h", - "cctz_posix.cc", - "cctz_posix.h", - "tzfile.h", - ], - hdrs = ["cctz.h"], - linkopts = [ - "-lm", - "-lpthread", - ], - visibility = ["//visibility:public"], -) diff --git a/src/cctz.h b/src/cctz.h deleted file mode 100644 index d5b073d1..00000000 --- a/src/cctz.h +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// 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. - -// CCTZ is a library for translating between absolute times (represented as -// std::chrono::time_points of the std::chrono::system_clock) and civil times -// (year, month, day, hour, minute, second) using the rules defined by a time -// zone (cctz::TimeZone). -// -// Example: -// -// cctz::TimeZone lax; -// if (cctz::LoadTimeZone("America/Los_Angeles", &lax)) { -// auto tp = cctz::MakeTime(2015, 1, 2, 3, 4, 5, lax); -// cctz::Breakdown bd = cctz::BreakTime(tp, lax); -// // bd.year == 2015 -// // bd.month == 1 -// // ... -// std:string s = cctz::Format("%Y-%m-%d %H:%M:%S %Ez", tp, lax); -// // s == "2015-01-02 03:04:05 -08:00" -// } - -#ifndef CCTZ_H_ -#define CCTZ_H_ - -#include -#include -#include -#include - -namespace cctz { - -// All time point arguments and return values in this library are defined in -// terms of the std::chrono::system_clock. System clock time points of any -// duration are accepted as arguments, and system clock time points containing -// 64-bits of seconds are returned from the MakeTime() and MakeTimeInfo() -// functions. The following aliases are defined as a shorthand, not as new -// concepts. -template -using time_point = std::chrono::time_point; -using seconds64 = std::chrono::duration; - -// cctz::TimeZone is an opaque, small, value-type class representing a -// geo-political region within which particular rules are used for mapping -// between absolute and civil times. TimeZones are named using the TZ -// identifiers from the IANA Time Zone Database, such as "America/Los_Angeles" -// or "Australia/Sydney". TimeZones are created from factory functions such -// as LoadTimeZone(). Note: strings like "PST" and "EDT" are not valid TZ -// identifiers. -// -// Example: -// cctz::TimeZone utc = cctz::UTCTimeZone(); -// cctz::TimeZone loc = cctz::LocalTimeZone(); -// cctz::TimeZone lax; -// if (!cctz::LoadTimeZone("America/Los_Angeles", &lax)) { ... } -// -// See also: -// - http://www.iana.org/time-zones -// - http://en.wikipedia.org/wiki/Zoneinfo -class TimeZone { - public: - // A value type. - TimeZone() = default; // Equivalent to UTC - TimeZone(const TimeZone&) = default; - TimeZone& operator=(const TimeZone&) = default; - - class Impl; - - private: - explicit TimeZone(const Impl* impl) : impl_(impl) {} - const Impl* impl_ = nullptr; -}; - -// Loads the named zone. May perform I/O on the initial load of the named -// zone. If the name is invalid, or some other kind of error occurs, returns -// false and "*tz" is set to the UTC time zone. -bool LoadTimeZone(const std::string& name, TimeZone* tz); -// Convenience method returning the UTC time zone. -TimeZone UTCTimeZone(); -// Convenience method returning the local time zone, or UTC if there is no -// configured local zone. -TimeZone LocalTimeZone(); - -// The calendar and wall-clock (a.k.a. "civil time") components of a -// time_point in a certain TimeZone. A better std::tm. This struct is not -// intended to represent an instant in time. So, rather than passing a -// Breakdown to a function, pass a time_point and a TimeZone. -struct Breakdown { - int64_t year; // year (e.g., 2013) - int month; // month of year [1:12] - int day; // day of month [1:31] - int hour; // hour of day [0:23] - int minute; // minute of hour [0:59] - int second; // second of minute [0:59] - int weekday; // 1==Mon, ..., 7=Sun - int yearday; // day of year [1:366] - - // Note: The following fields exist for backward compatibility with older - // APIs. Accessing these fields directly is a sign of imprudent logic in the - // calling code. Modern time-related code should only access this data - // indirectly by way of cctz::Format(). - int offset; // seconds east of UTC - bool is_dst; // is offset non-standard? - std::string abbr; // time-zone abbreviation (e.g., "PST") -}; - -// Returns the civil time components (and some other data) for the given -// absolute time in the given time zone. Accepts a system_clock time_point of -// any duration. -// -// Example: -// const cctz::TimeZone tz = ... -// const auto tp = std::chrono::system_clock::now(); -// const Breakdown bd = cctz::BreakTime(tp, tz); -template -Breakdown BreakTime(const time_point& tp, const TimeZone& tz); - -// Returns the system_clock time_point with 64-bits of seconds corresponding -// to the given civil time fields in the given TimeZone after normalizing the -// fields. If the given civil time refers to a time that is either skipped or -// repeated (see the TimeInfo doc), then the as-if rule is followed and the -// time_point according to the pre-transition offset is returned. -// -// Example: -// const cctz::TimeZone tz = ... -// const auto tp = cctz::MakeTime(2015, 1, 2, 3, 4, 5, tz); -time_point MakeTime(int64_t year, int mon, int day, - int hour, int min, int sec, - const TimeZone& tz); - -// A TimeInfo represents the conversion of year, month, day, hour, minute, and -// second values, in a particular cctz::TimeZone, to a time instant, as -// returned by MakeTimeInfo(). -// -// It is possible, though, for a caller to try to convert values that do not -// represent an actual or unique instant in time (due to a shift in UTC offset -// in the TimeZone, which results in a discontinuity in the civil-time -// components). For example, a daylight-saving-time transition skips or -// repeats civil times---in the United States, March 13, 2011 02:15 never -// occurred, while November 6, 2011 01:15 occurred twice---so requests for -// such times are not well-defined. -// -// To account for these possibilities, TimeInfo is richer than just a single -// time_point. When the civil time is skipped or repeated, MakeTimeInfo() -// returns times calculated using the pre-transition and post-transition UTC -// offsets, plus the transition time itself. -// -// Example: -// cctz::TimeZone lax; -// if (!cctz::LoadTimeZone("America/Los_Angeles", &lax)) { ... } -// -// // A unique civil time. -// cctz::TimeInfo jan01 = cctz::MakeTimeInfo(2011, 1, 1, 0, 0, 0, lax); -// // jan01.kind == TimeInfo::Kind::UNIQUE -// // jan01.pre is 2011/01/01 00:00:00 -0800 -// // jan01.trans is 2011/01/01 00:00:00 -0800 -// // jan01.post is 2011/01/01 00:00:00 -0800 -// -// // A Spring DST transition, when there is a gap in civil time. -// cctz::TimeInfo mar13 = cctz::MakeTimeInfo(2011, 3, 13, 2, 15, 0, lax); -// // mar13.kind == TimeInfo::Kind::SKIPPED -// // mar13.pre is 2011/03/13 03:15:00 -0700 -// // mar13.trans is 2011/03/13 03:00:00 -0700 -// // mar13.post is 2011/03/13 01:15:00 -0800 -// -// // A Fall DST transition, when civil times are repeated. -// cctz::TimeInfo nov06 = cctz::MakeTimeInfo(2011, 11, 6, 1, 15, 0, lax); -// // nov06.kind == TimeInfo::Kind::REPEATED -// // nov06.pre is 2011/11/06 01:15:00 -0700 -// // nov06.trans is 2011/11/06 01:00:00 -0800 -// // nov06.post is 2011/11/06 01:15:00 -0800 -// -// The input month, day, hour, minute, and second values can also be -// outside of their valid ranges, in which case they will be "normalized" -// during the conversion. -// -// Example: -// // "October 32" normalizes to "November 1". -// cctz::TimeZone tz = ... -// cctz::TimeInfo ti = cctz::MakeTimeInfo(2013, 10, 32, 8, 30, 0, tz); -// // ti.kind == TimeInfo::Kind::UNIQUE && ti.normalized == true -// // ti.pre.In(tz).month == 11 && ti.pre.In(tz).day == 1 -struct TimeInfo { - enum class Kind { - UNIQUE, // the civil time was singular (pre == trans == post) - SKIPPED, // the civil time did not exist - REPEATED, // the civil time was ambiguous - } kind; - time_point pre; // Uses the pre-transition offset - time_point trans; - time_point post; // Uses the post-transition offset - bool normalized; -}; - -// Returns a TimeInfo corresponding to the given civil time fields in -// the given TimeZone after normalizing the fields. NOTE: Prefer calling -// the MakeTime() function unless you know that the default time_point -// that it returns is not what you want. -// -// Example: -// // Calculates the first start of the day, given: -// // int year, month, day; -// const cctz::TimeZone tz = ... -// const auto ti = cctz::MakeTimeInfo(year, month, day, 0, 0, 0, tz); -// const auto day_start = -// (ti.kind == cctz::TimeInfo::Kind::SKIPPED) ? ti.trans : ti.pre; -TimeInfo MakeTimeInfo(int64_t year, int mon, int day, int hour, - int min, int sec, const TimeZone& tz); - -// Formats the given cctz::time_point in the given cctz::TimeZone according to -// the provided format string. Uses strftime()-like formatting options, with -// the following extensions: -// -// - %Ez - RFC3339-compatible numeric time zone (+hh:mm or -hh:mm) -// - %E#S - Seconds with # digits of fractional precision -// - %E*S - Seconds with full fractional precision (a literal '*') -// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999) -// -// Note that %Y produces as many characters as it takes to fully render the -// year. A year outside of [-999:9999] when formatted with %E4Y will produce -// more than four characters, just like %Y. -// -// Format strings should include %Ez so that the result uniquely identifies -// a time instant. -// -// Example: -// cctz::TimeZone lax; -// if (!cctz::LoadTimeZone("America/Los_Angeles", &lax)) { ... } -// auto tp = cctz::MakeTime(2013, 1, 2, 3, 4, 5, lax); -// -// std::string f = cctz::Format("%H:%M:%S", tp, lax); // "03:04:05" -// f = cctz::Format("%H:%M:%E3S", tp, lax); // "03:04:05.000" -template -std::string Format(const std::string& format, const time_point& tp, - const TimeZone& tz); - -// Parses an input string according to the provided format string and returns -// the corresponding cctz::time_point. Uses strftime()-like formatting -// options, with the same extensions as cctz::Format(). -// -// %Y consumes as many numeric characters as it can, so the matching data -// should always be terminated with a non-numeric. %E4Y always consumes -// exactly four characters, including any sign. -// -// Unspecified fields are taken from the default date and time of ... -// -// "1970-01-01 00:00:00.0 +0000" -// -// For example, parsing a string of "15:45" (%H:%M) will return a -// cctz::time_point that represents "1970-01-01 15:45:00.0 +0000". -// Note: Since Parse() returns time instants, it makes the most sense to parse -// fully-specified date/time strings that include a UTC offset (%z/%Ez). -// -// Note also that Parse() only heeds the fields year, month, day, hour, -// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a -// or %A), while parsed for syntactic validity, are ignored in the conversion. -// -// Date and time fields that are out-of-range will be treated as errors rather -// than normalizing them like cctz::MakeTime() does. For example, it is an -// error to parse the date "Oct 32, 2013" because 32 is out of range. -// -// A leap second of ":60" is normalized to ":00" of the following minute with -// fractional seconds discarded. The following table shows how the given -// seconds and subseconds will be parsed: -// -// "59.x" -> 59.x // exact -// "60.x" -> 00.0 // normalized -// "00.x" -> 00.x // exact -// -// Errors are indicated by returning false. -// -// Example: -// const cctz::TimeZone tz = ... -// std::chrono::system_clock::time_point tp; -// if (cctz::Parse("%Y-%m-%d", "2015-10-09", tz, &tp)) { -// ... -// } -template -bool Parse(const std::string& format, const std::string& input, - const TimeZone& tz, time_point* tpp); - -} // namespace cctz - -// -// IMPLEMENTATION DETAILS -// -namespace cctz { - -namespace internal { -// Floors tp to a second boundary and sets *subseconds. -template -inline std::pair, D> -FloorSeconds(const time_point& tp) { - auto sec = std::chrono::time_point_cast(tp); - auto sub = tp - sec; - if (sub.count() < 0) { - sec -= seconds64(1); - sub += seconds64(1); - } - return {sec, std::chrono::duration_cast(sub)}; -} -// Overload for when tp is already second aligned. -inline std::pair, seconds64> -FloorSeconds(const time_point& tp) { - return {tp, seconds64(0)}; -} -} // namespace internal - -Breakdown BreakTime(const time_point&, const TimeZone&); -template -inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { - return BreakTime(internal::FloorSeconds(tp).first, tz); -} - -std::string Format(const std::string&, const time_point&, - const std::chrono::nanoseconds&, const TimeZone&); -template -inline std::string Format(const std::string& format, const time_point& tp, - const TimeZone& tz) { - const auto p = internal::FloorSeconds(tp); - const auto n = std::chrono::duration_cast(p.second); - return Format(format, p.first, n, tz); -} - -bool Parse(const std::string&, const std::string&, const TimeZone&, - time_point*, std::chrono::nanoseconds*); -template -inline bool Parse(const std::string& format, const std::string& input, - const TimeZone& tz, time_point* tpp) { - time_point tp{}; - std::chrono::nanoseconds ns{0}; - const bool b = Parse(format, input, tz, &tp, &ns); - if (b) { - *tpp = std::chrono::time_point_cast(tp); - *tpp += std::chrono::duration_cast(ns); - } - return b; -} - -} // namespace cctz - -#endif // CCTZ_H_ diff --git a/src/cctz_cnv.cc b/src/cctz_cnv.cc deleted file mode 100644 index d725c9fd..00000000 --- a/src/cctz_cnv.cc +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// 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. - -#include "src/cctz.h" - -#include - -#include "src/cctz_impl.h" - -namespace cctz { - -TimeZone UTCTimeZone() { - TimeZone tz; - LoadTimeZone("UTC", &tz); - return tz; -} - -TimeZone LocalTimeZone() { - const char* zone = std::getenv("TZ"); - if (zone != nullptr) { - if (*zone == ':') ++zone; - } else { - zone = "localtime"; - } - TimeZone tz; - if (!LoadTimeZone(zone, &tz)) { - LoadTimeZone("UTC", &tz); - } - return tz; -} - -bool LoadTimeZone(const std::string& name, TimeZone* tz) { - return TimeZone::Impl::LoadTimeZone(name, tz); -} - -Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { - return TimeZone::Impl::get(tz).BreakTime(tp); -} - -time_point MakeTime(int64_t year, int mon, int day, - int hour, int min, int sec, - const TimeZone& tz) { - return MakeTimeInfo(year, mon, day, hour, min, sec, tz).pre; -} - -TimeInfo MakeTimeInfo(int64_t year, int mon, int day, - int hour, int min, int sec, - const TimeZone& tz) { - return TimeZone::Impl::get(tz).MakeTimeInfo(year, mon, day, hour, min, sec); -} - -} // namespace cctz diff --git a/src/cctz_if.h b/src/cctz_if.h deleted file mode 100644 index f7bd97fb..00000000 --- a/src/cctz_if.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// 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. - -#ifndef CCTZ_IF_H_ -#define CCTZ_IF_H_ - -#include -#include - -#include "src/cctz.h" - -namespace cctz { - -// A simple interface used to hide time-zone complexities from TimeZone::Impl. -// Subclasses implement the functions for civil-time conversions in the zone. -class TimeZoneIf { - public: - // A factory function for TimeZoneIf implementations. - static std::unique_ptr Load(const std::string& name); - - virtual ~TimeZoneIf() {} - - virtual Breakdown BreakTime(const time_point& tp) const = 0; - virtual TimeInfo MakeTimeInfo(int64_t year, int mon, int day, - int hour, int min, int sec) const = 0; - - protected: - TimeZoneIf() {} -}; - -// Converts tp to a count of seconds since the Unix epoch. -inline int64_t ToUnixSeconds(const time_point& tp) { - return (tp - std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(0))) - .count(); -} - -// Converts a count of seconds since the Unix epoch to a time_point. -inline time_point FromUnixSeconds(int64_t t) { - return std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(0)) + - seconds64(t); -} - -} // namespace cctz - -#endif // CCTZ_IF_H_ diff --git a/src/cctz_impl.cc b/src/cctz_impl.cc deleted file mode 100644 index 7b572043..00000000 --- a/src/cctz_impl.cc +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// 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. - -#include "src/cctz_impl.h" - -#include -#include - -namespace cctz { - -namespace { - -// TimeZone::Impls are linked into a map to support fast lookup by name. -typedef std::map TimeZoneImplByName; -TimeZoneImplByName* time_zone_map = nullptr; - -// Mutual exclusion for time_zone_map. -std::mutex time_zone_mutex; - -// The UTCTimeZone(). Also used for time zones that fail to load. -const TimeZone::Impl* utc_time_zone = nullptr; - -// utc_time_zone should only be referenced in a thread that has just done -// a LoadUTCTimeZone(). -std::once_flag load_utc_once; -void LoadUTCTimeZone() { - std::call_once(load_utc_once, []() { UTCTimeZone(); }); -} - -} // namespace - -bool TimeZone::Impl::LoadTimeZone(const std::string& name, TimeZone* tz) { - const bool is_utc = (name.compare("UTC") == 0); - - // First check, under a shared lock, whether the time zone has already - // been loaded. This is the common path. TODO: Move to shared_mutex. - { - std::lock_guard lock(time_zone_mutex); - if (time_zone_map != nullptr) { - TimeZoneImplByName::const_iterator itr = time_zone_map->find(name); - if (itr != time_zone_map->end()) { - *tz = TimeZone(itr->second); - return is_utc || itr->second != utc_time_zone; - } - } - } - - if (!is_utc) { - // Ensure that UTC is loaded before any other time zones. - LoadUTCTimeZone(); - } - - // Now check again, under an exclusive lock. - std::lock_guard lock(time_zone_mutex); - if (time_zone_map == nullptr) time_zone_map = new TimeZoneImplByName; - const TimeZone::Impl*& impl = (*time_zone_map)[name]; - bool fallback_utc = false; - if (impl == nullptr) { - // The first thread in loads the new time zone. - TimeZone::Impl* new_impl = new TimeZone::Impl(name); - new_impl->zone_ = TimeZoneIf::Load(new_impl->name_); - if (new_impl->zone_ == nullptr) { - delete new_impl; // free the nascent TimeZone::Impl - impl = utc_time_zone; // and fallback to UTC - fallback_utc = true; - } else { - if (is_utc) { - // Happens before any reference to utc_time_zone. - utc_time_zone = new_impl; - } - impl = new_impl; // install new time zone - } - } - *tz = TimeZone(impl); - return !fallback_utc; -} - -const TimeZone::Impl& TimeZone::Impl::get(const TimeZone& tz) { - if (tz.impl_ == nullptr) { - // Dereferencing an implicit-UTC TimeZone is expected to be - // rare, so we don't mind paying a small synchronization cost. - LoadUTCTimeZone(); - return *utc_time_zone; - } - return *tz.impl_; -} - -TimeZone::Impl::Impl(const std::string& name) : name_(name) {} - -Breakdown TimeZone::Impl::BreakTime(const time_point& tp) const { - return zone_->BreakTime(tp); -} - -TimeInfo TimeZone::Impl::MakeTimeInfo(int64_t year, int mon, int day, - int hour, int min, int sec) const { - return zone_->MakeTimeInfo(year, mon, day, hour, min, sec); -} - -} // namespace cctz diff --git a/src/cctz_impl.h b/src/cctz_impl.h deleted file mode 100644 index 16df494d..00000000 --- a/src/cctz_impl.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// 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. - -#ifndef CCTZ_IMPL_H_ -#define CCTZ_IMPL_H_ - -#include -#include - -#include "src/cctz.h" -#include "src/cctz_info.h" - -namespace cctz { - -// TimeZone::Impl is the internal object referenced by a cctz::TimeZone. -class TimeZone::Impl { - public: - // Load a named time zone. Returns false if the name is invalid, or if - // some other kind of error occurs. Note that loading "UTC" never fails. - static bool LoadTimeZone(const std::string& name, TimeZone* tz); - - // Dereferences the TimeZone to obtain its Impl. - static const TimeZone::Impl& get(const TimeZone& tz); - - // Breaks a time_point down to civil-time components in this time zone. - Breakdown BreakTime(const time_point& tp) const; - - // Converts the civil-time components in this time zone into a time_point. - // That is, the opposite of BreakTime(). The requested civil time may be - // ambiguous or illegal due to a change of UTC offset. - TimeInfo MakeTimeInfo(int64_t year, int mon, int day, - int hour, int min, int sec) const; - - private: - explicit Impl(const std::string& name); - - const std::string name_; - std::unique_ptr zone_; -}; - -} // namespace cctz - -#endif // CCTZ_IMPL_H_ diff --git a/src/civil_time.h b/src/civil_time.h new file mode 100644 index 00000000..03d609b7 --- /dev/null +++ b/src/civil_time.h @@ -0,0 +1,162 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +// A library for computing with civil times (Y-M-D h:m:s) in a +// time-zone-independent manner. These classes may help with rounding, +// iterating, and arithmetic, while avoiding complications like DST. + +#ifndef CCTZ_CIVIL_TIME_H_ +#define CCTZ_CIVIL_TIME_H_ + +#include + +namespace cctz { + +namespace detail { +#include "civil_time_detail.h" +} // namespace detail + +// The types civil_year, civil_month, civil_day, civil_hour, civil_minute, +// and civil_second each implement the concept of a "civil time" that +// is aligned to the boundary of the unit indicated by the type's name. +// A "civil time" is a time-zone-independent representation of the six +// fields---year, month, day, hour, minute, and second---that follow the +// rules of the proleptic Gregorian calendar with exactly 24-hour days, +// 60-minute hours, and 60-second minutes. +// +// Each of these six types implement the same API, so learning to use any +// one of them will translate to all of the others. Some of the following +// examples will use civil_day and civil_month, but any other civil-time +// types may be substituted. +// +// CONSTRUCTION: +// +// All civil-time types allow convenient construction from various sets +// of arguments. Unspecified fields default to their minimum value. +// Specified fields that are out of range are first normalized (e.g., +// Oct 32 is normalized to Nov 1). Specified fields that are smaller +// than the indicated alignment unit are set to their minimum value +// (i.e., 1 for months and days; 0 for hours, minutes, and seconds). +// +// civil_day a(2015, 6, 28); // Construct from Y-M-D +// civil_day b(2015, 6, 28, 9, 9, 9); // H:M:S floored to 00:00:00 +// civil_day c(2015); // Defaults to Jan 1 +// civil_month m(a); // Floors the given day to a month boundary +// +// VALUE SEMANTICS: +// +// All civil-time types are small, value types that should be copied, +// assigned to, and passed to functions by value. +// +// void F(civil_day day); // Accepts by value, not const reference +// civil_day a(2015, 6, 28); +// civil_day b; +// b = a; // Copy +// F(b); // Passed by value +// +// PROPERTIES: +// +// All civil-time types have accessors for all six of the civil-time +// fields: year, month, day, hour, minute, and second. +// +// civil_day a(2015, 6, 28); +// assert(a.year() == 2015); +// assert(a.month() == 6); +// assert(a.day() == 28); +// assert(a.hour() == 0); +// assert(a.minute() == 0); +// assert(a.second() == 0); +// +// ARITHMETIC: +// +// All civil-time types allow natural arithmetic expressions that respect +// the type's indicated alignment. For example, adding 1 to a civil_month +// adds one month, and adding 1 to a civil_day adds one day. +// +// civil_day a(2015, 6, 28); +// ++a; // a = 2015-06-29 (--a is also supported) +// a++; // a = 2015-06-30 (a-- is also supported) +// civil_day b = a + 1; // b = 2015-07-01 (a - 1 is also supported) +// civil_day c = 1 + b; // c = 2015-07-02 +// int n = c - a; // n = 2 (days) +// +// COMPARISON: +// +// All civil-time types may be compared with each other, regardless of +// the type's alignment. Comparison is equivalent to comparing all six +// civil-time fields. +// +// // Iterates all the days of June. +// // (Compares a civil_day with a civil_month) +// for (civil_day day(2015, 6, 1); day < civil_month(2015, 7); ++day) { +// // ... +// } +// +using civil_year = detail::civil_year; +using civil_month = detail::civil_month; +using civil_day = detail::civil_day; +using civil_hour = detail::civil_hour; +using civil_minute = detail::civil_minute; +using civil_second = detail::civil_second; + +// An enum class with members monday, tuesday, wednesday, thursday, +// friday, saturday, and sunday. +using detail::weekday; + +// Returns the weekday for the given civil_day. +// +// civil_day a(2015, 8, 13); +// weekday wd = get_weekday(a); // a == weekday::thursday +// +using detail::get_weekday; + +// Returns the civil_day that strictly follows or precedes the argument, +// and that falls on the given weekday. +// +// For example, given: +// +// August 2015 +// Su Mo Tu We Th Fr Sa +// 1 +// 2 3 4 5 6 7 8 +// 9 10 11 12 13 14 15 +// 16 17 18 19 20 21 22 +// 23 24 25 26 27 28 29 +// 30 31 +// +// civil_day a(2015, 8, 13); // Thursday +// civil_day b = next_weekday(a, weekday::thursday); // b = 2015-08-20 +// civil_day c = prev_weekday(a, weekday::thursday); // c = 2015-08-06 +// +// civil_day d = ... +// // Gets the following Thursday if d is not already Thursday +// civil_day thurs1 = PrevWeekday(d, weekday::thursday) + 7; +// // Gets the previous Thursday if d is not already Thursday +// civil_day thurs2 = NextWeekday(d, weekday::thursday) - 7; +// +using detail::next_weekday; +using detail::prev_weekday; + +// Returns the day-of-year for the given civil_day. +// +// civil_day a(2015, 1, 1); +// int yd_jan_1 = get_yearday(a); // yd_jan_1 = 1 +// civil_day b(2015, 12, 31); +// int yd_dec_31 = get_yearday(b); // yd_dec_31 = 365 +// +using detail::get_yearday; + +} // namespace cctz + +#endif // CCTZ_CIVIL_TIME_H_ diff --git a/src/civil_time_detail.h b/src/civil_time_detail.h new file mode 100644 index 00000000..7b56a1f5 --- /dev/null +++ b/src/civil_time_detail.h @@ -0,0 +1,462 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +// Normalized civil-time fields: Y-M-D HH:MM:SS. +struct fields { + int y; + int m; + int d; + int hh; + int mm; + int ss; +}; + +struct year_tag {}; +struct month_tag {}; +struct day_tag {}; +struct hour_tag {}; +struct minute_tag {}; +struct second_tag {}; + +//////////////////////////////////////////////////////////////////////// + +// Field normalization. + +namespace impl { + +// The month lengths in non-leap and leap years respectively. +constexpr signed char k_dpm[2][12] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, +}; + +// The number of days in the 100 years starting in the mod-400 index year, +// stored as a 36524-deficit value (i.e., 0 == 36524, 1 == 36525). +constexpr signed char k_dpC[400] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; + +// The number of days in the 4 years starting in the mod-400 index year, +// stored as a 1460-deficit value (i.e., 0 == 1460, 1 == 1461). +constexpr signed char k_dp4y[401] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; + +constexpr bool is_leap(int y) { + return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); +} +constexpr int dpm(int y, int m) { + return k_dpm[is_leap(y + (m > 2))][m - 1]; +} +constexpr int year_index(int y, int m) { + return (((y + (m > 2)) % 400) + 400) % 400; +} +constexpr int dpC(int y, int m) { return 36524 + k_dpC[year_index(y, m)]; } +constexpr int dp4y(int y, int m) { return 1460 + k_dp4y[year_index(y, m)]; } +constexpr int dpy(int y, int m) { return is_leap(y + (m > 2)) ? 366 : 365; } + +constexpr fields n_ymn(int y, int m, int d, int n, int hh, int mm, int ss) { + return (d <= n) + ? fields{y, m, d, hh, mm, ss} + : (m == 12) ? n_ymn(y + 1, 1, d - n, dpm(y + 1, 1), hh, mm, ss) + : n_ymn(y, m + 1, d - n, dpm(y, m + 1), hh, mm, ss); +} +constexpr fields n_1yn(int y, int m, int d, int n, int hh, int mm, int ss) { + return (d > n) ? n_1yn(y + 1, m, d - n, dpy(y + 1, m), hh, mm, ss) + : n_ymn(y, m, d, dpm(y, m), hh, mm, ss); +} +constexpr fields n_4yn(int y, int m, int d, int n, int hh, int mm, int ss) { + return (d > n) ? n_4yn(y + 4, m, d - n, dp4y(y + 4, m), hh, mm, ss) + : n_1yn(y, m, d, dpy(y, m), hh, mm, ss); +} + +constexpr fields n_Cn(int y, int m, int d, int n, int hh, int mm, int ss) { + return (d > n) ? n_Cn(y + 100, m, d - n, dpC(y + 100, m), hh, mm, ss) + : n_4yn(y, m, d, dp4y(y, m), hh, mm, ss); +} +constexpr fields n_C(int y, int m, int d, int hh, int mm, int ss) { + return n_Cn(y, m, d, dpC(y, m), hh, mm, ss); +} + +constexpr fields n_C4d2(int y, int m, int d, int hh, int mm, int ss) { + return (d < 0) ? n_C(y - 400, m, d + 146097, hh, mm, ss) + : (d == 0) ? n_C(y - 400, m, 146097, hh, mm, ss) + : n_C(y, m, d, hh, mm, ss); +} +constexpr fields n_C4d(int y, int m, int d, int c, int hh, int mm, int ss) { + return n_C4d2(y + (d / 146097) * 400, m, d % 146097 + c, hh, mm, ss); +} +constexpr fields n_C4c2(int y, int m, int d, int c, int hh, int mm, int ss) { + return (c < 0) ? n_C4d(y - 400, m, d, c + 146097, hh, mm, ss) + : n_C4d(y, m, d, c, hh, mm, ss); +} +constexpr fields n_C4c(int y, int m, int d, int c, int hh, int mm, int ss) { + return n_C4c2(y + (c / 146097) * 400, m, d, c % 146097, hh, mm, ss); +} + +constexpr fields n_m_n(int y, int cy, int m, int d, int cd, int hh, int mm, + int ss) { + return (m < 0) ? n_C4c(y + cy - 1, m + 12, d, cd, hh, mm, ss) + : (m == 0) ? n_C4c(y + cy - 1, 12, d, cd, hh, mm, ss) + : n_C4c(y + cy, m, d, cd, hh, mm, ss); +} +constexpr fields n_m(int y, int m, int d, int hh, int mm, int ss) { + return n_m_n(y, m / 12, m % 12, d, 0, hh, mm, ss); +} +constexpr fields n_m_c(int y, int m, int d, int c, int hh, int mm, int ss) { + return n_m_n(y, m / 12, m % 12, d, c, hh, mm, ss); +} + +constexpr fields n_hh_n(int y, int m, int d, int c, int hh, int mm, int ss) { + return (hh < 0) ? n_m_c(y, m, d, c - 1, hh + 24, mm, ss) + : n_m_c(y, m, d, c, hh, mm, ss); +} +constexpr fields n_hh(int y, int m, int d, int hh, int mm, int ss) { + return n_hh_n(y, m, d, hh / 24, hh % 24, mm, ss); +} +constexpr fields n_hh_c2(int y, int m, int d, int c, int hh, int mm, int ss) { + return n_hh_n(y, m, d, hh / 24 + c, hh % 24, mm, ss); +} +constexpr fields n_hh_c(int y, int m, int d, int hh, int c, int mm, int ss) { + return n_hh_c2(y, m, d, hh / 24 + c / 24, hh % 24 + c % 24, mm, ss); +} + +constexpr fields n_mm_n(int y, int m, int d, int hh, int c, int mm, int ss) { + return (mm < 0) ? n_hh_c(y, m, d, hh, c - 1, mm + 60, ss) + : n_hh_c(y, m, d, hh, c, mm, ss); +} +constexpr fields n_mm(int y, int m, int d, int hh, int mm, int ss) { + return n_mm_n(y, m, d, hh, mm / 60, mm % 60, ss); +} +constexpr fields n_mm_c2(int y, int m, int d, int hh, int c, int mm, int ss) { + return n_mm_n(y, m, d, hh, mm / 60 + c, mm % 60, ss); +} +constexpr fields n_mm_c(int y, int m, int d, int hh, int mm, int c, int ss) { + return n_mm_c2(y, m, d, hh, mm / 60 + c / 60, mm % 60 + c % 60, ss); +} + +constexpr fields n_ss_n(int y, int m, int d, int hh, int mm, int c, int ss) { + return (ss < 0) ? n_mm_c(y, m, d, hh, mm, c - 1, ss + 60) + : n_mm_c(y, m, d, hh, mm, c, ss); +} +constexpr fields n_ss(int y, int m, int d, int hh, int mm, int ss) { + return n_ss_n(y, m, d, hh, mm, ss / 60, ss % 60); +} + +} // namespace impl + +//////////////////////////////////////////////////////////////////////// + +namespace impl { + +// Map a (normalized) Y/M/D to the number of days before/after 1970-01-01. +// Will overflow outside of the range [-5877641-06-23 ... 5881580-07-11]. +inline constexpr int doy(int m, int d) { + return (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; +} +inline constexpr int doe(int yoe, int m, int d) { + return yoe * 365 + yoe / 4 - yoe / 100 + doy(m, d); +} +inline constexpr int era_eymd_ord(int era, int eyear, int m, int d) { + return era * 146097 + doe(eyear - era * 400, m, d) - 719468; +} +inline constexpr int eymd_ord(int eyear, int m, int d) { + return era_eymd_ord((eyear >= 0 ? eyear : eyear - 399) / 400, eyear, m, d); +} +inline constexpr int ymd_ord(int y, int m, int d) { + return eymd_ord(m <= 2 ? y - 1 : y, m, d); +} + +} // namespace impl + +//////////////////////////////////////////////////////////////////////// + +// Aligns the (normalized) fields struct to the indicated field. +inline constexpr fields align(second_tag, fields f) { + return f; +} +inline constexpr fields align(minute_tag, fields f) { + return fields{f.y, f.m, f.d, f.hh, f.mm, 0}; +} +inline constexpr fields align(hour_tag, fields f) { + return fields{f.y, f.m, f.d, f.hh, 0, 0}; +} +inline constexpr fields align(day_tag, fields f) { + return fields{f.y, f.m, f.d, 0, 0, 0}; +} +inline constexpr fields align(month_tag, fields f) { + return fields{f.y, f.m, 1, 0, 0, 0}; +} +inline constexpr fields align(year_tag, fields f) { + return fields{f.y, 1, 1, 0, 0, 0}; +} + +//////////////////////////////////////////////////////////////////////// + +// Increments the indicated (normalized) field by "n". +inline constexpr fields step(second_tag, fields f, int n) { + return impl::n_ss(f.y, f.m, f.d, f.hh, f.mm + n / 60, f.ss + n % 60); +} +inline constexpr fields step(minute_tag, fields f, int n) { + return impl::n_mm(f.y, f.m, f.d, f.hh + n / 60, f.mm + n % 60, f.ss); +} +inline constexpr fields step(hour_tag, fields f, int n) { + return impl::n_hh(f.y, f.m, f.d + n / 24, f.hh + n % 24, f.mm, f.ss); +} +inline constexpr fields step(day_tag, fields f, int n) { + return impl::n_C4c(f.y, f.m, f.d, n, f.hh, f.mm, f.ss); +} +inline constexpr fields step(month_tag, fields f, int n) { + return impl::n_m(f.y + n / 12, f.m + n % 12, f.d, f.hh, f.mm, f.ss); +} +inline constexpr fields step(year_tag, fields f, int n) { + return fields{f.y + n, f.m, f.d, f.hh, f.mm, f.ss}; +} + +//////////////////////////////////////////////////////////////////////// + +// Returns the difference between fields structs using the indicated unit. +constexpr int difference(year_tag, fields f1, fields f2) { + return f1.y - f2.y; +} +constexpr int difference(month_tag, fields f1, fields f2) { + return difference(year_tag{}, f1, f2) * 12 + (f1.m - f2.m); +} +constexpr int difference(day_tag, fields f1, fields f2) { + return impl::ymd_ord(f1.y, f1.m, f1.d) - impl::ymd_ord(f2.y, f2.m, f2.d); +} +constexpr int difference(hour_tag, fields f1, fields f2) { + return difference(day_tag{}, f1, f2) * 24 + (f1.hh - f2.hh); +} +constexpr int difference(minute_tag, fields f1, fields f2) { + return difference(hour_tag{}, f1, f2) * 60 + (f1.mm - f2.mm); +} +constexpr int difference(second_tag, fields f1, fields f2) { + return difference(minute_tag{}, f1, f2) * 60 + (f1.ss - f2.ss); +} + +//////////////////////////////////////////////////////////////////////// + +template +class civil_time { + public: + explicit constexpr civil_time(int y, int m = 1, int d = 1, int hh = 0, + int mm = 0, int ss = 0) + : civil_time(impl::n_ss(y, m, d, hh, mm, ss)) {} + + constexpr civil_time() : civil_time(1970) {} + constexpr civil_time(const civil_time&) = default; + civil_time& operator=(const civil_time&) = default; + + // Explicit conversion between civil times of different alignment. + template + explicit constexpr civil_time(civil_time ct) : civil_time(ct.f_) {} + + // Field accessors. + constexpr int year() const { return f_.y; } + constexpr int month() const { return f_.m; } + constexpr int day() const { return f_.d; } + constexpr int hour() const { return f_.hh; } + constexpr int minute() const { return f_.mm; } + constexpr int second() const { return f_.ss; } + + // Assigning arithmetic. + civil_time& operator+=(int n) { + f_ = step(T{}, f_, n); + return *this; + } + civil_time& operator-=(int n) { + if (n != std::numeric_limits::min()) { + f_ = step(T{}, f_, -n); + } else { + f_ = step(T(), step(T{}, f_, -(n + 1)), 1); + } + return *this; + } + civil_time& operator++() { + return *this += 1; + } + civil_time operator++(int) { + civil_time a = *this; + ++*this; + return a; + } + civil_time& operator--() { + return *this -= 1; + } + civil_time operator--(int) { + civil_time a = *this; + --*this; + return a; + } + + // Binary arithmetic operators. + inline friend constexpr civil_time operator+(const civil_time& a, int n) { + return civil_time(step(T{}, a.f_, n)); + } + inline friend constexpr civil_time operator+(int n, const civil_time& a) { + return civil_time(step(T{}, a.f_, n)); + } + inline friend constexpr civil_time operator-(const civil_time& a, int n) { + return civil_time(step(T{}, a.f_, -n)); + } + inline friend constexpr int operator-(const civil_time& lhs, + const civil_time& rhs) { + return difference(T{}, lhs.f_, rhs.f_); + } + + private: + // All instantiations of this template are allowed to call the following + // private constructor and access the private fields member. + template + friend class civil_time; + + // The designated constructor that all others eventually call. + explicit constexpr civil_time(fields f) : f_(align(T{}, f)) {} + + fields f_; +}; + +using civil_year = civil_time; +using civil_month = civil_time; +using civil_day = civil_time; +using civil_hour = civil_time; +using civil_minute = civil_time; +using civil_second = civil_time; + +//////////////////////////////////////////////////////////////////////// + +// Relational operators that work with differently aligned objects. +// Always compares all six fields. +template +constexpr bool operator<(const civil_time& lhs, + const civil_time& rhs) { + return (lhs.year() < rhs.year() || + (lhs.year() == rhs.year() && + (lhs.month() < rhs.month() || + (lhs.month() == rhs.month() && + (lhs.day() < rhs.day() || + (lhs.day() == rhs.day() && + (lhs.hour() < rhs.hour() || + (lhs.hour() == rhs.hour() && + (lhs.minute() < rhs.minute() || + (lhs.minute() == rhs.minute() && + (lhs.second() < rhs.second() || + (lhs.second() == rhs.second())))))))))))); +} +template +constexpr bool operator<=(const civil_time& lhs, + const civil_time& rhs) { + return !(rhs < lhs); +} +template +constexpr bool operator>=(const civil_time& lhs, + const civil_time& rhs) { + return !(lhs < rhs); +} +template +constexpr bool operator>(const civil_time& lhs, + const civil_time& rhs) { + return rhs < lhs; +} +template +constexpr bool operator==(const civil_time& lhs, + const civil_time& rhs) { + return lhs.year() == rhs.year() && lhs.month() == rhs.month() && + lhs.day() == rhs.day() && lhs.hour() == rhs.hour() && + lhs.minute() == rhs.minute() && lhs.second() == rhs.second(); +} +template +constexpr bool operator!=(const civil_time& lhs, + const civil_time& rhs) { + return !(lhs == rhs); +} + +//////////////////////////////////////////////////////////////////////// + +enum class weekday { + monday, + tuesday, + wednesday, + thursday, + friday, + saturday, + sunday, +}; + +namespace impl { +constexpr weekday k_weekday_by_thu_off[] = { + weekday::thursday, weekday::friday, weekday::saturday, + weekday::sunday, weekday::monday, weekday::tuesday, + weekday::wednesday, +}; +} // namespace impl + +constexpr weekday get_weekday(const civil_day& cd) { + return impl::k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; +} + +//////////////////////////////////////////////////////////////////////// + +namespace impl { +constexpr civil_day scan_weekday(const civil_day& cd, weekday wd, int incr) { + return get_weekday(cd) == wd ? cd : scan_weekday(cd + incr, wd, incr); +} +} // namespace impl + +constexpr civil_day next_weekday(const civil_day& cd, weekday wd) { + return impl::scan_weekday(cd + 1, wd, 1); +} + +constexpr civil_day prev_weekday(const civil_day& cd, weekday wd) { + return impl::scan_weekday(cd - 1, wd, -1); +} + +//////////////////////////////////////////////////////////////////////// + +constexpr int get_yearday(const civil_day& cd) { + return cd - civil_day(cd.year(), 1, 0); +} diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc new file mode 100644 index 00000000..813ac2ce --- /dev/null +++ b/src/civil_time_test.cc @@ -0,0 +1,851 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +#include "civil_time.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace cctz { + +namespace { + +std::string Format(const civil_second& cs) { + char buf[sizeof "-2147483648-12-31T23:59:59"]; + std::snprintf(buf, sizeof buf, "%d-%02d-%02dT%02d:%02d:%02d", cs.year(), + cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second()); + return std::string(buf); +} + +std::string Format(const civil_minute& cm) { + char buf[sizeof "-2147483648-12-31T23:59"]; + std::snprintf(buf, sizeof buf, "%d-%02d-%02dT%02d:%02d", cm.year(), + cm.month(), cm.day(), cm.hour(), cm.minute()); + return std::string(buf); +} + +std::string Format(const civil_hour& ch) { + char buf[sizeof "-2147483648-12-31T23"]; + std::snprintf(buf, sizeof buf, "%d-%02d-%02dT%02d", ch.year(), ch.month(), + ch.day(), ch.hour()); + return std::string(buf); +} + +std::string Format(const civil_day& cd) { + char buf[sizeof "-2147483648-12-31"]; + std::snprintf(buf, sizeof buf, "%d-%02d-%02d", cd.year(), cd.month(), + cd.day()); + return std::string(buf); +} + +std::string Format(const civil_month& cm) { + char buf[sizeof "-2147483648-12"]; + std::snprintf(buf, sizeof buf, "%d-%02d", cm.year(), cm.month()); + return std::string(buf); +} + +std::string Format(const civil_year& cy) { + char buf[sizeof "-2147483648"]; + std::snprintf(buf, sizeof buf, "%d", cy.year()); + return std::string(buf); +} + +} // namespace + +// Construction tests + +TEST(CivilTime, Normal) { + constexpr civil_second css(2016, 1, 28, 17, 14, 12); + constexpr civil_minute cmm(2016, 1, 28, 17, 14); + constexpr civil_hour chh(2016, 1, 28, 17); + constexpr civil_day cd(2016, 1, 28); + constexpr civil_month cm(2016, 1); + constexpr civil_year cy(2016); +} + +TEST(CivilTime, Conversion) { + constexpr civil_year cy(2016); + constexpr civil_month cm(cy); + constexpr civil_day cd(cm); + constexpr civil_hour chh(cd); + constexpr civil_minute cmm(chh); + constexpr civil_second css(cmm); +} + +// Normalization tests + +TEST(CivilTime, Normalized) { + constexpr civil_second cs(2016, 1, 28, 17, 14, 12); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, SecondOverflow) { + constexpr civil_second cs(2016, 1, 28, 17, 14, 121); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(16, cs.minute()); + EXPECT_EQ(1, cs.second()); +} + +TEST(CivilTime, SecondUnderflow) { + constexpr civil_second cs(2016, 1, 28, 17, 14, -121); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(11, cs.minute()); + EXPECT_EQ(59, cs.second()); +} + +TEST(CivilTime, MinuteOverflow) { + constexpr civil_second cs(2016, 1, 28, 17, 121, 12); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(19, cs.hour()); + EXPECT_EQ(1, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, MinuteUnderflow) { + constexpr civil_second cs(2016, 1, 28, 17, -121, 12); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(14, cs.hour()); + EXPECT_EQ(59, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, HourOverflow) { + constexpr civil_second cs(2016, 1, 28, 49, 14, 12); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(30, cs.day()); + EXPECT_EQ(1, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, HourUnderflow) { + constexpr civil_second cs(2016, 1, 28, -49, 14, 12); + EXPECT_EQ(2016, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(25, cs.day()); + EXPECT_EQ(23, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, MonthOverflow) { + constexpr civil_second cs(2016, 25, 28, 17, 14, 12); + EXPECT_EQ(2018, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, MonthUnderflow) { + constexpr civil_second cs(2016, -25, 28, 17, 14, 12); + EXPECT_EQ(2013, cs.year()); + EXPECT_EQ(11, cs.month()); + EXPECT_EQ(28, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, C4Overflow) { + constexpr civil_second cs(2016, 1, 292195, 17, 14, 12); + EXPECT_EQ(2816, cs.year()); + EXPECT_EQ(1, cs.month()); + EXPECT_EQ(1, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, C4Underflow) { + constexpr civil_second cs(2016, 1, -292195, 17, 14, 12); + EXPECT_EQ(1215, cs.year()); + EXPECT_EQ(12, cs.month()); + EXPECT_EQ(30, cs.day()); + EXPECT_EQ(17, cs.hour()); + EXPECT_EQ(14, cs.minute()); + EXPECT_EQ(12, cs.second()); +} + +TEST(CivilTime, MixedNormalization) { + constexpr civil_second cs(2016, -42, 122, 99, -147, 4949); + EXPECT_EQ(2012, cs.year()); + EXPECT_EQ(10, cs.month()); + EXPECT_EQ(4, cs.day()); + EXPECT_EQ(1, cs.hour()); + EXPECT_EQ(55, cs.minute()); + EXPECT_EQ(29, cs.second()); +} + +// Relational tests + +TEST(CivilTime, Less) { + constexpr civil_second cs1(2016, 1, 28, 17, 14, 12); + constexpr civil_second cs2(2016, 1, 28, 17, 14, 13); + constexpr bool less = cs1 < cs2; + EXPECT_TRUE(less); +} + +// Arithmetic tests + +TEST(CivilTime, Addition) { + constexpr civil_second cs1(2016, 1, 28, 17, 14, 12); + constexpr civil_second cs2 = cs1 + 50; + EXPECT_EQ(2016, cs2.year()); + EXPECT_EQ(1, cs2.month()); + EXPECT_EQ(28, cs2.day()); + EXPECT_EQ(17, cs2.hour()); + EXPECT_EQ(15, cs2.minute()); + EXPECT_EQ(2, cs2.second()); +} + +TEST(CivilTime, Subtraction) { + constexpr civil_second cs1(2016, 1, 28, 17, 14, 12); + constexpr civil_second cs2 = cs1 - 50; + EXPECT_EQ(2016, cs2.year()); + EXPECT_EQ(1, cs2.month()); + EXPECT_EQ(28, cs2.day()); + EXPECT_EQ(17, cs2.hour()); + EXPECT_EQ(13, cs2.minute()); + EXPECT_EQ(22, cs2.second()); +} + +TEST(CivilTime, Diff) { + constexpr civil_day cd1(2016, 1, 28); + constexpr civil_day cd2(2015, 1, 28); + constexpr int diff = cd1 - cd2; + EXPECT_EQ(365, diff); +} + +// Helper tests + +TEST(CivilTime, WeekDay) { + constexpr civil_day cd(2016, 1, 28); + constexpr weekday wd = get_weekday(cd); + EXPECT_EQ(weekday::thursday, wd); +} + +TEST(CivilTime, NextWeekDay) { + constexpr civil_day cd(2016, 1, 28); + constexpr civil_day next = next_weekday(cd, weekday::thursday); + EXPECT_EQ(2016, next.year()); + EXPECT_EQ(2, next.month()); + EXPECT_EQ(4, next.day()); +} + +TEST(CivilTime, PrevWeekDay) { + constexpr civil_day cd(2016, 1, 28); + constexpr civil_day prev = prev_weekday(cd, weekday::thursday); + EXPECT_EQ(2016, prev.year()); + EXPECT_EQ(1, prev.month()); + EXPECT_EQ(21, prev.day()); +} + +TEST(CivilTime, YearDay) { + constexpr civil_day cd(2016, 1, 28); + constexpr int yd = get_yearday(cd); + EXPECT_EQ(28, yd); +} + +// START OF google3 TESTS + +TEST(CivilTime, DefaultConstruction) { + civil_second ss; + EXPECT_EQ("1970-01-01T00:00:00", Format(ss)); + + civil_minute mm; + EXPECT_EQ("1970-01-01T00:00", Format(mm)); + + civil_hour hh; + EXPECT_EQ("1970-01-01T00", Format(hh)); + + civil_day d; + EXPECT_EQ("1970-01-01", Format(d)); + + civil_month m; + EXPECT_EQ("1970-01", Format(m)); + + civil_year y; + EXPECT_EQ("1970", Format(y)); +} + +TEST(CivilTime, StructMember) { + struct S { + civil_day day; + }; + S s = {}; + EXPECT_EQ(civil_day{}, s.day); +} + +TEST(CivilTime, FieldsConstruction) { + EXPECT_EQ("2015-01-02T03:04:05", Format(civil_second(2015, 1, 2, 3, 4, 5))); + EXPECT_EQ("2015-01-02T03:04:00", Format(civil_second(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015-01-02T03:00:00", Format(civil_second(2015, 1, 2, 3))); + EXPECT_EQ("2015-01-02T00:00:00", Format(civil_second(2015, 1, 2))); + EXPECT_EQ("2015-01-01T00:00:00", Format(civil_second(2015, 1))); + EXPECT_EQ("2015-01-01T00:00:00", Format(civil_second(2015))); + + EXPECT_EQ("2015-01-02T03:04", Format(civil_minute(2015, 1, 2, 3, 4, 5))); + EXPECT_EQ("2015-01-02T03:04", Format(civil_minute(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015-01-02T03:00", Format(civil_minute(2015, 1, 2, 3))); + EXPECT_EQ("2015-01-02T00:00", Format(civil_minute(2015, 1, 2))); + EXPECT_EQ("2015-01-01T00:00", Format(civil_minute(2015, 1))); + EXPECT_EQ("2015-01-01T00:00", Format(civil_minute(2015))); + + EXPECT_EQ("2015-01-02T03", Format(civil_hour(2015, 1, 2, 3, 4, 5))); + EXPECT_EQ("2015-01-02T03", Format(civil_hour(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015-01-02T03", Format(civil_hour(2015, 1, 2, 3))); + EXPECT_EQ("2015-01-02T00", Format(civil_hour(2015, 1, 2))); + EXPECT_EQ("2015-01-01T00", Format(civil_hour(2015, 1))); + EXPECT_EQ("2015-01-01T00", Format(civil_hour(2015))); + + EXPECT_EQ("2015-01-02", Format(civil_day(2015, 1, 2, 3, 4, 5))); + EXPECT_EQ("2015-01-02", Format(civil_day(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015-01-02", Format(civil_day(2015, 1, 2, 3))); + EXPECT_EQ("2015-01-02", Format(civil_day(2015, 1, 2))); + EXPECT_EQ("2015-01-01", Format(civil_day(2015, 1))); + EXPECT_EQ("2015-01-01", Format(civil_day(2015))); + + EXPECT_EQ("2015-01", Format(civil_month(2015, 1, 2, 3, 4, 5))); + EXPECT_EQ("2015-01", Format(civil_month(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015-01", Format(civil_month(2015, 1, 2, 3))); + EXPECT_EQ("2015-01", Format(civil_month(2015, 1, 2))); + EXPECT_EQ("2015-01", Format(civil_month(2015, 1))); + EXPECT_EQ("2015-01", Format(civil_month(2015))); + + EXPECT_EQ("2015", Format(civil_year(2015, 1, 2, 3, 4, 5))); + EXPECT_EQ("2015", Format(civil_year(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015", Format(civil_year(2015, 1, 2, 3))); + EXPECT_EQ("2015", Format(civil_year(2015, 1, 2))); + EXPECT_EQ("2015", Format(civil_year(2015, 1))); + EXPECT_EQ("2015", Format(civil_year(2015))); +} + +TEST(CivilTime, FieldsConstructionLimits) { + const int kIntMax = std::numeric_limits::max(); + EXPECT_EQ("2038-01-19T03:14:07", + Format(civil_second(1970, 1, 1, 0, 0, kIntMax))); + EXPECT_EQ("6121-02-11T05:21:07", + Format(civil_second(1970, 1, 1, 0, kIntMax, kIntMax))); + EXPECT_EQ("251104-11-20T12:21:07", + Format(civil_second(1970, 1, 1, kIntMax, kIntMax, kIntMax))); + EXPECT_EQ("6130715-05-30T12:21:07", + Format(civil_second(1970, 1, kIntMax, kIntMax, kIntMax, kIntMax))); + EXPECT_EQ( + "185087685-11-26T12:21:07", + Format(civil_second(1970, kIntMax, kIntMax, kIntMax, kIntMax, kIntMax))); + + const int kIntMin = std::numeric_limits::min(); + EXPECT_EQ("1901-12-13T20:45:52", + Format(civil_second(1970, 1, 1, 0, 0, kIntMin))); + EXPECT_EQ("-2182-11-20T18:37:52", + Format(civil_second(1970, 1, 1, 0, kIntMin, kIntMin))); + EXPECT_EQ("-247165-02-11T10:37:52", + Format(civil_second(1970, 1, 1, kIntMin, kIntMin, kIntMin))); + EXPECT_EQ("-6126776-08-01T10:37:52", + Format(civil_second(1970, 1, kIntMin, kIntMin, kIntMin, kIntMin))); + EXPECT_EQ( + "-185083747-10-31T10:37:52", + Format(civil_second(1970, kIntMin, kIntMin, kIntMin, kIntMin, kIntMin))); +} + +TEST(CivilTime, ExplicitCrossAlignment) { + // + // Assign from smaller units -> larger units + // + + civil_second second(2015, 1, 2, 3, 4, 5); + EXPECT_EQ("2015-01-02T03:04:05", Format(second)); + + civil_minute minute(second); + EXPECT_EQ("2015-01-02T03:04", Format(minute)); + + civil_hour hour(minute); + EXPECT_EQ("2015-01-02T03", Format(hour)); + + civil_day day(hour); + EXPECT_EQ("2015-01-02", Format(day)); + + civil_month month(day); + EXPECT_EQ("2015-01", Format(month)); + + civil_year year(month); + EXPECT_EQ("2015", Format(year)); + + // + // Now assign from larger units -> smaller units + // + + month = civil_month(year); + EXPECT_EQ("2015-01", Format(month)); + + day = civil_day(month); + EXPECT_EQ("2015-01-01", Format(day)); + + hour = civil_hour(day); + EXPECT_EQ("2015-01-01T00", Format(hour)); + + minute = civil_minute(hour); + EXPECT_EQ("2015-01-01T00:00", Format(minute)); + + second = civil_second(minute); + EXPECT_EQ("2015-01-01T00:00:00", Format(second)); +} + +TEST(CivilTime, ValueSemantics) { + const civil_hour a(2015, 1, 2, 3); + const civil_hour b = a; + const civil_hour c(b); + civil_hour d; + d = c; + EXPECT_EQ("2015-01-02T03", Format(d)); +} + +TEST(CivilTime, Relational) { + // Tests that the alignment unit is ignored in comparison. + const civil_year year(2014); + const civil_month month(year); + EXPECT_EQ(year, month); + +#define TEST_RELATIONAL(OLDER, YOUNGER) \ + do { \ + EXPECT_EQ(OLDER, OLDER); \ + EXPECT_NE(OLDER, YOUNGER); \ + EXPECT_LT(OLDER, YOUNGER); \ + EXPECT_LE(OLDER, YOUNGER); \ + EXPECT_GT(YOUNGER, OLDER); \ + EXPECT_GE(YOUNGER, OLDER); \ + } while (0) + + // Alignment is ignored in comparison (verified above), so kSecond is used + // to test comparison in all field positions. + TEST_RELATIONAL(civil_second(2014, 1, 1, 0, 0, 0), + civil_second(2015, 1, 1, 0, 0, 0)); + TEST_RELATIONAL(civil_second(2014, 1, 1, 0, 0, 0), + civil_second(2014, 2, 1, 0, 0, 0)); + TEST_RELATIONAL(civil_second(2014, 1, 1, 0, 0, 0), + civil_second(2014, 1, 2, 0, 0, 0)); + TEST_RELATIONAL(civil_second(2014, 1, 1, 0, 0, 0), + civil_second(2014, 1, 1, 1, 0, 0)); + TEST_RELATIONAL(civil_second(2014, 1, 1, 1, 0, 0), + civil_second(2014, 1, 1, 1, 1, 0)); + TEST_RELATIONAL(civil_second(2014, 1, 1, 1, 1, 0), + civil_second(2014, 1, 1, 1, 1, 1)); + + // Tests the relational operators of two different CivilTime types. + TEST_RELATIONAL(civil_day(2014, 1, 1), civil_minute(2014, 1, 1, 1, 1)); + TEST_RELATIONAL(civil_day(2014, 1, 1), civil_month(2014, 2)); + +#undef TEST_RELATIONAL +} + +TEST(CivilTime, Arithmetic) { + civil_second second(2015, 1, 2, 3, 4, 5); + EXPECT_EQ("2015-01-02T03:04:06", Format(second += 1)); + EXPECT_EQ("2015-01-02T03:04:07", Format(second + 1)); + EXPECT_EQ("2015-01-02T03:04:08", Format(2 + second)); + EXPECT_EQ("2015-01-02T03:04:05", Format(second - 1)); + EXPECT_EQ("2015-01-02T03:04:05", Format(second -= 1)); + EXPECT_EQ("2015-01-02T03:04:05", Format(second++)); + EXPECT_EQ("2015-01-02T03:04:07", Format(++second)); + EXPECT_EQ("2015-01-02T03:04:07", Format(second--)); + EXPECT_EQ("2015-01-02T03:04:05", Format(--second)); + + civil_minute minute(2015, 1, 2, 3, 4); + EXPECT_EQ("2015-01-02T03:05", Format(minute += 1)); + EXPECT_EQ("2015-01-02T03:06", Format(minute + 1)); + EXPECT_EQ("2015-01-02T03:07", Format(2 + minute)); + EXPECT_EQ("2015-01-02T03:04", Format(minute - 1)); + EXPECT_EQ("2015-01-02T03:04", Format(minute -= 1)); + EXPECT_EQ("2015-01-02T03:04", Format(minute++)); + EXPECT_EQ("2015-01-02T03:06", Format(++minute)); + EXPECT_EQ("2015-01-02T03:06", Format(minute--)); + EXPECT_EQ("2015-01-02T03:04", Format(--minute)); + + civil_hour hour(2015, 1, 2, 3); + EXPECT_EQ("2015-01-02T04", Format(hour += 1)); + EXPECT_EQ("2015-01-02T05", Format(hour + 1)); + EXPECT_EQ("2015-01-02T06", Format(2 + hour)); + EXPECT_EQ("2015-01-02T03", Format(hour - 1)); + EXPECT_EQ("2015-01-02T03", Format(hour -= 1)); + EXPECT_EQ("2015-01-02T03", Format(hour++)); + EXPECT_EQ("2015-01-02T05", Format(++hour)); + EXPECT_EQ("2015-01-02T05", Format(hour--)); + EXPECT_EQ("2015-01-02T03", Format(--hour)); + + civil_day day(2015, 1, 2); + EXPECT_EQ("2015-01-03", Format(day += 1)); + EXPECT_EQ("2015-01-04", Format(day + 1)); + EXPECT_EQ("2015-01-05", Format(2 + day)); + EXPECT_EQ("2015-01-02", Format(day - 1)); + EXPECT_EQ("2015-01-02", Format(day -= 1)); + EXPECT_EQ("2015-01-02", Format(day++)); + EXPECT_EQ("2015-01-04", Format(++day)); + EXPECT_EQ("2015-01-04", Format(day--)); + EXPECT_EQ("2015-01-02", Format(--day)); + + civil_month month(2015, 1); + EXPECT_EQ("2015-02", Format(month += 1)); + EXPECT_EQ("2015-03", Format(month + 1)); + EXPECT_EQ("2015-04", Format(2 + month)); + EXPECT_EQ("2015-01", Format(month - 1)); + EXPECT_EQ("2015-01", Format(month -= 1)); + EXPECT_EQ("2015-01", Format(month++)); + EXPECT_EQ("2015-03", Format(++month)); + EXPECT_EQ("2015-03", Format(month--)); + EXPECT_EQ("2015-01", Format(--month)); + + civil_year year(2015); + EXPECT_EQ("2016", Format(year += 1)); + EXPECT_EQ("2017", Format(year + 1)); + EXPECT_EQ("2018", Format(2 + year)); + EXPECT_EQ("2015", Format(year - 1)); + EXPECT_EQ("2015", Format(year -= 1)); + EXPECT_EQ("2015", Format(year++)); + EXPECT_EQ("2017", Format(++year)); + EXPECT_EQ("2017", Format(year--)); + EXPECT_EQ("2015", Format(--year)); +} + +TEST(CivilTime, ArithmeticLimits) { + constexpr int kIntMax = std::numeric_limits::max(); + constexpr int kIntMin = std::numeric_limits::min(); + + civil_second second(1970, 1, 1, 0, 0, 0); + second += kIntMax; + EXPECT_EQ("2038-01-19T03:14:07", Format(second)); + second -= kIntMax; + EXPECT_EQ("1970-01-01T00:00:00", Format(second)); + second += kIntMin; + EXPECT_EQ("1901-12-13T20:45:52", Format(second)); + second -= kIntMin; + EXPECT_EQ("1970-01-01T00:00:00", Format(second)); + + civil_minute minute(1970, 1, 1, 0, 0); + minute += kIntMax; + EXPECT_EQ("6053-01-23T02:07", Format(minute)); + minute -= kIntMax; + EXPECT_EQ("1970-01-01T00:00", Format(minute)); + minute += kIntMin; + EXPECT_EQ("-2114-12-08T21:52", Format(minute)); + minute -= kIntMin; + EXPECT_EQ("1970-01-01T00:00", Format(minute)); + + civil_hour hour(1970, 1, 1, 0); + hour += kIntMax; + EXPECT_EQ("246953-10-09T07", Format(hour)); + hour -= kIntMax; + EXPECT_EQ("1970-01-01T00", Format(hour)); + hour += kIntMin; + EXPECT_EQ("-243014-03-24T16", Format(hour)); + hour -= kIntMin; + EXPECT_EQ("1970-01-01T00", Format(hour)); + + civil_day day(1970, 1, 1); + day += kIntMax; + EXPECT_EQ("5881580-07-11", Format(day)); + day -= kIntMax; + EXPECT_EQ("1970-01-01", Format(day)); + day += kIntMin; + EXPECT_EQ("-5877641-06-23", Format(day)); + day -= kIntMin; + EXPECT_EQ("1970-01-01", Format(day)); + + civil_month month(1970, 1); + month += kIntMax; + EXPECT_EQ("178958940-08", Format(month)); + month -= kIntMax; + EXPECT_EQ("1970-01", Format(month)); + month += kIntMin; + EXPECT_EQ("-178955001-05", Format(month)); + month -= kIntMin; + EXPECT_EQ("1970-01", Format(month)); + + civil_year year(0); + year += kIntMax; + EXPECT_EQ("2147483647", Format(year)); + year -= kIntMax; + EXPECT_EQ("0", Format(year)); + year += kIntMin; + EXPECT_EQ("-2147483648", Format(year)); + year -= kIntMin; + EXPECT_EQ("0", Format(year)); +} + +TEST(CivilTime, Difference) { + civil_second second(2015, 1, 2, 3, 4, 5); + EXPECT_EQ(0, second - second); + EXPECT_EQ(10, (second + 10) - second); + EXPECT_EQ(-10, (second - 10) - second); + + civil_minute minute(2015, 1, 2, 3, 4); + EXPECT_EQ(0, minute - minute); + EXPECT_EQ(10, (minute + 10) - minute); + EXPECT_EQ(-10, (minute - 10) - minute); + + civil_hour hour(2015, 1, 2, 3); + EXPECT_EQ(0, hour - hour); + EXPECT_EQ(10, (hour + 10) - hour); + EXPECT_EQ(-10, (hour - 10) - hour); + + civil_day day(2015, 1, 2); + EXPECT_EQ(0, day - day); + EXPECT_EQ(10, (day + 10) - day); + EXPECT_EQ(-10, (day - 10) - day); + + civil_month month(2015, 1); + EXPECT_EQ(0, month - month); + EXPECT_EQ(10, (month + 10) - month); + EXPECT_EQ(-10, (month - 10) - month); + + civil_year year(2015); + EXPECT_EQ(0, year - year); + EXPECT_EQ(10, (year + 10) - year); + EXPECT_EQ(-10, (year - 10) - year); +} + +TEST(CivilTime, DifferenceLimits) { + const int kIntMax = std::numeric_limits::max(); + const int kIntMin = std::numeric_limits::min(); + + // Check day arithmetic at the end of the year range. + const civil_day max_day(kIntMax, 12, 31); + EXPECT_EQ(1, max_day - (max_day - 1)); + EXPECT_EQ(-1, (max_day - 1) - max_day); + + // Check day arithmetic at the end of the year range. + const civil_day min_day(kIntMin, 1, 1); + EXPECT_EQ(1, (min_day + 1) - min_day); + EXPECT_EQ(-1, min_day - (min_day + 1)); + + // Check the limits of the return value. + const civil_day d1(1970, 1, 1); + const civil_day d2(5881580, 7, 11); + EXPECT_EQ(kIntMax, d2 - d1); + EXPECT_EQ(kIntMin, d1 - (d2 + 1)); +} + +TEST(CivilTime, Properties) { + civil_second ss(2015, 2, 3, 4, 5, 6); + EXPECT_EQ(2015, ss.year()); + EXPECT_EQ(2, ss.month()); + EXPECT_EQ(3, ss.day()); + EXPECT_EQ(4, ss.hour()); + EXPECT_EQ(5, ss.minute()); + EXPECT_EQ(6, ss.second()); + + civil_minute mm(2015, 2, 3, 4, 5, 6); + EXPECT_EQ(2015, mm.year()); + EXPECT_EQ(2, mm.month()); + EXPECT_EQ(3, mm.day()); + EXPECT_EQ(4, mm.hour()); + EXPECT_EQ(5, mm.minute()); + EXPECT_EQ(0, mm.second()); + + civil_hour hh(2015, 2, 3, 4, 5, 6); + EXPECT_EQ(2015, hh.year()); + EXPECT_EQ(2, hh.month()); + EXPECT_EQ(3, hh.day()); + EXPECT_EQ(4, hh.hour()); + EXPECT_EQ(0, hh.minute()); + EXPECT_EQ(0, hh.second()); + + civil_day d(2015, 2, 3, 4, 5, 6); + EXPECT_EQ(2015, d.year()); + EXPECT_EQ(2, d.month()); + EXPECT_EQ(3, d.day()); + EXPECT_EQ(0, d.hour()); + EXPECT_EQ(0, d.minute()); + EXPECT_EQ(0, d.second()); + EXPECT_EQ(weekday::tuesday, get_weekday(d)); + + civil_month m(2015, 2, 3, 4, 5, 6); + EXPECT_EQ(2015, m.year()); + EXPECT_EQ(2, m.month()); + EXPECT_EQ(1, m.day()); + EXPECT_EQ(0, m.hour()); + EXPECT_EQ(0, m.minute()); + EXPECT_EQ(0, m.second()); + + civil_year y(2015, 2, 3, 4, 5, 6); + EXPECT_EQ(2015, y.year()); + EXPECT_EQ(1, y.month()); + EXPECT_EQ(1, y.day()); + EXPECT_EQ(0, y.hour()); + EXPECT_EQ(0, y.minute()); + EXPECT_EQ(0, y.second()); +} + +TEST(CivilTime, NextPrevWeekday) { + // Jan 1, 1970 was a Thursday. + const civil_day thursday(1970, 1, 1); + EXPECT_EQ(weekday::thursday, get_weekday(thursday)) << Format(thursday); + + // Thursday -> Thursday + civil_day d = next_weekday(thursday, weekday::thursday); + EXPECT_EQ(7, d - thursday) << Format(d); + EXPECT_EQ(d - 14, prev_weekday(thursday, weekday::thursday)); + + // Thursday -> Friday + d = next_weekday(thursday, weekday::friday); + EXPECT_EQ(1, d - thursday) << Format(d); + EXPECT_EQ(d - 7, prev_weekday(thursday, weekday::friday)); + + // Thursday -> Saturday + d = next_weekday(thursday, weekday::saturday); + EXPECT_EQ(2, d - thursday) << Format(d); + EXPECT_EQ(d - 7, prev_weekday(thursday, weekday::saturday)); + + // Thursday -> Sunday + d = next_weekday(thursday, weekday::sunday); + EXPECT_EQ(3, d - thursday) << Format(d); + EXPECT_EQ(d - 7, prev_weekday(thursday, weekday::sunday)); + + // Thursday -> Monday + d = next_weekday(thursday, weekday::monday); + EXPECT_EQ(4, d - thursday) << Format(d); + EXPECT_EQ(d - 7, prev_weekday(thursday, weekday::monday)); + + // Thursday -> Tuesday + d = next_weekday(thursday, weekday::tuesday); + EXPECT_EQ(5, d - thursday) << Format(d); + EXPECT_EQ(d - 7, prev_weekday(thursday, weekday::tuesday)); + + // Thursday -> Wednesday + d = next_weekday(thursday, weekday::wednesday); + EXPECT_EQ(6, d - thursday) << Format(d); + EXPECT_EQ(d - 7, prev_weekday(thursday, weekday::wednesday)); +} + +// NOTE: Run this with --copt=-ftrapv to detect overflow problems. +TEST(CivilTime, DifferenceWithHugeYear) { + civil_day d1(5881579, 1, 1); + civil_day d2(5881579, 12, 31); + EXPECT_EQ(364, d2 - d1); + + d1 = civil_day(-5877640, 1, 1); + d2 = civil_day(-5877640, 12, 31); + EXPECT_EQ(365, d2 - d1); + + // Check the limits of the return value with large positive year. + d1 = civil_day(5881580, 7, 11); + d2 = civil_day(1970, 1, 1); + EXPECT_EQ(2147483647, d1 - d2); + d2 = d2 - 1; + EXPECT_EQ(-2147483647 - 1, d2 - d1); + + // Check the limits of the return value with large negative year. + d1 = civil_day(-5877641, 6, 23); + d2 = civil_day(1969, 12, 31); + EXPECT_EQ(2147483647, d2 - d1); + d2 = d2 + 1; + EXPECT_EQ(-2147483647 - 1, d1 - d2); + + // Check the limits of the return value from either side of year 0. + d1 = civil_day(-2939806, 9, 26); + d2 = civil_day(2939805, 4, 6); + EXPECT_EQ(2147483647, d2 - d1); + d2 = d2 + 1; + EXPECT_EQ(-2147483647 - 1, d1 - d2); +} + +TEST(CivilTime, NormalizeWithHugeYear) { + civil_month c(2147483647, 1); + EXPECT_EQ("2147483647-01", Format(c)); + c = c - 1; // Causes normalization + EXPECT_EQ("2147483646-12", Format(c)); + + c = civil_month(-2147483647 - 1, 1); + EXPECT_EQ("-2147483648-01", Format(c)); + c = c + 12; // Causes normalization + EXPECT_EQ("-2147483647-01", Format(c)); +} + +TEST(CivilTime, LeapYears) { + // Test data for leap years. + const struct { + int year; + int days; + struct { + int month; + int day; + } leap_day; // The date of the day after Feb 28. + } kLeapYearTable[]{ + {1900, 365, {3, 1}}, + {1999, 365, {3, 1}}, + {2000, 366, {2, 29}}, // leap year + {2001, 365, {3, 1}}, + {2002, 365, {3, 1}}, + {2003, 365, {3, 1}}, + {2004, 366, {2, 29}}, // leap year + {2005, 365, {3, 1}}, + {2006, 365, {3, 1}}, + {2007, 365, {3, 1}}, + {2008, 366, {2, 29}}, // leap year + {2009, 365, {3, 1}}, + {2100, 365, {3, 1}}, + }; + + for (size_t i = 0; i < (sizeof kLeapYearTable) / (sizeof kLeapYearTable[0]); + ++i) { + const int y = kLeapYearTable[i].year; + const int m = kLeapYearTable[i].leap_day.month; + const int d = kLeapYearTable[i].leap_day.day; + const int n = kLeapYearTable[i].days; + + // Tests incrementing through the leap day. + const civil_day feb28(y, 2, 28); + const civil_day next_day = feb28 + 1; + EXPECT_EQ(m, next_day.month()); + EXPECT_EQ(d, next_day.day()); + + // Tests difference in days of leap years. + const civil_year year(feb28); + const civil_year next_year = year + 1; + EXPECT_EQ(n, civil_day(next_year) - civil_day(year)); + } +} + +TEST(CivilTime, FirstThursdayInMonth) { + const civil_day nov1(2014, 11, 1); + const civil_day thursday = prev_weekday(nov1, weekday::thursday) + 7; + EXPECT_EQ("2014-11-06", Format(thursday)); + + // Bonus: Date of Thanksgiving in the United States + // Rule: Fourth Thursday of November + const civil_day thanksgiving = thursday + 7 * 3; + EXPECT_EQ("2014-11-27", Format(thanksgiving)); +} + +} // namespace cctz diff --git a/tools/time_tool.cc b/src/time_tool.cc similarity index 62% rename from tools/time_tool.cc rename to src/time_tool.cc index 4ae5f3d4..05d9cb50 100644 --- a/tools/time_tool.cc +++ b/src/time_tool.cc @@ -1,3 +1,17 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + // A command-line tool for exercising the CCTZ library. #include @@ -11,14 +25,15 @@ #include #include -#include "src/cctz.h" +#include "civil_time.h" +#include "time_zone.h" // Pulls in the aliases from cctz for brevity. template using time_point = cctz::time_point; -using seconds64 = cctz::seconds64; +using sys_seconds = cctz::sys_seconds; -// Parse() specifiers for command-line time arguments. +// parse() specifiers for command-line time arguments. const char* const kFormats[] = { "%Y %m %d %H %M %E*S", "%Y - %m - %d T %H : %M : %E*S", @@ -42,12 +57,12 @@ const char* const kFormats[] = { nullptr }; -bool ParseTimeSpec(const std::string& args, cctz::TimeZone zone, - time_point* when) { +bool ParseTimeSpec(const std::string& args, cctz::time_zone zone, + time_point* when) { for (const char* const* fmt = kFormats; *fmt != NULL; ++fmt) { const std::string format = std::string(*fmt) + " %Ez"; - time_point tp; - if (cctz::Parse(format, args, zone, &tp)) { + time_point tp; + if (cctz::parse(format, args, zone, &tp)) { *when = tp; return true; } @@ -55,12 +70,13 @@ bool ParseTimeSpec(const std::string& args, cctz::TimeZone zone, return false; } -bool ParseBreakdownSpec(const std::string& args, cctz::Breakdown* when) { - const cctz::TimeZone utc = cctz::UTCTimeZone(); +bool ParseBreakdownSpec(const std::string& args, + cctz::time_zone::absolute_lookup* when) { + const cctz::time_zone utc = cctz::utc_time_zone(); for (const char* const* fmt = kFormats; *fmt != NULL; ++fmt) { - time_point tp; - if (cctz::Parse(*fmt, args, utc, &tp)) { - *when = cctz::BreakTime(tp, utc); + time_point tp; + if (cctz::parse(*fmt, args, utc, &tp)) { + *when = utc.lookup(tp); return true; } } @@ -70,24 +86,35 @@ bool ParseBreakdownSpec(const std::string& args, cctz::Breakdown* when) { // The FormatTime() specifier for output. const char* const kFormat = "%Y-%m-%d %H:%M:%S %Ez (%Z)"; -const char* const kWeekDayNames[] = { - "Unused", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" -}; +const char* WeekDayName(cctz::weekday wd) { + switch (wd) { + case cctz::weekday::monday: return "Mon"; + case cctz::weekday::tuesday: return "Tue"; + case cctz::weekday::wednesday: return "Wed"; + case cctz::weekday::thursday: return "Thu"; + case cctz::weekday::friday: return "Fri"; + case cctz::weekday::saturday: return "Sat"; + case cctz::weekday::sunday: return "Sun"; + } + return "XXX"; +} -std::string FormatTimeInZone(time_point when, cctz::TimeZone zone) { +std::string FormatTimeInZone(time_point when, + cctz::time_zone zone) { std::ostringstream oss; - oss << std::setw(33) << std::left << cctz::Format(kFormat, when, zone); - cctz::Breakdown bd = cctz::BreakTime(when, zone); - oss << " [wd=" << kWeekDayNames[bd.weekday] - << " yd=" << std::setw(3) << std::setfill('0') << bd.yearday + oss << std::setw(33) << std::left << cctz::format(kFormat, when, zone); + cctz::time_zone::absolute_lookup bd = zone.lookup(when); + oss << " [wd=" << WeekDayName(cctz::get_weekday(cctz::civil_day(bd.cs))) + << " yd=" << std::setw(3) << std::setfill('0') << std::right + << cctz::get_yearday(cctz::civil_day(bd.cs)) << " dst=" << (bd.is_dst ? 'T' : 'F') << " off=" << std::showpos << bd.offset << std::noshowpos << "]"; return oss.str(); } -void InstantInfo(const std::string& label, time_point when, - cctz::TimeZone zone) { - const cctz::TimeZone utc = cctz::UTCTimeZone(); // might == zone +void InstantInfo(const std::string& label, time_point when, + cctz::time_zone zone) { + const cctz::time_zone utc = cctz::utc_time_zone(); // might == zone const std::string time_label = "time_t"; const std::string utc_label = "UTC"; const std::string zone_label = "in-tz"; @@ -96,7 +123,7 @@ void InstantInfo(const std::string& label, time_point when, zone_label.size()); std::cout << label << " {\n"; std::cout << std::setw(width) << std::right << time_label << ": "; - std::cout << std::setw(10) << Format("%s", when, utc); + std::cout << std::setw(10) << format("%s", when, utc); std::cout << "\n"; std::cout << std::setw(width) << std::right << utc_label << ": "; std::cout << FormatTimeInZone(when, utc) << "\n"; @@ -105,18 +132,17 @@ void InstantInfo(const std::string& label, time_point when, std::cout << "}\n"; } -// Report everything we know about a Breakdown (YMDHMS). -int BreakdownInfo(const cctz::Breakdown& when, cctz::TimeZone zone) { - cctz::TimeInfo ti = - cctz::MakeTimeInfo(when.year, when.month, when.day, - when.hour, when.minute, when.second, zone); +// Report everything we know about a time_zone::absolute_lookup (YMDHMS). +int BreakdownInfo(const cctz::time_zone::absolute_lookup& when, + cctz::time_zone zone) { + cctz::time_zone::civil_lookup ti = zone.lookup(when.cs); switch (ti.kind) { - case cctz::TimeInfo::Kind::UNIQUE: { + case cctz::time_zone::civil_lookup::UNIQUE: { std::cout << "kind: UNIQUE\n"; InstantInfo("when", ti.pre, zone); break; } - case cctz::TimeInfo::Kind::SKIPPED: { + case cctz::time_zone::civil_lookup::SKIPPED: { std::cout << "kind: SKIPPED\n"; InstantInfo("post", ti.post, zone); // might == trans-1 InstantInfo("trans-1", ti.trans - std::chrono::seconds(1), zone); @@ -124,7 +150,7 @@ int BreakdownInfo(const cctz::Breakdown& when, cctz::TimeZone zone) { InstantInfo("pre", ti.pre, zone); // might == trans break; } - case cctz::TimeInfo::Kind::REPEATED: { + case cctz::time_zone::civil_lookup::REPEATED: { std::cout << "kind: REPEATED\n"; InstantInfo("pre", ti.pre, zone); // might == trans-1 InstantInfo("trans-1", ti.trans - std::chrono::seconds(1), zone); @@ -136,8 +162,8 @@ int BreakdownInfo(const cctz::Breakdown& when, cctz::TimeZone zone) { return 0; } -// Report everything we know about a time_point. -int TimeInfo(time_point when, cctz::TimeZone zone) { +// Report everything we know about a time_point. +int TimeInfo(time_point when, cctz::time_zone zone) { std::cout << "kind: UNIQUE\n"; InstantInfo("when", when, zone); return 0; @@ -175,7 +201,7 @@ int main(int argc, char** argv) { } // Determine the time zone. - cctz::TimeZone zone = cctz::LocalTimeZone(); + cctz::time_zone zone = cctz::local_time_zone(); for (;;) { static option opts[] = { {"tz", required_argument, nullptr, 'z'}, @@ -185,7 +211,7 @@ int main(int argc, char** argv) { if (c == -1) break; switch (c) { case 'z': - if (!cctz::LoadTimeZone(optarg, &zone)) { + if (!cctz::load_time_zone(optarg, &zone)) { std::cerr << optarg << ": Unrecognized time zone\n"; return 1; } @@ -197,8 +223,8 @@ int main(int argc, char** argv) { } // Determine the time point. - time_point tp = - std::chrono::time_point_cast(std::chrono::system_clock::now()); + time_point tp = std::chrono::time_point_cast( + std::chrono::system_clock::now()); std::string args; for (int i = optind; i < argc; ++i) { if (i != optind) args += " "; @@ -214,14 +240,14 @@ int main(int argc, char** argv) { std::size_t end; const time_t t = std::stoll(spec, &end); if (end == spec.size()) { - tp = std::chrono::time_point_cast( + tp = std::chrono::time_point_cast( std::chrono::system_clock::from_time_t(0)) + - seconds64(t); + sys_seconds(t); have_time = true; } } } - cctz::Breakdown when = cctz::BreakTime(tp, zone); + cctz::time_zone::absolute_lookup when = zone.lookup(tp); bool have_break_down = !have_time && ParseBreakdownSpec(args, &when); // Show results. diff --git a/src/time_zone.h b/src/time_zone.h new file mode 100644 index 00000000..59146825 --- /dev/null +++ b/src/time_zone.h @@ -0,0 +1,261 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +// A library for translating between absolute times (represented by +// std::chrono::time_points of the std::chrono::system_clock) and civil +// times (represented by cctz::civil_second) using the rules defined by +// a time zone (cctz::time_zone). + +#ifndef CCTZ_TIME_ZONE_H_ +#define CCTZ_TIME_ZONE_H_ + +#include +#include + +#include "civil_time.h" + +namespace cctz { + +// Convenience aliases. Not intended as public API points. +template +using time_point = std::chrono::time_point; +using sys_seconds = std::chrono::duration; + +// cctz::time_zone is an opaque, small, value-type class representing a +// geo-political region within which particular rules are used for mapping +// between absolute and civil times. Time zones are named using the TZ +// identifiers from the IANA Time Zone Database, such as "America/Los_Angeles" +// or "Australia/Sydney". Time zones are created from factory functions such +// as load_time_zone(). Note: strings like "PST" and "EDT" are not valid TZ +// identifiers. +// +// Example: +// cctz::time_zone utc = cctz::utc_time_zone(); +// cctz::time_zone loc = cctz::local_time_zone(); +// cctz::time_zone lax; +// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... } +// +// See also: +// - http://www.iana.org/time-zones +// - http://en.wikipedia.org/wiki/Zoneinfo +class time_zone { + public: + time_zone() = default; // Equivalent to UTC + time_zone(const time_zone&) = default; + time_zone& operator=(const time_zone&) = default; + + // The civil_second denoted by a time_point in a certain time_zone. A + // better std::tm. This struct is not intended to represent an instant + // in time. So, rather than passing an absolute_lookup to a function, + // pass a time_point and a time_zone. + // + // Example: + // const cctz::time_zone tz = ... + // const auto tp = std::chrono::system_clock::now(); + // const cctz::civil_second cs = tz.lookup(tp).cs; + struct absolute_lookup { + civil_second cs; + // Note: The following fields exist for backward compatibility with + // older APIs. Accessing these fields directly is a sign of imprudent + // logic in the calling code. Modern time-related code should only + // access this data indirectly by way of cctz::format(). + int offset; // seconds east of UTC + bool is_dst; // is offset non-standard? + std::string abbr; // time-zone abbreviation (e.g., "PST") + }; + absolute_lookup lookup(const time_point& tp) const; + template + absolute_lookup lookup(const time_point& tp) const { + return lookup(std::chrono::time_point_cast(tp)); + } + + // A civil_lookup represents the conversion of a cctz::civil_second in a + // particular cctz::time_zone to a time instant, as returned by lookup(). + // It is possible, though, for a caller to try to convert civil_second + // fields that do not represent an actual or unique instant in time + // (due to a shift in UTC offset in the time zone, which results in a + // discontinuity in the civil-time components). + // + // To account for these possibilities, civil_lookup is richer than just + // a single time_point. When the civil time is skipped or repeated, + // lookup() returns times calculated using the pre-transition and post- + // transition UTC offsets, plus the transition time itself. + // + // Example: + // cctz::time_zone lax; + // if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... } + // + // // A unique civil time. + // auto jan01 = lax.lookup(cctz::civil_second(2011, 1, 1, 0, 0, 0)); + // // jan01.kind == cctz::time_zone::civil_lookup::UNIQUE + // // jan01.pre is 2011/01/01 00:00:00 -0800 + // // jan01.trans is 2011/01/01 00:00:00 -0800 + // // jan01.post is 2011/01/01 00:00:00 -0800 + // + // // A Spring DST transition, when there is a gap in civil time. + // auto mar13 = lax.lookup(cctz::civil_second(2011, 3, 13, 2, 15, 0)); + // // mar13.kind == cctz::time_zone::civil_lookup::SKIPPED + // // mar13.pre is 2011/03/13 03:15:00 -0700 + // // mar13.trans is 2011/03/13 03:00:00 -0700 + // // mar13.post is 2011/03/13 01:15:00 -0800 + // + // // A Fall DST transition, when civil times are repeated. + // auto nov06 = lax.lookup(cctz::civil_second(2011, 11, 6, 1, 15, 0)); + // // nov06.kind == cctz::time_zone::civil_lookup::REPEATED + // // nov06.pre is 2011/11/06 01:15:00 -0700 + // // nov06.trans is 2011/11/06 01:00:00 -0800 + // // nov06.post is 2011/11/06 01:15:00 -0800 + struct civil_lookup { + enum civil_kind { + UNIQUE, // the civil time was singular (pre == trans == post) + SKIPPED, // the civil time did not exist + REPEATED, // the civil time was ambiguous + } kind; + time_point pre; // Uses the pre-transition offset + time_point trans; // Instant of civil-offset change + time_point post; // Uses the post-transition offset + }; + civil_lookup lookup(const civil_second& cs) const; + + class Impl; + + private: + explicit time_zone(const Impl* impl) : impl_(impl) {} + const Impl* impl_ = nullptr; +}; + +// Loads the named time zone. May perform I/O on the initial load. +// If the name is invalid, or some other kind of error occurs, returns +// false and "*tz" is set to the UTC time zone. +bool load_time_zone(const std::string& name, time_zone* tz); + +// Returns a time_zone representing UTC. Cannot fail. +time_zone utc_time_zone(); + +// Returns a time zone representing the local time zone. Falls back to UTC. +time_zone local_time_zone(); + +namespace internal { +// Floors tp to a second boundary and sets *subseconds. +template +inline std::pair, D> +FloorSeconds(const time_point& tp) { + auto sec = std::chrono::time_point_cast(tp); + auto sub = tp - sec; + if (sub.count() < 0) { + sec -= sys_seconds(1); + sub += sys_seconds(1); + } + return {sec, std::chrono::duration_cast(sub)}; +} +// Overload for when tp is already second aligned. +inline std::pair, sys_seconds> +FloorSeconds(const time_point& tp) { + return {tp, sys_seconds(0)}; +} +} // namespace internal + +// Formats the given time_point in the given cctz::time_zone according to +// the provided format string. Uses strftime()-like formatting options, +// with the following extensions: +// +// - %Ez - RFC3339-compatible numeric time zone (+hh:mm or -hh:mm) +// - %E#S - Seconds with # digits of fractional precision +// - %E*S - Seconds with full fractional precision (a literal '*') +// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999) +// +// Note that %Y produces as many characters as it takes to fully render the +// year. A year outside of [-999:9999] when formatted with %E4Y will produce +// more than four characters, just like %Y. +// +// Format strings should include %Ez so that the result uniquely identifies +// a time instant. +// +// Example: +// cctz::time_zone lax; +// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... } +// auto tp = lax.lookup(cctz::civil_second(2013, 1, 2, 3, 4, 5)).pre; +// std::string f = cctz::format("%H:%M:%S", tp, lax); // "03:04:05" +// f = cctz::format("%H:%M:%E3S", tp, lax); // "03:04:05.000" +std::string format(const std::string&, const time_point&, + const std::chrono::nanoseconds&, const time_zone&); +template +inline std::string format(const std::string& fmt, const time_point& tp, + const time_zone& tz) { + const auto p = internal::FloorSeconds(tp); + const auto n = std::chrono::duration_cast(p.second); + return format(fmt, p.first, n, tz); +} + +// Parses an input string according to the provided format string and +// returns the corresponding time_point. Uses strftime()-like formatting +// options, with the same extensions as cctz::format(). +// +// %Y consumes as many numeric characters as it can, so the matching data +// should always be terminated with a non-numeric. %E4Y always consumes +// exactly four characters, including any sign. +// +// Unspecified fields are taken from the default date and time of ... +// +// "1970-01-01 00:00:00.0 +0000" +// +// For example, parsing a string of "15:45" (%H:%M) will return a time_point +// that represents "1970-01-01 15:45:00.0 +0000". +// +// Note that Parse() returns time instants, so it makes most sense to parse +// fully-specified date/time strings that include a UTC offset (%z/%Ez). +// +// Note also that Parse() only heeds the fields year, month, day, hour, +// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a +// or %A), while parsed for syntactic validity, are ignored in the conversion. +// +// Date and time fields that are out-of-range will be treated as errors rather +// than normalizing them like cctz::civil_second() would do. For example, it +// is an error to parse the date "Oct 32, 2013" because 32 is out of range. +// +// A leap second of ":60" is normalized to ":00" of the following minute with +// fractional seconds discarded. The following table shows how the given +// seconds and subseconds will be parsed: +// +// "59.x" -> 59.x // exact +// "60.x" -> 00.0 // normalized +// "00.x" -> 00.x // exact +// +// Errors are indicated by returning false. +// +// Example: +// const cctz::time_zone tz = ... +// std::chrono::system_clock::time_point tp; +// if (cctz::parse("%Y-%m-%d", "2015-10-09", tz, &tp)) { +// ... +// } +bool parse(const std::string&, const std::string&, const time_zone&, + time_point*, std::chrono::nanoseconds*); +template +inline bool parse(const std::string& fmt, const std::string& input, + const time_zone& tz, time_point* tpp) { + time_point tp{}; + std::chrono::nanoseconds ns{0}; + const bool b = parse(fmt, input, tz, &tp, &ns); + if (b) { + *tpp = std::chrono::time_point_cast(tp); + *tpp += std::chrono::duration_cast(ns); + } + return b; +} + +} // namespace cctz + +#endif // CCTZ_TIME_ZONE_H_ diff --git a/src/cctz_fmt.cc b/src/time_zone_format.cc similarity index 87% rename from src/cctz_fmt.cc rename to src/time_zone_format.cc index b6aaee2f..260123eb 100644 --- a/src/cctz_fmt.cc +++ b/src/time_zone_format.cc @@ -1,17 +1,16 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. #if !defined(HAS_STRPTIME) # if !defined(_WIN32) && !defined(_WIN64) @@ -19,8 +18,8 @@ # endif #endif -#include "src/cctz.h" -#include "src/cctz_if.h" +#include "time_zone.h" +#include "time_zone_if.h" #include #include @@ -48,25 +47,47 @@ char* strptime(const char* s, const char* fmt, std::tm* tm) { } #endif -std::tm ToTM(const Breakdown& bd) { +std::tm ToTM(const time_zone::absolute_lookup& bd) { std::tm tm{}; - tm.tm_sec = bd.second; - tm.tm_min = bd.minute; - tm.tm_hour = bd.hour; - tm.tm_mday = bd.day; - tm.tm_mon = bd.month - 1; + tm.tm_sec = bd.cs.second(); + tm.tm_min = bd.cs.minute(); + tm.tm_hour = bd.cs.hour(); + tm.tm_mday = bd.cs.day(); + tm.tm_mon = bd.cs.month() - 1; // Saturate tm.tm_year is cases of over/underflow. - if (bd.year < std::numeric_limits::min() + 1900) { + if (bd.cs.year() < std::numeric_limits::min() + 1900) { tm.tm_year = std::numeric_limits::min(); - } else if (bd.year - 1900 > std::numeric_limits::max()) { + } else if (bd.cs.year() - 1900 > std::numeric_limits::max()) { tm.tm_year = std::numeric_limits::max(); } else { - tm.tm_year = static_cast(bd.year - 1900); + tm.tm_year = static_cast(bd.cs.year() - 1900); } - tm.tm_wday = bd.weekday % 7; - tm.tm_yday = bd.yearday - 1; + switch (get_weekday(civil_day(bd.cs))) { + case weekday::sunday: + tm.tm_wday = 0; + break; + case weekday::monday: + tm.tm_wday = 1; + break; + case weekday::tuesday: + tm.tm_wday = 2; + break; + case weekday::wednesday: + tm.tm_wday = 3; + break; + case weekday::thursday: + tm.tm_wday = 4; + break; + case weekday::friday: + tm.tm_wday = 5; + break; + case weekday::saturday: + tm.tm_wday = 6; + break; + } + tm.tm_yday = get_yearday(civil_day(bd.cs)) - 1; tm.tm_isdst = bd.is_dst ? 1 : 0; return tm; } @@ -238,10 +259,10 @@ const int64_t kExp10[kDigits10_64 + 1] = { // // We also handle the %z and %Z specifiers to accommodate platforms that do // not support the tm_gmtoff and tm_zone extensions to std::tm. -std::string Format(const std::string& format, const time_point& tp, - const std::chrono::nanoseconds& ns, const TimeZone& tz) { +std::string format(const std::string& format, const time_point& tp, + const std::chrono::nanoseconds& ns, const time_zone& tz) { std::string result; - const Breakdown bd = BreakTime(tp, tz); + const time_zone::absolute_lookup bd = tz.lookup(tp); const std::tm tm = ToTM(bd); // Scratch buffer for internal conversions. @@ -297,29 +318,29 @@ std::string Format(const std::string& format, const time_point& tp, case 'Y': // This avoids the tm_year overflow problem for %Y, however // tm.tm_year will still be used by other specifiers like %D. - bp = Format64(ep, 0, bd.year); + bp = Format64(ep, 0, bd.cs.year()); result.append(bp, ep - bp); break; case 'm': - bp = Format02d(ep, bd.month); + bp = Format02d(ep, bd.cs.month()); result.append(bp, ep - bp); break; case 'd': case 'e': - bp = Format02d(ep, bd.day); + bp = Format02d(ep, bd.cs.day()); if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows result.append(bp, ep - bp); break; case 'H': - bp = Format02d(ep, bd.hour); + bp = Format02d(ep, bd.cs.hour()); result.append(bp, ep - bp); break; case 'M': - bp = Format02d(ep, bd.minute); + bp = Format02d(ep, bd.cs.minute()); result.append(bp, ep - bp); break; case 'S': - bp = Format02d(ep, bd.second); + bp = Format02d(ep, bd.cs.second()); result.append(bp, ep - bp); break; case 'z': @@ -359,7 +380,7 @@ std::string Format(const std::string& format, const time_point& tp, bp = Format64(cp, 9, ns.count()); while (cp != bp && cp[-1] == '0') --cp; if (cp != bp) *--bp = '.'; - bp = Format02d(bp, bd.second); + bp = Format02d(bp, bd.cs.second()); result.append(bp, cp - bp); pending = cur += 2; } else if (*cur == '4' && cur + 1 != end && *(cur + 1) == 'Y') { @@ -367,7 +388,7 @@ std::string Format(const std::string& format, const time_point& tp, if (cur - 2 != pending) { FormatTM(&result, std::string(pending, cur - 2), tm); } - bp = Format64(ep, 4, bd.year); + bp = Format64(ep, 4, bd.cs.year()); result.append(bp, ep - bp); pending = cur += 2; } else if (std::isdigit(*cur)) { @@ -386,7 +407,7 @@ std::string Format(const std::string& format, const time_point& tp, : ns.count() / kExp10[9 - n]); *--bp = '.'; } - bp = Format02d(bp, bd.second); + bp = Format02d(bp, bd.cs.second()); result.append(bp, ep - bp); pending = cur = np; } @@ -437,7 +458,8 @@ const char* ParseZone(const char* dp, std::string* zone) { return dp; } -const char* ParseSubSeconds(const char* dp, std::chrono::nanoseconds* subseconds) { +const char* ParseSubSeconds(const char* dp, + std::chrono::nanoseconds* subseconds) { if (dp != nullptr) { if (*dp == '.') { int64_t v = 0; @@ -475,7 +497,7 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { } // namespace // Uses strptime(3) to parse the given input. Supports the same extended -// format specifiers as Format(), although %E#S and %E*S are treated +// format specifiers as format(), although %E#S and %E*S are treated // identically. // // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are @@ -487,8 +509,8 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { // // We also handle the %z specifier to accommodate platforms that do not // support the tm_gmtoff extension to std::tm. %Z is parsed but ignored. -bool Parse(const std::string& format, const std::string& input, - const TimeZone& tz, time_point* tpp, +bool parse(const std::string& format, const std::string& input, + const time_zone& tz, time_point* tpp, std::chrono::nanoseconds* ns) { // The unparsed input. const char* data = input.c_str(); // NUL terminated @@ -684,10 +706,10 @@ bool Parse(const std::string& format, const std::string& input, // If we saw %z or %Ez then we want to interpret the parsed fields in // UTC and then shift by that offset. Otherwise we want to interpret - // the fields directly in the passed TimeZone. - TimeZone ptz = tz; + // the fields directly in the passed time_zone. + time_zone ptz = tz; if (offset != kintmin) { - ptz = UTCTimeZone(); // Override tz. Offset applied later. + ptz = utc_time_zone(); // Override tz. Offset applied later. } else { offset = 0; // No offset from passed tz. } @@ -705,14 +727,20 @@ bool Parse(const std::string& format, const std::string& input, } else { year += 1900; } - const TimeInfo ti = MakeTimeInfo(year, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, ptz); + + // TODO: Eliminate extra normalization. + const civil_second cs(year, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); // Parse() fails if any normalization was done. That is, // parsing "Sep 31" will not produce the equivalent of "Oct 1". - if (ti.normalized) return false; + if (cs.year() != year || cs.month() != tm.tm_mon + 1 || + cs.day() != tm.tm_mday || cs.hour() != tm.tm_hour || + cs.minute() != tm.tm_min || cs.second() != tm.tm_sec) { + return false; + } - *tpp = ti.pre - seconds64(offset); + *tpp = ptz.lookup(cs).pre - sys_seconds(offset); *ns = subseconds; return true; } diff --git a/test/fmt_test.cc b/src/time_zone_format_test.cc similarity index 50% rename from test/fmt_test.cc rename to src/time_zone_format_test.cc index d7403034..1798ed76 100644 --- a/test/fmt_test.cc +++ b/src/time_zone_format_test.cc @@ -1,21 +1,21 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. -#include "src/cctz.h" +#include "time_zone.h" #include +#include #include #include #include @@ -40,12 +40,12 @@ namespace { // correct line numbers. #define ExpectTime(bd, y, m, d, hh, mm, ss, off, isdst, zone) \ do { \ - EXPECT_EQ(y, bd.year); \ - EXPECT_EQ(m, bd.month); \ - EXPECT_EQ(d, bd.day); \ - EXPECT_EQ(hh, bd.hour); \ - EXPECT_EQ(mm, bd.minute); \ - EXPECT_EQ(ss, bd.second); \ + EXPECT_EQ(y, bd.cs.year()); \ + EXPECT_EQ(m, bd.cs.month()); \ + EXPECT_EQ(d, bd.cs.day()); \ + EXPECT_EQ(hh, bd.cs.hour()); \ + EXPECT_EQ(mm, bd.cs.minute()); \ + EXPECT_EQ(ss, bd.cs.second()); \ EXPECT_EQ(off, bd.offset); \ EXPECT_EQ(isdst, bd.is_dst); \ EXPECT_EQ(zone, bd.abbr); \ @@ -60,69 +60,75 @@ const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; // A helper that tests the given format specifier by itself, and with leading // and trailing characters. For example: TestFormatSpecifier(tp, "%a", "Thu"). template -void TestFormatSpecifier(time_point tp, TimeZone tz, const std::string& fmt, +void TestFormatSpecifier(time_point tp, time_zone tz, const std::string& fmt, const std::string& ans) { - EXPECT_EQ(ans, Format(fmt, tp, tz)); - EXPECT_EQ("xxx " + ans, Format("xxx " + fmt, tp, tz)); - EXPECT_EQ(ans + " yyy", Format(fmt + " yyy", tp, tz)); - EXPECT_EQ("xxx " + ans + " yyy", Format("xxx " + fmt + " yyy", tp, tz)); + EXPECT_EQ(ans, format(fmt, tp, tz)) << fmt; + EXPECT_EQ("xxx " + ans, format("xxx " + fmt, tp, tz)); + EXPECT_EQ(ans + " yyy", format(fmt + " yyy", tp, tz)); + EXPECT_EQ("xxx " + ans + " yyy", format("xxx " + fmt + " yyy", tp, tz)); +} + +// A helper for converting a YMDhms to a time_point. +time_point MakeTime(int y, int m, int d, int hh, int mm, int ss, + const time_zone& tz) { + return tz.lookup(civil_second(y, m, d, hh, mm, ss)).pre; } } // namespace // -// Testing Format() +// Testing format() // TEST(Format, TimePointResolution) { using std::chrono::time_point_cast; const char kFmt[] = "%H:%M:%E*S"; - const TimeZone utc = UTCTimeZone(); + const time_zone utc = utc_time_zone(); const time_point t0 = system_clock::from_time_t(1420167845) + std::chrono::milliseconds(123) + std::chrono::microseconds(456) + std::chrono::nanoseconds(789); EXPECT_EQ("03:04:05.123456789", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); EXPECT_EQ("03:04:05.123456", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); EXPECT_EQ("03:04:05.123", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); EXPECT_EQ("03:04:05", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); EXPECT_EQ("03:04:05", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); EXPECT_EQ("03:04:00", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); EXPECT_EQ("03:00:00", - Format(kFmt, time_point_cast(t0), utc)); + format(kFmt, time_point_cast(t0), utc)); } TEST(Format, Basics) { - TimeZone tz = UTCTimeZone(); + time_zone tz = utc_time_zone(); time_point tp = system_clock::from_time_t(0); // Starts with a couple basic edge cases. - EXPECT_EQ("", Format("", tp, tz)); - EXPECT_EQ(" ", Format(" ", tp, tz)); - EXPECT_EQ(" ", Format(" ", tp, tz)); - EXPECT_EQ("xxx", Format("xxx", tp, tz)); + EXPECT_EQ("", format("", tp, tz)); + EXPECT_EQ(" ", format(" ", tp, tz)); + EXPECT_EQ(" ", format(" ", tp, tz)); + EXPECT_EQ("xxx", format("xxx", tp, tz)); std::string big(128, 'x'); - EXPECT_EQ(big, Format(big, tp, tz)); + EXPECT_EQ(big, format(big, tp, tz)); // Cause the 1024-byte buffer to grow. std::string bigger(100000, 'x'); - EXPECT_EQ(bigger, Format(bigger, tp, tz)); + EXPECT_EQ(bigger, format(bigger, tp, tz)); tp += hours(13) + minutes(4) + seconds(5); tp += milliseconds(6) + microseconds(7) + nanoseconds(8); - EXPECT_EQ("1970-01-01", Format("%Y-%m-%d", tp, tz)); - EXPECT_EQ("13:04:05", Format("%H:%M:%S", tp, tz)); - EXPECT_EQ("13:04:05.006", Format("%H:%M:%E3S", tp, tz)); - EXPECT_EQ("13:04:05.006007", Format("%H:%M:%E6S", tp, tz)); - EXPECT_EQ("13:04:05.006007008", Format("%H:%M:%E9S", tp, tz)); + EXPECT_EQ("1970-01-01", format("%Y-%m-%d", tp, tz)); + EXPECT_EQ("13:04:05", format("%H:%M:%S", tp, tz)); + EXPECT_EQ("13:04:05.006", format("%H:%M:%E3S", tp, tz)); + EXPECT_EQ("13:04:05.006007", format("%H:%M:%E6S", tp, tz)); + EXPECT_EQ("13:04:05.006007008", format("%H:%M:%E9S", tp, tz)); } TEST(Format, PosixConversions) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); auto tp = system_clock::from_time_t(0); TestFormatSpecifier(tp, tz, "%d", "01"); @@ -162,7 +168,7 @@ TEST(Format, PosixConversions) { } TEST(Format, LocaleSpecific) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); auto tp = system_clock::from_time_t(0); TestFormatSpecifier(tp, tz, "%a", "Thu"); @@ -171,7 +177,7 @@ TEST(Format, LocaleSpecific) { TestFormatSpecifier(tp, tz, "%B", "January"); // %c should at least produce the numeric year and time-of-day. - const std::string s = Format("%c", tp, UTCTimeZone()); + const std::string s = format("%c", tp, utc_time_zone()); EXPECT_THAT(s, HasSubstr("1970")); EXPECT_THAT(s, HasSubstr("00:00:00")); @@ -211,7 +217,7 @@ TEST(Format, LocaleSpecific) { } TEST(Format, Escaping) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); auto tp = system_clock::from_time_t(0); TestFormatSpecifier(tp, tz, "%%", "%"); @@ -230,416 +236,416 @@ TEST(Format, Escaping) { } TEST(Format, ExtendedSeconds) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); time_point tp = system_clock::from_time_t(0); tp += hours(3) + minutes(4) + seconds(5); tp += milliseconds(6) + microseconds(7) + nanoseconds(8); - EXPECT_EQ("11045", Format("%s", tp, tz)); - - EXPECT_EQ("03:04:05", Format("%H:%M:%E0S", tp, tz)); - EXPECT_EQ("03:04:05.0", Format("%H:%M:%E1S", tp, tz)); - EXPECT_EQ("03:04:05.00", Format("%H:%M:%E2S", tp, tz)); - EXPECT_EQ("03:04:05.006", Format("%H:%M:%E3S", tp, tz)); - EXPECT_EQ("03:04:05.0060", Format("%H:%M:%E4S", tp, tz)); - EXPECT_EQ("03:04:05.00600", Format("%H:%M:%E5S", tp, tz)); - EXPECT_EQ("03:04:05.006007", Format("%H:%M:%E6S", tp, tz)); - EXPECT_EQ("03:04:05.0060070", Format("%H:%M:%E7S", tp, tz)); - EXPECT_EQ("03:04:05.00600700", Format("%H:%M:%E8S", tp, tz)); - EXPECT_EQ("03:04:05.006007008", Format("%H:%M:%E9S", tp, tz)); - EXPECT_EQ("03:04:05.0060070080", Format("%H:%M:%E10S", tp, tz)); - EXPECT_EQ("03:04:05.00600700800", Format("%H:%M:%E11S", tp, tz)); - EXPECT_EQ("03:04:05.006007008000", Format("%H:%M:%E12S", tp, tz)); - EXPECT_EQ("03:04:05.0060070080000", Format("%H:%M:%E13S", tp, tz)); - EXPECT_EQ("03:04:05.00600700800000", Format("%H:%M:%E14S", tp, tz)); - EXPECT_EQ("03:04:05.006007008000000", Format("%H:%M:%E15S", tp, tz)); - - EXPECT_EQ("03:04:05.006007008", Format("%H:%M:%E*S", tp, tz)); + EXPECT_EQ("11045", format("%s", tp, tz)); + + EXPECT_EQ("03:04:05", format("%H:%M:%E0S", tp, tz)); + EXPECT_EQ("03:04:05.0", format("%H:%M:%E1S", tp, tz)); + EXPECT_EQ("03:04:05.00", format("%H:%M:%E2S", tp, tz)); + EXPECT_EQ("03:04:05.006", format("%H:%M:%E3S", tp, tz)); + EXPECT_EQ("03:04:05.0060", format("%H:%M:%E4S", tp, tz)); + EXPECT_EQ("03:04:05.00600", format("%H:%M:%E5S", tp, tz)); + EXPECT_EQ("03:04:05.006007", format("%H:%M:%E6S", tp, tz)); + EXPECT_EQ("03:04:05.0060070", format("%H:%M:%E7S", tp, tz)); + EXPECT_EQ("03:04:05.00600700", format("%H:%M:%E8S", tp, tz)); + EXPECT_EQ("03:04:05.006007008", format("%H:%M:%E9S", tp, tz)); + EXPECT_EQ("03:04:05.0060070080", format("%H:%M:%E10S", tp, tz)); + EXPECT_EQ("03:04:05.00600700800", format("%H:%M:%E11S", tp, tz)); + EXPECT_EQ("03:04:05.006007008000", format("%H:%M:%E12S", tp, tz)); + EXPECT_EQ("03:04:05.0060070080000", format("%H:%M:%E13S", tp, tz)); + EXPECT_EQ("03:04:05.00600700800000", format("%H:%M:%E14S", tp, tz)); + EXPECT_EQ("03:04:05.006007008000000", format("%H:%M:%E15S", tp, tz)); + + EXPECT_EQ("03:04:05.006007008", format("%H:%M:%E*S", tp, tz)); // Times before the Unix epoch. tp = system_clock::from_time_t(0) + microseconds(-1); EXPECT_EQ("1969-12-31 23:59:59.999999", - Format("%Y-%m-%d %H:%M:%E*S", tp, tz)); + format("%Y-%m-%d %H:%M:%E*S", tp, tz)); // Here is a "%E*S" case we got wrong for a while. While the first // instant below is correctly rendered as "...:07.333304", the second // one used to appear as "...:07.33330499999999999". tp = system_clock::from_time_t(0) + microseconds(1395024427333304); EXPECT_EQ("2014-03-17 02:47:07.333304", - Format("%Y-%m-%d %H:%M:%E*S", tp, tz)); + format("%Y-%m-%d %H:%M:%E*S", tp, tz)); tp += microseconds(1); EXPECT_EQ("2014-03-17 02:47:07.333305", - Format("%Y-%m-%d %H:%M:%E*S", tp, tz)); + format("%Y-%m-%d %H:%M:%E*S", tp, tz)); } TEST(Format, ExtendedOffset) { auto tp = system_clock::from_time_t(0); - TimeZone tz = UTCTimeZone(); + time_zone tz = utc_time_zone(); TestFormatSpecifier(tp, tz, "%Ez", "+00:00"); - EXPECT_TRUE(LoadTimeZone("America/New_York", &tz)); + EXPECT_TRUE(load_time_zone("America/New_York", &tz)); TestFormatSpecifier(tp, tz, "%Ez", "-05:00"); - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &tz)); + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); TestFormatSpecifier(tp, tz, "%Ez", "-08:00"); - EXPECT_TRUE(LoadTimeZone("Australia/Sydney", &tz)); + EXPECT_TRUE(load_time_zone("Australia/Sydney", &tz)); TestFormatSpecifier(tp, tz, "%Ez", "+10:00"); - EXPECT_TRUE(LoadTimeZone("Africa/Monrovia", &tz)); + EXPECT_TRUE(load_time_zone("Africa/Monrovia", &tz)); // The true offset is -00:44:30 but %z only gives (truncated) minutes. TestFormatSpecifier(tp, tz, "%z", "-0044"); TestFormatSpecifier(tp, tz, "%Ez", "-00:44"); } TEST(Format, ExtendedYears) { - const TimeZone utc = UTCTimeZone(); + const time_zone utc = utc_time_zone(); const char e4y_fmt[] = "%E4Y%m%d"; // no separators // %E4Y zero-pads the year to produce at least 4 chars, including the sign. auto tp = MakeTime(-999, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("-9991127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("-9991127", format(e4y_fmt, tp, utc)); tp = MakeTime(-99, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("-0991127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("-0991127", format(e4y_fmt, tp, utc)); tp = MakeTime(-9, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("-0091127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("-0091127", format(e4y_fmt, tp, utc)); tp = MakeTime(-1, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("-0011127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("-0011127", format(e4y_fmt, tp, utc)); tp = MakeTime(0, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("00001127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("00001127", format(e4y_fmt, tp, utc)); tp = MakeTime(1, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("00011127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("00011127", format(e4y_fmt, tp, utc)); tp = MakeTime(9, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("00091127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("00091127", format(e4y_fmt, tp, utc)); tp = MakeTime(99, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("00991127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("00991127", format(e4y_fmt, tp, utc)); tp = MakeTime(999, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("09991127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("09991127", format(e4y_fmt, tp, utc)); tp = MakeTime(9999, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("99991127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("99991127", format(e4y_fmt, tp, utc)); // When the year is outside [-999:9999], more than 4 chars are produced. tp = MakeTime(-1000, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("-10001127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("-10001127", format(e4y_fmt, tp, utc)); tp = MakeTime(10000, 11, 27, 0, 0, 0, utc); - EXPECT_EQ("100001127", Format(e4y_fmt, tp, utc)); + EXPECT_EQ("100001127", format(e4y_fmt, tp, utc)); } TEST(Format, RFC3339Format) { - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &tz)); + time_zone tz; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); time_point tp = MakeTime(1977, 6, 28, 9, 8, 7, tz); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += milliseconds(100); - EXPECT_EQ("1977-06-28T09:08:07.1-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.1-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += milliseconds(20); - EXPECT_EQ("1977-06-28T09:08:07.12-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.12-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += milliseconds(3); - EXPECT_EQ("1977-06-28T09:08:07.123-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.123-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += microseconds(400); - EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += microseconds(50); - EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += microseconds(6); - EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += nanoseconds(700); - EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += nanoseconds(80); - EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); tp += nanoseconds(9); EXPECT_EQ("1977-06-28T09:08:07.123456789-07:00", - Format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", Format(RFC3339_sec, tp, tz)); + format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); } TEST(Format, RFC1123Format) { // locale specific - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &tz)); + time_zone tz; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); auto tp = MakeTime(1977, 6, 28, 9, 8, 7, tz); - EXPECT_EQ("Tue, 28 Jun 1977 09:08:07 -0700", Format(RFC1123_full, tp, tz)); - EXPECT_EQ("28 Jun 1977 09:08:07 -0700", Format(RFC1123_no_wday, tp, tz)); + EXPECT_EQ("Tue, 28 Jun 1977 09:08:07 -0700", format(RFC1123_full, tp, tz)); + EXPECT_EQ("28 Jun 1977 09:08:07 -0700", format(RFC1123_no_wday, tp, tz)); } // -// Testing Parse() +// Testing parse() // TEST(Parse, TimePointResolution) { using std::chrono::time_point_cast; const char kFmt[] = "%H:%M:%E*S"; - const TimeZone utc = UTCTimeZone(); + const time_zone utc = utc_time_zone(); time_point tp_ns; - EXPECT_TRUE(Parse(kFmt, "03:04:05.123456789", utc, &tp_ns)); - EXPECT_EQ("03:04:05.123456789", Format(kFmt, tp_ns, utc)); - EXPECT_TRUE(Parse(kFmt, "03:04:05.123456", utc, &tp_ns)); - EXPECT_EQ("03:04:05.123456", Format(kFmt, tp_ns, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_ns)); + EXPECT_EQ("03:04:05.123456789", format(kFmt, tp_ns, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ns)); + EXPECT_EQ("03:04:05.123456", format(kFmt, tp_ns, utc)); time_point tp_us; - EXPECT_TRUE(Parse(kFmt, "03:04:05.123456789", utc, &tp_us)); - EXPECT_EQ("03:04:05.123456", Format(kFmt, tp_us, utc)); - EXPECT_TRUE(Parse(kFmt, "03:04:05.123456", utc, &tp_us)); - EXPECT_EQ("03:04:05.123456", Format(kFmt, tp_us, utc)); - EXPECT_TRUE(Parse(kFmt, "03:04:05.123", utc, &tp_us)); - EXPECT_EQ("03:04:05.123", Format(kFmt, tp_us, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_us)); + EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_us)); + EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_us)); + EXPECT_EQ("03:04:05.123", format(kFmt, tp_us, utc)); time_point tp_ms; - EXPECT_TRUE(Parse(kFmt, "03:04:05.123456", utc, &tp_ms)); - EXPECT_EQ("03:04:05.123", Format(kFmt, tp_ms, utc)); - EXPECT_TRUE(Parse(kFmt, "03:04:05.123", utc, &tp_ms)); - EXPECT_EQ("03:04:05.123", Format(kFmt, tp_ms, utc)); - EXPECT_TRUE(Parse(kFmt, "03:04:05", utc, &tp_ms)); - EXPECT_EQ("03:04:05", Format(kFmt, tp_ms, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ms)); + EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_ms)); + EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_ms)); + EXPECT_EQ("03:04:05", format(kFmt, tp_ms, utc)); time_point tp_s; - EXPECT_TRUE(Parse(kFmt, "03:04:05.123", utc, &tp_s)); - EXPECT_EQ("03:04:05", Format(kFmt, tp_s, utc)); - EXPECT_TRUE(Parse(kFmt, "03:04:05", utc, &tp_s)); - EXPECT_EQ("03:04:05", Format(kFmt, tp_s, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_s)); + EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_s)); + EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc)); time_point tp_m; - EXPECT_TRUE(Parse(kFmt, "03:04:05", utc, &tp_m)); - EXPECT_EQ("03:04:00", Format(kFmt, tp_m, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_m)); + EXPECT_EQ("03:04:00", format(kFmt, tp_m, utc)); time_point tp_h; - EXPECT_TRUE(Parse(kFmt, "03:04:05", utc, &tp_h)); - EXPECT_EQ("03:00:00", Format(kFmt, tp_h, utc)); + EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_h)); + EXPECT_EQ("03:00:00", format(kFmt, tp_h, utc)); } TEST(Parse, Basics) { - TimeZone tz = UTCTimeZone(); + time_zone tz = utc_time_zone(); time_point tp = system_clock::from_time_t(1234567890); // Simple edge cases. - EXPECT_TRUE(Parse("", "", tz, &tp)); + EXPECT_TRUE(parse("", "", tz, &tp)); EXPECT_EQ(system_clock::from_time_t(0), tp); // everything defaulted - EXPECT_TRUE(Parse(" ", " ", tz, &tp)); - EXPECT_TRUE(Parse(" ", " ", tz, &tp)); - EXPECT_TRUE(Parse("x", "x", tz, &tp)); - EXPECT_TRUE(Parse("xxx", "xxx", tz, &tp)); + EXPECT_TRUE(parse(" ", " ", tz, &tp)); + EXPECT_TRUE(parse(" ", " ", tz, &tp)); + EXPECT_TRUE(parse("x", "x", tz, &tp)); + EXPECT_TRUE(parse("xxx", "xxx", tz, &tp)); EXPECT_TRUE( - Parse("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 -0800", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); + parse("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 -0800", tz, &tp)); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 6, 29, 3, 8, 9, 0, false, "UTC"); } TEST(Parse, WithTimeZone) { - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &tz)); + time_zone tz; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); time_point tp; // We can parse a string without a UTC offset if we supply a timezone. - EXPECT_TRUE(Parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); + EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp)); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 6, 28, 19, 8, 9, -7 * 60 * 60, true, "PDT"); // But the timezone is ignored when a UTC offset is present. EXPECT_TRUE( - Parse("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 +0800", tz, &tp)); - bd = BreakTime(tp, UTCTimeZone()); + parse("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 +0800", tz, &tp)); + bd = utc_time_zone().lookup(tp); ExpectTime(bd, 2013, 6, 28, 11, 8, 9, 0, false, "UTC"); - // Check a skipped time (a Spring DST transition). Parse() returns + // Check a skipped time (a Spring DST transition). parse() returns // the preferred-offset result, as defined for ConvertDateTime(). - EXPECT_TRUE(Parse("%Y-%m-%d %H:%M:%S", "2011-03-13 02:15:00", tz, &tp)); - bd = BreakTime(tp, tz); + EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2011-03-13 02:15:00", tz, &tp)); + bd = tz.lookup(tp); ExpectTime(bd, 2011, 3, 13, 3, 15, 0, -7 * 60 * 60, true, "PDT"); - // Check a repeated time (a Fall DST transition). Parse() returns + // Check a repeated time (a Fall DST transition). parse() returns // the preferred-offset result, as defined for ConvertDateTime(). - EXPECT_TRUE(Parse("%Y-%m-%d %H:%M:%S", "2011-11-06 01:15:00", tz, &tp)); - bd = BreakTime(tp, tz); + EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2011-11-06 01:15:00", tz, &tp)); + bd = tz.lookup(tp); ExpectTime(bd, 2011, 11, 6, 1, 15, 0, -7 * 60 * 60, true, "PDT"); } TEST(Parse, LeapSecond) { - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &tz)); + time_zone tz; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); time_point tp; // ":59" -> ":59" - EXPECT_TRUE(Parse(RFC3339_full, "2013-06-28T07:08:59-08:00", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); + EXPECT_TRUE(parse(RFC3339_full, "2013-06-28T07:08:59-08:00", tz, &tp)); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 6, 28, 8, 8, 59, -7 * 60 * 60, true, "PDT"); // ":59.5" -> ":59.5" - EXPECT_TRUE(Parse(RFC3339_full, "2013-06-28T07:08:59.5-08:00", tz, &tp)); - bd = BreakTime(tp, tz); + EXPECT_TRUE(parse(RFC3339_full, "2013-06-28T07:08:59.5-08:00", tz, &tp)); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 6, 28, 8, 8, 59, -7 * 60 * 60, true, "PDT"); // ":60" -> ":00" - EXPECT_TRUE(Parse(RFC3339_full, "2013-06-28T07:08:60-08:00", tz, &tp)); - bd = BreakTime(tp, tz); + EXPECT_TRUE(parse(RFC3339_full, "2013-06-28T07:08:60-08:00", tz, &tp)); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 6, 28, 8, 9, 0, -7 * 60 * 60, true, "PDT"); // ":60.5" -> ":00.0" - EXPECT_TRUE(Parse(RFC3339_full, "2013-06-28T07:08:60.5-08:00", tz, &tp)); - bd = BreakTime(tp, tz); + EXPECT_TRUE(parse(RFC3339_full, "2013-06-28T07:08:60.5-08:00", tz, &tp)); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 6, 28, 8, 9, 0, -7 * 60 * 60, true, "PDT"); // ":61" -> error - EXPECT_FALSE(Parse(RFC3339_full, "2013-06-28T07:08:61-08:00", tz, &tp)); + EXPECT_FALSE(parse(RFC3339_full, "2013-06-28T07:08:61-08:00", tz, &tp)); } TEST(Parse, ErrorCases) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); auto tp = system_clock::from_time_t(0); // Illegal trailing data. - EXPECT_FALSE(Parse("%S", "123", tz, &tp)); + EXPECT_FALSE(parse("%S", "123", tz, &tp)); // Can't parse an illegal format specifier. - EXPECT_FALSE(Parse("%Q", "x", tz, &tp)); + EXPECT_FALSE(parse("%Q", "x", tz, &tp)); // Fails because of trailing, unparsed data "blah". - EXPECT_FALSE(Parse("%m-%d", "2-3 blah", tz, &tp)); + EXPECT_FALSE(parse("%m-%d", "2-3 blah", tz, &tp)); // Trailing whitespace is allowed. - EXPECT_TRUE(Parse("%m-%d", "2-3 ", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, UTCTimeZone()).month); - EXPECT_EQ(3, BreakTime(tp, UTCTimeZone()).day); + EXPECT_TRUE(parse("%m-%d", "2-3 ", tz, &tp)); + EXPECT_EQ(2, utc_time_zone().lookup(tp).cs.month()); + EXPECT_EQ(3, utc_time_zone().lookup(tp).cs.day()); // Feb 31 requires normalization. - EXPECT_FALSE(Parse("%m-%d", "2-31", tz, &tp)); + EXPECT_FALSE(parse("%m-%d", "2-31", tz, &tp)); // Check that we cannot have spaces in UTC offsets. - EXPECT_TRUE(Parse("%z", "-0203", tz, &tp)); - EXPECT_FALSE(Parse("%z", "- 2 3", tz, &tp)); - EXPECT_TRUE(Parse("%Ez", "-02:03", tz, &tp)); - EXPECT_FALSE(Parse("%Ez", "- 2: 3", tz, &tp)); + EXPECT_TRUE(parse("%z", "-0203", tz, &tp)); + EXPECT_FALSE(parse("%z", "- 2 3", tz, &tp)); + EXPECT_TRUE(parse("%Ez", "-02:03", tz, &tp)); + EXPECT_FALSE(parse("%Ez", "- 2: 3", tz, &tp)); // Check that we reject other malformed UTC offsets. - EXPECT_FALSE(Parse("%Ez", "+-08:00", tz, &tp)); - EXPECT_FALSE(Parse("%Ez", "-+08:00", tz, &tp)); + EXPECT_FALSE(parse("%Ez", "+-08:00", tz, &tp)); + EXPECT_FALSE(parse("%Ez", "-+08:00", tz, &tp)); // Check that we do not accept "-0" in fields that allow zero. - EXPECT_FALSE(Parse("%Y", "-0", tz, &tp)); - EXPECT_FALSE(Parse("%E4Y", "-0", tz, &tp)); - EXPECT_FALSE(Parse("%H", "-0", tz, &tp)); - EXPECT_FALSE(Parse("%M", "-0", tz, &tp)); - EXPECT_FALSE(Parse("%S", "-0", tz, &tp)); - EXPECT_FALSE(Parse("%z", "+-000", tz, &tp)); - EXPECT_FALSE(Parse("%Ez", "+-0:00", tz, &tp)); - EXPECT_FALSE(Parse("%z", "-00-0", tz, &tp)); - EXPECT_FALSE(Parse("%Ez", "-00:-0", tz, &tp)); + EXPECT_FALSE(parse("%Y", "-0", tz, &tp)); + EXPECT_FALSE(parse("%E4Y", "-0", tz, &tp)); + EXPECT_FALSE(parse("%H", "-0", tz, &tp)); + EXPECT_FALSE(parse("%M", "-0", tz, &tp)); + EXPECT_FALSE(parse("%S", "-0", tz, &tp)); + EXPECT_FALSE(parse("%z", "+-000", tz, &tp)); + EXPECT_FALSE(parse("%Ez", "+-0:00", tz, &tp)); + EXPECT_FALSE(parse("%z", "-00-0", tz, &tp)); + EXPECT_FALSE(parse("%Ez", "-00:-0", tz, &tp)); } TEST(Parse, PosixConversions) { - TimeZone tz = UTCTimeZone(); + time_zone tz = utc_time_zone(); auto tp = system_clock::from_time_t(0); const auto reset = MakeTime(1977, 6, 28, 9, 8, 7, tz); tp = reset; - EXPECT_TRUE(Parse("%d", "15", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).day); + EXPECT_TRUE(parse("%d", "15", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.day()); // %e is an extension, but is supported internally. tp = reset; - EXPECT_TRUE(Parse("%e", "15", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).day); // Equivalent to %d + EXPECT_TRUE(parse("%e", "15", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.day()); // Equivalent to %d tp = reset; - EXPECT_TRUE(Parse("%H", "17", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).hour); + EXPECT_TRUE(parse("%H", "17", tz, &tp)); + EXPECT_EQ(17, tz.lookup(tp).cs.hour()); tp = reset; - EXPECT_TRUE(Parse("%I", "5", tz, &tp)); - EXPECT_EQ(5, BreakTime(tp, tz).hour); + EXPECT_TRUE(parse("%I", "5", tz, &tp)); + EXPECT_EQ(5, tz.lookup(tp).cs.hour()); // %j is parsed but ignored. - EXPECT_TRUE(Parse("%j", "32", tz, &tp)); + EXPECT_TRUE(parse("%j", "32", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%m", "11", tz, &tp)); - EXPECT_EQ(11, BreakTime(tp, tz).month); + EXPECT_TRUE(parse("%m", "11", tz, &tp)); + EXPECT_EQ(11, tz.lookup(tp).cs.month()); tp = reset; - EXPECT_TRUE(Parse("%M", "33", tz, &tp)); - EXPECT_EQ(33, BreakTime(tp, tz).minute); + EXPECT_TRUE(parse("%M", "33", tz, &tp)); + EXPECT_EQ(33, tz.lookup(tp).cs.minute()); tp = reset; - EXPECT_TRUE(Parse("%S", "55", tz, &tp)); - EXPECT_EQ(55, BreakTime(tp, tz).second); + EXPECT_TRUE(parse("%S", "55", tz, &tp)); + EXPECT_EQ(55, tz.lookup(tp).cs.second()); // %U is parsed but ignored. - EXPECT_TRUE(Parse("%U", "15", tz, &tp)); + EXPECT_TRUE(parse("%U", "15", tz, &tp)); // %w is parsed but ignored. - EXPECT_TRUE(Parse("%w", "2", tz, &tp)); + EXPECT_TRUE(parse("%w", "2", tz, &tp)); // %W is parsed but ignored. - EXPECT_TRUE(Parse("%W", "22", tz, &tp)); + EXPECT_TRUE(parse("%W", "22", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%y", "04", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%y", "04", tz, &tp)); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); tp = reset; - EXPECT_TRUE(Parse("%Y", "2004", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%Y", "2004", tz, &tp)); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); - EXPECT_TRUE(Parse("%%", "%", tz, &tp)); + EXPECT_TRUE(parse("%%", "%", tz, &tp)); #if defined(__linux__) // SU/C99/TZ extensions tp = reset; - EXPECT_TRUE(Parse("%C", "20", tz, &tp)); - EXPECT_EQ(2000, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%C", "20", tz, &tp)); + EXPECT_EQ(2000, tz.lookup(tp).cs.year()); tp = reset; - EXPECT_TRUE(Parse("%D", "02/03/04", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); - EXPECT_EQ(3, BreakTime(tp, tz).day); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%D", "02/03/04", tz, &tp)); + EXPECT_EQ(2, tz.lookup(tp).cs.month()); + EXPECT_EQ(3, tz.lookup(tp).cs.day()); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); - EXPECT_TRUE(Parse("%n", "\n", tz, &tp)); + EXPECT_TRUE(parse("%n", "\n", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%R", "03:44", tz, &tp)); - EXPECT_EQ(3, BreakTime(tp, tz).hour); - EXPECT_EQ(44, BreakTime(tp, tz).minute); + EXPECT_TRUE(parse("%R", "03:44", tz, &tp)); + EXPECT_EQ(3, tz.lookup(tp).cs.hour()); + EXPECT_EQ(44, tz.lookup(tp).cs.minute()); - EXPECT_TRUE(Parse("%t", "\t\v\f\n\r ", tz, &tp)); + EXPECT_TRUE(parse("%t", "\t\v\f\n\r ", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%T", "03:44:55", tz, &tp)); - EXPECT_EQ(3, BreakTime(tp, tz).hour); - EXPECT_EQ(44, BreakTime(tp, tz).minute); - EXPECT_EQ(55, BreakTime(tp, tz).second); + EXPECT_TRUE(parse("%T", "03:44:55", tz, &tp)); + EXPECT_EQ(3, tz.lookup(tp).cs.hour()); + EXPECT_EQ(44, tz.lookup(tp).cs.minute()); + EXPECT_EQ(55, tz.lookup(tp).cs.second()); tp = reset; - EXPECT_TRUE(Parse("%s", "1234567890", tz, &tp)); + EXPECT_TRUE(parse("%s", "1234567890", tz, &tp)); EXPECT_EQ(system_clock::from_time_t(1234567890), tp); // %s conversion, like %z/%Ez, pays no heed to the optional zone. - TimeZone lax; - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &lax)); + time_zone lax; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax)); tp = reset; - EXPECT_TRUE(Parse("%s", "1234567890", lax, &tp)); + EXPECT_TRUE(parse("%s", "1234567890", lax, &tp)); EXPECT_EQ(system_clock::from_time_t(1234567890), tp); // This is most important when the time has the same YMDhms @@ -647,89 +653,89 @@ TEST(Parse, PosixConversions) { // 1414917000 in US/Pacific -> Sun Nov 2 01:30:00 2014 (PDT) // 1414920600 in US/Pacific -> Sun Nov 2 01:30:00 2014 (PST) tp = reset; - EXPECT_TRUE(Parse("%s", "1414917000", lax, &tp)); + EXPECT_TRUE(parse("%s", "1414917000", lax, &tp)); EXPECT_EQ(system_clock::from_time_t(1414917000), tp); tp = reset; - EXPECT_TRUE(Parse("%s", "1414920600", lax, &tp)); + EXPECT_TRUE(parse("%s", "1414920600", lax, &tp)); EXPECT_EQ(system_clock::from_time_t(1414920600), tp); #endif } TEST(Parse, LocaleSpecific) { - TimeZone tz = UTCTimeZone(); + time_zone tz = utc_time_zone(); auto tp = system_clock::from_time_t(0); const auto reset = MakeTime(1977, 6, 28, 9, 8, 7, tz); // %a is parsed but ignored. - EXPECT_TRUE(Parse("%a", "Mon", tz, &tp)); + EXPECT_TRUE(parse("%a", "Mon", tz, &tp)); // %A is parsed but ignored. - EXPECT_TRUE(Parse("%A", "Monday", tz, &tp)); + EXPECT_TRUE(parse("%A", "Monday", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%b", "Feb", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); + EXPECT_TRUE(parse("%b", "Feb", tz, &tp)); + EXPECT_EQ(2, tz.lookup(tp).cs.month()); tp = reset; - EXPECT_TRUE(Parse("%B", "February", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); + EXPECT_TRUE(parse("%B", "February", tz, &tp)); + EXPECT_EQ(2, tz.lookup(tp).cs.month()); // %p is parsed but ignored if it's alone. But it's used with %I. - EXPECT_TRUE(Parse("%p", "AM", tz, &tp)); + EXPECT_TRUE(parse("%p", "AM", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%I %p", "5 PM", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).hour); + EXPECT_TRUE(parse("%I %p", "5 PM", tz, &tp)); + EXPECT_EQ(17, tz.lookup(tp).cs.hour()); tp = reset; - EXPECT_TRUE(Parse("%x", "02/03/04", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); - EXPECT_EQ(3, BreakTime(tp, tz).day); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%x", "02/03/04", tz, &tp)); + EXPECT_EQ(2, tz.lookup(tp).cs.month()); + EXPECT_EQ(3, tz.lookup(tp).cs.day()); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); tp = reset; - EXPECT_TRUE(Parse("%X", "15:44:55", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).hour); - EXPECT_EQ(44, BreakTime(tp, tz).minute); - EXPECT_EQ(55, BreakTime(tp, tz).second); + EXPECT_TRUE(parse("%X", "15:44:55", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.hour()); + EXPECT_EQ(44, tz.lookup(tp).cs.minute()); + EXPECT_EQ(55, tz.lookup(tp).cs.second()); #if defined(__linux__) // SU/C99/TZ extensions tp = reset; - EXPECT_TRUE(Parse("%h", "Feb", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); // Equivalent to %b + EXPECT_TRUE(parse("%h", "Feb", tz, &tp)); + EXPECT_EQ(2, tz.lookup(tp).cs.month()); // Equivalent to %b tp = reset; - EXPECT_TRUE(Parse("%l %p", "5 PM", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).hour); + EXPECT_TRUE(parse("%l %p", "5 PM", tz, &tp)); + EXPECT_EQ(17, tz.lookup(tp).cs.hour()); tp = reset; - EXPECT_TRUE(Parse("%r", "03:44:55 PM", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).hour); - EXPECT_EQ(44, BreakTime(tp, tz).minute); - EXPECT_EQ(55, BreakTime(tp, tz).second); + EXPECT_TRUE(parse("%r", "03:44:55 PM", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.hour()); + EXPECT_EQ(44, tz.lookup(tp).cs.minute()); + EXPECT_EQ(55, tz.lookup(tp).cs.second()); tp = reset; - EXPECT_TRUE(Parse("%Ec", "Tue Nov 19 05:06:07 2013", tz, &tp)); + EXPECT_TRUE(parse("%Ec", "Tue Nov 19 05:06:07 2013", tz, &tp)); EXPECT_EQ(MakeTime(2013, 11, 19, 5, 6, 7, tz), tp); // Modified conversion specifiers %E_ tp = reset; - EXPECT_TRUE(Parse("%EC", "20", tz, &tp)); - EXPECT_EQ(2000, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%EC", "20", tz, &tp)); + EXPECT_EQ(2000, tz.lookup(tp).cs.year()); tp = reset; - EXPECT_TRUE(Parse("%Ex", "02/03/04", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); - EXPECT_EQ(3, BreakTime(tp, tz).day); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%Ex", "02/03/04", tz, &tp)); + EXPECT_EQ(2, tz.lookup(tp).cs.month()); + EXPECT_EQ(3, tz.lookup(tp).cs.day()); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); tp = reset; - EXPECT_TRUE(Parse("%EX", "15:44:55", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).hour); - EXPECT_EQ(44, BreakTime(tp, tz).minute); - EXPECT_EQ(55, BreakTime(tp, tz).second); + EXPECT_TRUE(parse("%EX", "15:44:55", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.hour()); + EXPECT_EQ(44, tz.lookup(tp).cs.minute()); + EXPECT_EQ(55, tz.lookup(tp).cs.second()); // %Ey, the year offset from %EC, doesn't really make sense alone as there // is no way to represent it in tm_year (%EC is not simply the century). @@ -738,83 +744,83 @@ TEST(Parse, LocaleSpecific) { // skip the %Ey case. #if 0 tp = reset; - EXPECT_TRUE(Parse("%Ey", "04", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%Ey", "04", tz, &tp)); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); #endif tp = reset; - EXPECT_TRUE(Parse("%EY", "2004", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%EY", "2004", tz, &tp)); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); // Modified conversion specifiers %O_ tp = reset; - EXPECT_TRUE(Parse("%Od", "15", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).day); + EXPECT_TRUE(parse("%Od", "15", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.day()); tp = reset; - EXPECT_TRUE(Parse("%Oe", "15", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).day); // Equivalent to %d + EXPECT_TRUE(parse("%Oe", "15", tz, &tp)); + EXPECT_EQ(15, tz.lookup(tp).cs.day()); // Equivalent to %d tp = reset; - EXPECT_TRUE(Parse("%OH", "17", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).hour); + EXPECT_TRUE(parse("%OH", "17", tz, &tp)); + EXPECT_EQ(17, tz.lookup(tp).cs.hour()); tp = reset; - EXPECT_TRUE(Parse("%OI", "5", tz, &tp)); - EXPECT_EQ(5, BreakTime(tp, tz).hour); + EXPECT_TRUE(parse("%OI", "5", tz, &tp)); + EXPECT_EQ(5, tz.lookup(tp).cs.hour()); tp = reset; - EXPECT_TRUE(Parse("%Om", "11", tz, &tp)); - EXPECT_EQ(11, BreakTime(tp, tz).month); + EXPECT_TRUE(parse("%Om", "11", tz, &tp)); + EXPECT_EQ(11, tz.lookup(tp).cs.month()); tp = reset; - EXPECT_TRUE(Parse("%OM", "33", tz, &tp)); - EXPECT_EQ(33, BreakTime(tp, tz).minute); + EXPECT_TRUE(parse("%OM", "33", tz, &tp)); + EXPECT_EQ(33, tz.lookup(tp).cs.minute()); tp = reset; - EXPECT_TRUE(Parse("%OS", "55", tz, &tp)); - EXPECT_EQ(55, BreakTime(tp, tz).second); + EXPECT_TRUE(parse("%OS", "55", tz, &tp)); + EXPECT_EQ(55, tz.lookup(tp).cs.second()); // %OU is parsed but ignored. - EXPECT_TRUE(Parse("%OU", "15", tz, &tp)); + EXPECT_TRUE(parse("%OU", "15", tz, &tp)); // %Ow is parsed but ignored. - EXPECT_TRUE(Parse("%Ow", "2", tz, &tp)); + EXPECT_TRUE(parse("%Ow", "2", tz, &tp)); // %OW is parsed but ignored. - EXPECT_TRUE(Parse("%OW", "22", tz, &tp)); + EXPECT_TRUE(parse("%OW", "22", tz, &tp)); tp = reset; - EXPECT_TRUE(Parse("%Oy", "04", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); + EXPECT_TRUE(parse("%Oy", "04", tz, &tp)); + EXPECT_EQ(2004, tz.lookup(tp).cs.year()); #endif } TEST(Parse, ExtendedSeconds) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); // Here is a "%E*S" case we got wrong for a while. The fractional // part of the first instant is less than 2^31 and was correctly // parsed, while the second (and any subsecond field >=2^31) failed. time_point tp = system_clock::from_time_t(0); - EXPECT_TRUE(Parse("%E*S", "0.2147483647", tz, &tp)); + EXPECT_TRUE(parse("%E*S", "0.2147483647", tz, &tp)); EXPECT_EQ(system_clock::from_time_t(0) + nanoseconds(214748364), tp); tp = system_clock::from_time_t(0); - EXPECT_TRUE(Parse("%E*S", "0.2147483648", tz, &tp)); + EXPECT_TRUE(parse("%E*S", "0.2147483648", tz, &tp)); EXPECT_EQ(system_clock::from_time_t(0) + nanoseconds(214748364), tp); // We should also be able to specify long strings of digits far // beyond the current resolution and have them convert the same way. tp = system_clock::from_time_t(0); - EXPECT_TRUE(Parse( + EXPECT_TRUE(parse( "%E*S", "0.214748364801234567890123456789012345678901234567890123456789", tz, &tp)); EXPECT_EQ(system_clock::from_time_t(0) + nanoseconds(214748364), tp); } TEST(Parse, ExtendedSecondsScan) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); time_point tp; for (int64_t ms = 0; ms < 1000; ms += 111) { for (int64_t us = 0; us < 1000; us += 27) { @@ -826,7 +832,7 @@ TEST(Parse, ExtendedSecondsScan) { oss << "0." << std::setfill('0') << std::setw(3); oss << ms << std::setw(3) << us << std::setw(3) << ns; const std::string input = oss.str(); - EXPECT_TRUE(Parse("%E*S", input, tz, &tp)); + EXPECT_TRUE(parse("%E*S", input, tz, &tp)); EXPECT_EQ(expected, tp) << input; } } @@ -834,123 +840,123 @@ TEST(Parse, ExtendedSecondsScan) { } TEST(Parse, ExtendedOffset) { - const TimeZone utc = UTCTimeZone(); - time_point tp; + const time_zone utc = utc_time_zone(); + time_point tp; // %z against +-HHMM. - EXPECT_TRUE(Parse("%z", "+0000", utc, &tp)); + EXPECT_TRUE(parse("%z", "+0000", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%z", "-1234", utc, &tp)); + EXPECT_TRUE(parse("%z", "-1234", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 12, 34, 0, utc), tp); - EXPECT_TRUE(Parse("%z", "+1234", utc, &tp)); + EXPECT_TRUE(parse("%z", "+1234", utc, &tp)); EXPECT_EQ(MakeTime(1969, 12, 31, 11, 26, 0, utc), tp); - EXPECT_FALSE(Parse("%z", "-123", utc, &tp)); + EXPECT_FALSE(parse("%z", "-123", utc, &tp)); // %z against +-HH. - EXPECT_TRUE(Parse("%z", "+00", utc, &tp)); + EXPECT_TRUE(parse("%z", "+00", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%z", "-12", utc, &tp)); + EXPECT_TRUE(parse("%z", "-12", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 12, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%z", "+12", utc, &tp)); + EXPECT_TRUE(parse("%z", "+12", utc, &tp)); EXPECT_EQ(MakeTime(1969, 12, 31, 12, 0, 0, utc), tp); - EXPECT_FALSE(Parse("%z", "-1", utc, &tp)); + EXPECT_FALSE(parse("%z", "-1", utc, &tp)); // %Ez against +-HH:MM. - EXPECT_TRUE(Parse("%Ez", "+00:00", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "+00:00", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%Ez", "-12:34", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "-12:34", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 12, 34, 0, utc), tp); - EXPECT_TRUE(Parse("%Ez", "+12:34", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "+12:34", utc, &tp)); EXPECT_EQ(MakeTime(1969, 12, 31, 11, 26, 0, utc), tp); - EXPECT_FALSE(Parse("%Ez", "-12:3", utc, &tp)); + EXPECT_FALSE(parse("%Ez", "-12:3", utc, &tp)); // %Ez against +-HHMM. - EXPECT_TRUE(Parse("%Ez", "+0000", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "+0000", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%Ez", "-1234", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "-1234", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 12, 34, 0, utc), tp); - EXPECT_TRUE(Parse("%Ez", "+1234", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "+1234", utc, &tp)); EXPECT_EQ(MakeTime(1969, 12, 31, 11, 26, 0, utc), tp); - EXPECT_FALSE(Parse("%Ez", "-123", utc, &tp)); + EXPECT_FALSE(parse("%Ez", "-123", utc, &tp)); // %Ez against +-HH. - EXPECT_TRUE(Parse("%Ez", "+00", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "+00", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%Ez", "-12", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "-12", utc, &tp)); EXPECT_EQ(MakeTime(1970, 1, 1, 12, 0, 0, utc), tp); - EXPECT_TRUE(Parse("%Ez", "+12", utc, &tp)); + EXPECT_TRUE(parse("%Ez", "+12", utc, &tp)); EXPECT_EQ(MakeTime(1969, 12, 31, 12, 0, 0, utc), tp); - EXPECT_FALSE(Parse("%Ez", "-1", utc, &tp)); + EXPECT_FALSE(parse("%Ez", "-1", utc, &tp)); } TEST(Parse, ExtendedYears) { - const TimeZone utc = UTCTimeZone(); + const time_zone utc = utc_time_zone(); const char e4y_fmt[] = "%E4Y%m%d"; // no separators - time_point tp; + time_point tp; // %E4Y consumes exactly four chars, including any sign. - EXPECT_TRUE(Parse(e4y_fmt, "-9991127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "-9991127", utc, &tp)); EXPECT_EQ(MakeTime(-999, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "-0991127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "-0991127", utc, &tp)); EXPECT_EQ(MakeTime(-99, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "-0091127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "-0091127", utc, &tp)); EXPECT_EQ(MakeTime(-9, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "-0011127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "-0011127", utc, &tp)); EXPECT_EQ(MakeTime(-1, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "00001127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "00001127", utc, &tp)); EXPECT_EQ(MakeTime(0, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "00011127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "00011127", utc, &tp)); EXPECT_EQ(MakeTime(1, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "00091127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "00091127", utc, &tp)); EXPECT_EQ(MakeTime(9, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "00991127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "00991127", utc, &tp)); EXPECT_EQ(MakeTime(99, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "09991127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "09991127", utc, &tp)); EXPECT_EQ(MakeTime(999, 11, 27, 0, 0, 0, utc), tp); - EXPECT_TRUE(Parse(e4y_fmt, "99991127", utc, &tp)); + EXPECT_TRUE(parse(e4y_fmt, "99991127", utc, &tp)); EXPECT_EQ(MakeTime(9999, 11, 27, 0, 0, 0, utc), tp); // When the year is outside [-999:9999], the parse fails. - EXPECT_FALSE(Parse(e4y_fmt, "-10001127", utc, &tp)); - EXPECT_FALSE(Parse(e4y_fmt, "100001127", utc, &tp)); + EXPECT_FALSE(parse(e4y_fmt, "-10001127", utc, &tp)); + EXPECT_FALSE(parse(e4y_fmt, "100001127", utc, &tp)); } TEST(Parse, RFC3339Format) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); time_point tp; - EXPECT_TRUE(Parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp)); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2014, 2, 12, 20, 21, 0, 0, false, "UTC"); // Check that %Ez also accepts "Z" as a synonym for "+00:00". time_point tp2; - EXPECT_TRUE(Parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2)); + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2)); EXPECT_EQ(tp, tp2); } // -// Roundtrip test for Format()/Parse(). +// Roundtrip test for format()/parse(). // TEST(FormatParse, RoundTrip) { - TimeZone lax; - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &lax)); + time_zone lax; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax)); const auto in = MakeTime(1977, 6, 28, 9, 8, 7, lax); const auto subseconds = nanoseconds(654321); // RFC3339, which renders subseconds. { time_point out; - const std::string s = Format(RFC3339_full, in + subseconds, lax); - EXPECT_TRUE(Parse(RFC3339_full, s, lax, &out)) << s; + const std::string s = format(RFC3339_full, in + subseconds, lax); + EXPECT_TRUE(parse(RFC3339_full, s, lax, &out)) << s; EXPECT_EQ(in + subseconds, out); // RFC3339_full includes %Ez } // RFC1123, which only does whole seconds. { time_point out; - const std::string s = Format(RFC1123_full, in, lax); - EXPECT_TRUE(Parse(RFC1123_full, s, lax, &out)) << s; + const std::string s = format(RFC1123_full, in, lax); + EXPECT_TRUE(parse(RFC1123_full, s, lax, &out)) << s; EXPECT_EQ(in, out); // RFC1123_full includes %z } @@ -958,9 +964,9 @@ TEST(FormatParse, RoundTrip) { // but only in the 0-offset timezone. { time_point out; - TimeZone utc = UTCTimeZone(); - const std::string s = Format("%c", in, utc); - EXPECT_TRUE(Parse("%c", s, utc, &out)) << s; + time_zone utc = utc_time_zone(); + const std::string s = format("%c", in, utc); + EXPECT_TRUE(parse("%c", s, utc, &out)) << s; EXPECT_EQ(in, out); } } diff --git a/src/cctz_if.cc b/src/time_zone_if.cc similarity index 58% rename from src/cctz_if.cc rename to src/time_zone_if.cc index 4f0b53ed..675474be 100644 --- a/src/cctz_if.cc +++ b/src/time_zone_if.cc @@ -1,21 +1,20 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. -#include "src/cctz_if.h" -#include "src/cctz_info.h" -#include "src/cctz_libc.h" +#include "time_zone_if.h" +#include "time_zone_info.h" +#include "time_zone_libc.h" namespace cctz { diff --git a/src/time_zone_if.h b/src/time_zone_if.h new file mode 100644 index 00000000..57a6cde7 --- /dev/null +++ b/src/time_zone_if.h @@ -0,0 +1,93 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +#ifndef CCTZ_TIME_ZONE_IF_H_ +#define CCTZ_TIME_ZONE_IF_H_ + +#include +#include +#include + +#include "civil_time.h" +#include "time_zone.h" + +namespace cctz { + +// The calendar and wall-clock (a.k.a. "civil time") components of a +// time_point in a certain time_zone. A better std::tm. Note that we +// cannot use time_zone::absolute_lookup because we need a 64-bit year. +struct Breakdown { + int64_t year; // year (e.g., 2013) + int month; // month of year [1:12] + int day; // day of month [1:31] + int hour; // hour of day [0:23] + int minute; // minute of hour [0:59] + int second; // second of minute [0:59] + int weekday; // 1==Mon, ..., 7=Sun + int yearday; // day of year [1:366] + + // Note: The following fields exist for backward compatibility with older + // APIs. Accessing these fields directly is a sign of imprudent logic in the + // calling code. Modern time-related code should only access this data + // indirectly by way of cctz::format(). + int offset; // seconds east of UTC + bool is_dst; // is offset non-standard? + std::string abbr; // time-zone abbreviation (e.g., "PST") +}; + +// A TimeInfo represents the conversion of year, month, day, hour, minute, +// and second values in a particular time_zone to a time instant. +struct TimeInfo { + time_zone::civil_lookup::civil_kind kind; + time_point pre; // Uses the pre-transition offset + time_point trans; + time_point post; // Uses the post-transition offset + bool normalized; +}; + +// A simple interface used to hide time-zone complexities from time_zone::Impl. +// Subclasses implement the functions for civil-time conversions in the zone. +class TimeZoneIf { + public: + // A factory function for TimeZoneIf implementations. + static std::unique_ptr Load(const std::string& name); + + virtual ~TimeZoneIf() {} + + virtual Breakdown BreakTime(const time_point& tp) const = 0; + virtual TimeInfo MakeTimeInfo(int64_t year, int mon, int day, + int hour, int min, int sec) const = 0; + + protected: + TimeZoneIf() {} +}; + +// Converts tp to a count of seconds since the Unix epoch. +inline int64_t ToUnixSeconds(const time_point& tp) { + return (tp - std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0))) + .count(); +} + +// Converts a count of seconds since the Unix epoch to a +// time_point. +inline time_point FromUnixSeconds(int64_t t) { + return std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(t); +} + +} // namespace cctz + +#endif // CCTZ_TIME_ZONE_IF_H_ diff --git a/src/time_zone_impl.cc b/src/time_zone_impl.cc new file mode 100644 index 00000000..2792601a --- /dev/null +++ b/src/time_zone_impl.cc @@ -0,0 +1,125 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +#include "time_zone_impl.h" + +#include +#include + +namespace cctz { + +namespace { + +// time_zone::Impls are linked into a map to support fast lookup by name. +typedef std::map TimeZoneImplByName; +TimeZoneImplByName* time_zone_map = nullptr; + +// Mutual exclusion for time_zone_map. +std::mutex time_zone_mutex; + +// The utc_time_zone(). Also used for time zones that fail to load. +const time_zone::Impl* utc_zone = nullptr; + +// utc_zone should only be referenced in a thread that has just done +// a LoadUTCTimeZone(). +std::once_flag load_utc_once; +void LoadUTCTimeZone() { + std::call_once(load_utc_once, []() { utc_time_zone(); }); +} + +} // namespace + +bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) { + const bool is_utc = (name.compare("UTC") == 0); + + // First check, under a shared lock, whether the time zone has already + // been loaded. This is the common path. TODO: Move to shared_mutex. + { + std::lock_guard lock(time_zone_mutex); + if (time_zone_map != nullptr) { + TimeZoneImplByName::const_iterator itr = time_zone_map->find(name); + if (itr != time_zone_map->end()) { + *tz = time_zone(itr->second); + return is_utc || itr->second != utc_zone; + } + } + } + + if (!is_utc) { + // Ensure that UTC is loaded before any other time zones. + LoadUTCTimeZone(); + } + + // Now check again, under an exclusive lock. + std::lock_guard lock(time_zone_mutex); + if (time_zone_map == nullptr) time_zone_map = new TimeZoneImplByName; + const time_zone::Impl*& impl = (*time_zone_map)[name]; + bool fallback_utc = false; + if (impl == nullptr) { + // The first thread in loads the new time zone. + time_zone::Impl* new_impl = new time_zone::Impl(name); + new_impl->zone_ = TimeZoneIf::Load(new_impl->name_); + if (new_impl->zone_ == nullptr) { + delete new_impl; // free the nascent time_zone::Impl + impl = utc_zone; // and fallback to UTC + fallback_utc = true; + } else { + if (is_utc) { + // Happens before any reference to utc_zone. + utc_zone = new_impl; + } + impl = new_impl; // install new time zone + } + } + *tz = time_zone(impl); + return !fallback_utc; +} + +const time_zone::Impl& time_zone::Impl::get(const time_zone& tz) { + if (tz.impl_ == nullptr) { + // Dereferencing an implicit-UTC time_zone is expected to be + // rare, so we don't mind paying a small synchronization cost. + LoadUTCTimeZone(); + return *utc_zone; + } + return *tz.impl_; +} + +time_zone::Impl::Impl(const std::string& name) : name_(name) {} + +time_zone::absolute_lookup time_zone::Impl::BreakTime( + const time_point& tp) const { + time_zone::absolute_lookup res; + Breakdown b = zone_->BreakTime(tp); + // TODO: Eliminate extra normalization. + res.cs = civil_second(b.year, b.month, b.day, b.hour, b.minute, b.second); + res.offset = b.offset; + res.is_dst = b.is_dst; + res.abbr = b.abbr; + return res; +} + +time_zone::civil_lookup time_zone::Impl::MakeTimeInfo(civil_second cs) const { + time_zone::civil_lookup res; + // TODO: Eliminate extra normalization. + TimeInfo t = zone_->MakeTimeInfo(cs.year(), cs.month(), cs.day(), + cs.hour(), cs.minute(), cs.second()); + res.kind = t.kind; + res.pre = t.pre; + res.trans = t.trans; + res.post = t.post; + return res; +} + +} // namespace cctz diff --git a/src/time_zone_impl.h b/src/time_zone_impl.h new file mode 100644 index 00000000..67b2f35e --- /dev/null +++ b/src/time_zone_impl.h @@ -0,0 +1,53 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +#ifndef CCTZ_TIME_ZONE_IMPL_H_ +#define CCTZ_TIME_ZONE_IMPL_H_ + +#include +#include + +#include "time_zone.h" +#include "time_zone_info.h" + +namespace cctz { + +// time_zone::Impl is the internal object referenced by a cctz::time_zone. +class time_zone::Impl { + public: + // Load a named time zone. Returns false if the name is invalid, or if + // some other kind of error occurs. Note that loading "UTC" never fails. + static bool LoadTimeZone(const std::string& name, time_zone* tz); + + // Dereferences the time_zone to obtain its Impl. + static const time_zone::Impl& get(const time_zone& tz); + + // Breaks a time_point down to civil-time components in this time zone. + time_zone::absolute_lookup BreakTime(const time_point& tp) const; + + // Converts the civil-time components in this time zone into a time_point. + // That is, the opposite of BreakTime(). The requested civil time may be + // ambiguous or illegal due to a change of UTC offset. + time_zone::civil_lookup MakeTimeInfo(civil_second cs) const; + + private: + explicit Impl(const std::string& name); + + const std::string name_; + std::unique_ptr zone_; +}; + +} // namespace cctz + +#endif // CCTZ_TIME_ZONE_IMPL_H_ diff --git a/src/cctz_info.cc b/src/time_zone_info.cc similarity index 97% rename from src/cctz_info.cc rename to src/time_zone_info.cc index c517f1df..7674bc49 100644 --- a/src/cctz_info.cc +++ b/src/time_zone_info.cc @@ -1,17 +1,16 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. // This file implements the TimeZoneIf interface using the "zoneinfo" // data provided by the IANA Time Zone Database (i.e., the only real game @@ -31,7 +30,7 @@ // Note that we assume the proleptic Gregorian calendar and 60-second // minutes throughout. -#include "src/cctz_info.h" +#include "time_zone_info.h" #include #include @@ -43,7 +42,7 @@ #include #include -#include "src/cctz_posix.h" +#include "time_zone_posix.h" namespace cctz { @@ -58,7 +57,7 @@ char* errmsg(int errnum, char* buf, size_t buflen) { #elif defined(__APPLE__) strerror_r(errnum, buf, buflen); return buf; -#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE +#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE strerror_r(errnum, buf, buflen); return buf; #else @@ -259,7 +258,7 @@ int64_t TransOffset(bool leap_year, int jan1_weekday, inline TimeInfo MakeUnique(int64_t unix_time, bool normalized) { TimeInfo ti; ti.pre = ti.trans = ti.post = FromUnixSeconds(unix_time); - ti.kind = TimeInfo::Kind::UNIQUE; + ti.kind = time_zone::civil_lookup::UNIQUE; ti.normalized = normalized; return ti; } @@ -270,7 +269,7 @@ inline TimeInfo MakeSkipped(const Transition& tr, const DateTime& dt, ti.pre = FromUnixSeconds(tr.unix_time - 1 + (dt - tr.prev_date_time)); ti.trans = FromUnixSeconds(tr.unix_time); ti.post = FromUnixSeconds(tr.unix_time - (tr.date_time - dt)); - ti.kind = TimeInfo::Kind::SKIPPED; + ti.kind = time_zone::civil_lookup::SKIPPED; ti.normalized = normalized; return ti; } @@ -281,7 +280,7 @@ inline TimeInfo MakeRepeated(const Transition& tr, const DateTime& dt, ti.pre = FromUnixSeconds(tr.unix_time - 1 - (tr.prev_date_time - dt)); ti.trans = FromUnixSeconds(tr.unix_time); ti.post = FromUnixSeconds(tr.unix_time + (dt - tr.date_time)); - ti.kind = TimeInfo::Kind::REPEATED; + ti.kind = time_zone::civil_lookup::REPEATED; ti.normalized = normalized; return ti; } @@ -379,7 +378,7 @@ void TimeZoneInfo::ResetToBuiltinUTC(int seconds) { transitions_[0].prev_date_time = transitions_[0].date_time; transitions_[0].prev_date_time.offset -= 1; default_transition_type_ = 0; - abbreviations_ = "UTC"; // TODO: handle non-zero offset + abbreviations_ = "UTC"; // TODO: Handle non-zero offset. abbreviations_.append(1, '\0'); // add NUL future_spec_.clear(); // never needed for a fixed-offset zone extended_ = false; @@ -782,7 +781,7 @@ TimeInfo TimeZoneInfo::TimeLocal(int64_t year, int mon, int day, int hour, return ti; } -Breakdown TimeZoneInfo::BreakTime(const time_point& tp) const { +Breakdown TimeZoneInfo::BreakTime(const time_point& tp) const { int64_t unix_time = ToUnixSeconds(tp); const int32_t timecnt = transitions_.size(); if (timecnt == 0 || unix_time < transitions_[0].unix_time) { @@ -796,7 +795,7 @@ Breakdown TimeZoneInfo::BreakTime(const time_point& tp) const { if (extended_) { const int64_t diff = unix_time - transitions_[timecnt - 1].unix_time; const int64_t shift = diff / kSecPer400Years + 1; - const auto d = seconds64(shift * kSecPer400Years); + const auto d = sys_seconds(shift * kSecPer400Years); Breakdown bd = BreakTime(tp - d); bd.year += shift * 400; return bd; diff --git a/src/cctz_info.h b/src/time_zone_info.h similarity index 84% rename from src/cctz_info.h rename to src/time_zone_info.h index 55f285dc..69be5cd8 100644 --- a/src/cctz_info.h +++ b/src/time_zone_info.h @@ -1,20 +1,19 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. -#ifndef CCTZ_INFO_H_ -#define CCTZ_INFO_H_ +#ifndef CCTZ_TIME_ZONE_INFO_H_ +#define CCTZ_TIME_ZONE_INFO_H_ #include #include @@ -22,18 +21,18 @@ #include #include -#include "src/cctz_if.h" -#include "src/tzfile.h" +#include "time_zone_if.h" +#include "tzfile.h" namespace cctz { // A zone-independent date/time. A DateTime represents a "Y/M/D H:M:S" // as an offset in seconds from some epoch DateTime, without taking into -// account the value of, or changes in any TimeZone's UTC offset (i.e., as +// account the value of, or changes in any time_zone's UTC offset (i.e., as // if the date/time was in UTC). This allows "Y/M/D H:M:S" values to be // quickly ordered by offset (although this may not be the same ordering as -// their corresponding times in a TimeZone). Also, if two DateTimes are not -// separated by a UTC-offset change in some TimeZone, then the number of +// their corresponding times in a time_zone). Also, if two DateTimes are not +// separated by a UTC-offset change in some time_zone, then the number of // seconds between them can be computed as a simple difference of offsets. // // Note: Because the DateTime epoch does not correspond to the time_point @@ -95,7 +94,7 @@ class TimeZoneInfo : public TimeZoneIf { bool Load(const std::string& name); // TimeZoneIf implementations. - Breakdown BreakTime(const time_point& tp) const override; + Breakdown BreakTime(const time_point& tp) const override; TimeInfo MakeTimeInfo(int64_t year, int mon, int day, int hour, int min, int sec) const override; @@ -139,4 +138,4 @@ class TimeZoneInfo : public TimeZoneIf { } // namespace cctz -#endif // CCTZ_INFO_H_ +#endif // CCTZ_TIME_ZONE_INFO_H_ diff --git a/src/cctz_libc.cc b/src/time_zone_libc.cc similarity index 89% rename from src/cctz_libc.cc rename to src/time_zone_libc.cc index 8c75057d..0e4acec8 100644 --- a/src/cctz_libc.cc +++ b/src/time_zone_libc.cc @@ -1,19 +1,18 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. -#include "src/cctz_libc.h" +#include "time_zone_libc.h" #include #include @@ -51,7 +50,7 @@ TimeZoneLibC::TimeZoneLibC(const std::string& name) { } } -Breakdown TimeZoneLibC::BreakTime(const time_point& tp) const { +Breakdown TimeZoneLibC::BreakTime(const time_point& tp) const { Breakdown bd; std::time_t t = ToUnixSeconds(tp); std::tm tm; @@ -175,7 +174,7 @@ TimeInfo TimeZoneLibC::MakeTimeInfo(int64_t year, int mon, int day, t = ((((DayOrdinal(year, mon, day) * 24) + hour) * 60) + min) * 60 + sec; } TimeInfo ti; - ti.kind = TimeInfo::Kind::UNIQUE; + ti.kind = time_zone::civil_lookup::UNIQUE; ti.pre = ti.trans = ti.post = FromUnixSeconds(t); ti.normalized = normalized; return ti; diff --git a/src/cctz_libc.h b/src/time_zone_libc.h similarity index 55% rename from src/cctz_libc.h rename to src/time_zone_libc.h index 0d0b077a..77fbc505 100644 --- a/src/cctz_libc.h +++ b/src/time_zone_libc.h @@ -1,24 +1,24 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. -#ifndef CCTZ_LIBC_H_ -#define CCTZ_LIBC_H_ +#ifndef CCTZ_TIME_ZONE_LIBC_H_ +#define CCTZ_TIME_ZONE_LIBC_H_ +#include #include -#include "src/cctz_if.h" +#include "time_zone_if.h" namespace cctz { @@ -29,7 +29,7 @@ class TimeZoneLibC : public TimeZoneIf { explicit TimeZoneLibC(const std::string& name); // TimeZoneIf implementations. - Breakdown BreakTime(const time_point& tp) const override; + Breakdown BreakTime(const time_point& tp) const override; TimeInfo MakeTimeInfo(int64_t year, int mon, int day, int hour, int min, int sec) const override; @@ -41,4 +41,4 @@ class TimeZoneLibC : public TimeZoneIf { } // namespace cctz -#endif // CCTZ_LIBC_H_ +#endif // CCTZ_TIME_ZONE_LIBC_H_ diff --git a/src/time_zone_lookup.cc b/src/time_zone_lookup.cc new file mode 100644 index 00000000..b58556a7 --- /dev/null +++ b/src/time_zone_lookup.cc @@ -0,0 +1,56 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +#include "time_zone.h" + +#include + +#include "time_zone_impl.h" + +namespace cctz { + +time_zone utc_time_zone() { + time_zone tz; + load_time_zone("UTC", &tz); + return tz; +} + +time_zone local_time_zone() { + const char* zone = std::getenv("TZ"); + if (zone != nullptr) { + if (*zone == ':') ++zone; + } else { + zone = "localtime"; + } + time_zone tz; + if (!load_time_zone(zone, &tz)) { + load_time_zone("UTC", &tz); + } + return tz; +} + +bool load_time_zone(const std::string& name, time_zone* tz) { + return time_zone::Impl::LoadTimeZone(name, tz); +} + +time_zone::absolute_lookup time_zone::lookup( + const time_point& tp) const { + return time_zone::Impl::get(*this).BreakTime(tp); +} + +time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const { + return time_zone::Impl::get(*this).MakeTimeInfo(cs); +} + +} // namespace cctz diff --git a/test/cnv_test.cc b/src/time_zone_lookup_test.cc similarity index 78% rename from test/cnv_test.cc rename to src/time_zone_lookup_test.cc index 79b93b5b..e4e8f0d0 100644 --- a/test/cnv_test.cc +++ b/src/time_zone_lookup_test.cc @@ -1,19 +1,18 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. -#include "src/cctz.h" +#include "time_zone.h" #include #include @@ -618,9 +617,9 @@ const char* const kTimeZoneNames[] = { }; // Helper to return a loaded time zone by value (UTC on error). -TimeZone LoadZone(const std::string& name) { - TimeZone tz; - LoadTimeZone(name, &tz); +time_zone LoadZone(const std::string& name) { + time_zone tz; + load_time_zone(name, &tz); return tz; } @@ -628,17 +627,29 @@ TimeZone LoadZone(const std::string& name) { // correct line numbers. #define ExpectTime(bd, y, m, d, hh, mm, ss, off, isdst, zone) \ do { \ - EXPECT_EQ(y, bd.year); \ - EXPECT_EQ(m, bd.month); \ - EXPECT_EQ(d, bd.day); \ - EXPECT_EQ(hh, bd.hour); \ - EXPECT_EQ(mm, bd.minute); \ - EXPECT_EQ(ss, bd.second); \ + EXPECT_EQ(y, bd.cs.year()); \ + EXPECT_EQ(m, bd.cs.month()); \ + EXPECT_EQ(d, bd.cs.day()); \ + EXPECT_EQ(hh, bd.cs.hour()); \ + EXPECT_EQ(mm, bd.cs.minute()); \ + EXPECT_EQ(ss, bd.cs.second()); \ EXPECT_EQ(off, bd.offset); \ EXPECT_EQ(isdst, bd.is_dst); \ EXPECT_EQ(zone, bd.abbr); \ } while (0) +// Helpers for converting a YMDhms to a time_point. +time_zone::civil_lookup MakeTimeInfo(int y, int m, int d, + int hh, int mm, int ss, + const time_zone& tz) { + return tz.lookup(civil_second(y, m, d, hh, mm, ss)); +} +time_point MakeTime(int y, int m, int d, + int hh, int mm, int ss, + const time_zone& tz) { + return MakeTimeInfo(y, m, d, hh, mm, ss, tz).pre; +} + } // namespace TEST(TimeZones, LoadZonesConcurrently) { @@ -647,9 +658,9 @@ TEST(TimeZones, LoadZonesConcurrently) { auto load_zones = [ready_future](std::promise* started) { started->set_value(); ready_future.wait(); - TimeZone tz; + time_zone tz; for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) { - EXPECT_TRUE(LoadTimeZone(*np, &tz)); + EXPECT_TRUE(load_time_zone(*np, &tz)); } }; @@ -668,23 +679,23 @@ TEST(TimeZones, LoadZonesConcurrently) { } TEST(TimeZone, Failures) { - TimeZone tz; - EXPECT_FALSE(LoadTimeZone(":America/Los_Angeles", &tz)); + time_zone tz; + EXPECT_FALSE(load_time_zone(":America/Los_Angeles", &tz)); tz = LoadZone("America/Los_Angeles"); - EXPECT_FALSE(LoadTimeZone("Invalid/TimeZone", &tz)); + EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz)); EXPECT_EQ(system_clock::from_time_t(0), MakeTime(1970, 1, 1, 0, 0, 0, tz)); // UTC // Ensures that the load still fails on a subsequent attempt. tz = LoadZone("America/Los_Angeles"); - EXPECT_FALSE(LoadTimeZone("Invalid/TimeZone", &tz)); + EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz)); EXPECT_EQ(system_clock::from_time_t(0), MakeTime(1970, 1, 1, 0, 0, 0, tz)); // UTC // Loading an empty string timezone should fail. tz = LoadZone("America/Los_Angeles"); - EXPECT_FALSE(LoadTimeZone("", &tz)); + EXPECT_FALSE(load_time_zone("", &tz)); EXPECT_EQ(system_clock::from_time_t(0), MakeTime(1970, 1, 1, 0, 0, 0, tz)); // UTC } @@ -699,73 +710,81 @@ TEST(StdChronoTimePoint, TimeTAlignment) { TEST(BreakTime, TimePointResolution) { using std::chrono::time_point_cast; - const TimeZone utc = UTCTimeZone(); + const time_zone utc = utc_time_zone(); const auto t0 = system_clock::from_time_t(0); - Breakdown bd{}; + time_zone::absolute_lookup bd{}; - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = BreakTime(time_point_cast(t0), utc); + bd = utc.lookup(time_point_cast(t0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); } TEST(BreakTime, LocalTimeInUTC) { - const Breakdown bd = BreakTime(system_clock::from_time_t(0), UTCTimeZone()); + const time_zone::absolute_lookup bd = + utc_time_zone().lookup(system_clock::from_time_t(0)); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - EXPECT_EQ(4, bd.weekday); // Thursday + EXPECT_EQ(weekday::thursday, get_weekday(civil_day(bd.cs))); } TEST(BreakTime, LocalTimePosix) { // See IEEE Std 1003.1-1988 B.2.3 General Terms, Epoch. - const Breakdown bd = - BreakTime(system_clock::from_time_t(536457599), UTCTimeZone()); + const time_zone::absolute_lookup bd = + utc_time_zone().lookup(system_clock::from_time_t(536457599)); ExpectTime(bd, 1986, 12, 31, 23, 59, 59, 0, false, "UTC"); - EXPECT_EQ(3, bd.weekday); // Wednesday + EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(bd.cs))); } TEST(BreakTime, LocalTimeInNewYork) { - const TimeZone tz = LoadZone("America/New_York"); - const Breakdown bd = BreakTime(system_clock::from_time_t(45), tz); + const time_zone tz = LoadZone("America/New_York"); + const time_zone::absolute_lookup bd = + tz.lookup(system_clock::from_time_t(45)); ExpectTime(bd, 1969, 12, 31, 19, 0, 45, -5 * 60 * 60, false, "EST"); - EXPECT_EQ(3, bd.weekday); // Wednesday + EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(bd.cs))); } TEST(BreakTime, LocalTimeInMTV) { - const TimeZone tz = LoadZone("America/Los_Angeles"); - const Breakdown bd = BreakTime(system_clock::from_time_t(1380855729), tz); + const time_zone tz = LoadZone("America/Los_Angeles"); + const time_zone::absolute_lookup bd = + tz.lookup(system_clock::from_time_t(1380855729)); ExpectTime(bd, 2013, 10, 3, 20, 2, 9, -7 * 60 * 60, true, "PDT"); - EXPECT_EQ(4, bd.weekday); // Thursday + EXPECT_EQ(weekday::thursday, get_weekday(civil_day(bd.cs))); } TEST(BreakTime, LocalTimeInSydney) { - const TimeZone tz = LoadZone("Australia/Sydney"); - const Breakdown bd = BreakTime(system_clock::from_time_t(90), tz); + const time_zone tz = LoadZone("Australia/Sydney"); + const time_zone::absolute_lookup bd = + tz.lookup(system_clock::from_time_t(90)); ExpectTime(bd, 1970, 1, 1, 10, 1, 30, 10 * 60 * 60, false, "AEST"); - EXPECT_EQ(4, bd.weekday); // Thursday + EXPECT_EQ(weekday::thursday, get_weekday(civil_day(bd.cs))); } TEST(MakeTime, TimePointResolution) { - const TimeZone utc = UTCTimeZone(); - const time_point tp_ns = MakeTime(2015, 1, 2, 3, 4, 5, utc); - EXPECT_EQ("04:05", Format("%M:%E*S", tp_ns, utc)); - const time_point tp_us = MakeTime(2015, 1, 2, 3, 4, 5, utc); - EXPECT_EQ("04:05", Format("%M:%E*S", tp_us, utc)); - const time_point tp_ms = MakeTime(2015, 1, 2, 3, 4, 5, utc); - EXPECT_EQ("04:05", Format("%M:%E*S", tp_ms, utc)); - const time_point tp_s = MakeTime(2015, 1, 2, 3, 4, 5, utc); - EXPECT_EQ("04:05", Format("%M:%E*S", tp_s, utc)); - const time_point tp_s64 = MakeTime(2015, 1, 2, 3, 4, 5, utc); - EXPECT_EQ("04:05", Format("%M:%E*S", tp_s64, utc)); + const time_zone utc = utc_time_zone(); + const time_point tp_ns = + MakeTime(2015, 1, 2, 3, 4, 5, utc); + EXPECT_EQ("04:05", format("%M:%E*S", tp_ns, utc)); + const time_point tp_us = + MakeTime(2015, 1, 2, 3, 4, 5, utc); + EXPECT_EQ("04:05", format("%M:%E*S", tp_us, utc)); + const time_point tp_ms = + MakeTime(2015, 1, 2, 3, 4, 5, utc); + EXPECT_EQ("04:05", format("%M:%E*S", tp_ms, utc)); + const time_point tp_s = + MakeTime(2015, 1, 2, 3, 4, 5, utc); + EXPECT_EQ("04:05", format("%M:%E*S", tp_s, utc)); + const time_point tp_s64 = MakeTime(2015, 1, 2, 3, 4, 5, utc); + EXPECT_EQ("04:05", format("%M:%E*S", tp_s64, utc)); // These next two require time_point_cast because the conversion from a // resolution of seconds (the return value of MakeTime()) to a coarser @@ -773,14 +792,14 @@ TEST(MakeTime, TimePointResolution) { using std::chrono::time_point_cast; const time_point tp_m = time_point_cast(MakeTime(2015, 1, 2, 3, 4, 5, utc)); - EXPECT_EQ("04:00", Format("%M:%E*S", tp_m, utc)); + EXPECT_EQ("04:00", format("%M:%E*S", tp_m, utc)); const time_point tp_h = time_point_cast(MakeTime(2015, 1, 2, 3, 4, 5, utc)); - EXPECT_EQ("00:00", Format("%M:%E*S", tp_h, utc)); + EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc)); } TEST(MakeTime, Normalization) { - const TimeZone tz = LoadZone("America/New_York"); + const time_zone tz = LoadZone("America/New_York"); const auto tp = MakeTime(2009, 2, 13, 18, 31, 30, tz); EXPECT_EQ(system_clock::from_time_t(1234567890), tp); @@ -793,130 +812,130 @@ TEST(MakeTime, Normalization) { } TEST(TimeZoneEdgeCase, AmericaNewYork) { - const TimeZone tz = LoadZone("America/New_York"); + const time_zone tz = LoadZone("America/New_York"); // Spring 1:59:59 -> 3:00:00 auto tp = MakeTime(2013, 3, 10, 1, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 3, 10, 3, 0, 0, -4 * 3600, true, "EDT"); // Fall 1:59:59 -> 1:00:00 tp = MakeTime(2013, 11, 3, 1, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST"); } TEST(TimeZoneEdgeCase, AmericaLosAngeles) { - const TimeZone tz = LoadZone("America/Los_Angeles"); + const time_zone tz = LoadZone("America/Los_Angeles"); // Spring 1:59:59 -> 3:00:00 auto tp = MakeTime(2013, 3, 10, 1, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 3, 10, 3, 0, 0, -7 * 3600, true, "PDT"); // Fall 1:59:59 -> 1:00:00 tp = MakeTime(2013, 11, 3, 1, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST"); } TEST(TimeZoneEdgeCase, ArizonaNoTransition) { - const TimeZone tz = LoadZone("America/Phoenix"); + const time_zone tz = LoadZone("America/Phoenix"); // No transition in Spring. auto tp = MakeTime(2013, 3, 10, 1, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST"); // No transition in Fall. tp = MakeTime(2013, 11, 3, 1, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); } TEST(TimeZoneEdgeCase, AsiaKathmandu) { - const TimeZone tz = LoadZone("Asia/Kathmandu"); + const time_zone tz = LoadZone("Asia/Kathmandu"); // A non-DST offset change from +0530 to +0545 // // 504901799 == Tue, 31 Dec 1985 23:59:59 +0530 (IST) // 504901800 == Wed, 1 Jan 1986 00:15:00 +0545 (NPT) auto tp = MakeTime(1985, 12, 31, 23, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "IST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "NPT"); } TEST(TimeZoneEdgeCase, PacificChatham) { - const TimeZone tz = LoadZone("Pacific/Chatham"); + const time_zone tz = LoadZone("Pacific/Chatham"); // One-hour DST offset changes, but at atypical values // // 1365256799 == Sun, 7 Apr 2013 03:44:59 +1345 (CHADT) // 1365256800 == Sun, 7 Apr 2013 02:45:00 +1245 (CHAST) auto tp = MakeTime(2013, 4, 7, 3, 44, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "CHADT"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 4, 7, 2, 45, 0, 12.75 * 3600, false, "CHAST"); // 1380376799 == Sun, 29 Sep 2013 02:44:59 +1245 (CHAST) // 1380376800 == Sun, 29 Sep 2013 03:45:00 +1345 (CHADT) tp = MakeTime(2013, 9, 29, 2, 44, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, "CHAST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "CHADT"); } TEST(TimeZoneEdgeCase, AustraliaLordHowe) { - const TimeZone tz = LoadZone("Australia/Lord_Howe"); + const time_zone tz = LoadZone("Australia/Lord_Howe"); // Half-hour DST offset changes // // 1365260399 == Sun, 7 Apr 2013 01:59:59 +1100 (LHDT) // 1365260400 == Sun, 7 Apr 2013 01:30:00 +1030 (LHST) auto tp = MakeTime(2013, 4, 7, 1, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "LHDT"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 4, 7, 1, 30, 0, 10.5 * 3600, false, "LHST"); // 1380986999 == Sun, 6 Oct 2013 01:59:59 +1030 (LHST) // 1380987000 == Sun, 6 Oct 2013 02:30:00 +1100 (LHDT) tp = MakeTime(2013, 10, 6, 1, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "LHST"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "LHDT"); } TEST(TimeZoneEdgeCase, PacificApia) { - const TimeZone tz = LoadZone("Pacific/Apia"); + const time_zone tz = LoadZone("Pacific/Apia"); // At the end of December 2011, Samoa jumped forward by one day, // skipping 30 December from the local calendar, when the nation @@ -927,42 +946,42 @@ TEST(TimeZoneEdgeCase, PacificApia) { // 1325239199 == Thu, 29 Dec 2011 23:59:59 -1000 (SDT) // 1325239200 == Sat, 31 Dec 2011 00:00:00 +1400 (WSDT) auto tp = MakeTime(2011, 12, 29, 23, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "SDT"); - EXPECT_EQ(363, bd.yearday); + EXPECT_EQ(363, get_yearday(civil_day(bd.cs))); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "WSDT"); - EXPECT_EQ(365, bd.yearday); // What else could 2011/12/31 be!? + EXPECT_EQ(365, get_yearday(civil_day(bd.cs))); } TEST(TimeZoneEdgeCase, AfricaCairo) { - const TimeZone tz = LoadZone("Africa/Cairo"); + const time_zone tz = LoadZone("Africa/Cairo"); // An interesting case of midnight not existing. // // 1400191199 == Thu, 15 May 2014 23:59:59 +0200 (EET) // 1400191200 == Fri, 16 May 2014 01:00:00 +0300 (EEST) auto tp = MakeTime(2014, 5, 15, 23, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); } TEST(TimeZoneEdgeCase, AfricaMonrovia) { - const TimeZone tz = LoadZone("Africa/Monrovia"); + const time_zone tz = LoadZone("Africa/Monrovia"); // Strange offset change -00:44:30 -> +00:00:00 (non-DST) // // 73529069 == Sun, 30 Apr 1972 23:59:59 -0044 (LRT) // 73529070 == Mon, 1 May 1972 00:44:30 +0000 (GMT) auto tp = MakeTime(1972, 4, 30, 23, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 1972, 4, 30, 23, 59, 59, -44.5 * 60, false, "LRT"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1972, 5, 1, 0, 44, 30, 0 * 60, false, "GMT"); } @@ -972,156 +991,134 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) { // Note that the 32-bit times used in a (tzh_version == 0) zoneinfo // file cannot represent the abbreviation-only transition of 1890, // so we ignore the abbreviation by expecting what we received. - const TimeZone tz = LoadZone("America/Jamaica"); + const time_zone tz = LoadZone("America/Jamaica"); // Before the first transition. auto tp = MakeTime(1889, 12, 31, 0, 0, 0, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 1889, 12, 31, 0, 0, 0, -18431, false, bd.abbr); // Over the first (abbreviation-change only) transition. // -2524503170 == Tue, 31 Dec 1889 23:59:59 -0507 (LMT) // -2524503169 == Wed, 1 Jan 1890 00:00:00 -0507 (KMT) tp = MakeTime(1889, 12, 31, 23, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1889, 12, 31, 23, 59, 59, -18431, false, bd.abbr); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1890, 1, 1, 0, 0, 0, -18431, false, "KMT"); // Over the last (DST) transition. // 436341599 == Sun, 30 Oct 1983 01:59:59 -0400 (EDT) // 436341600 == Sun, 30 Oct 1983 01:00:00 -0500 (EST) tp = MakeTime(1983, 10, 30, 1, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); // After the last transition. tp = MakeTime(1983, 12, 31, 23, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1983, 12, 31, 23, 59, 59, -5 * 3600, false, "EST"); } TEST(TimeZoneEdgeCase, WET) { // Cover some non-existent times within forward transitions. - const TimeZone tz = LoadZone("WET"); + const time_zone tz = LoadZone("WET"); // Before the first transition. auto tp = MakeTime(1977, 1, 1, 0, 0, 0, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 1977, 1, 1, 0, 0, 0, 0, false, "WET"); // Over the first transition. // 228877199 == Sun, 3 Apr 1977 00:59:59 +0000 (WET) // 228877200 == Sun, 3 Apr 1977 02:00:00 +0100 (WEST) tp = MakeTime(1977, 4, 3, 0, 59, 59, tz); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1977, 4, 3, 0, 59, 59, 0, false, "WET"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); // A non-existent time within the first transition. - TimeInfo ti1 = MakeTimeInfo(1977, 4, 3, 1, 15, 0, tz); - EXPECT_FALSE(ti1.normalized); - EXPECT_EQ(TimeInfo::Kind::SKIPPED, ti1.kind); - bd = BreakTime(ti1.pre, tz); + time_zone::civil_lookup ti1 = MakeTimeInfo(1977, 4, 3, 1, 15, 0, tz); + EXPECT_EQ(time_zone::civil_lookup::SKIPPED, ti1.kind); + bd = tz.lookup(ti1.pre); ExpectTime(bd, 1977, 4, 3, 2, 15, 0, 1 * 3600, true, "WEST"); - bd = BreakTime(ti1.trans, tz); + bd = tz.lookup(ti1.trans); ExpectTime(bd, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); - bd = BreakTime(ti1.post, tz); + bd = tz.lookup(ti1.post); ExpectTime(bd, 1977, 4, 3, 0, 15, 0, 0 * 3600, false, "WET"); // A non-existent time within the second forward transition. - TimeInfo ti2 = MakeTimeInfo(1978, 4, 2, 1, 15, 0, tz); - EXPECT_FALSE(ti2.normalized); - EXPECT_EQ(TimeInfo::Kind::SKIPPED, ti2.kind); - bd = BreakTime(ti2.pre, tz); + time_zone::civil_lookup ti2 = MakeTimeInfo(1978, 4, 2, 1, 15, 0, tz); + EXPECT_EQ(time_zone::civil_lookup::SKIPPED, ti2.kind); + bd = tz.lookup(ti2.pre); ExpectTime(bd, 1978, 4, 2, 2, 15, 0, 1 * 3600, true, "WEST"); - bd = BreakTime(ti2.trans, tz); + bd = tz.lookup(ti2.trans); ExpectTime(bd, 1978, 4, 2, 2, 0, 0, 1 * 3600, true, "WEST"); - bd = BreakTime(ti2.post, tz); + bd = tz.lookup(ti2.post); ExpectTime(bd, 1978, 4, 2, 0, 15, 0, 0 * 3600, false, "WET"); } TEST(TimeZoneEdgeCase, FixedOffsets) { - const TimeZone gmtm5 = LoadZone("Etc/GMT+5"); // -0500 + const time_zone gmtm5 = LoadZone("Etc/GMT+5"); // -0500 auto tp = MakeTime(1970, 1, 1, 0, 0, 0, gmtm5); - Breakdown bd = BreakTime(tp, gmtm5); + time_zone::absolute_lookup bd = gmtm5.lookup(tp); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, -5 * 3600, false, "GMT+5"); EXPECT_EQ(system_clock::from_time_t(5 * 3600), tp); - const TimeZone gmtp5 = LoadZone("Etc/GMT-5"); // +0500 + const time_zone gmtp5 = LoadZone("Etc/GMT-5"); // +0500 tp = MakeTime(1970, 1, 1, 0, 0, 0, gmtp5); - bd = BreakTime(tp, gmtp5); + bd = gmtp5.lookup(tp); ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 5 * 3600, false, "GMT-5"); EXPECT_EQ(system_clock::from_time_t(-5 * 3600), tp); } TEST(TimeZoneEdgeCase, NegativeYear) { // Tests transition from year 0 (aka 1BCE) to year -1. - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); auto tp = MakeTime(0, 1, 1, 0, 0, 0, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); - EXPECT_EQ(6, bd.weekday); + EXPECT_EQ(weekday::saturday, get_weekday(civil_day(bd.cs))); tp -= std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); - EXPECT_EQ(5, bd.weekday); + EXPECT_EQ(weekday::friday, get_weekday(civil_day(bd.cs))); } TEST(TimeZoneEdgeCase, UTC32bitLimit) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); // Limits of signed 32-bit time_t // // 2147483647 == Tue, 19 Jan 2038 03:14:07 +0000 (UTC) // 2147483648 == Tue, 19 Jan 2038 03:14:08 +0000 (UTC) auto tp = MakeTime(2038, 1, 19, 3, 14, 7, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); } TEST(TimeZoneEdgeCase, UTC5DigitYear) { - const TimeZone tz = UTCTimeZone(); + const time_zone tz = utc_time_zone(); // Rollover to 5-digit year // // 253402300799 == Fri, 31 Dec 9999 23:59:59 +0000 (UTC) // 253402300800 == Sat, 1 Jan 1000 00:00:00 +0000 (UTC) auto tp = MakeTime(9999, 12, 31, 23, 59, 59, tz); - Breakdown bd = BreakTime(tp, tz); + time_zone::absolute_lookup bd = tz.lookup(tp); ExpectTime(bd, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); tp += std::chrono::seconds(1); - bd = BreakTime(tp, tz); + bd = tz.lookup(tp); ExpectTime(bd, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); } -TEST(TimeZoneEdgeCase, East64bitLimit) { - // For zones with positive offsets we cannot really get all the way to the - // maximal time_point as anything closer than the offset will (currently) - // result in an internal integer overflow. (Check 15:30:08 with -ftrapv.) - const TimeZone tz = LoadZone("Etc/GMT-14"); - time_point tp = MakeTime(292277026596, 12, 4, 15, 30, 7, tz); - EXPECT_EQ(std::numeric_limits::max() - 14 * 3600, - tp.time_since_epoch().count()); -} - -TEST(TimeZoneEdgeCase, West64bitLimit) { - // For zones with negative offsets we cannot really get all the way to the - // minimal time_point as anything closer than the offset will (currently) - // result in an internal integer overflow. (Check 08:29:51 with -ftrapv.) - const TimeZone tz = LoadZone("Etc/GMT+12"); - time_point tp = MakeTime(-292277022657, 1, 27, 8, 29, 52, tz); - EXPECT_EQ(std::numeric_limits::min() + 12 * 3600, - tp.time_since_epoch().count()); -} - } // namespace cctz diff --git a/src/cctz_posix.cc b/src/time_zone_posix.cc similarity index 89% rename from src/cctz_posix.cc rename to src/time_zone_posix.cc index 17e5e84b..5f0cc1ff 100644 --- a/src/cctz_posix.cc +++ b/src/time_zone_posix.cc @@ -1,19 +1,18 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. - -#include "src/cctz_posix.h" +// 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. + +#include "time_zone_posix.h" #include #include diff --git a/src/cctz_posix.h b/src/time_zone_posix.h similarity index 85% rename from src/cctz_posix.h rename to src/time_zone_posix.h index 78f75e41..64f937be 100644 --- a/src/cctz_posix.h +++ b/src/time_zone_posix.h @@ -1,17 +1,16 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // 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 +// 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. +// 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. // Parsing of a POSIX zone spec as described in the TZ part of section 8.3 in // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html. @@ -50,8 +49,8 @@ // } // } -#ifndef CCTZ_POSIX_H_ -#define CCTZ_POSIX_H_ +#ifndef CCTZ_TIME_ZONE_POSIX_H_ +#define CCTZ_TIME_ZONE_POSIX_H_ #include #include @@ -112,4 +111,4 @@ bool ParsePosixSpec(const std::string& spec, PosixTimeZone* res); } // namespace cctz -#endif // CCTZ_POSIX_H_ +#endif // CCTZ_TIME_ZONE_POSIX_H_ diff --git a/test/BUILD b/test/BUILD deleted file mode 100644 index 2593559c..00000000 --- a/test/BUILD +++ /dev/null @@ -1,43 +0,0 @@ -# Builds the Google Test source that was fetched from another repository. -cc_library( - name = "gtest", - srcs = glob( - [ - "google*/src/*.cc", - ], - exclude = glob([ - "google*/src/*-all.cc", - "googlemock/src/gmock_main.cc", - ]), - ), - hdrs = glob(["*/include/**/*.h"]), - includes = [ - "googlemock/", - "googlemock/include", - "googletest/", - "googletest/include", - ], - linkopts = ["-pthread"], - textual_hdrs = ["googletest/src/gtest-internal-inl.h"], - visibility = ["//visibility:public"], -) - -cc_test( - name = "cnv_test", - size = "small", - srcs = ["cnv_test.cc"], - deps = [ - "@gtest//:gtest", - "//src:cctz", - ], -) - -cc_test( - name = "fmt_test", - size = "small", - srcs = ["fmt_test.cc"], - deps = [ - "@gtest//:gtest", - "//src:cctz", - ], -) diff --git a/tools/BUILD b/tools/BUILD deleted file mode 100644 index fc950d5f..00000000 --- a/tools/BUILD +++ /dev/null @@ -1,7 +0,0 @@ -cc_binary( - name = "time_tool", - srcs = ["time_tool.cc"], - deps = [ - "//src:cctz", - ], -)