///|
pub fn parse_duration(input : String) -> Result[DurationValue, DurationError] {
Ok(parse_duration_or_raise(input)) catch {
err => Err(err)
}
}
///|
fn parse_duration_or_raise(input : String) -> DurationValue raise DurationError {
let value = trim_ascii(input)
if value.length() == 0 {
raise EmptyDuration
}
let mut negative = false
let mut i = 0
if value[0] == 45 {
negative = true
i = 1
}
if i >= value.length() || value[i] != 80 {
raise MissingDurationPrefix
}
i += 1
let mut weeks = 0
let mut days = 0
let mut hours = 0
let mut minutes = 0
let mut seconds = 0
let mut in_time = false
let mut saw_value = false
while i < value.length() {
if value[i] == 84 {
if in_time {
raise BadDurationDesignator("T")
}
in_time = true
i += 1
} else {
let number_start = i
while i < value.length() && value[i] >= 48 && value[i] <= 57 {
i += 1
}
if number_start == i {
raise BadDurationNumber(value[number_start:value.length()].to_owned())
}
let number_text = value[number_start:i].to_owned()
let amount = parse_duration_int(number_text)
if i >= value.length() {
raise BadDurationDesignator(number_text)
}
let designator = value[i]
if designator == 87 {
if in_time || days > 0 || hours > 0 || minutes > 0 || seconds > 0 {
raise MixedWeekAndDateTime
}
if weeks > 0 {
raise BadDurationDesignator("W")
}
weeks = amount
} else if designator == 68 {
if weeks > 0 {
raise MixedWeekAndDateTime
}
if in_time || days > 0 {
raise BadDurationDesignator("D")
}
days = amount
} else if designator == 72 {
if weeks > 0 {
raise MixedWeekAndDateTime
}
if !in_time || hours > 0 {
raise BadDurationDesignator("H")
}
hours = amount
} else if designator == 77 {
if weeks > 0 {
raise MixedWeekAndDateTime
}
if !in_time || minutes > 0 {
raise BadDurationDesignator("M")
}
minutes = amount
} else if designator == 83 {
if weeks > 0 {
raise MixedWeekAndDateTime
}
if !in_time || seconds > 0 {
raise BadDurationDesignator("S")
}
seconds = amount
} else {
raise BadDurationDesignator(value[i:i + 1].to_owned())
}
saw_value = true
i += 1
}
}
if !saw_value {
raise EmptyDurationBody
}
if in_time && hours == 0 && minutes == 0 && seconds == 0 {
raise EmptyDurationBody
}
{ negative, weeks, days, hours, minutes, seconds }
}
///|
pub fn render_duration(value : DurationValue) -> String {
let b = StringBuilder::new()
if value.negative {
b.write_string("-")
}
b.write_string("P")
if value.weeks > 0 {
b.write_string(value.weeks.to_string())
b.write_string("W")
return b.to_string()
}
if value.days > 0 {
b.write_string(value.days.to_string())
b.write_string("D")
}
if value.hours > 0 || value.minutes > 0 || value.seconds > 0 {
b.write_string("T")
if value.hours > 0 {
b.write_string(value.hours.to_string())
b.write_string("H")
}
if value.minutes > 0 {
b.write_string(value.minutes.to_string())
b.write_string("M")
}
if value.seconds > 0 {
b.write_string(value.seconds.to_string())
b.write_string("S")
}
}
if value.days == 0 &&
value.hours == 0 &&
value.minutes == 0 &&
value.seconds == 0 {
b.write_string("T0S")
}
b.to_string()
}
///|
pub fn normalize_duration(input : String) -> Result[String, DurationError] {
match parse_duration(input) {
Ok(value) => Ok(render_duration(value))
Err(err) => Err(err)
}
}
///|
pub fn duration_from_seconds(seconds : Int) -> DurationValue {
let mut remaining = seconds
let mut negative = false
if remaining < 0 {
negative = true
remaining = 0 - remaining
}
let days = remaining / 86400
remaining = remaining - days * 86400
let hours = remaining / 3600
remaining = remaining - hours * 3600
let minutes = remaining / 60
remaining = remaining - minutes * 60
{ negative, weeks: 0, days, hours, minutes, seconds: remaining }
}
///|
pub fn duration_from_minutes(minutes : Int) -> DurationValue {
duration_from_seconds(minutes * 60)
}
///|
pub fn DurationValue::total_seconds_abs(self : DurationValue) -> Int {
self.weeks * 604800 +
self.days * 86400 +
self.hours * 3600 +
self.minutes * 60 +
self.seconds
}
///|
pub fn DurationValue::signed_seconds(self : DurationValue) -> Int {
if self.negative {
0 - self.total_seconds_abs()
} else {
self.total_seconds_abs()
}
}
///|
pub fn DurationValue::is_zero(self : DurationValue) -> Bool {
self.total_seconds_abs() == 0
}
///|
pub fn DurationValue::has_week_form(self : DurationValue) -> Bool {
self.weeks > 0
}
///|
pub fn DurationValue::has_date_form(self : DurationValue) -> Bool {
self.days > 0
}
///|
pub fn DurationValue::has_time_form(self : DurationValue) -> Bool {
self.hours > 0 || self.minutes > 0 || self.seconds > 0
}
///|
pub fn DurationValue::is_negative(self : DurationValue) -> Bool {
self.negative && !self.is_zero()
}
///|
pub fn DurationValue::is_positive(self : DurationValue) -> Bool {
!self.negative && !self.is_zero()
}
///|
pub fn DurationValue::is_short_alarm_lead(self : DurationValue) -> Bool {
let seconds = self.total_seconds_abs()
seconds > 0 && seconds <= 3600
}
///|
pub fn DurationValue::is_long_alarm_lead(self : DurationValue) -> Bool {
self.total_seconds_abs() > 86400
}
///|
pub fn DurationValue::human_label(self : DurationValue) -> String {
let b = StringBuilder::new()
if self.negative && !self.is_zero() {
b.write_string("before ")
} else if !self.is_zero() {
b.write_string("after ")
}
if self.weeks > 0 {
b.write_string(self.weeks.to_string())
b.write_string(" week")
if self.weeks != 1 {
b.write_string("s")
}
return b.to_string()
}
let mut wrote = false
wrote = write_label_part(b, self.days, "day", wrote)
wrote = write_label_part(b, self.hours, "hour", wrote)
wrote = write_label_part(b, self.minutes, "minute", wrote)
wrote = write_label_part(b, self.seconds, "second", wrote)
if !wrote {
b.write_string("0 seconds")
}
b.to_string()
}
///|
pub fn compare_duration(a : DurationValue, b : DurationValue) -> Int {
let left = a.signed_seconds()
let right = b.signed_seconds()
if left < right {
-1
} else if left > right {
1
} else {
0
}
}
///|
pub fn is_duration_text(input : String) -> Bool {
parse_duration(input) is Ok(_)
}
///|
pub fn trigger_is_relative(input : String) -> Bool {
is_duration_text(input)
}
///|
pub fn trigger_is_absolute(input : String) -> Bool {
let value = trim_ascii(input)
is_datetime(value) || is_date(value)
}
///|
pub fn trigger_kind(input : String) -> String {
if trigger_is_relative(input) {
"relative"
} else if trigger_is_absolute(input) {
"absolute"
} else {
"invalid"
}
}
///|
fn write_label_part(
b : StringBuilder,
amount : Int,
unit : String,
wrote : Bool,
) -> Bool {
if amount <= 0 {
return wrote
}
if wrote {
b.write_string(" ")
}
b.write_string(amount.to_string())
b.write_string(" ")
b.write_string(unit)
if amount != 1 {
b.write_string("s")
}
true
}
///|
fn parse_duration_int(raw : String) -> Int raise DurationError {
let value = trim_ascii(raw)
if value.length() == 0 {
raise BadDurationNumber(raw)
}
let mut total = 0
let mut i = 0
while i < value.length() {
let code = value[i].to_int()
if code < 48 || code > 57 {
raise BadDurationNumber(raw)
}
total = total * 10 + code - 48
i += 1
}
total
}