feat: implement caldav search

This commit is contained in:
2022-04-18 12:47:47 +02:00
parent f9e8f4b9c1
commit d7191461eb
57 changed files with 3893 additions and 20 deletions

View File

@ -0,0 +1,87 @@
package components
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/values"
"github.com/dolanor/caldav-go/utils"
"time"
)
type Calendar struct {
// specifies the identifier corresponding to the highest version number or the minimum and maximum
// range of the iCalendar specification that is required in order to interpret the iCalendar object.
Version string `ical:",2.0"`
// specifies the identifier for the product that created the iCalendar object
ProductId string `ical:"prodid,-//dolanor/caldav-go//NONSGML v1.0.0//EN"`
// specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
TimeZoneId string `ical:"tzid,omitempty"`
// defines the iCalendar object method associated with the calendar object.
Method values.Method `ical:",omitempty"`
// defines the calendar scale used for the calendar information specified in the iCalendar object.
CalScale values.CalScale `ical:",omitempty"`
// defines the different timezones used by the various components nested within
TimeZones []*TimeZone `ical:",omitempty"`
// unique events to be stored together in the icalendar file
Events []*Event `ical:",omitempty"`
}
func (c *Calendar) UseTimeZone(location *time.Location) *TimeZone {
tz := NewDynamicTimeZone(location)
c.TimeZones = append(c.TimeZones, tz)
c.TimeZoneId = tz.Id
return tz
}
func (c *Calendar) UsingTimeZone() bool {
return len(c.TimeZoneId) > 0
}
func (c *Calendar) UsingGlobalTimeZone() bool {
return c.UsingTimeZone() && c.TimeZoneId[0] == '/'
}
func (c *Calendar) ValidateICalValue() error {
for i, e := range c.Events {
if e == nil {
continue // skip nil events
}
if err := e.ValidateICalValue(); err != nil {
msg := fmt.Sprintf("event %d failed validation", i)
return utils.NewError(c.ValidateICalValue, msg, c, err)
}
if e.DateStart == nil && c.Method == "" {
msg := fmt.Sprintf("no value for method and no start date defined on event %d", i)
return utils.NewError(c.ValidateICalValue, msg, c, nil)
}
}
if c.UsingTimeZone() && !c.UsingGlobalTimeZone() {
for i, t := range c.TimeZones {
if t == nil || t.Id != c.TimeZoneId {
msg := fmt.Sprintf("timezone ID does not match timezone %d", i)
return utils.NewError(c.ValidateICalValue, msg, c, nil)
}
}
}
return nil
}
func NewCalendar(events ...*Event) *Calendar {
cal := new(Calendar)
cal.Events = events
return cal
}

View File

@ -0,0 +1,172 @@
package components
import (
"github.com/dolanor/caldav-go/icalendar/values"
"github.com/dolanor/caldav-go/utils"
"time"
)
type Event struct {
// defines the persistent, globally unique identifier for the calendar component.
UID string `ical:",required"`
// indicates the date/time that the instance of the iCalendar object was created.
DateStamp *values.DateTime `ical:"dtstamp,required"`
// specifies when the calendar component begins.
DateStart *values.DateTime `ical:"dtstart,required"`
// specifies the date and time that a calendar component ends.
DateEnd *values.DateTime `ical:"dtend,omitempty"`
// specifies a positive duration of time.
Duration *values.Duration `ical:",omitempty"`
// defines the access classification for a calendar component.
AccessClassification values.EventAccessClassification `ical:"class,omitempty"`
// specifies the date and time that the calendar information was created by the calendar user agent in the
// calendar store.
// Note: This is analogous to the creation date and time for a file in the file system.
Created *values.DateTime `ical:",omitempty"`
// provides a more complete description of the calendar component, than that provided by the Summary property.
Description string `ical:",omitempty"`
// specifies information related to the global position for the activity specified by a calendar component.
Geo *values.Geo `ical:",omitempty"`
// specifies the date and time that the information associated with the calendar component was last revised in the
// calendar store.
// Note: This is analogous to the modification date and time for a file in the file system.
LastModified *values.DateTime `ical:"last-modified,omitempty"`
// defines the intended venue for the activity defined by a calendar component.
Location *values.Location `ical:",omitempty"`
// defines the organizer for a calendar component.
Organizer *values.OrganizerContact `ical:",omitempty"`
// defines the relative priority for a calendar component.
Priority int `ical:",omitempty"`
// defines the revision sequence number of the calendar component within a sequence of revisions.
Sequence int `ical:",omitempty"`
// efines the overall status or confirmation for the calendar component.
Status values.EventStatus `ical:",omitempty"`
// defines a short summary or subject for the calendar component.
Summary string `ical:",omitempty"`
// defines whether an event is transparent or not to busy time searches.
values.TimeTransparency `ical:"transp,omitempty"`
// defines a Uniform Resource Locator (URL) associated with the iCalendar object.
Url *values.Url `ical:",omitempty"`
// used in conjunction with the "UID" and "SEQUENCE" property to identify a specific instance of a recurring
// event calendar component. The property value is the effective value of the DateStart property of the
// recurrence instance.
RecurrenceId *values.DateTime `ical:"recurrence_id,omitempty"`
// defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
RecurrenceRules []*values.RecurrenceRule `ical:",omitempty"`
// property provides the capability to associate a document object with a calendar component.
Attachment *values.Url `ical:"attach,omitempty"`
// defines an "Attendee" within a calendar component.
Attendees []*values.AttendeeContact `ical:",omitempty"`
// defines the categories for a calendar component.
Categories *values.CSV `ical:",omitempty"`
// specifies non-processing information intended to provide a comment to the calendar user.
Comments []values.Comment `ical:",omitempty"`
// used to represent contact information or alternately a reference to contact information associated with the calendar component.
ContactInfo *values.CSV `ical:"contact,omitempty"`
// defines the list of date/time exceptions for a recurring calendar component.
*values.ExceptionDateTimes `ical:",omitempty"`
// defines the list of date/times for a recurrence set.
*values.RecurrenceDateTimes `ical:",omitempty"`
// used to represent a relationship or reference between one calendar component and another.
RelatedTo *values.Url `ical:"related-to,omitempty"`
// defines the equipment or resources anticipated for an activity specified by a calendar entity.
Resources *values.CSV `ical:",omitempty"`
}
// validates the event internals
func (e *Event) ValidateICalValue() error {
if e.UID == "" {
return utils.NewError(e.ValidateICalValue, "the UID value must be set", e, nil)
}
if e.DateStart == nil {
return utils.NewError(e.ValidateICalValue, "event start date must be set", e, nil)
}
if e.DateEnd == nil && e.Duration == nil {
return utils.NewError(e.ValidateICalValue, "event end date or duration must be set", e, nil)
}
if e.DateEnd != nil && e.Duration != nil {
return utils.NewError(e.ValidateICalValue, "event end date and duration are mutually exclusive fields", e, nil)
}
return nil
}
// adds one or more recurrence rule to the event
func (e *Event) AddRecurrenceRules(r ...*values.RecurrenceRule) {
e.RecurrenceRules = append(e.RecurrenceRules, r...)
}
// adds one or more recurrence rule exception to the event
func (e *Event) AddRecurrenceExceptions(d ...*values.DateTime) {
if e.ExceptionDateTimes == nil {
e.ExceptionDateTimes = new(values.ExceptionDateTimes)
}
*e.ExceptionDateTimes = append(*e.ExceptionDateTimes, d...)
}
// checks to see if the event is a recurrence
func (e *Event) IsRecurrence() bool {
return e.RecurrenceId != nil
}
// checks to see if the event is a recurrence override
func (e *Event) IsOverride() bool {
return e.IsRecurrence() && !e.RecurrenceId.Equals(e.DateStart)
}
// creates a new iCalendar event with no end time
func NewEvent(uid string, start time.Time) *Event {
e := new(Event)
e.UID = uid
e.DateStamp = values.NewDateTime(time.Now().UTC())
e.DateStart = values.NewDateTime(start)
return e
}
// creates a new iCalendar event that lasts a certain duration
func NewEventWithDuration(uid string, start time.Time, duration time.Duration) *Event {
e := NewEvent(uid, start)
e.Duration = values.NewDuration(duration)
return e
}
// creates a new iCalendar event that has an explicit start and end time
func NewEventWithEnd(uid string, start time.Time, end time.Time) *Event {
e := NewEvent(uid, start)
e.DateEnd = values.NewDateTime(end)
return e
}

View File

