///|
pub fn find_event_by_uid(calendar : Calendar, uid : String) -> Component? {
for event in calendar.events() {
match event.get("UID") {
Some(value) => if value == uid { return Some(event) }
None => ()
}
}
None
}
///|
pub fn calendar_has_uid(calendar : Calendar, uid : String) -> Bool {
find_event_by_uid(calendar, uid) is Some(_)
}
///|
pub fn events_on_date(calendar : Calendar, date : String) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
if event_starts_on(event, date) {
out.push(event)
}
}
out
}
///|
pub fn events_between_dates(
calendar : Calendar,
start_date : String,
end_date : String,
) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
match event_start_date(event) {
Some(date) =>
if date >= start_date && date <= end_date {
out.push(event)
}
None => ()
}
}
out
}
///|
pub fn events_with_status(
calendar : Calendar,
status : String,
) -> Array[Component] {
let out : Array[Component] = []
let wanted = ascii_upper(trim_ascii(status))
for event in calendar.events() {
if event_status(event) == wanted {
out.push(event)
}
}
out
}
///|
pub fn events_with_alarms(calendar : Calendar) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
if event_has_alarms(event) {
out.push(event)
}
}
out
}
///|
pub fn recurring_events(calendar : Calendar) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
if event_is_recurring(event) {
out.push(event)
}
}
out
}
///|
pub fn all_day_events(calendar : Calendar) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
if event_is_all_day(event) {
out.push(event)
}
}
out
}
///|
pub fn timed_events(calendar : Calendar) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
if event_is_timed(event) {
out.push(event)
}
}
out
}
///|
pub fn cancelled_events(calendar : Calendar) -> Array[Component] {
events_with_status(calendar, "CANCELLED")
}
///|
pub fn confirmed_events(calendar : Calendar) -> Array[Component] {
events_with_status(calendar, "CONFIRMED")
}
///|
pub fn tentative_events(calendar : Calendar) -> Array[Component] {
events_with_status(calendar, "TENTATIVE")
}
///|
pub fn events_with_category(
calendar : Calendar,
category : String,
) -> Array[Component] {
let out : Array[Component] = []
let wanted = ascii_upper(trim_ascii(category))
for event in calendar.events() {
if event_has_category(event, wanted) {
out.push(event)
}
}
out
}
///|
pub fn event_has_category(event : Component, category : String) -> Bool {
let wanted = ascii_upper(trim_ascii(category))
for prop in event.properties_named("CATEGORIES") {
let values = split_by_char(prop.value, 44)
for value in values {
if ascii_upper(trim_ascii(value)) == wanted {
return true
}
}
}
false
}
///|
pub fn event_is_recurring(event : Component) -> Bool {
event.has("RRULE") || event.has("RDATE")
}
///|
pub fn event_is_all_day(event : Component) -> Bool {
match event.get("DTSTART") {
Some(value) => is_date(value)
None => false
}
}
///|
pub fn event_is_timed(event : Component) -> Bool {
match event.get("DTSTART") {
Some(value) => is_datetime(value)
None => false
}
}
///|
pub fn event_is_cancelled(event : Component) -> Bool {
event_status(event) == "CANCELLED"
}
///|
pub fn event_is_confirmed(event : Component) -> Bool {
event_status(event) == "CONFIRMED"
}
///|
pub fn event_is_tentative(event : Component) -> Bool {
event_status(event) == "TENTATIVE"
}
///|
pub fn event_status(event : Component) -> String {
ascii_upper(trim_ascii(event.get("STATUS").unwrap_or("")))
}
///|
pub fn event_summary_text(event : Component) -> String {
unescape_text(event.get("SUMMARY").unwrap_or(""))
}
///|
pub fn event_location_text(event : Component) -> String {
unescape_text(event.get("LOCATION").unwrap_or(""))
}
///|
pub fn event_start_value(event : Component) -> String {
event.get("DTSTART").unwrap_or("")
}
///|
pub fn event_end_value(event : Component) -> String {
event.get("DTEND").unwrap_or("")
}
///|
pub fn event_start_date(event : Component) -> String? {
let start = event_start_value(event)
if start.length() >= 8 {
Some(start[0:8].to_owned())
} else {
None
}
}
///|
pub fn event_starts_on(event : Component, date : String) -> Bool {
match event_start_date(event) {
Some(value) => value == date
None => false
}
}
///|
pub fn event_timezone(event : Component) -> String? {
match event.param_value("DTSTART", "TZID") {
Some(value) => Some(value)
None => event.param_value("DTEND", "TZID")
}
}
///|
pub fn event_duration_text(event : Component) -> String {
match event.get("DURATION") {
Some(value) => value
None =>
if event_start_value(event).length() > 0 &&
event_end_value(event).length() > 0 {
event_start_value(event) + ".." + event_end_value(event)
} else {
""
}
}
}
///|
pub fn event_primary_rule(event : Component) -> RecurrenceRule? {
match event.get("RRULE") {
Some(value) =>
match parse_recurrence_rule(value) {
Ok(rule) => Some(rule)
Err(_) => None
}
None => None
}
}
///|
pub fn event_recurrence_label(event : Component) -> String {
match event_primary_rule(event) {
Some(rule) => rule.shape_label()
None => if event.has("RDATE") { "RDATE" } else { "" }
}
}
///|
pub fn event_sort_key(event : Component) -> String {
let b = StringBuilder::new()
b.write_string(event_start_value(event))
b.write_string("|")
b.write_string(event.get("UID").unwrap_or(""))
b.write_string("|")
b.write_string(event_summary_text(event))
b.to_string()
}
///|
pub fn event_matches_text(event : Component, needle : String) -> Bool {
let query = ascii_upper(trim_ascii(needle))
if query.length() == 0 {
return true
}
string_contains_ascii_upper(event_summary_text(event), query) ||
string_contains_ascii_upper(event_location_text(event), query) ||
string_contains_ascii_upper(event.get("UID").unwrap_or(""), query) ||
string_contains_ascii_upper(event.get("DESCRIPTION").unwrap_or(""), query)
}
///|
pub fn search_events(calendar : Calendar, needle : String) -> Array[Component] {
let out : Array[Component] = []
for event in calendar.events() {
if event_matches_text(event, needle) {
out.push(event)
}
}
out
}
///|
pub fn duplicate_event_uids(calendar : Calendar) -> Array[String] {
let duplicates : Array[String] = []
let seen : Array[String] = []
for event in calendar.events() {
match event.get("UID") {
Some(uid) =>
if contains_query_string(seen, uid) {
if !contains_query_string(duplicates, uid) {
duplicates.push(uid)
}
} else {
seen.push(uid)
}
None => ()
}
}
duplicates
}
///|
pub fn calendar_has_duplicate_event_uids(calendar : Calendar) -> Bool {
duplicate_event_uids(calendar).length() > 0
}
///|
pub fn render_agenda(calendar : Calendar) -> String {
let b = StringBuilder::new()
b.write_string("# Agenda\n\n")
let events = calendar.events()
if events.length() == 0 {
b.write_string("No events.\n")
return b.to_string()
}
for event in events {
b.write_string("- ")
b.write_string(render_agenda_event(event))
b.write_string("\n")
}
b.to_string()
}
///|
pub fn render_agenda_for_date(calendar : Calendar, date : String) -> String {
let b = StringBuilder::new()
b.write_string("# Agenda ")
b.write_string(date)
b.write_string("\n\n")
let events = events_on_date(calendar, date)
if events.length() == 0 {
b.write_string("No events.\n")
return b.to_string()
}
for event in events {
b.write_string("- ")
b.write_string(render_agenda_event(event))
b.write_string("\n")
}
b.to_string()
}
///|
pub fn render_agenda_event(event : Component) -> String {
let b = StringBuilder::new()
let start = event_start_value(event)
if start.length() > 0 {
b.write_string(start)
b.write_string(" ")
}
let end = event_end_value(event)
if end.length() > 0 {
b.write_string("to ")
b.write_string(end)
b.write_string(" ")
}
let summary = event_summary_text(event)
if summary.length() > 0 {
b.write_string(summary)
} else {
b.write_string("(untitled)")
}
let location = event_location_text(event)
if location.length() > 0 {
b.write_string(" @ ")
b.write_string(location)
}
let status = event_status(event)
if status.length() > 0 {
b.write_string(" [")
b.write_string(status)
b.write_string("]")
}
if event_is_recurring(event) {
b.write_string(" ")
b.write_string(event_recurrence_label(event))
}
if event_has_alarms(event) {
b.write_string(" alarms=")
b.write_string(event_alarm_count(event).to_string())
}
b.to_string()
}
///|
pub fn calendar_status_count(calendar : Calendar, status : String) -> Int {
events_with_status(calendar, status).length()
}
///|
pub fn calendar_alarm_coverage_percent(calendar : Calendar) -> Int {
let events = calendar.events()
if events.length() == 0 {
return 0
}
events_with_alarms(calendar).length() * 100 / events.length()
}
///|
pub fn calendar_recurrence_coverage_percent(calendar : Calendar) -> Int {
let events = calendar.events()
if events.length() == 0 {
return 0
}
recurring_events(calendar).length() * 100 / events.length()
}
///|
fn string_contains_ascii_upper(value : String, query : String) -> Bool {
let haystack = ascii_upper(value)
if query.length() == 0 {
return true
}
if haystack.length() < query.length() {
return false
}
let mut i = 0
while i + query.length() <= haystack.length() {
if haystack[i:i + query.length()].to_owned() == query {
return true
}
i += 1
}
false
}
///|
fn contains_query_string(values : Array[String], target : String) -> Bool {
for value in values {
if value == target {
return true
}
}
false
}