Generate iCal (RFC 5545) calendar feeds from typed Elm values.
import Date
import Ical
import Ical.Generator as Generator
import Ical.Recurrence as Recurrence
import Time
teamCalendar : Time.Posix -> String
teamCalendar now =
let
weeklySync : Generator.Event
weeklySync =
Generator.event
{ id = "weekly-sync"
, stamp = now
, time =
Generator.timedEvent
{ start = mar18at10am
, end = mar18at11am
}
, summary = "Weekly Team Sync"
}
|> Generator.withRecurrenceRule
(Generator.rule (Recurrence.Weekly { every = 1, weekStart = Time.Mon }))
offsite : Generator.Event
offsite =
Generator.event
{ id = "offsite-q2"
, stamp = now
, time =
Generator.allDayRange
{ start = Date.fromCalendarDate 2021 Time.Jun 14
, end = Date.fromCalendarDate 2021 Time.Jun 16
}
, summary = "Q2 Team Offsite"
}
|> Generator.withLocation "Moscone Center, 747 Howard St, San Francisco, CA 94103"
in
Generator.generate
(Generator.config
{ id = "//mycompany//team//EN"
, domain = "mycompany.com"
}
|> Generator.withName "Engineering Team"
)
{ events = [ weeklySync, offsite ]
, journals = []
}
mar18at10am : Time.Posix
mar18at10am =
Time.millisToPosix 1616065200000
mar18at11am : Time.Posix
mar18at11am =
Time.millisToPosix 1616068800000All generation types are opaque with builder functions. Invalid inputs like reversed start/end times, negative intervals, or incompatible recurrence parts are silently normalized.
Generate the iCal string for a single VEVENT component, without the
surrounding VCALENDAR wrapper. Useful for embedding in a larger calendar
you are building up. For a complete iCal document, use generate.
An opaque type representing calendar configuration. Create one with
config and customize with withName,
withCalendarDescription, and withUrl.
Create a calendar configuration with the required fields.
id — identifies your product in the PRODID property
(e.g. "//mycompany//myapp//EN").domain — your domain, used to generate globally unique event UIDs
(e.g. "example.com" produces UIDs like "[email protected]").Generator.config { id = "//mycompany//myapp//EN", domain = "example.com" }Set the calendar description.
An opaque type representing a calendar event. Create one with event
and customize it with the with* functions.
Create an event with the required fields.
Generator.event
{ id = "unique-id-123"
, stamp = timestamp
, time = Generator.timedEvent { start = startTime, end = endTime }
, summary = "Team Meeting"
}Represents the time span of an event. Create values with allDay,
allDayRange, timedEvent, or
floatingTime.
Create a single-day all-day event.
Generator.allDay (Date.fromCalendarDate 2021 Time.Mar 18)The library automatically adds one day to produce the exclusive DTEND required by iCal.
Create a multi-day all-day event time span. The end date is inclusive.
The library automatically adds one day to produce the exclusive DTEND
required by iCal.
Generator.allDayRange
{ start = Date.fromCalendarDate 2021 Time.Jun 14
, end = Date.fromCalendarDate 2021 Time.Jun 16
}If end is before start, the dates are swapped automatically.
Create a timed event time span with UTC start and end times.
If end is before start, the times are swapped automatically.
Create a floating-time event — a local date-time with no timezone. The event represents the same wall-clock time regardless of the viewer's timezone (e.g. "meeting at 2pm, wherever you are").
Generator.floatingTime
{ start = { date = Date.fromCalendarDate 2021 Time.Mar 18, hour = 14, minute = 0, second = 0 }
, end = { date = Date.fromCalendarDate 2021 Time.Mar 18, hour = 15, minute = 30, second = 0 }
}If end is before start, the times are swapped automatically.
Out-of-range hour/minute/second values are clamped.
A local date-time with no timezone. Used for floating-time events that represent the same wall-clock time regardless of the viewer's timezone.
{ date = Date.fromCalendarDate 2021 Time.Mar 18
, hour = 14
, minute = 0
, second = 0
}Out-of-range values are clamped: hour to 0–23, minute to 0–59, second to 0–59.
A person with a name and email address, used for the ORGANIZER property.
A person with a name and email address, used for the ATTENDEE property.
Set a plain-text description for the event.
Set the event location (LOCATION property). Use a full, resolved address so calendar apps can map it directly:
Generator.event
{ id = "offsite-q2"
, stamp = now
, time = Generator.allDayRange { start = jun14, end = jun16 }
, summary = "Q2 Team Offsite"
}
|> Generator.withLocation "Moscone Center, 747 Howard St, San Francisco, CA 94103"Set the event organizer.
Set an HTML-formatted description. Calendar apps that support rich text will display this instead of the plain-text description.
Set the event status. Uses the shared Status type.
Set the event transparency. Uses the shared Transparency type.
Set the creation timestamp.
Set the last-modified timestamp.
Add a recurrence rule to the event.
Generator.event { ... }
|> Generator.withRecurrenceRule
(Generator.rule (Recurrence.Weekly { every = 1, weekStart = Time.Mon })
|> Generator.withCount 10
)Incompatible rule parts are dropped to match the event time shape. For
example, all-day events do not keep BYHOUR, BYMINUTE, or BYSECOND.
Add an attendee to the event.
Generator.event { ... }
|> Generator.withAttendee { name = "Jane Smith", email = "[email protected]" }Add an alarm to the event. Multiple alarms can be attached by calling this multiple times.
Generator.event { ... }
|> Generator.withAlarm
(Generator.displayAlarm
{ description = "Meeting in 15 minutes"
, trigger = Generator.SecondsFromStart (-15 * 60)
}
)An opaque alarm (VALARM). Create one with displayAlarm
or audioAlarm, then attach to an event with
withAlarm.
The RFC also defines EMAIL alarms, but in practice they are rarely supported by modern calendar clients (Google Calendar, Apple Calendar, Outlook all use push notifications instead). Only DISPLAY and AUDIO are provided here.
Create a display alarm that shows a text notification.
Generator.displayAlarm
{ description = "Meeting in 15 minutes"
, trigger = Generator.SecondsFromStart (-15 * 60)
}Create an audio alarm that plays a sound.
Generator.audioAlarm
{ trigger = Generator.SecondsFromStart (-15 * 60)
}When an alarm fires, as a signed offset in seconds. Negative values mean before, positive values mean after.
-- 15 minutes before the event starts
SecondsFromStart (-15 * 60)
-- 1 day before the event ends
SecondsFromEnd (-24 * 60 * 60)
-- at the moment the event starts
SecondsFromStart 0An opaque type representing a journal entry. Create one with
journal and customize with the withJournal* functions.
Create a journal entry with the required fields.
Generator.journal
{ id = "journal-1"
, stamp = timestamp
, summary = "Daily standup notes"
}The time of a journal entry. Journals have no time span, just an optional date or datetime stamp.
Set a plain-text description for the journal entry.
Set the journal date (all-day).
Set the journal time (UTC).
Set the journal time as a floating local datetime.
Set the journal status. Uses the shared JournalStatus type.
Set the journal creation timestamp.
Set the journal last-modified timestamp.
Set the journal organizer.
An opaque recurrence rule. Create one with rule and customize
with the with* builder functions.
Generator.rule (Recurrence.Weekly { every = 1, weekStart = Time.Mon })
|> Generator.withByDay [ Recurrence.every Time.Mon ]
|> Generator.withCount 10Create a recurrence rule with the given frequency. Negative every values
are clamped to 1.
Generator.rule (Recurrence.Weekly { every = 1, weekStart = Time.Mon })
|> Generator.withByDay [ Recurrence.every Time.Mon ]
|> Generator.withCount 10Set the recurrence end to a specific date.
Set the recurrence end to a specific date-time.
Set which days of the week the rule applies to.
Ordinal day specs are kept only for monthly rules and yearly rules without
BYWEEKNO. In other frequencies they are dropped.
Set which days of the month the rule applies to. Valid values are -31 to -1 and 1 to 31. Negative values count from the end of the month (e.g. -1 is the last day). Invalid values are dropped. This part is also dropped for weekly rules.
Set which months the rule applies to.
Generator.rule (Recurrence.Yearly { every = 1 })
|> Generator.withByMonth [ Time.Jan, Time.Apr, Time.Jul, Time.Oct ]Filter to specific positions within each recurrence period. For example,
with BYDAY=MO,TU,WE,TH,FR, a bySetPos of [ -1 ] means "the last weekday."
Invalid values are dropped. If no other BY* filters remain, BYSETPOS is
dropped too.
Set which hours the rule applies to. Valid values are 0–23. Invalid values are dropped. This part is dropped for all-day events when the rule is attached.
Set which minutes the rule applies to. Valid values are 0–59. Invalid values are dropped. This part is dropped for all-day events when the rule is attached.
Set which seconds the rule applies to. Valid values are 0–60 (60 for leap second). Invalid values are dropped. This part is dropped for all-day events when the rule is attached.
Set which days of the year the rule applies to. Valid values are 1 to 366 and -366 to -1. Negative values count from the end of the year. Invalid values are dropped. This part is dropped for daily, weekly, and monthly rules.
Set which ISO week numbers the rule applies to. Valid values are 1 to 53 and -53 to -1. Negative values count from the end of the year. Invalid values are dropped. This part is dropped unless the rule frequency is yearly.
Generate a complete iCal calendar string with the given config, events, and journals.