@ -0,0 +1,40 @@
package components
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/values"
"net/url"
"time"
)
type TimeZone struct {
// defines the persistent, globally unique identifier for the calendar component.
Id string `ical:"tzid,required"`
// the location name, as defined by the standards body
ExtLocationName string `ical:"x-lic-location,omitempty"`
// defines a Uniform Resource Locator (URL) associated with the iCalendar object.
Url *values.Url `ical:"tzurl,omitempty"`
// specifies the date and time that the information associated with the calendar component was last revised in the
// calendar store.
// Note: This is analogous to the modification date and time for a file in the file system.
LastModified *values.DateTime `ical:"last-modified,omitempty"`
// TODO need to figure out how to handle standard and daylight savings time
}
func NewDynamicTimeZone(location *time.Location) *TimeZone {
t := new(TimeZone)
t.Id = location.String()
t.ExtLocationName = location.String()
t.Url = values.NewUrl(url.URL{
Scheme: "http",
Host: "tzurl.org",
Path: fmt.Sprintf("/zoneinfo/%s", t.Id),
})
return t
}

View File

@ -0,0 +1,196 @@
package icalendar
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"strings"
)
const (
Newline = "\r\n"
)
var _ = log.Print
type encoder func(reflect.Value) (string, error)
func tagAndJoinValue(v reflect.Value, in []string) (string, error) {
if tag, err := extractTagFromValue(v); err != nil {
return "", utils.NewError(tagAndJoinValue, "unable to extract tag from value", v, err)
} else {
var out []string
out = append(out, properties.MarshalProperty(properties.NewProperty("begin", tag)))
out = append(out, in...)
out = append(out, properties.MarshalProperty(properties.NewProperty("end", tag)))
return strings.Join(out, Newline), nil
}
}
func marshalCollection(v reflect.Value) (string, error) {
var out []string
for i, n := 0, v.Len(); i < n; i++ {
vi := v.Index(i).Interface()
if encoded, err := Marshal(vi); err != nil {
msg := fmt.Sprintf("unable to encode interface at index %d", i)
return "", utils.NewError(marshalCollection, msg, vi, err)
} else if encoded != "" {
out = append(out, encoded)
}
}
return strings.Join(out, Newline), nil
}
func marshalStruct(v reflect.Value) (string, error) {
var out []string
// iterate over all fields
vtype := v.Type()
n := vtype.NumField()
for i := 0; i < n; i++ {
// keep a reference to the field value and definition
fv := v.Field(i)
fs := vtype.Field(i)
// use the field definition to extract out property defaults
p := properties.PropertyFromStructField(fs)
if p == nil {
continue // skip explicitly ignored fields and private members
}
fi := fv.Interface()
// some fields are not properties, but actually nested objects.
// detect those early using the property and object encoder...
if _, ok := fi.(properties.CanEncodeValue); !ok && !isInvalidOrEmptyValue(fv) {
if encoded, err := encode(fv, objectEncoder); err != nil {
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
} else if encoded != "" {
// encoding worked! no need to process as a property
out = append(out, encoded)
continue
}
}
// now check to see if the field value overrides the defaults...
if !isInvalidOrEmptyValue(fv) {
// first, check the field value interface for overrides...
if overrides, err := properties.PropertyFromInterface(fi); err != nil {
msg := fmt.Sprintf("field %s failed validation", fs.Name)
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
} else if p.Merge(overrides); p.Value == "" {
// then, if we couldn't find an override from the interface,
// try the simple string encoder...
if p.Value, err = stringEncoder(fv); err != nil {
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
}
}
}
// make sure we have a value by this point
if !p.HasNameAndValue() {
if p.OmitEmpty {
continue
} else if p.DefaultValue != "" {
p.Value = p.DefaultValue
} else if p.Required {
msg := fmt.Sprintf("missing value for required field %s", fs.Name)
return "", utils.NewError(Marshal, msg, v.Interface(), nil)
}
}
// encode in the property
out = append(out, properties.MarshalProperty(p))
}
// wrap the fields in the enclosing struct tags
return tagAndJoinValue(v, out)
}
func objectEncoder(v reflect.Value) (string, error) {
// decompose the value into its interface parts
v = dereferencePointerValue(v)
// encode the value based off of its type
switch v.Kind() {
case reflect.Slice:
fallthrough
case reflect.Array:
return marshalCollection(v)
case reflect.Struct:
return marshalStruct(v)
}
return "", nil
}
func stringEncoder(v reflect.Value) (string, error) {
return fmt.Sprintf("%v", v.Interface()), nil
}
func propertyEncoder(v reflect.Value) (string, error) {
vi := v.Interface()
if p, err := properties.PropertyFromInterface(vi); err != nil {
// return early if interface fails its own validation
return "", err
} else if p.HasNameAndValue() {
// if an interface encodes its own name and value, it's a property
return properties.MarshalProperty(p), nil
}
return "", nil
}
func encode(v reflect.Value, encoders ...encoder) (string, error) {
for _, encode := range encoders {
if encoded, err := encode(v); err != nil {
return "", err
} else if encoded != "" {
return encoded, nil
}
}
return "", nil
}
// converts an iCalendar component into its string representation
func Marshal(target interface{}) (string, error) {
// don't do anything with invalid interfaces
v := reflect.ValueOf(target)
if isInvalidOrEmptyValue(v) {
return "", utils.NewError(Marshal, "unable to marshal empty or invalid values", target, nil)
}
if encoded, err := encode(v, propertyEncoder, objectEncoder, stringEncoder); err != nil {
return "", err
} else if encoded == "" {
return "", utils.NewError(Marshal, "unable to encode interface, all methods exhausted", v.Interface(), nil)
} else {
return encoded, nil
}
}

View File

@ -0,0 +1,29 @@
package properties
type CanValidateValue interface {
ValidateICalValue() error
}
type CanDecodeValue interface {
DecodeICalValue(string) error
}
type CanDecodeParams interface {
DecodeICalParams(Params) error
}
type CanEncodeTag interface {
EncodeICalTag() (string, error)
}
type CanEncodeValue interface {
EncodeICalValue() (string, error)
}
type CanEncodeName interface {
EncodeICalName() (PropertyName, error)
}
type CanEncodeParams interface {
EncodeICalParams() (Params, error)
}

View File

@ -0,0 +1,31 @@
package properties
import "strings"
type PropertyName string
const (
UIDPropertyName PropertyName = "UID"
CommentPropertyName = "COMMENT"
OrganizerPropertyName = "ORGANIZER"
AttendeePropertyName = "ATTENDEE"
ExceptionDateTimesPropertyName = "EXDATE"
RecurrenceDateTimesPropertyName = "RDATE"
RecurrenceRulePropertyName = "RRULE"
LocationPropertyName = "LOCATION"
)
type ParameterName string
const (
CanonicalNameParameterName ParameterName = "CN"
TimeZoneIdPropertyName = "TZID"
ValuePropertyName = "VALUE"
AlternateRepresentationName = "ALTREP"
)
type Params map[ParameterName]string
func (p PropertyName) Equals(test string) bool {
return strings.EqualFold(string(p), test)
}

View File

