lib: added reuse::TzWrapper class and utilities
[barry.git] / src / vevent.cc
blobead77119816ad5cb4d48828be8f07e7ff5e26d1a
1 //
2 // \file vevent.cc
3 // Conversion routines for vevents (VCALENDAR, etc)
4 //
6 /*
7 Copyright (C) 2006-2010, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "vevent.h"
23 //#include "trace.h"
24 #include <stdint.h>
25 #include <glib.h>
26 #include <strings.h>
27 #include <sstream>
28 #include <string>
30 using namespace std;
32 namespace Barry { namespace Sync {
34 //////////////////////////////////////////////////////////////////////////////
35 // vCalendar
37 vCalendar::vCalendar(vTimeZone &vtz)
38 : m_vtz(vtz)
39 , m_gCalData(0)
43 vCalendar::~vCalendar()
45 if( m_gCalData ) {
46 g_free(m_gCalData);
50 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
52 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
54 for( int i = 0; i < 7; i++ ) {
55 if( strcasecmp(dayname, WeekDays[i]) == 0 )
56 return i;
58 return 0;
61 bool vCalendar::HasMultipleVEvents() const
63 int count = 0;
64 b_VFormat *format = const_cast<b_VFormat*>(Format());
65 GList *attrs = format ? b_vformat_get_attributes(format) : 0;
66 for( ; attrs; attrs = attrs->next ) {
67 b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data;
68 if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 &&
69 strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
71 count++;
74 return count > 1;
77 void vCalendar::RecurToVCal()
79 using namespace Barry;
80 using namespace std;
81 Barry::Calendar &cal = m_BarryCal;
83 if( !cal.Recurring )
84 return;
86 vAttrPtr attr = NewAttr("RRULE");
88 switch( cal.RecurringType )
90 case Calendar::Day: // eg. every day
91 AddParam(attr, "FREQ", "DAILY");
92 break;
94 case Calendar::MonthByDate: // eg. every month on the 12th
95 // see: DayOfMonth
96 AddParam(attr, "FREQ", "MONTHLY");
98 ostringstream oss;
99 oss << cal.DayOfMonth;
100 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
102 break;
104 case Calendar::MonthByDay: // eg. every month on 3rd Wed
105 // see: DayOfWeek and WeekOfMonth
106 AddParam(attr, "FREQ", "MONTHLY");
107 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
108 ostringstream oss;
109 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
110 AddParam(attr, "BYDAY", oss.str().c_str());
112 break;
114 case Calendar::YearByDate: // eg. every year on March 5
115 // see: DayOfMonth and MonthOfYear
116 AddParam(attr, "FREQ", "YEARLY");
118 ostringstream oss;
119 oss << cal.MonthOfYear;
120 AddParam(attr, "BYMONTH", oss.str().c_str());
123 ostringstream oss;
124 oss << cal.DayOfMonth;
125 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
127 break;
129 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
130 // see: DayOfWeek, WeekOfMonth, and
131 // MonthOfYear
132 AddParam(attr, "FREQ", "YEARLY");
133 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
134 ostringstream oss;
135 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
136 AddParam(attr, "BYDAY", oss.str().c_str());
138 oss.str("");
139 oss << cal.MonthOfYear;
140 AddParam(attr, "BYMONTH", oss.str().c_str());
142 break;
144 case Calendar::Week: // eg. every week on Mon and Fri
145 // see: WeekDays
146 AddParam(attr, "FREQ", "WEEKLY");
148 ostringstream oss;
149 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
150 if( cal.WeekDays & bm ) {
151 if( cnt )
152 oss << ",";
153 oss << WeekDays[i];
154 cnt++;
157 AddParam(attr, "BYDAY", oss.str().c_str());
159 break;
161 default:
162 throw ConvertError("Unknown RecurringType in Barry Calendar object");
165 // add some common parameters
166 if( cal.Interval > 1 ) {
167 ostringstream oss;
168 oss << cal.Interval;
169 AddParam(attr, "INTERVAL", oss.str().c_str());
171 if( !cal.Perpetual ) {
172 AddParam(attr, "UNTIL",
173 m_vtz.unix2vtime(&cal.RecurringEndTime).c_str());
176 AddAttr(attr);
179 bool AllDayEvent;
182 /// Recurring data
184 /// Note: interval can be used on all of these recurring types to
185 /// make it happen "every other time" or more, etc.
188 bool Recurring;
189 RecurringCodeType RecurringType;
190 unsigned short Interval; // must be >= 1
191 time_t RecurringEndTime; // only pertains if Recurring is true
192 // sets the date and time when
193 // recurrence of this appointment
194 // should no longer occur
195 // If a perpetual appointment, this
196 // is 0xFFFFFFFF in the low level data
197 // Instead, set the following flag.
198 bool Perpetual; // if true, this will always recur
199 unsigned short TimeZoneCode; // the time zone originally used
200 // for the recurrence data...
201 // seems to have little use, but
202 // set to your current time zone
203 // as a good default
205 unsigned short // recurring details, depending on type
206 DayOfWeek, // 0-6
207 WeekOfMonth, // 1-5
208 DayOfMonth, // 1-31
209 MonthOfYear; // 1-12
210 unsigned char WeekDays; // bitmask, bit 0 = sunday
212 #define CAL_WD_SUN 0x01
213 #define CAL_WD_MON 0x02
214 #define CAL_WD_TUE 0x04
215 #define CAL_WD_WED 0x08
216 #define CAL_WD_THU 0x10
217 #define CAL_WD_FRI 0x20
218 #define CAL_WD_SAT 0x40
224 void vCalendar::RecurToBarryCal()
226 // FIXME - needs to be implemented
228 // GetWeekDayIndex()
231 // Main conversion routine for converting from Barry::Calendar to
232 // a vCalendar string of data.
233 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
235 // Trace trace("vCalendar::ToVCal");
236 std::ostringstream oss;
237 cal.Dump(oss);
238 // trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
240 // start fresh
241 Clear();
242 SetFormat( b_vformat_new() );
243 if( !Format() )
244 throw ConvertError("resource error allocating vformat");
246 // store the Barry object we're working with
247 m_BarryCal = cal;
249 // begin building vCalendar data
250 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
251 AddAttr(NewAttr("BEGIN", "VEVENT"));
252 AddAttr(NewAttr("SEQUENCE", "0"));
253 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
254 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
255 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
257 string start(m_vtz.unix2vtime(&cal.StartTime));
258 string end(m_vtz.unix2vtime(&cal.EndTime));
259 string notify(m_vtz.unix2vtime(&cal.NotificationTime));
261 AddAttr(NewAttr("DTSTART", start.c_str()));
262 AddAttr(NewAttr("DTEND", end.c_str()));
263 // FIXME - add a truly globally unique "UID" string?
266 AddAttr(NewAttr("BEGIN", "VALARM"));
267 AddAttr(NewAttr("ACTION", "AUDIO"));
269 // notify must be UTC, when specified in DATE-TIME
270 vAttrPtr trigger = NewAttr("TRIGGER", notify.c_str());
271 AddParam(trigger, "VALUE", "DATE-TIME");
272 AddAttr(trigger);
274 AddAttr(NewAttr("END", "VALARM"));
277 if( cal.Recurring ) {
278 RecurToVCal();
281 AddAttr(NewAttr("END", "VEVENT"));
283 // generate the raw VCALENDAR data
284 m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20);
285 m_vCalData = m_gCalData;
287 // trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
288 return m_vCalData;
291 // Main conversion routine for converting from vCalendar data string
292 // to a Barry::Calendar object.
293 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
295 using namespace std;
297 // Trace trace("vCalendar::ToBarry");
298 // trace.logf("ToBarry, working on vcal data: %s", vcal);
300 // we only handle vCalendar data with one vevent block
301 if( HasMultipleVEvents() )
302 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
304 // start fresh
305 Clear();
307 // store the vCalendar raw data
308 m_vCalData = vcal;
310 // create format parser structures
311 SetFormat( b_vformat_new_from_string(vcal) );
312 if( !Format() )
313 throw ConvertError("resource error allocating vformat");
315 string start = GetAttr("DTSTART", "/vevent");
316 // trace.logf("DTSTART attr retrieved: %s", start.c_str());
317 string end = GetAttr("DTEND", "/vevent");
318 // trace.logf("DTEND attr retrieved: %s", end.c_str());
319 string subject = GetAttr("SUMMARY", "/vevent");
320 // trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
321 if( subject.size() == 0 ) {
322 subject = "<blank subject>";
323 // trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
325 vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
327 string location = GetAttr("LOCATION", "/vevent");
328 // trace.logf("LOCATION attr retrieved: %s", location.c_str());
330 string notes = GetAttr("DESCRIPTION", "/vevent");
331 // trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
335 // Now, run checks and convert into Barry object
339 // FIXME - we are assuming that any non-UTC timestamps
340 // in the vcalendar record will be in the current timezone...
341 // This is wrong! fix this later.
343 // Also, we current ignore any time zone
344 // parameters that might be in the vcalendar format... this
345 // must be fixed.
347 time_t now = time(NULL);
348 int zoneoffset = m_vtz.timezone_diff(localtime(&now));
350 Barry::Calendar &rec = m_BarryCal;
351 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
353 if( !start.size() )
354 throw ConvertError("Blank DTSTART");
355 rec.StartTime = m_vtz.vtime2unix(start.c_str(), zoneoffset);
357 if( !end.size() ) {
358 // DTEND is actually optional! According to the
359 // RFC, a DTSTART with no DTEND should be treated
360 // like a "special day" like an anniversary, which occupies
361 // no time.
363 // Since the Blackberry doesn't really map well to this
364 // case, we'll set the end time to 1 day past start.
366 rec.EndTime = rec.StartTime + 24 * 60 * 60;
368 else {
369 rec.EndTime = m_vtz.vtime2unix(end.c_str(), zoneoffset);
372 rec.Subject = subject;
373 rec.Location = location;
374 rec.Notes = notes;
376 // convert trigger time into notification time
377 // assume no notification, by default
378 rec.NotificationTime = 0;
379 if( trigger_obj.Get() ) {
380 string trigger_type = trigger_obj.GetParam("VALUE");
381 string trigger = trigger_obj.GetValue();
383 if( trigger.size() == 0 ) {
384 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
386 else if( trigger_type == "DATE-TIME" ) {
387 rec.NotificationTime = m_vtz.vtime2unix(trigger.c_str(), zoneoffset);
389 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
390 // default is DURATION (RFC 4.8.6.3)
391 string related = trigger_obj.GetParam("RELATED");
393 // default to relative to start time
394 time_t *relative = &rec.StartTime;
395 if( related == "END" )
396 relative = &rec.EndTime;
398 rec.NotificationTime = *relative + m_vtz.alarmdu2sec(trigger.c_str());
400 else {
401 throw ConvertError("Unknown TRIGGER VALUE");
404 else {
405 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
408 std::ostringstream oss;
409 m_BarryCal.Dump(oss);
410 // trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
411 return m_BarryCal;
414 // Transfers ownership of m_gCalData to the caller.
415 char* vCalendar::ExtractVCal()
417 char *ret = m_gCalData;
418 m_gCalData = 0;
419 return ret;
422 void vCalendar::Clear()
424 vBase::Clear();
425 m_vCalData.clear();
426 m_BarryCal.Clear();
428 if( m_gCalData ) {
429 g_free(m_gCalData);
430 m_gCalData = 0;
434 }} // namespace Barry::Sync