Reduration, a human-readable second-wise time duration format
Let:
- "Calender-wise duration" processes durations, such as 2 months, relative to a specific date-time. The value 2 simply advances the month by two regardless the number of days and number of leap seconds in those months.
- "Second-wise duration" just handles the number of elapsed seconds.
Reduration is designed and intended for the "second-wise" duration. Therefore:
- it does not have second-ambiguous units, "month" or "year". "week" is not second-ambiguous but not included to keep it a small format.
- it always interprets an hour as exactly 3600 seconds and a day as exactly 24 * 3600 seconds.
- in chat: "The token is valid for
300s
, not3000s
." - in YAML:
cachePolicy: defaultTTL: 30 mins
- in a URL parameter:
?valid_for=1days%20-1secs
instead of?valid_for=86399
- serialization/deserialization:
"{\"valid_for\": \"2 hours\"}"
into/from#[derive(Serialize, Deserialize)] struct Options { #[serde(with = "reduration::serde::rich")] valid_for: Duration }
- in database:
INSERT INTO cache_ttl VALUES ('ryo33', '1h 2m 3.4s');
- in a source code:
static DEFAULT_TTL: Duration = reduration!("1 hours");`
- in a protocol, API reference, or API schema:
id: uuid - ID for this resource name: string - A name for this resource valid_for: reduration - ttl for this resouce
28 days
1 hours 1 nanos
(cannot be singular form)9.58s
(shorthand style)1h -1s
(same as3599s
)999_999_999 days
(999999999 is the max value for each field)plus 1 days
andminus 1 days
(plus/minus must be explicit for signed-reduration)
; **case sensitive**
reduration = day-part
signed-reduration = ("plus" / "minus") day-part
day-part = days [ws [sign] hour-part]
day-part =/ hour-part
hour-part = hours [ws [sign] min-part]
hour-part =/ min-part
min-part = mins [ws [sign] sec-part]
min-part =/ sec-part
sec-part = secs-frac ; no milli-part
sec-part =/ secs [ws [sign] milli-part]
sec-part =/ milli-part
milli-part = millis-frac ; no micro-part
milli-part =/ millis [ws [sign] micro-part]
milli-part =/ micro-part
micro-part = micros-frac ; no nanos
micro-part =/ micros [ws [sign] nanos]
micro-part =/ nanos
days = 1*9dec [ws] ("days" / "d")
hours = 1*9dec [ws] ("hours" / "h")
mins = 1*9dec [ws] ("mins" / "m")
secs = 1*9dec [ws] ("secs" / "s")
millis = 1*9dec [ws] ("millis" / "ms")
micros = 1*9dec [ws] ("micros" / "us")
nanos = 1*9dec [ws] ("nanos" / "ns")
secs-frac = 1*9dec "." 1*9dec [ws] ("secs" / "s")
millis-frac = 1*9dec "." 1*6dec [ws] ("millis" / "ms")
micros-frac = 1*9dec "." 1*3dec [ws] ("micros" / "us")
dec = DIGIT / "_"
sign = ("-" / "+") [ws]
ws = " "
struct Reduration {
days: Dec9,
hours: Signed9,
mins: Signed9,
secs: Signed9,
secs_frac: Dec9,
millis: Signed9,
millis_frac: Dec6,
micros: Signed9,
micros_frac: Dec3,
nanos: Signed9
}
struct Signed9(i32); // -999_999_999 to 999_999_999
struct Dec9(u32); // 0 to 999_999_999
struct Dec6(u32); // 0 to 999_999
struct Dec3(u16); // 0 to 999
enum Value<P, N> {
Positive(P),
Negative(N),
}
struct SignedReduration {
minus: bool,
tail: Reduration,
}
No leap second.
let timestamp_in_nano: BigUint = nanos + 1000 * (millis + 1000 * (secs + 60 * (mins + 60 * (hours + 24 * days))));
Lack of precision must be reported as an error. There are two cases (into and from);
- a reduration object converted into other representation that lacks time precision to represent the same value;
- a reduration object converted from a value of other representation that has picoseconds-level or more granular values.
Therefore, a user need to manually perform a round/ceil/floor before such a conversion.
Every integer overflows in conversion between reduration and other representations must be reported as errors. A user still can ignore the overflow errors.
Reduration format can be used in both use-case, signed duration or unsigned duration, but a system must not allow both grammars reduration
and signed-reduration
.
Regardless of which grammar, the day-part
must be 0 or positive nanoseconds, and negative values must be rejected as invalid reduration.
Valid strings:
1 hours
-1 hours 61min
Invalid strings:
-1 hours
1 hours -61min
Leading zeros are ignored.
A fractional value must be handled as a pair of signed-int and unsigned-int, not floating point.
Examples:
1.234s
equals to1s 234ms
1.23456
equals to1s 234ms 560us
1s 23456.7us
equals to1s 23456us 700ns
1h +0s
and 1h -0s
are same as 1h 0s
or just 1h
.
A serializer must follow the grammar.
A deserializer:
- must handle an input in UTF-8 encoding.
- can allow incorrect casing like
1 Hours
or1H
. - can allow incorrect spacing like
1 hours
(too many spaces) or1mins2secs
(needs a space aftermins
). - can allow leading/trailing spaces.
- can allow UTF-8 whitespaces.
- should not allow other incorrect grammars not listed in the above.
A converter should have an option that let users choose seil
, floor
, or round
as fallback logic on the precision error.
As shown in the semantics/data structure section, naive numerical data structure takes not-tiny memory. The following technique is recommended for memory efficiency.
let mut nanos = 0;
let mut previous_token_kind = None;
while let Some(token) = parse_next_token(&mut input) {
if let Some(prev) = previous_next_token {
assert!(prev > token.kind);
}
match token {
Token::Nanos(value) => {
checked!(nanos += value);
}
Token::Micros { value, frac } => {
checked!(nanos += value * 1000);
assert!(frac <= 999);
checked!(nanos += frac);
}
_ => unimplemented!(),
}
previous_token_kind = Some(token.kind);
}
- tailhook/humantime
- ISO 8601 (and RFC 3339)