@ -0,0 +1,177 @@
package properties
import (
"fmt"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"strings"
)
var _ = log.Print
var propNameSanitizer = strings.NewReplacer(
"_", "-",
":", "\\:",
)
var propValueSanitizer = strings.NewReplacer(
"\"", "'",
"\\", "\\\\",
"\n", "\\n",
)
var propNameDesanitizer = strings.NewReplacer(
"-", "_",
"\\:", ":",
)
var propValueDesanitizer = strings.NewReplacer(
"'", "\"",
"\\\\", "\\",
"\\n", "\n",
)
type Property struct {
Name PropertyName
Value, DefaultValue string
Params Params
OmitEmpty, Required bool
}
func (p *Property) HasNameAndValue() bool {
return p.Name != "" && p.Value != ""
}
func (p *Property) Merge(override *Property) {
if override.Name != "" {
p.Name = override.Name
}
if override.Value != "" {
p.Value = override.Value
}
if override.Params != nil {
p.Params = override.Params
}
}
func PropertyFromStructField(fs reflect.StructField) (p *Property) {
ftag := fs.Tag.Get("ical")
if fs.PkgPath != "" || ftag == "-" {
return
}
p = new(Property)
// parse the field tag
if ftag != "" {
tags := strings.Split(ftag, ",")
p.Name = PropertyName(tags[0])
if len(tags) > 1 {
if tags[1] == "omitempty" {
p.OmitEmpty = true
} else if tags[1] == "required" {
p.Required = true
} else {
p.DefaultValue = tags[1]
}
}
}
// make sure we have a name
if p.Name == "" {
p.Name = PropertyName(fs.Name)
}
p.Name = PropertyName(strings.ToUpper(string(p.Name)))
return
}
func MarshalProperty(p *Property) string {
name := strings.ToUpper(propNameSanitizer.Replace(string(p.Name)))
value := propValueSanitizer.Replace(p.Value)
keys := []string{name}
for name, value := range p.Params {
name = ParameterName(strings.ToUpper(propNameSanitizer.Replace(string(name))))
value = propValueSanitizer.Replace(value)
if strings.ContainsAny(value, " :") {
keys = append(keys, fmt.Sprintf("%s=\"%s\"", name, value))
} else {
keys = append(keys, fmt.Sprintf("%s=%s", name, value))
}
}
name = strings.Join(keys, ";")
return fmt.Sprintf("%s:%s", name, value)
}
func PropertyFromInterface(target interface{}) (p *Property, err error) {
var ierr error
if va, ok := target.(CanValidateValue); ok {
if ierr = va.ValidateICalValue(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed validation", target, ierr)
return
}
}
p = new(Property)
if enc, ok := target.(CanEncodeName); ok {
if p.Name, ierr = enc.EncodeICalName(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed name encoding", target, ierr)
return
}
}
if enc, ok := target.(CanEncodeParams); ok {
if p.Params, ierr = enc.EncodeICalParams(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed params encoding", target, ierr)
return
}
}
if enc, ok := target.(CanEncodeValue); ok {
if p.Value, ierr = enc.EncodeICalValue(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed value encoding", target, ierr)
return
}
}
return
}
func UnmarshalProperty(line string) *Property {
nvp := strings.SplitN(line, ":", 2)
prop := new(Property)
if len(nvp) > 1 {
prop.Value = strings.TrimSpace(nvp[1])
}
npp := strings.Split(nvp[0], ";")
if len(npp) > 1 {
prop.Params = make(map[ParameterName]string, 0)
for i := 1; i < len(npp); i++ {
var key, value string
kvp := strings.Split(npp[i], "=")
key = strings.TrimSpace(kvp[0])
key = propNameDesanitizer.Replace(key)
if len(kvp) > 1 {
value = strings.TrimSpace(kvp[1])
value = propValueDesanitizer.Replace(value)
value = strings.Trim(value, "\"")
}
prop.Params[ParameterName(key)] = value
}
}
prop.Name = PropertyName(strings.TrimSpace(npp[0]))
prop.Name = PropertyName(propNameDesanitizer.Replace(string(prop.Name)))
prop.Value = propValueDesanitizer.Replace(prop.Value)
return prop
}
func NewProperty(name, value string) *Property {
return &Property{Name: PropertyName(name), Value: value}
}

View File

