Skip to content

Commit

Permalink
parsing Time with units
Browse files Browse the repository at this point in the history
  • Loading branch information
danovaro committed Sep 5, 2023
1 parent ffdee81 commit 5e63c45
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 61 deletions.
127 changes: 67 additions & 60 deletions src/eckit/types/Time.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* does it submit to any jurisdiction.
*/

#include <regex>

#include "eckit/eckit.h"

#include "eckit/persist/DumpLoad.h"
Expand Down Expand Up @@ -37,70 +39,76 @@ Time::Time(long seconds) :
}
}

Time::Time(const std::string& s) {
Tokenizer parse(":");
std::vector<std::string> 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, mm = 0, hh = 0, dd = 0;
std::smatch m;

if (std::regex_match (s, m, std::regex("^[0-9]+$"))) { // only digits
long t = std::stol(s);
if (s.length() < 3) { // cases: h, hh
hh = t;
} else {
if (s.length() < 5) { // 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-5]?[0-9])(:[0-5]?[0-9])?$"))) {
for (int i=1; i<m.size(); i++) {
if (m[i].matched) {
switch (i) {
case 1: hh = std::stol(m[i].str()); break;
case 2: mm = std::stol(m[i].str()); break;
case 3: std::string aux = m[i].str();
aux.erase(0,1);
ss = std::stol(aux); break;
}
}
}
}
else {
if (std::regex_match (s, m, std::regex("^([0-9]+[dD])?([0-9]+[hH])?([0-9]+[mM])?([0-9]+[sS])?$"))) {
for (int i=1; i<m.size(); i++) {
if (m[i].matched) {
std::string aux = m[i].str();
aux.pop_back();
long t = std::stol(aux);
switch (i) {
case 1: dd = t; break;
case 2: hh = t; break;
case 3: mm = t; break;
case 4: ss = t;
}
}
}
if (extended) {
ss += 60 * (mm + 60 * (hh + 24 * dd));
dd = ss / 86400;
hh = (ss / 3600) % 24;
mm = (ss / 60) % 60;
ss = ss % 60;
}
} else {
std::string msg = "Wrong input for time: ";
msg += s;
throw BadTime(msg);
}
}
}

if (hh >= 24 || mm >= 60 || ss >= 60 || hh < 0 || mm < 0 || ss < 0) {
if ((!extended && (hh >= 24 || dd > 0)) || mm >= 60 || ss >= 60 || hh < 0 || mm < 0 || ss < 0) {
std::string msg = "Wrong input for time: ";
Translator<long, std::string> t;
if (dd>0) {
msg += t(dd);
msg += " days ";
}
msg += t(hh);
msg += " hours ";
msg += t(mm);
Expand All @@ -109,8 +117,7 @@ Time::Time(const std::string& s) {
msg += " seconds";
throw BadTime(msg);
}

seconds_ = hh * 3600 + mm * 60 + ss;
seconds_ = dd * 86400 + hh * 3600 + mm * 60 + ss;
}

Time::operator std::string() const {
Expand Down
2 changes: 1 addition & 1 deletion src/eckit/types/Time.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Time {
public: // methods
Time(long, long, long);
Time(long seconds = 0);
Time(const std::string&);
Time(const std::string&, bool extended = false);

#include "eckit/types/Time.b"

Expand Down
4 changes: 4 additions & 0 deletions tests/types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
138 changes: 138 additions & 0 deletions tests/types/test_time.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* (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 <iomanip>
#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(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(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_THROWS(Time("120m"));
EXPECT(Time(2,0,0) == Time("120m", true));
EXPECT_THROWS(Time("7200s"));
EXPECT(Time(2,0,0) == Time("7200s", true));

EXPECT(Time(0,3,0) == Time("3M"));
EXPECT(Time(0,3,0) == Time("180s", true));

EXPECT(Time(1,23,45) == Time("1h23m45s"));
EXPECT(Time(1,23,45) == Time("01h23m45s"));
EXPECT(Time(1,23,45) == Time("5025s", true));
EXPECT(Time(1,23,45) == Time("83m45s", true));

EXPECT_THROWS(Time("25h"));
EXPECT_NO_THROW(Time("25h", true));

EXPECT(Time("0d3h10m20s") == Time("3h10m20s"));
EXPECT(Time("0d3h10m20s") == Time("3h620s", true));
EXPECT(Time("2D3h", true) == Time("51h", true));
}

//----------------------------------------------------------------------------------------------------------------------

} // namespace eckit::test

int main(int argc, char* argv[]) {
return run_tests(argc, argv);
}

0 comments on commit 5e63c45

Please sign in to comment.