///|
/// A simple DateTime type for cron calculations.
/// The five-argument constructor defaults seconds to zero.
pub struct DateTime {
year : Int
month : Int // 1..12
day : Int // 1..31
hour : Int // 0..23
minute : Int // 0..59
second : Int // 0..59
}
///|
/// Create a new DateTime value.
pub fn DateTime::new(
year : Int,
month : Int,
day : Int,
hour : Int,
minute : Int,
) -> DateTime {
{ year, month, day, hour, minute, second: 0 }
}
///|
/// Create a DateTime value with explicit second precision.
pub fn DateTime::new_with_second(
year : Int,
month : Int,
day : Int,
hour : Int,
minute : Int,
second : Int,
) -> DateTime {
{ year, month, day, hour, minute, second }
}
///|
/// Compare two DateTime values for equality.
pub fn DateTime::eq(self : DateTime, other : DateTime) -> Bool {
self.year == other.year &&
self.month == other.month &&
self.day == other.day &&
self.hour == other.hour &&
self.minute == other.minute &&
self.second == other.second
}
///|
/// Check if self is before other.
pub fn DateTime::lt(self : DateTime, other : DateTime) -> Bool {
if self.year != other.year {
return self.year < other.year
}
if self.month != other.month {
return self.month < other.month
}
if self.day != other.day {
return self.day < other.day
}
if self.hour != other.hour {
return self.hour < other.hour
}
if self.minute != other.minute {
return self.minute < other.minute
}
self.second < other.second
}
///|
/// Check if self is after other.
pub fn DateTime::gt(self : DateTime, other : DateTime) -> Bool {
other.lt(self)
}
///|
/// Check if self is before or equal to other.
pub fn DateTime::le(self : DateTime, other : DateTime) -> Bool {
self.eq(other) || self.lt(other)
}
///|
/// Check if self is after or equal to other.
pub fn DateTime::ge(self : DateTime, other : DateTime) -> Bool {
self.eq(other) || self.gt(other)
}
///|
/// Format DateTime as "YYYY-MM-DD HH:MM", adding ":SS" when seconds are non-zero.
pub fn DateTime::to_string(self : DateTime) -> String {
let year_str = self.year.to_string()
let month_str = pad2(self.month)
let day_str = pad2(self.day)
let hour_str = pad2(self.hour)
let minute_str = pad2(self.minute)
let base = year_str +
"-" +
month_str +
"-" +
day_str +
" " +
hour_str +
":" +
minute_str
if self.second == 0 {
base
} else {
base + ":" + pad2(self.second)
}
}
///|
/// Format DateTime with an explicit seconds component.
pub fn DateTime::to_string_with_seconds(self : DateTime) -> String {
if self.second == 0 {
self.to_string() + ":00"
} else {
self.to_string()
}
}
///|
/// Pad integer to 2-digit string.
fn pad2(n : Int) -> String {
if n < 10 {
"0" + n.to_string()
} else {
n.to_string()
}
}
///|
/// Parse a DateTime from "YYYY-MM-DD HH:MM" or "YYYY-MM-DD HH:MM:SS".
pub fn parse_datetime(s : String) -> DateTime? {
if s.length() != 16 && s.length() != 19 {
return None
}
if s[4].to_int() != 45 ||
s[7].to_int() != 45 ||
s[10].to_int() != 32 ||
s[13].to_int() != 58 {
return None
}
if s.length() == 19 && s[16].to_int() != 58 {
return None
}
let year_str = s[0:4].to_owned()
let month_str = s[5:7].to_owned()
let day_str = s[8:10].to_owned()
let hour_str = s[11:13].to_owned()
let minute_str = s[14:16].to_owned()
match parse_int(year_str) {
Some(year) =>
match parse_int(month_str) {
Some(month) =>
match parse_int(day_str) {
Some(day) =>
match parse_int(hour_str) {
Some(hour) =>
match parse_int(minute_str) {
Some(minute) => {
let second = if s.length() == 19 {
match parse_int(s[17:19].to_owned()) {
Some(value) => value
None => return None
}
} else {
0
}
let dt = { year, month, day, hour, minute, second }
if is_valid_datetime(dt) {
Some(dt)
} else {
None
}
}
None => None
}
None => None
}
None => None
}
None => None
}
None => None
}
}
///|
/// Parse a positive integer from a string, returning None on failure.
fn parse_int(s : String) -> Int? {
if s == "" {
return None
}
let mut value = 0
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if code < 48 || code > 57 {
return None
}
value = value * 10 + (code - 48)
}
Some(value)
}