@ -0,0 +1,78 @@
package icalendar
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"strings"
)
var _ = log.Print
func isInvalidOrEmptyValue(v reflect.Value) bool {
if !v.IsValid() {
return true
}
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
func newValue(in reflect.Value) (out reflect.Value, isArrayElement bool) {
typ := in.Type()
kind := typ.Kind()
for {
if kind == reflect.Array || kind == reflect.Slice {
isArrayElement = true
} else if kind != reflect.Ptr {
break
}
typ = typ.Elem()
kind = typ.Kind()
}
out = reflect.New(typ)
return
}
func dereferencePointerValue(v reflect.Value) reflect.Value {
for (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.Elem().IsValid() {
v = v.Elem()
}
return v
}
func extractTagFromValue(v reflect.Value) (string, error) {
vdref := dereferencePointerValue(v)
vtemp, _ := newValue(vdref)
if encoder, ok := vtemp.Interface().(properties.CanEncodeTag); ok {
if tag, err := encoder.EncodeICalTag(); err != nil {
return "", utils.NewError(extractTagFromValue, "unable to extract tag from interface", v.Interface(), err)
} else {
return strings.ToUpper(tag), nil
}
} else {
typ := vtemp.Elem().Type()
return strings.ToUpper(fmt.Sprintf("v%s", typ.Name())), nil
}
}

View File

@ -0,0 +1,365 @@
package icalendar
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"regexp"
"strconv"
"strings"
)
var _ = log.Print
var splitter = regexp.MustCompile("\r?\n")
type token struct {
name string
components map[string][]*token
properties map[properties.PropertyName][]*properties.Property
}
func tokenize(encoded string) (*token, error) {
if encoded = strings.TrimSpace(encoded); encoded == "" {
return nil, utils.NewError(tokenize, "no content to tokenize", encoded, nil)
}
return tokenizeSlice(splitter.Split(encoded, -1))
}
func tokenizeSlice(slice []string, name ...string) (*token, error) {
tok := new(token)
size := len(slice)
if len(name) > 0 {
tok.name = name[0]
} else if size <= 0 {
return nil, utils.NewError(tokenizeSlice, "token has no content", slice, nil)
}
tok.properties = make(map[properties.PropertyName][]*properties.Property, 0)
tok.components = make(map[string][]*token, 0)
for i := 0; i < size; i++ {
// Handle iCalendar's space-indented line break format
// See: https://www.ietf.org/rfc/rfc2445.txt section 4.1
// "a long line can be split between any two characters by inserting a CRLF immediately followed by a single
// linear white space character"
line := slice[i]
for ; i < size-1 && strings.HasPrefix(slice[i+1], " "); i++ {
next := slice[i+1]
line += next[1:len(next)]
}
prop := properties.UnmarshalProperty(line)
if prop.Name.Equals("begin") {
for j := i; j < size; j++ {
end := strings.Replace(line, "BEGIN", "END", 1)
if slice[j] == end {
if component, err := tokenizeSlice(slice[i+1:j], prop.Value); err != nil {
msg := fmt.Sprintf("unable to tokenize %s component", prop.Value)
return nil, utils.NewError(tokenizeSlice, msg, slice, err)
} else {
existing, _ := tok.components[prop.Value]
tok.components[prop.Value] = append(existing, component)
i = j
break
}
}
}
} else if existing, ok := tok.properties[prop.Name]; ok {
tok.properties[prop.Name] = []*properties.Property{prop}
} else {
tok.properties[prop.Name] = append(existing, prop)
}
}
return tok, nil
}
func hydrateInterface(v reflect.Value, prop *properties.Property) (bool, error) {
// unable to decode into empty values
if isInvalidOrEmptyValue(v) {
return false, nil
}
var i = v.Interface()
var hasValue = false
// decode a value if possible
if decoder, ok := i.(properties.CanDecodeValue); ok {
if err := decoder.DecodeICalValue(prop.Value); err != nil {
return false, utils.NewError(hydrateInterface, "error decoding property value", v, err)
} else {
hasValue = true
}
}
// decode any params, if supported
if len(prop.Params) > 0 {
if decoder, ok := i.(properties.CanDecodeParams); ok {
if err := decoder.DecodeICalParams(prop.Params); err != nil {
return false, utils.NewError(hydrateInterface, "error decoding property parameters", v, err)
}
}
}
// finish with any validation
if validator, ok := i.(properties.CanValidateValue); ok {
if err := validator.ValidateICalValue(); err != nil {
return false, utils.NewError(hydrateInterface, "error validating property value", v, err)
}
}
return hasValue, nil
}
func hydrateLiteral(v reflect.Value, prop *properties.Property) (reflect.Value, error) {
literal := dereferencePointerValue(v)
switch literal.Kind() {
case reflect.Bool:
if i, err := strconv.ParseBool(prop.Value); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode bool "+prop.Value, literal.Interface(), err)
} else {
literal.SetBool(i)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if i, err := strconv.ParseInt(prop.Value, 10, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode int "+prop.Value, literal.Interface(), err)
} else {
literal.SetInt(i)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if i, err := strconv.ParseUint(prop.Value, 10, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode uint "+prop.Value, literal.Interface(), err)
} else {
literal.SetUint(i)
}
case reflect.Float32, reflect.Float64:
if i, err := strconv.ParseFloat(prop.Value, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode float "+prop.Value, literal.Interface(), err)
} else {
literal.SetFloat(i)
}
case reflect.String:
literal.SetString(prop.Value)
default:
return literal, utils.NewError(hydrateLiteral, "unable to decode value as literal "+prop.Value, literal.Interface(), nil)
}
return literal, nil
}
func hydrateProperty(v reflect.Value, prop *properties.Property) error {
// check to see if the interface handles it's own hydration
if handled, err := hydrateInterface(v, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate interface", v, err)
} else if handled {
return nil // exit early if handled by the interface
}
// if we got here, we need to create a new instance to
// set into the property.
var vnew, varr = newValue(v)
var vlit bool
// check to see if the new value handles it's own hydration
if handled, err := hydrateInterface(vnew, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new interface value", vnew, err)
} else if vlit = !handled; vlit {
// if not, treat it as a literal
if vnewlit, err := hydrateLiteral(vnew, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new literal value", vnew, err)
} else if _, err := hydrateInterface(vnewlit, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new literal interface value", vnewlit, err)
}
}
// now we can set the value
vnewval := dereferencePointerValue(vnew)
voldval := dereferencePointerValue(v)
// make sure we can set the new value into the provided pointer
if varr {
// for arrays, append the new value into the array structure
if !voldval.CanSet() {
return utils.NewError(hydrateProperty, "unable to set array value", v, nil)
} else {
voldval.Set(reflect.Append(voldval, vnewval))
}
} else if vlit {
// for literals, set the dereferenced value
if !voldval.CanSet() {
return utils.NewError(hydrateProperty, "unable to set literal value", v, nil)
} else {
voldval.Set(vnewval)
}
} else if !v.CanSet() {
return utils.NewError(hydrateProperty, "unable to set pointer value", v, nil)
} else {
// everything else should be a pointer, set it directly
v.Set(vnew)
}
return nil
}
func hydrateNestedComponent(v reflect.Value, component *token) error {
// create a new object to hold the property value
var vnew, varr = newValue(v)
if err := hydrateComponent(vnew, component); err != nil {
return utils.NewError(hydrateNestedComponent, "unable to decode component", component, err)
}
if varr {
// for arrays, append the new value into the array structure
voldval := dereferencePointerValue(v)
if !voldval.CanSet() {
return utils.NewError(hydrateNestedComponent, "unable to set array value", v, nil)
} else {
voldval.Set(reflect.Append(voldval, vnew))
}
} else if !v.CanSet() {
return utils.NewError(hydrateNestedComponent, "unable to set pointer value", v, nil)
} else {
// everything else should be a pointer, set it directly
v.Set(vnew)
}
return nil
}
func hydrateProperties(v reflect.Value, component *token) error {
vdref := dereferencePointerValue(v)
vtype := vdref.Type()
vkind := vdref.Kind()
if vkind != reflect.Struct {
return utils.NewError(hydrateProperties, "unable to hydrate properties of non-struct", v, nil)
}
n := vtype.NumField()
for i := 0; i < n; i++ {
prop := properties.PropertyFromStructField(vtype.Field(i))
if prop == nil {
continue // skip if field is ignored
}
vfield := vdref.Field(i)
// first try to hydrate property values
if properties, ok := component.properties[prop.Name]; ok {
for _, prop := range properties {
if err := hydrateProperty(vfield, prop); err != nil {
msg := fmt.Sprintf("unable to hydrate property %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
}
}
}
// then try to hydrate components
vtemp, _ := newValue(vfield)
if tag, err := extractTagFromValue(vtemp); err != nil {
msg := fmt.Sprintf("unable to extract tag from property %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
} else if components, ok := component.components[tag]; ok {
for _, comp := range components {
if err := hydrateNestedComponent(vfield, comp); err != nil {
msg := fmt.Sprintf("unable to hydrate component %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
}
}
}
}
return nil
}
func hydrateComponent(v reflect.Value, component *token) error {
if tag, err := extractTagFromValue(v); err != nil {
return utils.NewError(hydrateComponent, "error extracting tag from value", component, err)
} else if tag != component.name {
msg := fmt.Sprintf("expected %s and found %s", tag, component.name)
return utils.NewError(hydrateComponent, msg, component, nil)
} else if err := hydrateProperties(v, component); err != nil {
return utils.NewError(hydrateComponent, "unable to hydrate properties", component, err)
}
return nil
}
func hydrateComponents(v reflect.Value, components []*token) error {
vdref := dereferencePointerValue(v)
for i, component := range components {
velem := reflect.New(vdref.Type().Elem())
if err := hydrateComponent(velem, component); err != nil {
msg := fmt.Sprintf("unable to hydrate component %d", i)
return utils.NewError(hydrateComponent, msg, component, err)
} else {
v.Set(reflect.Append(vdref, velem))
}
}
return nil
}
func hydrateValue(v reflect.Value, component *token) error {
if !v.IsValid() || v.Kind() != reflect.Ptr {
return utils.NewError(hydrateValue, "unmarshal target must be a valid pointer", v, nil)
}
// handle any encodable properties
if encoder, isprop := v.Interface().(properties.CanEncodeName); isprop {
if name, err := encoder.EncodeICalName(); err != nil {
return utils.NewError(hydrateValue, "unable to lookup property name", v, err)
} else if properties, found := component.properties[name]; !found || len(properties) == 0 {
return utils.NewError(hydrateValue, "no matching propery values found for "+string(name), v, nil)
} else if len(properties) > 1 {
return utils.NewError(hydrateValue, "more than one property value matches single property interface", v, nil)
} else {
return hydrateProperty(v, properties[0])
}
}
// handle components
vkind := dereferencePointerValue(v).Kind()
if tag, err := extractTagFromValue(v); err != nil {
return utils.NewError(hydrateValue, "unable to extract component tag", v, err)
} else if components, found := component.components[tag]; !found || len(components) == 0 {
msg := fmt.Sprintf("unable to find matching component for %s", tag)
return utils.NewError(hydrateValue, msg, v, nil)
} else if vkind == reflect.Array || vkind == reflect.Slice {
return hydrateComponents(v, components)
} else if len(components) > 1 {
return utils.NewError(hydrateValue, "non-array interface provided but more than one component found!", v, nil)
} else {
return hydrateComponent(v, components[0])
}
}
// decodes encoded icalendar data into a native interface
func Unmarshal(encoded string, into interface{}) error {
if component, err := tokenize(encoded); err != nil {
return utils.NewError(Unmarshal, "unable to tokenize encoded data", encoded, err)
} else {
return hydrateValue(reflect.ValueOf(into), component)
}
}

View File

@ -0,0 +1,7 @@
package values
type CalScale string
const (
GregorianCalScale CalScale = "GREGORIAN"
)

View File

@ -0,0 +1,33 @@
package values
import (
"github.com/dolanor/caldav-go/icalendar/properties"
)
// specifies non-processing information intended to provide a comment to the calendar user.
type Comment string
// encodes the comment value for the iCalendar specification
func (c Comment) EncodeICalValue() (string, error) {
return string(c), nil
}
// decodes the comment value from the iCalendar specification
func (c Comment) DecodeICalValue(value string) error {
c = Comment(value)
return nil
}
// encodes the comment value for the iCalendar specification
func (c Comment) EncodeICalName() (properties.PropertyName, error) {
return properties.CommentPropertyName, nil
}
// creates a list of comments from strings
func NewComments(comments ...string) []Comment {
var _comments []Comment
for _, comment := range comments {
_comments = append(_comments, Comment(comment))
}
return _comments
}

View File

@ -0,0 +1,139 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"net/mail"
"strings"
)
var _ = log.Print
// Specifies the organizer of a group scheduled calendar entity. The property is specified within the "VFREEBUSY"
// calendar component to specify the calendar user requesting the free or busy time. When publishing a "VFREEBUSY"
// calendar component, the property is used to specify the calendar that the published busy time came from.
//
// The property has the property parameters CN, for specifying the common or display name associated with the
// "Organizer", DIR, for specifying a pointer to the directory information associated with the "Organizer",
// SENT-BY, for specifying another calendar user that is acting on behalf of the "Organizer". The non-standard
// parameters may also be specified on this property. If the LANGUAGE property parameter is specified, the identified
// language applies to the CN parameter value.
type Contact struct {
Entry mail.Address
}
type AttendeeContact Contact
type OrganizerContact Contact
// creates a new icalendar attendee representation
func NewAttendeeContact(name, email string) *AttendeeContact {
return &AttendeeContact{Entry: mail.Address{Name: name, Address: email}}
}
// creates a new icalendar organizer representation
func NewOrganizerContact(name, email string) *OrganizerContact {
return &OrganizerContact{Entry: mail.Address{Name: name, Address: email}}
}
// validates the contact value for the iCalendar specification
func (c *Contact) ValidateICalValue() error {
email := c.Entry.String()
if _, err := mail.ParseAddress(email); err != nil {
msg := fmt.Sprintf("unable to validate address %s", email)
return utils.NewError(c.ValidateICalValue, msg, c, err)
} else {
return nil
}
}
// encodes the contact value for the iCalendar specification
func (c *Contact) EncodeICalValue() (string, error) {
return fmt.Sprintf("MAILTO:%s", c.Entry.Address), nil
}
// encodes the contact params for the iCalendar specification
func (c *Contact) EncodeICalParams() (params properties.Params, err error) {
if c.Entry.Name != "" {
params = properties.Params{properties.CanonicalNameParameterName: c.Entry.Name}
}
return
}
// decodes the contact value from the iCalendar specification
func (c *Contact) DecodeICalValue(value string) error {
parts := strings.SplitN(value, ":", 2)
if len(parts) > 1 {
c.Entry.Address = parts[1]
}
return nil
}
// decodes the contact params from the iCalendar specification
func (c *Contact) DecodeICalParams(params properties.Params) error {
if name, found := params[properties.CanonicalNameParameterName]; found {
c.Entry.Name = name
}
return nil
}
// validates the contact value for the iCalendar specification
func (c *OrganizerContact) ValidateICalValue() error {
return (*Contact)(c).ValidateICalValue()
}
// encodes the contact value for the iCalendar specification
func (c *OrganizerContact) EncodeICalValue() (string, error) {
return (*Contact)(c).EncodeICalValue()
}
// encodes the contact params for the iCalendar specification
func (c *OrganizerContact) EncodeICalParams() (params properties.Params, err error) {
return (*Contact)(c).EncodeICalParams()
}
// decodes the contact value from the iCalendar specification
func (c *OrganizerContact) DecodeICalValue(value string) error {
return (*Contact)(c).DecodeICalValue(value)
}
// decodes the contact params from the iCalendar specification
func (c *OrganizerContact) DecodeICalParams(params properties.Params) error {
return (*Contact)(c).DecodeICalParams(params)
}
// encodes the contact property name for the iCalendar specification
func (o *OrganizerContact) EncodeICalName() (properties.PropertyName, error) {
return properties.OrganizerPropertyName, nil
}
// validates the contact value for the iCalendar specification
func (c *AttendeeContact) ValidateICalValue() error {
return (*Contact)(c).ValidateICalValue()
}
// encodes the contact value for the iCalendar specification
func (c *AttendeeContact) EncodeICalValue() (string, error) {
return (*Contact)(c).EncodeICalValue()
}
// encodes the contact params for the iCalendar specification
func (c *AttendeeContact) EncodeICalParams() (params properties.Params, err error) {
return (*Contact)(c).EncodeICalParams()
}
// decodes the contact value from the iCalendar specification
func (c *AttendeeContact) DecodeICalValue(value string) error {
return (*Contact)(c).DecodeICalValue(value)
}
// decodes the contact params from the iCalendar specification
func (c *AttendeeContact) DecodeICalParams(params properties.Params) error {
return (*Contact)(c).DecodeICalParams(params)
}
// encodes the contact property name for the iCalendar specification
func (o *AttendeeContact) EncodeICalName() (properties.PropertyName, error) {
return properties.AttendeePropertyName, nil
}

View File

@ -0,0 +1,24 @@
package values
import (
"log"
"strings"
)
var _ = log.Print
type CSV []string
func (csv *CSV) EncodeICalValue() (string, error) {
return strings.Join(*csv, ","), nil
}
func (csv *CSV) DecodeICalValue(value string) error {
value = strings.TrimSpace(value)
*csv = CSV(strings.Split(value, ","))
return nil
}
func NewCSV(items ...string) *CSV {
return (*CSV)(&items)
}

View File

@ -0,0 +1,265 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"strings"
"time"
)
var _ = log.Print
const DateFormatString = "20060102"
const DateTimeFormatString = "20060102T150405"
const UTCDateTimeFormatString = "20060102T150405Z"
// a representation of a date and time for iCalendar
type DateTime struct {
t time.Time
}
type DateTimes []*DateTime
// The exception dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence set
// is generated by gathering all of the start date-times generated by any of the specified "RRULE" and "RDATE"
// properties, and then excluding any start date and times which fall within the union of start date and times
// generated by any specified "EXRULE" and "EXDATE" properties. This implies that start date and times within exclusion
// related properties (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
// one recurrence is considered. Duplicate instances are ignored.
//
// The "EXDATE" property can be used to exclude the value specified in "DTSTART". However, in such cases the original
// "DTSTART" date MUST still be maintained by the calendaring and scheduling system because the original "DTSTART"
// value has inherent usage dependencies by other properties such as the "RECURRENCE-ID".
type ExceptionDateTimes DateTimes
// The recurrence dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence set
// is generated by gathering all of the start date-times generated by any of the specified "RRULE" and "RDATE"
// properties, and then excluding any start date and times which fall within the union of start date and times
// generated by any specified "EXRULE" and "EXDATE" properties. This implies that start date and times within exclusion
// related properties (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
// one recurrence is considered. Duplicate instances are ignored.
type RecurrenceDateTimes DateTimes
// creates a new icalendar datetime representation
func NewDateTime(t time.Time) *DateTime {
return &DateTime{t: t.Truncate(time.Second)}
}
// creates a new icalendar datetime array representation
func NewDateTimes(dates ...*DateTime) DateTimes {
return DateTimes(dates)
}
// creates a new icalendar datetime array representation
func NewExceptionDateTimes(dates ...*DateTime) *ExceptionDateTimes {
datetimes := NewDateTimes(dates...)
return (*ExceptionDateTimes)(&datetimes)
}
// creates a new icalendar datetime array representation
func NewRecurrenceDateTimes(dates ...*DateTime) *RecurrenceDateTimes {
datetimes := NewDateTimes(dates...)
return (*RecurrenceDateTimes)(&datetimes)
}
// checks to see if two datetimes are equal
func (d *DateTime) Equals(test *DateTime) bool {
return d.t.Equal(test.t)
}
// returns the native time for the datetime object
func (d *DateTime) NativeTime() time.Time {
return d.t
}
// encodes the datetime value for the iCalendar specification
func (d *DateTime) EncodeICalValue() (string, error) {
val := d.t.Format(DateTimeFormatString)
loc := d.t.Location()
if loc == time.UTC {
val = fmt.Sprintf("%sZ", val)
}
return val, nil
}
// decodes the datetime value from the iCalendar specification
func (d *DateTime) DecodeICalValue(value string) error {
layout := DateTimeFormatString
if strings.HasSuffix(value, "Z") {
layout = UTCDateTimeFormatString
} else if len(value) == 8 {
layout = DateFormatString
}
var err error
d.t, err = time.ParseInLocation(layout, value, time.UTC)
if err != nil {
return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err)
} else {
return nil
}
}
// encodes the datetime params for the iCalendar specification
func (d *DateTime) EncodeICalParams() (params properties.Params, err error) {
loc := d.t.Location()
if loc != time.UTC {
params = properties.Params{properties.TimeZoneIdPropertyName: loc.String()}
}
return
}
// decodes the datetime params from the iCalendar specification
func (d *DateTime) DecodeICalParams(params properties.Params) error {
layout := DateTimeFormatString
value := d.t.Format(layout)
if name, found := params[properties.TimeZoneIdPropertyName]; !found {
return nil
} else if loc, err := time.LoadLocation(name); err != nil {
return utils.NewError(d.DecodeICalValue, "unable to parse timezone", d, err)
} else if t, err := time.ParseInLocation(layout, value, loc); err != nil {
return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err)
} else {
d.t = t
return nil
}
}
// validates the datetime value against the iCalendar specification
func (d *DateTime) ValidateICalValue() error {
loc := d.t.Location()
if loc == time.Local {
msg := "DateTime location may not Local, please use UTC or explicit Location"
return utils.NewError(d.ValidateICalValue, msg, d, nil)
}
if loc.String() == "" {
msg := "DateTime location must have a valid name"
return utils.NewError(d.ValidateICalValue, msg, d, nil)
}
return nil
}
// encodes the datetime value for the iCalendar specification
func (d *DateTime) String() string {
if s, err := d.EncodeICalValue(); err != nil {
panic(err)
} else {
return s
}
}
// encodes a list of datetime values for the iCalendar specification
func (ds *DateTimes) EncodeICalValue() (string, error) {
var csv CSV
for i, d := range *ds {
if s, err := d.EncodeICalValue(); err != nil {
msg := fmt.Sprintf("unable to encode datetime at index %d", i)
return "", utils.NewError(ds.EncodeICalValue, msg, ds, err)
} else {
csv = append(csv, s)
}
}
return csv.EncodeICalValue()
}
// encodes a list of datetime params for the iCalendar specification
func (ds *DateTimes) EncodeICalParams() (params properties.Params, err error) {
if len(*ds) > 0 {
params, err = (*ds)[0].EncodeICalParams()
}
return
}
// decodes a list of datetime params from the iCalendar specification
func (ds *DateTimes) DecodeICalParams(params properties.Params) error {
for i, d := range *ds {
if err := d.DecodeICalParams(params); err != nil {
msg := fmt.Sprintf("unable to decode datetime params for index %d", i)
return utils.NewError(ds.DecodeICalValue, msg, ds, err)
}
}
return nil
}
// encodes a list of datetime values for the iCalendar specification
func (ds *DateTimes) DecodeICalValue(value string) error {
csv := new(CSV)
if err := csv.DecodeICalValue(value); err != nil {
return utils.NewError(ds.DecodeICalValue, "unable to decode datetime list as CSV", ds, err)
}
for i, value := range *csv {
d := new(DateTime)
if err := d.DecodeICalValue(value); err != nil {
msg := fmt.Sprintf("unable to decode datetime at index %d", i)
return utils.NewError(ds.DecodeICalValue, msg, ds, err)
} else {
*ds = append(*ds, d)
}
}
return nil
}
// encodes exception date times property name for icalendar
func (e *ExceptionDateTimes) EncodeICalName() (properties.PropertyName, error) {
return properties.ExceptionDateTimesPropertyName, nil
}
// encodes recurrence date times property name for icalendar
func (r *RecurrenceDateTimes) EncodeICalName() (properties.PropertyName, error) {
return properties.RecurrenceDateTimesPropertyName, nil
}
// encodes exception date times property value for icalendar
func (e *ExceptionDateTimes) EncodeICalValue() (string, error) {
return (*DateTimes)(e).EncodeICalValue()
}
// encodes recurrence date times property value for icalendar
func (r *RecurrenceDateTimes) EncodeICalValue() (string, error) {
return (*DateTimes)(r).EncodeICalValue()
}
// decodes exception date times property value for icalendar
func (e *ExceptionDateTimes) DecodeICalValue(value string) error {
return (*DateTimes)(e).DecodeICalValue(value)
}
// decodes recurrence date times property value for icalendar
func (r *RecurrenceDateTimes) DecodeICalValue(value string) error {
return (*DateTimes)(r).DecodeICalValue(value)
}
// encodes exception date times property params for icalendar
func (e *ExceptionDateTimes) EncodeICalParams() (params properties.Params, err error) {
return (*DateTimes)(e).EncodeICalParams()
}
// encodes recurrence date times property params for icalendar
func (r *RecurrenceDateTimes) EncodeICalParams() (params properties.Params, err error) {
return (*DateTimes)(r).EncodeICalParams()
}
// encodes exception date times property params for icalendar
func (e *ExceptionDateTimes) DecodeICalParams(params properties.Params) error {
return (*DateTimes)(e).DecodeICalParams(params)
}
// encodes recurrence date times property params for icalendar
func (r *RecurrenceDateTimes) DecodeICalParams(params properties.Params) error {
return (*DateTimes)(r).DecodeICalParams(params)
}

View File

@ -0,0 +1,132 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/utils"
"log"
"math"
"regexp"
"strconv"
"strings"
"time"
)
var _ = log.Print
// a representation of duration for iCalendar
type Duration struct {
d time.Duration
}
// breaks apart the duration into its component time parts
func (d *Duration) Decompose() (weeks, days, hours, minutes, seconds int64) {
// chip away at this
rem := time.Duration(math.Abs(float64(d.d)))
div := time.Hour * 24 * 7
weeks = int64(rem / div)
rem = rem % div
div = div / 7
days = int64(rem / div)
rem = rem % div
div = div / 24
hours = int64(rem / div)
rem = rem % div
div = div / 60
minutes = int64(rem / div)
rem = rem % div
div = div / 60
seconds = int64(rem / div)
return
}
// returns the native golang duration
func (d *Duration) NativeDuration() time.Duration {
return d.d
}
// returns true if the duration is negative
func (d *Duration) IsPast() bool {
return d.d < 0
}
// encodes the duration of time into iCalendar format
func (d *Duration) EncodeICalValue() (string, error) {
var parts []string
weeks, days, hours, minutes, seconds := d.Decompose()
if d.IsPast() {
parts = append(parts, "-")
}
parts = append(parts, "P")
if weeks > 0 {
parts = append(parts, fmt.Sprintf("%dW", weeks))
}
if days > 0 {
parts = append(parts, fmt.Sprintf("%dD", days))
}
if hours > 0 || minutes > 0 || seconds > 0 {
parts = append(parts, "T")
if hours > 0 {
parts = append(parts, fmt.Sprintf("%dH", hours))
}
if minutes > 0 {
parts = append(parts, fmt.Sprintf("%dM", minutes))
}
if seconds > 0 {
parts = append(parts, fmt.Sprintf("%dS", seconds))
}
}
return strings.Join(parts, ""), nil
}
var durationRegEx = regexp.MustCompile("(\\d+)(\\w)")
// decodes the duration of time from iCalendar format
func (d *Duration) DecodeICalValue(value string) error {
var seconds int64
var isPast = strings.HasPrefix(value, "-P")
var matches = durationRegEx.FindAllStringSubmatch(value, -1)
for _, match := range matches {
var multiplier int64
ivalue, err := strconv.ParseInt(match[1], 10, 64)
if err != nil {
return utils.NewError(d.DecodeICalValue, "unable to decode duration value "+match[1], d, nil)
}
switch match[2] {
case "S":
multiplier = 1
case "M":
multiplier = 60
case "H":
multiplier = 60 * 60
case "D":
multiplier = 60 * 60 * 24
case "W":
multiplier = 60 * 60 * 24 * 7
default:
return utils.NewError(d.DecodeICalValue, "unable to decode duration segment "+match[2], d, nil)
}
seconds = seconds + multiplier*ivalue
}
d.d = time.Duration(seconds) * time.Second
if isPast {
d.d = -d.d
}
return nil
}
func (d *Duration) String() string {
if s, err := d.EncodeICalValue(); err != nil {
panic(err)
} else {
return s
}
}
// creates a new iCalendar duration representation
func NewDuration(d time.Duration) *Duration {
return &Duration{d: d}
}

