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/BUILD b/BUILD new file mode 100644 index 00000000..5d9cdc3c --- /dev/null +++ b/BUILD @@ -0,0 +1,208 @@ +# 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 = "civil_time", + hdrs = [ + "include/civil_time.h", + ], + includes = ["include"], + textual_hdrs = ["include/civil_time_detail.h"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "time_zone", + 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 = [ + "include/time_zone.h", + ], + includes = ["include"], + linkopts = [ + "-lm", + "-lpthread", + ], + visibility = ["//visibility:public"], + deps = [":civil_time"], +) + +cc_library( + name = "cctz_v1", + hdrs = [ + "src/cctz.h", + ], + includes = ["include"], + visibility = ["//visibility:public"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +### 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", + ":civil_time", + ], +) + +cc_test( + name = "time_zone_format_test", + size = "small", + srcs = ["src/time_zone_format_test.cc"], + deps = [ + "@gtest//:gtest", + ":civil_time", + ":time_zone", + ], +) + +cc_test( + name = "time_zone_lookup_test", + size = "small", + srcs = ["src/time_zone_lookup_test.cc"], + deps = [ + "@gtest//:gtest", + ":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", + ], +) + +### examples + +cc_binary( + name = "classic", + srcs = ["examples/classic.cc"], +) + +cc_binary( + name = "epoch_shift", + srcs = ["examples/epoch_shift.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +cc_binary( + name = "example1", + srcs = ["examples/example1.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +cc_binary( + name = "example2", + srcs = ["examples/example2.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +cc_binary( + name = "example3", + srcs = ["examples/example3.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +cc_binary( + name = "example4", + srcs = ["examples/example4.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +cc_binary( + name = "hello", + srcs = ["examples/hello.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) + +### binaries + +cc_binary( + name = "time_tool", + srcs = ["src/time_tool.cc"], + deps = [ + ":civil_time", + ":time_zone", + ], +) 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..b97feded --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +# 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. + +# 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 = g++ +STD = c++11 +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) +CPPFLAGS = -Wall -I$(SRC)include -std=$(STD) -pthread \ + $(TEST_FLAGS) $(OPT) -fPIC -MMD +ARFLAGS = rcs +LDFLAGS = -pthread +LDLIBS = $(TEST_LIBS) + +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 + +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) -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) + +-include $(CCTZ_OBJS:.o=.d) $(TESTS:=.d) $(TOOLS:=.d) $(EXAMPLES:=.d) diff --git a/README.md b/README.md index 6cb126e6..d40325fb 100644 --- a/README.md +++ b/README.md @@ -2,82 +2,188 @@ 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 -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 +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: When using CCTZ in your own project, you might find it easiest to compile +the sources using your existing build system. + +Next Steps: + +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 +3. Join our mailing list to ask questions and keep informed of changes: + * https://groups.google.com/forum/#!forum/cctz # 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. 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 + +## 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 [cctz.h](https://github.com/google/cctz/blob/master/src/cctz.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 + +* 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. +* 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 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..b8f28958 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 @@ -20,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; @@ -31,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 c2c6a576..080a2c99 100644 --- a/examples/epoch_shift.cc +++ b/examples/epoch_shift.cc @@ -1,24 +1,23 @@ -// 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 -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; @@ -37,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"); diff --git a/examples/example1.cc b/examples/example1.cc index 12cc90d8..25896d39 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 = cctz::convert(cctz::civil_second(2015, 9, 22, 9, 0, 0), lax); - 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..216acca4 100644 --- a/examples/example3.cc +++ b/examples/example3.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); const auto now = std::chrono::system_clock::now(); - const cctz::Breakdown bd = cctz::BreakTime(now, lax); + const cctz::civil_second cs = cctz::convert(now, lax); // 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 = 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); + 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..7e43f727 100644 --- a/examples/example4.cc +++ b/examples/example4.cc @@ -1,37 +1,34 @@ -// 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) { + return cctz::convert(cctz::civil_day(cctz::convert(tp, tz)), tz); } 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..52882b7d 100644 --- a/examples/hello.cc +++ b/examples/hello.cc @@ -1,37 +1,39 @@ -// 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 = + cctz::convert(cctz::civil_second(1969, 7, 21, 12, 56, 0), syd); - 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 = + cctz::convert(cctz::civil_second(1969, 7, 20, 22, 56, 0), nyc); return tp2 == tp1 ? 0 : 1; } diff --git a/include/civil_time.h b/include/civil_time.h new file mode 100644 index 00000000..4071822e --- /dev/null +++ b/include/civil_time.h @@ -0,0 +1,325 @@ +// 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_CIVIL_TIME_H_ +#define CCTZ_CIVIL_TIME_H_ + +#include "civil_time_detail.h" + +namespace cctz { + +// 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 following six classes form the core of this civil-time library: +// +// * 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 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. +// +// 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". 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 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 +// 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 +// 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 +// 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 +// 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 default_value; // 1970-01-01 00:00:00 +// +// 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 +// +// 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 +// +// 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 +// +// 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 +// 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 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 constructor arguments to the normalized values returned by the YMDHMS +// properties. +// +// PROPERTIES: +// +// 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() == 6 +// // d.day() == 28 +// // d.hour() == 0 +// // d.minute() == 0 +// // d.second() == 0 +// +// COMPARISON: +// +// 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 February 2015. +// for (civil_day d(2015, 2, 1); d < civil_month(2015, 3); ++d) { +// // ... +// } +// +// 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, +// 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 (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 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: +// +// 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 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 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); +// +// // 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 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 the normalized answer is not in next month. +// if (civil_month(ans_normalized) != next_month) { +// // error, month overflow +// } +// +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. 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. +// +// civil_day a(2015, 8, 13); +// weekday wd = get_weekday(a); // wd == weekday::thursday +// +using detail::get_weekday; + +// Returns the civil_day that strictly follows or precedes the given +// civil_day, 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); // 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 +// +// civil_day d = ... +// // Gets the following Thursday if d is not already Thursday +// civil_day thurs1 = prev_weekday(d, weekday::thursday) + 7; +// // Gets the previous Thursday if d is not already Thursday +// civil_day thurs2 = next_weekday(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/include/civil_time_detail.h b/include/civil_time_detail.h new file mode 100644 index 00000000..976b39d4 --- /dev/null +++ b/include/civil_time_detail.h @@ -0,0 +1,511 @@ +// 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 +#include +#include +#include +#include + +// Disable constexpr support unless we are using clang in C++14 mode. +#if __clang__ && __cpp_constexpr >= 201304 +#define CONSTEXPR_D constexpr // data +#define CONSTEXPR_F constexpr // function +#define CONSTEXPR_M constexpr // member +#define CONSTEXPR_T constexpr // template +#else +#define CONSTEXPR_D const +#define CONSTEXPR_F inline +#define CONSTEXPR_M +#define CONSTEXPR_T +#endif + +namespace cctz { +namespace detail { + +// 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 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 {}; + +//////////////////////////////////////////////////////////////////////// + +// Field normalization (without avoidable overflow). + +namespace impl { + +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) noexcept { + return (((y + (m > 2)) % 400) + 400) % 400; +} +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) 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) noexcept { + return is_leap_year(y + (m > 2)) ? 366 : 365; +} +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 // 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) noexcept { + y += (cd / 146097) * 400; + cd %= 146097; + if (cd < 0) { + y -= 400; + cd += 146097; + } + y += (d / 146097) * 400; + d = d % 146097 + cd; + if (d <= 0) { + y -= 400; + d += 146097; + } else if (d > 146097) { + y += 400; + d -= 146097; + } + 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; + } + } + 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}; +} +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) { + y -= 1; + m += 12; + } + 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) noexcept { + cd += hh / 24; + hh %= 24; + if (hh < 0) { + cd -= 1; + hh += 24; + } + 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) noexcept { + ch += mm / 60; + mm %= 60; + if (mm < 0) { + ch -= 1; + mm += 60; + } + 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) noexcept { + int cm = ss / 60; + ss %= 60; + if (ss < 0) { + cm -= 1; + ss += 60; + } + return n_min(y, m, d, hh, mm / 60 + cm / 60, mm % 60 + cm % 60, ss); +} + +} // namespace impl + +//////////////////////////////////////////////////////////////////////// + +// Increments the indicated (normalized) field by "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) 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) 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) 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) 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) 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) noexcept { + return 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) noexcept { + return fields{f.y, f.m, f.d, f.hh, 0, 0}; +} +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) noexcept { + return fields{f.y, f.m, 1, 0, 0, 0}; +} +CONSTEXPR_F fields align(year_tag, fields f) noexcept { + return fields{f.y, 1, 1, 0, 0, 0}; +} + +//////////////////////////////////////////////////////////////////////// + +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) noexcept { + 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) noexcept { + return f1.y - f2.y; +} +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) 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) noexcept { + return difference(day_tag{}, f1, f2) * 24 + (f1.hh - f2.hh); +} +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) noexcept { + return difference(minute_tag{}, f1, f2) * 60 + (f1.ss - f2.ss); +} + +//////////////////////////////////////////////////////////////////////// + +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) noexcept + : civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {} + + CONSTEXPR_M civil_time() noexcept : civil_time(1970) {} + civil_time(const civil_time&) = default; + civil_time& operator=(const civil_time&) = default; + + // 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 + CONSTEXPR_M civil_time(const civil_time& ct, + preserves_data* = nullptr) noexcept + : civil_time(ct.f_) {} + template + explicit CONSTEXPR_M civil_time(const civil_time& ct, + 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; } + 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) noexcept { + f_ = step(T{}, f_, n); + return *this; + } + CONSTEXPR_M civil_time& operator-=(int n) noexcept { + if (n != std::numeric_limits::min()) { + f_ = step(T{}, f_, -n); + } else { + f_ = step(T(), step(T{}, f_, -(n + 1)), 1); + } + return *this; + } + CONSTEXPR_M civil_time& operator++() noexcept { + return *this += 1; + } + CONSTEXPR_M civil_time operator++(int) noexcept { + const civil_time a = *this; + ++*this; + return a; + } + CONSTEXPR_M civil_time& operator--() noexcept { + return *this -= 1; + } + 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) noexcept { + return civil_time(step(T{}, a.f_, n)); + } + 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) noexcept { + return civil_time(step(T{}, a.f_, -n)); + } + inline friend CONSTEXPR_M int operator-(const civil_time& lhs, + const civil_time& rhs) noexcept { + 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_M civil_time(fields f) noexcept : 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_T bool operator<(const civil_time& lhs, + const civil_time& rhs) noexcept { + 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()))))))))))); +} +template +CONSTEXPR_T bool operator<=(const civil_time& lhs, + const civil_time& rhs) noexcept { + return !(rhs < lhs); +} +template +CONSTEXPR_T bool operator>=(const civil_time& lhs, + const civil_time& rhs) noexcept { + return !(lhs < rhs); +} +template +CONSTEXPR_T bool operator>(const civil_time& lhs, + const civil_time& rhs) noexcept { + return rhs < lhs; +} +template +CONSTEXPR_T bool operator==(const civil_time& lhs, + 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) noexcept { + return !(lhs == rhs); +} + +//////////////////////////////////////////////////////////////////////// + +// 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, const civil_year& y) { + std::stringstream ss; + ss << y.year(); // No padding. + return os << ss.str(); +} +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, 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, 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, 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, const civil_second& s) { + std::stringstream ss; + ss << civil_minute(s) << ':'; + ss << std::setfill('0') << std::setw(2) << s.second(); + return os << ss.str(); +} + +//////////////////////////////////////////////////////////////////////// + +enum class weekday { + monday, + tuesday, + wednesday, + thursday, + friday, + saturday, + sunday, +}; + +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"; + } +} + +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, + weekday::wednesday, + }; + return k_weekday_by_thu_off[((cd - civil_day()) % 7 + 7) % 7]; +} + +//////////////////////////////////////////////////////////////////////// + +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) noexcept { + do { cd -= 1; } while (get_weekday(cd) != wd); + return cd; +} + +CONSTEXPR_F int get_yearday(const civil_day& cd) noexcept { + return cd - civil_day(civil_year(cd)) + 1; +} + +} // namespace detail +} // namespace cctz + +#undef CONSTEXPR_T +#undef CONSTEXPR_M +#undef CONSTEXPR_F +#undef CONSTEXPR_D diff --git a/include/time_zone.h b/include/time_zone.h new file mode 100644 index 00000000..3bd3a012 --- /dev/null +++ b/include/time_zone.h @@ -0,0 +1,283 @@ +// 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; + + // 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 = ... + // const auto tp = std::chrono::system_clock::now(); + // 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; // civil 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 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. + // + // 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; + // 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(); + +// 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; +} + +// 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); + if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans; + return cl.pre; +} + +namespace detail { +template +inline std::pair, D> +split_seconds(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)}; +} +inline std::pair, sys_seconds> +split_seconds(const time_point& tp) { + return {tp, sys_seconds(0)}; +} +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, +// 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. +// +// 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; +// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... } +// 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" +template +inline std::string format(const std::string& fmt, const time_point& tp, + const time_zone& tz) { + const auto p = detail::split_seconds(tp); + const auto n = std::chrono::duration_cast(p.second); + return detail::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 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 +// 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 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)) { +// ... +// } +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 = detail::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/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 index d5b073d1..650a1fe7 100644 --- a/src/cctz.h +++ b/src/cctz.h @@ -1,353 +1,162 @@ -// 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. - -// 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" -// } +// 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 -#include +#include -namespace cctz { +#include "civil_time.h" +#include "time_zone.h" -// 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(); +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); +} -// 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") + 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; }; - -// 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); +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}; +} -// 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); +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; +} -// 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 + UNIQUE, + SKIPPED, + REPEATED, } kind; - time_point pre; // Uses the pre-transition offset + time_point pre; time_point trans; - time_point post; // Uses the post-transition offset + time_point post; 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); +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 {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); + return {kind, cl.pre, cl.trans, cl.post, norm}; } -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, +inline std::string Format(const std::string& fmt, 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); + return format(fmt, tp, 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, +inline bool Parse(const std::string& fmt, 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; + return parse(fmt, input, tz, tpp); } +} // namespace deprecated_v1_api } // 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/test/cnv_test.cc b/src/cctz_v1_test.cc similarity index 98% rename from test/cnv_test.cc rename to src/cctz_v1_test.cc index 79b93b5b..0705dbd2 100644 --- a/test/cnv_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 @@ -1104,6 +1109,8 @@ TEST(TimeZoneEdgeCase, UTC5DigitYear) { 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) @@ -1123,5 +1130,6 @@ TEST(TimeZoneEdgeCase, West64bitLimit) { EXPECT_EQ(std::numeric_limits::min() + 12 * 3600, tp.time_since_epoch().count()); } +#endif } // namespace cctz diff --git a/src/civil_time_test.cc b/src/civil_time_test.cc new file mode 100644 index 00000000..d3e8a636 --- /dev/null +++ b/src/civil_time_test.cc @@ -0,0 +1,1003 @@ +// 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 +#include + +#include "gtest/gtest.h" + +namespace cctz { + +namespace { + +template +std::string Format(const T& t) { + std::stringstream ss; + ss << t; + return ss.str(); +} + +} // namespace + +#if __clang__ && __cpp_constexpr >= 201304 +// 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 constexpr tests + +TEST(CivilTime, Normalized) { + constexpr civil_second cs(2016, 1, 28, 17, 14, 12); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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 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; + static_assert(less, "Less"); +} + +// Arithmetic constexpr tests + +TEST(CivilTime, Addition) { + constexpr civil_second cs1(2016, 1, 28, 17, 14, 12); + constexpr civil_second cs2 = cs1 + 50; + 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; + 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; + static_assert(diff == 365, "Diff"); +} + +// Helper constexpr tests + +TEST(CivilTime, WeekDay) { + constexpr civil_day cd(2016, 1, 28); + constexpr weekday wd = get_weekday(cd); + 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); + 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); + 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); + static_assert(yd == 28, "YearDay"); +} +#endif // __clang__ && __cpp_constexpr >= 201304 + +// The remaining tests do not use constexpr. + +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, 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; + 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 + // + + 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_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); \ + 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) { + 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; + 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)); + EXPECT_EQ(34, get_yearday(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, 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, 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); + EXPECT_EQ(weekday::thursday, get_weekday(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 55% rename from tools/time_tool.cc rename to src/time_tool.cc index 4ae5f3d4..05fbe0ef 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,12 @@ 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::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 = cctz::BreakTime(tp, utc); + time_point tp; + if (cctz::parse(*fmt, args, utc, &tp)) { + *when = cctz::convert(tp, utc); return true; } } @@ -70,24 +85,36 @@ 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 - << " dst=" << (bd.is_dst ? 'T' : 'F') - << " off=" << std::showpos << bd.offset << std::noshowpos << "]"; + oss << std::setw(33) << std::left << cctz::format(kFormat, when, zone); + 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(); } -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,39 +132,37 @@ 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); - switch (ti.kind) { - case cctz::TimeInfo::Kind::UNIQUE: { +// 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::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); - 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::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); - 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; } } 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 +200,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 +210,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 +222,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 +239,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::civil_second when = cctz::convert(tp, zone); bool have_break_down = !have_time && ParseBreakdownSpec(args, &when); // Show results. diff --git a/src/cctz_fmt.cc b/src/time_zone_format.cc similarity index 85% rename from src/cctz_fmt.cc rename to src/time_zone_format.cc index b6aaee2f..5ef9126c 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 @@ -35,6 +34,7 @@ #endif namespace cctz { +namespace detail { namespace { @@ -48,26 +48,48 @@ 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& al) { 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 = 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.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.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.year - 1900); + tm.tm_year = static_cast(al.cs.year() - 1900); } - tm.tm_wday = bd.weekday % 7; - tm.tm_yday = bd.yearday - 1; - tm.tm_isdst = bd.is_dst ? 1 : 0; + switch (get_weekday(civil_day(al.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(al.cs)) - 1; + tm.tm_isdst = al.is_dst ? 1 : 0; return tm; } @@ -238,11 +260,11 @@ 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 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 @@ -297,37 +319,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.year); + bp = Format64(ep, 0, al.cs.year()); result.append(bp, ep - bp); break; case 'm': - bp = Format02d(ep, bd.month); + bp = Format02d(ep, al.cs.month()); result.append(bp, ep - bp); break; case 'd': case 'e': - bp = Format02d(ep, bd.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.hour); + bp = Format02d(ep, al.cs.hour()); result.append(bp, ep - bp); break; case 'M': - bp = Format02d(ep, bd.minute); + bp = Format02d(ep, al.cs.minute()); result.append(bp, ep - bp); break; case 'S': - bp = Format02d(ep, bd.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)); @@ -347,7 +369,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') { @@ -359,7 +381,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, al.cs.second()); result.append(bp, cp - bp); pending = cur += 2; } else if (*cur == '4' && cur + 1 != end && *(cur + 1) == 'Y') { @@ -367,7 +389,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, al.cs.year()); result.append(bp, ep - bp); pending = cur += 2; } else if (std::isdigit(*cur)) { @@ -386,7 +408,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, al.cs.second()); result.append(bp, ep - bp); pending = cur = np; } @@ -437,7 +459,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 +498,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 +510,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 +707,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,16 +728,23 @@ 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; } +} // namespace detail } // namespace cctz diff --git a/src/time_zone_format_test.cc b/src/time_zone_format_test.cc new file mode 100644 index 00000000..7f7338cb --- /dev/null +++ b/src/time_zone_format_test.cc @@ -0,0 +1,960 @@ +// 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 +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using std::chrono::system_clock; +using std::chrono::nanoseconds; +using std::chrono::microseconds; +using std::chrono::milliseconds; +using std::chrono::seconds; +using std::chrono::minutes; +using std::chrono::hours; +using testing::HasSubstr; + +namespace cctz { + +namespace { + +// This helper is a macro so that failed expectations show up with the +// correct line numbers. +#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_TRUE(isdst == al.is_dst); \ + EXPECT_EQ(zone, al.abbr); \ + } while (0) + +const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; +const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez"; + +const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; +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, time_zone tz, const std::string& fmt, + const std::string& ans) { + 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)); +} + +} // namespace + +// +// Testing format() +// + +TEST(Format, TimePointResolution) { + using std::chrono::time_point_cast; + const char kFmt[] = "%H:%M:%E*S"; + 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)); + EXPECT_EQ("03:04:05.123456", + format(kFmt, time_point_cast(t0), utc)); + EXPECT_EQ("03:04:05.123", + format(kFmt, time_point_cast(t0), utc)); + EXPECT_EQ("03:04:05", + format(kFmt, time_point_cast(t0), utc)); + EXPECT_EQ("03:04:05", + format(kFmt, time_point_cast(t0), utc)); + EXPECT_EQ("03:04:00", + format(kFmt, time_point_cast(t0), utc)); + EXPECT_EQ("03:00:00", + format(kFmt, time_point_cast(t0), utc)); +} + +TEST(Format, Basics) { + 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)); + std::string big(128, 'x'); + 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)); + + 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)); +} + +TEST(Format, PosixConversions) { + const time_zone tz = utc_time_zone(); + auto tp = system_clock::from_time_t(0); + + TestFormatSpecifier(tp, tz, "%d", "01"); + TestFormatSpecifier(tp, tz, "%e", " 1"); // extension but internal support + TestFormatSpecifier(tp, tz, "%H", "00"); + TestFormatSpecifier(tp, tz, "%I", "12"); + TestFormatSpecifier(tp, tz, "%j", "001"); + TestFormatSpecifier(tp, tz, "%m", "01"); + TestFormatSpecifier(tp, tz, "%M", "00"); + TestFormatSpecifier(tp, tz, "%S", "00"); + TestFormatSpecifier(tp, tz, "%U", "00"); + TestFormatSpecifier(tp, tz, "%w", "4"); // 4=Thursday + TestFormatSpecifier(tp, tz, "%W", "00"); + TestFormatSpecifier(tp, tz, "%y", "70"); + TestFormatSpecifier(tp, tz, "%Y", "1970"); + TestFormatSpecifier(tp, tz, "%z", "+0000"); + TestFormatSpecifier(tp, tz, "%Z", "UTC"); + TestFormatSpecifier(tp, tz, "%%", "%"); + +#if defined(__linux__) + // SU/C99/TZ extensions + TestFormatSpecifier(tp, tz, "%C", "19"); + TestFormatSpecifier(tp, tz, "%D", "01/01/70"); + TestFormatSpecifier(tp, tz, "%F", "1970-01-01"); + TestFormatSpecifier(tp, tz, "%g", "70"); + TestFormatSpecifier(tp, tz, "%G", "1970"); + TestFormatSpecifier(tp, tz, "%k", " 0"); + TestFormatSpecifier(tp, tz, "%l", "12"); + TestFormatSpecifier(tp, tz, "%n", "\n"); + TestFormatSpecifier(tp, tz, "%R", "00:00"); + TestFormatSpecifier(tp, tz, "%t", "\t"); + TestFormatSpecifier(tp, tz, "%T", "00:00:00"); + TestFormatSpecifier(tp, tz, "%u", "4"); // 4=Thursday + TestFormatSpecifier(tp, tz, "%V", "01"); + TestFormatSpecifier(tp, tz, "%s", "0"); +#endif +} + +TEST(Format, LocaleSpecific) { + const time_zone tz = utc_time_zone(); + auto tp = system_clock::from_time_t(0); + + TestFormatSpecifier(tp, tz, "%a", "Thu"); + TestFormatSpecifier(tp, tz, "%A", "Thursday"); + TestFormatSpecifier(tp, tz, "%b", "Jan"); + TestFormatSpecifier(tp, tz, "%B", "January"); + + // %c should at least produce the numeric year and time-of-day. + const std::string s = format("%c", tp, utc_time_zone()); + EXPECT_THAT(s, HasSubstr("1970")); + EXPECT_THAT(s, HasSubstr("00:00:00")); + + TestFormatSpecifier(tp, tz, "%p", "AM"); + TestFormatSpecifier(tp, tz, "%x", "01/01/70"); + TestFormatSpecifier(tp, tz, "%X", "00:00:00"); + +#if defined(__linux__) + // SU/C99/TZ extensions + TestFormatSpecifier(tp, tz, "%h", "Jan"); // Same as %b + TestFormatSpecifier(tp, tz, "%P", "am"); + TestFormatSpecifier(tp, tz, "%r", "12:00:00 AM"); + + // Modified conversion specifiers %E_ + TestFormatSpecifier(tp, tz, "%Ec", "Thu Jan 1 00:00:00 1970"); + TestFormatSpecifier(tp, tz, "%EC", "19"); + TestFormatSpecifier(tp, tz, "%Ex", "01/01/70"); + TestFormatSpecifier(tp, tz, "%EX", "00:00:00"); + TestFormatSpecifier(tp, tz, "%Ey", "70"); + TestFormatSpecifier(tp, tz, "%EY", "1970"); + + // Modified conversion specifiers %O_ + TestFormatSpecifier(tp, tz, "%Od", "01"); + TestFormatSpecifier(tp, tz, "%Oe", " 1"); + TestFormatSpecifier(tp, tz, "%OH", "00"); + TestFormatSpecifier(tp, tz, "%OI", "12"); + TestFormatSpecifier(tp, tz, "%Om", "01"); + TestFormatSpecifier(tp, tz, "%OM", "00"); + TestFormatSpecifier(tp, tz, "%OS", "00"); + TestFormatSpecifier(tp, tz, "%Ou", "4"); // 4=Thursday + TestFormatSpecifier(tp, tz, "%OU", "00"); + TestFormatSpecifier(tp, tz, "%OV", "01"); + TestFormatSpecifier(tp, tz, "%Ow", "4"); // 4=Thursday + TestFormatSpecifier(tp, tz, "%OW", "00"); + TestFormatSpecifier(tp, tz, "%Oy", "70"); +#endif +} + +TEST(Format, Escaping) { + const time_zone tz = utc_time_zone(); + auto tp = system_clock::from_time_t(0); + + TestFormatSpecifier(tp, tz, "%%", "%"); + TestFormatSpecifier(tp, tz, "%%a", "%a"); + TestFormatSpecifier(tp, tz, "%%b", "%b"); + TestFormatSpecifier(tp, tz, "%%Ea", "%Ea"); + TestFormatSpecifier(tp, tz, "%%Es", "%Es"); + TestFormatSpecifier(tp, tz, "%%E3S", "%E3S"); + TestFormatSpecifier(tp, tz, "%%OS", "%OS"); + TestFormatSpecifier(tp, tz, "%%O3S", "%O3S"); + + // Multiple levels of escaping. + TestFormatSpecifier(tp, tz, "%%%Y", "%1970"); + TestFormatSpecifier(tp, tz, "%%%E3S", "%00.000"); + TestFormatSpecifier(tp, tz, "%%%%E3S", "%%E3S"); +} + +TEST(Format, ExtendedSeconds) { + 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)); + + // 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)); + + // 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)); + tp += microseconds(1); + EXPECT_EQ("2014-03-17 02:47:07.333305", + format("%Y-%m-%d %H:%M:%E*S", tp, tz)); +} + +TEST(Format, ExtendedOffset) { + auto tp = system_clock::from_time_t(0); + + time_zone tz = utc_time_zone(); + TestFormatSpecifier(tp, tz, "%Ez", "+00:00"); + + EXPECT_TRUE(load_time_zone("America/New_York", &tz)); + TestFormatSpecifier(tp, tz, "%Ez", "-05:00"); + + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); + TestFormatSpecifier(tp, tz, "%Ez", "-08:00"); + + EXPECT_TRUE(load_time_zone("Australia/Sydney", &tz)); + TestFormatSpecifier(tp, tz, "%Ez", "+10:00"); + + 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 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 = convert(civil_second(-999, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("-9991127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(-99, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("-0991127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(-9, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("-0091127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(-1, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("-0011127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(0, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("00001127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(1, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("00011127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(9, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("00091127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(99, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("00991127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(999, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("09991127", format(e4y_fmt, tp, 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 = convert(civil_second(-1000, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("-10001127", format(e4y_fmt, tp, utc)); + tp = convert(civil_second(10000, 11, 27, 0, 0, 0), utc); + EXPECT_EQ("100001127", format(e4y_fmt, tp, utc)); +} + +TEST(Format, RFC3339Format) { + time_zone tz; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); +} + +TEST(Format, RFC1123Format) { // locale specific + time_zone tz; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &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)); +} + +// +// Testing parse() +// + +TEST(Parse, TimePointResolution) { + using std::chrono::time_point_cast; + const char kFmt[] = "%H:%M:%E*S"; + 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)); + + 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)); + + 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)); + + 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)); + + time_point tp_m; + 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)); +} + +TEST(Parse, Basics) { + time_zone tz = utc_time_zone(); + time_point tp = + system_clock::from_time_t(1234567890); + + // Simple edge cases. + 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("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 -0800", tz, &tp)); + ExpectTime(tp, tz, 2013, 6, 29, 3, 8, 9, 0, false, "UTC"); +} + +TEST(Parse, WithTimeZone) { + 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)); + 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", + 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)); + 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)); + ExpectTime(tp, tz, 2011, 11, 6, 1, 15, 0, -7 * 60 * 60, true, "PDT"); +} + +TEST(Parse, LeapSecond) { + 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)); + 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)); + 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)); + 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)); + 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)); +} + +TEST(Parse, ErrorCases) { + 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)); + + // Can't parse an illegal format specifier. + EXPECT_FALSE(parse("%Q", "x", tz, &tp)); + + // Fails because of trailing, unparsed data "blah". + 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, 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)); + + // 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)); + + // Check that we reject other malformed UTC offsets. + 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)); +} + +TEST(Parse, PosixConversions) { + time_zone tz = utc_time_zone(); + auto tp = system_clock::from_time_t(0); + 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, convert(tp, tz).day()); + + // %e is an extension, but is supported internally. + tp = reset; + EXPECT_TRUE(parse("%e", "15", tz, &tp)); + EXPECT_EQ(15, convert(tp, tz).day()); // Equivalent to %d + + tp = reset; + EXPECT_TRUE(parse("%H", "17", tz, &tp)); + EXPECT_EQ(17, convert(tp, tz).hour()); + + tp = reset; + EXPECT_TRUE(parse("%I", "5", tz, &tp)); + 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, convert(tp, tz).month()); + + tp = reset; + EXPECT_TRUE(parse("%M", "33", tz, &tp)); + EXPECT_EQ(33, convert(tp, tz).minute()); + + tp = reset; + EXPECT_TRUE(parse("%S", "55", tz, &tp)); + EXPECT_EQ(55, convert(tp, tz).second()); + + // %U is parsed but ignored. + EXPECT_TRUE(parse("%U", "15", tz, &tp)); + + // %w is parsed but ignored. + EXPECT_TRUE(parse("%w", "2", tz, &tp)); + + // %W is parsed but ignored. + EXPECT_TRUE(parse("%W", "22", tz, &tp)); + + tp = reset; + EXPECT_TRUE(parse("%y", "04", tz, &tp)); + EXPECT_EQ(2004, convert(tp, tz).year()); + + tp = reset; + EXPECT_TRUE(parse("%Y", "2004", tz, &tp)); + EXPECT_EQ(2004, convert(tp, tz).year()); + + EXPECT_TRUE(parse("%%", "%", tz, &tp)); + +#if defined(__linux__) + // SU/C99/TZ extensions + + tp = reset; + EXPECT_TRUE(parse("%C", "20", tz, &tp)); + EXPECT_EQ(2000, convert(tp, tz).year()); + + tp = reset; + EXPECT_TRUE(parse("%D", "02/03/04", tz, &tp)); + 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, 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, 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)); + EXPECT_EQ(system_clock::from_time_t(1234567890), tp); + + // %s conversion, like %z/%Ez, pays no heed to the optional zone. + time_zone lax; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax)); + tp = reset; + 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 + // breakdown in the zone as some other time. For example, ... + // 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_EQ(system_clock::from_time_t(1414917000), tp); + tp = reset; + EXPECT_TRUE(parse("%s", "1414920600", lax, &tp)); + EXPECT_EQ(system_clock::from_time_t(1414920600), tp); +#endif +} + +TEST(Parse, LocaleSpecific) { + time_zone tz = utc_time_zone(); + auto tp = system_clock::from_time_t(0); + 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)); + + // %A is parsed but ignored. + EXPECT_TRUE(parse("%A", "Monday", tz, &tp)); + + tp = reset; + EXPECT_TRUE(parse("%b", "Feb", tz, &tp)); + EXPECT_EQ(2, convert(tp, tz).month()); + + tp = reset; + EXPECT_TRUE(parse("%B", "February", tz, &tp)); + 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, convert(tp, tz).hour()); + + tp = reset; + EXPECT_TRUE(parse("%x", "02/03/04", tz, &tp)); + 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, 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, convert(tp, tz).month()); // Equivalent to %b + + tp = reset; + EXPECT_TRUE(parse("%l %p", "5 PM", tz, &tp)); + EXPECT_EQ(17, convert(tp, tz).hour()); + + tp = reset; + EXPECT_TRUE(parse("%r", "03:44:55 PM", tz, &tp)); + 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(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, convert(tp, tz).year()); + + tp = reset; + EXPECT_TRUE(parse("%Ex", "02/03/04", tz, &tp)); + 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, 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). +// Yet, because we handle each (non-internal) specifier in a separate call +// to strptime(), there is no way to group %EC and %Ey either. So we just +// skip the %Ey case. +#if 0 + tp = reset; + EXPECT_TRUE(parse("%Ey", "04", tz, &tp)); + EXPECT_EQ(2004, convert(tp, tz).year()); +#endif + + tp = reset; + EXPECT_TRUE(parse("%EY", "2004", tz, &tp)); + EXPECT_EQ(2004, convert(tp, tz).year()); + + // Modified conversion specifiers %O_ + + tp = reset; + EXPECT_TRUE(parse("%Od", "15", tz, &tp)); + EXPECT_EQ(15, convert(tp, tz).day()); + + tp = reset; + EXPECT_TRUE(parse("%Oe", "15", tz, &tp)); + EXPECT_EQ(15, convert(tp, tz).day()); // Equivalent to %d + + tp = reset; + EXPECT_TRUE(parse("%OH", "17", tz, &tp)); + EXPECT_EQ(17, convert(tp, tz).hour()); + + tp = reset; + EXPECT_TRUE(parse("%OI", "5", tz, &tp)); + EXPECT_EQ(5, convert(tp, tz).hour()); + + tp = reset; + EXPECT_TRUE(parse("%Om", "11", tz, &tp)); + EXPECT_EQ(11, convert(tp, tz).month()); + + tp = reset; + EXPECT_TRUE(parse("%OM", "33", tz, &tp)); + EXPECT_EQ(33, convert(tp, tz).minute()); + + tp = reset; + EXPECT_TRUE(parse("%OS", "55", tz, &tp)); + EXPECT_EQ(55, convert(tp, tz).second()); + + // %OU is parsed but ignored. + EXPECT_TRUE(parse("%OU", "15", tz, &tp)); + + // %Ow is parsed but ignored. + EXPECT_TRUE(parse("%Ow", "2", tz, &tp)); + + // %OW is parsed but ignored. + EXPECT_TRUE(parse("%OW", "22", tz, &tp)); + + tp = reset; + EXPECT_TRUE(parse("%Oy", "04", tz, &tp)); + EXPECT_EQ(2004, convert(tp, tz).year()); +#endif +} + +TEST(Parse, ExtendedSeconds) { + 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_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_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( + "%E*S", "0.214748364801234567890123456789012345678901234567890123456789", + tz, &tp)); + EXPECT_EQ(system_clock::from_time_t(0) + nanoseconds(214748364), tp); +} + +TEST(Parse, ExtendedSecondsScan) { + 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) { + const int64_t micros = ms * 1000 + us; + for (int64_t ns = 0; ns < 1000; ns += 9) { + const auto expected = + system_clock::from_time_t(0) + nanoseconds(micros * 1000 + ns); + std::ostringstream oss; + 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_EQ(expected, tp) << input; + } + } + } +} + +TEST(Parse, ExtendedOffset) { + const time_zone utc = utc_time_zone(); + time_point tp; + + // %z against +-HHMM. + EXPECT_TRUE(parse("%z", "+0000", utc, &tp)); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse("%z", "-1234", utc, &tp)); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); + EXPECT_TRUE(parse("%z", "+1234", 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(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse("%z", "-12", utc, &tp)); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp); + EXPECT_TRUE(parse("%z", "+12", 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(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse("%Ez", "-12:34", 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(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(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse("%Ez", "-1234", utc, &tp)); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); + EXPECT_TRUE(parse("%Ez", "+1234", 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(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse("%Ez", "-12", utc, &tp)); + EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp); + EXPECT_TRUE(parse("%Ez", "+12", utc, &tp)); + EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp); + EXPECT_FALSE(parse("%Ez", "-1", utc, &tp)); +} + +TEST(Parse, ExtendedYears) { + const time_zone utc = utc_time_zone(); + const char e4y_fmt[] = "%E4Y%m%d"; // no separators + time_point tp; + + // %E4Y consumes exactly four chars, including any sign. + EXPECT_TRUE(parse(e4y_fmt, "-9991127", 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(convert(civil_second(-99, 11, 27, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse(e4y_fmt, "-0091127", 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(convert(civil_second(-1, 11, 27, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse(e4y_fmt, "00001127", 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(convert(civil_second(1, 11, 27, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse(e4y_fmt, "00091127", 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(convert(civil_second(99, 11, 27, 0, 0, 0), utc), tp); + EXPECT_TRUE(parse(e4y_fmt, "09991127", 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(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)); + EXPECT_FALSE(parse(e4y_fmt, "100001127", utc, &tp)); +} + +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)); + 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; + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2)); + EXPECT_EQ(tp, tp2); +} + +// +// Roundtrip test for format()/parse(). +// + +TEST(FormatParse, RoundTrip) { + time_zone lax; + EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax)); + const auto in = convert(civil_second(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; + 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; + EXPECT_EQ(in, out); // RFC1123_full includes %z + } + + // Even though we don't know what %c will produce, it should roundtrip, + // but only in the 0-offset timezone. + { + time_point out; + 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); + } +} + +} // namespace cctz 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..9eef9ea9 --- /dev/null +++ b/src/time_zone_impl.cc @@ -0,0 +1,126 @@ +// 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 bd = zone_->BreakTime(tp); + // TODO: Eliminate extra normalization. + 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; +} + +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 94% rename from src/cctz_info.cc rename to src/time_zone_info.cc index c517f1df..346f42c7 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 @@ -167,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 @@ -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; @@ -433,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) @@ -648,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 { @@ -739,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; @@ -754,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]. @@ -782,9 +801,9 @@ 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(); + 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]); @@ -796,7 +815,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; @@ -805,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)) { @@ -830,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; @@ -847,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/cctz_info.h b/src/time_zone_info.h similarity index 81% rename from src/cctz_info.h rename to src/time_zone_info.h index 55f285dc..4a1dce90 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; @@ -133,10 +132,10 @@ 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 -#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 84% rename from src/cctz_libc.cc rename to src/time_zone_libc.cc index 8c75057d..ea19540c 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 @@ -35,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]) @@ -51,13 +53,13 @@ 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; if (local_) { #if defined(_WIN32) || defined(_WIN64) - tm = *localtime(&t); + localtime_s(&tm, &t); #else localtime_r(&t, &tm); #endif @@ -65,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 @@ -116,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 @@ -175,7 +177,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..449c3f5e --- /dev/null +++ b/src/time_zone_lookup.cc @@ -0,0 +1,65 @@ +// 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() { +#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 { + zone = "localtime"; + } + time_zone tz; + if (!load_time_zone(zone, &tz)) { + load_time_zone("UTC", &tz); + } +#if defined(_WIN32) || defined(_WIN64) + free(tz_env); +#endif + 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/src/time_zone_lookup_test.cc b/src/time_zone_lookup_test.cc new file mode 100644 index 00000000..816feec4 --- /dev/null +++ b/src/time_zone_lookup_test.cc @@ -0,0 +1,1068 @@ +// 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 +#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). +time_zone LoadZone(const std::string& name) { + time_zone tz; + load_time_zone(name, &tz); + return tz; +} + +// This helper is a macro so that failed expectations show up with the +// correct line numbers. +#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_TRUE(isdst == al.is_dst); \ + EXPECT_EQ(zone, al.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(); + time_zone tz; + for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) { + EXPECT_TRUE(load_time_zone(*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) { + time_zone tz; + EXPECT_FALSE(load_time_zone(":America/Los_Angeles", &tz)); + + tz = LoadZone("America/Los_Angeles"); + EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz)); + EXPECT_EQ(system_clock::from_time_t(0), + 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), + 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), + convert(civil_second(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 time_zone utc = utc_time_zone(); + const auto t0 = system_clock::from_time_t(0); + + 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 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 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 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 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 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 = + 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 = + 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 = + 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 = + 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 = + 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 convert()) to a coarser + // resolution requires an explicit cast. + using std::chrono::time_point_cast; + const time_point tp_m = + 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( + 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 = 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, 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 = 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); + ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -4 * 3600, true, "EDT"); + + // Fall 1:59:59 -> 1:00:00 + 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); + 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 = 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); + ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -7 * 3600, true, "PDT"); + + // Fall 1:59:59 -> 1:00:00 + 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); + 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 = 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); + ExpectTime(tp, tz, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST"); + + // No transition in Fall. + 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); + ExpectTime(tp, tz, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); +} + +TEST(TimeZoneEdgeCase, AsiaKathmandu) { + 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 = 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); + ExpectTime(tp, tz, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "NPT"); +} + +TEST(TimeZoneEdgeCase, PacificChatham) { + 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 = 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); + 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 = 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); + ExpectTime(tp, tz, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "CHADT"); +} + +TEST(TimeZoneEdgeCase, AustraliaLordHowe) { + 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 = 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); + 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 = 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); + ExpectTime(tp, tz, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "LHDT"); +} + +TEST(TimeZoneEdgeCase, PacificApia) { + 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 + // 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 = 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); + 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) { + 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 = 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); + ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); +} + +TEST(TimeZoneEdgeCase, AfricaMonrovia) { + 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 = 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); + ExpectTime(tp, tz, 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 time_zone tz = LoadZone("America/Jamaica"); + + // 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, + 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, + tz.lookup(tp).abbr); + tp += std::chrono::seconds(1); + 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 = 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); + ExpectTime(tp, tz, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); + + // After the last transition. + 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) { + // Cover some non-existent times within forward transitions. + const time_zone tz = LoadZone("WET"); + + // Before the first transition. + 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 = 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); + 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 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 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 = 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 = 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 = 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); + 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) { + 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 = 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); + ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); +} + +TEST(TimeZoneEdgeCase, UTC5DigitYear) { + 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 = 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); + ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); +} + +} // 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/test/fmt_test.cc b/test/fmt_test.cc deleted file mode 100644 index d7403034..00000000 --- a/test/fmt_test.cc +++ /dev/null @@ -1,968 +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 -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using std::chrono::system_clock; -using std::chrono::nanoseconds; -using std::chrono::microseconds; -using std::chrono::milliseconds; -using std::chrono::seconds; -using std::chrono::minutes; -using std::chrono::hours; -using testing::HasSubstr; - -namespace cctz { - -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.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) - -const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; -const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez"; - -const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; -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, - 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)); -} - -} // namespace - -// -// Testing Format() -// - -TEST(Format, TimePointResolution) { - using std::chrono::time_point_cast; - const char kFmt[] = "%H:%M:%E*S"; - const TimeZone utc = UTCTimeZone(); - 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)); - EXPECT_EQ("03:04:05.123456", - Format(kFmt, time_point_cast(t0), utc)); - EXPECT_EQ("03:04:05.123", - Format(kFmt, time_point_cast(t0), utc)); - EXPECT_EQ("03:04:05", - Format(kFmt, time_point_cast(t0), utc)); - EXPECT_EQ("03:04:05", - Format(kFmt, time_point_cast(t0), utc)); - EXPECT_EQ("03:04:00", - Format(kFmt, time_point_cast(t0), utc)); - EXPECT_EQ("03:00:00", - Format(kFmt, time_point_cast(t0), utc)); -} - -TEST(Format, Basics) { - TimeZone tz = UTCTimeZone(); - 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)); - std::string big(128, 'x'); - 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)); - - 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)); -} - -TEST(Format, PosixConversions) { - const TimeZone tz = UTCTimeZone(); - auto tp = system_clock::from_time_t(0); - - TestFormatSpecifier(tp, tz, "%d", "01"); - TestFormatSpecifier(tp, tz, "%e", " 1"); // extension but internal support - TestFormatSpecifier(tp, tz, "%H", "00"); - TestFormatSpecifier(tp, tz, "%I", "12"); - TestFormatSpecifier(tp, tz, "%j", "001"); - TestFormatSpecifier(tp, tz, "%m", "01"); - TestFormatSpecifier(tp, tz, "%M", "00"); - TestFormatSpecifier(tp, tz, "%S", "00"); - TestFormatSpecifier(tp, tz, "%U", "00"); - TestFormatSpecifier(tp, tz, "%w", "4"); // 4=Thursday - TestFormatSpecifier(tp, tz, "%W", "00"); - TestFormatSpecifier(tp, tz, "%y", "70"); - TestFormatSpecifier(tp, tz, "%Y", "1970"); - TestFormatSpecifier(tp, tz, "%z", "+0000"); - TestFormatSpecifier(tp, tz, "%Z", "UTC"); - TestFormatSpecifier(tp, tz, "%%", "%"); - -#if defined(__linux__) - // SU/C99/TZ extensions - TestFormatSpecifier(tp, tz, "%C", "19"); - TestFormatSpecifier(tp, tz, "%D", "01/01/70"); - TestFormatSpecifier(tp, tz, "%F", "1970-01-01"); - TestFormatSpecifier(tp, tz, "%g", "70"); - TestFormatSpecifier(tp, tz, "%G", "1970"); - TestFormatSpecifier(tp, tz, "%k", " 0"); - TestFormatSpecifier(tp, tz, "%l", "12"); - TestFormatSpecifier(tp, tz, "%n", "\n"); - TestFormatSpecifier(tp, tz, "%R", "00:00"); - TestFormatSpecifier(tp, tz, "%t", "\t"); - TestFormatSpecifier(tp, tz, "%T", "00:00:00"); - TestFormatSpecifier(tp, tz, "%u", "4"); // 4=Thursday - TestFormatSpecifier(tp, tz, "%V", "01"); - TestFormatSpecifier(tp, tz, "%s", "0"); -#endif -} - -TEST(Format, LocaleSpecific) { - const TimeZone tz = UTCTimeZone(); - auto tp = system_clock::from_time_t(0); - - TestFormatSpecifier(tp, tz, "%a", "Thu"); - TestFormatSpecifier(tp, tz, "%A", "Thursday"); - TestFormatSpecifier(tp, tz, "%b", "Jan"); - 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()); - EXPECT_THAT(s, HasSubstr("1970")); - EXPECT_THAT(s, HasSubstr("00:00:00")); - - TestFormatSpecifier(tp, tz, "%p", "AM"); - TestFormatSpecifier(tp, tz, "%x", "01/01/70"); - TestFormatSpecifier(tp, tz, "%X", "00:00:00"); - -#if defined(__linux__) - // SU/C99/TZ extensions - TestFormatSpecifier(tp, tz, "%h", "Jan"); // Same as %b - TestFormatSpecifier(tp, tz, "%P", "am"); - TestFormatSpecifier(tp, tz, "%r", "12:00:00 AM"); - - // Modified conversion specifiers %E_ - TestFormatSpecifier(tp, tz, "%Ec", "Thu Jan 1 00:00:00 1970"); - TestFormatSpecifier(tp, tz, "%EC", "19"); - TestFormatSpecifier(tp, tz, "%Ex", "01/01/70"); - TestFormatSpecifier(tp, tz, "%EX", "00:00:00"); - TestFormatSpecifier(tp, tz, "%Ey", "70"); - TestFormatSpecifier(tp, tz, "%EY", "1970"); - - // Modified conversion specifiers %O_ - TestFormatSpecifier(tp, tz, "%Od", "01"); - TestFormatSpecifier(tp, tz, "%Oe", " 1"); - TestFormatSpecifier(tp, tz, "%OH", "00"); - TestFormatSpecifier(tp, tz, "%OI", "12"); - TestFormatSpecifier(tp, tz, "%Om", "01"); - TestFormatSpecifier(tp, tz, "%OM", "00"); - TestFormatSpecifier(tp, tz, "%OS", "00"); - TestFormatSpecifier(tp, tz, "%Ou", "4"); // 4=Thursday - TestFormatSpecifier(tp, tz, "%OU", "00"); - TestFormatSpecifier(tp, tz, "%OV", "01"); - TestFormatSpecifier(tp, tz, "%Ow", "4"); // 4=Thursday - TestFormatSpecifier(tp, tz, "%OW", "00"); - TestFormatSpecifier(tp, tz, "%Oy", "70"); -#endif -} - -TEST(Format, Escaping) { - const TimeZone tz = UTCTimeZone(); - auto tp = system_clock::from_time_t(0); - - TestFormatSpecifier(tp, tz, "%%", "%"); - TestFormatSpecifier(tp, tz, "%%a", "%a"); - TestFormatSpecifier(tp, tz, "%%b", "%b"); - TestFormatSpecifier(tp, tz, "%%Ea", "%Ea"); - TestFormatSpecifier(tp, tz, "%%Es", "%Es"); - TestFormatSpecifier(tp, tz, "%%E3S", "%E3S"); - TestFormatSpecifier(tp, tz, "%%OS", "%OS"); - TestFormatSpecifier(tp, tz, "%%O3S", "%O3S"); - - // Multiple levels of escaping. - TestFormatSpecifier(tp, tz, "%%%Y", "%1970"); - TestFormatSpecifier(tp, tz, "%%%E3S", "%00.000"); - TestFormatSpecifier(tp, tz, "%%%%E3S", "%%E3S"); -} - -TEST(Format, ExtendedSeconds) { - const TimeZone tz = UTCTimeZone(); - 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)); - - // 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)); - - // 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)); - tp += microseconds(1); - EXPECT_EQ("2014-03-17 02:47:07.333305", - Format("%Y-%m-%d %H:%M:%E*S", tp, tz)); -} - -TEST(Format, ExtendedOffset) { - auto tp = system_clock::from_time_t(0); - - TimeZone tz = UTCTimeZone(); - TestFormatSpecifier(tp, tz, "%Ez", "+00:00"); - - EXPECT_TRUE(LoadTimeZone("America/New_York", &tz)); - TestFormatSpecifier(tp, tz, "%Ez", "-05:00"); - - EXPECT_TRUE(LoadTimeZone("America/Los_Angeles", &tz)); - TestFormatSpecifier(tp, tz, "%Ez", "-08:00"); - - EXPECT_TRUE(LoadTimeZone("Australia/Sydney", &tz)); - TestFormatSpecifier(tp, tz, "%Ez", "+10:00"); - - EXPECT_TRUE(LoadTimeZone("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 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)); - tp = MakeTime(-99, 11, 27, 0, 0, 0, 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)); - tp = MakeTime(-1, 11, 27, 0, 0, 0, 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)); - tp = MakeTime(1, 11, 27, 0, 0, 0, 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)); - tp = MakeTime(99, 11, 27, 0, 0, 0, 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)); - tp = MakeTime(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); - 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)); -} - -TEST(Format, RFC3339Format) { - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("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)); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); -} - -TEST(Format, RFC1123Format) { // locale specific - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("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)); -} - -// -// Testing Parse() -// - -TEST(Parse, TimePointResolution) { - using std::chrono::time_point_cast; - const char kFmt[] = "%H:%M:%E*S"; - const TimeZone utc = UTCTimeZone(); - - 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)); - - 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)); - - 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)); - - 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)); - - time_point tp_m; - 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)); -} - -TEST(Parse, Basics) { - TimeZone tz = UTCTimeZone(); - time_point tp = - system_clock::from_time_t(1234567890); - - // Simple edge cases. - 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("%Y-%m-%d %H:%M:%S %z", "2013-06-28 19:08:09 -0800", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); - ExpectTime(bd, 2013, 6, 29, 3, 8, 9, 0, false, "UTC"); -} - -TEST(Parse, WithTimeZone) { - TimeZone tz; - EXPECT_TRUE(LoadTimeZone("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); - 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()); - ExpectTime(bd, 2013, 6, 28, 11, 8, 9, 0, false, "UTC"); - - // 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); - ExpectTime(bd, 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 = BreakTime(tp, tz); - 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_point tp; - - // ":59" -> ":59" - EXPECT_TRUE(Parse(RFC3339_full, "2013-06-28T07:08:59-08:00", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); - 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); - 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); - 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); - 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)); -} - -TEST(Parse, ErrorCases) { - const TimeZone tz = UTCTimeZone(); - auto tp = system_clock::from_time_t(0); - - // Illegal trailing data. - EXPECT_FALSE(Parse("%S", "123", tz, &tp)); - - // Can't parse an illegal format specifier. - EXPECT_FALSE(Parse("%Q", "x", tz, &tp)); - - // Fails because of trailing, unparsed data "blah". - 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); - - // Feb 31 requires normalization. - 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)); - - // Check that we reject other malformed UTC offsets. - 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)); -} - -TEST(Parse, PosixConversions) { - TimeZone tz = UTCTimeZone(); - 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); - - // %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 - - tp = reset; - EXPECT_TRUE(Parse("%H", "17", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).hour); - - tp = reset; - EXPECT_TRUE(Parse("%I", "5", tz, &tp)); - EXPECT_EQ(5, BreakTime(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, BreakTime(tp, tz).month); - - tp = reset; - EXPECT_TRUE(Parse("%M", "33", tz, &tp)); - EXPECT_EQ(33, BreakTime(tp, tz).minute); - - tp = reset; - EXPECT_TRUE(Parse("%S", "55", tz, &tp)); - EXPECT_EQ(55, BreakTime(tp, tz).second); - - // %U is parsed but ignored. - EXPECT_TRUE(Parse("%U", "15", tz, &tp)); - - // %w is parsed but ignored. - EXPECT_TRUE(Parse("%w", "2", tz, &tp)); - - // %W is parsed but ignored. - EXPECT_TRUE(Parse("%W", "22", tz, &tp)); - - tp = reset; - EXPECT_TRUE(Parse("%y", "04", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); - - tp = reset; - EXPECT_TRUE(Parse("%Y", "2004", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); - - 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); - - 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("%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("%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); - - tp = reset; - 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)); - tp = reset; - 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 - // breakdown in the zone as some other time. For example, ... - // 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_EQ(system_clock::from_time_t(1414917000), tp); - tp = reset; - EXPECT_TRUE(Parse("%s", "1414920600", lax, &tp)); - EXPECT_EQ(system_clock::from_time_t(1414920600), tp); -#endif -} - -TEST(Parse, LocaleSpecific) { - TimeZone tz = UTCTimeZone(); - 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)); - - // %A is parsed but ignored. - EXPECT_TRUE(Parse("%A", "Monday", tz, &tp)); - - tp = reset; - EXPECT_TRUE(Parse("%b", "Feb", tz, &tp)); - EXPECT_EQ(2, BreakTime(tp, tz).month); - - tp = reset; - EXPECT_TRUE(Parse("%B", "February", tz, &tp)); - EXPECT_EQ(2, BreakTime(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, BreakTime(tp, tz).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); - - 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); - -#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 - - tp = reset; - EXPECT_TRUE(Parse("%l %p", "5 PM", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).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); - - 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); - - // Modified conversion specifiers %E_ - - tp = reset; - EXPECT_TRUE(Parse("%EC", "20", tz, &tp)); - EXPECT_EQ(2000, BreakTime(tp, tz).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); - - 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); - -// %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). -// Yet, because we handle each (non-internal) specifier in a separate call -// to strptime(), there is no way to group %EC and %Ey either. So we just -// skip the %Ey case. -#if 0 - tp = reset; - EXPECT_TRUE(Parse("%Ey", "04", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); -#endif - - tp = reset; - EXPECT_TRUE(Parse("%EY", "2004", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); - - // Modified conversion specifiers %O_ - - tp = reset; - EXPECT_TRUE(Parse("%Od", "15", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).day); - - tp = reset; - EXPECT_TRUE(Parse("%Oe", "15", tz, &tp)); - EXPECT_EQ(15, BreakTime(tp, tz).day); // Equivalent to %d - - tp = reset; - EXPECT_TRUE(Parse("%OH", "17", tz, &tp)); - EXPECT_EQ(17, BreakTime(tp, tz).hour); - - tp = reset; - EXPECT_TRUE(Parse("%OI", "5", tz, &tp)); - EXPECT_EQ(5, BreakTime(tp, tz).hour); - - tp = reset; - EXPECT_TRUE(Parse("%Om", "11", tz, &tp)); - EXPECT_EQ(11, BreakTime(tp, tz).month); - - tp = reset; - EXPECT_TRUE(Parse("%OM", "33", tz, &tp)); - EXPECT_EQ(33, BreakTime(tp, tz).minute); - - tp = reset; - EXPECT_TRUE(Parse("%OS", "55", tz, &tp)); - EXPECT_EQ(55, BreakTime(tp, tz).second); - - // %OU is parsed but ignored. - EXPECT_TRUE(Parse("%OU", "15", tz, &tp)); - - // %Ow is parsed but ignored. - EXPECT_TRUE(Parse("%Ow", "2", tz, &tp)); - - // %OW is parsed but ignored. - EXPECT_TRUE(Parse("%OW", "22", tz, &tp)); - - tp = reset; - EXPECT_TRUE(Parse("%Oy", "04", tz, &tp)); - EXPECT_EQ(2004, BreakTime(tp, tz).year); -#endif -} - -TEST(Parse, ExtendedSeconds) { - const TimeZone tz = UTCTimeZone(); - - // 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_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_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( - "%E*S", "0.214748364801234567890123456789012345678901234567890123456789", - tz, &tp)); - EXPECT_EQ(system_clock::from_time_t(0) + nanoseconds(214748364), tp); -} - -TEST(Parse, ExtendedSecondsScan) { - const TimeZone tz = UTCTimeZone(); - time_point tp; - for (int64_t ms = 0; ms < 1000; ms += 111) { - for (int64_t us = 0; us < 1000; us += 27) { - const int64_t micros = ms * 1000 + us; - for (int64_t ns = 0; ns < 1000; ns += 9) { - const auto expected = - system_clock::from_time_t(0) + nanoseconds(micros * 1000 + ns); - std::ostringstream oss; - 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_EQ(expected, tp) << input; - } - } - } -} - -TEST(Parse, ExtendedOffset) { - const TimeZone utc = UTCTimeZone(); - time_point tp; - - // %z against +-HHMM. - 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_EQ(MakeTime(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_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_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_EQ(MakeTime(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_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_EQ(MakeTime(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_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_EQ(MakeTime(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_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_EQ(MakeTime(1969, 12, 31, 12, 0, 0, utc), tp); - EXPECT_FALSE(Parse("%Ez", "-1", utc, &tp)); -} - -TEST(Parse, ExtendedYears) { - const TimeZone utc = UTCTimeZone(); - const char e4y_fmt[] = "%E4Y%m%d"; // no separators - time_point tp; - - // %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_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_EQ(MakeTime(-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_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_EQ(MakeTime(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_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_EQ(MakeTime(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); - - // 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)); -} - -TEST(Parse, RFC3339Format) { - const TimeZone tz = UTCTimeZone(); - time_point tp; - EXPECT_TRUE(Parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp)); - Breakdown bd = BreakTime(tp, tz); - 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_EQ(tp, tp2); -} - -// -// Roundtrip test for Format()/Parse(). -// - -TEST(FormatParse, RoundTrip) { - TimeZone lax; - EXPECT_TRUE(LoadTimeZone("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; - 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; - EXPECT_EQ(in, out); // RFC1123_full includes %z - } - - // Even though we don't know what %c will produce, it should 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; - EXPECT_EQ(in, out); - } -} - -} // namespace 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", - ], -)