dillonkearns/elm-ical/1.0.0

Ical.Generator

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 1616068800000

All generation types are opaque with builder functions. Invalid inputs like reversed start/end times, negative intervals, or incompatible recurrence parts are silently normalized.

Generating output

-> { events : List Event
, journals : List Journal
}
-> String

Generate a complete iCal calendar string with the given config, events, and journals.

Generator.generate
    (Generator.config { id = "//myapp//EN", domain = "example.com" })
    { events = [ weeklySync ]
    , journals = [ dailyNotes ]
    }
generateEvent : Config -> Event -> String

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.

Calendar configuration

type Config

An opaque type representing calendar configuration. Create one with config and customize with withName, withCalendarDescription, and withUrl.

{ id : String
, domain : String
}
-> Config

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" }
withName : String -> Config -> Config

Set the calendar display name.

Set the calendar description.

withUrl : String -> Config -> Config

Set the calendar URL.

Events

type Event

An opaque type representing a calendar event. Create one with event and customize it with the with* functions.

{ id : String
, stamp : Posix
, time : EventTime
, summary : String
}
-> Event

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.

allDay : Date -> EventTime

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.

{ start : Date
, end : Date
}

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.

{ start : Posix
, end : Posix
}

Create a timed event time span with UTC start and end times.

If end is before start, the times are swapped automatically.

{ start : FloatingDateTime
, end : FloatingDateTime
}

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.

type alias FloatingDateTime =
{ date : Date
, hour : Int
, minute : Int
, second : Int
}

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.

type alias Organizer =
{ name : String
, email : String
}

A person with a name and email address, used for the ORGANIZER property.

type alias Attendee =
{ name : String
, email : String
}

A person with a name and email address, used for the ATTENDEE property.

withDescription : String -> Event -> Event

Set a plain-text description for the event.

withLocation : String -> Event -> 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"
withOrganizer : Organizer -> Event -> Event

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.

withCreated : Posix -> Event -> Event

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.

withAttendee : Attendee -> Event -> Event

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)
            }
        )
type Alarm

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.

{ description : String
, trigger : AlarmTrigger
}
-> Alarm

Create a display alarm that shows a text notification.

Generator.displayAlarm
    { description = "Meeting in 15 minutes"
    , trigger = Generator.SecondsFromStart (-15 * 60)
    }
audioAlarm : { trigger : AlarmTrigger } -> Alarm

Create an audio alarm that plays a sound.

Generator.audioAlarm
    { trigger = Generator.SecondsFromStart (-15 * 60)
    }
= SecondsFromStart Int
| SecondsFromEnd Int

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 0

Journals

type Journal

An opaque type representing a journal entry. Create one with journal and customize with the withJournal* functions.

{ id : String
, stamp : Posix
, summary : String
}

Create a journal entry with the required fields.

Generator.journal
    { id = "journal-1"
    , stamp = timestamp
    , summary = "Daily standup notes"
    }
= JournalAllDay Date
| JournalWithTime Posix
| JournalFloatingTime FloatingDateTime

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).

withJournalFloatingTime : FloatingDateTime -> Journal -> Journal

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.

Recurrence rules

type Rule

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 10

Create 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 10
withCount : Int -> Rule -> Rule

Set a count limit. Values less than 1 are clamped to 1.

withUntilDate : Date -> Rule -> Rule

Set the recurrence end to a specific date.

Set the recurrence end to a specific date-time.

withByDay : List DaySpec -> Rule -> Rule

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.

withByMonthDay : List Int -> Rule -> Rule

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.

withByMonth : List Month -> Rule -> Rule

Set which months the rule applies to.

Generator.rule (Recurrence.Yearly { every = 1 })
    |> Generator.withByMonth [ Time.Jan, Time.Apr, Time.Jul, Time.Oct ]
withBySetPos : List Int -> Rule -> Rule

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.

withByHour : List Int -> Rule -> Rule

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.

withByMinute : List Int -> Rule -> Rule

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.

withBySecond : List Int -> Rule -> Rule

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.

withByYearDay : List Int -> Rule -> Rule

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.

withByWeekNo : List Int -> Rule -> Rule

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.