View File

@ -0,0 +1,17 @@
package values
// An access classification is only one component of the general security system within a calendar application.
// It provides a method of capturing the scope of the access the calendar owner intends for information within an
// individual calendar entry. The access classification of an individual iCalendar component is useful when measured
// along with the other security components of a calendar system (e.g., calendar user authentication, authorization,
// access rights, access role, etc.). Hence, the semantics of the individual access classifications cannot be completely
// defined by this memo alone. Additionally, due to the "blind" nature of most exchange processes using this memo, these
// access classifications cannot serve as an enforcement statement for a system receiving an iCalendar object. Rather,
// they provide a method for capturing the intention of the calendar owner for the access to the calendar component.
type EventAccessClassification string
const (
PublicEventAccessClassification EventAccessClassification = "PUBLIC"
PrivateEventAccessClassification = "PRIVATE"
ConfidentialEventAccessClassification = "CONFIDENTIAL"
)

View File

@ -0,0 +1,13 @@
package values
// In a group scheduled calendar component, the property is used by the "Organizer" to provide a confirmation of the
// event to the "Attendees".
// For example in an Event calendar component, the "Organizer" can indicate that a meeting is tentative, confirmed or
// cancelled.
type EventStatus string
const (
TentativeEventStatus EventStatus = "TENTATIVE" // Indicates event is tentative.
ConfirmedEventStatus = "CONFIRMED" // Indicates event is definite.
CancelledEventStatus = "CANCELLED" // Indicates event is cancelled.
)

