Skip to content

Commit 5df98cb

Browse files
committed
Duration: support negative durations by prefixing a '-' before the P in ISO format
According to [this MDN document](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration) there is an ECMAScript extension to ISO 8601 to allow signed durations by putting a + or - before the ISO duration format. Internally we support signed durations -- we will parse "-60w" or "-60min", which we store in a `time_t` (whose signedness is technically implementation defined, but which is signed on all major compilers). However, when formatting these as ISO 8601 we get get garbed output like PT-1H-56M-9S, which we cannot parse and probably neither can anything else. (Taskwarrior, when asked to assign a negative duration to a duration-typed UDA, will store this garbled output but then reproduce it as PT0S, an unfortunate user experience.) This PR updates `Duration::formatISO` to instead prepend a '-' before negative durations, and updates `Duration::parse_designated` to parse such things. Alternative to GothenburgBitFactory#110, which simply blesses the garbled format by extending the parser to support it.
1 parent 3da1534 commit 5df98cb

File tree

1 file changed

+16
-7
lines changed

1 file changed

+16
-7
lines changed

src/Duration.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,25 +217,28 @@ bool Duration::parse_designated (Pig& pig)
217217
{
218218
auto checkpoint = pig.cursor ();
219219

220+
// sign = -1 if a '-' is present, else 1
221+
int sign = !pig.skip ('-') * 2 - 1;
222+
220223
if (pig.skip ('P') &&
221224
! pig.eos ())
222225
{
223226
long long value;
224227
pig.save ();
225228
if (pig.getDigits (value) && pig.skip ('Y'))
226-
_year = value;
229+
_year = sign * value;
227230
else
228231
pig.restore ();
229232

230233
pig.save ();
231234
if (pig.getDigits (value) && pig.skip ('M'))
232-
_month = value;
235+
_month = sign * value;
233236
else
234237
pig.restore ();
235238

236239
pig.save ();
237240
if (pig.getDigits (value) && pig.skip ('D'))
238-
_day = value;
241+
_day = sign * value;
239242
else
240243
pig.restore ();
241244

@@ -244,19 +247,19 @@ bool Duration::parse_designated (Pig& pig)
244247
{
245248
pig.save ();
246249
if (pig.getDigits (value) && pig.skip ('H'))
247-
_hours = value;
250+
_hours = sign * value;
248251
else
249252
pig.restore ();
250253

251254
pig.save ();
252255
if (pig.getDigits (value) && pig.skip ('M'))
253-
_minutes = value;
256+
_minutes = sign * value;
254257
else
255258
pig.restore ();
256259

257260
pig.save ();
258261
if (pig.getDigits (value) && pig.skip ('S'))
259-
_seconds = value;
262+
_seconds = sign * value;
260263
else
261264
pig.restore ();
262265
}
@@ -454,12 +457,18 @@ std::string Duration::formatISO () const
454457
if (_period)
455458
{
456459
time_t t = _period;
460+
461+
std::stringstream s;
462+
if (t < 0) {
463+
s << '-';
464+
t *= -1;
465+
}
466+
457467
int seconds = t % 60; t /= 60;
458468
int minutes = t % 60; t /= 60;
459469
int hours = t % 24; t /= 24;
460470
int days = t;
461471

462-
std::stringstream s;
463472
s << 'P';
464473
if (days) s << days << 'D';
465474

0 commit comments

Comments
 (0)