Skip to content

Commit 2d71bd6

Browse files
committed
allow parsing and formatting of negative times
1 parent ee83128 commit 2d71bd6

File tree

2 files changed

+88
-20
lines changed

2 files changed

+88
-20
lines changed

src/elements/time.cpp

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ namespace adm {
4949
}
5050

5151
Time parseTimecode(const std::string& timecode) {
52-
const static std::regex commonFormat("(\\d{2}):(\\d{2}):(\\d{2}).(\\d+)");
52+
const static std::regex commonFormat(R"((-?)(\d{2}):(\d{2}):(\d{2}).(\d+))");
5353
const static std::regex fractionalFormat(
54-
"(\\d{2}):(\\d{2}):(\\d{2}).(\\d+)S(\\d+)");
54+
R"((-?)(\d{2}):(\d{2}):(\d{2}).(\d+)S(\d+))");
5555

5656
std::smatch timecodeMatch;
5757
if (std::regex_match(timecode, timecodeMatch, commonFormat)) {
58-
const std::string& ns_str = timecodeMatch[4];
58+
const std::string& ns_str = timecodeMatch[5];
5959

6060
// parse number of nanoseconds as if it always had 9 digits
6161
int64_t ns = 0;
@@ -64,18 +64,22 @@ namespace adm {
6464
if (i < ns_str.size()) ns += place_value * (ns_str[i] - '0');
6565
place_value *= 10;
6666
}
67+
auto isPositive = timecodeMatch[1].str().empty();
6768

68-
return std::chrono::hours(stoi(timecodeMatch[1])) +
69-
std::chrono::minutes(stoi(timecodeMatch[2])) +
70-
std::chrono::seconds(stoi(timecodeMatch[3])) +
71-
std::chrono::nanoseconds(ns);
69+
return (isPositive ? 1 : -1) *
70+
(std::chrono::hours(stoi(timecodeMatch[2])) +
71+
std::chrono::minutes(stoi(timecodeMatch[3])) +
72+
std::chrono::seconds(stoi(timecodeMatch[4])) +
73+
std::chrono::nanoseconds(ns));
7274
} else if (std::regex_match(timecode, timecodeMatch, fractionalFormat)) {
73-
int64_t seconds = 3600 * stoi(timecodeMatch[1]) +
74-
60 * stoi(timecodeMatch[2]) +
75-
1 * stoi(timecodeMatch[3]);
75+
auto isPositive = timecodeMatch[1].str().empty();
76+
int64_t seconds =
77+
3600 * stoi(timecodeMatch[2]) +
78+
60 * stoi(timecodeMatch[3]) +
79+
1 * stoi(timecodeMatch[4]);
7680

77-
int64_t numerator = stoi(timecodeMatch[4]);
78-
int64_t denominator = stoi(timecodeMatch[5]);
81+
int64_t numerator = stoi(timecodeMatch[5]);
82+
int64_t denominator = stoi(timecodeMatch[6]);
7983

8084
if (denominator == 0) {
8185
std::stringstream errorString;
@@ -84,7 +88,7 @@ namespace adm {
8488
throw std::runtime_error(errorString.str());
8589
}
8690

87-
return FractionalTime{seconds * denominator + numerator, denominator};
91+
return FractionalTime{(isPositive ? 1 : -1) * (seconds * denominator + numerator), denominator};
8892
} else {
8993
std::stringstream errorString;
9094
errorString << "invalid timecode: " << timecode;
@@ -95,18 +99,23 @@ namespace adm {
9599
struct FormatTimeVisitor : public boost::static_visitor<std::string> {
96100
std::string operator()(const std::chrono::nanoseconds& time) const {
97101
std::stringstream ss;
102+
auto formatTime = time;
103+
if (formatTime.count() < 0) {
104+
formatTime = -formatTime;
105+
ss << '-';
106+
}
98107
ss << std::setw(2) << std::setfill('0')
99-
<< std::chrono::duration_cast<std::chrono::hours>(time).count();
108+
<< std::chrono::duration_cast<std::chrono::hours>(formatTime).count();
100109
ss << ":";
101110
ss << std::setw(2) << std::setfill('0')
102-
<< std::chrono::duration_cast<std::chrono::minutes>(time).count() % 60;
111+
<< std::chrono::duration_cast<std::chrono::minutes>(formatTime).count() % 60;
103112
ss << ":";
104113
ss << std::setw(2) << std::setfill('0')
105-
<< std::chrono::duration_cast<std::chrono::seconds>(time).count() % 60;
114+
<< std::chrono::duration_cast<std::chrono::seconds>(formatTime).count() % 60;
106115
ss << ".";
107116

108117
{
109-
auto ns = time.count() % 1000000000;
118+
auto ns = formatTime.count() % 1000000000;
110119
// drop trailing zero digits, while keeping at least 5 to satisfy BS.2076-2
111120
int precision = 9;
112121
while (ns % 10 == 0 && precision > 5) {
@@ -120,18 +129,24 @@ namespace adm {
120129
}
121130

122131
std::string operator()(const FractionalTime& time) const {
123-
int64_t whole_seconds = time.numerator() / time.denominator();
132+
auto absNum = abs(time.numerator());
133+
auto absDenom = abs(time.denominator());
134+
int64_t whole_seconds = absNum / absDenom;
124135
int64_t frac_numerator =
125-
time.numerator() - whole_seconds * time.denominator();
136+
absNum - whole_seconds * absDenom;
126137

127138
std::stringstream ss;
139+
double floating = static_cast<double>(time.numerator()) / time.denominator();
140+
if (std::signbit(floating)) {
141+
ss << "-";
142+
}
128143
ss << std::setw(2) << std::setfill('0') << whole_seconds / 3600;
129144
ss << ":";
130145
ss << std::setw(2) << std::setfill('0') << (whole_seconds / 60) % 60;
131146
ss << ":";
132147
ss << std::setw(2) << std::setfill('0') << whole_seconds % 60;
133148
ss << ".";
134-
ss << frac_numerator << "S" << time.denominator();
149+
ss << frac_numerator << "S" << absDenom;
135150
return ss.str();
136151
}
137152
};

tests/adm_time_tests.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,56 @@ TEST_CASE("rational conversion") {
114114

115115
REQUIRE(asTime(RationalTime{1, 2}) == FractionalTime{1, 2});
116116
}
117+
118+
TEST_CASE("Parsed negative zero time == zero time") {
119+
auto time = parseTimecode("-00:00:00.00000");
120+
REQUIRE(time == Time{});
121+
}
122+
123+
TEST_CASE("Negative nanosecond times parsed correctly") {
124+
{
125+
auto time = parseTimecode("-00:00:01.00000");
126+
REQUIRE(time == Time{std::chrono::seconds{-1}});
127+
}
128+
{
129+
auto time = parseTimecode("-00:10:00.00000");
130+
REQUIRE(time == Time{std::chrono::minutes{-10}});
131+
}
132+
}
133+
134+
TEST_CASE("Format negative ns timecode") {
135+
{
136+
auto code = formatTimecode(Time{std::chrono::seconds{-1}});
137+
REQUIRE(code == "-00:00:01.00000");
138+
}
139+
{
140+
auto code = formatTimecode(Time{std::chrono::nanoseconds{-1}});
141+
REQUIRE(code == "-00:00:00.000000001");
142+
}
143+
{
144+
auto code = formatTimecode(Time{std::chrono::minutes{-1}});
145+
REQUIRE(code == "-00:01:00.00000");
146+
}
147+
{
148+
auto code = formatTimecode(Time{std::chrono::hours{-1}});
149+
REQUIRE(code == "-01:00:00.00000");
150+
}
151+
}
152+
153+
TEST_CASE("Parse negative fractional time") {
154+
REQUIRE(parseTimecode("-01:00:00.0S1") == Time(FractionalTime{-3600, 1}));
155+
REQUIRE(parseTimecode("-00:01:00.0S1") == Time(FractionalTime{-60, 1}));
156+
REQUIRE(parseTimecode("-00:00:01.0S1") == Time(FractionalTime{-1, 1}));
157+
REQUIRE(parseTimecode("-00:00:00.1S10") == Time(FractionalTime{-1, 10}));
158+
159+
// test leading zeros
160+
REQUIRE(parseTimecode("-00:00:00.01S010") == Time(FractionalTime{-1, 10}));
161+
}
162+
163+
TEST_CASE("Format negative fractional time") {
164+
REQUIRE("-01:00:00.0S1" == formatTimecode(FractionalTime{-3600, 1}));
165+
REQUIRE("-00:01:00.0S1" == formatTimecode(FractionalTime{-60, 1}));
166+
REQUIRE("-00:00:01.0S1" == formatTimecode(FractionalTime{-1, 1}));
167+
REQUIRE("-00:00:00.1S10" == formatTimecode(FractionalTime{-1, 10}));
168+
}
169+

0 commit comments

Comments
 (0)