View File

@ -0,0 +1,69 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/utils"
"log"
"strconv"
"strings"
)
var _ = log.Print
// a representation of a geographical point for iCalendar
type Geo struct {
coords []float64
}
// creates a new icalendar geo representation
func NewGeo(lat, lng float64) *Geo {
return &Geo{coords: []float64{lat, lng}}
}
// returns the latitude encoded into the geo point
func (g *Geo) Lat() float64 {
return g.coords[0]
}
// returns the longitude encoded into the geo point
func (g *Geo) Lng() float64 {
return g.coords[1]
}
// validates the geo value against the iCalendar specification
func (g *Geo) ValidateICalValue() error {
if len(g.coords) != 2 {
return utils.NewError(g.ValidateICalValue, "geo value must have length of 2", g, nil)
}
if g.Lat() < -90 || g.Lat() > 90 {
return utils.NewError(g.ValidateICalValue, "geo latitude must be between -90 and 90 degrees", g, nil)
}
if g.Lng() < -180 || g.Lng() > 180 {
return utils.NewError(g.ValidateICalValue, "geo longitude must be between -180 and 180 degrees", g, nil)
}
return nil
}
// encodes the geo value for the iCalendar specification
func (g *Geo) EncodeICalValue() (string, error) {
return fmt.Sprintf("%f %f", g.Lat(), g.Lng()), nil
}
// decodes the geo value from the iCalendar specification
func (g *Geo) DecodeICalValue(value string) error {
if latlng := strings.Split(value, " "); len(latlng) < 2 {
return utils.NewError(g.DecodeICalValue, "geo value must have both a latitude and longitude component", g, nil)
} else if lat, err := strconv.ParseFloat(latlng[0], 64); err != nil {
return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err)
} else if lng, err := strconv.ParseFloat(latlng[1], 64); err != nil {
return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err)
} else {
*g = Geo{coords: []float64{lat, lng}}
return nil
}
}

