mooncal

    MoonBit 原生 iCalendar 解析器和 RRULE 日程展开库。

    icalendar
    ics
    rrule
    calendar
    moonbit
    Download zip
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    44

    No dependencies

    #MoonCal

    MoonCal 是一个 MoonBit 原生 iCalendar 工具库,用于解析 .ics 日历文本、校验日历数据、展开常见 RRULE 重复事件,并处理 VTODO 待办任务和 VFREEBUSY 空闲忙碌时间段。

    #项目状态

    黑客松版本:0.1.2。

    当前版本包含命令行示例、可运行基础示例和 82 个测试用例。本地统计约 4,189 行有效 MoonBit 代码,已经达到活动说明中 4,000 到 10,000 行参考规模的下限区间。代码规模来自事件解析、重复规则展开、任务处理、空闲忙碌时间段处理、验证诊断、导出和测试,不包含无意义占位代码。

    #功能范围

    • 支持 iCalendar 折行展开;
    • 支持 VCALENDAR、VEVENT、VTODO、VFREEBUSY 结构解析;
    • 支持属性参数,例如 DTSTART;VALUE=DATE 和 FREEBUSY;FBTYPE=BUSY-TENTATIVE;
    • 支持事件字段:UID、DTSTART、DTEND、DURATION、SUMMARY、DESCRIPTION、LOCATION、STATUS、URL、CATEGORIES、CREATED、LAST-MODIFIED、SEQUENCE;
    • 支持重复规则:FREQ、INTERVAL、COUNT、UNTIL、BYDAY、BYMONTHDAY、BYMONTH;
    • 支持 DAILY、WEEKLY、MONTHLY、YEARLY 四类常见重复频率;
    • 支持 RDATE 追加日期和 EXDATE 排除日期;
    • 支持待办任务字段:DUE、COMPLETED、STATUS、PRIORITY、PERCENT-COMPLETE、RELATED-TO、CATEGORIES;
    • 支持任务查询:打开任务、已完成任务、逾期任务、指定日期到期任务、高优先级任务;
    • 支持空闲忙碌时间段解析:BUSY、BUSY-TENTATIVE、BUSY-UNAVAILABLE、FREE;
    • 支持忙碌窗口查询、指定时刻忙碌判断、后续忙碌时间段查询;
    • 提供日历摘要、任务摘要、空闲忙碌摘要、JSON 输出和基础 ICS 导出能力。

    #非目标范围

    • 不做 CalDAV 网络同步;
    • 不内置完整时区数据库和 VTIMEZONE 解析;
    • 不实现会议邀请回复、参会人状态同步等完整协作流程;
    • 不覆盖 BYSETPOS、WKST、1MO 这类高级 RRULE 组合;
    • 不面向超大 .ics 文件的流式解析。

    #安装

    通过 Mooncakes 安装:

    moon add ciqingweiyang/mooncal

    本地开发和验收命令:

    moon check moon build moon test moon run cmd/main moon run cmd/main -- --json moon run examples/basic

    #最小示例

    match @mooncal.parse(@mooncal.sample_ics) {
    Ok(calendar) => {
    let from = @mooncal.DateTime(2026, 8, 1)
    let until = @mooncal.DateTime(2026, 8, 31)
    match @mooncal.occurrences_between(calendar, from, until, limit=32) {
    Ok(items) => println(@mooncal.occurrences_to_json(items))
    Err(err) => println(@mooncal.error_to_text(err))
    }
    let due = @mooncal.tasks_due_on_date(calendar, @mooncal.DateTime(2026, 8, 18))
    println(@mooncal.task_to_json(due[0]))
    }
    Err(err) => println(@mooncal.error_to_text(err))
    }

    #命令行示例

    moon run cmd/main moon run cmd/main -- --json

    命令行示例会分析内置日历样例,并输出文本摘要或 JSON 摘要。文件输入不属于 0.1.2 范围,以保持核心库在不同 MoonBit 目标上的可移植性。

    #接口概览

    接口用途
    parse(input)解析 VCALENDAR 文档
    parse_rule(input)解析单条 RRULE 值
    occurrences_between(calendar, from, until, limit=...)展开指定时间窗口内的事件
    occurrences_on_date(calendar, date)展开指定日期的事件
    next_occurrences(calendar, after)查询后续即将发生的事件
    open_tasks(calendar)查询未完成、未取消的任务
    completed_tasks(calendar)查询已完成任务
    overdue_tasks(calendar, as_of)按调用方给定日期查询逾期任务
    tasks_due_on_date(calendar, date)查询指定日期到期任务
    high_priority_tasks(calendar)查询高优先级任务
    busy_periods_between(calendar, from, until)查询时间窗口内的忙碌时间段
    free_periods_between(calendar, from, until)查询显式空闲时间段
    time_is_busy(calendar, moment)判断某个时刻是否忙碌
    analyze(input)解析日历并生成摘要
    validate_calendar(calendar)返回日历验证诊断
    calendar_to_json(calendar)输出解析后的日历 JSON
    calendar_to_ics(calendar)导出基础 VCALENDAR 文本
    summary_to_text(summary)输出适合命令行展示的文本摘要
    error_to_text(err)将类型化错误转换为可读文本

    #项目定位

    MoonCal 补足 MoonBit 生态中日历数据处理能力的空白。它适用于排期工具、提醒工具、静态站点生成器、测试数据处理器,以及需要稳定解析日历数据的自动化流程。

    项目选择用 MoonBit 原生实现,不依赖 JavaScript、C 或其他语言库封装,便于在 MoonBit 生态中直接复用、测试、发布和维护。

    #验收材料

    验收自查、撞车检查和本地验证记录见 docs/ACCEPTANCE_SELF_CHECK.md。

    #原创性与参考

    MoonCal 是原创 MoonBit 实现。项目参考公开 iCalendar 标准 RFC 5545 的格式和概念说明,但没有复制其他项目代码。测试样例由项目自行构造。

    #许可证

    Apache-2.0。

    Calendar

    pub(all) struct Calendar {
    version : String?
    prodid : String?
    events : Array[Event]
    tasks : Array[Task]
    freebusy : Array[FreeBusy]
    properties : Array[IcsProperty]
    } derive(
    Debug
    )

    A parsed VCALENDAR document.

    CalendarSummary

    pub(all) struct CalendarSummary {
    event_count : Int
    recurring_event_count : Int
    all_day_event_count : Int
    task_count : Int
    open_task_count : Int
    completed_task_count : Int
    blocked_time_count : Int
    blocked_period_count : Int
    earliest : DateTime?
    latest : DateTime?
    diagnostics : Array[Diagnostic]
    } derive(
    Debug
    )

    Human-readable summary for CLI output and README examples.

    DateTime

    pub(all) struct DateTime {
    year : Int
    month : Int
    day : Int
    hour : Int
    minute : Int
    second : Int
    is_date : Bool
    is_utc : Bool
    } derive(Eq,
    Debug
    )

    Calendar date or date-time. is_date=true represents all-day DATE values.
    impl Show for DateTime

    DateTime::DateTime

    fn DateTime::DateTime(year : Int, month : Int, day : Int, hour? : Int, minute? : Int, second? : Int, is_date? : Bool, is_utc? : Bool) -> DateTime

    DateTime::add_days

    fn DateTime::add_days(self : DateTime, days : Int) -> DateTime

    DateTime::add_months

    fn DateTime::add_months(self : DateTime, months : Int) -> DateTime

    DateTime::add_seconds

    fn DateTime::add_seconds(self : DateTime, seconds : Int) -> DateTime

    DateTime::add_years

    fn DateTime::add_years(self : DateTime, years : Int) -> DateTime

    DateTime::after

    fn DateTime::after(self : DateTime, other : DateTime) -> Bool

    DateTime::before

    fn DateTime::before(self : DateTime, other : DateTime) -> Bool

    DateTime::compare

    fn DateTime::compare(self : DateTime, other : DateTime) -> Int

    DateTime::date_key

    fn DateTime::date_key(self : DateTime) -> Int

    DateTime::days_until

    fn DateTime::days_until(self : DateTime, other : DateTime) -> Int

    DateTime::format

    fn DateTime::format(self : DateTime) -> String

    DateTime::is_valid

    fn DateTime::is_valid(self : DateTime) -> Bool

    DateTime::months_until

    fn DateTime::months_until(self : DateTime, other : DateTime) -> Int

    DateTime::same_day

    fn DateTime::same_day(self : DateTime, other : DateTime) -> Bool

    DateTime::seconds_until

    fn DateTime::seconds_until(self : DateTime, other : DateTime) -> Int

    DateTime::to_ics_value

    fn DateTime::to_ics_value(self : DateTime) -> String

    DateTime::weekday

    fn DateTime::weekday(self : DateTime) -> Weekday

    DateTime::years_until

    fn DateTime::years_until(self : DateTime, other : DateTime) -> Int

    Diagnostic

    pub(all) struct Diagnostic {
    code : String
    message : String
    severity : String
    } derive(Eq,
    Debug
    )

    A validation or analysis note.

    Duration

    pub(all) struct Duration {
    negative : Bool
    weeks : Int
    days : Int
    hours : Int
    minutes : Int
    seconds : Int
    } derive(Eq,
    Debug
    )

    RFC-style duration subset used by VEVENT DURATION.
    impl Show for Duration

    Duration::Duration

    fn Duration::Duration(negative? : Bool, weeks? : Int, days? : Int, hours? : Int, minutes? : Int, seconds? : Int) -> Duration

    Duration::apply

    fn Duration::apply(self : Duration, start : DateTime) -> DateTime

    Duration::format

    fn Duration::format(self : Duration) -> String

    Duration::is_zero

    fn Duration::is_zero(self : Duration) -> Bool

    Duration::total_seconds

    fn Duration::total_seconds(self : Duration) -> Int

    Event

    pub(all) struct Event {
    uid : String
    start : DateTime
    end : DateTime?
    summary : String
    description : String?
    location : String?
    status : String?
    url : String?
    categories : Array[String]
    created : DateTime?
    last_modified : DateTime?
    sequence : Int?
    rrule : RRule?
    duration : Duration?
    rdate : Array[DateTime]
    exdate : Array[DateTime]
    raw : Array[IcsProperty]
    } derive(
    Debug
    )

    A VEVENT record with the fields MoonCal v1 understands.

    FreeBusy

    pub(all) struct FreeBusy {
    uid : String?
    start : DateTime?
    end : DateTime?
    dtstamp : DateTime?
    organizer : String?
    attendees : Array[String]
    comments : Array[String]
    url : String?
    periods : Array[FreeBusyPeriod]
    raw : Array[IcsProperty]
    } derive(
    Debug
    )

    A VFREEBUSY record used to describe blocked or available time windows.

    FreeBusyPeriod

    pub(all) struct FreeBusyPeriod {
    start : DateTime
    end : DateTime
    busy_type : String
    } derive(Eq,
    Debug
    )

    One FREEBUSY period inside a VFREEBUSY component.

    FreeBusySummary

    pub(all) struct FreeBusySummary {
    component_count : Int
    period_count : Int
    busy_period_count : Int
    tentative_period_count : Int
    unavailable_period_count : Int
    free_period_count : Int
    earliest_start : DateTime?
    latest_end : DateTime?
    } derive(
    Debug
    )

    Availability-focused summary over parsed VFREEBUSY data.

    Frequency

    pub(all) enum Frequency {
    Daily
    Weekly
    Monthly
    Yearly
    } derive(Eq,
    Debug
    )

    Supported recurrence frequencies for v1.

    IcsProperty

    pub(all) struct IcsProperty {
    name : String
    params : Map[String, String]
    value : String
    line : Int
    } derive(
    Debug
    )

    A parsed iCalendar property after RFC-style line unfolding.

    MoonCalError

    pub(all) enum MoonCalError {
    InvalidLine(line~ : Int, message~ : String)
    InvalidProperty(line~ : Int, name~ : String, message~ : String)
    InvalidDateTime(value~ : String)
    InvalidRRule(value~ : String, message~ : String)
    InvalidCalendar(message~ : String)
    MissingEventField(field~ : String)
    MissingTaskField(field~ : String)
    UnsupportedFeature(feature~ : String)
    LimitExceeded(limit~ : Int)
    } derive(Eq,
    Debug
    )

    Errors are data, not panics. This makes CLI output and tests predictable.

    Occurrence

    pub(all) struct Occurrence {
    uid : String
    start : DateTime
    end : DateTime?
    summary : String
    } derive(Eq,
    Debug
    )

    A concrete occurrence of an event after expanding recurrence data.

    RRule

    pub(all) struct RRule {
    freq : Frequency
    interval : Int
    count : Int?
    until : DateTime?
    byday : Array[Weekday]
    bymonthday : Array[Int]
    bymonth : Array[Int]
    } derive(Eq,
    Debug
    )

    A v1 recurrence rule. Unsupported RRULE parts are rejected during parsing.

    RRule::RRule

    fn RRule::RRule(freq : Frequency, interval? : Int, count? : Int, until? : DateTime, byday? : Array[Weekday], bymonthday? : Array[Int], bymonth? : Array[Int]) -> RRule

    Task

    pub(all) struct Task {
    uid : String
    start : DateTime?
    due : DateTime?
    completed : DateTime?
    created : DateTime?
    last_modified : DateTime?
    summary : String
    description : String?
    status : String?
    priority : Int?
    percent_complete : Int?
    categories : Array[String]
    url : String?
    related_to : Array[String]
    raw : Array[IcsProperty]
    } derive(
    Debug
    )

    A VTODO record with common task-tracking fields.

    TaskSummary

    pub(all) struct TaskSummary {
    task_count : Int
    open_task_count : Int
    completed_task_count : Int
    in_process_task_count : Int
    cancelled_task_count : Int
    high_priority_task_count : Int
    due_task_count : Int
    earliest_due : DateTime?
    latest_due : DateTime?
    } derive(
    Debug
    )

    Task-focused summary that callers can compute independently from CLI output.

    Weekday

    pub(all) enum Weekday {
    MO
    TU
    WE
    TH
    FR
    SA
    SU
    } derive(Eq,
    Debug
    )

    Weekdays use the iCalendar two-letter names.

    analyze

    fn analyze(input : String) -> Result[CalendarSummary, MoonCalError]

    Parse a document and immediately return an analysis summary.

    busy_periods_between

    fn busy_periods_between(calendar : Calendar, from : DateTime, until : DateTime) -> Array[FreeBusyPeriod]

    calendar_to_ics

    fn calendar_to_ics(calendar : Calendar) -> String

    calendar_to_json

    fn calendar_to_json(calendar : Calendar) -> String

    cancelled_tasks

    fn cancelled_tasks(calendar : Calendar) -> Array[Task]

    completed_tasks

    fn completed_tasks(calendar : Calendar) -> Array[Task]

    days_in_month

    fn days_in_month(year : Int, month : Int) -> Int

    error_to_text

    fn error_to_text(err : MoonCalError) -> String

    escape_ics_text

    fn escape_ics_text(value : String) -> String

    escape_json

    fn escape_json(value : String) -> String

    event_effective_end

    fn event_effective_end(event : Event) -> DateTime?

    event_is_recurring

    fn event_is_recurring(event : Event) -> Bool

    event_overlaps

    fn event_overlaps(event : Event, from : DateTime, until : DateTime) -> Bool

    event_to_ics

    fn event_to_ics(event : Event) -> String

    event_to_json

    fn event_to_json(event : Event) -> String

    events_between

    fn events_between(calendar : Calendar, from : DateTime, until : DateTime) -> Array[Event]

    expand_calendar_between

    fn expand_calendar_between(calendar : Calendar, from : DateTime, until : DateTime, limit? : Int) -> Result[Array[Occurrence], MoonCalError]

    expand_event_between

    fn expand_event_between(event : Event, from : DateTime, until : DateTime, limit? : Int) -> Result[Array[Occurrence], MoonCalError]

    find_event

    fn find_event(calendar : Calendar, uid : String) -> Event?

    find_task

    fn find_task(calendar : Calendar, uid : String) -> Task?

    free_periods_between

    fn free_periods_between(calendar : Calendar, from : DateTime, until : DateTime) -> Array[FreeBusyPeriod]

    freebusy_is_busy_type

    fn freebusy_is_busy_type(period : FreeBusyPeriod) -> Bool

    freebusy_period_contains

    fn freebusy_period_contains(period : FreeBusyPeriod, moment : DateTime) -> Bool

    freebusy_period_overlaps

    fn freebusy_period_overlaps(period : FreeBusyPeriod, from : DateTime, until : DateTime) -> Bool

    freebusy_period_to_ics

    fn freebusy_period_to_ics(period : FreeBusyPeriod) -> String

    freebusy_period_to_json

    fn freebusy_period_to_json(period : FreeBusyPeriod) -> String

    freebusy_periods_between

    fn freebusy_periods_between(calendar : Calendar, from : DateTime, until : DateTime) -> Array[FreeBusyPeriod]

    freebusy_summary_to_json

    fn freebusy_summary_to_json(summary : FreeBusySummary) -> String

    freebusy_to_ics

    fn freebusy_to_ics(item : FreeBusy) -> String

    freebusy_to_json

    fn freebusy_to_json(item : FreeBusy) -> String

    frequency_to_string

    fn frequency_to_string(freq : Frequency) -> String

    high_priority_tasks

    fn high_priority_tasks(calendar : Calendar) -> Array[Task]

    is_leap_year

    fn is_leap_year(year : Int) -> Bool

    is_valid_date

    fn is_valid_date(year : Int, month : Int, day : Int) -> Bool

    next_busy_periods

    fn next_busy_periods(calendar : Calendar, after : DateTime, limit? : Int) -> Array[FreeBusyPeriod]

    next_occurrences

    fn next_occurrences(calendar : Calendar, after : DateTime, limit? : Int) -> Result[Array[Occurrence], MoonCalError]

    occurrences_between

    fn occurrences_between(calendar : Calendar, from : DateTime, until : DateTime, limit? : Int) -> Result[Array[Occurrence], MoonCalError]

    Expand every event in a calendar inside an inclusive time window.

    occurrences_on_date

    fn occurrences_on_date(calendar : Calendar, date : DateTime, limit? : Int) -> Result[Array[Occurrence], MoonCalError]

    occurrences_to_json

    fn occurrences_to_json(items : Array[Occurrence]) -> String

    open_tasks

    fn open_tasks(calendar : Calendar) -> Array[Task]

    overdue_tasks

    fn overdue_tasks(calendar : Calendar, as_of : DateTime) -> Array[Task]

    package_name

    let package_name : String

    MoonCal is a small MoonBit-native iCalendar toolkit.

    The v1 surface intentionally focuses on reusable library behavior: parsing unfolded ICS properties, extracting VEVENT/VTODO/VFREEBUSY records, expanding common RRULE patterns, validating calendar shape, and exporting summaries.

    parse

    fn parse(input : String) -> Result[Calendar, MoonCalError]

    Parse an iCalendar document, returning structured data or a typed error.

    parse_calendar

    fn parse_calendar(input : String) -> Result[Calendar, MoonCalError]

    parse_datetime

    fn parse_datetime(value : String, is_date_hint? : Bool) -> Result[DateTime, MoonCalError]

    parse_duration

    fn parse_duration(input : String) -> Result[Duration, MoonCalError]

    parse_event

    fn parse_event(props : Array[IcsProperty]) -> Result[Event, MoonCalError]

    parse_freebusy

    fn parse_freebusy(props : Array[IcsProperty]) -> Result[FreeBusy, MoonCalError]

    parse_frequency

    fn parse_frequency(value : String) -> Frequency?

    parse_properties

    fn parse_properties(input : String) -> Result[Array[IcsProperty], MoonCalError]

    parse_property_line

    fn parse_property_line(line : String, line_number : Int) -> Result[IcsProperty, MoonCalError]

    parse_rrule

    fn parse_rrule(input : String) -> Result[RRule, MoonCalError]

    parse_rule

    fn parse_rule(input : String) -> Result[RRule, MoonCalError]

    Parse a single recurrence rule value, for example FREQ=WEEKLY;COUNT=3;BYDAY=MO,WE.

    parse_task

    fn parse_task(props : Array[IcsProperty]) -> Result[Task, MoonCalError]

    parse_weekday

    fn parse_weekday(value : String) -> Weekday?

    rrule_to_ics

    fn rrule_to_ics(rule : RRule) -> String

    sample_ics

    let sample_ics : String

    A compact fixture used by the CLI, examples, and smoke tests.

    summarize

    fn summarize(calendar : Calendar) -> CalendarSummary

    summarize_freebusy

    fn summarize_freebusy(calendar : Calendar) -> FreeBusySummary

    summarize_tasks

    fn summarize_tasks(calendar : Calendar) -> TaskSummary

    summary_to_json

    fn summary_to_json(summary : CalendarSummary) -> String

    summary_to_text

    fn summary_to_text(summary : CalendarSummary) -> String

    task_due_between

    fn task_due_between(task : Task, from : DateTime, until : DateTime) -> Bool

    task_due_on_date

    fn task_due_on_date(task : Task, date : DateTime) -> Bool

    task_is_cancelled

    fn task_is_cancelled(task : Task) -> Bool

    task_is_completed

    fn task_is_completed(task : Task) -> Bool

    task_is_in_process

    fn task_is_in_process(task : Task) -> Bool

    task_is_open

    fn task_is_open(task : Task) -> Bool

    task_is_overdue

    fn task_is_overdue(task : Task, as_of : DateTime) -> Bool

    task_summary_to_json

    fn task_summary_to_json(summary : TaskSummary) -> String

    task_to_ics

    fn task_to_ics(task : Task) -> String

    task_to_json

    fn task_to_json(task : Task) -> String

    tasks_due_between

    fn tasks_due_between(calendar : Calendar, from : DateTime, until : DateTime) -> Array[Task]

    tasks_due_on_date

    fn tasks_due_on_date(calendar : Calendar, date : DateTime) -> Array[Task]

    time_is_busy

    fn time_is_busy(calendar : Calendar, moment : DateTime) -> Bool

    unescape_text

    fn unescape_text(value : String) -> String

    unfold_lines

    fn unfold_lines(input : String) -> Result[Array[(Int, String)], MoonCalError]

    validate_calendar

    fn validate_calendar(calendar : Calendar) -> Array[Diagnostic]

    validate_event

    fn validate_event(event : Event) -> Array[Diagnostic]

    validate_freebusy

    fn validate_freebusy(item : FreeBusy) -> Array[Diagnostic]

    validate_task

    fn validate_task(task : Task) -> Array[Diagnostic]

    weekday_to_string

    fn weekday_to_string(day : Weekday) -> String