├── LICENSE ├── README.md ├── calendar.go ├── calendar_event.go ├── calendar_event_test.go ├── calendar_test.go ├── lib.go ├── node.go ├── parsers.go ├── properties.go ├── serializers.go └── todo.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Laurent Cozic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ical-go 2 | 3 | iCal package for Go (Golang) 4 | 5 | ## Installation 6 | 7 | go get github.com/laurent22/ical-go 8 | 9 | ## Status 10 | 11 | Currently, the package doesn't support the full iCal specification. It's still a work in progress towards that goal. 12 | 13 | The most useful function in the package is: 14 | 15 | ```go 16 | func ParseCalendar(data string) (*Node, error) 17 | ``` 18 | Parses a VCALENDAR string, unwrap and unfold lines, etc. and put all this into a usable structure (a collection of `Node`s with name, value, type, etc.). 19 | 20 | With the `Node` in hand, you can use several of its functions to, e.g., find specific parameters, children, etc. 21 | 22 | 23 | ## License 24 | 25 | MIT 26 | -------------------------------------------------------------------------------- /calendar.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | type Calendar struct { 4 | Items []CalendarEvent 5 | } 6 | 7 | func (this *Calendar) Serialize() string { 8 | serializer := calSerializer{ 9 | calendar: this, 10 | buffer: new(strBuffer), 11 | } 12 | return serializer.serialize() 13 | } 14 | 15 | func (this *Calendar) ToICS() string { 16 | return this.Serialize() 17 | } 18 | -------------------------------------------------------------------------------- /calendar_event.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type CalendarEvent struct { 8 | Id string 9 | Summary string 10 | Description string 11 | Location string 12 | URL string 13 | CreatedAtUTC *time.Time 14 | ModifiedAtUTC *time.Time 15 | StartAt *time.Time 16 | EndAt *time.Time 17 | } 18 | 19 | func (this *CalendarEvent) StartAtUTC() *time.Time { 20 | return inUTC(this.StartAt) 21 | } 22 | 23 | func (this *CalendarEvent) EndAtUTC() *time.Time { 24 | return inUTC(this.EndAt) 25 | } 26 | 27 | func (this *CalendarEvent) Serialize() string { 28 | buffer := new(strBuffer) 29 | return this.serializeWithBuffer(buffer) 30 | } 31 | 32 | func (this *CalendarEvent) ToICS() string { 33 | return this.Serialize() 34 | } 35 | 36 | func (this *CalendarEvent) serializeWithBuffer(buffer *strBuffer) string { 37 | serializer := calEventSerializer{ 38 | event: this, 39 | buffer: buffer, 40 | } 41 | return serializer.serialize() 42 | } 43 | 44 | func inUTC(t *time.Time) *time.Time { 45 | if t == nil { 46 | return nil 47 | } 48 | 49 | tUTC := t.UTC() 50 | return &tUTC 51 | } 52 | -------------------------------------------------------------------------------- /calendar_event_test.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestStartAndEndAtUTC(t *testing.T) { 10 | event := CalendarEvent{} 11 | 12 | if event.StartAtUTC() != nil { 13 | t.Error("StartAtUTC should have been nil") 14 | } 15 | if event.EndAtUTC() != nil { 16 | t.Error("EndAtUTC should have been nil") 17 | } 18 | 19 | tUTC := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 20 | event.StartAt = &tUTC 21 | event.EndAt = &tUTC 22 | startTime := *(event.StartAtUTC()) 23 | endTime := *(event.EndAtUTC()) 24 | 25 | if startTime != tUTC { 26 | t.Error("StartAtUTC should have been", tUTC, ", but was", startTime) 27 | } 28 | if endTime != tUTC { 29 | t.Error("EndAtUTC should have been", tUTC, ", but was", endTime) 30 | } 31 | 32 | tUTC = time.Date(2010, time.March, 8, 2, 0, 0, 0, time.UTC) 33 | nyk, err := time.LoadLocation("America/New_York") 34 | if err != nil { 35 | panic(err) 36 | } 37 | tNYK := tUTC.In(nyk) 38 | event.StartAt = &tNYK 39 | event.EndAt = &tNYK 40 | startTime = *(event.StartAtUTC()) 41 | endTime = *(event.EndAtUTC()) 42 | 43 | if startTime != tUTC { 44 | t.Error("StartAtUTC should have been", tUTC, ", but was", startTime) 45 | } 46 | if endTime != tUTC { 47 | t.Error("EndAtUTC should have been", tUTC, ", but was", endTime) 48 | } 49 | } 50 | 51 | func TestCalendarEventSerialize(t *testing.T) { 52 | ny, err := time.LoadLocation("America/New_York") 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | createdAt := time.Date(2010, time.January, 1, 12, 0, 1, 0, time.UTC) 58 | modifiedAt := createdAt.Add(time.Second) 59 | startsAt := createdAt.Add(time.Second * 2).In(ny) 60 | endsAt := createdAt.Add(time.Second * 3).In(ny) 61 | 62 | event := CalendarEvent{ 63 | Id: "123", 64 | CreatedAtUTC: &createdAt, 65 | ModifiedAtUTC: &modifiedAt, 66 | StartAt: &startsAt, 67 | EndAt: &endsAt, 68 | Summary: "Foo Bar", 69 | Location: "Berlin\nGermany", 70 | Description: "Lorem\nIpsum", 71 | URL: "https://www.example.com", 72 | } 73 | 74 | // expects that DTSTART and DTEND be in UTC (Z) 75 | // expects that string values (LOCATION for example) be escaped 76 | expected := ` 77 | BEGIN:VEVENT 78 | UID:123 79 | CREATED:20100101T120001Z 80 | LAST-MODIFIED:20100101T120002Z 81 | DTSTART:20100101T120003Z 82 | DTEND:20100101T120004Z 83 | SUMMARY:Foo Bar 84 | DESCRIPTION:Lorem\nIpsum 85 | LOCATION:Berlin\nGermany 86 | URL:https://www.example.com 87 | END:VEVENT` 88 | 89 | output := event.Serialize() 90 | if output != strings.TrimSpace(expected) { 91 | t.Error("Expected calendar event serialization to be:\n", expected, "\n\nbut got:\n", output) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /calendar_test.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCalendarSerialize(t *testing.T) { 8 | calendar := new(Calendar) 9 | 10 | // test calendar w/o items 11 | 12 | expected := "BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR" 13 | output := calendar.Serialize() 14 | 15 | if output != expected { 16 | t.Error("\nExpected calendar serialization to be:\n", expected, "\n\nbut got:\n", output) 17 | } 18 | 19 | // test calendar with items 20 | 21 | calendar.Items = append(calendar.Items, CalendarEvent{Summary: "Foo"}) 22 | 23 | expected = "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nSUMMARY:Foo\nEND:VEVENT\nEND:VCALENDAR" 24 | output = calendar.Serialize() 25 | 26 | if output != expected { 27 | t.Error("\nExpected calendar serialization to be:\n", expected, "\n\nbut got:\n", output) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | type strBuffer struct { 9 | buffer bytes.Buffer 10 | } 11 | 12 | func (b *strBuffer) Write(format string, elem ...interface{}) { 13 | b.buffer.WriteString(fmt.Sprintf(format, elem...)) 14 | } 15 | 16 | func (b *strBuffer) String() string { 17 | return b.buffer.String() 18 | } 19 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | type Node struct { 10 | Name string 11 | Value string 12 | Type int // 1 = Object, 0 = Name/Value 13 | Parameters map[string]string 14 | Children []*Node 15 | } 16 | 17 | func (this *Node) ChildrenByName(name string) []*Node { 18 | var output []*Node 19 | for _, child := range this.Children { 20 | if child.Name == name { 21 | output = append(output, child) 22 | } 23 | } 24 | return output 25 | } 26 | 27 | func (this *Node) ChildByName(name string) *Node { 28 | for _, child := range this.Children { 29 | if child.Name == name { 30 | return child 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | func (this *Node) PropString(name string, defaultValue string) string { 37 | for _, child := range this.Children { 38 | if child.Name == name { 39 | return child.Value 40 | } 41 | } 42 | return defaultValue 43 | } 44 | 45 | func (this *Node) PropDate(name string, defaultValue time.Time) time.Time { 46 | node := this.ChildByName(name) 47 | if node == nil { 48 | return defaultValue 49 | } 50 | tzid := node.Parameter("TZID", "") 51 | allDay := node.Parameter("VALUE", "") == "DATE" 52 | var output time.Time 53 | var err error 54 | if tzid != "" { 55 | loc, err := time.LoadLocation(tzid) 56 | if err != nil { 57 | panic(err) 58 | } 59 | output, err = time.ParseInLocation("20060102T150405", node.Value, loc) 60 | } else if allDay { 61 | output, err = time.Parse("20060102", node.Value) 62 | } else { 63 | output, err = time.Parse("20060102T150405Z", node.Value) 64 | } 65 | 66 | if err != nil { 67 | panic(err) 68 | } 69 | return output 70 | } 71 | 72 | func (this *Node) PropDuration(name string) time.Duration { 73 | durStr := this.PropString(name, "") 74 | 75 | if durStr == "" { 76 | return time.Duration(0) 77 | } 78 | 79 | durRgx := regexp.MustCompile("PT(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)S)?") 80 | matches := durRgx.FindStringSubmatch(durStr) 81 | 82 | if len(matches) != 4 { 83 | return time.Duration(0) 84 | } 85 | 86 | strToDuration := func(value string) time.Duration { 87 | d := 0 88 | if value != "" { 89 | d, _ = strconv.Atoi(value) 90 | } 91 | return time.Duration(d) 92 | } 93 | 94 | hours := strToDuration(matches[1]) 95 | min := strToDuration(matches[2]) 96 | sec := strToDuration(matches[3]) 97 | 98 | return hours*time.Hour + min*time.Minute + sec*time.Second 99 | } 100 | 101 | func (this *Node) PropInt(name string, defaultValue int) int { 102 | n := this.PropString(name, "") 103 | if n == "" { 104 | return defaultValue 105 | } 106 | output, err := strconv.Atoi(n) 107 | if err != nil { 108 | panic(err) 109 | } 110 | return output 111 | } 112 | 113 | func (this *Node) DigProperty(propPath ...string) (string, bool) { 114 | return this.dig("prop", propPath...) 115 | } 116 | 117 | func (this *Node) Parameter(name string, defaultValue string) string { 118 | if len(this.Parameters) <= 0 { 119 | return defaultValue 120 | } 121 | v, ok := this.Parameters[name] 122 | if !ok { 123 | return defaultValue 124 | } 125 | return v 126 | } 127 | 128 | func (this *Node) DigParameter(paramPath ...string) (string, bool) { 129 | return this.dig("param", paramPath...) 130 | } 131 | 132 | // Digs a value based on a given value path. 133 | // valueType: can be "param" or "prop". 134 | // valuePath: the path to access the value. 135 | // Returns ("", false) when not found or (value, true) when found. 136 | // 137 | // Example: 138 | // dig("param", "VCALENDAR", "VEVENT", "DTEND", "TYPE") -> It will search for "VCALENDAR" node, 139 | // then a "VEVENT" node, then a "DTEND" note, then finally the "TYPE" param. 140 | func (this *Node) dig(valueType string, valuePath ...string) (string, bool) { 141 | current := this 142 | lastIndex := len(valuePath) - 1 143 | for _, v := range valuePath[:lastIndex] { 144 | current = current.ChildByName(v) 145 | 146 | if current == nil { 147 | return "", false 148 | } 149 | } 150 | 151 | target := valuePath[lastIndex] 152 | 153 | value := "" 154 | if valueType == "param" { 155 | value = current.Parameter(target, "") 156 | } else if valueType == "prop" { 157 | value = current.PropString(target, "") 158 | } 159 | 160 | if value == "" { 161 | return "", false 162 | } 163 | 164 | return value, true 165 | } 166 | 167 | func (this *Node) String() string { 168 | s := "" 169 | if this.Type == 1 { 170 | s += "===== " + this.Name 171 | s += "\n" 172 | } else { 173 | s += this.Name 174 | s += ":" + this.Value 175 | s += "\n" 176 | } 177 | for _, child := range this.Children { 178 | s += child.String() 179 | } 180 | if this.Type == 1 { 181 | s += "===== /" + this.Name 182 | s += "\n" 183 | } 184 | return s 185 | } 186 | -------------------------------------------------------------------------------- /parsers.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | func ParseCalendar(data string) (*Node, error) { 11 | r := regexp.MustCompile("([\r|\t| ]*\n[\r|\t| ]*)+") 12 | lines := r.Split(strings.TrimSpace(data), -1) 13 | node, _, err, _ := parseCalendarNode(lines, 0) 14 | 15 | return node, err 16 | } 17 | 18 | func parseCalendarNode(lines []string, lineIndex int) (*Node, bool, error, int) { 19 | line := strings.TrimSpace(lines[lineIndex]) 20 | _ = log.Println 21 | colonIndex := strings.Index(line, ":") 22 | if colonIndex <= 0 { 23 | return nil, false, errors.New("Invalid value/pair: " + line), lineIndex + 1 24 | } 25 | name := line[0:colonIndex] 26 | splitted := strings.Split(name, ";") 27 | var parameters map[string]string 28 | if len(splitted) >= 2 { 29 | name = splitted[0] 30 | parameters = make(map[string]string) 31 | for i := 1; i < len(splitted); i++ { 32 | p := strings.Split(splitted[i], "=") 33 | if len(p) != 2 { 34 | panic("Invalid parameter format: " + name) 35 | } 36 | parameters[p[0]] = p[1] 37 | } 38 | } 39 | value := line[colonIndex+1 : len(line)] 40 | 41 | if name == "BEGIN" { 42 | node := new(Node) 43 | node.Name = value 44 | node.Type = 1 45 | lineIndex = lineIndex + 1 46 | for { 47 | child, finished, _, newLineIndex := parseCalendarNode(lines, lineIndex) 48 | if finished { 49 | return node, false, nil, newLineIndex 50 | } else { 51 | if child != nil { 52 | node.Children = append(node.Children, child) 53 | } 54 | lineIndex = newLineIndex 55 | } 56 | } 57 | } else if name == "END" { 58 | return nil, true, nil, lineIndex + 1 59 | } else { 60 | node := new(Node) 61 | node.Name = name 62 | if name == "DESCRIPTION" || name == "SUMMARY" { 63 | text, newLineIndex := parseTextType(lines, lineIndex) 64 | node.Value = text 65 | node.Parameters = parameters 66 | return node, false, nil, newLineIndex 67 | } else { 68 | node.Value = value 69 | node.Parameters = parameters 70 | return node, false, nil, lineIndex + 1 71 | } 72 | } 73 | 74 | panic("Unreachable") 75 | return nil, false, nil, lineIndex + 1 76 | } 77 | 78 | func parseTextType(lines []string, lineIndex int) (string, int) { 79 | line := lines[lineIndex] 80 | colonIndex := strings.Index(line, ":") 81 | output := strings.TrimSpace(line[colonIndex+1 : len(line)]) 82 | lineIndex++ 83 | for { 84 | line := lines[lineIndex] 85 | if line == "" || line[0] != ' ' { 86 | return unescapeTextType(output), lineIndex 87 | } 88 | output += line[1:len(line)] 89 | lineIndex++ 90 | } 91 | return unescapeTextType(output), lineIndex 92 | } 93 | 94 | func escapeTextType(input string) string { 95 | output := strings.Replace(input, "\\", "\\\\", -1) 96 | output = strings.Replace(output, ";", "\\;", -1) 97 | output = strings.Replace(output, ",", "\\,", -1) 98 | output = strings.Replace(output, "\n", "\\n", -1) 99 | return output 100 | } 101 | 102 | func unescapeTextType(s string) string { 103 | s = strings.Replace(s, "\\;", ";", -1) 104 | s = strings.Replace(s, "\\,", ",", -1) 105 | s = strings.Replace(s, "\\n", "\n", -1) 106 | s = strings.Replace(s, "\\\\", "\\", -1) 107 | return s 108 | } 109 | -------------------------------------------------------------------------------- /properties.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | const ( 4 | VCALENDAR = "VCALENDAR" 5 | VEVENT = "VEVENT" 6 | DTSTART = "DTSTART" 7 | DTEND = "DTEND" 8 | DURATION = "DURATION" 9 | ) 10 | -------------------------------------------------------------------------------- /serializers.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | ) 7 | 8 | type calSerializer struct { 9 | calendar *Calendar 10 | buffer *strBuffer 11 | } 12 | 13 | func (this *calSerializer) serialize() string { 14 | this.serializeCalendar() 15 | return strings.TrimSpace(this.buffer.String()) 16 | } 17 | 18 | func (this *calSerializer) serializeCalendar() { 19 | this.begin() 20 | this.version() 21 | this.items() 22 | this.end() 23 | } 24 | 25 | func (this *calSerializer) begin() { 26 | this.buffer.Write("BEGIN:VCALENDAR\n") 27 | } 28 | 29 | func (this *calSerializer) end() { 30 | this.buffer.Write("END:VCALENDAR\n") 31 | } 32 | 33 | func (this *calSerializer) version() { 34 | this.buffer.Write("VERSION:2.0\n") 35 | } 36 | 37 | func (this *calSerializer) items() { 38 | for _, item := range this.calendar.Items { 39 | item.serializeWithBuffer(this.buffer) 40 | } 41 | } 42 | 43 | type calEventSerializer struct { 44 | event *CalendarEvent 45 | buffer *strBuffer 46 | } 47 | 48 | const ( 49 | eventSerializerTimeFormat = "20060102T150405Z" 50 | ) 51 | 52 | func (this *calEventSerializer) serialize() string { 53 | this.serializeEvent() 54 | return strings.TrimSpace(this.buffer.String()) 55 | } 56 | 57 | func (this *calEventSerializer) serializeEvent() { 58 | this.begin() 59 | this.uid() 60 | this.created() 61 | this.lastModified() 62 | this.dtstart() 63 | this.dtend() 64 | this.summary() 65 | this.description() 66 | this.location() 67 | this.url() 68 | this.end() 69 | } 70 | 71 | func (this *calEventSerializer) begin() { 72 | this.buffer.Write("BEGIN:VEVENT\n") 73 | } 74 | 75 | func (this *calEventSerializer) end() { 76 | this.buffer.Write("END:VEVENT\n") 77 | } 78 | 79 | func (this *calEventSerializer) uid() { 80 | this.serializeStringProp("UID", this.event.Id) 81 | } 82 | 83 | func (this *calEventSerializer) summary() { 84 | this.serializeStringProp("SUMMARY", this.event.Summary) 85 | } 86 | 87 | func (this *calEventSerializer) description() { 88 | this.serializeStringProp("DESCRIPTION", this.event.Description) 89 | } 90 | 91 | func (this *calEventSerializer) location() { 92 | this.serializeStringProp("LOCATION", this.event.Location) 93 | } 94 | 95 | func (this *calEventSerializer) url() { 96 | this.serializeStringProp("URL", this.event.URL) 97 | } 98 | 99 | func (this *calEventSerializer) dtstart() { 100 | this.serializeTimeProp("DTSTART", this.event.StartAtUTC()) 101 | } 102 | 103 | func (this *calEventSerializer) dtend() { 104 | this.serializeTimeProp("DTEND", this.event.EndAtUTC()) 105 | } 106 | 107 | func (this *calEventSerializer) created() { 108 | this.serializeTimeProp("CREATED", this.event.CreatedAtUTC) 109 | } 110 | 111 | func (this *calEventSerializer) lastModified() { 112 | this.serializeTimeProp("LAST-MODIFIED", this.event.ModifiedAtUTC) 113 | } 114 | 115 | func (this *calEventSerializer) serializeStringProp(name, value string) { 116 | if value != "" { 117 | escapedValue := escapeTextType(value) 118 | this.buffer.Write("%s:%s\n", name, escapedValue) 119 | } 120 | } 121 | 122 | func (this *calEventSerializer) serializeTimeProp(name string, value *time.Time) { 123 | if value != nil { 124 | this.buffer.Write("%s:%s\n", name, value.Format(eventSerializerTimeFormat)) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /todo.go: -------------------------------------------------------------------------------- 1 | package ical 2 | 3 | // import ( 4 | // "time" 5 | // "strconv" 6 | // "strings" 7 | // ) 8 | // 9 | // func TodoFromNode(node *Node) Todo { 10 | // if node.Name != "VTODO" { panic("Node is not a VTODO") } 11 | // 12 | // var todo Todo 13 | // todo.SetId(node.PropString("UID", "")) 14 | // todo.SetSummary(node.PropString("SUMMARY", "")) 15 | // todo.SetDescription(node.PropString("DESCRIPTION", "")) 16 | // todo.SetDueDate(node.PropDate("DUE", time.Time{})) 17 | // //todo.SetAlarmDate(this.TimestampBytesToTime(reminderDate)) 18 | // todo.SetCreatedDate(node.PropDate("CREATED", time.Time{})) 19 | // todo.SetModifiedDate(node.PropDate("DTSTAMP", time.Time{})) 20 | // todo.SetPriority(node.PropInt("PRIORITY", 0)) 21 | // todo.SetPercentComplete(node.PropInt("PERCENT-COMPLETE", 0)) 22 | // return todo 23 | // } 24 | // 25 | // type Todo struct { 26 | // CalendarItem 27 | // dueDate time.Time 28 | // } 29 | // 30 | // func (this *Todo) SetDueDate(v time.Time) { this.dueDate = v } 31 | // func (this *Todo) DueDate() time.Time { return this.dueDate } 32 | // 33 | // func (this *Todo) ICalString(target string) string { 34 | // s := "BEGIN:VTODO\n" 35 | // 36 | // if target == "macTodo" { 37 | // status := "NEEDS-ACTION" 38 | // if this.PercentComplete() == 100 { 39 | // status = "COMPLETED" 40 | // } 41 | // s += "STATUS:" + status + "\n" 42 | // } 43 | // 44 | // s += encodeDateProperty("CREATED", this.CreatedDate()) + "\n" 45 | // s += "UID:" + this.Id() + "\n" 46 | // s += "SUMMARY:" + escapeTextType(this.Summary()) + "\n" 47 | // if this.PercentComplete() == 100 && !this.CompletedDate().IsZero() { 48 | // s += encodeDateProperty("COMPLETED", this.CompletedDate()) + "\n" 49 | // } 50 | // s += encodeDateProperty("DTSTAMP", this.ModifiedDate()) + "\n" 51 | // if this.Priority() != 0 { 52 | // s += "PRIORITY:" + strconv.Itoa(this.Priority()) + "\n" 53 | // } 54 | // if this.PercentComplete() != 0 { 55 | // s += "PERCENT-COMPLETE:" + strconv.Itoa(this.PercentComplete()) + "\n" 56 | // } 57 | // if target == "macTodo" { 58 | // s += "SEQUENCE:" + strconv.Itoa(this.Sequence()) + "\n" 59 | // } 60 | // if this.Description() != "" { 61 | // s += "DESCRIPTION:" + encodeTextType(this.Description()) + "\n" 62 | // } 63 | // 64 | // s += "END:VTODO\n" 65 | // 66 | // return s 67 | // } 68 | // 69 | // func encodeDateProperty(name string, t time.Time) string { 70 | // var output string 71 | // zone, _ := t.Zone() 72 | // if zone != "UTC" && zone != "" { 73 | // output = ";TZID=" + zone + ":" + t.Format("20060102T150405") 74 | // } else { 75 | // output = ":" + t.Format("20060102T150405") + "Z" 76 | // } 77 | // return name + output 78 | // } 79 | // 80 | // 81 | // func encodeTextType(s string) string { 82 | // output := "" 83 | // s = escapeTextType(s) 84 | // lineLength := 0 85 | // for _, c := range s { 86 | // if lineLength + len(string(c)) > 75 { 87 | // output += "\n " 88 | // lineLength = 1 89 | // } 90 | // output += string(c) 91 | // lineLength += len(string(c)) 92 | // } 93 | // return output 94 | // } 95 | --------------------------------------------------------------------------------