View File

@ -0,0 +1,78 @@
package values
import (
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"net/url"
)
var _ = log.Print
// Specific venues such as conference or meeting rooms may be explicitly specified using this property. An alternate
// representation may be specified that is a URI that points to directory information with more structured specification
// of the location. For example, the alternate representation may specify either an LDAP URI pointing to an LDAP server
// entry or a CID URI pointing to a MIME body part containing a vCard [RFC 2426] for the location.
type Location struct {
value string
altrep *url.URL
}
// creates a new icalendar location representation
func NewLocation(value string, altrep ...*url.URL) *Location {
loc := &Location{value: value}
if len(altrep) > 0 {
loc.altrep = altrep[0]
}
return loc
}
// returns an alternate representation for the location
// if one exists
func (l *Location) AltRep() *url.URL {
return l.altrep
}
// encodes the location for the iCalendar specification
func (l *Location) EncodeICalValue() (string, error) {
return l.value, nil
}
// decodes the location from the iCalendar specification
func (l *Location) DecodeICalValue(value string) error {
l.value = value
return nil
}
// encodes the location params for the iCalendar specification
func (l *Location) EncodeICalParams() (params properties.Params, err error) {
if l.altrep != nil {
params = properties.Params{properties.AlternateRepresentationName: l.altrep.String()}
}
return
}
// decodes the location params from the iCalendar specification
func (l *Location) DecodeICalParams(params properties.Params) error {
if rep, found := params[properties.AlternateRepresentationName]; !found {
return nil
} else if altrep, err := url.Parse(rep); err != nil {
return utils.NewError(l.DecodeICalValue, "unable to parse alternate representation", l, err)
} else {
l.altrep = altrep
return nil
}
}
// validates the location against the iCalendar specification
func (l *Location) ValidateICalValue() error {
if l.altrep != nil {
if _, err := url.Parse(l.altrep.String()); err != nil {
msg := "location alternate representation must be a valid url"
return utils.NewError(l.ValidateICalValue, msg, l, err)
}
}
return nil
}

View File

@ -0,0 +1,7 @@
package values
type Method string
const (
PublishMethod Method = "PUBLISH"
)

View File

