///|
fn parse_rule_int(text : String, field : String) -> Int raise RRuleParseError {
@string.parse_int(text, base=10) catch {
_ => raise InvalidInteger(field, text)
}
}
///|
fn parse_int_list(
text : String,
field : String,
) -> Array[Int] raise RRuleParseError {
if text.length() == 0 {
raise InvalidValue(field, "list cannot be empty")
}
let result : Array[Int] = []
for item in text.split(",") {
if item.length() == 0 {
raise InvalidValue(field, "list contains an empty item")
}
result.push(parse_rule_int(item.to_owned(), field))
}
result
}
///|
fn parse_byday_item(text : String) -> ByDay raise RRuleParseError {
if text.length() < 2 {
raise InvalidWeekday(text)
}
let weekday_text = text[text.length() - 2:].to_owned()
let weekday = match weekday_from_code(weekday_text) {
Some(value) => value
None => raise InvalidWeekday(text)
}
if text.length() == 2 {
return ByDay::new(weekday)
}
let ordinal_text = text[:text.length() - 2].to_owned()
ByDay::new(weekday, ordinal=parse_rule_int(ordinal_text, "BYDAY"))
}
///|
fn parse_byday_list(text : String) -> Array[ByDay] raise RRuleParseError {
if text.length() == 0 {
raise InvalidValue("BYDAY", "list cannot be empty")
}
let result : Array[ByDay] = []
for item in text.split(",") {
result.push(parse_byday_item(item.to_owned()))
}
result
}
///|
fn parse_until_value(text : String) -> DateTime raise RRuleParseError {
let date_text = if text.has_suffix("Z") {
text[:text.length() - 1].to_owned()
} else {
text
}
parse_datetime(date_text) catch {
_ => raise InvalidUntil(text)
}
}
///|
fn has_name(names : Array[String], name : String) -> Bool {
for candidate in names {
if candidate == name {
return true
}
}
false
}
///|
/// Parse an RFC 5545 recurrence rule. The optional RRULE: prefix is accepted.
pub fn parse_rrule(source : String) -> RRule raise RRuleParseError {
let trimmed = source.trim().to_owned()
if trimmed.length() == 0 {
raise EmptyRule
}
let body = if trimmed.to_upper().has_prefix("RRULE:") {
trimmed[6:].to_owned()
} else {
trimmed
}
let names : Array[String] = []
let mut frequency : Frequency? = None
let mut interval = 1
let mut count : Int? = None
let mut until : DateTime? = None
let mut week_start = Monday
let mut by_second : Array[Int] = []
let mut by_minute : Array[Int] = []
let mut by_hour : Array[Int] = []
let mut by_day : Array[ByDay] = []
let mut by_month_day : Array[Int] = []
let mut by_year_day : Array[Int] = []
let mut by_week_number : Array[Int] = []
let mut by_month : Array[Int] = []
let mut by_set_position : Array[Int] = []
for raw_part in body.split(";") {
let part = raw_part.to_owned()
let pair : Array[String] = part
.split("=")
.map(value => value.to_owned())
.collect()
if pair.length() != 2 || pair[0].length() == 0 || pair[1].length() == 0 {
raise InvalidPart(part)
}
let name = pair[0].to_upper()
let value = pair[1].to_upper()
if has_name(names, name) {
raise DuplicateField(name)
}
names.push(name)
match name {
"FREQ" =>
frequency = match frequency_from_code(value) {
Some(item) => Some(item)
None => raise UnknownFrequency(value)
}
"INTERVAL" => interval = parse_rule_int(value, name)
"COUNT" => count = Some(parse_rule_int(value, name))
"UNTIL" => until = Some(parse_until_value(value))
"WKST" =>
week_start = match weekday_from_code(value) {
Some(item) => item
None => raise InvalidWeekday(value)
}
"BYSECOND" => by_second = parse_int_list(value, name)
"BYMINUTE" => by_minute = parse_int_list(value, name)
"BYHOUR" => by_hour = parse_int_list(value, name)
"BYDAY" => by_day = parse_byday_list(value)
"BYMONTHDAY" => by_month_day = parse_int_list(value, name)
"BYYEARDAY" => by_year_day = parse_int_list(value, name)
"BYWEEKNO" => by_week_number = parse_int_list(value, name)
"BYMONTH" => by_month = parse_int_list(value, name)
"BYSETPOS" => by_set_position = parse_int_list(value, name)
_ => raise InvalidPart(part)
}
}
let required_frequency = match frequency {
Some(value) => value
None => raise MissingFrequency
}
{
frequency: required_frequency,
interval,
count,
until,
week_start,
by_second,
by_minute,
by_hour,
by_day,
by_month_day,
by_year_day,
by_week_number,
by_month,
by_set_position,
}
}
///|
fn frequency_en(frequency : Frequency) -> String {
match frequency {
Secondly => "second"
Minutely => "minute"
Hourly => "hour"
Daily => "day"
Weekly => "week"
Monthly => "month"
Yearly => "year"
}
}
///|
fn frequency_zh(frequency : Frequency) -> String {
match frequency {
Secondly => "秒"
Minutely => "分钟"
Hourly => "小时"
Daily => "天"
Weekly => "周"
Monthly => "月"
Yearly => "年"
}
}
///|
fn weekday_en(day : Weekday) -> String {
match day {
Monday => "Monday"
Tuesday => "Tuesday"
Wednesday => "Wednesday"
Thursday => "Thursday"
Friday => "Friday"
Saturday => "Saturday"
Sunday => "Sunday"
}
}
///|
fn weekday_zh(day : Weekday) -> String {
match day {
Monday => "周一"
Tuesday => "周二"
Wednesday => "周三"
Thursday => "周四"
Friday => "周五"
Saturday => "周六"
Sunday => "周日"
}
}
///|
pub fn RRule::explain_en(self : RRule) -> String {
let unit = frequency_en(self.frequency)
let mut text = if self.interval == 1 {
"Every " + unit
} else {
"Every " + self.interval.to_string() + " " + unit + "s"
}
if self.by_day.length() > 0 {
let days = self.by_day
.map(item => {
match item.ordinal {
Some(value) => value.to_string() + " " + weekday_en(item.weekday)
None => weekday_en(item.weekday)
}
})
.join(", ")
text = text + " on " + days
}
if self.by_month_day.length() > 0 {
text = text + " on month day " + join_ints(self.by_month_day)
}
if self.by_month.length() > 0 {
text = text + " in month " + join_ints(self.by_month)
}
match self.count {
Some(value) => text = text + ", for " + value.to_string() + " occurrences"
None => ()
}
match self.until {
Some(value) => text = text + ", until " + value.to_iso_string()
None => ()
}
text
}
///|
pub fn RRule::explain_zh(self : RRule) -> String {
let unit = frequency_zh(self.frequency)
let mut text = if self.interval == 1 {
"每" + unit
} else {
"每 " + self.interval.to_string() + " " + unit
}
if self.by_day.length() > 0 {
let days = self.by_day
.map(item => {
match item.ordinal {
Some(value) =>
"第 " + value.to_string() + " 个" + weekday_zh(item.weekday)
None => weekday_zh(item.weekday)
}
})
.join("、")
text = text + "的" + days
}
if self.by_month_day.length() > 0 {
text = text + ",日期为 " + join_ints(self.by_month_day)
}
if self.by_month.length() > 0 {
text = text + ",月份为 " + join_ints(self.by_month)
}
match self.count {
Some(value) => text = text + ",共 " + value.to_string() + " 次"
None => ()
}
match self.until {
Some(value) => text = text + ",截止 " + value.to_iso_string()
None => ()
}
text
}