diff --git a/src/elements/time.cpp b/src/elements/time.cpp index a5fe7316..647ef5d3 100644 --- a/src/elements/time.cpp +++ b/src/elements/time.cpp @@ -1,5 +1,6 @@ #include "adm/elements/time.hpp" #include +#include #include #include #include @@ -49,13 +50,14 @@ namespace adm { } Time parseTimecode(const std::string& timecode) { - const static std::regex commonFormat("(\\d{2}):(\\d{2}):(\\d{2}).(\\d+)"); + const static std::regex commonFormat( + R"((-?)(\d{2}):(\d{2}):(\d{2}).(\d+))"); const static std::regex fractionalFormat( - "(\\d{2}):(\\d{2}):(\\d{2}).(\\d+)S(\\d+)"); + R"((-?)(\d{2}):(\d{2}):(\d{2}).(\d+)S(\d+))"); std::smatch timecodeMatch; if (std::regex_match(timecode, timecodeMatch, commonFormat)) { - const std::string& ns_str = timecodeMatch[4]; + const std::string& ns_str = timecodeMatch[5]; // parse number of nanoseconds as if it always had 9 digits int64_t ns = 0; @@ -64,18 +66,21 @@ namespace adm { if (i < ns_str.size()) ns += place_value * (ns_str[i] - '0'); place_value *= 10; } + auto isPositive = timecodeMatch[1].str().empty(); - return std::chrono::hours(stoi(timecodeMatch[1])) + - std::chrono::minutes(stoi(timecodeMatch[2])) + - std::chrono::seconds(stoi(timecodeMatch[3])) + - std::chrono::nanoseconds(ns); + return (isPositive ? 1 : -1) * + (std::chrono::hours(stoi(timecodeMatch[2])) + + std::chrono::minutes(stoi(timecodeMatch[3])) + + std::chrono::seconds(stoi(timecodeMatch[4])) + + std::chrono::nanoseconds(ns)); } else if (std::regex_match(timecode, timecodeMatch, fractionalFormat)) { - int64_t seconds = 3600 * stoi(timecodeMatch[1]) + - 60 * stoi(timecodeMatch[2]) + - 1 * stoi(timecodeMatch[3]); + auto isPositive = timecodeMatch[1].str().empty(); + int64_t seconds = 3600 * stoi(timecodeMatch[2]) + + 60 * stoi(timecodeMatch[3]) + + 1 * stoi(timecodeMatch[4]); - int64_t numerator = stoi(timecodeMatch[4]); - int64_t denominator = stoi(timecodeMatch[5]); + int64_t numerator = stoi(timecodeMatch[5]); + int64_t denominator = stoi(timecodeMatch[6]); if (denominator == 0) { std::stringstream errorString; @@ -84,7 +89,9 @@ namespace adm { throw std::runtime_error(errorString.str()); } - return FractionalTime{seconds * denominator + numerator, denominator}; + return FractionalTime{ + (isPositive ? 1 : -1) * (seconds * denominator + numerator), + denominator}; } else { std::stringstream errorString; errorString << "invalid timecode: " << timecode; @@ -95,18 +102,27 @@ namespace adm { struct FormatTimeVisitor : public boost::static_visitor { std::string operator()(const std::chrono::nanoseconds& time) const { std::stringstream ss; + auto formatTime = time; + if (formatTime.count() < 0) { + formatTime = -formatTime; + ss << '-'; + } ss << std::setw(2) << std::setfill('0') - << std::chrono::duration_cast(time).count(); + << std::chrono::duration_cast(formatTime).count(); ss << ":"; ss << std::setw(2) << std::setfill('0') - << std::chrono::duration_cast(time).count() % 60; + << std::chrono::duration_cast(formatTime) + .count() % + 60; ss << ":"; ss << std::setw(2) << std::setfill('0') - << std::chrono::duration_cast(time).count() % 60; + << std::chrono::duration_cast(formatTime) + .count() % + 60; ss << "."; { - auto ns = time.count() % 1000000000; + auto ns = formatTime.count() % 1000000000; // drop trailing zero digits, while keeping at least 5 to satisfy BS.2076-2 int precision = 9; while (ns % 10 == 0 && precision > 5) { @@ -120,18 +136,24 @@ namespace adm { } std::string operator()(const FractionalTime& time) const { - int64_t whole_seconds = time.numerator() / time.denominator(); - int64_t frac_numerator = - time.numerator() - whole_seconds * time.denominator(); + auto absNum = abs(time.numerator()); + auto absDenom = abs(time.denominator()); + int64_t whole_seconds = absNum / absDenom; + int64_t frac_numerator = absNum - whole_seconds * absDenom; std::stringstream ss; + double floating = + static_cast(time.numerator()) / time.denominator(); + if (std::signbit(floating)) { + ss << "-"; + } ss << std::setw(2) << std::setfill('0') << whole_seconds / 3600; ss << ":"; ss << std::setw(2) << std::setfill('0') << (whole_seconds / 60) % 60; ss << ":"; ss << std::setw(2) << std::setfill('0') << whole_seconds % 60; ss << "."; - ss << frac_numerator << "S" << time.denominator(); + ss << frac_numerator << "S" << absDenom; return ss.str(); } }; diff --git a/tests/adm_time_tests.cpp b/tests/adm_time_tests.cpp index fe932881..8c77a12e 100644 --- a/tests/adm_time_tests.cpp +++ b/tests/adm_time_tests.cpp @@ -114,3 +114,55 @@ TEST_CASE("rational conversion") { REQUIRE(asTime(RationalTime{1, 2}) == FractionalTime{1, 2}); } + +TEST_CASE("Parsed negative zero time == zero time") { + auto time = parseTimecode("-00:00:00.00000"); + REQUIRE(time == Time{}); +} + +TEST_CASE("Negative nanosecond times parsed correctly") { + { + auto time = parseTimecode("-00:00:01.00000"); + REQUIRE(time == Time{std::chrono::seconds{-1}}); + } + { + auto time = parseTimecode("-00:10:00.00000"); + REQUIRE(time == Time{std::chrono::minutes{-10}}); + } +} + +TEST_CASE("Format negative ns timecode") { + { + auto code = formatTimecode(Time{std::chrono::seconds{-1}}); + REQUIRE(code == "-00:00:01.00000"); + } + { + auto code = formatTimecode(Time{std::chrono::nanoseconds{-1}}); + REQUIRE(code == "-00:00:00.000000001"); + } + { + auto code = formatTimecode(Time{std::chrono::minutes{-1}}); + REQUIRE(code == "-00:01:00.00000"); + } + { + auto code = formatTimecode(Time{std::chrono::hours{-1}}); + REQUIRE(code == "-01:00:00.00000"); + } +} + +TEST_CASE("Parse negative fractional time") { + REQUIRE(parseTimecode("-01:00:00.0S1") == Time(FractionalTime{-3600, 1})); + REQUIRE(parseTimecode("-00:01:00.0S1") == Time(FractionalTime{-60, 1})); + REQUIRE(parseTimecode("-00:00:01.0S1") == Time(FractionalTime{-1, 1})); + REQUIRE(parseTimecode("-00:00:00.1S10") == Time(FractionalTime{-1, 10})); + + // test leading zeros + REQUIRE(parseTimecode("-00:00:00.01S010") == Time(FractionalTime{-1, 10})); +} + +TEST_CASE("Format negative fractional time") { + REQUIRE("-01:00:00.0S1" == formatTimecode(FractionalTime{-3600, 1})); + REQUIRE("-00:01:00.0S1" == formatTimecode(FractionalTime{-60, 1})); + REQUIRE("-00:00:01.0S1" == formatTimecode(FractionalTime{-1, 1})); + REQUIRE("-00:00:00.1S10" == formatTimecode(FractionalTime{-1, 10})); +}