diff --git a/src/eckit/types/Time.cc b/src/eckit/types/Time.cc index a4931fbc9..f10eb9ba8 100644 --- a/src/eckit/types/Time.cc +++ b/src/eckit/types/Time.cc @@ -8,13 +8,15 @@ * does it submit to any jurisdiction. */ +#include +#include + #include "eckit/eckit.h" #include "eckit/persist/DumpLoad.h" #include "eckit/types/Time.h" #include "eckit/utils/Hash.h" #include "eckit/utils/Tokenizer.h" -#include "eckit/utils/Translator.h" namespace eckit { @@ -27,90 +29,108 @@ inline void printTime(std::ostream& s, long n) { s << n; } -Time::Time(long seconds) : - seconds_(seconds) { - if (seconds >= 86400 || seconds < 0) { - std::string msg = "Time in seconds must be positive and cannot exceed 86400, seconds: "; - Translator t; - msg += t(seconds); +Time::Time(long seconds, bool extended) : + seconds_(static_cast(seconds)) { + if ((seconds >= 86400 && !extended) || seconds < 0) { + std::string msg = "Time in seconds must be positive and less than 86400 seconds (24h): "; + msg += std::to_string(seconds); throw BadTime(msg); } } -Time::Time(const std::string& s) { - Tokenizer parse(":"); - std::vector result; - - parse(s, result); - - long hh = 0, mm = 0, ss = 0; - bool err = false; - long t = atol(s.c_str()); - - switch (result.size()) { - case 1: - // hh or hhmm or hhmmss - switch (s.length()) { - case 2: - hh = t; - break; - case 4: - hh = t / 100; - mm = t % 100; - break; - case 6: - hh = t / 10000; - mm = (t % 10000) / 100; - ss = (t % 10000) % 100; - break; - default: - err = true; - break; +Time::Time(const std::string& s, bool extended) { + long ss = 0; + long mm = 0; + long hh = 0; + long dd = 0; + std::smatch m; + + if (std::regex_match (s, m, std::regex("^-?[0-9]+$"))) { // only digits + long t = std::stol(s); + int sign = (s[0] == '-' ? 1 : 0); + if (extended || s.length() <= 2+sign) { // cases: h, hh, (or hhh..h for step parsing) + hh = t; + } else { + if (s.length() <= 4+sign) { // cases: hmm, hhmm + hh = t / 100; + mm = t % 100; + } else { // cases: hmmss, hhmmss + hh = t / 10000; + mm = (t / 100) % 100; + ss = t % 100; } - break; - - case 2: - // hh:mm - err = result[0].length() != 2 || result[1].length() != 2; - - hh = atol(result[0].c_str()); - mm = atol(result[1].c_str()); - - break; - - case 3: - // hh:mm:ss - - err = result[0].length() != 2 || result[1].length() != 2 || result[2].length() != 2; - - hh = atol(result[0].c_str()); - mm = atol(result[1].c_str()); - ss = atol(result[2].c_str()); - - break; - - default: - err = true; - break; + } } - - if (err) { - throw BadTime(std::string("Invalid time ") + s); + else { + if (std::regex_match (s, m, std::regex("^-?[0-9]*\\.[0-9]+$"))) { // floating point (hours) + long sec = std::round(std::stod(s)*3600); + hh = sec/3600; + sec -= hh*3600; + mm = sec/60; + sec -= mm*60; + ss = sec; + } + else { + if (std::regex_match (s, m, std::regex("^([0-9]+):([0-5]?[0-9])(:[0-5]?[0-9])?$"))) { + for (int i=1; i= 24 || mm >= 60 || ss >= 60 || hh < 0 || mm < 0 || ss < 0) { + if (mm >= 60 || ss >= 60 || (!extended && (hh >= 24 || dd > 0 || hh < 0 || mm < 0 || ss < 0))) { std::string msg = "Wrong input for time: "; - Translator t; - msg += t(hh); + if (dd>0) { + msg += std::to_string(dd); + msg += " days "; + } + msg += std::to_string(hh); msg += " hours "; - msg += t(mm); + msg += std::to_string(mm); msg += " minutes "; - msg += t(ss); + msg += std::to_string(ss); msg += " seconds"; throw BadTime(msg); } - - seconds_ = hh * 3600 + mm * 60 + ss; + seconds_ = dd * 86400 + hh * 3600 + mm * 60 + ss; } Time::operator std::string() const { @@ -127,16 +147,15 @@ Time& Time::operator=(const Time& other) { return *this; } -Time::Time(long hh, long mm, long ss) : +Time::Time(long hh, long mm, long ss, bool extended) : seconds_(hh * 3600 + mm * 60 + ss) { - if (hh >= 24 || mm >= 60 || ss >= 60 || hh < 0 || mm < 0 || ss < 0) { + if (mm >= 60 || ss >= 60 || (!extended && (hh >= 24 || hh < 0 || mm < 0 || ss < 0))) { std::string msg = "Wrong input for time: "; - Translator t; - msg += t(hh); + msg += std::to_string(hh); msg += " hours "; - msg += t(mm); + msg += std::to_string(mm); msg += " minutes "; - msg += t(ss); + msg += std::to_string(ss); msg += " seconds"; throw BadTime(msg); } diff --git a/src/eckit/types/Time.h b/src/eckit/types/Time.h index 0822b81a7..4e40c33c7 100644 --- a/src/eckit/types/Time.h +++ b/src/eckit/types/Time.h @@ -25,16 +25,16 @@ class DumpLoad; class Bless; class Hash; -typedef double Second; +using Second = double; //---------------------------------------------------------------------------------------------------------------------- class Time { public: // methods - Time(long, long, long); - Time(long seconds = 0); - Time(const std::string&); + Time(long hours, long minutes, long seconds, bool extended = false); + Time(long seconds = 0, bool extended = false); + Time(const std::string& time, bool extended = false); #include "eckit/types/Time.b" diff --git a/tests/types/CMakeLists.txt b/tests/types/CMakeLists.txt index d59aeb93e..5e2154093 100644 --- a/tests/types/CMakeLists.txt +++ b/tests/types/CMakeLists.txt @@ -14,6 +14,10 @@ ecbuild_add_test( TARGET eckit_test_types_floatcompare SOURCES test_floatcompare.cc LIBS eckit ) +ecbuild_add_test( TARGET eckit_test_types_time + SOURCES test_time.cc + LIBS eckit ) + ecbuild_add_test( TARGET eckit_test_types_uuid SOURCES test_uuid.cc LIBS eckit ) diff --git a/tests/types/test_time.cc b/tests/types/test_time.cc new file mode 100644 index 000000000..8d6c20388 --- /dev/null +++ b/tests/types/test_time.cc @@ -0,0 +1,176 @@ +/* + * (C) Copyright 2021- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + + +#include +#include "eckit/testing/Test.h" +#include "eckit/types/Time.h" + +using namespace std; +using namespace eckit; +using namespace eckit::testing; + +using eckit::types::is_approximately_equal; + +namespace eckit::test { + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("Time only digits (hhmmss)") { + EXPECT(Time(0,0,0) == Time("0")); + EXPECT(Time(0,0,0) == Time("00")); + EXPECT(Time(0,0,0) == Time("000")); + EXPECT(Time(0,0,0) == Time("0000")); + EXPECT(Time(0,0,0) == Time("00000")); + EXPECT(Time(0,0,0) == Time("000000")); + EXPECT(Time(0,0,0) == Time(0)); + EXPECT(Time(0,0,1) == Time(1)); + EXPECT(Time(0,1,0) == Time(60)); + EXPECT(Time(0,1,1) == Time(61)); + EXPECT(Time(1,0,0) == Time(3600)); + EXPECT(Time(1,0,1) == Time(3601)); + EXPECT(Time(1,1,0) == Time(3660)); + EXPECT(Time(1,1,1) == Time(3661)); + EXPECT(Time(2,3,4) == Time(3600*2 + 60*3 + 4)); + EXPECT_THROWS(Time(24*3600)); + EXPECT_THROWS(Time(24,0,0)); + EXPECT_THROWS(Time("24")); + EXPECT_THROWS(Time(-1)); + EXPECT_THROWS(Time("-1")); + EXPECT_NO_THROW(Time(-1,0,0, true)); + EXPECT_NO_THROW(Time("-1", true)); + EXPECT(Time(-1,0,0, true) == Time("-1", true)); + EXPECT(Time(24,0,0, true) == Time(24*3600, true)); + EXPECT(Time(24,0,0, true) == Time("24", true)); + EXPECT(Time(-1,0,0, true) == Time("-1", true)); + EXPECT(Time(-1,0,0, true) == Time("-01", true)); + EXPECT(Time(-100,0,0, true) == Time("-100", true)); + EXPECT(Time(-100,0,0, true) == Time("-0100", true)); + EXPECT(Time(0,-30,0, true) == Time("-0.5", true)); + + EXPECT(Time(2,0,0) == Time("2")); + EXPECT(Time(2,0,0) == Time("02")); + EXPECT(Time(2,0,0) == Time("200")); + EXPECT(Time(2,0,0) == Time("0200")); + EXPECT(Time(2,0,0) == Time("20000")); + EXPECT(Time(2,0,0) == Time("020000")); + + EXPECT(Time(20,0,0) == Time("20")); + EXPECT(Time(20,0,0) == Time("2000")); + EXPECT(Time(20,0,0) == Time("200000")); + EXPECT(Time(20,0,0) == Time("20", true)); + EXPECT_THROWS(Time(30,0,0)); + EXPECT_THROWS(Time("30")); + EXPECT_THROWS(Time("3000")); + EXPECT_THROWS(Time("300000")); + EXPECT(Time(30,0,0, true) == Time("30", true)); + + + EXPECT(Time(0,3,0) == Time("003")); + EXPECT(Time(0,3,0) == Time("0003")); + EXPECT(Time(0,3,0) == Time("00300")); + EXPECT(Time(0,3,0) == Time("000300")); + + EXPECT(Time(0,0,4) == Time("00004")); + EXPECT(Time(0,0,4) == Time("000004")); + + EXPECT(Time(1,23,0) == Time("123")); + EXPECT(Time(1,23,0) == Time("0123")); + EXPECT(Time(1,23,0) == Time("12300")); + EXPECT(Time(1,23,0) == Time("012300")); + + EXPECT(Time(1,23,45) == Time("12345")); + EXPECT(Time(1,23,45) == Time("012345")); + + EXPECT_THROWS(Time("25")); + EXPECT_THROWS(Time("175")); + EXPECT_THROWS(Time("0175")); + EXPECT_THROWS(Time("3025")); + EXPECT_THROWS(Time("017345")); + EXPECT_THROWS(Time("012375")); +} + +CASE("Time format (hh:mm:ss)") { + + EXPECT(Time(0,0,0) == Time("0:0")); + EXPECT(Time(0,0,0) == Time("0:00")); + EXPECT(Time(0,0,0) == Time("00:0")); + EXPECT(Time(0,0,0) == Time("00:00")); + EXPECT(Time(0,0,0) == Time("0:00:00")); + EXPECT(Time(0,0,0) == Time("00:0:00")); + EXPECT(Time(0,0,0) == Time("00:00:0")); + EXPECT(Time(0,0,0) == Time("00:00:00")); + + EXPECT(Time(2,0,0) == Time("2:0")); + EXPECT(Time(2,0,0) == Time("02:0")); + EXPECT(Time(2,0,0) == Time("2:00")); + EXPECT(Time(2,0,0) == Time("02:00")); + EXPECT(Time(2,0,0) == Time("2:00:00")); + EXPECT(Time(2,0,0) == Time("02:00:00")); + + EXPECT(Time(0,3,0) == Time("00:3")); + EXPECT(Time(0,3,0) == Time("00:03")); + EXPECT(Time(0,3,0) == Time("0:03:00")); + EXPECT(Time(0,3,0) == Time("00:3:00")); + EXPECT(Time(0,3,0) == Time("00:03:00")); + + EXPECT(Time(0,0,4) == Time("00:00:4")); + EXPECT(Time(0,0,4) == Time("00:00:04")); + + EXPECT(Time(1,23,0) == Time("1:23")); + EXPECT(Time(1,23,0) == Time("01:23")); + EXPECT(Time(1,23,0) == Time("1:23:00")); + EXPECT(Time(1,23,0) == Time("01:23:00")); + + EXPECT(Time(1,23,45) == Time("1:23:45")); + EXPECT(Time(1,23,45) == Time("01:23:45")); + + EXPECT_THROWS(Time("25")); + EXPECT_THROWS(Time("175")); + EXPECT_THROWS(Time("0175")); + EXPECT_THROWS(Time("3025")); + EXPECT_THROWS(Time("017345")); + EXPECT_THROWS(Time("012375")); +} + +CASE("Time with unit (__h__m__s)") { + EXPECT(Time(2,0,0) == Time("2h")); + EXPECT(Time(2,0,0) == Time("0002H")); + EXPECT(Time(2,0,0) == Time("120m")); + EXPECT(Time(2,0,0) == Time("7200s")); + + EXPECT(Time(0,3,0) == Time("3M")); + EXPECT(Time(0,3,0) == Time("180s")); + + EXPECT(Time(1,2,3) == Time("1h2m3s")); + EXPECT(Time(1,23,45) == Time("1h23m45s")); + EXPECT(Time(1,23,45) == Time("01h23m45s")); + EXPECT(Time(1,23,45) == Time("5025s")); + EXPECT(Time(1,23,45) == Time("83m45s")); + + EXPECT_THROWS(Time("25h")); + EXPECT_NO_THROW(Time("25h", true)); + + EXPECT(Time(0,-30,0, true) == Time("-30m", true)); + EXPECT(Time(-1,-30,0, true) == Time("-1h30m", true)); + EXPECT(Time(0,-90,0, true) == Time("-1h30m", true)); + + EXPECT(Time("0d3h10m20s") == Time("3h10m20s")); + EXPECT(Time("0d3h10m20s") == Time("3h620s")); + EXPECT(Time("2D3h", true) == Time("51h", true)); +} + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace eckit::test + +int main(int argc, char* argv[]) { + return run_tests(argc, argv); +}