SUTime: duration ranges produce a TIMEX3 element with no `value`
Found during a Claude audit of SUTime
SUTime: duration ranges produce a TIMEX3 element with no value
Summary
Expressions like "two to three days" are recognised correctly — right span, right type — but
the resulting TIMEX3 element has no value attribute. The range is carried in a
non-standard alt_value attribute instead.
This is not a regression and looks deliberate (see "Why it is like this" below). Filing it
to decide whether the current representation is what we want, since anything consuming
timex.value() sees nothing for these expressions.
Reproduction
Driving TimeExpressionExtractorImpl directly with the shipped English rules:
"It lasted two to three days."
<TIMEX3 alt_value="P2D/P3D" tid="t1" type="DURATION">two to three days</TIMEX3>
"It lasted three to five weeks."
<TIMEX3 alt_value="P3W/P5W" tid="t1" type="DURATION">three to five weeks</TIMEX3>For contrast, ordinary and inexact durations do get a value:
"It lasted about two months." -> value="P2M"
"a few days" -> value="PXD"
"several years" -> value="PXY"Why it is like this
SUTime.DurationRange.toFormattedString returns null for FORMAT_ISO and
FORMAT_TIMEX3_VALUE, with the call to super commented out:
public String toFormattedString(int flags) {
if ((flags & (FORMAT_ISO | FORMAT_TIMEX3_VALUE)) != 0) {
// return super.toFormattedString(flags);
return null;
}
...
}Temporal.getTimexAttributes then omits value when it is null and falls back to
alt_value, and DurationRange.includeTimexAltValue() returns true. So the behaviour is
intentional, and the commented-out line suggests someone considered emitting a value and
chose not to.
The underlying constraint is real: TIMEX3 value holds a single ISO 8601 duration and has
no representation for a range. P2D/P3D is an ISO interval, not a duration, so it is not a
legal value.
The machinery for picking a single value already exists —
DurationRange.getJodaTimePeriod() returns the midpoint of min and max — it is just not
used for the TIMEX value.
Options
Rough sketch, not a recommendation:
- Leave it. The information is present in
alt_valuefor anyone who looks. Costs nothing, butvaluestays absent and the attribute is non-standard. - Emit the max (or min) as
value, keepalt_valuefor the range. Every element gets a value; consumers that ignorealt_valueget something defensible rather than nothing. Loses precision silently. - Emit the midpoint, which
getJodaTimePeriod()already computes. Same shape as option 2, arguably less defensible for a discrete range like "two to three days". - Use the inexact form, e.g.
PXDwithalt_valuecarrying the exact range. Honest about the imprecision, consistent with how "a few days" is handled, but drops the magnitude entirely. - Use the TIMEX3
modattribute.Timexalready supportsmod, and TimeML definesEQUAL_OR_MORE/EQUAL_OR_LESS. Something likevalue="P2D" mod="EQUAL_OR_MORE"is closer to standard TIMEX3 thanalt_value, though it still cannot express both bounds.
Related, found while looking at this
alt_value does not survive an XML round trip through toXmlElement. Timex.java
writes the attribute under two different names depending on the serialiser:
toString()(line ~298 region, theStringBuilderpath) writesalt_valuetoXmlElement()writesaltValfromXml()readsalt_value
So a Timex serialised via toString round-trips, and one serialised via toXmlElement
comes back with altVal == null:
toString(): <TIMEX3 alt_value="P2D/P3D" ...> -> round trip altVal=P2D/P3D
toXmlElement(): <TIMEX3 altVal="P2D/P3D" ...> -> round trip altVal=nullThis matters more than it otherwise would precisely because alt_value is the only place a
duration range is recorded. Probably a one-word fix, but it is a separate change from the
representation question above and should be decided on its own.
Rule coverage gap. "between 2 and 4 hours" does not produce a range at all — only
4 hours is matched, as value="PT4H". Unrelated to the above; noting it so it is not
rediscovered.
Verification notes
Checked against the source-tree rule files (src/edu/stanford/nlp/time/rules/), not the
copies in the models jar, using TimeExpressionExtractorImpl directly with PTBTokenizer
input and the JollyDayHolidays binder disabled. The holidays binder plays no part in any of
these expressions.
Source: stanfordnlp/CoreNLP