// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// An amount of time with nanosecond precision.
struct Duration {
secs : Int64
nanos : Int
} derive(Eq, Compare, Debug)
///|
/// Creates a Duration from hours, minutes, seconds and nanoseconds.
pub fn Duration::of(
hours? : Int64 = 0L,
minutes? : Int64 = 0L,
seconds? : Int64 = 0L,
nanoseconds? : Int64 = 0L,
) -> Duration raise Error {
let hours_sec = checked_mul_int64(hours, seconds_per_hour)
let minutes_sec = checked_mul_int64(minutes, seconds_per_minute)
let mut secs = checked_add_int64(
checked_add_int64(hours_sec, minutes_sec),
seconds,
)
let nanos = (nanoseconds % nanoseconds_per_second).to_int()
secs += nanoseconds / nanoseconds_per_second
{ secs, nanos }
}
///|
fn create(secs : Int64, nanos : Int) -> Duration raise Error {
if nanos < min_nanosecond || nanos > max_nanosecond {
fail(invalid_duration_err)
}
{ secs, nanos }
}
///|
/// Returns a zero length duration.
pub fn Duration::zero() -> Duration {
{ secs: 0L, nanos: 0 }
}
///|
/// Parses a ISO 8601 format string like `PT[n]H[n]M[n].[n]S`.
#deprecated("Use `Duration::from_str` instead")
pub fn Duration::from_string(str : String) -> Duration raise Error {
Duration::parse_str(str)
}
///|
fn Duration::parse_str(str : StringView) -> Duration raise Error {
guard str is [.. "PT", ..] else { fail(invalid_duration_err) }
let mut hours = 0L
let mut minutes = 0L
let mut seconds = 0L
let mut nanoseconds = 0L
let buf = StringBuilder::new(size_hint=0)
for i = 2; i < str.length(); i = i + 1 {
match str.code_unit_at(i) {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '.' | '-' =>
buf.write_char(str.code_unit_at(i).unsafe_to_char())
'H' => {
hours = @string.from_str(buf.to_string())
buf.reset()
}
'M' => {
minutes = @string.from_str(buf.to_string())
buf.reset()
}
'S' => {
let s = buf.to_string()
if s.is_empty() {
fail(invalid_duration_err)
}
let splits = s.split(".")
let secs_str = match splits.next() {
Some(part) => part
None => fail(invalid_duration_err)
}
let nanos_str = splits.next()
match splits.next() {
Some(_) => fail(invalid_duration_err)
None => ()
}
if !secs_str.is_empty() {
seconds = @string.from_str(secs_str)
}
match nanos_str {
Some(nanos) if !nanos.is_empty() => {
let mut nanos_str = add_suffix_zero(nanos, 9)
if seconds < 0L {
nanos_str = "-" + nanos_str
}
nanoseconds = @string.from_str(nanos_str)
}
_ => ()
}
break
}
_ => fail(invalid_duration_err)
}
}
Duration::of(hours~, minutes~, seconds~, nanoseconds~)
}
///|
pub impl @string.FromStr for Duration with fn from_str(str) {
Duration::parse_str(str)
}
///|
/// Returns a string representation of this duration using ISO 8601 representation.
pub fn Duration::to_string(self : Duration) -> String {
if self.is_zero() {
return "PT0S"
}
let buf = StringBuilder::new(size_hint=0)
buf.write_string("PT")
let total_secs = self.secs
let hours = total_secs / seconds_per_hour
let minutes = total_secs % seconds_per_hour / seconds_per_minute
let seconds = total_secs % seconds_per_minute
let nanos_of_seconds = seconds * nanoseconds_per_second +
self.nanos.to_int64()
let secs = (nanos_of_seconds / nanoseconds_per_second).abs()
let nanos = (nanos_of_seconds % nanoseconds_per_second).abs()
if hours != 0L {
buf.write_string(hours.to_string())
buf.write_char('H')
}
if minutes != 0L {
buf.write_string(minutes.to_string())
buf.write_char('M')
}
if secs != 0L || nanos > 0L {
if nanos_of_seconds < 0L {
buf.write_char('-')
}
buf.write_string(secs.to_string())
if nanos > 0L {
buf.write_string(format_nanos(nanos).to_string())
}
buf.write_char('S')
}
buf.to_string()
}
///|
pub impl Show for Duration with fn output(self : Duration, logger : &Logger) -> Unit {
logger.write_string(self.to_string())
}
///|
/// Returns the number of seconds in this duration.
pub fn Duration::seconds(self : Duration) -> Int64 {
self.secs
}
///|
/// Returns the number of nanoseconds in this duration.
pub fn Duration::nanoseconds(self : Duration) -> Int {
self.nanos
}
///|
/// Checks if this duration is zero length.
pub fn Duration::is_zero(self : Duration) -> Bool {
self.secs == 0L && self.nanos == 0
}
///|
/// Checks if this duration is negative.
pub fn Duration::is_neg(self : Duration) -> Bool {
self.secs < 0L
}
///|
/// Adds specified hours to this duration, and returns a new duration.
pub fn Duration::add_hours(
self : Duration,
hours : Int64,
) -> Duration raise Error {
if hours == 0L {
return self
}
let secs = checked_add_int64(
self.secs,
checked_mul_int64(hours, seconds_per_hour),
)
{ ..self, secs, }
}
///|
/// Adds specified minutes to this duration, and returns a new duration.
pub fn Duration::add_minutes(
self : Duration,
minutes : Int64,
) -> Duration raise Error {
if minutes == 0L {
return self
}
let secs = checked_add_int64(
self.secs,
checked_mul_int64(minutes, seconds_per_minute),
)
{ ..self, secs, }
}
///|
/// Adds specified seconds to this duration, and returns a new duration.
pub fn Duration::add_seconds(
self : Duration,
seconds : Int64,
) -> Duration raise Error {
if seconds == 0L {
return self
}
let secs = checked_add_int64(self.secs, seconds)
{ ..self, secs, }
}
///|
/// Adds specified nanoseconds to this duration, and returns a new duration.
pub fn Duration::add_nanoseconds(
self : Duration,
nanoseconds : Int64,
) -> Duration raise Error {
if nanoseconds == 0L {
return self
}
let seconds = checked_add_int64(
self.secs,
nanoseconds / nanoseconds_per_second,
)
let nanoseconds = nanoseconds % nanoseconds_per_second
Duration::of(seconds~, nanoseconds~)
}
///|
/// Adds other duration to this duration, and returns a new duration.
pub fn Duration::add_duration(
self : Duration,
other : Duration,
) -> Duration raise Error {
if other.is_zero() {
return self
}
let mut nanos = self.nanos.to_int64() + other.nanos.to_int64()
let mut secs = checked_add_int64(self.secs, other.secs)
secs = checked_add_int64(secs, nanos / nanoseconds_per_second)
nanos = nanos % nanoseconds_per_second
if nanos < 0L {
secs -= 1L
nanos += nanoseconds_per_second
}
create(secs, nanos.to_int())
}
///|
pub fn Duration::op_add(
self : Duration,
other : Duration,
) -> Duration raise Error {
self.add_duration(other)
}
///|
/// Returns a new duration with the specified amount of seconds.
pub fn Duration::with_seconds(self : Duration, seconds : Int64) -> Duration {
if seconds == self.secs {
return self
}
{ secs: seconds, nanos: self.nanos }
}
///|
/// Returns a new duration with the specified nanosecond of second.
pub fn Duration::with_nanoseconds(
self : Duration,
nanoseconds : Int,
) -> Duration raise Error {
if nanoseconds == self.nanos {
return self
}
create(self.secs, nanoseconds)
}
///|
/// Converts this duration to the total length in nanoseconds.
pub fn Duration::to_nanoseconds(self : Duration) -> Int64 {
self.secs * nanoseconds_per_second + self.nanos.to_int64()
}