@ -0,0 +1,434 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"regexp"
"strconv"
"strings"
)
// The recurrence rule, if specified, is used in computing the recurrence set. The recurrence set is the complete set
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence
// set is generated by gathering all of the start date/times generated by any of the specified "RRULE" and "RDATE"
// properties, and excluding any start date/times which fall within the union of start date/times generated by any
// specified "EXRULE" and "EXDATE" properties. This implies that start date/times within exclusion related properties
// (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
// one recurrence is considered. Duplicate instances are ignored.
// The "DTSTART" and "DTEND" property pair or "DTSTART" and "DURATION" property pair, specified within the iCalendar
// object defines the first instance of the recurrence. When used with a recurrence rule, the "DTSTART" and "DTEND"
// properties MUST be specified in local time and the appropriate set of "VTIMEZONE" calendar components MUST be
// included. For detail on the usage of the "VTIMEZONE" calendar component, see the "VTIMEZONE" calendar component
// definition.
// Any duration associated with the iCalendar object applies to all members of the generated recurrence set. Any
// modified duration for specific recurrences MUST be explicitly specified using the "RDATE" property.
type RecurrenceRule struct {
Frequency RecurrenceFrequency
Until *DateTime
Count int
Interval int
BySecond []int
ByMinute []int
ByHour []int
ByDay []RecurrenceWeekday
ByMonthDay []int
ByYearDay []int
ByWeekNumber []int
ByMonth []int
BySetPosition []int
WeekStart RecurrenceWeekday
}
var _ = log.Print
// the frequency an event recurs
type RecurrenceFrequency string
const (
SecondRecurrenceFrequency RecurrenceFrequency = "SECONDLY"
MinuteRecurrenceFrequency = "MINUTELY"
HourRecurrenceFrequency = "HOURLY"
DayRecurrenceFrequency = "DAILY"
WeekRecurrenceFrequency = "WEEKLY"
MonthRecurrenceFrequency = "MONTHLY"
YearRecurrenceFrequency = "YEARLY"
)
// the frequency an event recurs
type RecurrenceWeekday string
const (
MondayRecurrenceWeekday RecurrenceWeekday = "MO"
TuesdayRecurrenceWeekday = "TU"
WednesdayRecurrenceWeekday = "WE"
ThursdayRecurrenceWeekday = "TH"
FridayRecurrenceWeekday = "FR"
SaturdayRecurrenceWeekday = "SA"
SundayRecurrenceWeekday = "SU"
)
// creates a new recurrence rule object for iCalendar
func NewRecurrenceRule(frequency RecurrenceFrequency) *RecurrenceRule {
return &RecurrenceRule{Frequency: frequency}
}
var weekdayRegExp = regexp.MustCompile("MO|TU|WE|TH|FR|SA|SU")
// returns true if weekday is a valid constant
func (r RecurrenceWeekday) IsValidWeekDay() bool {
return weekdayRegExp.MatchString(strings.ToUpper(string(r)))
}
var frequencyRegExp = regexp.MustCompile("SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY")
// returns true if weekday is a valid constant
func (r RecurrenceFrequency) IsValidFrequency() bool {
return frequencyRegExp.MatchString(strings.ToUpper(string(r)))
}
// returns the recurrence rule name for the iCalendar specification
func (r *RecurrenceRule) EncodeICalName() (properties.PropertyName, error) {
return properties.RecurrenceRulePropertyName, nil
}
// encodes the recurrence rule value for the iCalendar specification
func (r *RecurrenceRule) EncodeICalValue() (string, error) {
out := []string{fmt.Sprintf("FREQ=%s", strings.ToUpper(string(r.Frequency)))}
if r.Until != nil {
if encoded, err := r.Until.EncodeICalValue(); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode until date", r, err)
} else {
out = append(out, fmt.Sprintf("UNTIL=%s", encoded))
}
}
if r.Count > 0 {
out = append(out, fmt.Sprintf("COUNT=%d", r.Count))
}
if r.Interval > 0 {
out = append(out, fmt.Sprintf("INTERVAL=%d", r.Interval))
}
if len(r.BySecond) > 0 {
if encoded, err := intsToCSV(r.BySecond); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by second value", r, err)
} else {
out = append(out, fmt.Sprintf("BYSECOND=%s", encoded))
}
}
if len(r.ByMinute) > 0 {
if encoded, err := intsToCSV(r.ByMinute); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by minute value", r, err)
} else {
out = append(out, fmt.Sprintf("BYMINUTE=%s", encoded))
}
}
if len(r.ByHour) > 0 {
if encoded, err := intsToCSV(r.ByHour); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by hour value", r, err)
} else {
out = append(out, fmt.Sprintf("BYHOUR=%s", encoded))
}
}
if len(r.ByDay) > 0 {
if encoded, err := daysToCSV(r.ByDay); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by day value", r, err)
} else {
out = append(out, fmt.Sprintf("BYDAY=%s", encoded))
}
}
if len(r.ByMonthDay) > 0 {
if encoded, err := intsToCSV(r.ByMonthDay); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by month day value", r, err)
} else {
out = append(out, fmt.Sprintf("BYMONTHDAY=%s", encoded))
}
}
if len(r.ByYearDay) > 0 {
if encoded, err := intsToCSV(r.ByYearDay); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by year day value", r, err)
} else {
out = append(out, fmt.Sprintf("BYYEARDAY=%s", encoded))
}
}
if len(r.ByWeekNumber) > 0 {
if encoded, err := intsToCSV(r.ByWeekNumber); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by week number value", r, err)
} else {
out = append(out, fmt.Sprintf("BYWEEKNO=%s", encoded))
}
}
if len(r.ByMonth) > 0 {
if encoded, err := intsToCSV(r.ByMonth); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by month value", r, err)
} else {
out = append(out, fmt.Sprintf("BYMONTH=%s", encoded))
}
}
if len(r.BySetPosition) > 0 {
if encoded, err := intsToCSV(r.BySetPosition); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by set position value", r, err)
} else {
out = append(out, fmt.Sprintf("BYSETPOS=%s", encoded))
}
}
if r.WeekStart != "" {
out = append(out, fmt.Sprintf("WKST=%s", r.WeekStart))
}
return strings.Join(out, ";"), nil
}
var rruleParamRegExp = regexp.MustCompile("(\\w+)\\s*=\\s*([^;]+)")
// decodes the recurrence rule value from the iCalendar specification
func (r *RecurrenceRule) DecodeICalValue(value string) error {
matches := rruleParamRegExp.FindAllStringSubmatch(value, -1)
if len(matches) <= 0 {
return utils.NewError(r.DecodeICalValue, "no recurrence rules found", r, nil)
}
for _, match := range matches {
if err := r.decodeICalValue(match[1], match[2]); err != nil {
msg := fmt.Sprintf("unable to decode %s value", match[1])
return utils.NewError(r.DecodeICalValue, msg, r, err)
}
}
return nil
}
func (r *RecurrenceRule) decodeICalValue(name string, value string) error {
switch name {
case "FREQ":
r.Frequency = RecurrenceFrequency(value)
case "UNTIL":
until := new(DateTime)
if err := until.DecodeICalValue(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid until value "+value, r, err)
} else {
r.Until = until
}
case "COUNT":
if count, err := strconv.ParseInt(value, 10, 64); err != nil {
return utils.NewError(r.decodeICalValue, "invalid count value "+value, r, err)
} else {
r.Count = int(count)
}
case "INTERVAL":
if interval, err := strconv.ParseInt(value, 10, 64); err != nil {
return utils.NewError(r.decodeICalValue, "invalid interval value "+value, r, err)
} else {
r.Interval = int(interval)
}
case "BYSECOND":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by second value "+value, r, err)
} else {
r.BySecond = ints
}
case "BYMINUTE":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by minute value "+value, r, err)
} else {
r.ByMinute = ints
}
case "BYHOUR":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by hour value "+value, r, err)
} else {
r.ByHour = ints
}
case "BYDAY":
if days, err := csvToDays(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by day value "+value, r, err)
} else {
r.ByDay = days
}
case "BYMONTHDAY":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by month day value "+value, r, err)
} else {
r.ByMonthDay = ints
}
case "BYYEARDAY":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by year day value "+value, r, err)
} else {
r.ByYearDay = ints
}
case "BYWEEKNO":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by week number value "+value, r, err)
} else {
r.ByWeekNumber = ints
}
case "BYMONTH":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "unable to encode by month value "+value, r, err)
} else {
r.ByMonth = ints
}
case "BYSETPOS":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "unable to encode by set position value "+value, r, err)
} else {
r.BySetPosition = ints
}
case "WKST":
r.WeekStart = RecurrenceWeekday(value)
}
return nil
}
// validates the recurrence rule value against the iCalendar specification
func (r *RecurrenceRule) ValidateICalValue() error {
if !r.Frequency.IsValidFrequency() {
return utils.NewError(r.ValidateICalValue, "a frequency is required in all recurrence rules", r, nil)
} else if r.Until != nil && r.Count > 0 {
return utils.NewError(r.ValidateICalValue, "until and count values are mutually exclusive", r, nil)
} else if found, fine := intsInRange(r.BySecond, 59); !fine {
msg := fmt.Sprintf("by second value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByMinute, 59); !fine {
msg := fmt.Sprintf("by minute value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByHour, 23); !fine {
msg := fmt.Sprintf("by hour value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if err := daysInRange(r.ByDay); err != nil {
return utils.NewError(r.ValidateICalValue, "by day value not in range", r, err)
} else if found, fine := intsInRange(r.ByMonthDay, 31); !fine {
msg := fmt.Sprintf("by month day value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByYearDay, 366); !fine {
msg := fmt.Sprintf("by year day value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByMonth, 12); !fine {
msg := fmt.Sprintf("by month value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.BySetPosition, 366); !fine {
msg := fmt.Sprintf("by month value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if err := dayInRange(r.WeekStart); r.WeekStart != "" && err != nil {
return utils.NewError(r.ValidateICalValue, "week start value not in range", r, err)
} else {
return nil
}
}
func intsToCSV(ints []int) (string, error) {
csv := new(CSV)
for _, i := range ints {
*csv = append(*csv, fmt.Sprintf("%d", i))
}
return csv.EncodeICalValue()
}
func csvToInts(value string) (ints []int, err error) {
csv := new(CSV)
if ierr := csv.DecodeICalValue(value); err != nil {
err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr)
return
}
for _, v := range *csv {
if i, ierr := strconv.ParseInt(v, 10, 64); err != nil {
err = utils.NewError(csvToInts, "unable to parse int value "+v, value, ierr)
return
} else {
ints = append(ints, int(i))
}
}
return
}
func intsInRange(ints []int, max int) (int, bool) {
for _, i := range ints {
if i < -max || i > max {
return i, false
}
}
return 0, true
}
func daysInRange(days []RecurrenceWeekday) error {
for _, day := range days {
if err := dayInRange(day); err != nil {
msg := fmt.Sprintf("day value %s is not in range", day)
return utils.NewError(dayInRange, msg, days, err)
}
}
return nil
}
var dayRegExp = regexp.MustCompile("(\\d{1,2})?(\\w{2})")
func dayInRange(day RecurrenceWeekday) error {
var ordinal, weekday string
if matches := dayRegExp.FindAllStringSubmatch(string(day), -1); len(matches) <= 0 {
msg := fmt.Sprintf("weekday value %s is not in valid format", day)
return utils.NewError(dayInRange, msg, day, nil)
} else if len(matches[0]) > 2 {
ordinal = matches[0][1]
weekday = matches[0][2]
} else {
weekday = matches[0][1]
}
if !RecurrenceWeekday(weekday).IsValidWeekDay() {
msg := fmt.Sprintf("weekday value %s is not valid", weekday)
return utils.NewError(dayInRange, msg, day, nil)
} else if i, err := strconv.ParseInt(ordinal, 10, 64); ordinal != "" && err != nil {
msg := fmt.Sprintf("weekday ordinal value %d is not valid", i)
return utils.NewError(dayInRange, msg, day, err)
} else if i < -53 || i > 53 {
msg := fmt.Sprintf("weekday ordinal value %d is not in range", i)
return utils.NewError(dayInRange, msg, day, nil)
} else {
return nil
}
}
func daysToCSV(days []RecurrenceWeekday) (string, error) {
csv := new(CSV)
for _, day := range days {
*csv = append(*csv, strings.ToUpper(string(day)))
}
return csv.EncodeICalValue()
}
func csvToDays(value string) (days []RecurrenceWeekday, err error) {
csv := new(CSV)
if ierr := csv.DecodeICalValue(value); err != nil {
err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr)
return
}
for _, v := range *csv {
days = append(days, RecurrenceWeekday(v))
}
return
}

View File

@ -0,0 +1,12 @@
package values
// Time Transparency is the characteristic of an event that determines whether it appears to consume time on a calendar.
// Events that consume actual time for the individual or resource associated with the calendar SHOULD be recorded as
// OPAQUE, allowing them to be detected by free-busy time searches. Other events, which do not take up the individual's
// (or resource's) time SHOULD be recorded as TRANSPARENT, making them invisible to free-busy time searches.
type TimeTransparency string
const (
OpaqueTimeTransparency TimeTransparency = "OPAQUE" // Blocks or opaque on busy time searches. DEFAULT
TransparentTimeTransparency = "TRANSPARENT" // Transparent on busy time searches.
)

View File

@ -0,0 +1,49 @@
package values
import (
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"net/url"
)
// a representation of duration for iCalendar
type Url struct {
u url.URL
}
// encodes the URL into iCalendar format
func (u *Url) EncodeICalValue() (string, error) {
return u.u.String(), nil
}
// encodes the url params for the iCalendar specification
func (u *Url) EncodeICalParams() (params properties.Params, err error) {
params = properties.Params{
properties.ValuePropertyName: "URI",
}
return
}
// decodes the URL from iCalendar format
func (u *Url) DecodeICalValue(value string) error {
if parsed, err := url.Parse(value); err != nil {
return utils.NewError(u.ValidateICalValue, "unable to parse url", u, err)
} else {
u.u = *parsed
return nil
}
}
// validates the URL for iCalendar format
func (u *Url) ValidateICalValue() error {
if _, err := url.Parse(u.u.String()); err != nil {
return utils.NewError(u.ValidateICalValue, "invalid URL object", u, err)
} else {
return nil
}
}
// creates a new iCalendar duration representation
func NewUrl(u url.URL) *Url {
return &Url{u: u}
}