/ical

iCalendar (.ics) parser

Primary LanguageGoMIT LicenseMIT

iCalendar (.ics) parser

Test

This package parses iCalendars specified by RFC 5545.

Features

  • Concurrent lexing & parsing
  • Buffered lexer
  • allows CRLF & LF line breaks
  • Component validation

Implemented components

Install

go get github.com/bounoable/ical

Usage

View the docs for more examples.

package main

import (
  "fmt"
  "os"
  "github.com/bounoable/ical"
)

func main() {
  f, err := os.Open("/path/to/calendar.ics")
  if err != nil {
    panic(err)
  }
  defer f.Close()

  cal, err := ical.Parse(f)
  if err != nil {
    panic(err)
  }

  // use events
  for _, evt := range cal.Events {
    fmt.Println(evt)
  }
}

Context

You can attach a context.Context to both the lexer & parser via an option:

package main

import (
  "context"
  "os"
  "time"
  "github.com/bounoable/ical"
)

func main() {
  f, err := os.Open("/path/to/calendar.ics")
  if err != nil {
    panic(err)
  }
  defer f.Close()

  // cancel the lexing & parsing after 3 seconds
  ctx, cancel := context.WithTimeout(time.Second * 3)
  defer cancel()

  cal, err := ical.Parse(f, ical.Context(ctx))
  if err != nil {
    panic(err)
  }
}

Strict line breaks

By default, the lexer allows the input to use LF linebreaks instead of CRLF, because many iCalendars out there don't fully adhere to the spec. You can however enforce the use of CRLF line breaks via an option:

package main

import (
  "os"
  "github.com/bounoable/ical"
  "github.com/bounoable/ical/lex"
)

func main() {
  f, err := os.Open("/path/to/calendar.ics")
  if err != nil {
    panic(err)
  }
  defer f.Close()

  cal, err := ical.Parse(f, ical.LexWith(
    lex.StrictLineBreaks, // lexer option
  ))

  if err != nil {
    panic(err)
  }
}

Timezones

You can explicitly set the *time.Location that is used to parse DATE & DATE-TIME values that would otherwise be parsed in local time. This option overrides TZID parameters in the iCalendar.

package main

import (
  "os"
  "time"
  "github.com/bounoable/ical"
  "github.com/bounoable/ical/parse"
)

func main() {
  f, err := os.Open("/path/to/calendar.ics")
  if err != nil {
    panic(err)
  }
  defer f.Close()

  loc, err := time.LoadLocation("Europe/Berlin")
  if err != nil {
    panic(err)
  }

  cal, err := ical.Parse(f, ical.ParseWith(
    parse.Location(loc),
  ))

  if err != nil {
    panic(err)
  }
}

Lexing & parsing

Both the lexer & parser are being exposed as separate packages, so you could do the following:

package main

import (
  "fmt"
  "os"
  "github.com/bounoable/ical/lex"
  "github.com/bounoable/ical/parse"
)

func main() {
  f, err := os.Open("/path/to/calendar.ics")
  if err != nil {
    panic(err)
  }
  defer f.Close()

  items := lex.Reader(f) // items is a channel of lexer items/tokens

  cal, err := parse.Items(items) // pass the items channel to the parser
  if err != nil {
    panic(err)
  }

  for _, evt := range cal.Events {
    fmt.Println(evt)
  }
}