From 84abf32b44bf04a9dc091044033dc20148b2b2be Mon Sep 17 00:00:00 2001 From: Bradley White Date: Thu, 3 Mar 2016 16:12:52 -0500 Subject: [PATCH 01/69] CCTZ Version 2. Adds civil-time classes and new time-zone interfaces. These are being proposed for C++ standardization. --- BUILD | 166 ++++ CONTRIBUTING.md | 37 +- Makefile | 90 ++ README.md | 48 +- WORKSPACE | 2 +- examples/BUILD | 52 -- examples/classic.cc | 15 +- examples/epoch_shift.cc | 15 +- examples/example1.cc | 32 +- examples/example2.cc | 23 +- examples/example3.cc | 32 +- examples/example4.cc | 39 +- examples/hello.cc | 32 +- src/BUILD | 24 - src/cctz.h | 353 -------- src/cctz_cnv.cc | 64 -- src/cctz_if.h | 59 -- src/cctz_impl.cc | 111 --- src/cctz_impl.h | 55 -- src/civil_time.h | 162 ++++ src/civil_time_detail.h | 462 ++++++++++ src/civil_time_test.cc | 851 ++++++++++++++++++ {tools => src}/time_tool.cc | 108 ++- src/time_zone.h | 261 ++++++ src/{cctz_fmt.cc => time_zone_format.cc} | 116 ++- .../time_zone_format_test.cc | 714 +++++++-------- src/{cctz_if.cc => time_zone_if.cc} | 21 +- src/time_zone_if.h | 93 ++ src/time_zone_impl.cc | 125 +++ src/time_zone_impl.h | 53 ++ src/{cctz_info.cc => time_zone_info.cc} | 33 +- src/{cctz_info.h => time_zone_info.h} | 33 +- src/{cctz_libc.cc => time_zone_libc.cc} | 21 +- src/{cctz_libc.h => time_zone_libc.h} | 26 +- src/time_zone_lookup.cc | 56 ++ .../time_zone_lookup_test.cc | 315 ++++--- src/{cctz_posix.cc => time_zone_posix.cc} | 19 +- src/{cctz_posix.h => time_zone_posix.h} | 21 +- test/BUILD | 43 - tools/BUILD | 7 - 40 files changed, 3201 insertions(+), 1588 deletions(-) create mode 100644 BUILD create mode 100644 Makefile delete mode 100644 examples/BUILD delete mode 100644 src/BUILD delete mode 100644 src/cctz.h delete mode 100644 src/cctz_cnv.cc delete mode 100644 src/cctz_if.h delete mode 100644 src/cctz_impl.cc delete mode 100644 src/cctz_impl.h create mode 100644 src/civil_time.h create mode 100644 src/civil_time_detail.h create mode 100644 src/civil_time_test.cc rename {tools => src}/time_tool.cc (62%) create mode 100644 src/time_zone.h rename src/{cctz_fmt.cc => time_zone_format.cc} (87%) rename test/fmt_test.cc => src/time_zone_format_test.cc (50%) rename src/{cctz_if.cc => time_zone_if.cc} (58%) create mode 100644 src/time_zone_if.h create mode 100644 src/time_zone_impl.cc create mode 100644 src/time_zone_impl.h rename src/{cctz_info.cc => time_zone_info.cc} (97%) rename src/{cctz_info.h => time_zone_info.h} (84%) rename src/{cctz_libc.cc => time_zone_libc.cc} (89%) rename src/{cctz_libc.h => time_zone_libc.h} (55%) create mode 100644 src/time_zone_lookup.cc rename test/cnv_test.cc => src/time_zone_lookup_test.cc (78%) rename src/{cctz_posix.cc => time_zone_posix.cc} (89%) rename src/{cctz_posix.h => time_zone_posix.h} (85%) delete mode 100644 test/BUILD delete mode 100644 tools/BUILD 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", - ], -) From 36429706c5d4ff5e7ebb4acce91d3c1f6c88a534 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 8 Mar 2016 09:42:10 -0500 Subject: [PATCH 02/69] Add cctz::convert() helpers. --- examples/example1.cc | 2 +- examples/example3.cc | 7 +- examples/example4.cc | 8 +- examples/hello.cc | 6 +- src/time_tool.cc | 47 +++-- src/time_zone.h | 22 ++- src/time_zone_format.cc | 52 ++--- src/time_zone_format_test.cc | 252 ++++++++++++------------ src/time_zone_impl.cc | 11 +- src/time_zone_lookup_test.cc | 364 +++++++++++++++-------------------- 10 files changed, 358 insertions(+), 413 deletions(-) diff --git a/examples/example1.cc b/examples/example1.cc index ac73e3d7..25896d39 100644 --- a/examples/example1.cc +++ b/examples/example1.cc @@ -23,7 +23,7 @@ int main() { load_time_zone("America/Los_Angeles", &lax); // Time Programming Fundamentals @cppcon - const auto tp = lax.lookup(cctz::civil_second(2015, 9, 22, 9, 0, 0)).pre; + const auto tp = cctz::convert(cctz::civil_second(2015, 9, 22, 9, 0, 0), lax); cctz::time_zone nyc; load_time_zone("America/New_York", &nyc); diff --git a/examples/example3.cc b/examples/example3.cc index 7b112e50..22d88901 100644 --- a/examples/example3.cc +++ b/examples/example3.cc @@ -23,12 +23,11 @@ int main() { load_time_zone("America/Los_Angeles", &lax); const auto now = std::chrono::system_clock::now(); - const cctz::time_zone::absolute_lookup bd = lax.lookup(now); + const cctz::civil_second cs = cctz::convert(now, lax); // First day of month, 6 months from now. - const auto then = - lax.lookup(cctz::civil_second(bd.cs.year(), bd.cs.month() + 6, 1, - 0, 0, 0)).pre; + const auto then = cctz::convert( + cctz::civil_second(cs.year(), cs.month() + 6, 1, 0, 0, 0), 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 fe4fb846..1fcfb300 100644 --- a/examples/example4.cc +++ b/examples/example4.cc @@ -21,11 +21,9 @@ template 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; + const cctz::civil_second cs = cctz::convert(tp, tz); + const cctz::civil_day cd(cs.year(), cs.month(), cs.day()); + return cctz::convert(cctz::civil_second(cd), tz); } int main() { diff --git a/examples/hello.cc b/examples/hello.cc index 0b701982..52882b7d 100644 --- a/examples/hello.cc +++ b/examples/hello.cc @@ -24,7 +24,8 @@ int main() { if (!cctz::load_time_zone("Australia/Sydney", &syd)) return -1; // Neil Armstrong first walks on the moon - const auto tp1 = syd.lookup(cctz::civil_second(1969, 7, 21, 12, 56, 0)).pre; + const auto tp1 = + cctz::convert(cctz::civil_second(1969, 7, 21, 12, 56, 0), syd); const std::string s = cctz::format("%F %T %z", tp1, syd); std::cout << s << "\n"; @@ -32,6 +33,7 @@ int main() { cctz::time_zone nyc; cctz::load_time_zone("America/New_York", &nyc); - const auto tp2 = nyc.lookup(cctz::civil_second(1969, 7, 20, 22, 56, 0)).pre; + const auto tp2 = + cctz::convert(cctz::civil_second(1969, 7, 20, 22, 56, 0), nyc); return tp2 == tp1 ? 0 : 1; } diff --git a/src/time_tool.cc b/src/time_tool.cc index 05d9cb50..05fbe0ef 100644 --- a/src/time_tool.cc +++ b/src/time_tool.cc @@ -70,13 +70,12 @@ bool ParseTimeSpec(const std::string& args, cctz::time_zone zone, return false; } -bool ParseBreakdownSpec(const std::string& args, - cctz::time_zone::absolute_lookup* when) { +bool ParseBreakdownSpec(const std::string& args, cctz::civil_second* 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 = utc.lookup(tp); + *when = cctz::convert(tp, utc); return true; } } @@ -103,12 +102,13 @@ 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::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 << "]"; + cctz::time_zone::absolute_lookup al = zone.lookup(when); + cctz::civil_day cd(al.cs); + oss << " [wd=" << WeekDayName(cctz::get_weekday(cd)) + << " yd=" << std::setw(3) << std::setfill('0') + << std::right << cctz::get_yearday(cd) + << " dst=" << (al.is_dst ? 'T' : 'F') + << " off=" << std::showpos << al.offset << std::noshowpos << "]"; return oss.str(); } @@ -132,30 +132,29 @@ void InstantInfo(const std::string& label, time_point when, std::cout << "}\n"; } -// 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) { +// Report everything we know about a cctz::civil_second (YMDHMS). +int BreakdownInfo(const cctz::civil_second& cs, cctz::time_zone zone) { + cctz::time_zone::civil_lookup cl = zone.lookup(cs); + switch (cl.kind) { case cctz::time_zone::civil_lookup::UNIQUE: { std::cout << "kind: UNIQUE\n"; - InstantInfo("when", ti.pre, zone); + InstantInfo("when", cl.pre, zone); break; } 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); - InstantInfo("trans", ti.trans, zone); - InstantInfo("pre", ti.pre, zone); // might == trans + InstantInfo("post", cl.post, zone); // might == trans-1 + InstantInfo("trans-1", cl.trans - std::chrono::seconds(1), zone); + InstantInfo("trans", cl.trans, zone); + InstantInfo("pre", cl.pre, zone); // might == trans break; } 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); - InstantInfo("trans", ti.trans, zone); - InstantInfo("post", ti.post, zone); // might == trans + InstantInfo("pre", cl.pre, zone); // might == trans-1 + InstantInfo("trans-1", cl.trans - std::chrono::seconds(1), zone); + InstantInfo("trans", cl.trans, zone); + InstantInfo("post", cl.post, zone); // might == trans break; } } @@ -247,7 +246,7 @@ int main(int argc, char** argv) { } } } - cctz::time_zone::absolute_lookup when = zone.lookup(tp); + cctz::civil_second when = cctz::convert(tp, zone); bool have_break_down = !have_time && ParseBreakdownSpec(args, &when); // Show results. diff --git a/src/time_zone.h b/src/time_zone.h index 59146825..a033a160 100644 --- a/src/time_zone.h +++ b/src/time_zone.h @@ -64,7 +64,7 @@ class 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; + // const cctz::time_zone::absolute_lookup al = tz.lookup(tp); struct absolute_lookup { civil_second cs; // Note: The following fields exist for backward compatibility with @@ -147,6 +147,24 @@ time_zone utc_time_zone(); // Returns a time zone representing the local time zone. Falls back to UTC. time_zone local_time_zone(); +// The full information provided by the time_zone::absolute_lookup and +// time_zone::civil_lookup structs is frequently not needed by callers. +// Overloaded non-member convert() functions are provided to simplify the +// common cases. +template +inline civil_second convert(const time_point& tp, const time_zone& tz) { + return tz.lookup(tp).cs; +} + +// The return value here is chosen such that the relative order of civil +// times is preserved across conversions. +inline time_point convert(const civil_second& cs, + const time_zone& tz) { + const time_zone::civil_lookup cl = tz.lookup(cs); + if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans; + return cl.pre; +} + namespace internal { // Floors tp to a second boundary and sets *subseconds. template @@ -186,7 +204,7 @@ FloorSeconds(const time_point& tp) { // 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; +// auto tp = cctz::convert(cctz::civil_second(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" std::string format(const std::string&, const time_point&, diff --git a/src/time_zone_format.cc b/src/time_zone_format.cc index 260123eb..ba5be745 100644 --- a/src/time_zone_format.cc +++ b/src/time_zone_format.cc @@ -47,24 +47,24 @@ char* strptime(const char* s, const char* fmt, std::tm* tm) { } #endif -std::tm ToTM(const time_zone::absolute_lookup& bd) { +std::tm ToTM(const time_zone::absolute_lookup& al) { std::tm tm{}; - 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; + tm.tm_sec = al.cs.second(); + tm.tm_min = al.cs.minute(); + tm.tm_hour = al.cs.hour(); + tm.tm_mday = al.cs.day(); + tm.tm_mon = al.cs.month() - 1; // Saturate tm.tm_year is cases of over/underflow. - if (bd.cs.year() < std::numeric_limits::min() + 1900) { + if (al.cs.year() < std::numeric_limits::min() + 1900) { tm.tm_year = std::numeric_limits::min(); - } else if (bd.cs.year() - 1900 > std::numeric_limits::max()) { + } else if (al.cs.year() - 1900 > std::numeric_limits::max()) { tm.tm_year = std::numeric_limits::max(); } else { - tm.tm_year = static_cast(bd.cs.year() - 1900); + tm.tm_year = static_cast(al.cs.year() - 1900); } - switch (get_weekday(civil_day(bd.cs))) { + switch (get_weekday(civil_day(al.cs))) { case weekday::sunday: tm.tm_wday = 0; break; @@ -87,8 +87,8 @@ std::tm ToTM(const time_zone::absolute_lookup& bd) { tm.tm_wday = 6; break; } - tm.tm_yday = get_yearday(civil_day(bd.cs)) - 1; - tm.tm_isdst = bd.is_dst ? 1 : 0; + tm.tm_yday = get_yearday(civil_day(al.cs)) - 1; + tm.tm_isdst = al.is_dst ? 1 : 0; return tm; } @@ -262,8 +262,8 @@ const int64_t kExp10[kDigits10_64 + 1] = { std::string format(const std::string& format, const time_point& tp, const std::chrono::nanoseconds& ns, const time_zone& tz) { std::string result; - const time_zone::absolute_lookup bd = tz.lookup(tp); - const std::tm tm = ToTM(bd); + const time_zone::absolute_lookup al = tz.lookup(tp); + const std::tm tm = ToTM(al); // Scratch buffer for internal conversions. char buf[3 + kDigits10_64]; // enough for longest conversion @@ -318,37 +318,37 @@ 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.cs.year()); + bp = Format64(ep, 0, al.cs.year()); result.append(bp, ep - bp); break; case 'm': - bp = Format02d(ep, bd.cs.month()); + bp = Format02d(ep, al.cs.month()); result.append(bp, ep - bp); break; case 'd': case 'e': - bp = Format02d(ep, bd.cs.day()); + bp = Format02d(ep, al.cs.day()); if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows result.append(bp, ep - bp); break; case 'H': - bp = Format02d(ep, bd.cs.hour()); + bp = Format02d(ep, al.cs.hour()); result.append(bp, ep - bp); break; case 'M': - bp = Format02d(ep, bd.cs.minute()); + bp = Format02d(ep, al.cs.minute()); result.append(bp, ep - bp); break; case 'S': - bp = Format02d(ep, bd.cs.second()); + bp = Format02d(ep, al.cs.second()); result.append(bp, ep - bp); break; case 'z': - bp = FormatOffset(ep, bd.offset / 60, '\0'); + bp = FormatOffset(ep, al.offset / 60, '\0'); result.append(bp, ep - bp); break; case 'Z': - result.append(bd.abbr); + result.append(al.abbr); break; case 's': bp = Format64(ep, 0, ToUnixSeconds(tp)); @@ -368,7 +368,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 = FormatOffset(ep, bd.offset / 60, ':'); + bp = FormatOffset(ep, al.offset / 60, ':'); result.append(bp, ep - bp); pending = ++cur; } else if (*cur == '*' && cur + 1 != end && *(cur + 1) == 'S') { @@ -380,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.cs.second()); + bp = Format02d(bp, al.cs.second()); result.append(bp, cp - bp); pending = cur += 2; } else if (*cur == '4' && cur + 1 != end && *(cur + 1) == 'Y') { @@ -388,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.cs.year()); + bp = Format64(ep, 4, al.cs.year()); result.append(bp, ep - bp); pending = cur += 2; } else if (std::isdigit(*cur)) { @@ -407,7 +407,7 @@ std::string format(const std::string& format, const time_point& tp, : ns.count() / kExp10[9 - n]); *--bp = '.'; } - bp = Format02d(bp, bd.cs.second()); + bp = Format02d(bp, al.cs.second()); result.append(bp, ep - bp); pending = cur = np; } diff --git a/src/time_zone_format_test.cc b/src/time_zone_format_test.cc index 1798ed76..989fc237 100644 --- a/src/time_zone_format_test.cc +++ b/src/time_zone_format_test.cc @@ -38,17 +38,18 @@ namespace { // This helper is a macro so that failed expectations show up with the // correct line numbers. -#define ExpectTime(bd, y, m, d, hh, mm, ss, off, isdst, zone) \ - do { \ - 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); \ +#define ExpectTime(tp, tz, y, m, d, hh, mm, ss, off, isdst, zone) \ + do { \ + time_zone::absolute_lookup al = tz.lookup(tp); \ + EXPECT_EQ(y, al.cs.year()); \ + EXPECT_EQ(m, al.cs.month()); \ + EXPECT_EQ(d, al.cs.day()); \ + EXPECT_EQ(hh, al.cs.hour()); \ + EXPECT_EQ(mm, al.cs.minute()); \ + EXPECT_EQ(ss, al.cs.second()); \ + EXPECT_EQ(off, al.offset); \ + EXPECT_EQ(isdst, al.is_dst); \ + EXPECT_EQ(zone, al.abbr); \ } while (0) const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; @@ -68,12 +69,6 @@ void TestFormatSpecifier(time_point tp, time_zone tz, const std::string& fmt, 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 // @@ -304,31 +299,31 @@ TEST(Format, ExtendedYears) { 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); + auto tp = convert(civil_second(-999, 11, 27, 0, 0, 0), utc); EXPECT_EQ("-9991127", format(e4y_fmt, tp, utc)); - tp = MakeTime(-99, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(-99, 11, 27, 0, 0, 0), utc); EXPECT_EQ("-0991127", format(e4y_fmt, tp, utc)); - tp = MakeTime(-9, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(-9, 11, 27, 0, 0, 0), utc); EXPECT_EQ("-0091127", format(e4y_fmt, tp, utc)); - tp = MakeTime(-1, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(-1, 11, 27, 0, 0, 0), utc); EXPECT_EQ("-0011127", format(e4y_fmt, tp, utc)); - tp = MakeTime(0, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(0, 11, 27, 0, 0, 0), utc); EXPECT_EQ("00001127", format(e4y_fmt, tp, utc)); - tp = MakeTime(1, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(1, 11, 27, 0, 0, 0), utc); EXPECT_EQ("00011127", format(e4y_fmt, tp, utc)); - tp = MakeTime(9, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(9, 11, 27, 0, 0, 0), utc); EXPECT_EQ("00091127", format(e4y_fmt, tp, utc)); - tp = MakeTime(99, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(99, 11, 27, 0, 0, 0), utc); EXPECT_EQ("00991127", format(e4y_fmt, tp, utc)); - tp = MakeTime(999, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(999, 11, 27, 0, 0, 0), utc); EXPECT_EQ("09991127", format(e4y_fmt, tp, utc)); - tp = MakeTime(9999, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(9999, 11, 27, 0, 0, 0), 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); + tp = convert(civil_second(-1000, 11, 27, 0, 0, 0), utc); EXPECT_EQ("-10001127", format(e4y_fmt, tp, utc)); - tp = MakeTime(10000, 11, 27, 0, 0, 0, utc); + tp = convert(civil_second(10000, 11, 27, 0, 0, 0), utc); EXPECT_EQ("100001127", format(e4y_fmt, tp, utc)); } @@ -336,7 +331,8 @@ TEST(Format, RFC3339Format) { time_zone tz; EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); - time_point tp = MakeTime(1977, 6, 28, 9, 8, 7, tz); + time_point tp = + convert(civil_second(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)); @@ -382,7 +378,7 @@ TEST(Format, RFC1123Format) { // locale specific time_zone tz; EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); - auto tp = MakeTime(1977, 6, 28, 9, 8, 7, tz); + auto tp = convert(civil_second(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)); } @@ -448,8 +444,7 @@ TEST(Parse, Basics) { EXPECT_TRUE( 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"); + ExpectTime(tp, tz, 2013, 6, 29, 3, 8, 9, 0, false, "UTC"); } TEST(Parse, WithTimeZone) { @@ -459,26 +454,22 @@ TEST(Parse, WithTimeZone) { // 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)); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2013, 6, 28, 19, 8, 9, -7 * 60 * 60, true, "PDT"); + ExpectTime(tp, tz, 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 = utc_time_zone().lookup(tp); - ExpectTime(bd, 2013, 6, 28, 11, 8, 9, 0, false, "UTC"); + EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 +0800", + utc_time_zone(), &tp)); + ExpectTime(tp, tz, 2013, 6, 28, 19 - 8 - 7, 8, 9, -7 * 60 * 60, true, "PDT"); // 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 = tz.lookup(tp); - ExpectTime(bd, 2011, 3, 13, 3, 15, 0, -7 * 60 * 60, true, "PDT"); + ExpectTime(tp, tz, 2011, 3, 13, 3, 15, 0, -7 * 60 * 60, true, "PDT"); // 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 = tz.lookup(tp); - ExpectTime(bd, 2011, 11, 6, 1, 15, 0, -7 * 60 * 60, true, "PDT"); + ExpectTime(tp, tz, 2011, 11, 6, 1, 15, 0, -7 * 60 * 60, true, "PDT"); } TEST(Parse, LeapSecond) { @@ -488,23 +479,19 @@ TEST(Parse, LeapSecond) { // ":59" -> ":59" 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"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 6, 28, 8, 8, 59, -7 * 60 * 60, true, "PDT"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 6, 28, 8, 9, 0, -7 * 60 * 60, true, "PDT"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 6, 28, 8, 9, 0, -7 * 60 * 60, true, "PDT"); + ExpectTime(tp, tz, 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)); @@ -525,8 +512,8 @@ TEST(Parse, ErrorCases) { // Trailing whitespace is allowed. 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()); + EXPECT_EQ(2, convert(tp, utc_time_zone()).month()); + EXPECT_EQ(3, convert(tp, utc_time_zone()).day()); // Feb 31 requires normalization. EXPECT_FALSE(parse("%m-%d", "2-31", tz, &tp)); @@ -556,39 +543,39 @@ TEST(Parse, ErrorCases) { TEST(Parse, PosixConversions) { 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); + const auto reset = convert(civil_second(1977, 6, 28, 9, 8, 7), tz); tp = reset; EXPECT_TRUE(parse("%d", "15", tz, &tp)); - EXPECT_EQ(15, tz.lookup(tp).cs.day()); + EXPECT_EQ(15, convert(tp, tz).day()); // %e is an extension, but is supported internally. tp = reset; EXPECT_TRUE(parse("%e", "15", tz, &tp)); - EXPECT_EQ(15, tz.lookup(tp).cs.day()); // Equivalent to %d + EXPECT_EQ(15, convert(tp, tz).day()); // Equivalent to %d tp = reset; EXPECT_TRUE(parse("%H", "17", tz, &tp)); - EXPECT_EQ(17, tz.lookup(tp).cs.hour()); + EXPECT_EQ(17, convert(tp, tz).hour()); tp = reset; EXPECT_TRUE(parse("%I", "5", tz, &tp)); - EXPECT_EQ(5, tz.lookup(tp).cs.hour()); + EXPECT_EQ(5, convert(tp, tz).hour()); // %j is parsed but ignored. EXPECT_TRUE(parse("%j", "32", tz, &tp)); tp = reset; EXPECT_TRUE(parse("%m", "11", tz, &tp)); - EXPECT_EQ(11, tz.lookup(tp).cs.month()); + EXPECT_EQ(11, convert(tp, tz).month()); tp = reset; EXPECT_TRUE(parse("%M", "33", tz, &tp)); - EXPECT_EQ(33, tz.lookup(tp).cs.minute()); + EXPECT_EQ(33, convert(tp, tz).minute()); tp = reset; EXPECT_TRUE(parse("%S", "55", tz, &tp)); - EXPECT_EQ(55, tz.lookup(tp).cs.second()); + EXPECT_EQ(55, convert(tp, tz).second()); // %U is parsed but ignored. EXPECT_TRUE(parse("%U", "15", tz, &tp)); @@ -601,11 +588,11 @@ TEST(Parse, PosixConversions) { tp = reset; EXPECT_TRUE(parse("%y", "04", tz, &tp)); - EXPECT_EQ(2004, tz.lookup(tp).cs.year()); + EXPECT_EQ(2004, convert(tp, tz).year()); tp = reset; EXPECT_TRUE(parse("%Y", "2004", tz, &tp)); - EXPECT_EQ(2004, tz.lookup(tp).cs.year()); + EXPECT_EQ(2004, convert(tp, tz).year()); EXPECT_TRUE(parse("%%", "%", tz, &tp)); @@ -614,28 +601,28 @@ TEST(Parse, PosixConversions) { tp = reset; EXPECT_TRUE(parse("%C", "20", tz, &tp)); - EXPECT_EQ(2000, tz.lookup(tp).cs.year()); + EXPECT_EQ(2000, convert(tp, tz).year()); tp = reset; 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_EQ(2, convert(tp, tz).month()); + EXPECT_EQ(3, convert(tp, tz).day()); + EXPECT_EQ(2004, convert(tp, tz).year()); EXPECT_TRUE(parse("%n", "\n", tz, &tp)); tp = reset; 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_EQ(3, convert(tp, tz).hour()); + EXPECT_EQ(44, convert(tp, tz).minute()); 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, tz.lookup(tp).cs.hour()); - EXPECT_EQ(44, tz.lookup(tp).cs.minute()); - EXPECT_EQ(55, tz.lookup(tp).cs.second()); + EXPECT_EQ(3, convert(tp, tz).hour()); + EXPECT_EQ(44, convert(tp, tz).minute()); + EXPECT_EQ(55, convert(tp, tz).second()); tp = reset; EXPECT_TRUE(parse("%s", "1234567890", tz, &tp)); @@ -664,7 +651,7 @@ TEST(Parse, PosixConversions) { TEST(Parse, LocaleSpecific) { 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); + const auto reset = convert(civil_second(1977, 6, 28, 9, 8, 7), tz); // %a is parsed but ignored. EXPECT_TRUE(parse("%a", "Mon", tz, &tp)); @@ -674,68 +661,68 @@ TEST(Parse, LocaleSpecific) { tp = reset; EXPECT_TRUE(parse("%b", "Feb", tz, &tp)); - EXPECT_EQ(2, tz.lookup(tp).cs.month()); + EXPECT_EQ(2, convert(tp, tz).month()); tp = reset; EXPECT_TRUE(parse("%B", "February", tz, &tp)); - EXPECT_EQ(2, tz.lookup(tp).cs.month()); + EXPECT_EQ(2, convert(tp, tz).month()); // %p is parsed but ignored if it's alone. But it's used with %I. EXPECT_TRUE(parse("%p", "AM", tz, &tp)); tp = reset; EXPECT_TRUE(parse("%I %p", "5 PM", tz, &tp)); - EXPECT_EQ(17, tz.lookup(tp).cs.hour()); + EXPECT_EQ(17, convert(tp, tz).hour()); tp = reset; 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()); + EXPECT_EQ(2, convert(tp, tz).month()); + EXPECT_EQ(3, convert(tp, tz).day()); + EXPECT_EQ(2004, convert(tp, tz).year()); tp = reset; 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()); + EXPECT_EQ(15, convert(tp, tz).hour()); + EXPECT_EQ(44, convert(tp, tz).minute()); + EXPECT_EQ(55, convert(tp, tz).second()); #if defined(__linux__) // SU/C99/TZ extensions tp = reset; EXPECT_TRUE(parse("%h", "Feb", tz, &tp)); - EXPECT_EQ(2, tz.lookup(tp).cs.month()); // Equivalent to %b + EXPECT_EQ(2, convert(tp, tz).month()); // Equivalent to %b tp = reset; EXPECT_TRUE(parse("%l %p", "5 PM", tz, &tp)); - EXPECT_EQ(17, tz.lookup(tp).cs.hour()); + EXPECT_EQ(17, convert(tp, tz).hour()); tp = reset; 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()); + EXPECT_EQ(15, convert(tp, tz).hour()); + EXPECT_EQ(44, convert(tp, tz).minute()); + EXPECT_EQ(55, convert(tp, tz).second()); tp = reset; EXPECT_TRUE(parse("%Ec", "Tue Nov 19 05:06:07 2013", tz, &tp)); - EXPECT_EQ(MakeTime(2013, 11, 19, 5, 6, 7, tz), tp); + EXPECT_EQ(convert(civil_second(2013, 11, 19, 5, 6, 7), tz), tp); // Modified conversion specifiers %E_ tp = reset; EXPECT_TRUE(parse("%EC", "20", tz, &tp)); - EXPECT_EQ(2000, tz.lookup(tp).cs.year()); + EXPECT_EQ(2000, convert(tp, tz).year()); tp = reset; 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()); + EXPECT_EQ(2, convert(tp, tz).month()); + EXPECT_EQ(3, convert(tp, tz).day()); + EXPECT_EQ(2004, convert(tp, tz).year()); tp = reset; 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()); + EXPECT_EQ(15, convert(tp, tz).hour()); + EXPECT_EQ(44, convert(tp, tz).minute()); + EXPECT_EQ(55, convert(tp, tz).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). @@ -745,42 +732,42 @@ TEST(Parse, LocaleSpecific) { #if 0 tp = reset; EXPECT_TRUE(parse("%Ey", "04", tz, &tp)); - EXPECT_EQ(2004, tz.lookup(tp).cs.year()); + EXPECT_EQ(2004, convert(tp, tz).year()); #endif tp = reset; EXPECT_TRUE(parse("%EY", "2004", tz, &tp)); - EXPECT_EQ(2004, tz.lookup(tp).cs.year()); + EXPECT_EQ(2004, convert(tp, tz).year()); // Modified conversion specifiers %O_ tp = reset; EXPECT_TRUE(parse("%Od", "15", tz, &tp)); - EXPECT_EQ(15, tz.lookup(tp).cs.day()); + EXPECT_EQ(15, convert(tp, tz).day()); tp = reset; EXPECT_TRUE(parse("%Oe", "15", tz, &tp)); - EXPECT_EQ(15, tz.lookup(tp).cs.day()); // Equivalent to %d + EXPECT_EQ(15, convert(tp, tz).day()); // Equivalent to %d tp = reset; EXPECT_TRUE(parse("%OH", "17", tz, &tp)); - EXPECT_EQ(17, tz.lookup(tp).cs.hour()); + EXPECT_EQ(17, convert(tp, tz).hour()); tp = reset; EXPECT_TRUE(parse("%OI", "5", tz, &tp)); - EXPECT_EQ(5, tz.lookup(tp).cs.hour()); + EXPECT_EQ(5, convert(tp, tz).hour()); tp = reset; EXPECT_TRUE(parse("%Om", "11", tz, &tp)); - EXPECT_EQ(11, tz.lookup(tp).cs.month()); + EXPECT_EQ(11, convert(tp, tz).month()); tp = reset; EXPECT_TRUE(parse("%OM", "33", tz, &tp)); - EXPECT_EQ(33, tz.lookup(tp).cs.minute()); + EXPECT_EQ(33, convert(tp, tz).minute()); tp = reset; EXPECT_TRUE(parse("%OS", "55", tz, &tp)); - EXPECT_EQ(55, tz.lookup(tp).cs.second()); + EXPECT_EQ(55, convert(tp, tz).second()); // %OU is parsed but ignored. EXPECT_TRUE(parse("%OU", "15", tz, &tp)); @@ -793,7 +780,7 @@ TEST(Parse, LocaleSpecific) { tp = reset; EXPECT_TRUE(parse("%Oy", "04", tz, &tp)); - EXPECT_EQ(2004, tz.lookup(tp).cs.year()); + EXPECT_EQ(2004, convert(tp, tz).year()); #endif } @@ -845,47 +832,47 @@ TEST(Parse, ExtendedOffset) { // %z against +-HHMM. EXPECT_TRUE(parse("%z", "+0000", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse("%z", "-1234", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 12, 34, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); EXPECT_TRUE(parse("%z", "+1234", utc, &tp)); - EXPECT_EQ(MakeTime(1969, 12, 31, 11, 26, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp); EXPECT_FALSE(parse("%z", "-123", utc, &tp)); // %z against +-HH. EXPECT_TRUE(parse("%z", "+00", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse("%z", "-12", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 12, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp); EXPECT_TRUE(parse("%z", "+12", utc, &tp)); - EXPECT_EQ(MakeTime(1969, 12, 31, 12, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp); EXPECT_FALSE(parse("%z", "-1", utc, &tp)); // %Ez against +-HH:MM. EXPECT_TRUE(parse("%Ez", "+00:00", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse("%Ez", "-12:34", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 12, 34, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); EXPECT_TRUE(parse("%Ez", "+12:34", utc, &tp)); - EXPECT_EQ(MakeTime(1969, 12, 31, 11, 26, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp); EXPECT_FALSE(parse("%Ez", "-12:3", utc, &tp)); // %Ez against +-HHMM. EXPECT_TRUE(parse("%Ez", "+0000", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse("%Ez", "-1234", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 12, 34, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); EXPECT_TRUE(parse("%Ez", "+1234", utc, &tp)); - EXPECT_EQ(MakeTime(1969, 12, 31, 11, 26, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp); EXPECT_FALSE(parse("%Ez", "-123", utc, &tp)); // %Ez against +-HH. EXPECT_TRUE(parse("%Ez", "+00", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse("%Ez", "-12", utc, &tp)); - EXPECT_EQ(MakeTime(1970, 1, 1, 12, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp); EXPECT_TRUE(parse("%Ez", "+12", utc, &tp)); - EXPECT_EQ(MakeTime(1969, 12, 31, 12, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp); EXPECT_FALSE(parse("%Ez", "-1", utc, &tp)); } @@ -896,25 +883,25 @@ TEST(Parse, ExtendedYears) { // %E4Y consumes exactly four chars, including any sign. EXPECT_TRUE(parse(e4y_fmt, "-9991127", utc, &tp)); - EXPECT_EQ(MakeTime(-999, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(-999, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "-0991127", utc, &tp)); - EXPECT_EQ(MakeTime(-99, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(-99, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "-0091127", utc, &tp)); - EXPECT_EQ(MakeTime(-9, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(-9, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "-0011127", utc, &tp)); - EXPECT_EQ(MakeTime(-1, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(-1, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "00001127", utc, &tp)); - EXPECT_EQ(MakeTime(0, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(0, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "00011127", utc, &tp)); - EXPECT_EQ(MakeTime(1, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(1, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "00091127", utc, &tp)); - EXPECT_EQ(MakeTime(9, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(9, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "00991127", utc, &tp)); - EXPECT_EQ(MakeTime(99, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(99, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "09991127", utc, &tp)); - EXPECT_EQ(MakeTime(999, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(999, 11, 27, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(e4y_fmt, "99991127", utc, &tp)); - EXPECT_EQ(MakeTime(9999, 11, 27, 0, 0, 0, utc), tp); + EXPECT_EQ(convert(civil_second(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)); @@ -925,8 +912,7 @@ TEST(Parse, RFC3339Format) { const time_zone tz = utc_time_zone(); time_point tp; 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"); + ExpectTime(tp, tz, 2014, 2, 12, 20, 21, 0, 0, false, "UTC"); // Check that %Ez also accepts "Z" as a synonym for "+00:00". time_point tp2; @@ -941,7 +927,7 @@ TEST(Parse, RFC3339Format) { TEST(FormatParse, RoundTrip) { 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 in = convert(civil_second(1977, 6, 28, 9, 8, 7), lax); const auto subseconds = nanoseconds(654321); // RFC3339, which renders subseconds. diff --git a/src/time_zone_impl.cc b/src/time_zone_impl.cc index 2792601a..9eef9ea9 100644 --- a/src/time_zone_impl.cc +++ b/src/time_zone_impl.cc @@ -101,12 +101,13 @@ 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); + Breakdown bd = 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; + res.cs = + civil_second(bd.year, bd.month, bd.day, bd.hour, bd.minute, bd.second); + res.offset = bd.offset; + res.is_dst = bd.is_dst; + res.abbr = bd.abbr; return res; } diff --git a/src/time_zone_lookup_test.cc b/src/time_zone_lookup_test.cc index e4e8f0d0..b27a4b0c 100644 --- a/src/time_zone_lookup_test.cc +++ b/src/time_zone_lookup_test.cc @@ -625,31 +625,20 @@ time_zone LoadZone(const std::string& name) { // This helper is a macro so that failed expectations show up with the // correct line numbers. -#define ExpectTime(bd, y, m, d, hh, mm, ss, off, isdst, zone) \ - do { \ - 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); \ +#define ExpectTime(tp, tz, y, m, d, hh, mm, ss, off, isdst, zone) \ + do { \ + time_zone::absolute_lookup al = tz.lookup(tp); \ + EXPECT_EQ(y, al.cs.year()); \ + EXPECT_EQ(m, al.cs.month()); \ + EXPECT_EQ(d, al.cs.day()); \ + EXPECT_EQ(hh, al.cs.hour()); \ + EXPECT_EQ(mm, al.cs.minute()); \ + EXPECT_EQ(ss, al.cs.second()); \ + EXPECT_EQ(off, al.offset); \ + EXPECT_EQ(isdst, al.is_dst); \ + EXPECT_EQ(zone, al.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) { @@ -685,19 +674,19 @@ TEST(TimeZone, Failures) { tz = LoadZone("America/Los_Angeles"); 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 + convert(civil_second(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(load_time_zone("Invalid/TimeZone", &tz)); EXPECT_EQ(system_clock::from_time_t(0), - MakeTime(1970, 1, 1, 0, 0, 0, tz)); // UTC + convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC // Loading an empty string timezone should fail. tz = LoadZone("America/Los_Angeles"); EXPECT_FALSE(load_time_zone("", &tz)); EXPECT_EQ(system_clock::from_time_t(0), - MakeTime(1970, 1, 1, 0, 0, 0, tz)); // UTC + convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC } TEST(StdChronoTimePoint, TimeTAlignment) { @@ -712,163 +701,153 @@ TEST(BreakTime, TimePointResolution) { using std::chrono::time_point_cast; const time_zone utc = utc_time_zone(); const auto t0 = system_clock::from_time_t(0); - time_zone::absolute_lookup bd{}; - - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); - bd = utc.lookup(time_point_cast(t0)); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + ExpectTime(time_point_cast(t0), utc, + 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); } TEST(BreakTime, LocalTimeInUTC) { - 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(weekday::thursday, get_weekday(civil_day(bd.cs))); + const auto tp = system_clock::from_time_t(0); + const time_zone::absolute_lookup al = utc_time_zone().lookup(tp); + ExpectTime(tp, utc_time_zone(), 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + EXPECT_EQ(weekday::thursday, + get_weekday(civil_day(convert(tp, utc_time_zone())))); } TEST(BreakTime, LocalTimePosix) { // See IEEE Std 1003.1-1988 B.2.3 General Terms, Epoch. - 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(weekday::wednesday, get_weekday(civil_day(bd.cs))); + const auto tp = system_clock::from_time_t(536457599); + const time_zone::absolute_lookup al = utc_time_zone().lookup(tp); + ExpectTime(tp, utc_time_zone(), 1986, 12, 31, 23, 59, 59, 0, false, "UTC"); + EXPECT_EQ(weekday::wednesday, + get_weekday(civil_day(convert(tp, utc_time_zone())))); } TEST(BreakTime, LocalTimeInNewYork) { 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(weekday::wednesday, get_weekday(civil_day(bd.cs))); + const auto tp = system_clock::from_time_t(45); + ExpectTime(tp, tz, 1969, 12, 31, 19, 0, 45, -5 * 60 * 60, false, "EST"); + EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz)))); } TEST(BreakTime, LocalTimeInMTV) { 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(weekday::thursday, get_weekday(civil_day(bd.cs))); + const auto tp = system_clock::from_time_t(1380855729); + ExpectTime(tp, tz, 2013, 10, 3, 20, 2, 9, -7 * 60 * 60, true, "PDT"); + EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz)))); } TEST(BreakTime, LocalTimeInSydney) { 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(weekday::thursday, get_weekday(civil_day(bd.cs))); + const auto tp = system_clock::from_time_t(90); + const time_zone::absolute_lookup al = tz.lookup(tp); + ExpectTime(tp, tz, 1970, 1, 1, 10, 1, 30, 10 * 60 * 60, false, "AEST"); + EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz)))); } TEST(MakeTime, TimePointResolution) { const time_zone utc = utc_time_zone(); const time_point tp_ns = - MakeTime(2015, 1, 2, 3, 4, 5, utc); + convert(civil_second(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); + convert(civil_second(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); + convert(civil_second(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); + convert(civil_second(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); + const time_point tp_s64 = + convert(civil_second(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 + // resolution of seconds (the return value of convert()) to a coarser // resolution requires an explicit cast. using std::chrono::time_point_cast; const time_point tp_m = - time_point_cast(MakeTime(2015, 1, 2, 3, 4, 5, utc)); + time_point_cast( + convert(civil_second(2015, 1, 2, 3, 4, 5), 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)); + time_point_cast( + convert(civil_second(2015, 1, 2, 3, 4, 5), utc)); EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc)); } TEST(MakeTime, Normalization) { const time_zone tz = LoadZone("America/New_York"); - const auto tp = MakeTime(2009, 2, 13, 18, 31, 30, tz); + const auto tp = convert(civil_second(2009, 2, 13, 18, 31, 30), tz); EXPECT_EQ(system_clock::from_time_t(1234567890), tp); // Now requests for the same time_point but with out-of-range fields. - EXPECT_EQ(tp, MakeTime(2008, 14, 13, 18, 31, 30, tz)); // month - EXPECT_EQ(tp, MakeTime(2009, 1, 44, 18, 31, 30, tz)); // day - EXPECT_EQ(tp, MakeTime(2009, 2, 12, 42, 31, 30, tz)); // hour - EXPECT_EQ(tp, MakeTime(2009, 2, 13, 17, 91, 30, tz)); // minute - EXPECT_EQ(tp, MakeTime(2009, 2, 13, 18, 30, 90, tz)); // second + EXPECT_EQ(tp, convert(civil_second(2008, 14, 13, 18, 31, 30), tz)); // month + EXPECT_EQ(tp, convert(civil_second(2009, 1, 44, 18, 31, 30), tz)); // day + EXPECT_EQ(tp, convert(civil_second(2009, 2, 12, 42, 31, 30), tz)); // hour + EXPECT_EQ(tp, convert(civil_second(2009, 2, 13, 17, 91, 30), tz)); // minute + EXPECT_EQ(tp, convert(civil_second(2009, 2, 13, 18, 30, 90), tz)); // second } TEST(TimeZoneEdgeCase, AmericaNewYork) { 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST"); + auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 3, 10, 3, 0, 0, -4 * 3600, true, "EDT"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT"); + tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST"); + ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST"); } TEST(TimeZoneEdgeCase, AmericaLosAngeles) { 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST"); + auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 3, 10, 3, 0, 0, -7 * 3600, true, "PDT"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT"); + tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST"); + ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST"); } TEST(TimeZoneEdgeCase, ArizonaNoTransition) { const time_zone tz = LoadZone("America/Phoenix"); // No transition in Spring. - auto tp = MakeTime(2013, 3, 10, 1, 59, 59, tz); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST"); + auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST"); + ExpectTime(tp, tz, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST"); // No transition in Fall. - tp = MakeTime(2013, 11, 3, 1, 59, 59, tz); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST"); + tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); + ExpectTime(tp, tz, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); } TEST(TimeZoneEdgeCase, AsiaKathmandu) { @@ -878,12 +857,10 @@ TEST(TimeZoneEdgeCase, AsiaKathmandu) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "IST"); + auto tp = convert(civil_second(1985, 12, 31, 23, 59, 59), tz); + ExpectTime(tp, tz, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "IST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "NPT"); + ExpectTime(tp, tz, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "NPT"); } TEST(TimeZoneEdgeCase, PacificChatham) { @@ -893,21 +870,18 @@ TEST(TimeZoneEdgeCase, PacificChatham) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "CHADT"); + auto tp = convert(civil_second(2013, 4, 7, 3, 44, 59), tz); + ExpectTime(tp, tz, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "CHADT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 4, 7, 2, 45, 0, 12.75 * 3600, false, "CHAST"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, "CHAST"); + tp = convert(civil_second(2013, 9, 29, 2, 44, 59), tz); + ExpectTime(tp, tz, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, + "CHAST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "CHADT"); + ExpectTime(tp, tz, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "CHADT"); } TEST(TimeZoneEdgeCase, AustraliaLordHowe) { @@ -917,21 +891,17 @@ TEST(TimeZoneEdgeCase, AustraliaLordHowe) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "LHDT"); + auto tp = convert(civil_second(2013, 4, 7, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "LHDT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 4, 7, 1, 30, 0, 10.5 * 3600, false, "LHST"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "LHST"); + tp = convert(civil_second(2013, 10, 6, 1, 59, 59), tz); + ExpectTime(tp, tz, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "LHST"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "LHDT"); + ExpectTime(tp, tz, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "LHDT"); } TEST(TimeZoneEdgeCase, PacificApia) { @@ -945,14 +915,12 @@ 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "SDT"); - EXPECT_EQ(363, get_yearday(civil_day(bd.cs))); + auto tp = convert(civil_second(2011, 12, 29, 23, 59, 59), tz); + ExpectTime(tp, tz, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "SDT"); + EXPECT_EQ(363, get_yearday(civil_day(convert(tp, tz)))); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "WSDT"); - EXPECT_EQ(365, get_yearday(civil_day(bd.cs))); + ExpectTime(tp, tz, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "WSDT"); + EXPECT_EQ(365, get_yearday(civil_day(convert(tp, tz)))); } TEST(TimeZoneEdgeCase, AfricaCairo) { @@ -962,12 +930,10 @@ TEST(TimeZoneEdgeCase, AfricaCairo) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); + auto tp = convert(civil_second(2014, 5, 15, 23, 59, 59), tz); + ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); + ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); } TEST(TimeZoneEdgeCase, AfricaMonrovia) { @@ -977,12 +943,10 @@ TEST(TimeZoneEdgeCase, AfricaMonrovia) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 1972, 4, 30, 23, 59, 59, -44.5 * 60, false, "LRT"); + auto tp = convert(civil_second(1972, 4, 30, 23, 59, 59), tz); + ExpectTime(tp, tz, 1972, 4, 30, 23, 59, 59, -44.5 * 60, false, "LRT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 1972, 5, 1, 0, 44, 30, 0 * 60, false, "GMT"); + ExpectTime(tp, tz, 1972, 5, 1, 0, 44, 30, 0 * 60, false, "GMT"); } TEST(TimeZoneEdgeCase, AmericaJamaica) { @@ -994,34 +958,28 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) { const time_zone tz = LoadZone("America/Jamaica"); // Before the first transition. - auto tp = MakeTime(1889, 12, 31, 0, 0, 0, tz); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 1889, 12, 31, 0, 0, 0, -18431, false, bd.abbr); + auto tp = convert(civil_second(1889, 12, 31, 0, 0, 0), tz); + ExpectTime(tp, tz, 1889, 12, 31, 0, 0, 0, -18431, false, "LMT"); // 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 = tz.lookup(tp); - ExpectTime(bd, 1889, 12, 31, 23, 59, 59, -18431, false, bd.abbr); + tp = convert(civil_second(1889, 12, 31, 23, 59, 59), tz); + ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18431, false, "LMT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 1890, 1, 1, 0, 0, 0, -18431, false, "KMT"); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); + tp = convert(civil_second(1983, 10, 30, 1, 59, 59), tz); + ExpectTime(tp, tz, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); + ExpectTime(tp, tz, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); // After the last transition. - tp = MakeTime(1983, 12, 31, 23, 59, 59, tz); - bd = tz.lookup(tp); - ExpectTime(bd, 1983, 12, 31, 23, 59, 59, -5 * 3600, false, "EST"); + tp = convert(civil_second(1983, 12, 31, 23, 59, 59), tz); + ExpectTime(tp, tz, 1983, 12, 31, 23, 59, 59, -5 * 3600, false, "EST"); } TEST(TimeZoneEdgeCase, WET) { @@ -1029,66 +987,54 @@ TEST(TimeZoneEdgeCase, WET) { const time_zone tz = LoadZone("WET"); // Before the first transition. - auto tp = MakeTime(1977, 1, 1, 0, 0, 0, tz); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 1977, 1, 1, 0, 0, 0, 0, false, "WET"); + auto tp = convert(civil_second(1977, 1, 1, 0, 0, 0), tz); + ExpectTime(tp, tz, 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 = tz.lookup(tp); - ExpectTime(bd, 1977, 4, 3, 0, 59, 59, 0, false, "WET"); + tp = convert(civil_second(1977, 4, 3, 0, 59, 59), tz); + ExpectTime(tp, tz, 1977, 4, 3, 0, 59, 59, 0, false, "WET"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); + ExpectTime(tp, tz, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); // A non-existent time within the first transition. - 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 = tz.lookup(ti1.trans); - ExpectTime(bd, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); - bd = tz.lookup(ti1.post); - ExpectTime(bd, 1977, 4, 3, 0, 15, 0, 0 * 3600, false, "WET"); + time_zone::civil_lookup cl1 = tz.lookup(civil_second(1977, 4, 3, 1, 15, 0)); + EXPECT_EQ(time_zone::civil_lookup::SKIPPED, cl1.kind); + ExpectTime(cl1.pre, tz, 1977, 4, 3, 2, 15, 0, 1 * 3600, true, "WEST"); + ExpectTime(cl1.trans, tz, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); + ExpectTime(cl1.post, tz, 1977, 4, 3, 0, 15, 0, 0 * 3600, false, "WET"); // A non-existent time within the second forward transition. - 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 = tz.lookup(ti2.trans); - ExpectTime(bd, 1978, 4, 2, 2, 0, 0, 1 * 3600, true, "WEST"); - bd = tz.lookup(ti2.post); - ExpectTime(bd, 1978, 4, 2, 0, 15, 0, 0 * 3600, false, "WET"); + time_zone::civil_lookup cl2 = tz.lookup(civil_second(1978, 4, 2, 1, 15, 0)); + EXPECT_EQ(time_zone::civil_lookup::SKIPPED, cl2.kind); + ExpectTime(cl2.pre, tz, 1978, 4, 2, 2, 15, 0, 1 * 3600, true, "WEST"); + ExpectTime(cl2.trans, tz, 1978, 4, 2, 2, 0, 0, 1 * 3600, true, "WEST"); + ExpectTime(cl2.post, tz, 1978, 4, 2, 0, 15, 0, 0 * 3600, false, "WET"); } TEST(TimeZoneEdgeCase, FixedOffsets) { const time_zone gmtm5 = LoadZone("Etc/GMT+5"); // -0500 - auto tp = MakeTime(1970, 1, 1, 0, 0, 0, gmtm5); - time_zone::absolute_lookup bd = gmtm5.lookup(tp); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, -5 * 3600, false, "GMT+5"); + auto tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtm5); + ExpectTime(tp, gmtm5, 1970, 1, 1, 0, 0, 0, -5 * 3600, false, "GMT+5"); EXPECT_EQ(system_clock::from_time_t(5 * 3600), tp); const time_zone gmtp5 = LoadZone("Etc/GMT-5"); // +0500 - tp = MakeTime(1970, 1, 1, 0, 0, 0, gmtp5); - bd = gmtp5.lookup(tp); - ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 5 * 3600, false, "GMT-5"); + tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtp5); + ExpectTime(tp, gmtp5, 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 time_zone tz = utc_time_zone(); - auto tp = MakeTime(0, 1, 1, 0, 0, 0, tz); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); - EXPECT_EQ(weekday::saturday, get_weekday(civil_day(bd.cs))); + auto tp = convert(civil_second(0, 1, 1, 0, 0, 0), tz); + time_zone::absolute_lookup al = tz.lookup(tp); + ExpectTime(tp, tz, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); + EXPECT_EQ(weekday::saturday, get_weekday(civil_day(convert(tp, tz)))); tp -= std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); - EXPECT_EQ(weekday::friday, get_weekday(civil_day(bd.cs))); + ExpectTime(tp, tz, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); + EXPECT_EQ(weekday::friday, get_weekday(civil_day(convert(tp, tz)))); } TEST(TimeZoneEdgeCase, UTC32bitLimit) { @@ -1098,12 +1044,10 @@ TEST(TimeZoneEdgeCase, UTC32bitLimit) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC"); + auto tp = convert(civil_second(2038, 1, 19, 3, 14, 7), tz); + ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); + ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); } TEST(TimeZoneEdgeCase, UTC5DigitYear) { @@ -1113,12 +1057,10 @@ TEST(TimeZoneEdgeCase, UTC5DigitYear) { // // 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); - time_zone::absolute_lookup bd = tz.lookup(tp); - ExpectTime(bd, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); + auto tp = convert(civil_second(9999, 12, 31, 23, 59, 59), tz); + ExpectTime(tp, tz, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); tp += std::chrono::seconds(1); - bd = tz.lookup(tp); - ExpectTime(bd, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); + ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); } } // namespace cctz From 820bbb2805fb8e60f42be4fe50e9c6b9982648c5 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 8 Mar 2016 09:43:50 -0500 Subject: [PATCH 03/69] Correct k_dp4y[] size. --- src/civil_time_detail.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/civil_time_detail.h b/src/civil_time_detail.h index 7b56a1f5..de01511f 100644 --- a/src/civil_time_detail.h +++ b/src/civil_time_detail.h @@ -65,7 +65,7 @@ constexpr signed char k_dpC[400] = { // 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] = { +constexpr signed char k_dp4y[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, From fa5f24470ce87e7bfd85a1327527711ce19471a7 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 8 Mar 2016 11:25:56 -0500 Subject: [PATCH 04/69] Fix a get_weekday() comment typo. --- src/civil_time.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/civil_time.h b/src/civil_time.h index 03d609b7..13a352df 100644 --- a/src/civil_time.h +++ b/src/civil_time.h @@ -117,7 +117,7 @@ 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 +// weekday wd = get_weekday(a); // wd == weekday::thursday // using detail::get_weekday; @@ -135,7 +135,7 @@ using detail::get_weekday; // 23 24 25 26 27 28 29 // 30 31 // -// civil_day a(2015, 8, 13); // Thursday +// civil_day a(2015, 8, 13); // get_weekday(a) == weekday::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 // From 48703f300f38b0f5802ce7d2290766c9eb52df53 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 8 Mar 2016 11:29:33 -0500 Subject: [PATCH 05/69] Move the public headers to their own directory. --- BUILD | 9 ++++++--- {src => include}/civil_time.h | 0 {src => include}/time_zone.h | 0 3 files changed, 6 insertions(+), 3 deletions(-) rename {src => include}/civil_time.h (100%) rename {src => include}/time_zone.h (100%) diff --git a/BUILD b/BUILD index 1b730a30..2b8d28ad 100644 --- a/BUILD +++ b/BUILD @@ -32,10 +32,13 @@ cc_library( "src/tzfile.h", ], hdrs = [ - "src/civil_time.h", - "src/time_zone.h", + "include/civil_time.h", + "include/time_zone.h", + ], + includes = [ + "include", + "src", ], - includes = ["src"], linkopts = [ "-lm", "-lpthread", diff --git a/src/civil_time.h b/include/civil_time.h similarity index 100% rename from src/civil_time.h rename to include/civil_time.h diff --git a/src/time_zone.h b/include/time_zone.h similarity index 100% rename from src/time_zone.h rename to include/time_zone.h From ce8a534c22083a43e47802a560bedee1c41a7860 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 8 Mar 2016 11:42:08 -0500 Subject: [PATCH 06/69] Move civil_time_detail.h to the public header directory too. --- BUILD | 7 ++----- {src => include}/civil_time_detail.h | 0 2 files changed, 2 insertions(+), 5 deletions(-) rename {src => include}/civil_time_detail.h (100%) diff --git a/BUILD b/BUILD index 2b8d28ad..2d746912 100644 --- a/BUILD +++ b/BUILD @@ -35,15 +35,12 @@ cc_library( "include/civil_time.h", "include/time_zone.h", ], - includes = [ - "include", - "src", - ], + includes = ["include"], linkopts = [ "-lm", "-lpthread", ], - textual_hdrs = ["src/civil_time_detail.h"], + textual_hdrs = ["include/civil_time_detail.h"], visibility = ["//visibility:public"], ) diff --git a/src/civil_time_detail.h b/include/civil_time_detail.h similarity index 100% rename from src/civil_time_detail.h rename to include/civil_time_detail.h From 3559c98ee1d17cd2287c197d9774525e8a498de4 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 8 Mar 2016 11:45:05 -0500 Subject: [PATCH 07/69] Do not include civil_time_detail.h from within a namespace. --- include/civil_time.h | 6 +----- include/civil_time_detail.h | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/civil_time.h b/include/civil_time.h index 13a352df..aaa5199d 100644 --- a/include/civil_time.h +++ b/include/civil_time.h @@ -19,14 +19,10 @@ #ifndef CCTZ_CIVIL_TIME_H_ #define CCTZ_CIVIL_TIME_H_ -#include +#include "civil_time_detail.h" 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. diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index de01511f..dd819474 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +namespace cctz { +namespace detail { + // Normalized civil-time fields: Y-M-D HH:MM:SS. struct fields { int y; @@ -460,3 +465,6 @@ constexpr civil_day prev_weekday(const civil_day& cd, weekday wd) { constexpr int get_yearday(const civil_day& cd) { return cd - civil_day(cd.year(), 1, 0); } + +} // namespace detail +} // namespace cctz From ce3774901f1f10c15ae9760da393308da6e78ccd Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Wed, 9 Mar 2016 17:02:19 -0500 Subject: [PATCH 08/69] Reworded some comments --- include/civil_time.h | 237 ++++++++++++++++++++++++++--------- include/time_zone.h | 88 +++++++------ src/time_zone_format.cc | 2 + src/time_zone_format_test.cc | 2 +- src/time_zone_lookup_test.cc | 2 +- 5 files changed, 228 insertions(+), 103 deletions(-) diff --git a/include/civil_time.h b/include/civil_time.h index aaa5199d..8e3a3e79 100644 --- a/include/civil_time.h +++ b/include/civil_time.h @@ -12,10 +12,6 @@ // 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_ @@ -23,82 +19,203 @@ namespace cctz { -// 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. +// The term "civil time" refers to the legally recognized human-scale time +// that is represented by the six fields YYYY-MM-DD hh:mm:ss. Modern-day civil +// time follows the Gregorian Calendar and is a time-zone-independent concept. +// A "date" is perhaps the most common example of a civil time (represented in +// this library as cctz::civil_day). This library provides six classes and a +// handful of functions that help with rounding, iterating, and arithmetic on +// civil times while avoiding complications like daylight-saving time (DST). +// +// The core of the Civil-Time Library is based on the following six classes: +// +// * civil_second +// * civil_minute +// * civil_hour +// * civil_day +// * civil_month +// * civil_year +// +// Each class is a simple value type with the same interface for construction +// and the same six accessors for each of the fields (year, month, day, hour, +// minute, and second, aka YMDHMS). These classes differ only in their +// alignment, which is indicated by the type name and specifies the field on +// which arithmetic operates. +// +// Each class can be constructed by passing up to six optional integer +// arguments representing the YMDHMS fields (in that order) to the +// constructor. Omitted fields are assigned their minimum valid value. Hours, +// minutes, and seconds will be set to 0, month and day will be set to 1, and +// since there is no minimum valid year it will be set to 1970. So, a +// default-constructed civil-time object will have YMDHMS fields representing +// "1970-01-01 00:00:00". +// +// Each civil-time class is aligned to the civil-time field indicated in the +// class's name. Alignment is performed by setting all the inferior fields to +// their minimum valid value (as described above). The following are examples +// of how each of the six types would align the fields representing November +// 22, 2015 at 12:34:56 in the afternoon. (Note: the string format used here +// is not important; it's just a shorthand way of showing the six YMDHMS +// fields.) +// +// civil_second 2015-11-22 12:34:56 +// civil_minute 2015-11-22 12:34:00 +// civil_hour 2015-11-22 12:00:00 +// civil_day 2015-11-22 00:00:00 +// civil_month 2015-11-01 00:00:00 +// civil_year 2015-01-01 00:00:00 +// +// Each civil-time type performs arithmetic on the field to which it is +// aligned. This means that adding 1 to a civil_day increments the day field +// (normalizing as necessary), and subtracting 7 from a civil_month operates +// on the month field (normalizing as necessary). All arithmetic produces a +// new value that represents a valid civil time. Difference requires two +// similarly aligned civil time types and returns the scalar answer in units +// of the given alignment. For example, the difference between two civil_hour +// objects will give an answer in hours. +// +// In addition to the six civil-time types just described, there are +// a handful of helper functions and algorithms for performing common +// calculations. These are described below. // // 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). +// Each of the civil-time types can be constructed in two ways: by directly +// passing to the constructor up to six (optional) integers representing the +// YMDHMS fields, or by copying the YMDHMS fields from a differently aligned +// civil-time type. // -// 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 +// civil_day default_value; // 1970-01-01 00:00:00 // -// VALUE SEMANTICS: +// civil_day a(2015, 2, 3); // 2015-02-03 00:00:00 +// civil_day b(2015, 2, 3, 4, 5, 6); // 2015-02-03 00:00:00 +// civil_day c(2015); // 2015-01-01 00:00:00 // -// All civil-time types are small, value types that should be copied, -// assigned to, and passed to functions by value. +// civil_second ss(2015, 2, 3, 4, 5, 6); // 2015-02-03 04:05:06 +// civil_minute mm(ss); // 2015-02-03 04:05:00 +// civil_hour hh(mm); // 2015-02-03 04:00:00 +// civil_day d(hh); // 2015-02-03 00:00:00 +// civil_month m(d); // 2015-02-01 00:00:00 +// civil_year y(m); // 2015-01-01 00:00:00 // -// 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 +// m = civil_month(y); // 2015-01-01 00:00:00 +// d = civil_day(m); // 2015-01-01 00:00:00 +// hh = civil_hour(d); // 2015-01-01 00:00:00 +// mm = civil_minute(hh); // 2015-01-01 00:00:00 +// ss = civil_second(mm); // 2015-01-01 00:00:00 // -// PROPERTIES: +// NORMALIZATION: // -// All civil-time types have accessors for all six of the civil-time -// fields: year, month, day, hour, minute, and second. +// Integer arguments passed to the constructor may be out-of-range, in which +// case they are normalized to produce a valid civil-time object. This enables +// natural arithmetic on constructor arguments without worrying about the +// field's range. Normalization guarantees that there are no invalid +// civil-time objects. // -// 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); +// civil_day d(2016, 10, 32); // Out-of-range day; normalized to 2016-11-01 // -// ARITHMETIC: +// Note: If normalization is undesired, you can signal an error by comparing +// the YMDHMS getters to the constructor arguments. // -// 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. +// PROPERTIES: // -// 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) +// All civil-time types have accessors for all six of the civil-time fields: +// year, month, day, hour, minute, and second. Recall that fields inferior to +// the type's aligment will be set to their minimum valid value. +// +// civil_day d(2015, 6, 28); +// // d.year() == 2015 +// // d.month() == 2 +// // d.day() == 3 +// // d.hour() == 0 +// // d.minute() == 0 +// // d.second() == 0 // // 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. +// Comparison always considers all six YMDHMS fields, regardless of the type's +// alignment. Comparison between differently aligned civil-time types is +// allowed. +// +// civil_day feb_3(2015, 2, 3); // 2015-02-03 00:00:00 +// civil_day mar_4(2015, 3, 4); // 2015-03-04 00:00:00 +// // feb_3 < mar_4 +// // civil_year(feb_3) == civil_year(mar_4) +// +// civil_second feb_3_noon(2015, 2, 3, 12, 0, 0); // 2015-02-03 12:00:00 +// // feb_3 < feb_3_noon +// // feb_3 == civil_day(feb_3_noon) // -// // 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) { +// // Iterates all the days of February 2015. +// for (civil_day d(2015, 2, 1); d < civil_month(2015, 3); ++d) { // // ... // } // +// ARITHMETIC: +// +// Civil-time types support natural arithmetic operators such as addition, +// subtraction, and difference. Arithmetic operates on the civil-time field +// indicated in the type's name. Difference requires arguments with the same +// alignment and returns the answer in units of the alignment. +// +// civil_day a(2015, 2, 3); +// ++a; // 2015-02-04 00:00:00 +// --a; // 2015-02-03 00:00:00 +// civil_day b = a + 1; // 2015-02-04 00:00:00 +// civil_day c = 1 + b; // 2015-02-05 00:00:00 +// int n = c - a; // n = 2 (days) +// int m = c - civil_month(c); // Won't compile: different types. +// +// EXAMPLE: Adding a month to January 31. +// +// One of the classic questions that arises when talking about a civil-time +// library (or a date library or a date/time library) is this: "What happens +// when you add a month to January 31?" This is an interesting question +// because there could be a number of possible answers: +// +// 1. March 3 (or 2 if a leap year). This may make sense if the operation +// wants the equivalent of February 31. +// 2. February 28 (or 29 if a leap year). This may make sense if the operation +// wants the last day of January to go to the last day of February. +// 3. Error. The caller may get some error, an exception, an invalid date +// object, or maybe false is returned. This may make sense because there is +// no single unambiguously correct answer to the question. +// +// Practically speaking, any answer that is not what the programmer intended +// is the wrong answer. +// +// This civil-time library avoids this problem by making it impossible to ask +// such an ambiguous question. All civil-time objects are aligned to a +// particular civil-field boundary (such as aligned to a year, month, day, +// hour, minute, or second), and arithmetic operates on the field to which the +// object is aligned. This means that in order to "add a month" the object +// must first be aligned to a month boundary, which is equivalent to the first +// day of that month. +// +// Of course, there are ways to answer the question at hand using this +// civil-time library, but they require the programmer to be more explicit +// so that they get their intended answer. Let's see how to get all three of +// the above answers: +// +// const civil_day d(2015, 1, 31); +// +// // Answer 1: +// // Add 1 to the month field in the constructor, and rely on normalization. +// const auto ans_normalized = civil_day(d.year(), d.month() + 1, d.day()); +// // ans_normalized == 2015-03-03 (aka Feb 31) +// +// // Answer 2: +// // Add 1 to month field, capping to the end of next month. +// const auto last_day_of_next_month = civil_day(civil_month(d) + 2) - 1; +// const auto ans_capped = std::min(ans_normalized, last_day_of_next_month); +// // ans_capped == 2015-02-28 +// +// // Answer 3: +// // Signal an error. +// if (civil_month(ans_normalized) - civil_month(d) != 1) { +// // error, month overflow +// } +// using civil_year = detail::civil_year; using civil_month = detail::civil_month; using civil_day = detail::civil_day; diff --git a/include/time_zone.h b/include/time_zone.h index a033a160..1486bbaa 100644 --- a/include/time_zone.h +++ b/include/time_zone.h @@ -56,10 +56,10 @@ class time_zone { 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. + // An absolute_lookup represents the civil time (cctz::civil_second) within + // this time_zone at the given absolute time (time_point). There are + // additionally a few other fields that may be useful when working with + // older APIs, such as std::tm. // // Example: // const cctz::time_zone tz = ... @@ -67,11 +67,11 @@ class time_zone { // const cctz::time_zone::absolute_lookup al = tz.lookup(tp); 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 + // 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; // civil seconds east of UTC bool is_dst; // is offset non-standard? std::string abbr; // time-zone abbreviation (e.g., "PST") }; @@ -81,17 +81,20 @@ class time_zone { 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). + // A civil_lookup represents the absolute time(s) (time_point) that + // correspond to the given civil time (cctz::civil_second) within this + // time_zone. Usually the given civil time represents a unique instant in + // time, in which case the conversion is unambiguous and correct. However, + // within this time zone, the given civil time may be skipped (e.g., during + // a positive UTC offset shift), or repeated (e.g., during a negative UTC + // offset shift). To account for these possibilities, civil_lookup is richer + // than just a single output time_point. // - // 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. + // In all cases the civil_lookup::kind enum will indicate the nature of the + // given civil-time argument, and the pre, trans, and post, members will + // give the absolute time answers using the pre-transition offset, the + // transition point itself, and the post-transition offset, respectively + // (these are all equal if kind == UNIQUE). // // Example: // cctz::time_zone lax; @@ -147,17 +150,20 @@ time_zone utc_time_zone(); // Returns a time zone representing the local time zone. Falls back to UTC. time_zone local_time_zone(); -// The full information provided by the time_zone::absolute_lookup and -// time_zone::civil_lookup structs is frequently not needed by callers. -// Overloaded non-member convert() functions are provided to simplify the -// common cases. +// Returns the civil time (cctz::civil_second) within the given time zone at +// the given absolute time (time_point). Since the additional fields provided +// by the time_zone::absolute_lookup struct should rarely be needed in modern +// code, this convert() function is simpler and should be preferred. template inline civil_second convert(const time_point& tp, const time_zone& tz) { return tz.lookup(tp).cs; } -// The return value here is chosen such that the relative order of civil -// times is preserved across conversions. +// Returns the absolute time (time_point) that corresponds to the given civil +// time within the given time zone. If the civil time is not unique (i.e., if +// it was either repeated or non-existent), then the returned time_point is +// the best estimate that preserves relative order. That is, this function +// guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz). inline time_point convert(const civil_second& cs, const time_zone& tz) { const time_zone::civil_lookup cl = tz.lookup(cs); @@ -165,11 +171,11 @@ inline time_point convert(const civil_second& cs, return cl.pre; } -namespace internal { +namespace detail { // Floors tp to a second boundary and sets *subseconds. template inline std::pair, D> -FloorSeconds(const time_point& tp) { +SplitSeconds(const time_point& tp) { auto sec = std::chrono::time_point_cast(tp); auto sub = tp - sec; if (sub.count() < 0) { @@ -180,10 +186,14 @@ FloorSeconds(const time_point& tp) { } // Overload for when tp is already second aligned. inline std::pair, sys_seconds> -FloorSeconds(const time_point& tp) { +SplitSeconds(const time_point& tp) { return {tp, sys_seconds(0)}; } -} // namespace internal +std::string format(const std::string&, const time_point&, + const std::chrono::nanoseconds&, const time_zone&); +bool parse(const std::string&, const std::string&, const time_zone&, + time_point*, std::chrono::nanoseconds*); +} // namespace detail // Formats the given time_point in the given cctz::time_zone according to // the provided format string. Uses strftime()-like formatting options, @@ -198,8 +208,8 @@ FloorSeconds(const time_point& tp) { // 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. +// Tip: Format strings should include the UTC offset (e.g., %z or %Ez) so that +// the resultng string uniquely identifies an absolute time. // // Example: // cctz::time_zone lax; @@ -207,14 +217,12 @@ FloorSeconds(const time_point& tp) { // auto tp = cctz::convert(cctz::civil_second(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" -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 p = detail::SplitSeconds(tp); const auto n = std::chrono::duration_cast(p.second); - return format(fmt, p.first, n, tz); + return detail::format(fmt, p.first, n, tz); } // Parses an input string according to the provided format string and @@ -232,10 +240,10 @@ inline std::string format(const std::string& fmt, const time_point& tp, // 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 +// 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, +// 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. // @@ -243,7 +251,7 @@ inline std::string format(const std::string& fmt, const time_point& tp, // 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 +// A 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: // @@ -259,14 +267,12 @@ inline std::string format(const std::string& fmt, const 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); + const bool b = detail::parse(fmt, input, tz, &tp, &ns); if (b) { *tpp = std::chrono::time_point_cast(tp); *tpp += std::chrono::duration_cast(ns); diff --git a/src/time_zone_format.cc b/src/time_zone_format.cc index ba5be745..5ef9126c 100644 --- a/src/time_zone_format.cc +++ b/src/time_zone_format.cc @@ -34,6 +34,7 @@ #endif namespace cctz { +namespace detail { namespace { @@ -745,4 +746,5 @@ bool parse(const std::string& format, const std::string& input, return true; } +} // namespace detail } // namespace cctz diff --git a/src/time_zone_format_test.cc b/src/time_zone_format_test.cc index 989fc237..7f7338cb 100644 --- a/src/time_zone_format_test.cc +++ b/src/time_zone_format_test.cc @@ -48,7 +48,7 @@ namespace { EXPECT_EQ(mm, al.cs.minute()); \ EXPECT_EQ(ss, al.cs.second()); \ EXPECT_EQ(off, al.offset); \ - EXPECT_EQ(isdst, al.is_dst); \ + EXPECT_TRUE(isdst == al.is_dst); \ EXPECT_EQ(zone, al.abbr); \ } while (0) diff --git a/src/time_zone_lookup_test.cc b/src/time_zone_lookup_test.cc index b27a4b0c..0d0405eb 100644 --- a/src/time_zone_lookup_test.cc +++ b/src/time_zone_lookup_test.cc @@ -635,7 +635,7 @@ time_zone LoadZone(const std::string& name) { EXPECT_EQ(mm, al.cs.minute()); \ EXPECT_EQ(ss, al.cs.second()); \ EXPECT_EQ(off, al.offset); \ - EXPECT_EQ(isdst, al.is_dst); \ + EXPECT_TRUE(isdst == al.is_dst); \ EXPECT_EQ(zone, al.abbr); \ } while (0) From 5a552bda0458a9df72f0000ebeb156b3d07ec0c7 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 09:22:20 -0500 Subject: [PATCH 09/69] more comment tweaking --- include/civil_time.h | 77 +++++++++++++++++++++++--------------------- include/time_zone.h | 10 +++--- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/include/civil_time.h b/include/civil_time.h index 8e3a3e79..bdc1a557 100644 --- a/include/civil_time.h +++ b/include/civil_time.h @@ -27,7 +27,7 @@ namespace cctz { // handful of functions that help with rounding, iterating, and arithmetic on // civil times while avoiding complications like daylight-saving time (DST). // -// The core of the Civil-Time Library is based on the following six classes: +// The following six classes form the core of this civil-time library: // // * civil_second // * civil_minute @@ -37,8 +37,8 @@ namespace cctz { // * civil_year // // Each class is a simple value type with the same interface for construction -// and the same six accessors for each of the fields (year, month, day, hour, -// minute, and second, aka YMDHMS). These classes differ only in their +// and the same six accessors for each of the civil fields (year, month, day, +// hour, minute, and second, aka YMDHMS). These classes differ only in their // alignment, which is indicated by the type name and specifies the field on // which arithmetic operates. // @@ -46,17 +46,19 @@ namespace cctz { // arguments representing the YMDHMS fields (in that order) to the // constructor. Omitted fields are assigned their minimum valid value. Hours, // minutes, and seconds will be set to 0, month and day will be set to 1, and -// since there is no minimum valid year it will be set to 1970. So, a +// since there is no minimum valid year, it will be set to 1970. So, a // default-constructed civil-time object will have YMDHMS fields representing -// "1970-01-01 00:00:00". +// "1970-01-01 00:00:00". Fields that are out-of-range are normalized (e.g., +// October 32 -> November 1) so that all civil-time objects represent valid +// values. // // Each civil-time class is aligned to the civil-time field indicated in the -// class's name. Alignment is performed by setting all the inferior fields to -// their minimum valid value (as described above). The following are examples -// of how each of the six types would align the fields representing November -// 22, 2015 at 12:34:56 in the afternoon. (Note: the string format used here -// is not important; it's just a shorthand way of showing the six YMDHMS -// fields.) +// class's name after normalization. Alignment is performed by setting all the +// inferior fields to their minimum valid value (as described above). The +// following are examples of how each of the six types would align the fields +// representing November 22, 2015 at 12:34:56 in the afternoon. (Note: the +// string format used here is not important; it's just a shorthand way of +// showing the six YMDHMS fields.) // // civil_second 2015-11-22 12:34:56 // civil_minute 2015-11-22 12:34:00 @@ -69,10 +71,10 @@ namespace cctz { // aligned. This means that adding 1 to a civil_day increments the day field // (normalizing as necessary), and subtracting 7 from a civil_month operates // on the month field (normalizing as necessary). All arithmetic produces a -// new value that represents a valid civil time. Difference requires two -// similarly aligned civil time types and returns the scalar answer in units -// of the given alignment. For example, the difference between two civil_hour -// objects will give an answer in hours. +// valid civil time. Difference requires two similarly aligned civil-time +// objects and returns the scalar answer in units of the objects' alignment. +// For example, the difference between two civil_hour objects will give an +// answer in units of civil hours. // // In addition to the six civil-time types just described, there are // a handful of helper functions and algorithms for performing common @@ -115,7 +117,8 @@ namespace cctz { // civil_day d(2016, 10, 32); // Out-of-range day; normalized to 2016-11-01 // // Note: If normalization is undesired, you can signal an error by comparing -// the YMDHMS getters to the constructor arguments. +// the constructor arguments to the normalized values returned by the YMDHMS +// properties. // // PROPERTIES: // @@ -125,8 +128,8 @@ namespace cctz { // // civil_day d(2015, 6, 28); // // d.year() == 2015 -// // d.month() == 2 -// // d.day() == 3 +// // d.month() == 6 +// // d.day() == 28 // // d.hour() == 0 // // d.minute() == 0 // // d.second() == 0 @@ -163,12 +166,12 @@ namespace cctz { // --a; // 2015-02-03 00:00:00 // civil_day b = a + 1; // 2015-02-04 00:00:00 // civil_day c = 1 + b; // 2015-02-05 00:00:00 -// int n = c - a; // n = 2 (days) +// int n = c - a; // n = 2 (civil days) // int m = c - civil_month(c); // Won't compile: different types. // // EXAMPLE: Adding a month to January 31. // -// One of the classic questions that arises when talking about a civil-time +// One of the classic questions that arises when considering a civil-time // library (or a date library or a date/time library) is this: "What happens // when you add a month to January 31?" This is an interesting question // because there could be a number of possible answers: @@ -184,18 +187,19 @@ namespace cctz { // Practically speaking, any answer that is not what the programmer intended // is the wrong answer. // -// This civil-time library avoids this problem by making it impossible to ask -// such an ambiguous question. All civil-time objects are aligned to a -// particular civil-field boundary (such as aligned to a year, month, day, -// hour, minute, or second), and arithmetic operates on the field to which the -// object is aligned. This means that in order to "add a month" the object -// must first be aligned to a month boundary, which is equivalent to the first -// day of that month. +// This civil-time library avoids the problem by making it impossible to ask +// ambiguous questions. All civil-time objects are aligned to a particular +// civil-field boundary (such as aligned to a year, month, day, hour, minute, +// or second), and arithmetic operates on the field to which the object is +// aligned. This means that in order to "add a month" the object must first be +// aligned to a month boundary, which is equivalent to the first day of that +// month. // -// Of course, there are ways to answer the question at hand using this -// civil-time library, but they require the programmer to be more explicit -// so that they get their intended answer. Let's see how to get all three of -// the above answers: +// Of course, there are ways to compute an answer the question at hand using +// this civil-time library, but they require the programmer to be explicit +// about the answer they expect. To illustrate, let's see how to compute all +// three of the above possible answers to the question of "Jan 31 plus 1 +// month": // // const civil_day d(2015, 1, 31); // @@ -206,13 +210,14 @@ namespace cctz { // // // Answer 2: // // Add 1 to month field, capping to the end of next month. -// const auto last_day_of_next_month = civil_day(civil_month(d) + 2) - 1; +// const auto next_month = civil_month(d) + 1; +// const auto last_day_of_next_month = civil_day(next_month + 1) - 1; // const auto ans_capped = std::min(ans_normalized, last_day_of_next_month); // // ans_capped == 2015-02-28 // // // Answer 3: -// // Signal an error. -// if (civil_month(ans_normalized) - civil_month(d) != 1) { +// // Signal an error if the normalized answer is not in next month. +// if (civil_month(ans_normalized) != next_month) { // // error, month overflow // } // @@ -254,9 +259,9 @@ using detail::get_weekday; // // civil_day d = ... // // Gets the following Thursday if d is not already Thursday -// civil_day thurs1 = PrevWeekday(d, weekday::thursday) + 7; +// civil_day thurs1 = prev_weekday(d, weekday::thursday) + 7; // // Gets the previous Thursday if d is not already Thursday -// civil_day thurs2 = NextWeekday(d, weekday::thursday) - 7; +// civil_day thurs2 = next_weekday(d, weekday::thursday) - 7; // using detail::next_weekday; using detail::prev_weekday; diff --git a/include/time_zone.h b/include/time_zone.h index 1486bbaa..3bd3a012 100644 --- a/include/time_zone.h +++ b/include/time_zone.h @@ -172,10 +172,9 @@ inline time_point convert(const civil_second& cs, } namespace detail { -// Floors tp to a second boundary and sets *subseconds. template inline std::pair, D> -SplitSeconds(const time_point& tp) { +split_seconds(const time_point& tp) { auto sec = std::chrono::time_point_cast(tp); auto sub = tp - sec; if (sub.count() < 0) { @@ -184,9 +183,8 @@ SplitSeconds(const time_point& tp) { } return {sec, std::chrono::duration_cast(sub)}; } -// Overload for when tp is already second aligned. inline std::pair, sys_seconds> -SplitSeconds(const time_point& tp) { +split_seconds(const time_point& tp) { return {tp, sys_seconds(0)}; } std::string format(const std::string&, const time_point&, @@ -220,7 +218,7 @@ bool parse(const std::string&, const std::string&, const time_zone&, template inline std::string format(const std::string& fmt, const time_point& tp, const time_zone& tz) { - const auto p = detail::SplitSeconds(tp); + const auto p = detail::split_seconds(tp); const auto n = std::chrono::duration_cast(p.second); return detail::format(fmt, p.first, n, tz); } @@ -241,7 +239,7 @@ inline std::string format(const std::string& fmt, const time_point& tp, // 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). +// fully-specified date/time strings that include a UTC offset (%z or %Ez). // // Note also that parse() only heeds the fields year, month, day, hour, // minute, (fractional) second, and UTC offset. Other fields, like weekday (%a From 26b652ea3c529697bfa33015cd581c0464b1dd40 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 10:05:16 -0500 Subject: [PATCH 10/69] Added separate library targets for civiltime and timezone --- BUILD | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/BUILD b/BUILD index 2d746912..b6fb8913 100644 --- a/BUILD +++ b/BUILD @@ -15,7 +15,17 @@ ### libraries cc_library( - name = "cctz", + name = "cctz_civil_time", + hdrs = [ + "include/civil_time.h", + ], + includes = ["include"], + textual_hdrs = ["include/civil_time_detail.h"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "cctz_time_zone", srcs = [ "src/time_zone_format.cc", "src/time_zone_if.cc", @@ -32,7 +42,6 @@ cc_library( "src/tzfile.h", ], hdrs = [ - "include/civil_time.h", "include/time_zone.h", ], includes = ["include"], @@ -40,8 +49,8 @@ cc_library( "-lm", "-lpthread", ], - textual_hdrs = ["include/civil_time_detail.h"], visibility = ["//visibility:public"], + deps = [":cctz_civil_time"], ) ### tests @@ -76,7 +85,7 @@ cc_test( srcs = ["src/civil_time_test.cc"], deps = [ "@gtest//:gtest", - ":cctz", + ":cctz_civil_time", ], ) @@ -86,7 +95,8 @@ cc_test( srcs = ["src/time_zone_format_test.cc"], deps = [ "@gtest//:gtest", - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -96,7 +106,8 @@ cc_test( srcs = ["src/time_zone_lookup_test.cc"], deps = [ "@gtest//:gtest", - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -111,7 +122,8 @@ cc_binary( name = "epoch_shift", srcs = ["examples/epoch_shift.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -119,7 +131,8 @@ cc_binary( name = "example1", srcs = ["examples/example1.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -127,7 +140,8 @@ cc_binary( name = "example2", srcs = ["examples/example2.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -135,7 +149,8 @@ cc_binary( name = "example3", srcs = ["examples/example3.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -143,7 +158,8 @@ cc_binary( name = "example4", srcs = ["examples/example4.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -151,7 +167,8 @@ cc_binary( name = "hello", srcs = ["examples/hello.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) @@ -161,6 +178,7 @@ cc_binary( name = "time_tool", srcs = ["src/time_tool.cc"], deps = [ - ":cctz", + ":cctz_civil_time", + ":cctz_time_zone", ], ) From 6fc904cfb75f3e9d8717acda42954cc1ab38dc85 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Thu, 10 Mar 2016 11:33:38 -0500 Subject: [PATCH 11/69] Move the public headers to their own directory. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ef2498c4..25b6aecf 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,8 @@ CC = $(CXX) OPT = -g # TEST_FLAGS = # TEST_LIBS = -CPPFLAGS = -Isrc $(TEST_FLAGS) -Wall -std=c++11 -pthread $(OPT) -fPIC -VPATH = examples:src +CPPFLAGS = -Iinclude $(TEST_FLAGS) -Wall -std=c++11 -pthread $(OPT) -fPIC +VPATH = include:src:examples LDFLAGS = -pthread LDLIBS = $(TEST_LIBS) -lm ARFLAGS = rcs From d54c9ec342ebfa7ffc3074b100b1a7559052826d Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 11:34:18 -0500 Subject: [PATCH 12/69] fixed operator< --- include/civil_time_detail.h | 3 +-- src/civil_time_test.cc | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index dd819474..e904e6c2 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -389,8 +389,7 @@ constexpr bool operator<(const civil_time& lhs, (lhs.hour() == rhs.hour() && (lhs.minute() < rhs.minute() || (lhs.minute() == rhs.minute() && - (lhs.second() < rhs.second() || - (lhs.second() == rhs.second())))))))))))); + (lhs.second() < rhs.second()))))))))))); } template constexpr bool operator<=(const civil_time& lhs, diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index 813ac2ce..b7a74240 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -440,6 +440,14 @@ TEST(CivilTime, Relational) { #define TEST_RELATIONAL(OLDER, YOUNGER) \ do { \ + EXPECT_FALSE(OLDER < OLDER); \ + EXPECT_FALSE(OLDER > OLDER); \ + EXPECT_TRUE(OLDER >= OLDER); \ + EXPECT_TRUE(OLDER <= OLDER); \ + EXPECT_FALSE(YOUNGER < YOUNGER); \ + EXPECT_FALSE(YOUNGER > YOUNGER); \ + EXPECT_TRUE(YOUNGER >= YOUNGER); \ + EXPECT_TRUE(YOUNGER <= YOUNGER); \ EXPECT_EQ(OLDER, OLDER); \ EXPECT_NE(OLDER, YOUNGER); \ EXPECT_LT(OLDER, YOUNGER); \ From 089826f6696f9debdde6e1501b22d10213d82c99 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 12:39:18 -0500 Subject: [PATCH 13/69] adds operator<< support for civil time --- include/civil_time_detail.h | 30 +++++++++++++ src/civil_time_test.cc | 86 +++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index e904e6c2..b2c57018 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include namespace cctz { namespace detail { @@ -372,6 +374,34 @@ using civil_hour = civil_time; using civil_minute = civil_time; using civil_second = civil_time; +//////////////////////////////////////////////////////////////////////// +// Output stream operators output a format matching YYYY-MM-DDThh:mm:ss, +// while omitting fields inferior to the type's alignment. For example, +// civil_day is formatted only as YYYY-MM-DD. +inline std::ostream& operator<<(std::ostream& os, civil_year y) { + return os << y.year(); // No padding. +} +inline std::ostream& operator<<(std::ostream& os, civil_month m) { + return os << civil_year(m) << '-' << std::setfill('0') << std::setw(2) + << m.month(); +} +inline std::ostream& operator<<(std::ostream& os, civil_day d) { + return os << civil_month(d) << '-' << std::setfill('0') << std::setw(2) + << d.day(); +} +inline std::ostream& operator<<(std::ostream& os, civil_hour h) { + return os << civil_day(h) << 'T' << std::setfill('0') << std::setw(2) + << h.hour(); +} +inline std::ostream& operator<<(std::ostream& os, civil_minute m) { + return os << civil_hour(m) << ':' << std::setfill('0') << std::setw(2) + << m.minute(); +} +inline std::ostream& operator<<(std::ostream& os, civil_second s) { + return os << civil_minute(s) << ':' << std::setfill('0') << std::setw(2) + << s.second(); +} + //////////////////////////////////////////////////////////////////////// // Relational operators that work with differently aligned objects. diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index b7a74240..ed8a9b63 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include "gtest/gtest.h" @@ -24,44 +25,11 @@ 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); +template +std::string Format(const T& t) { + std::stringstream ss; + ss << t; + return ss.str(); } } // namespace @@ -278,6 +246,48 @@ TEST(CivilTime, YearDay) { EXPECT_EQ(28, yd); } +TEST(CivilTime, OutputStream) { + std::stringstream ss; + + // Tests formatting civil_year, which does not pad. + ss << civil_year(2016); + EXPECT_EQ("2016", ss.str()); + ss.str(""); + + ss << civil_year(123); + EXPECT_EQ("123", ss.str()); + ss.str(""); + + ss << civil_year(0); + EXPECT_EQ("0", ss.str()); + ss.str(""); + + ss << civil_year(-1); + EXPECT_EQ("-1", ss.str()); + ss.str(""); + + // Tests formatting of sub-year types, which pad to 2 digits + ss << civil_month(2016, 2); + EXPECT_EQ("2016-02", ss.str()); + ss.str(""); + + ss << civil_day(2016, 2, 3); + EXPECT_EQ("2016-02-03", ss.str()); + ss.str(""); + + ss << civil_hour(2016, 2, 3, 4); + EXPECT_EQ("2016-02-03T04", ss.str()); + ss.str(""); + + ss << civil_minute(2016, 2, 3, 4, 5); + EXPECT_EQ("2016-02-03T04:05", ss.str()); + ss.str(""); + + ss << civil_second(2016, 2, 3, 4, 5, 6); + EXPECT_EQ("2016-02-03T04:05:06", ss.str()); + ss.str(""); +} + // START OF google3 TESTS TEST(CivilTime, DefaultConstruction) { From 8faa02f8c96813283e144318aadf60f036de9077 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 13:17:45 -0500 Subject: [PATCH 14/69] Added blank line per Brad's suggestion. --- include/civil_time_detail.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index b2c57018..18b8dd1a 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -375,6 +375,7 @@ using civil_minute = civil_time; using civil_second = civil_time; //////////////////////////////////////////////////////////////////////// + // Output stream operators output a format matching YYYY-MM-DDThh:mm:ss, // while omitting fields inferior to the type's alignment. For example, // civil_day is formatted only as YYYY-MM-DD. From 60f1b476d0bfe88f6227c847082c3163ab387658 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 14:06:07 -0500 Subject: [PATCH 15/69] added streaming support to cctz::weekday --- include/civil_time_detail.h | 19 +++++++++++++++++++ src/civil_time_test.cc | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 18b8dd1a..a8af2271 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -474,6 +474,25 @@ constexpr weekday get_weekday(const civil_day& cd) { return impl::k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; } +inline std::ostream& operator<<(std::ostream& os, weekday wd) { + switch (wd) { + case weekday::monday: + return os << "Monday"; + case weekday::tuesday: + return os << "Tuesday"; + case weekday::wednesday: + return os << "Wednesday"; + case weekday::thursday: + return os << "Thursday"; + case weekday::friday: + return os << "Friday"; + case weekday::saturday: + return os << "Saturday"; + case weekday::sunday: + return os << "Sunday"; + } +} + //////////////////////////////////////////////////////////////////////// namespace impl { diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index ed8a9b63..f688373d 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -240,6 +240,38 @@ TEST(CivilTime, PrevWeekDay) { EXPECT_EQ(21, prev.day()); } +TEST(CivilTime, WeekdayStream) { + std::stringstream ss; + + ss << weekday::monday; + EXPECT_EQ("Monday", ss.str()); + ss.str(""); + + ss << weekday::tuesday; + EXPECT_EQ("Tuesday", ss.str()); + ss.str(""); + + ss << weekday::wednesday; + EXPECT_EQ("Wednesday", ss.str()); + ss.str(""); + + ss << weekday::thursday; + EXPECT_EQ("Thursday", ss.str()); + ss.str(""); + + ss << weekday::friday; + EXPECT_EQ("Friday", ss.str()); + ss.str(""); + + ss << weekday::saturday; + EXPECT_EQ("Saturday", ss.str()); + ss.str(""); + + ss << weekday::sunday; + EXPECT_EQ("Sunday", ss.str()); + ss.str(""); +} + TEST(CivilTime, YearDay) { constexpr civil_day cd(2016, 1, 28); constexpr int yd = get_yearday(cd); From 748798907a212e97b7a41248e38cf3ef6a9fc8e9 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 15:05:26 -0500 Subject: [PATCH 16/69] updated readme --- README.md | 225 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 159 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index ddcadf4a..dd338024 100644 --- a/README.md +++ b/README.md @@ -2,88 +2,181 @@ 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. +CCTZ contains two libraries that cooperate with `` to give C++ +programmers all the necessary tools for computing with dates, times, and time +zones in a simple and correct manner. The libraries in CCTZ are: -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`. +* The Civil-Time Library — This is a header-only library that supports computing + with human-scale time, such as dates (which are represented by the + `cctz::civil_day` class). This library is declared in `include/civil_time.h`. +* The Time-Zone Library — This library uses the IANA time zone database that is + installed on the system to convert between *absolute time* and *civil time*. + This library is declared in `include/time_zone.h`. -CCTZ is built using http://bazel.io and tested using -https://github.com/google/googletest +These libraries are currently known to work on **Linux** and **Mac OS X**. We +are actively interested in help getting them working on Windows. Please contact +us if you're interested in contributing. # Getting Started +CCTZ is built using the [Bazel](http://bazel.io) build system and tested using +the [Google Test](https://github.com/google/googletest) framework. + 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 + +Note that when using CCTZ in your own code, you might find it easiest to compile +the sources directly yourself. + +Next Steps: + +1. See the CCTZ API: + * Civil Time: `include/civil_time.h` + * Time Zone: `include/time_zone.h` +2. 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)) ] - -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 -in time. Every event occurs at a specific absolute time, and everyone in the -world will agree on the absolute time when the event occurred. A `time_t` is a -well-known absolute time type. Consider the moment when Neil Armstrong first put -his left foot on the moon. He did that for the first time only once. He did not -do it again an hour later for the audience one time zone to the west. Everyone -in the world will agree on the *absolute time* when this event happened. - -On the other hand, not everyone will agree on the *civil time* when that giant -leap was made. A civil time is represented as _six individual fields_ that -represent a year, month, day, hour, minute, and second. These six fields -represent the time as defined by some local government. Your civil time matches -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. - -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 -evident in their identifiers, which look like "America/New_York", -"America/Los_Angeles", and "Australia/Sydney". A time-zone's rules include -things like the region's offset from the UTC time standard, daylight-saving -adjustments, and short abbreviation strings. Since these rules may change at the -whim of the region's local government, time zones have a history of disparate -rules that apply only for certain periods. Time zones are tremendously -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. +*[The concepts presented here describe general truths about the problem domain +and are library and language agnostic. An understanding of these concepts helps +the programmer correctly reason about even the most complicated time-programming +challenges and produce the simplest possible solutions.]* + +There are two main ways to think about time in a computer program: As *absolute +time*, and as *civil time*. Both have their uses and it is important to +understand when each is appropriate. Absolute and civil times may be converted +back and forth using a time zone — this is the only way to correctly convert +between them. Let us now look more deeply at the three main concepts of time +programming: Absolute Time, Civil Time, and Time Zone. + +*Absolute time* uniquely and universally represents a specific instant in time. +It has no notion of calendars, or dates, or times of day. Instead, it is a +measure of the passage of real time, typically as a simple count of ticks since +some epoch. Absolute times are independent of all time zones and do not suffer +from human-imposed complexities such as daylight-saving time (DST). Many C++ +types exist to represent absolute times, classically `time_t` and more recently +`std::chrono::time_point`. + +*Civil time* is the legally recognized representation of time for ordinary +affairs (cf. http://www.merriam-webster.com/dictionary/civil). It is a +human-scale representation of time that consists of the six fields — year, +month, day, hour, minute, and second (sometimes shortened to "YMDHMS") — and it +follows the rules of the Proleptic Gregorian Calendar, with 24-hour days divided +into 60-minute hours and 60-second minutes. Like absolute times, civil times are +also independent of all time zones and their related complexities (e.g., DST). +While `std::tm` contains the six civil-time fields (YMDHMS), plus a few more, it +does not have behavior to enforce the rules of civil time. + +*Time zones* are geo-political regions within which human-defined rules are +shared to convert between the absolute-time and civil-time domains. A time +zone's rules include things like the region's offset from the UTC time standard, +daylight-saving adjustments, and short abbreviation strings. Time zones often +have a history of disparate rules that apply only for certain periods, because +the rules may change at the whim of a region's local government. For this +reason, time-zone rules are usually compiled into data snapshots that are used +at runtime to perform conversions between absolute and civil times. There is +currently no C++ standard library supporting arbitrary time zones. + +In order for programmers to reason about and program applications that correctly +deal with these concepts, they must have a library that correctly implements the +above concepts. The CCTZ library adds to the existing C++11 `` library +to fully implement the above concepts. + +* Absolute time — This is implemented by the existing C++11 `` library + without modification. For example, and absolute point in time is represented + by a `std::chrono::time_point`. +* Civil time — This is implemented by the `include/civil_time.h` library that is + provided as part of the CCTZ library. For example, a "date" is represented by + a `cctz::civil_day`. +* Time zone — This is implemented by the `include/time_zone.h` library that is + provided as part of the CCTZ library. For example, a time zone is represented + by an instance of the class `cctz::time_zone`. + +# Examples + +## Hello February 2016 + +This "hello world" example uses a for-loop to iterate the days from the first of +February until the month of March. Each day is streamed to output, and if the +day happens to be the 29th, we also output the day of the week. + +``` +#include +#include "civil_time.h" +int main() { + for (cctz::civil_day d(2016, 2, 1); d < cctz::civil_month(2016, 3); ++d) { + std::cout << "Hello " << d; + if (d.day() == 29) { + std::cout << " <- leap day is a " << cctz::get_weekday(d); + } + std::cout << "\n"; + } +} +``` + +The output of the above program is ``` -Civil Time = F(Absolute Time, Time Zone) -Absolute Time = F(Civil Time, Time Zone) +Hello 2016-02-01 +Hello 2016-02-02 +Hello 2016-02-03 +[...] +Hello 2016-02-27 +Hello 2016-02-28 +Hello 2016-02-29 <- leap day is a Monday ``` -The concepts described thus far—absolute time, civil time, and time -zone—are universal concepts that apply to _all programming languages_ -equally because they describe time in the real world. Different programming -languages and libraries may model these concepts differently with different -classes and sometimes even different names, but these fundamental concepts and -relationships will still exist. +## One giant leap. + +This example shows how to use all three libraries (``, civil time, and +time zone) together. In this example, we know that viewers in New York watched +Neil Armstrong first walk on the moon on July 20, 1969 at 10:56 PM. But we'd +like to see what time it was for our friend watching in Sydney Australia. -# These concepts in CCTZ +``` +#include +#include "civil_time.h" +#include "time_zone.h" +int main() { + cctz::time_zone nyc; + cctz::load_time_zone("America/New_York", &nyc); + + // Converts the input civil time in NYC to an absolute time. + const auto moon_walk = + cctz::convert(cctz::civil_second(1969, 7, 20, 22, 56, 0), nyc); + + std::cout << "Moon walk in NYC: " + << cctz::format("%Y-%m-%d %H:%M:%S %Ez\n", moon_walk, nyc); + + cctz::time_zone syd; + if (!cctz::load_time_zone("Australia/Sydney", &syd)) return -1; + std::cout << "Moon walk in SYD: " + << cctz::format("%Y-%m-%d %H:%M:%S %Ez\n", moon_walk, syd); +} +``` -* 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`. +The output of the above program is + +``` +Moon walk in NYC: 1969-07-20 22:56:00 -04:00 +Moon walk in SYD: 1969-07-21 12:56:00 +10:00 +``` -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). +This example shows that the absolute time (the `std::chrono::time_point`) of the +first walk on the moon is the same no matter the time zone of the viewer (the +same time point is used in both calls to `format()`). The only difference is the +time zone in which the `moon_walk` time point is rendered. And in this case we +can see that our friend in Sydney was probably eating lunch while watching that +historic event. + +# References + +* See also the [Time Programming Fundamentals](https://youtu.be/2rnIHsqABfM) +talk from CppCon 2015 ([slides available here](http://goo.gl/ofof4N)). This talk +mostly describes the older CCTZ v1 API, but the *concepts* are the same. +* ISO C++ proposal to standardize the Civil-Time Library: + https://github.com/devjgm/papers/blob/master/d0215r1.md +* ISO C++ proposal to standardize the Time-Zone Library: + https://github.com/devjgm/papers/blob/master/d0216r1.md From 7e4d6e11ad0538c56141a6ed127a511ebfdfe65d Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 15:06:51 -0500 Subject: [PATCH 17/69] Fixed code formatting. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd338024..9f053942 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ This "hello world" example uses a for-loop to iterate the days from the first of February until the month of March. Each day is streamed to output, and if the day happens to be the 29th, we also output the day of the week. -``` +```cpp #include #include "civil_time.h" int main() { @@ -135,7 +135,7 @@ time zone) together. In this example, we know that viewers in New York watched Neil Armstrong first walk on the moon on July 20, 1969 at 10:56 PM. But we'd like to see what time it was for our friend watching in Sydney Australia. -``` +```cpp #include #include "civil_time.h" #include "time_zone.h" From 21900b929edff3814cdd4ddefb82bfa66f6efa31 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 15:27:08 -0500 Subject: [PATCH 18/69] Added link to mailing list. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f053942..16f5f366 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ CCTZ contains two libraries that cooperate with `` to give C++ programmers all the necessary tools for computing with dates, times, and time zones in a simple and correct manner. The libraries in CCTZ are: -* The Civil-Time Library — This is a header-only library that supports computing +* **The Civil-Time Library** — This is a header-only library that supports computing with human-scale time, such as dates (which are represented by the `cctz::civil_day` class). This library is declared in `include/civil_time.h`. -* The Time-Zone Library — This library uses the IANA time zone database that is +* **The Time-Zone Library** — This library uses the IANA time zone database that is installed on the system to convert between *absolute time* and *civil time*. This library is declared in `include/time_zone.h`. @@ -36,6 +36,8 @@ Next Steps: * Civil Time: `include/civil_time.h` * Time Zone: `include/time_zone.h` 2. Look at the examples in https://github.com/google/cctz/tree/master/examples +3. Join our mailing list to ask questions and keep informed of changes: + * https://groups.google.com/forum/#!forum/cctz # Fundamental Concepts From 16d01eb8015702b1238197ef4ec345c98e0b7451 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 16:04:19 -0500 Subject: [PATCH 19/69] Added logic to clean up .dSYM files --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 25b6aecf..5bfadb75 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,8 @@ clean: @$(RM) $(TOOLS:=.o) $(TOOLS) @$(RM) $(TESTS:=.o) $(TESTS) @$(RM) $(CCTZ_OBJS) $(CCTZ_LIB) + @$(RM) -r $(EXAMPLES:=.dSYM) $(EXAMPLES) + @$(RM) -r $(TOOLS:=.dSYM) $(TOOLS) # dependencies From 41233eac2f7b4bdf8d663a7f12a45e0052cc6dc8 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 16:28:56 -0500 Subject: [PATCH 20/69] . --- Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 5bfadb75..348ca53a 100644 --- a/Makefile +++ b/Makefile @@ -55,12 +55,10 @@ install: $(CCTZ_HDRS) $(CCTZ_LIB) 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) - @$(RM) -r $(EXAMPLES:=.dSYM) $(EXAMPLES) - @$(RM) -r $(TOOLS:=.dSYM) $(TOOLS) + @$(RM) -r $(EXAMPLES:=.dSYM) $(EXAMPLES:=.o) $(EXAMPLES) + @$(RM) -r $(TOOLS:=.dSYM) $(TOOLS:=.o) $(TOOLS) # dependencies From bbcce66fb7d882451dd45701be107c501f78ea33 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 16:42:19 -0500 Subject: [PATCH 21/69] . --- README.md | 63 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 16f5f366..18bd0ef5 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ CCTZ contains two libraries that cooperate with `` to give C++ programmers all the necessary tools for computing with dates, times, and time zones in a simple and correct manner. The libraries in CCTZ are: -* **The Civil-Time Library** — This is a header-only library that supports computing - with human-scale time, such as dates (which are represented by the +* **The Civil-Time Library** — This is a header-only library that supports + computing with human-scale time, such as dates (which are represented by the `cctz::civil_day` class). This library is declared in `include/civil_time.h`. -* **The Time-Zone Library** — This library uses the IANA time zone database that is - installed on the system to convert between *absolute time* and *civil time*. - This library is declared in `include/time_zone.h`. +* **The Time-Zone Library** — This library uses the IANA time zone + database that is installed on the system to convert between *absolute time* + and *civil time*. This library is declared in `include/time_zone.h`. These libraries are currently known to work on **Linux** and **Mac OS X**. We are actively interested in help getting them working on Windows. Please contact @@ -32,7 +32,7 @@ the sources directly yourself. Next Steps: -1. See the CCTZ API: +1. See the documentation for the libraries in CCTZ: * Civil Time: `include/civil_time.h` * Time Zone: `include/time_zone.h` 2. Look at the examples in https://github.com/google/cctz/tree/master/examples @@ -46,12 +46,12 @@ and are library and language agnostic. An understanding of these concepts helps the programmer correctly reason about even the most complicated time-programming challenges and produce the simplest possible solutions.]* -There are two main ways to think about time in a computer program: As *absolute +There are two main ways to think about time in a computer program: as *absolute time*, and as *civil time*. Both have their uses and it is important to understand when each is appropriate. Absolute and civil times may be converted -back and forth using a time zone — this is the only way to correctly convert -between them. Let us now look more deeply at the three main concepts of time -programming: Absolute Time, Civil Time, and Time Zone. +back and forth using a *time zone* — this is the only way to correctly +convert between them. Let us now look more deeply at the three main concepts of +time programming: Absolute Time, Civil Time, and Time Zone. *Absolute time* uniquely and universally represents a specific instant in time. It has no notion of calendars, or dates, or times of day. Instead, it is a @@ -63,13 +63,14 @@ types exist to represent absolute times, classically `time_t` and more recently *Civil time* is the legally recognized representation of time for ordinary affairs (cf. http://www.merriam-webster.com/dictionary/civil). It is a -human-scale representation of time that consists of the six fields — year, -month, day, hour, minute, and second (sometimes shortened to "YMDHMS") — and it -follows the rules of the Proleptic Gregorian Calendar, with 24-hour days divided -into 60-minute hours and 60-second minutes. Like absolute times, civil times are -also independent of all time zones and their related complexities (e.g., DST). -While `std::tm` contains the six civil-time fields (YMDHMS), plus a few more, it -does not have behavior to enforce the rules of civil time. +human-scale representation of time that consists of the six fields — +year, month, day, hour, minute, and second (sometimes shortened to "YMDHMS") +— and it follows the rules of the Proleptic Gregorian Calendar, with +24-hour days divided into 60-minute hours and 60-second minutes. Like absolute +times, civil times are also independent of all time zones and their related +complexities (e.g., DST). While `std::tm` contains the six civil-time fields +(YMDHMS), plus a few more, it does not have behavior to enforce the rules of +civil time. *Time zones* are geo-political regions within which human-defined rules are shared to convert between the absolute-time and civil-time domains. A time @@ -83,18 +84,18 @@ currently no C++ standard library supporting arbitrary time zones. In order for programmers to reason about and program applications that correctly deal with these concepts, they must have a library that correctly implements the -above concepts. The CCTZ library adds to the existing C++11 `` library -to fully implement the above concepts. - -* Absolute time — This is implemented by the existing C++11 `` library - without modification. For example, and absolute point in time is represented - by a `std::chrono::time_point`. -* Civil time — This is implemented by the `include/civil_time.h` library that is - provided as part of the CCTZ library. For example, a "date" is represented by - a `cctz::civil_day`. -* Time zone — This is implemented by the `include/time_zone.h` library that is - provided as part of the CCTZ library. For example, a time zone is represented - by an instance of the class `cctz::time_zone`. +above concepts. CCTZ adds to the existing C++11 `` library to fully +implement the above concepts. + +* Absolute time — This is implemented by the existing C++11 `` + library without modification. For example, an absolute point in time is + represented by a `std::chrono::time_point`. +* Civil time — This is implemented by the `include/civil_time.h` library + that is provided as part of CCTZ. For example, a "date" is represented by a + `cctz::civil_day`. +* Time zone — This is implemented by the `include/time_zone.h` library + that is provided as part of CCTZ. For example, a time zone is represented by + an instance of the class `cctz::time_zone`. # Examples @@ -176,8 +177,8 @@ historic event. # References * See also the [Time Programming Fundamentals](https://youtu.be/2rnIHsqABfM) -talk from CppCon 2015 ([slides available here](http://goo.gl/ofof4N)). This talk -mostly describes the older CCTZ v1 API, but the *concepts* are the same. + talk from CppCon 2015 ([slides available here](http://goo.gl/ofof4N)). This + talk mostly describes the older CCTZ v1 API, but the *concepts* are the same. * ISO C++ proposal to standardize the Civil-Time Library: https://github.com/devjgm/papers/blob/master/d0215r1.md * ISO C++ proposal to standardize the Time-Zone Library: From ea482cd41746ba1496678da736716b1b214e16fa Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 16:45:10 -0500 Subject: [PATCH 22/69] Removed syntax highlighting of code blocks. The highlighter was making some percent signs in the format string red, which looked terrible. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18bd0ef5..4643f7b4 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ This "hello world" example uses a for-loop to iterate the days from the first of February until the month of March. Each day is streamed to output, and if the day happens to be the 29th, we also output the day of the week. -```cpp +``` #include #include "civil_time.h" int main() { @@ -138,7 +138,7 @@ time zone) together. In this example, we know that viewers in New York watched Neil Armstrong first walk on the moon on July 20, 1969 at 10:56 PM. But we'd like to see what time it was for our friend watching in Sydney Australia. -```cpp +``` #include #include "civil_time.h" #include "time_zone.h" From 4553c5590b0c79668ed4ee6ff8c5e54dc411609c Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 16:52:00 -0500 Subject: [PATCH 23/69] added a makefile comment --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 348ca53a..9d197c49 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +# While Bazel (http://bazel.io) is the primary build system used by cctz, this +# Makefile is provided as a convenience for those who can't use Bazel and can't +# compile the sources in their own build system. + CC = $(CXX) OPT = -g # TEST_FLAGS = From c736b7708bfabcde8a292c8621ced85742ba9d54 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Thu, 10 Mar 2016 17:14:57 -0500 Subject: [PATCH 24/69] Reinstate allowance for 32-bit zoneinfo on pre-1902 dates. --- src/time_zone_lookup_test.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/time_zone_lookup_test.cc b/src/time_zone_lookup_test.cc index 0d0405eb..816feec4 100644 --- a/src/time_zone_lookup_test.cc +++ b/src/time_zone_lookup_test.cc @@ -959,13 +959,15 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) { // Before the first transition. auto tp = convert(civil_second(1889, 12, 31, 0, 0, 0), tz); - ExpectTime(tp, tz, 1889, 12, 31, 0, 0, 0, -18431, false, "LMT"); + ExpectTime(tp, tz, 1889, 12, 31, 0, 0, 0, -18431, false, + tz.lookup(tp).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 = convert(civil_second(1889, 12, 31, 23, 59, 59), tz); - ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18431, false, "LMT"); + ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18431, false, + tz.lookup(tp).abbr); tp += std::chrono::seconds(1); ExpectTime(tp, tz, 1890, 1, 1, 0, 0, 0, -18431, false, "KMT"); From d8610e5c7c0bec2ce4d0dd7e66f00f84ba17459b Mon Sep 17 00:00:00 2001 From: Bradley White Date: Thu, 10 Mar 2016 17:33:06 -0500 Subject: [PATCH 25/69] Also clean up .dSYM for tests (if Makefile ever builds them). --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9d197c49..f6f0c5b1 100644 --- a/Makefile +++ b/Makefile @@ -59,10 +59,10 @@ install: $(CCTZ_HDRS) $(CCTZ_LIB) sudo cp -p $(CCTZ_LIB) $(PREFIX)/lib clean: - @$(RM) $(TESTS:=.o) $(TESTS) - @$(RM) $(CCTZ_OBJS) $(CCTZ_LIB) @$(RM) -r $(EXAMPLES:=.dSYM) $(EXAMPLES:=.o) $(EXAMPLES) @$(RM) -r $(TOOLS:=.dSYM) $(TOOLS:=.o) $(TOOLS) + @$(RM) -r $(TESTS:=.dSYM) $(TESTS:=.o) $(TESTS) + @$(RM) $(CCTZ_OBJS) $(CCTZ_LIB) # dependencies From 318a1433d027a6ad97d47eee19d12bc17f87ee21 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 10 Mar 2016 21:00:07 -0500 Subject: [PATCH 26/69] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4643f7b4..1940792d 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ the [Google Test](https://github.com/google/googletest) framework. cctz` 3. Build cctz and run the tests: `bazel test :all` -Note that when using CCTZ in your own code, you might find it easiest to compile -the sources directly yourself. +Note: If you're unable to use bazel, there is also a `Makefile` that should help +get you started. When using CCTZ in your own project, you might find it easiest +to compile the sources using your existing build system. Next Steps: @@ -131,7 +132,7 @@ Hello 2016-02-28 Hello 2016-02-29 <- leap day is a Monday ``` -## One giant leap. +## One giant leap This example shows how to use all three libraries (``, civil time, and time zone) together. In this example, we know that viewers in New York watched @@ -176,6 +177,7 @@ historic event. # References +* CCTZ [FAQ](https://github.com/google/cctz/wiki/FAQ) * See also the [Time Programming Fundamentals](https://youtu.be/2rnIHsqABfM) talk from CppCon 2015 ([slides available here](http://goo.gl/ofof4N)). This talk mostly describes the older CCTZ v1 API, but the *concepts* are the same. From 2a3a8c622d4132be881af63dd11466d6605a8af2 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 11 Mar 2016 16:55:32 -0500 Subject: [PATCH 27/69] Makefile: support building from a subdir, and auto dependencies. --- .gitignore | 2 ++ Makefile | 71 +++++++++++++++++++++--------------------------------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index a71d86dc..96d1e749 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ # ignore all files in the bazel directories /bazel-* +# ignore non-bazel build products +/build diff --git a/Makefile b/Makefile index f6f0c5b1..0fd83ac4 100644 --- a/Makefile +++ b/Makefile @@ -12,20 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. -# While Bazel (http://bazel.io) is the primary build system used by cctz, this -# Makefile is provided as a convenience for those who can't use Bazel and can't -# compile the sources in their own build system. +# While Bazel (http://bazel.io) is the primary build system used by cctz, +# this Makefile is provided as a convenience for those who can't use Bazel +# and can't compile the sources in their own build system. +# +# Suggested usage: +# make -C build -f ../Makefile SRC=../ -j `nproc` + +# local configuration +CXX = clang++-3.6 +STD = c++14 +OPT = -O +PREFIX = /usr/local +# possible support for googletest +## TESTS = civil_time_test time_zone_lookup_test time_zone_format_test +## TEST_FLAGS = ... +## TEST_LIBS = ... + +VPATH = $(SRC)include:$(SRC)src:$(SRC)examples CC = $(CXX) -OPT = -g -# TEST_FLAGS = -# TEST_LIBS = -CPPFLAGS = -Iinclude $(TEST_FLAGS) -Wall -std=c++11 -pthread $(OPT) -fPIC -VPATH = include:src:examples -LDFLAGS = -pthread -LDLIBS = $(TEST_LIBS) -lm +CPPFLAGS = -Wall -I$(SRC)include -std=$(STD) -pthread \ + $(TEST_FLAGS) $(OPT) -fPIC -MD ARFLAGS = rcs -PREFIX = /usr/local +LDFLAGS = -pthread +LDLIBS = $(TEST_LIBS) CCTZ_LIB = libcctz.a @@ -43,7 +54,6 @@ CCTZ_OBJS = \ 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 @@ -59,36 +69,9 @@ install: $(CCTZ_HDRS) $(CCTZ_LIB) sudo cp -p $(CCTZ_LIB) $(PREFIX)/lib clean: - @$(RM) -r $(EXAMPLES:=.dSYM) $(EXAMPLES:=.o) $(EXAMPLES) - @$(RM) -r $(TOOLS:=.dSYM) $(TOOLS:=.o) $(TOOLS) - @$(RM) -r $(TESTS:=.dSYM) $(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 + @$(RM) -r $(EXAMPLES:=.dSYM) $(EXAMPLES:=.o) $(EXAMPLES:=.d) $(EXAMPLES) + @$(RM) -r $(TOOLS:=.dSYM) $(TOOLS:=.o) $(TOOLS:=.d) $(TOOLS) + @$(RM) -r $(TESTS:=.dSYM) $(TESTS:=.o) $(TESTS:=.d) $(TESTS) + @$(RM) $(CCTZ_OBJS) $(CCTZ_OBJS:.o=.d) $(CCTZ_LIB) -civil_time.h: civil_time_detail.h +-include $(CCTZ_OBJS:.o=.d) $(TESTS:=.d) $(TOOLS:=.d) $(EXAMPLES:=.d) From 0518420bfcb7670abcf6f72089c200c3cb857433 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 11 Mar 2016 16:57:31 -0500 Subject: [PATCH 28/69] Simplify examples/example4.cc. --- examples/example4.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/example4.cc b/examples/example4.cc index 1fcfb300..815ce5a8 100644 --- a/examples/example4.cc +++ b/examples/example4.cc @@ -21,8 +21,7 @@ template cctz::time_point FloorDay(cctz::time_point tp, cctz::time_zone tz) { - const cctz::civil_second cs = cctz::convert(tp, tz); - const cctz::civil_day cd(cs.year(), cs.month(), cs.day()); + const cctz::civil_day cd(cctz::convert(tp, tz)); return cctz::convert(cctz::civil_second(cd), tz); } From 0997784fb9ed3d838d06035507e393b3f8d4b646 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Fri, 11 Mar 2016 17:00:27 -0500 Subject: [PATCH 29/69] . --- examples/classic.cc | 6 +++--- examples/epoch_shift.cc | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/classic.cc b/examples/classic.cc index 986f1f67..b8f28958 100644 --- a/examples/classic.cc +++ b/examples/classic.cc @@ -19,7 +19,7 @@ #include #include -std::string Format(const std::string& fmt, const std::tm& tm) { +std::string format(const std::string& fmt, const std::tm& tm) { char buf[100]; std::strftime(buf, sizeof(buf), fmt.c_str(), &tm); return buf; @@ -30,9 +30,9 @@ int main() { std::tm tm_utc; gmtime_r(&now, &tm_utc); - std::cout << Format("UTC: %F %T\n", tm_utc); + std::cout << format("UTC: %F %T\n", tm_utc); std::tm tm_local; localtime_r(&now, &tm_local); - std::cout << Format("Local: %F %T\n", tm_local); + std::cout << format("Local: %F %T\n", tm_local); } diff --git a/examples/epoch_shift.cc b/examples/epoch_shift.cc index 532df3f1..080a2c99 100644 --- a/examples/epoch_shift.cc +++ b/examples/epoch_shift.cc @@ -17,7 +17,7 @@ #include #include -std::string Format(const std::string& fmt, const std::tm& tm) { +std::string format(const std::string& fmt, const std::tm& tm) { char buf[100]; std::strftime(buf, sizeof(buf), fmt.c_str(), &tm); return buf; @@ -36,7 +36,7 @@ int main() { const std::time_t now_nyc = now + off; std::tm tm_nyc; gmtime_r(&now_nyc, &tm_nyc); - std::cout << Format("NYC: %F %T\n", tm_nyc); + std::cout << format("NYC: %F %T\n", tm_nyc); // Shift back: "local time_t" to UTC off = GetOffset(now_nyc, "America/New_York"); From dc18cd7523e128fa501e358cc8351bd949b2ca51 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 11 Mar 2016 17:46:36 -0500 Subject: [PATCH 30/69] Omit system header files from generated dependencies. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0fd83ac4..0e1887c5 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ PREFIX = /usr/local VPATH = $(SRC)include:$(SRC)src:$(SRC)examples CC = $(CXX) CPPFLAGS = -Wall -I$(SRC)include -std=$(STD) -pthread \ - $(TEST_FLAGS) $(OPT) -fPIC -MD + $(TEST_FLAGS) $(OPT) -fPIC -MMD ARFLAGS = rcs LDFLAGS = -pthread LDLIBS = $(TEST_LIBS) From 6ee6a87b0c01147f99522cdfe23e133b86becb40 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 11 Mar 2016 17:48:28 -0500 Subject: [PATCH 31/69] Make CXX=g++ and STD=c++11 the default configuration. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0e1887c5..b97feded 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,8 @@ # make -C build -f ../Makefile SRC=../ -j `nproc` # local configuration -CXX = clang++-3.6 -STD = c++14 +CXX = g++ +STD = c++11 OPT = -O PREFIX = /usr/local From 723d9d1dd87a854cd920666e6536d7f4f7ca411c Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 11 Mar 2016 18:08:52 -0500 Subject: [PATCH 32/69] Remove the redundant inline from the constexpr functions. --- include/civil_time_detail.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index a8af2271..63fd5ffa 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -201,19 +201,19 @@ 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) { +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) { +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) { +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) { +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) { +constexpr int ymd_ord(int y, int m, int d) { return eymd_ord(m <= 2 ? y - 1 : y, m, d); } @@ -222,44 +222,44 @@ inline constexpr int ymd_ord(int y, int m, int d) { //////////////////////////////////////////////////////////////////////// // Aligns the (normalized) fields struct to the indicated field. -inline constexpr fields align(second_tag, fields f) { +constexpr fields align(second_tag, fields f) { return f; } -inline constexpr fields align(minute_tag, fields f) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +constexpr fields step(year_tag, fields f, int n) { return fields{f.y + n, f.m, f.d, f.hh, f.mm, f.ss}; } From 6f4352cd1958b67018778a496ec5aa103fec8c80 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Thu, 17 Mar 2016 11:34:07 -0400 Subject: [PATCH 33/69] Change constexpr tests to use static_assert. --- src/civil_time_test.cc | 318 ++++++++++++++++++----------------------- 1 file changed, 140 insertions(+), 178 deletions(-) diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index f688373d..55e14008 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -14,7 +14,6 @@ #include "civil_time.h" -#include #include #include #include @@ -34,293 +33,231 @@ std::string Format(const T& t) { } // namespace -// Construction tests +// Construction constexpr tests TEST(CivilTime, Normal) { constexpr civil_second css(2016, 1, 28, 17, 14, 12); + static_assert(css.second() == 12, "Normal.second"); constexpr civil_minute cmm(2016, 1, 28, 17, 14); + static_assert(cmm.minute() == 14, "Normal.minute"); constexpr civil_hour chh(2016, 1, 28, 17); + static_assert(chh.hour() == 17, "Normal.hour"); constexpr civil_day cd(2016, 1, 28); + static_assert(cd.day() == 28, "Normal.day"); constexpr civil_month cm(2016, 1); + static_assert(cm.month() == 1, "Normal.month"); constexpr civil_year cy(2016); + static_assert(cy.year() == 2016, "Normal.year"); } TEST(CivilTime, Conversion) { constexpr civil_year cy(2016); + static_assert(cy.year() == 2016, "Conversion.year"); constexpr civil_month cm(cy); + static_assert(cm.month() == 1, "Conversion.month"); constexpr civil_day cd(cm); + static_assert(cd.day() == 1, "Conversion.day"); constexpr civil_hour chh(cd); + static_assert(chh.hour() == 0, "Conversion.hour"); constexpr civil_minute cmm(chh); + static_assert(cmm.minute() == 0, "Conversion.minute"); constexpr civil_second css(cmm); + static_assert(css.second() == 0, "Conversion.second"); } -// Normalization tests +// Normalization constexpr 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()); + static_assert(cs.year() == 2016, "Normalized.year"); + static_assert(cs.month() == 1, "Normalized.month"); + static_assert(cs.day() == 28, "Normalized.day"); + static_assert(cs.hour() == 17, "Normalized.hour"); + static_assert(cs.minute() == 14, "Normalized.minute"); + static_assert(cs.second() == 12, "Normalized.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()); + static_assert(cs.year() == 2016, "SecondOverflow.year"); + static_assert(cs.month() == 1, "SecondOverflow.month"); + static_assert(cs.day() == 28, "SecondOverflow.day"); + static_assert(cs.hour() == 17, "SecondOverflow.hour"); + static_assert(cs.minute() == 16, "SecondOverflow.minute"); + static_assert(cs.second() == 1, "SecondOverflow.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()); + static_assert(cs.year() == 2016, "SecondUnderflow.year"); + static_assert(cs.month() == 1, "SecondUnderflow.month"); + static_assert(cs.day() == 28, "SecondUnderflow.day"); + static_assert(cs.hour() == 17, "SecondUnderflow.hour"); + static_assert(cs.minute() == 11, "SecondUnderflow.minute"); + static_assert(cs.second() == 59, "SecondUnderflow.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()); + static_assert(cs.year() == 2016, "MinuteOverflow.year"); + static_assert(cs.month() == 1, "MinuteOverflow.month"); + static_assert(cs.day() == 28, "MinuteOverflow.day"); + static_assert(cs.hour() == 19, "MinuteOverflow.hour"); + static_assert(cs.minute() == 1, "MinuteOverflow.minute"); + static_assert(cs.second() == 12, "MinuteOverflow.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()); + static_assert(cs.year() == 2016, "MinuteUnderflow.year"); + static_assert(cs.month() == 1, "MinuteUnderflow.month"); + static_assert(cs.day() == 28, "MinuteUnderflow.day"); + static_assert(cs.hour() == 14, "MinuteUnderflow.hour"); + static_assert(cs.minute() == 59, "MinuteUnderflow.minute"); + static_assert(cs.second() == 12, "MinuteUnderflow.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()); + static_assert(cs.year() == 2016, "HourOverflow.year"); + static_assert(cs.month() == 1, "HourOverflow.month"); + static_assert(cs.day() == 30, "HourOverflow.day"); + static_assert(cs.hour() == 1, "HourOverflow.hour"); + static_assert(cs.minute() == 14, "HourOverflow.minute"); + static_assert(cs.second() == 12, "HourOverflow.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()); + static_assert(cs.year() == 2016, "HourUnderflow.year"); + static_assert(cs.month() == 1, "HourUnderflow.month"); + static_assert(cs.day() == 25, "HourUnderflow.day"); + static_assert(cs.hour() == 23, "HourUnderflow.hour"); + static_assert(cs.minute() == 14, "HourUnderflow.minute"); + static_assert(cs.second() == 12, "HourUnderflow.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()); + static_assert(cs.year() == 2018, "MonthOverflow.year"); + static_assert(cs.month() == 1, "MonthOverflow.month"); + static_assert(cs.day() == 28, "MonthOverflow.day"); + static_assert(cs.hour() == 17, "MonthOverflow.hour"); + static_assert(cs.minute() == 14, "MonthOverflow.minute"); + static_assert(cs.second() == 12, "MonthOverflow.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()); + static_assert(cs.year() == 2013, "MonthUnderflow.year"); + static_assert(cs.month() == 11, "MonthUnderflow.month"); + static_assert(cs.day() == 28, "MonthUnderflow.day"); + static_assert(cs.hour() == 17, "MonthUnderflow.hour"); + static_assert(cs.minute() == 14, "MonthUnderflow.minute"); + static_assert(cs.second() == 12, "MonthUnderflow.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()); + static_assert(cs.year() == 2816, "C4Overflow.year"); + static_assert(cs.month() == 1, "C4Overflow.month"); + static_assert(cs.day() == 1, "C4Overflow.day"); + static_assert(cs.hour() == 17, "C4Overflow.hour"); + static_assert(cs.minute() == 14, "C4Overflow.minute"); + static_assert(cs.second() == 12, "C4Overflow.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()); + static_assert(cs.year() == 1215, "C4Underflow.year"); + static_assert(cs.month() == 12, "C4Underflow.month"); + static_assert(cs.day() == 30, "C4Underflow.day"); + static_assert(cs.hour() == 17, "C4Underflow.hour"); + static_assert(cs.minute() == 14, "C4Underflow.minute"); + static_assert(cs.second() == 12, "C4Underflow.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()); + static_assert(cs.year() == 2012, "MixedNormalization.year"); + static_assert(cs.month() == 10, "MixedNormalization.month"); + static_assert(cs.day() == 4, "MixedNormalization.day"); + static_assert(cs.hour() == 1, "MixedNormalization.hour"); + static_assert(cs.minute() == 55, "MixedNormalization.minute"); + static_assert(cs.second() == 29, "MixedNormalization.second"); } -// Relational tests +// Relational constexpr 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); + static_assert(less, "Less"); } -// Arithmetic tests +// Arithmetic constexpr 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()); + static_assert(cs2.year() == 2016, "Addition.year"); + static_assert(cs2.month() == 1, "Addition.month"); + static_assert(cs2.day() == 28, "Addition.day"); + static_assert(cs2.hour() == 17, "Addition.hour"); + static_assert(cs2.minute() == 15, "Addition.minute"); + static_assert(cs2.second() == 2, "Addition.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()); + static_assert(cs2.year() == 2016, "Subtraction.year"); + static_assert(cs2.month() == 1, "Subtraction.month"); + static_assert(cs2.day() == 28, "Subtraction.day"); + static_assert(cs2.hour() == 17, "Subtraction.hour"); + static_assert(cs2.minute() == 13, "Subtraction.minute"); + static_assert(cs2.second() == 22, "Subtraction.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); + static_assert(diff == 365, "Diff"); } -// Helper tests +// Helper constexpr tests TEST(CivilTime, WeekDay) { constexpr civil_day cd(2016, 1, 28); constexpr weekday wd = get_weekday(cd); - EXPECT_EQ(weekday::thursday, wd); + static_assert(wd == weekday::thursday, "Weekday"); } 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()); + static_assert(next.year() == 2016, "NextWeekDay.year"); + static_assert(next.month() == 2, "NextWeekDay.month"); + static_assert(next.day() == 4, "NextWeekDay.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, WeekdayStream) { - std::stringstream ss; - - ss << weekday::monday; - EXPECT_EQ("Monday", ss.str()); - ss.str(""); - - ss << weekday::tuesday; - EXPECT_EQ("Tuesday", ss.str()); - ss.str(""); - - ss << weekday::wednesday; - EXPECT_EQ("Wednesday", ss.str()); - ss.str(""); - - ss << weekday::thursday; - EXPECT_EQ("Thursday", ss.str()); - ss.str(""); - - ss << weekday::friday; - EXPECT_EQ("Friday", ss.str()); - ss.str(""); - - ss << weekday::saturday; - EXPECT_EQ("Saturday", ss.str()); - ss.str(""); - - ss << weekday::sunday; - EXPECT_EQ("Sunday", ss.str()); - ss.str(""); + static_assert(prev.year() == 2016, "PrevWeekDay.year"); + static_assert(prev.month() == 1, "PrevWeekDay.month"); + static_assert(prev.day() == 21, "PrevWeekDay.day"); } TEST(CivilTime, YearDay) { constexpr civil_day cd(2016, 1, 28); constexpr int yd = get_yearday(cd); - EXPECT_EQ(28, yd); + static_assert(yd == 28, "YearDay"); } -TEST(CivilTime, OutputStream) { - std::stringstream ss; - - // Tests formatting civil_year, which does not pad. - ss << civil_year(2016); - EXPECT_EQ("2016", ss.str()); - ss.str(""); - - ss << civil_year(123); - EXPECT_EQ("123", ss.str()); - ss.str(""); - - ss << civil_year(0); - EXPECT_EQ("0", ss.str()); - ss.str(""); - - ss << civil_year(-1); - EXPECT_EQ("-1", ss.str()); - ss.str(""); - - // Tests formatting of sub-year types, which pad to 2 digits - ss << civil_month(2016, 2); - EXPECT_EQ("2016-02", ss.str()); - ss.str(""); - - ss << civil_day(2016, 2, 3); - EXPECT_EQ("2016-02-03", ss.str()); - ss.str(""); - - ss << civil_hour(2016, 2, 3, 4); - EXPECT_EQ("2016-02-03T04", ss.str()); - ss.str(""); - - ss << civil_minute(2016, 2, 3, 4, 5); - EXPECT_EQ("2016-02-03T04:05", ss.str()); - ss.str(""); - - ss << civil_second(2016, 2, 3, 4, 5, 6); - EXPECT_EQ("2016-02-03T04:05:06", ss.str()); - ss.str(""); -} - -// START OF google3 TESTS +// The remaining tests do not use constexpr. TEST(CivilTime, DefaultConstruction) { civil_second ss; @@ -589,8 +526,8 @@ TEST(CivilTime, Arithmetic) { } TEST(CivilTime, ArithmeticLimits) { - constexpr int kIntMax = std::numeric_limits::max(); - constexpr int kIntMin = std::numeric_limits::min(); + const int kIntMax = std::numeric_limits::max(); + const int kIntMin = std::numeric_limits::min(); civil_second second(1970, 1, 1, 0, 0, 0); second += kIntMax; @@ -739,6 +676,7 @@ TEST(CivilTime, Properties) { EXPECT_EQ(0, d.minute()); EXPECT_EQ(0, d.second()); EXPECT_EQ(weekday::tuesday, get_weekday(d)); + EXPECT_EQ(34, get_yearday(d)); civil_month m(2015, 2, 3, 4, 5, 6); EXPECT_EQ(2015, m.year()); @@ -757,10 +695,34 @@ TEST(CivilTime, Properties) { EXPECT_EQ(0, y.second()); } +TEST(CivilTime, OutputStream) { + // Tests formatting of civil_year, which does not pad. + EXPECT_EQ("2016", Format(civil_year(2016))); + EXPECT_EQ("123", Format(civil_year(123))); + EXPECT_EQ("0", Format(civil_year(0))); + EXPECT_EQ("-1", Format(civil_year(-1))); + + // Tests formatting of sub-year types, which pad to 2 digits + EXPECT_EQ("2016-02", Format(civil_month(2016, 2))); + EXPECT_EQ("2016-02-03", Format(civil_day(2016, 2, 3))); + EXPECT_EQ("2016-02-03T04", Format(civil_hour(2016, 2, 3, 4))); + EXPECT_EQ("2016-02-03T04:05", Format(civil_minute(2016, 2, 3, 4, 5))); + EXPECT_EQ("2016-02-03T04:05:06", Format(civil_second(2016, 2, 3, 4, 5, 6))); + + // Tests formatting of weekday. + EXPECT_EQ("Monday", Format(weekday::monday)); + EXPECT_EQ("Tuesday", Format(weekday::tuesday)); + EXPECT_EQ("Wednesday", Format(weekday::wednesday)); + EXPECT_EQ("Thursday", Format(weekday::thursday)); + EXPECT_EQ("Friday", Format(weekday::friday)); + EXPECT_EQ("Saturday", Format(weekday::saturday)); + EXPECT_EQ("Sunday", Format(weekday::sunday)); +} + 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); + EXPECT_EQ(weekday::thursday, get_weekday(thursday)); // Thursday -> Thursday civil_day d = next_weekday(thursday, weekday::thursday); From db67188b49a2a01888ba248407e5e020c3847264 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Wed, 23 Mar 2016 18:26:17 -0400 Subject: [PATCH 34/69] Apply some Windows portability fixes (untested). --- src/time_zone_info.cc | 40 ++++++++++++++++++++++++++++++---------- src/time_zone_info.h | 4 ++-- src/time_zone_libc.cc | 9 ++++++--- src/time_zone_lookup.cc | 9 +++++++++ 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/time_zone_info.cc b/src/time_zone_info.cc index 7674bc49..346f42c7 100644 --- a/src/time_zone_info.cc +++ b/src/time_zone_info.cc @@ -166,7 +166,7 @@ inline int DaysPerYear(int year) { return kDaysPerYear[IsLeap(year)]; } int64_t DayOrdinal(int64_t year, int month, int day) { year -= (month <= 2 ? 1 : 0); const int64_t era = (year >= 0 ? year : year - 399) / 400; - const int yoe = year - era * 400; + const int yoe = static_cast(year - era * 400); const int doy = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1; const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; return era * 146097 + doe - 719468; // shift epoch to 1970-01-01 @@ -432,7 +432,7 @@ bool TimeZoneInfo::Load(const std::string& name, FILE* fp) { size_t time_len = 4; if (tzh.tzh_version[0] != '\0') { // Skip the 4-byte data. - if (fseek(fp, hdr.DataLength(time_len), SEEK_CUR) != 0) + if (fseek(fp, static_cast(hdr.DataLength(time_len)), SEEK_CUR) != 0) return false; // Read and validate the header for the 8-byte data. if (fread(&tzh, 1, sizeof tzh, fp) != sizeof tzh) @@ -647,20 +647,40 @@ bool TimeZoneInfo::Load(const std::string& name) { // Map time-zone name to its machine-specific path. std::string path; if (name == "localtime") { +#if defined(_WIN32) || defined(_WIN64) + char* localtime = nullptr; + _dupenv_s(&localtime, nullptr, "LOCALTIME"); + path = localtime ? localtime : "/etc/localtime"; + free(localtime); +#else const char* localtime = std::getenv("LOCALTIME"); path = localtime ? localtime : "/etc/localtime"; +#endif } else if (!name.empty() && name[0] == '/') { path = name; } else { +#if defined(_WIN32) || defined(_WIN64) + char* tzdir = nullptr; + _dupenv_s(&tzdir, nullptr, "TZDIR"); + path = tzdir ? tzdir : "/usr/share/zoneinfo"; + free(tzdir); +#else const char* tzdir = std::getenv("TZDIR"); path = tzdir ? tzdir : "/usr/share/zoneinfo"; +#endif path += '/'; path += name; } // Load the time-zone data. bool loaded = false; - if (FILE* fp = fopen(path.c_str(), "rb")) { +#if defined(_WIN32) || defined(_WIN64) + FILE* fp; + if (fopen_s(&fp, path.c_str(), "rb") != 0) fp = nullptr; +#else + FILE* fp = fopen(path.c_str(), "rb"); +#endif + if (fp != nullptr) { loaded = Load(name, fp); fclose(fp); } else { @@ -738,7 +758,7 @@ Breakdown TimeZoneInfo::LocalTime(int64_t unix_time, } // Handle months and days. - bd.yearday = (seconds / SECSPERDAY) + 1; + bd.yearday = static_cast(seconds / SECSPERDAY) + 1; seconds %= SECSPERDAY; bd.month = TM_DECEMBER + 1; bd.day = bd.yearday; @@ -753,9 +773,9 @@ Breakdown TimeZoneInfo::LocalTime(int64_t unix_time, } // Handle hours, minutes, and seconds. - bd.hour = seconds / SECSPERHOUR; + bd.hour = static_cast(seconds / SECSPERHOUR); seconds %= SECSPERHOUR; - bd.minute = seconds / SECSPERMIN; + bd.minute = static_cast(seconds / SECSPERMIN); bd.second = seconds % SECSPERMIN; // Shift weekday to [1==Mon, ..., 7=Sun]. @@ -783,7 +803,7 @@ TimeInfo TimeZoneInfo::TimeLocal(int64_t year, int mon, int day, int hour, Breakdown TimeZoneInfo::BreakTime(const time_point& tp) const { int64_t unix_time = ToUnixSeconds(tp); - const int32_t timecnt = transitions_.size(); + const size_t timecnt = transitions_.size(); if (timecnt == 0 || unix_time < transitions_[0].unix_time) { const int type_index = default_transition_type_; return LocalTime(unix_time, transition_types_[type_index]); @@ -804,7 +824,7 @@ Breakdown TimeZoneInfo::BreakTime(const time_point& tp) const { return LocalTime(unix_time, transition_types_[type_index]); } - const int32_t hint = local_time_hint_.load(std::memory_order_relaxed); + const size_t hint = local_time_hint_.load(std::memory_order_relaxed); if (0 < hint && hint < timecnt) { if (unix_time < transitions_[hint].unix_time) { if (!(unix_time < transitions_[hint - 1].unix_time)) { @@ -829,7 +849,7 @@ TimeInfo TimeZoneInfo::MakeTimeInfo(int64_t year, int mon, int day, DateTime& dt(target.date_time); const bool normalized = dt.Normalize(year, mon, day, hour, min, sec); - const int32_t timecnt = transitions_.size(); + const size_t timecnt = transitions_.size(); if (timecnt == 0) { // Use the default offset. int32_t offset = transition_types_[default_transition_type_].utc_offset; @@ -846,7 +866,7 @@ TimeInfo TimeZoneInfo::MakeTimeInfo(int64_t year, int mon, int day, } else if (!(dt < transitions_[timecnt - 1].date_time)) { tr = end; } else { - const int32_t hint = time_local_hint_.load(std::memory_order_relaxed); + const size_t hint = time_local_hint_.load(std::memory_order_relaxed); if (0 < hint && hint < timecnt) { if (dt < transitions_[hint].date_time) { if (!(dt < transitions_[hint - 1].date_time)) { diff --git a/src/time_zone_info.h b/src/time_zone_info.h index 69be5cd8..4a1dce90 100644 --- a/src/time_zone_info.h +++ b/src/time_zone_info.h @@ -132,8 +132,8 @@ class TimeZoneInfo : public TimeZoneIf { bool extended_; // future_spec_ was used to generate transitions int64_t last_year_; // the final year of the generated transitions - mutable std::atomic local_time_hint_; // BreakTime() search hint - mutable std::atomic time_local_hint_; // MakeTimeInfo() search hint + mutable std::atomic local_time_hint_; // BreakTime() search hint + mutable std::atomic time_local_hint_; // MakeTimeInfo() search hint }; } // namespace cctz diff --git a/src/time_zone_libc.cc b/src/time_zone_libc.cc index 0e4acec8..ea19540c 100644 --- a/src/time_zone_libc.cc +++ b/src/time_zone_libc.cc @@ -34,6 +34,9 @@ #elif defined(__sun) # define OFFSET(tm) ((tm).tm_isdst > 0 ? altzone : timezone) # define ABBR(tm) (tzname[(tm).tm_isdst > 0]) +#elif defined(_WIN32) || defined(_WIN64) +# define OFFSET(tm) (_timezone + ((tm).tm_isdst > 0 ? 60 * 60 : 0)) +# define ABBR(tm) (_tzname[(tm).tm_isdst > 0]) #else # define OFFSET(tm) (timezone + ((tm).tm_isdst > 0 ? 60 * 60 : 0)) # define ABBR(tm) (tzname[(tm).tm_isdst > 0]) @@ -56,7 +59,7 @@ Breakdown TimeZoneLibC::BreakTime(const time_point& tp) const { std::tm tm; if (local_) { #if defined(_WIN32) || defined(_WIN64) - tm = *localtime(&t); + localtime_s(&tm, &t); #else localtime_r(&t, &tm); #endif @@ -64,7 +67,7 @@ Breakdown TimeZoneLibC::BreakTime(const time_point& tp) const { bd.abbr = ABBR(tm); } else { #if defined(_WIN32) || defined(_WIN64) - tm = *gmtime(&t); + gmtime_s(&tm, &t); #else gmtime_r(&t, &tm); #endif @@ -115,7 +118,7 @@ const int kDaysPerYear[2] = {365, 366}; std::time_t DayOrdinal(int64_t year, int month, int day) { year -= (month <= 2 ? 1 : 0); const std::time_t era = (year >= 0 ? year : year - 399) / 400; - const int yoe = year - era * 400; + const int yoe = static_cast(year - era * 400); const int doy = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1; const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; return era * 146097 + doe - 719468; // shift epoch to 1970-01-01 diff --git a/src/time_zone_lookup.cc b/src/time_zone_lookup.cc index b58556a7..449c3f5e 100644 --- a/src/time_zone_lookup.cc +++ b/src/time_zone_lookup.cc @@ -27,7 +27,13 @@ time_zone utc_time_zone() { } time_zone local_time_zone() { +#if defined(_WIN32) || defined(_WIN64) + char* tz_env = nullptr; + _dupenv_s(*tz_env, nullptr, "TZ"); + const char* zone = tz_env; +#else const char* zone = std::getenv("TZ"); +#endif if (zone != nullptr) { if (*zone == ':') ++zone; } else { @@ -37,6 +43,9 @@ time_zone local_time_zone() { if (!load_time_zone(zone, &tz)) { load_time_zone("UTC", &tz); } +#if defined(_WIN32) || defined(_WIN64) + free(tz_env); +#endif return tz; } From 67aff898fef85d83c544a3d8b48d746783df8f8d Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 24 Mar 2016 11:10:15 -0400 Subject: [PATCH 35/69] Added a v1 compatibility interface in src/cctz.h --- BUILD | 68 ++- src/cctz.h | 154 ++++++ src/cctz_v1_test.cc | 1130 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1330 insertions(+), 22 deletions(-) create mode 100644 src/cctz.h create mode 100644 src/cctz_v1_test.cc diff --git a/BUILD b/BUILD index b6fb8913..5d9cdc3c 100644 --- a/BUILD +++ b/BUILD @@ -15,7 +15,7 @@ ### libraries cc_library( - name = "cctz_civil_time", + name = "civil_time", hdrs = [ "include/civil_time.h", ], @@ -25,7 +25,7 @@ cc_library( ) cc_library( - name = "cctz_time_zone", + name = "time_zone", srcs = [ "src/time_zone_format.cc", "src/time_zone_if.cc", @@ -50,7 +50,20 @@ cc_library( "-lpthread", ], visibility = ["//visibility:public"], - deps = [":cctz_civil_time"], + deps = [":civil_time"], +) + +cc_library( + name = "cctz_v1", + hdrs = [ + "src/cctz.h", + ], + includes = ["include"], + visibility = ["//visibility:public"], + deps = [ + ":civil_time", + ":time_zone", + ], ) ### tests @@ -85,7 +98,7 @@ cc_test( srcs = ["src/civil_time_test.cc"], deps = [ "@gtest//:gtest", - ":cctz_civil_time", + ":civil_time", ], ) @@ -95,8 +108,8 @@ cc_test( srcs = ["src/time_zone_format_test.cc"], deps = [ "@gtest//:gtest", - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -106,8 +119,19 @@ cc_test( srcs = ["src/time_zone_lookup_test.cc"], deps = [ "@gtest//:gtest", - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", + ], +) + +cc_test( + name = "cctz_v1_test", + size = "small", + srcs = ["src/cctz_v1_test.cc"], + defines = ["CCTZ_ACK_V1_DEPRECATION=1"], + deps = [ + "@gtest//:gtest", + ":cctz_v1", ], ) @@ -122,8 +146,8 @@ cc_binary( name = "epoch_shift", srcs = ["examples/epoch_shift.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -131,8 +155,8 @@ cc_binary( name = "example1", srcs = ["examples/example1.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -140,8 +164,8 @@ cc_binary( name = "example2", srcs = ["examples/example2.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -149,8 +173,8 @@ cc_binary( name = "example3", srcs = ["examples/example3.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -158,8 +182,8 @@ cc_binary( name = "example4", srcs = ["examples/example4.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -167,8 +191,8 @@ cc_binary( name = "hello", srcs = ["examples/hello.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) @@ -178,7 +202,7 @@ cc_binary( name = "time_tool", srcs = ["src/time_tool.cc"], deps = [ - ":cctz_civil_time", - ":cctz_time_zone", + ":civil_time", + ":time_zone", ], ) diff --git a/src/cctz.h b/src/cctz.h new file mode 100644 index 00000000..3d5ff240 --- /dev/null +++ b/src/cctz.h @@ -0,0 +1,154 @@ +// 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_H_ +#define CCTZ_H_ + +#include +#include +#include + +#include "civil_time.h" +#include "time_zone.h" + +namespace cctz { +inline namespace deprecated_v1_api { +// DEPRECATED: Will be deleted on 2016-10-01. +// +// This header defines the CCTZ v1 API, as it was when originally announced in +// September 2015. The v2 API is declared in two headers: include/civil_time.h +// and include/time_zone.h. For help migrating to the v2 API, see +// https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface +#if CCTZ_ACK_V1_DEPRECATION != 1 +#pragma message \ + "This CCTZ v1 API is depreated and will be deleted 2016-10-01. See " \ + "https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface " \ + "for details about migrating to the v2 API. " \ + "To disable this warning, define CCTZ_ACK_V1_DEPRECATION=1" +#endif + +using TimeZone = ::cctz::time_zone; +inline TimeZone UTCTimeZone() { return utc_time_zone(); } +inline TimeZone LocalTimeZone() { return local_time_zone(); } +inline bool LoadTimeZone(const std::string& s, TimeZone* tz) { + return load_time_zone(s, tz); +} + +struct Breakdown { + int64_t year; + int month; + int day; + int hour; + int minute; + int second; + int weekday; + int yearday; + int offset; + bool is_dst; + std::string abbr; +}; +template +inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { + // Warn if the time point might require a civil-year value that is greater + // than a signed 32-bit value can hold. Note that + // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 + assert(std::chrono::system_clock::to_time_t(tp) < 67767976233446399); + const auto al = tz.lookup(tp); + const auto cs = al.cs; + const auto yd = get_yearday(civil_day(cs)); + int wd{}; + switch (get_weekday(civil_day(cs))) { + case weekday::monday: + wd = 1; + break; + case weekday::tuesday: + wd = 2; + break; + case weekday::wednesday: + wd = 3; + break; + case weekday::thursday: + wd = 4; + break; + case weekday::friday: + wd = 5; + break; + case weekday::saturday: + wd = 6; + break; + case weekday::sunday: + wd = 7; + break; + } + return {cs.year(), cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second(), + wd, yd, al.offset, al.is_dst, al.abbr}; +} + +using seconds64 = std::chrono::duration; +inline time_point MakeTime(int64_t year, int mon, int day, int hour, + int min, int sec, const TimeZone& tz) { + assert(year < std::numeric_limits::max()); + return tz.lookup(civil_second(year, mon, day, hour, min, sec)).pre; +} + +struct TimeInfo { + enum class Kind { + UNIQUE, + SKIPPED, + REPEATED, + } kind; + time_point pre; + time_point trans; + time_point post; + bool normalized; +}; +inline TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss, + const TimeZone& tz) { + assert(y < std::numeric_limits::max()); + const civil_second cs(y, m, d, hh, mm, ss); + const bool norm = cs.year() != y || cs.month() != m || cs.day() != d || + cs.hour() != hh || cs.minute() != mm || cs.second() != ss; + const auto cl = tz.lookup(cs); + TimeInfo::Kind kind; + switch (cl.kind) { + case time_zone::civil_lookup::civil_kind::UNIQUE: + kind = TimeInfo::Kind::UNIQUE; + break; + case time_zone::civil_lookup::civil_kind::SKIPPED: + kind = TimeInfo::Kind::SKIPPED; + break; + case time_zone::civil_lookup::civil_kind::REPEATED: + kind = TimeInfo::Kind::REPEATED; + break; + } + return {kind, cl.pre, cl.trans, cl.post, norm}; +} + +template +inline std::string Format(const std::string& fmt, const time_point& tp, + const TimeZone& tz) { + return format(fmt, tp, tz); +} + +template +inline bool Parse(const std::string& fmt, const std::string& input, + const TimeZone& tz, time_point* tpp) { + return parse(fmt, input, tz, tpp); +} + +} // namespace deprecated_v1_api +} // namespace cctz + +#endif // CCTZ_H_ diff --git a/src/cctz_v1_test.cc b/src/cctz_v1_test.cc new file mode 100644 index 00000000..f9036931 --- /dev/null +++ b/src/cctz_v1_test.cc @@ -0,0 +1,1130 @@ +// 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 +#include +#include +#include +#include + +#include "gtest/gtest.h" + +using std::chrono::system_clock; + +namespace cctz { + +namespace { + +// A list of known time-zone names. +const char* const kTimeZoneNames[] = { + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Wayne", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Rosario", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/South_Pole", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar", + "Asia/Ulan_Bator", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/LHI", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/NSW", + "Australia/North", + "Australia/Perth", + "Australia/Queensland", + "Australia/South", + "Australia/Sydney", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "CET", + "CST6CDT", + "Canada/Atlantic", + "Canada/Central", + "Canada/East-Saskatchewan", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "Chile/Continental", + "Chile/EasterIsland", + "Cuba", + "EET", + "EST", + "EST5EDT", + "Egypt", + "Eire", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "HST", + "Hongkong", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "MST", + "MST7MDT", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "NZ", + "NZ-CHAT", + "Navajo", + "PRC", + "PST8PDT", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROC", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Pacific-New", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu", + nullptr +}; + +// Helper to return a loaded time zone by value (UTC on error). +TimeZone LoadZone(const std::string& name) { + TimeZone tz; + LoadTimeZone(name, &tz); + return tz; +} + +// This helper is a macro so that failed expectations show up with the +// 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(off, bd.offset); \ + EXPECT_EQ(isdst, bd.is_dst); \ + EXPECT_EQ(zone, bd.abbr); \ + } while (0) + +} // namespace + +TEST(TimeZones, LoadZonesConcurrently) { + std::promise ready_promise; + std::shared_future ready_future(ready_promise.get_future()); + auto load_zones = [ready_future](std::promise* started) { + started->set_value(); + ready_future.wait(); + TimeZone tz; + for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) { + EXPECT_TRUE(LoadTimeZone(*np, &tz)); + } + }; + + std::vector threads; + for (size_t i = 0; i != 256; ++i) { + std::promise started; + threads.emplace_back(load_zones, &started); + started.get_future().wait(); + } + + ready_promise.set_value(); + + for (auto& thread : threads) { + thread.join(); + } +} + +TEST(TimeZone, Failures) { + TimeZone tz; + EXPECT_FALSE(LoadTimeZone(":America/Los_Angeles", &tz)); + + tz = LoadZone("America/Los_Angeles"); + EXPECT_FALSE(LoadTimeZone("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_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_EQ(system_clock::from_time_t(0), + MakeTime(1970, 1, 1, 0, 0, 0, tz)); // UTC +} + +TEST(StdChronoTimePoint, TimeTAlignment) { + // Ensures that the Unix epoch and the system clock epoch are an integral + // number of seconds apart. This simplifies conversions to/from time_t. + using TP = std::chrono::system_clock::time_point; + auto diff = TP() - std::chrono::system_clock::from_time_t(0); + EXPECT_EQ(TP::duration::zero(), diff % std::chrono::seconds(1)); +} + +TEST(BreakTime, TimePointResolution) { + using std::chrono::time_point_cast; + const TimeZone utc = UTCTimeZone(); + const auto t0 = system_clock::from_time_t(0); + Breakdown bd{}; + + bd = BreakTime(time_point_cast(t0), utc); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + bd = BreakTime(time_point_cast(t0), utc); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + bd = BreakTime(time_point_cast(t0), utc); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + bd = BreakTime(time_point_cast(t0), utc); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + bd = BreakTime(time_point_cast(t0), utc); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + bd = BreakTime(time_point_cast(t0), utc); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + bd = BreakTime(time_point_cast(t0), utc); + 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()); + ExpectTime(bd, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); + EXPECT_EQ(4, bd.weekday); // Thursday +} + +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()); + ExpectTime(bd, 1986, 12, 31, 23, 59, 59, 0, false, "UTC"); + EXPECT_EQ(3, bd.weekday); // Wednesday +} + +TEST(BreakTime, LocalTimeInNewYork) { + const TimeZone tz = LoadZone("America/New_York"); + const Breakdown bd = BreakTime(system_clock::from_time_t(45), tz); + ExpectTime(bd, 1969, 12, 31, 19, 0, 45, -5 * 60 * 60, false, "EST"); + EXPECT_EQ(3, bd.weekday); // Wednesday +} + +TEST(BreakTime, LocalTimeInMTV) { + const TimeZone tz = LoadZone("America/Los_Angeles"); + const Breakdown bd = BreakTime(system_clock::from_time_t(1380855729), tz); + ExpectTime(bd, 2013, 10, 3, 20, 2, 9, -7 * 60 * 60, true, "PDT"); + EXPECT_EQ(4, bd.weekday); // Thursday +} + +TEST(BreakTime, LocalTimeInSydney) { + const TimeZone tz = LoadZone("Australia/Sydney"); + const Breakdown bd = BreakTime(system_clock::from_time_t(90), tz); + ExpectTime(bd, 1970, 1, 1, 10, 1, 30, 10 * 60 * 60, false, "AEST"); + EXPECT_EQ(4, bd.weekday); // Thursday +} + +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)); + + // These next two require time_point_cast because the conversion from a + // resolution of seconds (the return value of MakeTime()) to a coarser + // resolution requires an explicit cast. + 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)); + 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)); +} + +TEST(MakeTime, Normalization) { + const TimeZone 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); + + // Now requests for the same time_point but with out-of-range fields. + EXPECT_EQ(tp, MakeTime(2008, 14, 13, 18, 31, 30, tz)); // month + EXPECT_EQ(tp, MakeTime(2009, 1, 44, 18, 31, 30, tz)); // day + EXPECT_EQ(tp, MakeTime(2009, 2, 12, 42, 31, 30, tz)); // hour + EXPECT_EQ(tp, MakeTime(2009, 2, 13, 17, 91, 30, tz)); // minute + EXPECT_EQ(tp, MakeTime(2009, 2, 13, 18, 30, 90, tz)); // second +} + +TEST(TimeZoneEdgeCase, AmericaNewYork) { + const TimeZone 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); + ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST"); +} + +TEST(TimeZoneEdgeCase, AmericaLosAngeles) { + const TimeZone 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); + ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST"); +} + +TEST(TimeZoneEdgeCase, ArizonaNoTransition) { + const TimeZone tz = LoadZone("America/Phoenix"); + + // No transition in Spring. + auto tp = MakeTime(2013, 3, 10, 1, 59, 59, tz); + Breakdown bd = BreakTime(tp, tz); + ExpectTime(bd, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); +} + +TEST(TimeZoneEdgeCase, AsiaKathmandu) { + const TimeZone 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); + ExpectTime(bd, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "IST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "NPT"); +} + +TEST(TimeZoneEdgeCase, PacificChatham) { + const TimeZone 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); + ExpectTime(bd, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "CHADT"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, "CHAST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "CHADT"); +} + +TEST(TimeZoneEdgeCase, AustraliaLordHowe) { + const TimeZone 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); + ExpectTime(bd, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "LHDT"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "LHST"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "LHDT"); +} + +TEST(TimeZoneEdgeCase, PacificApia) { + const TimeZone 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 + // moved to the west of the International Date Line. + // + // A one-day, non-DST offset change + // + // 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); + ExpectTime(bd, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "SDT"); + EXPECT_EQ(363, bd.yearday); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "WSDT"); + EXPECT_EQ(365, bd.yearday); // What else could 2011/12/31 be!? +} + +TEST(TimeZoneEdgeCase, AfricaCairo) { + const TimeZone 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); + ExpectTime(bd, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); +} + +TEST(TimeZoneEdgeCase, AfricaMonrovia) { + const TimeZone 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); + ExpectTime(bd, 1972, 4, 30, 23, 59, 59, -44.5 * 60, false, "LRT"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 1972, 5, 1, 0, 44, 30, 0 * 60, false, "GMT"); +} + +TEST(TimeZoneEdgeCase, AmericaJamaica) { + // Jamaica discontinued DST transitions in 1983, and is now at a + // constant -0500. This makes it an interesting edge-case target. + // 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"); + + // Before the first transition. + auto tp = MakeTime(1889, 12, 31, 0, 0, 0, tz); + Breakdown bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 1889, 12, 31, 23, 59, 59, -18431, false, bd.abbr); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + 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"); + + // Before the first transition. + auto tp = MakeTime(1977, 1, 1, 0, 0, 0, tz); + Breakdown bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 1977, 4, 3, 0, 59, 59, 0, false, "WET"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + 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); + ExpectTime(bd, 1977, 4, 3, 2, 15, 0, 1 * 3600, true, "WEST"); + bd = BreakTime(ti1.trans, tz); + ExpectTime(bd, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); + bd = BreakTime(ti1.post, tz); + 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); + ExpectTime(bd, 1978, 4, 2, 2, 15, 0, 1 * 3600, true, "WEST"); + bd = BreakTime(ti2.trans, tz); + ExpectTime(bd, 1978, 4, 2, 2, 0, 0, 1 * 3600, true, "WEST"); + bd = BreakTime(ti2.post, tz); + ExpectTime(bd, 1978, 4, 2, 0, 15, 0, 0 * 3600, false, "WET"); +} + +TEST(TimeZoneEdgeCase, FixedOffsets) { + const TimeZone gmtm5 = LoadZone("Etc/GMT+5"); // -0500 + auto tp = MakeTime(1970, 1, 1, 0, 0, 0, gmtm5); + Breakdown bd = BreakTime(tp, gmtm5); + 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 + tp = MakeTime(1970, 1, 1, 0, 0, 0, gmtp5); + bd = BreakTime(tp, gmtp5); + 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(); + auto tp = MakeTime(0, 1, 1, 0, 0, 0, tz); + Breakdown bd = BreakTime(tp, tz); + ExpectTime(bd, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); + EXPECT_EQ(6, bd.weekday); + tp -= std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); + EXPECT_EQ(5, bd.weekday); +} + +TEST(TimeZoneEdgeCase, UTC32bitLimit) { + const TimeZone tz = UTCTimeZone(); + + // 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); + ExpectTime(bd, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); +} + +TEST(TimeZoneEdgeCase, UTC5DigitYear) { + const TimeZone tz = UTCTimeZone(); + + // 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); + ExpectTime(bd, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); + tp += std::chrono::seconds(1); + bd = BreakTime(tp, tz); + ExpectTime(bd, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); +} + +// These tests are disabled because they require 64-bit civil years. +#if 0 +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()); +} +#endif + +} // namespace cctz From 758ab047911f49d1a09478013b6c0ed397aab155 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Thu, 24 Mar 2016 17:14:46 -0400 Subject: [PATCH 36/69] Avoid integer overflow when checking for extreme time_point values. --- src/cctz.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cctz.h b/src/cctz.h index 3d5ff240..8b79cb86 100644 --- a/src/cctz.h +++ b/src/cctz.h @@ -61,10 +61,19 @@ struct Breakdown { }; template inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { - // Warn if the time point might require a civil-year value that is greater - // than a signed 32-bit value can hold. Note that - // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 - assert(std::chrono::system_clock::to_time_t(tp) < 67767976233446399); + // Assert that the time point does not require a civil year beyond + // the limits of a signed 32-bit value, as dictated by the v2 API. + // -67768100567884800 == -2147483648-01-02 00:00:00 +00:00 + // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 + assert(std::chrono::time_point_cast(tp) >= + std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(-67768100567884800)); + assert(std::chrono::time_point_cast(tp) <= + std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(67767976233446399)); + const auto al = tz.lookup(tp); const auto cs = al.cs; const auto yd = get_yearday(civil_day(cs)); From 76e3cce245449efe90b7e6396d3be0e057e91e9d Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 25 Mar 2016 13:47:42 -0400 Subject: [PATCH 37/69] Move cctz.h to the include directory. --- BUILD | 2 +- include/cctz.h | 162 ++++++++++++++++++++++++++++++++++++++++++++ src/cctz_v1_test.cc | 2 +- 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 include/cctz.h diff --git a/BUILD b/BUILD index 5d9cdc3c..60aceb2b 100644 --- a/BUILD +++ b/BUILD @@ -56,7 +56,7 @@ cc_library( cc_library( name = "cctz_v1", hdrs = [ - "src/cctz.h", + "include/cctz.h", ], includes = ["include"], visibility = ["//visibility:public"], diff --git a/include/cctz.h b/include/cctz.h new file mode 100644 index 00000000..650a1fe7 --- /dev/null +++ b/include/cctz.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. + +#ifndef CCTZ_H_ +#define CCTZ_H_ + +#include +#include +#include + +#include "civil_time.h" +#include "time_zone.h" + +namespace cctz { +inline namespace deprecated_v1_api { +// DEPRECATED: Will be deleted on 2016-10-01. +// +// This header defines the CCTZ v1 API, as it was when originally announced in +// September 2015. The v2 API is declared in two headers: include/civil_time.h +// and include/time_zone.h. For help migrating to the v2 API, see +// https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface +#if CCTZ_ACK_V1_DEPRECATION != 1 +#pragma message \ + "This CCTZ v1 API is depreated and will be deleted 2016-10-01. See " \ + "https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface " \ + "for details about migrating to the v2 API. " \ + "To disable this warning, define CCTZ_ACK_V1_DEPRECATION=1" +#endif + +using TimeZone = ::cctz::time_zone; +inline TimeZone UTCTimeZone() { return utc_time_zone(); } +inline TimeZone LocalTimeZone() { return local_time_zone(); } +inline bool LoadTimeZone(const std::string& s, TimeZone* tz) { + return load_time_zone(s, tz); +} + +struct Breakdown { + int64_t year; + int month; + int day; + int hour; + int minute; + int second; + int weekday; + int yearday; + int offset; + bool is_dst; + std::string abbr; +}; +template +inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { + // Assert that the time point does not require a civil year beyond + // the limits of a signed 32-bit value, as dictated by the v2 API. + // -67768100567884800 == -2147483648-01-02 00:00:00 +00:00 + // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 + assert(std::chrono::time_point_cast(tp) >= + std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(-67768100567884800)); + assert(std::chrono::time_point_cast(tp) <= + std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(67767976233446399)); + + const auto al = tz.lookup(tp); + const auto cs = al.cs; + const auto yd = get_yearday(civil_day(cs)); + int wd{}; + switch (get_weekday(civil_day(cs))) { + case weekday::monday: + wd = 1; + break; + case weekday::tuesday: + wd = 2; + break; + case weekday::wednesday: + wd = 3; + break; + case weekday::thursday: + wd = 4; + break; + case weekday::friday: + wd = 5; + break; + case weekday::saturday: + wd = 6; + break; + case weekday::sunday: + wd = 7; + break; + } + return {cs.year(), cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second(), + wd, yd, al.offset, al.is_dst, al.abbr}; +} + +using seconds64 = std::chrono::duration; +inline time_point MakeTime(int64_t year, int mon, int day, int hour, + int min, int sec, const TimeZone& tz) { + assert(year < std::numeric_limits::max()); + return tz.lookup(civil_second(year, mon, day, hour, min, sec)).pre; +} + +struct TimeInfo { + enum class Kind { + UNIQUE, + SKIPPED, + REPEATED, + } kind; + time_point pre; + time_point trans; + time_point post; + bool normalized; +}; +inline TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss, + const TimeZone& tz) { + assert(y < std::numeric_limits::max()); + const civil_second cs(y, m, d, hh, mm, ss); + const bool norm = cs.year() != y || cs.month() != m || cs.day() != d || + cs.hour() != hh || cs.minute() != mm || cs.second() != ss; + const auto cl = tz.lookup(cs); + TimeInfo::Kind kind; + switch (cl.kind) { + case time_zone::civil_lookup::civil_kind::UNIQUE: + kind = TimeInfo::Kind::UNIQUE; + break; + case time_zone::civil_lookup::civil_kind::SKIPPED: + kind = TimeInfo::Kind::SKIPPED; + break; + case time_zone::civil_lookup::civil_kind::REPEATED: + kind = TimeInfo::Kind::REPEATED; + break; + } + return {kind, cl.pre, cl.trans, cl.post, norm}; +} + +template +inline std::string Format(const std::string& fmt, const time_point& tp, + const TimeZone& tz) { + return format(fmt, tp, tz); +} + +template +inline bool Parse(const std::string& fmt, const std::string& input, + const TimeZone& tz, time_point* tpp) { + return parse(fmt, input, tz, tpp); +} + +} // namespace deprecated_v1_api +} // namespace cctz + +#endif // CCTZ_H_ diff --git a/src/cctz_v1_test.cc b/src/cctz_v1_test.cc index f9036931..57401281 100644 --- a/src/cctz_v1_test.cc +++ b/src/cctz_v1_test.cc @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "src/cctz.h" +#include "cctz.h" #include #include From e4ae4e4267be105d8210a8bda1f53c3a6df92123 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 25 Mar 2016 13:49:29 -0400 Subject: [PATCH 38/69] Remove cctz.h from src directory. --- src/cctz.h | 163 ----------------------------------------------------- 1 file changed, 163 deletions(-) delete mode 100644 src/cctz.h diff --git a/src/cctz.h b/src/cctz.h deleted file mode 100644 index 8b79cb86..00000000 --- a/src/cctz.h +++ /dev/null @@ -1,163 +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_H_ -#define CCTZ_H_ - -#include -#include -#include - -#include "civil_time.h" -#include "time_zone.h" - -namespace cctz { -inline namespace deprecated_v1_api { -// DEPRECATED: Will be deleted on 2016-10-01. -// -// This header defines the CCTZ v1 API, as it was when originally announced in -// September 2015. The v2 API is declared in two headers: include/civil_time.h -// and include/time_zone.h. For help migrating to the v2 API, see -// https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface -#if CCTZ_ACK_V1_DEPRECATION != 1 -#pragma message \ - "This CCTZ v1 API is depreated and will be deleted 2016-10-01. See " \ - "https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface " \ - "for details about migrating to the v2 API. " \ - "To disable this warning, define CCTZ_ACK_V1_DEPRECATION=1" -#endif - -using TimeZone = ::cctz::time_zone; -inline TimeZone UTCTimeZone() { return utc_time_zone(); } -inline TimeZone LocalTimeZone() { return local_time_zone(); } -inline bool LoadTimeZone(const std::string& s, TimeZone* tz) { - return load_time_zone(s, tz); -} - -struct Breakdown { - int64_t year; - int month; - int day; - int hour; - int minute; - int second; - int weekday; - int yearday; - int offset; - bool is_dst; - std::string abbr; -}; -template -inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { - // Assert that the time point does not require a civil year beyond - // the limits of a signed 32-bit value, as dictated by the v2 API. - // -67768100567884800 == -2147483648-01-02 00:00:00 +00:00 - // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 - assert(std::chrono::time_point_cast(tp) >= - std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(0)) + - sys_seconds(-67768100567884800)); - assert(std::chrono::time_point_cast(tp) <= - std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(0)) + - sys_seconds(67767976233446399)); - - const auto al = tz.lookup(tp); - const auto cs = al.cs; - const auto yd = get_yearday(civil_day(cs)); - int wd{}; - switch (get_weekday(civil_day(cs))) { - case weekday::monday: - wd = 1; - break; - case weekday::tuesday: - wd = 2; - break; - case weekday::wednesday: - wd = 3; - break; - case weekday::thursday: - wd = 4; - break; - case weekday::friday: - wd = 5; - break; - case weekday::saturday: - wd = 6; - break; - case weekday::sunday: - wd = 7; - break; - } - return {cs.year(), cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second(), - wd, yd, al.offset, al.is_dst, al.abbr}; -} - -using seconds64 = std::chrono::duration; -inline time_point MakeTime(int64_t year, int mon, int day, int hour, - int min, int sec, const TimeZone& tz) { - assert(year < std::numeric_limits::max()); - return tz.lookup(civil_second(year, mon, day, hour, min, sec)).pre; -} - -struct TimeInfo { - enum class Kind { - UNIQUE, - SKIPPED, - REPEATED, - } kind; - time_point pre; - time_point trans; - time_point post; - bool normalized; -}; -inline TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss, - const TimeZone& tz) { - assert(y < std::numeric_limits::max()); - const civil_second cs(y, m, d, hh, mm, ss); - const bool norm = cs.year() != y || cs.month() != m || cs.day() != d || - cs.hour() != hh || cs.minute() != mm || cs.second() != ss; - const auto cl = tz.lookup(cs); - TimeInfo::Kind kind; - switch (cl.kind) { - case time_zone::civil_lookup::civil_kind::UNIQUE: - kind = TimeInfo::Kind::UNIQUE; - break; - case time_zone::civil_lookup::civil_kind::SKIPPED: - kind = TimeInfo::Kind::SKIPPED; - break; - case time_zone::civil_lookup::civil_kind::REPEATED: - kind = TimeInfo::Kind::REPEATED; - break; - } - return {kind, cl.pre, cl.trans, cl.post, norm}; -} - -template -inline std::string Format(const std::string& fmt, const time_point& tp, - const TimeZone& tz) { - return format(fmt, tp, tz); -} - -template -inline bool Parse(const std::string& fmt, const std::string& input, - const TimeZone& tz, time_point* tpp) { - return parse(fmt, input, tz, tpp); -} - -} // namespace deprecated_v1_api -} // namespace cctz - -#endif // CCTZ_H_ From 77ea1246a8cd969e15be087b3751a38792abb3a6 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 29 Mar 2016 13:22:39 -0400 Subject: [PATCH 39/69] Begin to restrict constexpr support to C++14 compilers, where normalization can be expressed more naturally. --- include/civil_time_detail.h | 209 ++++++++++++++++++++---------------- src/civil_time_test.cc | 2 + 2 files changed, 121 insertions(+), 90 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 63fd5ffa..152aa45e 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -16,6 +16,11 @@ #include #include +// Disable constexpr support unless we are using clang in C++14 mode. +#if !__clang__ || __cpp_constexpr < 201304 +#define constexpr +#endif + namespace cctz { namespace detail { @@ -43,14 +48,14 @@ struct second_tag {}; namespace impl { // The month lengths in non-leap and leap years respectively. -constexpr signed char k_dpm[2][12] = { +constexpr const 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] = { +constexpr const 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, @@ -72,7 +77,7 @@ constexpr signed char k_dpC[400] = { // 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[400] = { +constexpr const signed char k_dp4y[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -92,104 +97,125 @@ constexpr signed char k_dp4y[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; -constexpr bool is_leap(int y) { +inline constexpr bool is_leap(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } -constexpr int dpm(int y, int m) { +inline 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) { +inline 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; } +inline constexpr int dpC(int y, int m) { + return 36524 + k_dpC[year_index(y, m)]; +} +inline constexpr int dp4y(int y, int m) { + return 1460 + k_dp4y[year_index(y, m)]; +} +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline 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); } @@ -201,19 +227,19 @@ 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]. -constexpr int doy(int m, int d) { +inline constexpr int doy(int m, int d) { return (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; } -constexpr int doe(int yoe, int m, int d) { +inline constexpr int doe(int yoe, int m, int d) { return yoe * 365 + yoe / 4 - yoe / 100 + doy(m, d); } -constexpr int era_eymd_ord(int era, int eyear, int m, int 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; } -constexpr int eymd_ord(int eyear, int m, int d) { +inline constexpr int eymd_ord(int eyear, int m, int d) { return era_eymd_ord((eyear >= 0 ? eyear : eyear - 399) / 400, eyear, m, d); } -constexpr int ymd_ord(int y, int m, int d) { +inline constexpr int ymd_ord(int y, int m, int d) { return eymd_ord(m <= 2 ? y - 1 : y, m, d); } @@ -222,66 +248,66 @@ constexpr int ymd_ord(int y, int m, int d) { //////////////////////////////////////////////////////////////////////// // Aligns the (normalized) fields struct to the indicated field. -constexpr fields align(second_tag, fields f) { +inline constexpr fields align(second_tag, fields f) { return f; } -constexpr fields align(minute_tag, fields f) { +inline constexpr fields align(minute_tag, fields f) { return fields{f.y, f.m, f.d, f.hh, f.mm, 0}; } -constexpr fields align(hour_tag, fields f) { +inline constexpr fields align(hour_tag, fields f) { return fields{f.y, f.m, f.d, f.hh, 0, 0}; } -constexpr fields align(day_tag, fields f) { +inline constexpr fields align(day_tag, fields f) { return fields{f.y, f.m, f.d, 0, 0, 0}; } -constexpr fields align(month_tag, fields f) { +inline constexpr fields align(month_tag, fields f) { return fields{f.y, f.m, 1, 0, 0, 0}; } -constexpr fields align(year_tag, fields f) { +inline constexpr fields align(year_tag, fields f) { return fields{f.y, 1, 1, 0, 0, 0}; } //////////////////////////////////////////////////////////////////////// // Increments the indicated (normalized) field by "n". -constexpr fields step(second_tag, fields f, int 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); } -constexpr fields step(minute_tag, fields f, int n) { +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); } -constexpr fields step(hour_tag, fields f, int n) { +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); } -constexpr fields step(day_tag, fields f, int n) { +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); } -constexpr fields step(month_tag, fields f, int n) { +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); } -constexpr fields step(year_tag, fields f, int n) { +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) { +inline constexpr int difference(year_tag, fields f1, fields f2) { return f1.y - f2.y; } -constexpr int difference(month_tag, fields f1, fields f2) { +inline 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) { +inline 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) { +inline 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) { +inline 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) { +inline constexpr int difference(second_tag, fields f1, fields f2) { return difference(minute_tag{}, f1, f2) * 60 + (f1.ss - f2.ss); } @@ -376,35 +402,6 @@ using civil_second = civil_time; //////////////////////////////////////////////////////////////////////// -// Output stream operators output a format matching YYYY-MM-DDThh:mm:ss, -// while omitting fields inferior to the type's alignment. For example, -// civil_day is formatted only as YYYY-MM-DD. -inline std::ostream& operator<<(std::ostream& os, civil_year y) { - return os << y.year(); // No padding. -} -inline std::ostream& operator<<(std::ostream& os, civil_month m) { - return os << civil_year(m) << '-' << std::setfill('0') << std::setw(2) - << m.month(); -} -inline std::ostream& operator<<(std::ostream& os, civil_day d) { - return os << civil_month(d) << '-' << std::setfill('0') << std::setw(2) - << d.day(); -} -inline std::ostream& operator<<(std::ostream& os, civil_hour h) { - return os << civil_day(h) << 'T' << std::setfill('0') << std::setw(2) - << h.hour(); -} -inline std::ostream& operator<<(std::ostream& os, civil_minute m) { - return os << civil_hour(m) << ':' << std::setfill('0') << std::setw(2) - << m.minute(); -} -inline std::ostream& operator<<(std::ostream& os, civil_second s) { - return os << civil_minute(s) << ':' << std::setfill('0') << std::setw(2) - << s.second(); -} - -//////////////////////////////////////////////////////////////////////// - // Relational operators that work with differently aligned objects. // Always compares all six fields. template @@ -452,6 +449,35 @@ constexpr bool operator!=(const civil_time& lhs, //////////////////////////////////////////////////////////////////////// +// Output stream operators output a format matching YYYY-MM-DDThh:mm:ss, +// while omitting fields inferior to the type's alignment. For example, +// civil_day is formatted only as YYYY-MM-DD. +inline std::ostream& operator<<(std::ostream& os, civil_year y) { + return os << y.year(); // No padding. +} +inline std::ostream& operator<<(std::ostream& os, civil_month m) { + return os << civil_year(m) << '-' << std::setfill('0') << std::setw(2) + << m.month(); +} +inline std::ostream& operator<<(std::ostream& os, civil_day d) { + return os << civil_month(d) << '-' << std::setfill('0') << std::setw(2) + << d.day(); +} +inline std::ostream& operator<<(std::ostream& os, civil_hour h) { + return os << civil_day(h) << 'T' << std::setfill('0') << std::setw(2) + << h.hour(); +} +inline std::ostream& operator<<(std::ostream& os, civil_minute m) { + return os << civil_hour(m) << ':' << std::setfill('0') << std::setw(2) + << m.minute(); +} +inline std::ostream& operator<<(std::ostream& os, civil_second s) { + return os << civil_minute(s) << ':' << std::setfill('0') << std::setw(2) + << s.second(); +} + +//////////////////////////////////////////////////////////////////////// + enum class weekday { monday, tuesday, @@ -463,14 +489,14 @@ enum class weekday { }; namespace impl { -constexpr weekday k_weekday_by_thu_off[] = { +constexpr const 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) { +inline constexpr weekday get_weekday(const civil_day& cd) { return impl::k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; } @@ -496,24 +522,27 @@ inline std::ostream& operator<<(std::ostream& os, weekday wd) { //////////////////////////////////////////////////////////////////////// namespace impl { -constexpr civil_day scan_weekday(const civil_day& cd, weekday wd, int incr) { +inline 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) { +inline 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) { +inline 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) { +inline constexpr int get_yearday(const civil_day& cd) { return cd - civil_day(cd.year(), 1, 0); } } // namespace detail } // namespace cctz + +#undef constexpr diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index 55e14008..bf1b353f 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -33,6 +33,7 @@ std::string Format(const T& t) { } // namespace +#if __clang__ && __cpp_constexpr >= 201304 // Construction constexpr tests TEST(CivilTime, Normal) { @@ -256,6 +257,7 @@ TEST(CivilTime, YearDay) { constexpr int yd = get_yearday(cd); static_assert(yd == 28, "YearDay"); } +#endif // __clang__ && __cpp_constexpr >= 201304 // The remaining tests do not use constexpr. From 01106351a261d354e4e1fb9fbe5782d71817effa Mon Sep 17 00:00:00 2001 From: Bradley White Date: Wed, 30 Mar 2016 13:01:53 -0400 Subject: [PATCH 40/69] Adds implicit cross-alignment conversions to civil-time types when they are safe. --- examples/example3.cc | 3 +- examples/example4.cc | 3 +- include/civil_time_detail.h | 26 +++++++++++---- src/civil_time_test.cc | 66 +++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/examples/example3.cc b/examples/example3.cc index 22d88901..216acca4 100644 --- a/examples/example3.cc +++ b/examples/example3.cc @@ -26,8 +26,7 @@ int main() { const cctz::civil_second cs = cctz::convert(now, lax); // First day of month, 6 months from now. - const auto then = cctz::convert( - cctz::civil_second(cs.year(), cs.month() + 6, 1, 0, 0, 0), lax); + const auto then = cctz::convert(cctz::civil_month(cs) + 6, 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 815ce5a8..7e43f727 100644 --- a/examples/example4.cc +++ b/examples/example4.cc @@ -21,8 +21,7 @@ template cctz::time_point FloorDay(cctz::time_point tp, cctz::time_zone tz) { - const cctz::civil_day cd(cctz::convert(tp, tz)); - return cctz::convert(cctz::civil_second(cd), tz); + return cctz::convert(cctz::civil_day(cctz::convert(tp, tz)), tz); } int main() { diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 152aa45e..98c8d9d7 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -15,6 +15,7 @@ #include #include #include +#include // Disable constexpr support unless we are using clang in C++14 mode. #if !__clang__ || __cpp_constexpr < 201304 @@ -34,12 +35,12 @@ struct fields { int ss; }; -struct year_tag {}; -struct month_tag {}; -struct day_tag {}; -struct hour_tag {}; -struct minute_tag {}; struct second_tag {}; +struct minute_tag : second_tag {}; +struct hour_tag : minute_tag {}; +struct day_tag : hour_tag {}; +struct month_tag : day_tag {}; +struct year_tag : month_tag {}; //////////////////////////////////////////////////////////////////////// @@ -324,9 +325,20 @@ class civil_time { constexpr civil_time(const civil_time&) = default; civil_time& operator=(const civil_time&) = default; - // Explicit conversion between civil times of different alignment. + // Conversion between civil times of different alignment. Conversion to + // a more precise alignment is allowed implicitly (e.g., day -> hour), + // but conversion where information is discarded must be explicit + // (e.g., second -> minute). + template + using preserves_data = + typename std::enable_if::value>::type; template - explicit constexpr civil_time(civil_time ct) : civil_time(ct.f_) {} + civil_time(const civil_time& ct, preserves_data* = nullptr) + : civil_time(ct.f_) {} + template + explicit constexpr civil_time(const civil_time& ct, + preserves_data* = nullptr) + : civil_time(ct.f_) {} // Field accessors. constexpr int year() const { return f_.y; } diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index bf1b353f..00446427 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include "gtest/gtest.h" @@ -361,6 +362,71 @@ TEST(CivilTime, FieldsConstructionLimits) { Format(civil_second(1970, kIntMin, kIntMin, kIntMin, kIntMin, kIntMin))); } +TEST(CivilTime, ImplicitCrossAlignment) { + civil_year year(2015); + civil_month month = year; + civil_day day = month; + civil_hour hour = day; + civil_minute minute = hour; + civil_second second = minute; + + second = year; + EXPECT_EQ(second, year); + second = month; + EXPECT_EQ(second, month); + second = day; + EXPECT_EQ(second, day); + second = hour; + EXPECT_EQ(second, hour); + second = minute; + EXPECT_EQ(second, minute); + + minute = year; + EXPECT_EQ(minute, year); + minute = month; + EXPECT_EQ(minute, month); + minute = day; + EXPECT_EQ(minute, day); + minute = hour; + EXPECT_EQ(minute, hour); + + hour = year; + EXPECT_EQ(hour, year); + hour = month; + EXPECT_EQ(hour, month); + hour = day; + EXPECT_EQ(hour, day); + + day = year; + EXPECT_EQ(day, year); + day = month; + EXPECT_EQ(day, month); + + month = year; + EXPECT_EQ(month, year); + + // Ensures unsafe conversions are not allowed. + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + + EXPECT_FALSE((std::is_convertible::value)); + EXPECT_FALSE((std::is_convertible::value)); + + EXPECT_FALSE((std::is_convertible::value)); +} + TEST(CivilTime, ExplicitCrossAlignment) { // // Assign from smaller units -> larger units From fb7c1243d8ae056690d047fa3210551fc51bb24e Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 31 Mar 2016 14:18:26 -0400 Subject: [PATCH 41/69] Revert "Move cctz.h to the include directory." This reverts commit 76e3cce245449efe90b7e6396d3be0e057e91e9d. --- BUILD | 2 +- include/cctz.h | 162 -------------------------------------------- src/cctz_v1_test.cc | 2 +- 3 files changed, 2 insertions(+), 164 deletions(-) delete mode 100644 include/cctz.h diff --git a/BUILD b/BUILD index 60aceb2b..5d9cdc3c 100644 --- a/BUILD +++ b/BUILD @@ -56,7 +56,7 @@ cc_library( cc_library( name = "cctz_v1", hdrs = [ - "include/cctz.h", + "src/cctz.h", ], includes = ["include"], visibility = ["//visibility:public"], diff --git a/include/cctz.h b/include/cctz.h deleted file mode 100644 index 650a1fe7..00000000 --- a/include/cctz.h +++ /dev/null @@ -1,162 +0,0 @@ -// 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_H_ -#define CCTZ_H_ - -#include -#include -#include - -#include "civil_time.h" -#include "time_zone.h" - -namespace cctz { -inline namespace deprecated_v1_api { -// DEPRECATED: Will be deleted on 2016-10-01. -// -// This header defines the CCTZ v1 API, as it was when originally announced in -// September 2015. The v2 API is declared in two headers: include/civil_time.h -// and include/time_zone.h. For help migrating to the v2 API, see -// https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface -#if CCTZ_ACK_V1_DEPRECATION != 1 -#pragma message \ - "This CCTZ v1 API is depreated and will be deleted 2016-10-01. See " \ - "https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface " \ - "for details about migrating to the v2 API. " \ - "To disable this warning, define CCTZ_ACK_V1_DEPRECATION=1" -#endif - -using TimeZone = ::cctz::time_zone; -inline TimeZone UTCTimeZone() { return utc_time_zone(); } -inline TimeZone LocalTimeZone() { return local_time_zone(); } -inline bool LoadTimeZone(const std::string& s, TimeZone* tz) { - return load_time_zone(s, tz); -} - -struct Breakdown { - int64_t year; - int month; - int day; - int hour; - int minute; - int second; - int weekday; - int yearday; - int offset; - bool is_dst; - std::string abbr; -}; -template -inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { - // Assert that the time point does not require a civil year beyond - // the limits of a signed 32-bit value, as dictated by the v2 API. - // -67768100567884800 == -2147483648-01-02 00:00:00 +00:00 - // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 - assert(std::chrono::time_point_cast(tp) >= - std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(0)) + - sys_seconds(-67768100567884800)); - assert(std::chrono::time_point_cast(tp) <= - std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(0)) + - sys_seconds(67767976233446399)); - - const auto al = tz.lookup(tp); - const auto cs = al.cs; - const auto yd = get_yearday(civil_day(cs)); - int wd{}; - switch (get_weekday(civil_day(cs))) { - case weekday::monday: - wd = 1; - break; - case weekday::tuesday: - wd = 2; - break; - case weekday::wednesday: - wd = 3; - break; - case weekday::thursday: - wd = 4; - break; - case weekday::friday: - wd = 5; - break; - case weekday::saturday: - wd = 6; - break; - case weekday::sunday: - wd = 7; - break; - } - return {cs.year(), cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second(), - wd, yd, al.offset, al.is_dst, al.abbr}; -} - -using seconds64 = std::chrono::duration; -inline time_point MakeTime(int64_t year, int mon, int day, int hour, - int min, int sec, const TimeZone& tz) { - assert(year < std::numeric_limits::max()); - return tz.lookup(civil_second(year, mon, day, hour, min, sec)).pre; -} - -struct TimeInfo { - enum class Kind { - UNIQUE, - SKIPPED, - REPEATED, - } kind; - time_point pre; - time_point trans; - time_point post; - bool normalized; -}; -inline TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss, - const TimeZone& tz) { - assert(y < std::numeric_limits::max()); - const civil_second cs(y, m, d, hh, mm, ss); - const bool norm = cs.year() != y || cs.month() != m || cs.day() != d || - cs.hour() != hh || cs.minute() != mm || cs.second() != ss; - const auto cl = tz.lookup(cs); - TimeInfo::Kind kind; - switch (cl.kind) { - case time_zone::civil_lookup::civil_kind::UNIQUE: - kind = TimeInfo::Kind::UNIQUE; - break; - case time_zone::civil_lookup::civil_kind::SKIPPED: - kind = TimeInfo::Kind::SKIPPED; - break; - case time_zone::civil_lookup::civil_kind::REPEATED: - kind = TimeInfo::Kind::REPEATED; - break; - } - return {kind, cl.pre, cl.trans, cl.post, norm}; -} - -template -inline std::string Format(const std::string& fmt, const time_point& tp, - const TimeZone& tz) { - return format(fmt, tp, tz); -} - -template -inline bool Parse(const std::string& fmt, const std::string& input, - const TimeZone& tz, time_point* tpp) { - return parse(fmt, input, tz, tpp); -} - -} // namespace deprecated_v1_api -} // namespace cctz - -#endif // CCTZ_H_ diff --git a/src/cctz_v1_test.cc b/src/cctz_v1_test.cc index 57401281..f9036931 100644 --- a/src/cctz_v1_test.cc +++ b/src/cctz_v1_test.cc @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "cctz.h" +#include "src/cctz.h" #include #include From 9ec4759774d4db5362af3989f4fc8dd595c52d60 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 31 Mar 2016 14:18:35 -0400 Subject: [PATCH 42/69] Revert "Remove cctz.h from src directory." This reverts commit e4ae4e4267be105d8210a8bda1f53c3a6df92123. --- src/cctz.h | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/cctz.h diff --git a/src/cctz.h b/src/cctz.h new file mode 100644 index 00000000..8b79cb86 --- /dev/null +++ b/src/cctz.h @@ -0,0 +1,163 @@ +// 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_H_ +#define CCTZ_H_ + +#include +#include +#include + +#include "civil_time.h" +#include "time_zone.h" + +namespace cctz { +inline namespace deprecated_v1_api { +// DEPRECATED: Will be deleted on 2016-10-01. +// +// This header defines the CCTZ v1 API, as it was when originally announced in +// September 2015. The v2 API is declared in two headers: include/civil_time.h +// and include/time_zone.h. For help migrating to the v2 API, see +// https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface +#if CCTZ_ACK_V1_DEPRECATION != 1 +#pragma message \ + "This CCTZ v1 API is depreated and will be deleted 2016-10-01. See " \ + "https://github.com/google/cctz/wiki/Migrating-from-V1-to-V2-interface " \ + "for details about migrating to the v2 API. " \ + "To disable this warning, define CCTZ_ACK_V1_DEPRECATION=1" +#endif + +using TimeZone = ::cctz::time_zone; +inline TimeZone UTCTimeZone() { return utc_time_zone(); } +inline TimeZone LocalTimeZone() { return local_time_zone(); } +inline bool LoadTimeZone(const std::string& s, TimeZone* tz) { + return load_time_zone(s, tz); +} + +struct Breakdown { + int64_t year; + int month; + int day; + int hour; + int minute; + int second; + int weekday; + int yearday; + int offset; + bool is_dst; + std::string abbr; +}; +template +inline Breakdown BreakTime(const time_point& tp, const TimeZone& tz) { + // Assert that the time point does not require a civil year beyond + // the limits of a signed 32-bit value, as dictated by the v2 API. + // -67768100567884800 == -2147483648-01-02 00:00:00 +00:00 + // 67767976233446399 == 2147483647-12-30 23:59:59 +00:00 + assert(std::chrono::time_point_cast(tp) >= + std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(-67768100567884800)); + assert(std::chrono::time_point_cast(tp) <= + std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(0)) + + sys_seconds(67767976233446399)); + + const auto al = tz.lookup(tp); + const auto cs = al.cs; + const auto yd = get_yearday(civil_day(cs)); + int wd{}; + switch (get_weekday(civil_day(cs))) { + case weekday::monday: + wd = 1; + break; + case weekday::tuesday: + wd = 2; + break; + case weekday::wednesday: + wd = 3; + break; + case weekday::thursday: + wd = 4; + break; + case weekday::friday: + wd = 5; + break; + case weekday::saturday: + wd = 6; + break; + case weekday::sunday: + wd = 7; + break; + } + return {cs.year(), cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second(), + wd, yd, al.offset, al.is_dst, al.abbr}; +} + +using seconds64 = std::chrono::duration; +inline time_point MakeTime(int64_t year, int mon, int day, int hour, + int min, int sec, const TimeZone& tz) { + assert(year < std::numeric_limits::max()); + return tz.lookup(civil_second(year, mon, day, hour, min, sec)).pre; +} + +struct TimeInfo { + enum class Kind { + UNIQUE, + SKIPPED, + REPEATED, + } kind; + time_point pre; + time_point trans; + time_point post; + bool normalized; +}; +inline TimeInfo MakeTimeInfo(int64_t y, int m, int d, int hh, int mm, int ss, + const TimeZone& tz) { + assert(y < std::numeric_limits::max()); + const civil_second cs(y, m, d, hh, mm, ss); + const bool norm = cs.year() != y || cs.month() != m || cs.day() != d || + cs.hour() != hh || cs.minute() != mm || cs.second() != ss; + const auto cl = tz.lookup(cs); + TimeInfo::Kind kind; + switch (cl.kind) { + case time_zone::civil_lookup::civil_kind::UNIQUE: + kind = TimeInfo::Kind::UNIQUE; + break; + case time_zone::civil_lookup::civil_kind::SKIPPED: + kind = TimeInfo::Kind::SKIPPED; + break; + case time_zone::civil_lookup::civil_kind::REPEATED: + kind = TimeInfo::Kind::REPEATED; + break; + } + return {kind, cl.pre, cl.trans, cl.post, norm}; +} + +template +inline std::string Format(const std::string& fmt, const time_point& tp, + const TimeZone& tz) { + return format(fmt, tp, tz); +} + +template +inline bool Parse(const std::string& fmt, const std::string& input, + const TimeZone& tz, time_point* tpp) { + return parse(fmt, input, tz, tpp); +} + +} // namespace deprecated_v1_api +} // namespace cctz + +#endif // CCTZ_H_ From 5b352860dba0b67f8cfd20c8bceee1bd593003ae Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Thu, 31 Mar 2016 15:07:27 -0400 Subject: [PATCH 43/69] s/constexpr/CONSTEXPR/g --- include/civil_time_detail.h | 170 ++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 84 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 98c8d9d7..ab09bdf4 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -18,8 +18,10 @@ #include // Disable constexpr support unless we are using clang in C++14 mode. -#if !__clang__ || __cpp_constexpr < 201304 -#define constexpr +#if __clang__ && __cpp_constexpr >= 201304 +#define CONSTEXPR constexpr +#else +#define CONSTEXPR #endif namespace cctz { @@ -49,14 +51,14 @@ struct year_tag : month_tag {}; namespace impl { // The month lengths in non-leap and leap years respectively. -constexpr const signed char k_dpm[2][12] = { +CONSTEXPR const 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 const signed char k_dpC[400] = { +CONSTEXPR const 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, @@ -78,7 +80,7 @@ constexpr const signed char k_dpC[400] = { // 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 const signed char k_dp4y[400] = { +CONSTEXPR const signed char k_dp4y[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -98,125 +100,125 @@ constexpr const signed char k_dp4y[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; -inline constexpr bool is_leap(int y) { +inline CONSTEXPR bool is_leap(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } -inline constexpr int dpm(int y, int m) { +inline CONSTEXPR int dpm(int y, int m) { return k_dpm[is_leap(y + (m > 2))][m - 1]; } -inline constexpr int year_index(int y, int m) { +inline CONSTEXPR int year_index(int y, int m) { return (((y + (m > 2)) % 400) + 400) % 400; } -inline constexpr int dpC(int y, int m) { +inline CONSTEXPR int dpC(int y, int m) { return 36524 + k_dpC[year_index(y, m)]; } -inline constexpr int dp4y(int y, int m) { +inline CONSTEXPR int dp4y(int y, int m) { return 1460 + k_dp4y[year_index(y, m)]; } -inline constexpr int dpy(int y, int m) { +inline CONSTEXPR int dpy(int y, int m) { return is_leap(y + (m > 2)) ? 366 : 365; } -inline constexpr fields n_ymn(int y, int m, int d, int n, int hh, int mm, +inline 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); } -inline constexpr fields n_1yn(int y, int m, int d, int n, int hh, int mm, +inline 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); } -inline constexpr fields n_4yn(int y, int m, int d, int n, int hh, int mm, +inline 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); } -inline constexpr fields n_Cn(int y, int m, int d, int n, int hh, int mm, +inline 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); } -inline constexpr fields n_C(int y, int m, int d, int hh, int mm, int ss) { +inline 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); } -inline constexpr fields n_C4d2(int y, int m, int d, int hh, int mm, int ss) { +inline 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); } -inline constexpr fields n_C4d(int y, int m, int d, int c, int hh, int mm, +inline 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); } -inline constexpr fields n_C4c2(int y, int m, int d, int c, int hh, int mm, +inline 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); } -inline constexpr fields n_C4c(int y, int m, int d, int c, int hh, int mm, +inline 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); } -inline constexpr fields n_m_n(int y, int cy, int m, int d, int cd, int hh, +inline 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); } -inline constexpr fields n_m(int y, int m, int d, int hh, int mm, int ss) { +inline 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); } -inline constexpr fields n_m_c(int y, int m, int d, int c, int hh, int mm, +inline 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); } -inline constexpr fields n_hh_n(int y, int m, int d, int c, int hh, int mm, +inline 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); } -inline constexpr fields n_hh(int y, int m, int d, int hh, int mm, int ss) { +inline 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); } -inline constexpr fields n_hh_c2(int y, int m, int d, int c, int hh, int mm, +inline 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); } -inline constexpr fields n_hh_c(int y, int m, int d, int hh, int c, int mm, +inline 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); } -inline constexpr fields n_mm_n(int y, int m, int d, int hh, int c, int mm, +inline 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); } -inline constexpr fields n_mm(int y, int m, int d, int hh, int mm, int ss) { +inline 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); } -inline constexpr fields n_mm_c2(int y, int m, int d, int hh, int c, int mm, +inline 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); } -inline constexpr fields n_mm_c(int y, int m, int d, int hh, int mm, int c, +inline 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); } -inline constexpr fields n_ss_n(int y, int m, int d, int hh, int mm, int c, +inline 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); } -inline constexpr fields n_ss(int y, int m, int d, int hh, int mm, int ss) { +inline 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); } @@ -228,19 +230,19 @@ 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) { +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) { +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) { +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) { +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) { +inline CONSTEXPR int ymd_ord(int y, int m, int d) { return eymd_ord(m <= 2 ? y - 1 : y, m, d); } @@ -249,66 +251,66 @@ inline constexpr int ymd_ord(int y, int m, int d) { //////////////////////////////////////////////////////////////////////// // Aligns the (normalized) fields struct to the indicated field. -inline constexpr fields align(second_tag, fields f) { +inline CONSTEXPR fields align(second_tag, fields f) { return f; } -inline constexpr fields align(minute_tag, fields 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) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +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) { +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. -inline constexpr int difference(year_tag, fields f1, fields f2) { +inline CONSTEXPR int difference(year_tag, fields f1, fields f2) { return f1.y - f2.y; } -inline constexpr int difference(month_tag, fields f1, fields f2) { +inline CONSTEXPR int difference(month_tag, fields f1, fields f2) { return difference(year_tag{}, f1, f2) * 12 + (f1.m - f2.m); } -inline constexpr int difference(day_tag, fields f1, fields f2) { +inline 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); } -inline constexpr int difference(hour_tag, fields f1, fields f2) { +inline CONSTEXPR int difference(hour_tag, fields f1, fields f2) { return difference(day_tag{}, f1, f2) * 24 + (f1.hh - f2.hh); } -inline constexpr int difference(minute_tag, fields f1, fields f2) { +inline CONSTEXPR int difference(minute_tag, fields f1, fields f2) { return difference(hour_tag{}, f1, f2) * 60 + (f1.mm - f2.mm); } -inline constexpr int difference(second_tag, fields f1, fields f2) { +inline CONSTEXPR int difference(second_tag, fields f1, fields f2) { return difference(minute_tag{}, f1, f2) * 60 + (f1.ss - f2.ss); } @@ -317,12 +319,12 @@ inline constexpr int difference(second_tag, fields f1, fields f2) { template class civil_time { public: - explicit constexpr civil_time(int y, int m = 1, int d = 1, int hh = 0, + 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; + CONSTEXPR civil_time() : civil_time(1970) {} + CONSTEXPR civil_time(const civil_time&) = default; civil_time& operator=(const civil_time&) = default; // Conversion between civil times of different alignment. Conversion to @@ -336,17 +338,17 @@ class civil_time { civil_time(const civil_time& ct, preserves_data* = nullptr) : civil_time(ct.f_) {} template - explicit constexpr civil_time(const civil_time& ct, + explicit CONSTEXPR civil_time(const civil_time& ct, preserves_data* = nullptr) : 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; } + 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) { @@ -379,16 +381,16 @@ class civil_time { } // Binary arithmetic operators. - inline friend constexpr civil_time operator+(const civil_time& a, int n) { + 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) { + 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) { + 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, + inline friend CONSTEXPR int operator-(const civil_time& lhs, const civil_time& rhs) { return difference(T{}, lhs.f_, rhs.f_); } @@ -400,7 +402,7 @@ class civil_time { friend class civil_time; // The designated constructor that all others eventually call. - explicit constexpr civil_time(fields f) : f_(align(T{}, f)) {} + explicit CONSTEXPR civil_time(fields f) : f_(align(T{}, f)) {} fields f_; }; @@ -417,7 +419,7 @@ 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, +CONSTEXPR bool operator<(const civil_time& lhs, const civil_time& rhs) { return (lhs.year() < rhs.year() || (lhs.year() == rhs.year() && @@ -432,29 +434,29 @@ constexpr bool operator<(const civil_time& lhs, (lhs.second() < rhs.second()))))))))))); } template -constexpr bool operator<=(const civil_time& lhs, +CONSTEXPR bool operator<=(const civil_time& lhs, const civil_time& rhs) { return !(rhs < lhs); } template -constexpr bool operator>=(const civil_time& lhs, +CONSTEXPR bool operator>=(const civil_time& lhs, const civil_time& rhs) { return !(lhs < rhs); } template -constexpr bool operator>(const civil_time& lhs, +CONSTEXPR bool operator>(const civil_time& lhs, const civil_time& rhs) { return rhs < lhs; } template -constexpr bool operator==(const civil_time& lhs, +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, +CONSTEXPR bool operator!=(const civil_time& lhs, const civil_time& rhs) { return !(lhs == rhs); } @@ -501,14 +503,14 @@ enum class weekday { }; namespace impl { -constexpr const weekday k_weekday_by_thu_off[] = { +CONSTEXPR const weekday k_weekday_by_thu_off[] = { weekday::thursday, weekday::friday, weekday::saturday, weekday::sunday, weekday::monday, weekday::tuesday, weekday::wednesday, }; } // namespace impl -inline constexpr weekday get_weekday(const civil_day& cd) { +inline CONSTEXPR weekday get_weekday(const civil_day& cd) { return impl::k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; } @@ -534,27 +536,27 @@ inline std::ostream& operator<<(std::ostream& os, weekday wd) { //////////////////////////////////////////////////////////////////////// namespace impl { -inline constexpr civil_day scan_weekday(const civil_day& cd, weekday wd, +inline 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 -inline constexpr civil_day next_weekday(const civil_day& cd, weekday wd) { +inline CONSTEXPR civil_day next_weekday(const civil_day& cd, weekday wd) { return impl::scan_weekday(cd + 1, wd, 1); } -inline constexpr civil_day prev_weekday(const civil_day& cd, weekday wd) { +inline CONSTEXPR civil_day prev_weekday(const civil_day& cd, weekday wd) { return impl::scan_weekday(cd - 1, wd, -1); } //////////////////////////////////////////////////////////////////////// -inline constexpr int get_yearday(const civil_day& cd) { +inline CONSTEXPR int get_yearday(const civil_day& cd) { return cd - civil_day(cd.year(), 1, 0); } } // namespace detail } // namespace cctz -#undef constexpr +#undef CONSTEXPR From 42531e8dac01cb70b3c26bbb998104c3fef40f65 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 15:14:07 -0400 Subject: [PATCH 44/69] Move the constexpr data into the matching constexpr function now that we demand C++14 support. Also optimize dpm() a little. --- include/civil_time_detail.h | 98 ++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index ab09bdf4..e62cbb61 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -50,69 +50,65 @@ struct year_tag : month_tag {}; namespace impl { -// The month lengths in non-leap and leap years respectively. -CONSTEXPR const 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 const 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 const signed char k_dp4y[400] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 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, -}; - inline CONSTEXPR bool is_leap(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } inline CONSTEXPR int dpm(int y, int m) { - return k_dpm[is_leap(y + (m > 2))][m - 1]; + // The month lengths in non-leap years. + CONSTEXPR const signed char k_dpm[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + return k_dpm[m - 1] + (m == 2 && is_leap(y)); } inline CONSTEXPR int year_index(int y, int m) { return (((y + (m > 2)) % 400) + 400) % 400; } inline CONSTEXPR int dpC(int y, int m) { + // 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 const 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, + }; return 36524 + k_dpC[year_index(y, m)]; } inline CONSTEXPR int dp4y(int y, int m) { + // 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 const signed char k_dp4y[400] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 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, + }; return 1460 + k_dp4y[year_index(y, m)]; } inline CONSTEXPR int dpy(int y, int m) { From d342dc83ff22499adae21766896bcf82a14ce571 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 16:59:56 -0400 Subject: [PATCH 45/69] Add missing constexpr to implicit civil_time constructor. --- include/civil_time_detail.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index e62cbb61..3c7fcb09 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -331,7 +331,8 @@ class civil_time { using preserves_data = typename std::enable_if::value>::type; template - civil_time(const civil_time& ct, preserves_data* = nullptr) + CONSTEXPR civil_time(const civil_time& ct, + preserves_data* = nullptr) : civil_time(ct.f_) {} template explicit CONSTEXPR civil_time(const civil_time& ct, From a42ddebbd7cd898b75fa9aaef17eab4b4455b629 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 18:16:12 -0400 Subject: [PATCH 46/69] Field normalization: convert direct recursion to loops. --- include/civil_time_detail.h | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 3c7fcb09..5e1fba39 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -117,26 +117,36 @@ inline CONSTEXPR int dpy(int y, int m) { inline 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); + while (d > n) { + d -= n; + n = (m == 12) ? dpm(++y, m = 1) : dpm(y, ++m); + } + return fields{y, m, d, hh, mm, ss}; } inline 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); + while (d > n) { + d -= n; + n = dpy(++y, m); + } + return n_ymn(y, m, d, dpm(y, m), hh, mm, ss); } inline 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); + while (d > n) { + d -= n; + n = dp4y(y += 4, m); + } + return n_1yn(y, m, d, dpy(y, m), hh, mm, ss); } inline 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); + while (d > n) { + d -= n; + n = dpC(y += 100, m); + } + return n_4yn(y, m, d, dp4y(y, m), hh, mm, ss); } inline 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); From 0ac5ed8c44030cdfc97232507212dd27fe6b8310 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 18:30:23 -0400 Subject: [PATCH 47/69] Field normalization: coalesce n_C() descendants into a single function. --- include/civil_time_detail.h | 47 ++++++++++++++----------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 5e1fba39..ac56a00c 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -53,13 +53,6 @@ namespace impl { inline CONSTEXPR bool is_leap(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } -inline CONSTEXPR int dpm(int y, int m) { - // The month lengths in non-leap years. - CONSTEXPR const signed char k_dpm[12] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - return k_dpm[m - 1] + (m == 2 && is_leap(y)); -} inline CONSTEXPR int year_index(int y, int m) { return (((y + (m > 2)) % 400) + 400) % 400; } @@ -114,42 +107,36 @@ inline CONSTEXPR int dp4y(int y, int m) { inline CONSTEXPR int dpy(int y, int m) { return is_leap(y + (m > 2)) ? 366 : 365; } +inline CONSTEXPR int dpm(int y, int m) { + // The month lengths in non-leap years. + CONSTEXPR const signed char k_dpm[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + return k_dpm[m - 1] + (m == 2 && is_leap(y)); +} -inline CONSTEXPR fields n_ymn(int y, int m, int d, int n, int hh, int mm, - int ss) { +inline CONSTEXPR fields n_C(int y, int m, int d, int hh, int mm, int ss) { + int n = dpC(y, m); while (d > n) { d -= n; - n = (m == 12) ? dpm(++y, m = 1) : dpm(y, ++m); + n = dpC(y += 100, m); } - return fields{y, m, d, hh, mm, ss}; -} -inline CONSTEXPR fields n_1yn(int y, int m, int d, int n, int hh, int mm, - int ss) { + n = dp4y(y, m); while (d > n) { d -= n; - n = dpy(++y, m); + n = dp4y(y += 4, m); } - return n_ymn(y, m, d, dpm(y, m), hh, mm, ss); -} -inline CONSTEXPR fields n_4yn(int y, int m, int d, int n, int hh, int mm, - int ss) { + n = dpy(y, m); while (d > n) { d -= n; - n = dp4y(y += 4, m); + n = dpy(++y, m); } - return n_1yn(y, m, d, dpy(y, m), hh, mm, ss); -} - -inline CONSTEXPR fields n_Cn(int y, int m, int d, int n, int hh, int mm, - int ss) { + n = dpm(y, m); while (d > n) { d -= n; - n = dpC(y += 100, m); + n = (m == 12) ? dpm(++y, m = 1) : dpm(y, ++m); } - return n_4yn(y, m, d, dp4y(y, m), hh, mm, ss); -} -inline 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); + return fields{y, m, d, hh, mm, ss}; } inline CONSTEXPR fields n_C4d2(int y, int m, int d, int hh, int mm, int ss) { From 785574e276b03905f8e18b55fab5330b58645730 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 18:44:06 -0400 Subject: [PATCH 48/69] Field normalization: coalesce n_C4c() descendants into a single function. --- include/civil_time_detail.h | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index ac56a00c..93ed19e6 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -115,7 +115,20 @@ inline CONSTEXPR int dpm(int y, int m) { return k_dpm[m - 1] + (m == 2 && is_leap(y)); } -inline CONSTEXPR fields n_C(int y, int m, int d, int hh, int mm, int ss) { +inline CONSTEXPR fields n_C4c(int y, int m, int d, int c, int hh, int mm, + int ss) { + y += (c / 146097) * 400; + c %= 146097; + if (c < 0) { + y -= 400; + c += 146097; + } + y += (d / 146097) * 400; + d = d % 146097 + c; + if (d <= 0) { + y -= 400; + d += 146097; + } int n = dpC(y, m); while (d > n) { d -= n; @@ -139,25 +152,6 @@ inline CONSTEXPR fields n_C(int y, int m, int d, int hh, int mm, int ss) { return fields{y, m, d, hh, mm, ss}; } -inline 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); -} -inline 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); -} -inline 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); -} -inline 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); -} - inline 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) From 8aa56b40e8ff7cb894cc45d86669da9c3d16d55f Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 19:21:25 -0400 Subject: [PATCH 49/69] Field normalization: coalesce month/hour/minute/second functions. --- include/civil_time_detail.h | 78 +++++++++++++++---------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 93ed19e6..192e389c 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -152,61 +152,47 @@ inline CONSTEXPR fields n_C4c(int y, int m, int d, int c, int hh, int mm, return fields{y, m, d, hh, mm, ss}; } -inline 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); -} -inline 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); -} -inline CONSTEXPR fields n_m_c(int y, int m, int d, int c, int hh, int mm, +inline CONSTEXPR fields n_m_n(int y, int m, int d, int cd, int hh, int mm, int ss) { - return n_m_n(y, m / 12, m % 12, d, c, hh, mm, ss); + y += m / 12; + m %= 12; + if (m <= 0) { + y -= 1; + m += 12; + } + return n_C4c(y, m, d, cd, hh, mm, ss); } -inline 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); -} -inline 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); -} inline 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); -} -inline 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); + c += hh / 24; + hh %= 24; + if (hh < 0) { + c -= 1; + hh += 24; + } + return n_m_n(y, m, d, c, hh, mm, ss); } -inline 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); -} -inline 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); -} inline 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); -} -inline 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); + c += mm / 60; + mm %= 60; + if (mm < 0) { + c -= 1; + mm += 60; + } + return n_hh_c2(y, m, d, hh / 24 + c / 24, hh % 24 + c % 24, mm, ss); } -inline 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); -} inline 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); + int c = ss / 60; + ss %= 60; + if (ss < 0) { + c -= 1; + ss += 60; + } + return n_mm_c2(y, m, d, hh, mm / 60 + c / 60, mm % 60 + c % 60, ss); } } // namespace impl @@ -264,16 +250,16 @@ 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); + return impl::n_mm_c2(f.y, f.m, f.d, f.hh + n / 60, 0, 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); + return impl::n_hh_c2(f.y, f.m, f.d + n / 24, 0, 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); + return impl::n_m_n(f.y + n / 12, f.m + n % 12, f.d, 0, 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}; From a8dd0ce66b00a8e98eda61d68cded0bbbda10e9a Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 21:46:45 -0400 Subject: [PATCH 50/69] Convert next/prev_weekday() recursion to loops. --- include/civil_time_detail.h | 54 +++++++++++++++---------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 192e389c..2785491f 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -325,11 +325,11 @@ class civil_time { CONSTEXPR int second() const { return f_.ss; } // Assigning arithmetic. - civil_time& operator+=(int n) { + CONSTEXPR civil_time& operator+=(int n) { f_ = step(T{}, f_, n); return *this; } - civil_time& operator-=(int n) { + CONSTEXPR civil_time& operator-=(int n) { if (n != std::numeric_limits::min()) { f_ = step(T{}, f_, -n); } else { @@ -337,18 +337,18 @@ class civil_time { } return *this; } - civil_time& operator++() { + CONSTEXPR civil_time& operator++() { return *this += 1; } - civil_time operator++(int) { + CONSTEXPR civil_time operator++(int) { civil_time a = *this; ++*this; return a; } - civil_time& operator--() { + CONSTEXPR civil_time& operator--() { return *this -= 1; } - civil_time operator--(int) { + CONSTEXPR civil_time operator--(int) { civil_time a = *this; --*this; return a; @@ -476,18 +476,6 @@ enum class weekday { sunday, }; -namespace impl { -CONSTEXPR const weekday k_weekday_by_thu_off[] = { - weekday::thursday, weekday::friday, weekday::saturday, - weekday::sunday, weekday::monday, weekday::tuesday, - weekday::wednesday, -}; -} // namespace impl - -inline CONSTEXPR weekday get_weekday(const civil_day& cd) { - return impl::k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; -} - inline std::ostream& operator<<(std::ostream& os, weekday wd) { switch (wd) { case weekday::monday: @@ -507,27 +495,29 @@ inline std::ostream& operator<<(std::ostream& os, weekday wd) { } } -//////////////////////////////////////////////////////////////////////// - -namespace impl { -inline 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); +inline CONSTEXPR weekday get_weekday(const civil_day& cd) { + CONSTEXPR const weekday k_weekday_by_thu_off[] = { + weekday::thursday, weekday::friday, weekday::saturday, + weekday::sunday, weekday::monday, weekday::tuesday, + weekday::wednesday, + }; + return k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; } -} // namespace impl -inline CONSTEXPR civil_day next_weekday(const civil_day& cd, weekday wd) { - return impl::scan_weekday(cd + 1, wd, 1); -} +//////////////////////////////////////////////////////////////////////// -inline CONSTEXPR civil_day prev_weekday(const civil_day& cd, weekday wd) { - return impl::scan_weekday(cd - 1, wd, -1); +inline CONSTEXPR civil_day next_weekday(civil_day cd, weekday wd) { + do { cd += 1; } while (get_weekday(cd) != wd); + return cd; } -//////////////////////////////////////////////////////////////////////// +inline CONSTEXPR civil_day prev_weekday(civil_day cd, weekday wd) { + do { cd -= 1; } while (get_weekday(cd) != wd); + return cd; +} inline CONSTEXPR int get_yearday(const civil_day& cd) { - return cd - civil_day(cd.year(), 1, 0); + return cd - civil_day(civil_year(cd)) + 1; } } // namespace detail From 942d72f09ff0e14e30f11da340b286804fbae642 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 22:59:28 -0400 Subject: [PATCH 51/69] Streaming of civil_time values should behave like single strings. --- include/civil_time_detail.h | 35 ++++++++++++++++-------- src/civil_time_test.cc | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 2785491f..fce19eb5 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -15,6 +15,7 @@ #include #include #include +#include #include // Disable constexpr support unless we are using clang in C++14 mode. @@ -441,27 +442,39 @@ CONSTEXPR bool operator!=(const civil_time& lhs, // while omitting fields inferior to the type's alignment. For example, // civil_day is formatted only as YYYY-MM-DD. inline std::ostream& operator<<(std::ostream& os, civil_year y) { - return os << y.year(); // No padding. + std::stringstream ss; + ss << y.year(); // No padding. + return os << ss.str(); } inline std::ostream& operator<<(std::ostream& os, civil_month m) { - return os << civil_year(m) << '-' << std::setfill('0') << std::setw(2) - << m.month(); + std::stringstream ss; + ss << civil_year(m) << '-'; + ss << std::setfill('0') << std::setw(2) << m.month(); + return os << ss.str(); } inline std::ostream& operator<<(std::ostream& os, civil_day d) { - return os << civil_month(d) << '-' << std::setfill('0') << std::setw(2) - << d.day(); + std::stringstream ss; + ss << civil_month(d) << '-'; + ss << std::setfill('0') << std::setw(2) << d.day(); + return os << ss.str(); } inline std::ostream& operator<<(std::ostream& os, civil_hour h) { - return os << civil_day(h) << 'T' << std::setfill('0') << std::setw(2) - << h.hour(); + std::stringstream ss; + ss << civil_day(h) << 'T'; + ss << std::setfill('0') << std::setw(2) << h.hour(); + return os << ss.str(); } inline std::ostream& operator<<(std::ostream& os, civil_minute m) { - return os << civil_hour(m) << ':' << std::setfill('0') << std::setw(2) - << m.minute(); + std::stringstream ss; + ss << civil_hour(m) << ':'; + ss << std::setfill('0') << std::setw(2) << m.minute(); + return os << ss.str(); } inline std::ostream& operator<<(std::ostream& os, civil_second s) { - return os << civil_minute(s) << ':' << std::setfill('0') << std::setw(2) - << s.second(); + std::stringstream ss; + ss << civil_minute(s) << ':'; + ss << std::setfill('0') << std::setw(2) << s.second(); + return os << ss.str(); } //////////////////////////////////////////////////////////////////////// diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index 00446427..92f9fd7d 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -14,6 +14,7 @@ #include "civil_time.h" +#include #include #include #include @@ -787,6 +788,58 @@ TEST(CivilTime, OutputStream) { EXPECT_EQ("Sunday", Format(weekday::sunday)); } +TEST(CivilTime, OutputStreamLeftFillWidth) { + civil_second cs(2016, 2, 3, 4, 5, 6); + { + std::stringstream ss; + ss << std::left << std::setfill('.'); + ss << std::setw(3) << 'X'; + ss << std::setw(21) << civil_year(cs); + ss << std::setw(3) << 'X'; + EXPECT_EQ("X..2016.................X..", ss.str()); + } + { + std::stringstream ss; + ss << std::left << std::setfill('.'); + ss << std::setw(3) << 'X'; + ss << std::setw(21) << civil_month(cs); + ss << std::setw(3) << 'X'; + EXPECT_EQ("X..2016-02..............X..", ss.str()); + } + { + std::stringstream ss; + ss << std::left << std::setfill('.'); + ss << std::setw(3) << 'X'; + ss << std::setw(21) << civil_day(cs); + ss << std::setw(3) << 'X'; + EXPECT_EQ("X..2016-02-03...........X..", ss.str()); + } + { + std::stringstream ss; + ss << std::left << std::setfill('.'); + ss << std::setw(3) << 'X'; + ss << std::setw(21) << civil_hour(cs); + ss << std::setw(3) << 'X'; + EXPECT_EQ("X..2016-02-03T04........X..", ss.str()); + } + { + std::stringstream ss; + ss << std::left << std::setfill('.'); + ss << std::setw(3) << 'X'; + ss << std::setw(21) << civil_minute(cs); + ss << std::setw(3) << 'X'; + EXPECT_EQ("X..2016-02-03T04:05.....X..", ss.str()); + } + { + std::stringstream ss; + ss << std::left << std::setfill('.'); + ss << std::setw(3) << 'X'; + ss << std::setw(21) << civil_second(cs); + ss << std::setw(3) << 'X'; + EXPECT_EQ("X..2016-02-03T04:05:06..X..", ss.str()); + } +} + TEST(CivilTime, NextPrevWeekday) { // Jan 1, 1970 was a Thursday. const civil_day thursday(1970, 1, 1); From c120829d5028c07f407dcada1efe231f27091ef3 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Fri, 1 Apr 2016 23:45:32 -0400 Subject: [PATCH 52/69] Field normalization: use simpler names after constexpr re-rolling. --- include/civil_time_detail.h | 72 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index fce19eb5..8d658098 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -51,16 +51,16 @@ struct year_tag : month_tag {}; namespace impl { -inline CONSTEXPR bool is_leap(int y) { +inline CONSTEXPR bool is_leap_year(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } inline CONSTEXPR int year_index(int y, int m) { return (((y + (m > 2)) % 400) + 400) % 400; } -inline CONSTEXPR int dpC(int y, int m) { +inline CONSTEXPR int days_per_century(int y, int m) { // 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 const signed char k_dpC[400] = { + CONSTEXPR const signed char k_days_per_century[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, @@ -79,12 +79,12 @@ inline CONSTEXPR int dpC(int y, int m) { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; - return 36524 + k_dpC[year_index(y, m)]; + return 36524 + k_days_per_century[year_index(y, m)]; } -inline CONSTEXPR int dp4y(int y, int m) { +inline CONSTEXPR int days_per_4years(int y, int m) { // 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 const signed char k_dp4y[400] = { + CONSTEXPR const signed char k_days_per_4years[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -103,20 +103,20 @@ inline CONSTEXPR int dp4y(int y, int m) { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; - return 1460 + k_dp4y[year_index(y, m)]; + return 1460 + k_days_per_4years[year_index(y, m)]; } -inline CONSTEXPR int dpy(int y, int m) { - return is_leap(y + (m > 2)) ? 366 : 365; +inline CONSTEXPR int days_per_year(int y, int m) { + return is_leap_year(y + (m > 2)) ? 366 : 365; } -inline CONSTEXPR int dpm(int y, int m) { +inline CONSTEXPR int days_per_month(int y, int m) { // The month lengths in non-leap years. CONSTEXPR const signed char k_dpm[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - return k_dpm[m - 1] + (m == 2 && is_leap(y)); + return k_dpm[m - 1] + (m == 2 && is_leap_year(y)); } -inline CONSTEXPR fields n_C4c(int y, int m, int d, int c, int hh, int mm, +inline CONSTEXPR fields n_day(int y, int m, int d, int c, int hh, int mm, int ss) { y += (c / 146097) * 400; c %= 146097; @@ -130,30 +130,30 @@ inline CONSTEXPR fields n_C4c(int y, int m, int d, int c, int hh, int mm, y -= 400; d += 146097; } - int n = dpC(y, m); + int n = days_per_century(y, m); while (d > n) { d -= n; - n = dpC(y += 100, m); + n = days_per_century(y += 100, m); } - n = dp4y(y, m); + n = days_per_4years(y, m); while (d > n) { d -= n; - n = dp4y(y += 4, m); + n = days_per_4years(y += 4, m); } - n = dpy(y, m); + n = days_per_year(y, m); while (d > n) { d -= n; - n = dpy(++y, m); + n = days_per_year(++y, m); } - n = dpm(y, m); + n = days_per_month(y, m); while (d > n) { d -= n; - n = (m == 12) ? dpm(++y, m = 1) : dpm(y, ++m); + n = (m == 12) ? days_per_month(++y, m = 1) : days_per_month(y, ++m); } return fields{y, m, d, hh, mm, ss}; } -inline CONSTEXPR fields n_m_n(int y, int m, int d, int cd, int hh, int mm, +inline CONSTEXPR fields n_mon(int y, int m, int d, int cd, int hh, int mm, int ss) { y += m / 12; m %= 12; @@ -161,39 +161,39 @@ inline CONSTEXPR fields n_m_n(int y, int m, int d, int cd, int hh, int mm, y -= 1; m += 12; } - return n_C4c(y, m, d, cd, hh, mm, ss); + return n_day(y, m, d, cd, hh, mm, ss); } -inline CONSTEXPR fields n_hh_c2(int y, int m, int d, int c, int hh, int mm, - int ss) { +inline CONSTEXPR fields n_hour(int y, int m, int d, int c, int hh, int mm, + int ss) { c += hh / 24; hh %= 24; if (hh < 0) { c -= 1; hh += 24; } - return n_m_n(y, m, d, c, hh, mm, ss); + return n_mon(y, m, d, c, hh, mm, ss); } -inline CONSTEXPR fields n_mm_c2(int y, int m, int d, int hh, int c, int mm, - int ss) { +inline CONSTEXPR fields n_min(int y, int m, int d, int hh, int c, int mm, + int ss) { c += mm / 60; mm %= 60; if (mm < 0) { c -= 1; mm += 60; } - return n_hh_c2(y, m, d, hh / 24 + c / 24, hh % 24 + c % 24, mm, ss); + return n_hour(y, m, d, hh / 24 + c / 24, hh % 24 + c % 24, mm, ss); } -inline CONSTEXPR fields n_ss(int y, int m, int d, int hh, int mm, int ss) { +inline CONSTEXPR fields n_sec(int y, int m, int d, int hh, int mm, int ss) { int c = ss / 60; ss %= 60; if (ss < 0) { c -= 1; ss += 60; } - return n_mm_c2(y, m, d, hh, mm / 60 + c / 60, mm % 60 + c % 60, ss); + return n_min(y, m, d, hh, mm / 60 + c / 60, mm % 60 + c % 60, ss); } } // namespace impl @@ -248,19 +248,19 @@ inline CONSTEXPR fields align(year_tag, fields f) { // 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); + return impl::n_sec(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_c2(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss); + return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss); } inline CONSTEXPR fields step(hour_tag, fields f, int n) { - return impl::n_hh_c2(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss); + return impl::n_hour(f.y, f.m, f.d + n / 24, 0, 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); + return impl::n_day(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_n(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss); + return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, 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}; @@ -295,7 +295,7 @@ 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)) {} + : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {} CONSTEXPR civil_time() : civil_time(1970) {} CONSTEXPR civil_time(const civil_time&) = default; From 9f7d7320b56997770ae434084eebab0052649ed0 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Sat, 2 Apr 2016 00:02:15 -0400 Subject: [PATCH 53/69] Use finer-grained constexpr backwards-compatibility defines. --- include/civil_time_detail.h | 163 ++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 8d658098..6429081c 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -20,9 +20,15 @@ // Disable constexpr support unless we are using clang in C++14 mode. #if __clang__ && __cpp_constexpr >= 201304 -#define CONSTEXPR constexpr +#define CONSTEXPR_D constexpr // data +#define CONSTEXPR_F constexpr // function +#define CONSTEXPR_M constexpr // member +#define CONSTEXPR_T constexpr // template #else -#define CONSTEXPR +#define CONSTEXPR_D const +#define CONSTEXPR_F inline +#define CONSTEXPR_M +#define CONSTEXPR_T #endif namespace cctz { @@ -51,16 +57,16 @@ struct year_tag : month_tag {}; namespace impl { -inline CONSTEXPR bool is_leap_year(int y) { +CONSTEXPR_F bool is_leap_year(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } -inline CONSTEXPR int year_index(int y, int m) { +CONSTEXPR_F int year_index(int y, int m) { return (((y + (m > 2)) % 400) + 400) % 400; } -inline CONSTEXPR int days_per_century(int y, int m) { +CONSTEXPR_F int days_per_century(int y, int m) { // 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 const signed char k_days_per_century[400] = { + CONSTEXPR_D signed char k_days_per_century[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, @@ -81,10 +87,10 @@ inline CONSTEXPR int days_per_century(int y, int m) { }; return 36524 + k_days_per_century[year_index(y, m)]; } -inline CONSTEXPR int days_per_4years(int y, int m) { +CONSTEXPR_F int days_per_4years(int y, int m) { // 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 const signed char k_days_per_4years[400] = { + CONSTEXPR_D signed char k_days_per_4years[400] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -105,19 +111,18 @@ inline CONSTEXPR int days_per_4years(int y, int m) { }; return 1460 + k_days_per_4years[year_index(y, m)]; } -inline CONSTEXPR int days_per_year(int y, int m) { +CONSTEXPR_F int days_per_year(int y, int m) { return is_leap_year(y + (m > 2)) ? 366 : 365; } -inline CONSTEXPR int days_per_month(int y, int m) { +CONSTEXPR_F int days_per_month(int y, int m) { // The month lengths in non-leap years. - CONSTEXPR const signed char k_dpm[12] = { + CONSTEXPR_D signed char k_dpm[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; return k_dpm[m - 1] + (m == 2 && is_leap_year(y)); } -inline CONSTEXPR fields n_day(int y, int m, int d, int c, int hh, int mm, - int ss) { +CONSTEXPR_F fields n_day(int y, int m, int d, int c, int hh, int mm, int ss) { y += (c / 146097) * 400; c %= 146097; if (c < 0) { @@ -152,9 +157,7 @@ inline CONSTEXPR fields n_day(int y, int m, int d, int c, int hh, int mm, } return fields{y, m, d, hh, mm, ss}; } - -inline CONSTEXPR fields n_mon(int y, int m, int d, int cd, int hh, int mm, - int ss) { +CONSTEXPR_F fields n_mon(int y, int m, int d, int cd, int hh, int mm, int ss) { y += m / 12; m %= 12; if (m <= 0) { @@ -163,9 +166,7 @@ inline CONSTEXPR fields n_mon(int y, int m, int d, int cd, int hh, int mm, } return n_day(y, m, d, cd, hh, mm, ss); } - -inline CONSTEXPR fields n_hour(int y, int m, int d, int c, int hh, int mm, - int ss) { +CONSTEXPR_F fields n_hour(int y, int m, int d, int c, int hh, int mm, int ss) { c += hh / 24; hh %= 24; if (hh < 0) { @@ -174,9 +175,7 @@ inline CONSTEXPR fields n_hour(int y, int m, int d, int c, int hh, int mm, } return n_mon(y, m, d, c, hh, mm, ss); } - -inline CONSTEXPR fields n_min(int y, int m, int d, int hh, int c, int mm, - int ss) { +CONSTEXPR_F fields n_min(int y, int m, int d, int hh, int c, int mm, int ss) { c += mm / 60; mm %= 60; if (mm < 0) { @@ -185,8 +184,7 @@ inline CONSTEXPR fields n_min(int y, int m, int d, int hh, int c, int mm, } return n_hour(y, m, d, hh / 24 + c / 24, hh % 24 + c % 24, mm, ss); } - -inline CONSTEXPR fields n_sec(int y, int m, int d, int hh, int mm, int ss) { +CONSTEXPR_F fields n_sec(int y, int m, int d, int hh, int mm, int ss) { int c = ss / 60; ss %= 60; if (ss < 0) { @@ -204,19 +202,19 @@ 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) { +CONSTEXPR_F 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) { +CONSTEXPR_F 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) { +CONSTEXPR_F 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) { +CONSTEXPR_F 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) { +CONSTEXPR_F int ymd_ord(int y, int m, int d) { return eymd_ord(m <= 2 ? y - 1 : y, m, d); } @@ -225,66 +223,66 @@ inline CONSTEXPR int ymd_ord(int y, int m, int d) { //////////////////////////////////////////////////////////////////////// // Aligns the (normalized) fields struct to the indicated field. -inline CONSTEXPR fields align(second_tag, fields f) { +CONSTEXPR_F fields align(second_tag, fields f) { return f; } -inline CONSTEXPR fields align(minute_tag, fields f) { +CONSTEXPR_F 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) { +CONSTEXPR_F 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) { +CONSTEXPR_F 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) { +CONSTEXPR_F fields align(month_tag, fields f) { return fields{f.y, f.m, 1, 0, 0, 0}; } -inline CONSTEXPR fields align(year_tag, fields f) { +CONSTEXPR_F 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) { +CONSTEXPR_F fields step(second_tag, fields f, int n) { return impl::n_sec(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) { +CONSTEXPR_F fields step(minute_tag, fields f, int n) { return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss); } -inline CONSTEXPR fields step(hour_tag, fields f, int n) { +CONSTEXPR_F fields step(hour_tag, fields f, int n) { return impl::n_hour(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss); } -inline CONSTEXPR fields step(day_tag, fields f, int n) { +CONSTEXPR_F fields step(day_tag, fields f, int n) { return impl::n_day(f.y, f.m, f.d, n, f.hh, f.mm, f.ss); } -inline CONSTEXPR fields step(month_tag, fields f, int n) { +CONSTEXPR_F fields step(month_tag, fields f, int n) { return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss); } -inline CONSTEXPR fields step(year_tag, fields f, int n) { +CONSTEXPR_F 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. -inline CONSTEXPR int difference(year_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(year_tag, fields f1, fields f2) { return f1.y - f2.y; } -inline CONSTEXPR int difference(month_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(month_tag, fields f1, fields f2) { return difference(year_tag{}, f1, f2) * 12 + (f1.m - f2.m); } -inline CONSTEXPR int difference(day_tag, fields f1, fields f2) { +CONSTEXPR_F 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); } -inline CONSTEXPR int difference(hour_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(hour_tag, fields f1, fields f2) { return difference(day_tag{}, f1, f2) * 24 + (f1.hh - f2.hh); } -inline CONSTEXPR int difference(minute_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(minute_tag, fields f1, fields f2) { return difference(hour_tag{}, f1, f2) * 60 + (f1.mm - f2.mm); } -inline CONSTEXPR int difference(second_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(second_tag, fields f1, fields f2) { return difference(minute_tag{}, f1, f2) * 60 + (f1.ss - f2.ss); } @@ -293,12 +291,12 @@ inline CONSTEXPR int difference(second_tag, fields f1, fields f2) { template class civil_time { public: - explicit CONSTEXPR civil_time(int y, int m = 1, int d = 1, int hh = 0, + explicit CONSTEXPR_M civil_time(int y, int m = 1, int d = 1, int hh = 0, int mm = 0, int ss = 0) : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {} - CONSTEXPR civil_time() : civil_time(1970) {} - CONSTEXPR civil_time(const civil_time&) = default; + CONSTEXPR_M civil_time() : civil_time(1970) {} + CONSTEXPR_M civil_time(const civil_time&) = default; civil_time& operator=(const civil_time&) = default; // Conversion between civil times of different alignment. Conversion to @@ -309,28 +307,28 @@ class civil_time { using preserves_data = typename std::enable_if::value>::type; template - CONSTEXPR civil_time(const civil_time& ct, + CONSTEXPR_M civil_time(const civil_time& ct, preserves_data* = nullptr) : civil_time(ct.f_) {} template - explicit CONSTEXPR civil_time(const civil_time& ct, + explicit CONSTEXPR_M civil_time(const civil_time& ct, preserves_data* = nullptr) : 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; } + CONSTEXPR_M int year() const { return f_.y; } + CONSTEXPR_M int month() const { return f_.m; } + CONSTEXPR_M int day() const { return f_.d; } + CONSTEXPR_M int hour() const { return f_.hh; } + CONSTEXPR_M int minute() const { return f_.mm; } + CONSTEXPR_M int second() const { return f_.ss; } // Assigning arithmetic. - CONSTEXPR civil_time& operator+=(int n) { + CONSTEXPR_M civil_time& operator+=(int n) { f_ = step(T{}, f_, n); return *this; } - CONSTEXPR civil_time& operator-=(int n) { + CONSTEXPR_M civil_time& operator-=(int n) { if (n != std::numeric_limits::min()) { f_ = step(T{}, f_, -n); } else { @@ -338,34 +336,34 @@ class civil_time { } return *this; } - CONSTEXPR civil_time& operator++() { + CONSTEXPR_M civil_time& operator++() { return *this += 1; } - CONSTEXPR civil_time operator++(int) { + CONSTEXPR_M civil_time operator++(int) { civil_time a = *this; ++*this; return a; } - CONSTEXPR civil_time& operator--() { + CONSTEXPR_M civil_time& operator--() { return *this -= 1; } - CONSTEXPR civil_time operator--(int) { + CONSTEXPR_M 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) { + inline friend CONSTEXPR_M 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) { + inline friend CONSTEXPR_M 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) { + inline friend CONSTEXPR_M 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, + inline friend CONSTEXPR_M int operator-(const civil_time& lhs, const civil_time& rhs) { return difference(T{}, lhs.f_, rhs.f_); } @@ -377,7 +375,7 @@ class civil_time { friend class civil_time; // The designated constructor that all others eventually call. - explicit CONSTEXPR civil_time(fields f) : f_(align(T{}, f)) {} + explicit CONSTEXPR_M civil_time(fields f) : f_(align(T{}, f)) {} fields f_; }; @@ -394,7 +392,7 @@ 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, +CONSTEXPR_T bool operator<(const civil_time& lhs, const civil_time& rhs) { return (lhs.year() < rhs.year() || (lhs.year() == rhs.year() && @@ -409,29 +407,29 @@ CONSTEXPR bool operator<(const civil_time& lhs, (lhs.second() < rhs.second()))))))))))); } template -CONSTEXPR bool operator<=(const civil_time& lhs, +CONSTEXPR_T bool operator<=(const civil_time& lhs, const civil_time& rhs) { return !(rhs < lhs); } template -CONSTEXPR bool operator>=(const civil_time& lhs, +CONSTEXPR_T bool operator>=(const civil_time& lhs, const civil_time& rhs) { return !(lhs < rhs); } template -CONSTEXPR bool operator>(const civil_time& lhs, +CONSTEXPR_T bool operator>(const civil_time& lhs, const civil_time& rhs) { return rhs < lhs; } template -CONSTEXPR bool operator==(const civil_time& lhs, +CONSTEXPR_T 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, +CONSTEXPR_T bool operator!=(const civil_time& lhs, const civil_time& rhs) { return !(lhs == rhs); } @@ -508,8 +506,8 @@ inline std::ostream& operator<<(std::ostream& os, weekday wd) { } } -inline CONSTEXPR weekday get_weekday(const civil_day& cd) { - CONSTEXPR const weekday k_weekday_by_thu_off[] = { +CONSTEXPR_F weekday get_weekday(const civil_day& cd) { + CONSTEXPR_D weekday k_weekday_by_thu_off[] = { weekday::thursday, weekday::friday, weekday::saturday, weekday::sunday, weekday::monday, weekday::tuesday, weekday::wednesday, @@ -519,21 +517,24 @@ inline CONSTEXPR weekday get_weekday(const civil_day& cd) { //////////////////////////////////////////////////////////////////////// -inline CONSTEXPR civil_day next_weekday(civil_day cd, weekday wd) { +CONSTEXPR_F civil_day next_weekday(civil_day cd, weekday wd) { do { cd += 1; } while (get_weekday(cd) != wd); return cd; } -inline CONSTEXPR civil_day prev_weekday(civil_day cd, weekday wd) { +CONSTEXPR_F civil_day prev_weekday(civil_day cd, weekday wd) { do { cd -= 1; } while (get_weekday(cd) != wd); return cd; } -inline CONSTEXPR int get_yearday(const civil_day& cd) { +CONSTEXPR_F int get_yearday(const civil_day& cd) { return cd - civil_day(civil_year(cd)) + 1; } } // namespace detail } // namespace cctz -#undef CONSTEXPR +#undef CONSTEXPR_D +#undef CONSTEXPR_F +#undef CONSTEXPR_M +#undef CONSTEXPR_T From b3bea0f321f77f4ccf8e502589b4c2dd91367562 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Sat, 2 Apr 2016 00:25:32 -0400 Subject: [PATCH 54/69] Re-roll ymd_ord() implementation for C++14 constexpr. --- include/civil_time_detail.h | 75 ++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 6429081c..949b759c 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -198,27 +198,25 @@ CONSTEXPR_F fields n_sec(int y, int m, int d, int hh, int mm, int ss) { //////////////////////////////////////////////////////////////////////// -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]. -CONSTEXPR_F int doy(int m, int d) { - return (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; +// Increments the indicated (normalized) field by "n". +CONSTEXPR_F fields step(second_tag, fields f, int n) { + return impl::n_sec(f.y, f.m, f.d, f.hh, f.mm + n / 60, f.ss + n % 60); } -CONSTEXPR_F int doe(int yoe, int m, int d) { - return yoe * 365 + yoe / 4 - yoe / 100 + doy(m, d); +CONSTEXPR_F fields step(minute_tag, fields f, int n) { + return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss); } -CONSTEXPR_F int era_eymd_ord(int era, int eyear, int m, int d) { - return era * 146097 + doe(eyear - era * 400, m, d) - 719468; +CONSTEXPR_F fields step(hour_tag, fields f, int n) { + return impl::n_hour(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss); } -CONSTEXPR_F int eymd_ord(int eyear, int m, int d) { - return era_eymd_ord((eyear >= 0 ? eyear : eyear - 399) / 400, eyear, m, d); +CONSTEXPR_F fields step(day_tag, fields f, int n) { + return impl::n_day(f.y, f.m, f.d, n, f.hh, f.mm, f.ss); } -CONSTEXPR_F int ymd_ord(int y, int m, int d) { - return eymd_ord(m <= 2 ? y - 1 : y, m, d); +CONSTEXPR_F fields step(month_tag, fields f, int n) { + return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss); +} +CONSTEXPR_F fields step(year_tag, fields f, int n) { + return fields{f.y + n, f.m, f.d, f.hh, f.mm, f.ss}; } - -} // namespace impl //////////////////////////////////////////////////////////////////////// @@ -244,27 +242,20 @@ CONSTEXPR_F fields align(year_tag, fields f) { //////////////////////////////////////////////////////////////////////// -// Increments the indicated (normalized) field by "n". -CONSTEXPR_F fields step(second_tag, fields f, int n) { - return impl::n_sec(f.y, f.m, f.d, f.hh, f.mm + n / 60, f.ss + n % 60); -} -CONSTEXPR_F fields step(minute_tag, fields f, int n) { - return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss); -} -CONSTEXPR_F fields step(hour_tag, fields f, int n) { - return impl::n_hour(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss); -} -CONSTEXPR_F fields step(day_tag, fields f, int n) { - return impl::n_day(f.y, f.m, f.d, n, f.hh, f.mm, f.ss); -} -CONSTEXPR_F fields step(month_tag, fields f, int n) { - return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss); -} -CONSTEXPR_F fields step(year_tag, fields f, int n) { - return fields{f.y + n, f.m, f.d, f.hh, f.mm, f.ss}; +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]. +CONSTEXPR_F int ymd_ord(int y, int m, int d) { + const int eyear = (m <= 2) ? y - 1 : y; + const int era = (eyear >= 0 ? eyear : eyear - 399) / 400; + const int yoe = eyear - era * 400; + const int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; } -//////////////////////////////////////////////////////////////////////// +} // namespace impl // Returns the difference between fields structs using the indicated unit. CONSTEXPR_F int difference(year_tag, fields f1, fields f2) { @@ -364,7 +355,7 @@ class civil_time { return civil_time(step(T{}, a.f_, -n)); } inline friend CONSTEXPR_M int operator-(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) { return difference(T{}, lhs.f_, rhs.f_); } @@ -393,7 +384,7 @@ using civil_second = civil_time; // Always compares all six fields. template CONSTEXPR_T bool operator<(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) { return (lhs.year() < rhs.year() || (lhs.year() == rhs.year() && (lhs.month() < rhs.month() || @@ -408,29 +399,29 @@ CONSTEXPR_T bool operator<(const civil_time& lhs, } template CONSTEXPR_T bool operator<=(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) { return !(rhs < lhs); } template CONSTEXPR_T bool operator>=(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) { return !(lhs < rhs); } template CONSTEXPR_T bool operator>(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) { return rhs < lhs; } template CONSTEXPR_T bool operator==(const civil_time& lhs, - const civil_time& rhs) { + 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_T bool operator!=(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) { return !(lhs == rhs); } From 1191cdb08502cc9e7e6df008f8534418daa56b25 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Sat, 2 Apr 2016 00:36:13 -0400 Subject: [PATCH 55/69] Field normalization: Replace k_days_per_century[] with code. --- include/civil_time_detail.h | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 949b759c..9cabab5f 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -64,28 +64,8 @@ CONSTEXPR_F int year_index(int y, int m) { return (((y + (m > 2)) % 400) + 400) % 400; } CONSTEXPR_F int days_per_century(int y, int m) { - // 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_D signed char k_days_per_century[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, - }; - return 36524 + k_days_per_century[year_index(y, m)]; + const int yi = year_index(y, m); + return 36524 + (yi == 0 || yi > 300); } CONSTEXPR_F int days_per_4years(int y, int m) { // The number of days in the 4 years starting in the mod-400 index year, @@ -331,7 +311,7 @@ class civil_time { return *this += 1; } CONSTEXPR_M civil_time operator++(int) { - civil_time a = *this; + const civil_time a = *this; ++*this; return a; } @@ -339,7 +319,7 @@ class civil_time { return *this -= 1; } CONSTEXPR_M civil_time operator--(int) { - civil_time a = *this; + const civil_time a = *this; --*this; return a; } From 708d814360ab35e94ecd6785d13cf7e7aaf863d7 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Sat, 2 Apr 2016 00:41:48 -0400 Subject: [PATCH 56/69] Update cctz.h file header/copyright to match other files. --- src/cctz.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cctz.h b/src/cctz.h index 8b79cb86..650a1fe7 100644 --- a/src/cctz.h +++ b/src/cctz.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. #ifndef CCTZ_H_ #define CCTZ_H_ From f6101b26b75f68cd8f36a9fdaeb85e154865dbf3 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Sat, 2 Apr 2016 01:31:49 -0400 Subject: [PATCH 57/69] Field normalization: use a clearer name for the days-per-month array. --- include/civil_time_detail.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 9cabab5f..1548b8e4 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -53,7 +53,7 @@ struct year_tag : month_tag {}; //////////////////////////////////////////////////////////////////////// -// Field normalization. +// Field normalization (without avoidable overflow). namespace impl { @@ -95,11 +95,10 @@ CONSTEXPR_F int days_per_year(int y, int m) { return is_leap_year(y + (m > 2)) ? 366 : 365; } CONSTEXPR_F int days_per_month(int y, int m) { - // The month lengths in non-leap years. - CONSTEXPR_D signed char k_dpm[12] = { + CONSTEXPR_D signed char k_days_per_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - return k_dpm[m - 1] + (m == 2 && is_leap_year(y)); + return k_days_per_month[m - 1] + (m == 2 && is_leap_year(y)); } CONSTEXPR_F fields n_day(int y, int m, int d, int c, int hh, int mm, int ss) { From 10229ab02d5f292bedeb6735d5ce035d7ae80cb5 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Mon, 4 Apr 2016 00:01:51 -0400 Subject: [PATCH 58/69] Wrap some long lines. --- src/cctz_v1_test.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cctz_v1_test.cc b/src/cctz_v1_test.cc index f9036931..0705dbd2 100644 --- a/src/cctz_v1_test.cc +++ b/src/cctz_v1_test.cc @@ -756,15 +756,20 @@ TEST(BreakTime, LocalTimeInSydney) { TEST(MakeTime, TimePointResolution) { const TimeZone utc = UTCTimeZone(); - const time_point tp_ns = MakeTime(2015, 1, 2, 3, 4, 5, utc); + 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); + 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); + 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); + 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); + 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 From 08c8386d221f6c16c5a873b80531bf139f19d5ee Mon Sep 17 00:00:00 2001 From: Bradley White Date: Mon, 4 Apr 2016 00:03:36 -0400 Subject: [PATCH 59/69] Field normalization: add an extra 4-century check to n_day(). --- include/civil_time_detail.h | 84 +++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 1548b8e4..ca5ac44a 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -101,38 +101,42 @@ CONSTEXPR_F int days_per_month(int y, int m) { return k_days_per_month[m - 1] + (m == 2 && is_leap_year(y)); } -CONSTEXPR_F fields n_day(int y, int m, int d, int c, int hh, int mm, int ss) { - y += (c / 146097) * 400; - c %= 146097; - if (c < 0) { +CONSTEXPR_F fields n_day(int y, int m, int d, int cd, int hh, int mm, int ss) { + y += (cd / 146097) * 400; + cd %= 146097; + if (cd < 0) { y -= 400; - c += 146097; + cd += 146097; } y += (d / 146097) * 400; - d = d % 146097 + c; + d = d % 146097 + cd; if (d <= 0) { y -= 400; d += 146097; + } else if (d > 146097) { + y += 400; + d -= 146097; } - int n = days_per_century(y, m); - while (d > n) { - d -= n; - n = days_per_century(y += 100, m); - } - n = days_per_4years(y, m); - while (d > n) { - d -= n; - n = days_per_4years(y += 4, m); - } - n = days_per_year(y, m); - while (d > n) { - d -= n; - n = days_per_year(++y, m); + if (d > 365) { + for (int n = days_per_century(y, m); d > n; n = days_per_century(y, m)) { + d -= n; + y += 100; + } + for (int n = days_per_4years(y, m); d > n; n = days_per_4years(y, m)) { + d -= n; + y += 4; + } + for (int n = days_per_year(y, m); d > n; n = days_per_year(y, m)) { + d -= n; + ++y; + } } - n = days_per_month(y, m); - while (d > n) { + for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) { d -= n; - n = (m == 12) ? days_per_month(++y, m = 1) : days_per_month(y, ++m); + if (++m > 12) { + ++y; + m = 1; + } } return fields{y, m, d, hh, mm, ss}; } @@ -145,32 +149,32 @@ CONSTEXPR_F fields n_mon(int y, int m, int d, int cd, int hh, int mm, int ss) { } return n_day(y, m, d, cd, hh, mm, ss); } -CONSTEXPR_F fields n_hour(int y, int m, int d, int c, int hh, int mm, int ss) { - c += hh / 24; +CONSTEXPR_F fields n_hour(int y, int m, int d, int cd, int hh, int mm, int ss) { + cd += hh / 24; hh %= 24; if (hh < 0) { - c -= 1; + cd -= 1; hh += 24; } - return n_mon(y, m, d, c, hh, mm, ss); + return n_mon(y, m, d, cd, hh, mm, ss); } -CONSTEXPR_F fields n_min(int y, int m, int d, int hh, int c, int mm, int ss) { - c += mm / 60; +CONSTEXPR_F fields n_min(int y, int m, int d, int hh, int ch, int mm, int ss) { + ch += mm / 60; mm %= 60; if (mm < 0) { - c -= 1; + ch -= 1; mm += 60; } - return n_hour(y, m, d, hh / 24 + c / 24, hh % 24 + c % 24, mm, ss); + return n_hour(y, m, d, hh / 24 + ch / 24, hh % 24 + ch % 24, mm, ss); } CONSTEXPR_F fields n_sec(int y, int m, int d, int hh, int mm, int ss) { - int c = ss / 60; + int cm = ss / 60; ss %= 60; if (ss < 0) { - c -= 1; + cm -= 1; ss += 60; } - return n_min(y, m, d, hh, mm / 60 + c / 60, mm % 60 + c % 60, ss); + return n_min(y, m, d, hh, mm / 60 + cm / 60, mm % 60 + cm % 60, ss); } } // namespace impl @@ -262,7 +266,7 @@ template class civil_time { public: explicit CONSTEXPR_M civil_time(int y, int m = 1, int d = 1, int hh = 0, - int mm = 0, int ss = 0) + int mm = 0, int ss = 0) : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {} CONSTEXPR_M civil_time() : civil_time(1970) {} @@ -278,11 +282,11 @@ class civil_time { typename std::enable_if::value>::type; template CONSTEXPR_M civil_time(const civil_time& ct, - preserves_data* = nullptr) + preserves_data* = nullptr) : civil_time(ct.f_) {} template explicit CONSTEXPR_M civil_time(const civil_time& ct, - preserves_data* = nullptr) + preserves_data* = nullptr) : civil_time(ct.f_) {} // Field accessors. @@ -504,7 +508,7 @@ CONSTEXPR_F int get_yearday(const civil_day& cd) { } // namespace detail } // namespace cctz -#undef CONSTEXPR_D -#undef CONSTEXPR_F -#undef CONSTEXPR_M #undef CONSTEXPR_T +#undef CONSTEXPR_M +#undef CONSTEXPR_F +#undef CONSTEXPR_D From 50c326abfd547082f2d2ca152c49408c82823520 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Mon, 4 Apr 2016 16:09:41 -0400 Subject: [PATCH 60/69] Field normalization: bypass the days_per_month loop when possible. --- include/civil_time_detail.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index ca5ac44a..1662c7ff 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -131,11 +131,13 @@ CONSTEXPR_F fields n_day(int y, int m, int d, int cd, int hh, int mm, int ss) { ++y; } } - for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) { - d -= n; - if (++m > 12) { - ++y; - m = 1; + if (d > 28) { + for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) { + d -= n; + if (++m > 12) { + ++y; + m = 1; + } } } return fields{y, m, d, hh, mm, ss}; From 3a7770d9adf4d225e8536e91f012366ed0c9d9fb Mon Sep 17 00:00:00 2001 From: Bradley White Date: Mon, 4 Apr 2016 16:30:58 -0400 Subject: [PATCH 61/69] Field normalization: replace k_days_per_4years[] with code. --- include/civil_time_detail.h | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 1662c7ff..652a8582 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -68,28 +68,8 @@ CONSTEXPR_F int days_per_century(int y, int m) { return 36524 + (yi == 0 || yi > 300); } CONSTEXPR_F int days_per_4years(int y, int m) { - // 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_D signed char k_days_per_4years[400] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 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, - }; - return 1460 + k_days_per_4years[year_index(y, m)]; + const int yi = year_index(y, m); + return 1460 + (yi == 0 || yi > 300 || (yi - 1) % 100 < 96); } CONSTEXPR_F int days_per_year(int y, int m) { return is_leap_year(y + (m > 2)) ? 366 : 365; From 5fc1d6c73e0449fb55f188f63d8795e1a86e80f7 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Tue, 5 Apr 2016 16:40:22 -0400 Subject: [PATCH 62/69] added comment about alignment conversions --- include/civil_time.h | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/include/civil_time.h b/include/civil_time.h index bdc1a557..3f6b194d 100644 --- a/include/civil_time.h +++ b/include/civil_time.h @@ -106,6 +106,28 @@ namespace cctz { // mm = civil_minute(hh); // 2015-01-01 00:00:00 // ss = civil_second(mm); // 2015-01-01 00:00:00 // +// ALIGNMENT CONVERSION: +// +// The alignment of a civil-time object cannot change, but the object may be +// used to construct a new object with a different alignment. This is referred +// to as "realigning". When realigning to a type with the same or more +// precision (e.g., civil_day -> civil_second), the conversion may be +// performed implicitly since no information is lost. However, if information +// could be discarded (e.g., civil_second -> civil_day), the conversion must +// be explicit at the call site. +// +// void fun(const civil_day& day); +// +// civil_second cs; +// fun(cs); // Won't compile because data may be discarded +// fun(civil_day(cs)); // OK: explicit conversion +// +// civil_day cd; +// fun(cd); // OK: no conversion needed +// +// civil_month cm; +// fun(cm); // OK: implicit conversion to civil_day +// // NORMALIZATION: // // Integer arguments passed to the constructor may be out-of-range, in which @@ -239,8 +261,8 @@ using detail::weekday; // using detail::get_weekday; -// Returns the civil_day that strictly follows or precedes the argument, -// and that falls on the given weekday. +// Returns the civil_day that strictly follows or precedes the given +// civil_day, and that falls on the given weekday. // // For example, given: // From f097aa554946f6c381e590ace9077592f8adfbb2 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 5 Apr 2016 16:54:32 -0400 Subject: [PATCH 63/69] Add "noexcept" declarations to the civil-time interface. --- include/civil_time_detail.h | 131 +++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 652a8582..8d6478c5 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -57,31 +57,32 @@ struct year_tag : month_tag {}; namespace impl { -CONSTEXPR_F bool is_leap_year(int y) { +CONSTEXPR_F bool is_leap_year(int y) noexcept { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } -CONSTEXPR_F int year_index(int y, int m) { +CONSTEXPR_F int year_index(int y, int m) noexcept { return (((y + (m > 2)) % 400) + 400) % 400; } -CONSTEXPR_F int days_per_century(int y, int m) { +CONSTEXPR_F int days_per_century(int y, int m) noexcept { const int yi = year_index(y, m); return 36524 + (yi == 0 || yi > 300); } -CONSTEXPR_F int days_per_4years(int y, int m) { +CONSTEXPR_F int days_per_4years(int y, int m) noexcept { const int yi = year_index(y, m); return 1460 + (yi == 0 || yi > 300 || (yi - 1) % 100 < 96); } -CONSTEXPR_F int days_per_year(int y, int m) { +CONSTEXPR_F int days_per_year(int y, int m) noexcept { return is_leap_year(y + (m > 2)) ? 366 : 365; } -CONSTEXPR_F int days_per_month(int y, int m) { +CONSTEXPR_F int days_per_month(int y, int m) noexcept { CONSTEXPR_D signed char k_days_per_month[12] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // non leap year }; return k_days_per_month[m - 1] + (m == 2 && is_leap_year(y)); } -CONSTEXPR_F fields n_day(int y, int m, int d, int cd, int hh, int mm, int ss) { +CONSTEXPR_F fields n_day(int y, int m, int d, int cd, int hh, int mm, + int ss) noexcept { y += (cd / 146097) * 400; cd %= 146097; if (cd < 0) { @@ -122,7 +123,8 @@ CONSTEXPR_F fields n_day(int y, int m, int d, int cd, int hh, int mm, int ss) { } return fields{y, m, d, hh, mm, ss}; } -CONSTEXPR_F fields n_mon(int y, int m, int d, int cd, int hh, int mm, int ss) { +CONSTEXPR_F fields n_mon(int y, int m, int d, int cd, int hh, int mm, + int ss) noexcept { y += m / 12; m %= 12; if (m <= 0) { @@ -131,7 +133,8 @@ CONSTEXPR_F fields n_mon(int y, int m, int d, int cd, int hh, int mm, int ss) { } return n_day(y, m, d, cd, hh, mm, ss); } -CONSTEXPR_F fields n_hour(int y, int m, int d, int cd, int hh, int mm, int ss) { +CONSTEXPR_F fields n_hour(int y, int m, int d, int cd, int hh, int mm, + int ss) noexcept { cd += hh / 24; hh %= 24; if (hh < 0) { @@ -140,7 +143,8 @@ CONSTEXPR_F fields n_hour(int y, int m, int d, int cd, int hh, int mm, int ss) { } return n_mon(y, m, d, cd, hh, mm, ss); } -CONSTEXPR_F fields n_min(int y, int m, int d, int hh, int ch, int mm, int ss) { +CONSTEXPR_F fields n_min(int y, int m, int d, int hh, int ch, int mm, + int ss) noexcept { ch += mm / 60; mm %= 60; if (mm < 0) { @@ -149,7 +153,7 @@ CONSTEXPR_F fields n_min(int y, int m, int d, int hh, int ch, int mm, int ss) { } return n_hour(y, m, d, hh / 24 + ch / 24, hh % 24 + ch % 24, mm, ss); } -CONSTEXPR_F fields n_sec(int y, int m, int d, int hh, int mm, int ss) { +CONSTEXPR_F fields n_sec(int y, int m, int d, int hh, int mm, int ss) noexcept { int cm = ss / 60; ss %= 60; if (ss < 0) { @@ -164,44 +168,44 @@ CONSTEXPR_F fields n_sec(int y, int m, int d, int hh, int mm, int ss) { //////////////////////////////////////////////////////////////////////// // Increments the indicated (normalized) field by "n". -CONSTEXPR_F fields step(second_tag, fields f, int n) { +CONSTEXPR_F fields step(second_tag, fields f, int n) noexcept { return impl::n_sec(f.y, f.m, f.d, f.hh, f.mm + n / 60, f.ss + n % 60); } -CONSTEXPR_F fields step(minute_tag, fields f, int n) { +CONSTEXPR_F fields step(minute_tag, fields f, int n) noexcept { return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss); } -CONSTEXPR_F fields step(hour_tag, fields f, int n) { +CONSTEXPR_F fields step(hour_tag, fields f, int n) noexcept { return impl::n_hour(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss); } -CONSTEXPR_F fields step(day_tag, fields f, int n) { +CONSTEXPR_F fields step(day_tag, fields f, int n) noexcept { return impl::n_day(f.y, f.m, f.d, n, f.hh, f.mm, f.ss); } -CONSTEXPR_F fields step(month_tag, fields f, int n) { +CONSTEXPR_F fields step(month_tag, fields f, int n) noexcept { return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss); } -CONSTEXPR_F fields step(year_tag, fields f, int n) { +CONSTEXPR_F fields step(year_tag, fields f, int n) noexcept { return fields{f.y + n, f.m, f.d, f.hh, f.mm, f.ss}; } //////////////////////////////////////////////////////////////////////// // Aligns the (normalized) fields struct to the indicated field. -CONSTEXPR_F fields align(second_tag, fields f) { +CONSTEXPR_F fields align(second_tag, fields f) noexcept { return f; } -CONSTEXPR_F fields align(minute_tag, fields f) { +CONSTEXPR_F fields align(minute_tag, fields f) noexcept { return fields{f.y, f.m, f.d, f.hh, f.mm, 0}; } -CONSTEXPR_F fields align(hour_tag, fields f) { +CONSTEXPR_F fields align(hour_tag, fields f) noexcept { return fields{f.y, f.m, f.d, f.hh, 0, 0}; } -CONSTEXPR_F fields align(day_tag, fields f) { +CONSTEXPR_F fields align(day_tag, fields f) noexcept { return fields{f.y, f.m, f.d, 0, 0, 0}; } -CONSTEXPR_F fields align(month_tag, fields f) { +CONSTEXPR_F fields align(month_tag, fields f) noexcept { return fields{f.y, f.m, 1, 0, 0, 0}; } -CONSTEXPR_F fields align(year_tag, fields f) { +CONSTEXPR_F fields align(year_tag, fields f) noexcept { return fields{f.y, 1, 1, 0, 0, 0}; } @@ -211,7 +215,7 @@ 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]. -CONSTEXPR_F int ymd_ord(int y, int m, int d) { +CONSTEXPR_F int ymd_ord(int y, int m, int d) noexcept { const int eyear = (m <= 2) ? y - 1 : y; const int era = (eyear >= 0 ? eyear : eyear - 399) / 400; const int yoe = eyear - era * 400; @@ -223,22 +227,22 @@ CONSTEXPR_F int ymd_ord(int y, int m, int d) { } // namespace impl // Returns the difference between fields structs using the indicated unit. -CONSTEXPR_F int difference(year_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(year_tag, fields f1, fields f2) noexcept { return f1.y - f2.y; } -CONSTEXPR_F int difference(month_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(month_tag, fields f1, fields f2) noexcept { return difference(year_tag{}, f1, f2) * 12 + (f1.m - f2.m); } -CONSTEXPR_F int difference(day_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(day_tag, fields f1, fields f2) noexcept { return impl::ymd_ord(f1.y, f1.m, f1.d) - impl::ymd_ord(f2.y, f2.m, f2.d); } -CONSTEXPR_F int difference(hour_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(hour_tag, fields f1, fields f2) noexcept { return difference(day_tag{}, f1, f2) * 24 + (f1.hh - f2.hh); } -CONSTEXPR_F int difference(minute_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(minute_tag, fields f1, fields f2) noexcept { return difference(hour_tag{}, f1, f2) * 60 + (f1.mm - f2.mm); } -CONSTEXPR_F int difference(second_tag, fields f1, fields f2) { +CONSTEXPR_F int difference(second_tag, fields f1, fields f2) noexcept { return difference(minute_tag{}, f1, f2) * 60 + (f1.ss - f2.ss); } @@ -248,10 +252,10 @@ template class civil_time { public: explicit CONSTEXPR_M civil_time(int y, int m = 1, int d = 1, int hh = 0, - int mm = 0, int ss = 0) + int mm = 0, int ss = 0) noexcept : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {} - CONSTEXPR_M civil_time() : civil_time(1970) {} + CONSTEXPR_M civil_time() noexcept : civil_time(1970) {} CONSTEXPR_M civil_time(const civil_time&) = default; civil_time& operator=(const civil_time&) = default; @@ -264,27 +268,27 @@ class civil_time { typename std::enable_if::value>::type; template CONSTEXPR_M civil_time(const civil_time& ct, - preserves_data* = nullptr) + preserves_data* = nullptr) noexcept : civil_time(ct.f_) {} template explicit CONSTEXPR_M civil_time(const civil_time& ct, - preserves_data* = nullptr) + preserves_data* = nullptr) noexcept : civil_time(ct.f_) {} // Field accessors. - CONSTEXPR_M int year() const { return f_.y; } - CONSTEXPR_M int month() const { return f_.m; } - CONSTEXPR_M int day() const { return f_.d; } - CONSTEXPR_M int hour() const { return f_.hh; } - CONSTEXPR_M int minute() const { return f_.mm; } - CONSTEXPR_M int second() const { return f_.ss; } + CONSTEXPR_M int year() const noexcept { return f_.y; } + CONSTEXPR_M int month() const noexcept { return f_.m; } + CONSTEXPR_M int day() const noexcept { return f_.d; } + CONSTEXPR_M int hour() const noexcept { return f_.hh; } + CONSTEXPR_M int minute() const noexcept { return f_.mm; } + CONSTEXPR_M int second() const noexcept { return f_.ss; } // Assigning arithmetic. - CONSTEXPR_M civil_time& operator+=(int n) { + CONSTEXPR_M civil_time& operator+=(int n) noexcept { f_ = step(T{}, f_, n); return *this; } - CONSTEXPR_M civil_time& operator-=(int n) { + CONSTEXPR_M civil_time& operator-=(int n) noexcept { if (n != std::numeric_limits::min()) { f_ = step(T{}, f_, -n); } else { @@ -292,35 +296,38 @@ class civil_time { } return *this; } - CONSTEXPR_M civil_time& operator++() { + CONSTEXPR_M civil_time& operator++() noexcept { return *this += 1; } - CONSTEXPR_M civil_time operator++(int) { + CONSTEXPR_M civil_time operator++(int) noexcept { const civil_time a = *this; ++*this; return a; } - CONSTEXPR_M civil_time& operator--() { + CONSTEXPR_M civil_time& operator--() noexcept { return *this -= 1; } - CONSTEXPR_M civil_time operator--(int) { + CONSTEXPR_M civil_time operator--(int) noexcept { const civil_time a = *this; --*this; return a; } // Binary arithmetic operators. - inline friend CONSTEXPR_M civil_time operator+(const civil_time& a, int n) { + inline friend CONSTEXPR_M civil_time operator+(const civil_time& a, + int n) noexcept { return civil_time(step(T{}, a.f_, n)); } - inline friend CONSTEXPR_M civil_time operator+(int n, const civil_time& a) { + inline friend CONSTEXPR_M civil_time operator+(int n, + const civil_time& a) noexcept { return civil_time(step(T{}, a.f_, n)); } - inline friend CONSTEXPR_M civil_time operator-(const civil_time& a, int n) { + inline friend CONSTEXPR_M civil_time operator-(const civil_time& a, + int n) noexcept { return civil_time(step(T{}, a.f_, -n)); } inline friend CONSTEXPR_M int operator-(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { return difference(T{}, lhs.f_, rhs.f_); } @@ -331,7 +338,7 @@ class civil_time { friend class civil_time; // The designated constructor that all others eventually call. - explicit CONSTEXPR_M civil_time(fields f) : f_(align(T{}, f)) {} + explicit CONSTEXPR_M civil_time(fields f) noexcept : f_(align(T{}, f)) {} fields f_; }; @@ -349,7 +356,7 @@ using civil_second = civil_time; // Always compares all six fields. template CONSTEXPR_T bool operator<(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { return (lhs.year() < rhs.year() || (lhs.year() == rhs.year() && (lhs.month() < rhs.month() || @@ -364,29 +371,29 @@ CONSTEXPR_T bool operator<(const civil_time& lhs, } template CONSTEXPR_T bool operator<=(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { return !(rhs < lhs); } template CONSTEXPR_T bool operator>=(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { return !(lhs < rhs); } template CONSTEXPR_T bool operator>(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { return rhs < lhs; } template CONSTEXPR_T bool operator==(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { 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_T bool operator!=(const civil_time& lhs, - const civil_time& rhs) { + const civil_time& rhs) noexcept { return !(lhs == rhs); } @@ -462,7 +469,7 @@ inline std::ostream& operator<<(std::ostream& os, weekday wd) { } } -CONSTEXPR_F weekday get_weekday(const civil_day& cd) { +CONSTEXPR_F weekday get_weekday(const civil_day& cd) noexcept { CONSTEXPR_D weekday k_weekday_by_thu_off[] = { weekday::thursday, weekday::friday, weekday::saturday, weekday::sunday, weekday::monday, weekday::tuesday, @@ -473,17 +480,17 @@ CONSTEXPR_F weekday get_weekday(const civil_day& cd) { //////////////////////////////////////////////////////////////////////// -CONSTEXPR_F civil_day next_weekday(civil_day cd, weekday wd) { +CONSTEXPR_F civil_day next_weekday(civil_day cd, weekday wd) noexcept { do { cd += 1; } while (get_weekday(cd) != wd); return cd; } -CONSTEXPR_F civil_day prev_weekday(civil_day cd, weekday wd) { +CONSTEXPR_F civil_day prev_weekday(civil_day cd, weekday wd) noexcept { do { cd -= 1; } while (get_weekday(cd) != wd); return cd; } -CONSTEXPR_F int get_yearday(const civil_day& cd) { +CONSTEXPR_F int get_yearday(const civil_day& cd) noexcept { return cd - civil_day(civil_year(cd)) + 1; } From f1b70eef4c2e14e9343876ebbe8b0c2184181965 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Tue, 5 Apr 2016 17:03:38 -0400 Subject: [PATCH 64/69] made op<< take const-refs --- include/civil_time_detail.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 8d6478c5..12d922d5 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -402,36 +402,36 @@ CONSTEXPR_T bool operator!=(const civil_time& lhs, // Output stream operators output a format matching YYYY-MM-DDThh:mm:ss, // while omitting fields inferior to the type's alignment. For example, // civil_day is formatted only as YYYY-MM-DD. -inline std::ostream& operator<<(std::ostream& os, civil_year y) { +inline std::ostream& operator<<(std::ostream& os, const civil_year& y) { std::stringstream ss; ss << y.year(); // No padding. return os << ss.str(); } -inline std::ostream& operator<<(std::ostream& os, civil_month m) { +inline std::ostream& operator<<(std::ostream& os, const civil_month& m) { std::stringstream ss; ss << civil_year(m) << '-'; ss << std::setfill('0') << std::setw(2) << m.month(); return os << ss.str(); } -inline std::ostream& operator<<(std::ostream& os, civil_day d) { +inline std::ostream& operator<<(std::ostream& os, const civil_day& d) { std::stringstream ss; ss << civil_month(d) << '-'; ss << std::setfill('0') << std::setw(2) << d.day(); return os << ss.str(); } -inline std::ostream& operator<<(std::ostream& os, civil_hour h) { +inline std::ostream& operator<<(std::ostream& os, const civil_hour& h) { std::stringstream ss; ss << civil_day(h) << 'T'; ss << std::setfill('0') << std::setw(2) << h.hour(); return os << ss.str(); } -inline std::ostream& operator<<(std::ostream& os, civil_minute m) { +inline std::ostream& operator<<(std::ostream& os, const civil_minute& m) { std::stringstream ss; ss << civil_hour(m) << ':'; ss << std::setfill('0') << std::setw(2) << m.minute(); return os << ss.str(); } -inline std::ostream& operator<<(std::ostream& os, civil_second s) { +inline std::ostream& operator<<(std::ostream& os, const civil_second& s) { std::stringstream ss; ss << civil_minute(s) << ':'; ss << std::setfill('0') << std::setw(2) << s.second(); From 9776feeb9a429e853ae81e5bce9aad816d1a7db4 Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Tue, 5 Apr 2016 17:15:30 -0400 Subject: [PATCH 65/69] Added docs about streaming --- include/civil_time.h | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/include/civil_time.h b/include/civil_time.h index 3f6b194d..ee34a9ac 100644 --- a/include/civil_time.h +++ b/include/civil_time.h @@ -176,6 +176,21 @@ namespace cctz { // // ... // } // +// STREAMING: +// +// Each civil-time type may be sent to an output stream using operator<<(). +// The output format follows the pattern "YYYY-MM-DDThh:mm:ss" where fields +// inferior to the type's alignment are omitted. +// +// civil_second cs(2015, 2, 3, 4, 5, 6); +// std::cout << cs << "\n"; // Outputs: 2015-02-03T04:05:06 +// +// civil_day cd(cs); +// std::cout << cd << "\n"; // Outputs: 2015-02-03 +// +// civil_year cy(cs); +// std::cout << cy << "\n"; // Outputs: 2015 +// // ARITHMETIC: // // Civil-time types support natural arithmetic operators such as addition, @@ -250,8 +265,14 @@ 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. +// An enum class with members monday, tuesday, wednesday, thursday, friday, +// saturday, and sunday. These enum values may be sent to an output stream +// using operator<<(). The result is the full weekday name in English with a +// leading capital letter. +// +// weekday wd = weekday::thursday; +// std::cout << wd << "\n"; // Outputs: Thursday +// using detail::weekday; // Returns the weekday for the given civil_day. From c1d2fcf275025efef0135c0605f9d777f2f14ebc Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Tue, 5 Apr 2016 17:17:22 -0400 Subject: [PATCH 66/69] added comment about constexpr --- include/civil_time.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/civil_time.h b/include/civil_time.h index ee34a9ac..4071822e 100644 --- a/include/civil_time.h +++ b/include/civil_time.h @@ -80,6 +80,8 @@ namespace cctz { // a handful of helper functions and algorithms for performing common // calculations. These are described below. // +// Note: In C++14 and later, this library is usable in a constexpr context. +// // CONSTRUCTION: // // Each of the civil-time types can be constructed in two ways: by directly From 94299aba9aeab4e983a909ff93e1b2b645e90f36 Mon Sep 17 00:00:00 2001 From: Bradley White Date: Tue, 5 Apr 2016 17:36:20 -0400 Subject: [PATCH 67/69] Remove redundant constexpr from defaulted constructor. --- include/civil_time_detail.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 12d922d5..245dc6d0 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -256,7 +256,7 @@ class civil_time { : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {} CONSTEXPR_M civil_time() noexcept : civil_time(1970) {} - CONSTEXPR_M civil_time(const civil_time&) = default; + civil_time(const civil_time&) = default; civil_time& operator=(const civil_time&) = default; // Conversion between civil times of different alignment. Conversion to From 2a934690ce73170ff80897f3b0f8bf6016ff430d Mon Sep 17 00:00:00 2001 From: Greg Miller Date: Wed, 6 Apr 2016 16:39:32 -0400 Subject: [PATCH 68/69] . --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1940792d..d40325fb 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,17 @@ us if you're interested in contributing. # Getting Started -CCTZ is built using the [Bazel](http://bazel.io) build system and tested using -the [Google Test](https://github.com/google/googletest) framework. +CCTZ is best built and tested using the [Bazel](http://bazel.io) build system +and the [Google Test](https://github.com/google/googletest) framework. (There +is also a simple `Makefile` that should work if you're unable to use Bazel.) 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` -Note: If you're unable to use bazel, there is also a `Makefile` that should help -get you started. When using CCTZ in your own project, you might find it easiest -to compile the sources using your existing build system. +Note: When using CCTZ in your own project, you might find it easiest to compile +the sources using your existing build system. Next Steps: @@ -44,7 +44,7 @@ Next Steps: *[The concepts presented here describe general truths about the problem domain and are library and language agnostic. An understanding of these concepts helps -the programmer correctly reason about even the most complicated time-programming +the programmer correctly reason about even the most-complicated time-programming challenges and produce the simplest possible solutions.]* There are two main ways to think about time in a computer program: as *absolute @@ -109,6 +109,7 @@ day happens to be the 29th, we also output the day of the week. ``` #include #include "civil_time.h" + int main() { for (cctz::civil_day d(2016, 2, 1); d < cctz::civil_month(2016, 3); ++d) { std::cout << "Hello " << d; @@ -143,6 +144,7 @@ like to see what time it was for our friend watching in Sydney Australia. #include #include "civil_time.h" #include "time_zone.h" + int main() { cctz::time_zone nyc; cctz::load_time_zone("America/New_York", &nyc); From 582cab41ed1f6d7794277a0c479bda126a8caadf Mon Sep 17 00:00:00 2001 From: Bradley White Date: Wed, 6 Apr 2016 16:41:00 -0400 Subject: [PATCH 69/69] Add max()/min() factories for all civil_time types. --- include/civil_time_detail.h | 8 ++++++++ src/civil_time_test.cc | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/include/civil_time_detail.h b/include/civil_time_detail.h index 245dc6d0..976b39d4 100644 --- a/include/civil_time_detail.h +++ b/include/civil_time_detail.h @@ -275,6 +275,14 @@ class civil_time { preserves_data* = nullptr) noexcept : civil_time(ct.f_) {} + // Factories for the maximum/minimum representable civil_time. + static civil_time max() { + return civil_time(std::numeric_limits::max(), 12, 31, 23, 59, 59); + } + static civil_time min() { + return civil_time(std::numeric_limits::min(), 1, 1, 0, 0, 0); + } + // Field accessors. CONSTEXPR_M int year() const noexcept { return f_.y; } CONSTEXPR_M int month() const noexcept { return f_.m; } diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc index 92f9fd7d..d3e8a636 100644 --- a/src/civil_time_test.cc +++ b/src/civil_time_test.cc @@ -363,6 +363,25 @@ TEST(CivilTime, FieldsConstructionLimits) { Format(civil_second(1970, kIntMin, kIntMin, kIntMin, kIntMin, kIntMin))); } +TEST(CivilTime, RangeLimits) { + const int kIntMax = std::numeric_limits::max(); + const int kIntMin = std::numeric_limits::min(); + + EXPECT_EQ(civil_year(kIntMax), civil_year::max()); + EXPECT_EQ(civil_month(kIntMax, 12), civil_month::max()); + EXPECT_EQ(civil_day(kIntMax, 12, 31), civil_day::max()); + EXPECT_EQ(civil_hour(kIntMax, 12, 31, 23), civil_hour::max()); + EXPECT_EQ(civil_minute(kIntMax, 12, 31, 23, 59), civil_minute::max()); + EXPECT_EQ(civil_second(kIntMax, 12, 31, 23, 59, 59), civil_second::max()); + + EXPECT_EQ(civil_year(kIntMin), civil_year::min()); + EXPECT_EQ(civil_month(kIntMin, 1), civil_month::min()); + EXPECT_EQ(civil_day(kIntMin, 1, 1), civil_day::min()); + EXPECT_EQ(civil_hour(kIntMin, 1, 1, 0), civil_hour::min()); + EXPECT_EQ(civil_minute(kIntMin, 1, 1, 0, 0), civil_minute::min()); + EXPECT_EQ(civil_second(kIntMin, 1, 1, 0, 0, 0), civil_second::min()); +} + TEST(CivilTime, ImplicitCrossAlignment) { civil_year year(2015); civil_month month = year;