lib: added handling and checks for negative VEVENT recurrence values
[barry.git] / src / vevent.cc
blobdc4a368747b0e7309b645d600fe7fec1b0dd4466
1 ///
2 /// \file vevent.cc
3 /// Conversion routines for vevents (VCALENDAR, etc)
4 ///
6 /*
7 Copyright (C) 2006-2012, Net Direct Inc. (http://www.netdirect.ca/)
8 Copyright (C) 2010, Nicolas VIVIEN
9 Copyright (C) 2009, Dr J A Gow <J.A.Gow@wellfrazzled.com>
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 See the GNU General Public License in the COPYING file at the
21 root directory of this project for more details.
24 #include "vevent.h"
25 //#include "trace.h"
26 #include "log.h"
27 #include "time.h"
28 #include <stdint.h>
29 #include <glib.h>
30 #include <strings.h>
31 #include <stdlib.h>
32 #include <sstream>
33 #include <string>
35 using namespace std;
37 namespace Barry { namespace Sync {
39 //////////////////////////////////////////////////////////////////////////////
40 // vCalendar
42 vCalendar::vCalendar(vTimeConverter &vtc)
43 : m_vtc(vtc)
44 , m_gCalData(0)
48 vCalendar::~vCalendar()
50 if( m_gCalData ) {
51 g_free(m_gCalData);
55 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
57 uint16_t vCalendar::GetWeekDayIndex(const char *dayname)
59 for( int i = 0; i < 7; i++ ) {
60 if( strcasecmp(dayname, WeekDays[i]) == 0 )
61 return i;
63 return 0;
66 void vCalendar::CheckUnsupportedArg(const ArgMapType &args,
67 const std::string &name)
69 if( args.find(name) != args.end() ) {
70 barrylog("ERROR: recurrence rule contains " << name << ", unsupported by Barry. MIME conversion will be incorrect.");
71 barryverbose("Record data so far:\n" << m_BarryCal);
75 std::vector<std::string> vCalendar::SplitBYDAY(const std::string &ByDay)
77 std::vector<std::string> v = Tokenize(ByDay);
79 // BlackBerry recursion only supports one specification...
80 // i.e. only 3rd Wed of month, not 3rd Wed and 2nd Fri of month...
81 // if there's more than one item in v, warn the user, and just
82 // use the first item
83 if( v.size() > 1 ) {
84 barrylog("Warning: multiple items in BYDAY, not supported by device (" << ByDay << "). Using only the first item.");
85 barryverbose("Record data so far:\n" << m_BarryCal);
88 return v;
91 uint16_t vCalendar::GetMonthWeekNumFromBYDAY(const std::string& ByDay)
93 std::vector<std::string> v = SplitBYDAY(ByDay);
95 if( !v.size() || v[0].size() < 2 ) {
96 return 0;
98 else {
99 int week = atoi(v[0].substr(0,v[0].length()-2).c_str());
100 if( week < 0 ) {
101 // assume 4 weeks per month
102 int pos_week = 4 + (week + 1);
103 if( pos_week < 1 || pos_week > 4 ) {
104 pos_week = 1;
107 barrylog("Warning: negative week in BYDAY (" << week << "), unsupported by device. Converting to positive week, based on 4 week months: " << pos_week << ".");
108 barryverbose("Record data so far:\n" << m_BarryCal);
110 week = pos_week;
112 return week;
116 uint16_t vCalendar::GetWeekDayIndexFromBYDAY(const std::string& ByDay)
118 std::vector<std::string> v = SplitBYDAY(ByDay);
120 if( !v.size() || v[0].size() < 2 )
121 return 0;
122 return GetWeekDayIndex(v[0].substr(v[0].length()-2).c_str());
125 // month_override is specified in 1-12, or -1 to use m_BarryCal.StartTime
126 uint16_t vCalendar::GetDayOfMonthFromBYMONTHDAY(const ArgMapType &args,
127 int month_override)
129 time_t starttime = m_BarryCal.StartTime.Time;
130 struct tm datestruct;
131 localtime_r(&starttime,&datestruct);
132 if( month_override != -1 )
133 datestruct.tm_mon = month_override - 1;
134 int monthdays = DaysInMonth(datestruct);
136 ArgMapType::const_iterator vi = args.find("BYMONTHDAY");
137 if( vi == args.end() )
138 throw std::logic_error("Called GetDayOfMonthFromBYMONTHDAY() without a BYMONTHDAY");
140 int val = atoi(vi->second.c_str());
141 if( val == 0 ) {
142 barryverbose("Warning: BYMONTHDAY of 0, assuming 1.\n"
143 << "Record data so far:\n" << m_BarryCal);
144 val = 1;
146 else if( val > monthdays ) {
147 barryverbose("Warning: BYMONTHDAY larger than month (" << val << " days). Assuming 1.\nRecord data so far:\n" << m_BarryCal);
148 val = 1;
150 else if( val < 0 ) {
151 // See 4.3.10 RRULE in RFC 2445
152 // negative values mean "last n day of month", so
153 // convert to the fixed day, and then use that positive
154 // value instead, as an approximation
155 int pos_day = monthdays + (val + 1);
156 if( pos_day < 1 || pos_day > monthdays ) {
157 pos_day = 1;
159 barrylog("Warning: negative BYMONTHDAY (" << val << "), unsupported by device. Converting to positive day of month: " << pos_day << ".");
160 barryverbose("Record data so far:\n" << m_BarryCal);
162 val = pos_day;
165 return val;
169 bool vCalendar::HasMultipleVEvents() const
171 int count = 0;
172 b_VFormat *format = const_cast<b_VFormat*>(Format());
173 GList *attrs = format ? b_vformat_get_attributes(format) : 0;
174 for( ; attrs; attrs = attrs->next ) {
175 b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data;
176 if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 &&
177 strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
179 count++;
182 return count > 1;
185 void vCalendar::RecurToVCal()
187 using namespace Barry;
188 using namespace std;
189 Barry::Calendar &cal = m_BarryCal;
191 if( !cal.Recurring )
192 return;
194 vAttrPtr attr = NewAttr("RRULE");
196 switch( cal.RecurringType )
198 case Calendar::Day: // eg. every day
199 AddValue(attr,"FREQ=DAILY");
200 break;
202 case Calendar::MonthByDate: // eg. every month on the 12th
203 // see: DayOfMonth
204 AddValue(attr,"FREQ=MONTHLY");
206 ostringstream oss;
207 oss << "BYMONTHDAY=" << cal.DayOfMonth;
208 AddValue(attr, oss.str().c_str());
210 break;
212 case Calendar::MonthByDay: // eg. every month on 3rd Wed
213 // see: DayOfWeek and WeekOfMonth
214 AddValue(attr, "FREQ=MONTHLY");
215 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
216 ostringstream oss;
217 oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
218 AddValue(attr, oss.str().c_str());
220 break;
222 case Calendar::YearByDate: // eg. every year on March 5
223 // see: DayOfMonth and MonthOfYear
224 AddValue(attr, "FREQ=YEARLY");
226 ostringstream oss;
227 oss << "BYMONTH=" << cal.MonthOfYear;
228 AddValue(attr, oss.str().c_str());
231 ostringstream oss;
232 oss << "BYMONTHDAY=" << cal.DayOfMonth;
233 AddValue(attr, oss.str().c_str());
235 break;
237 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
238 // see: DayOfWeek, WeekOfMonth, and
239 // MonthOfYear
240 AddValue(attr, "FREQ=YEARLY");
241 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
242 ostringstream oss;
243 oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
244 AddValue(attr, oss.str().c_str());
246 oss.str("");
247 oss << "BYMONTH=" << cal.MonthOfYear;
248 AddValue(attr, oss.str().c_str());
250 break;
252 case Calendar::Week: // eg. every week on Mon and Fri
253 // see: WeekDays
254 AddValue(attr, "FREQ=WEEKLY");
256 ostringstream oss;
257 oss << "BYDAY=";
258 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
259 if( cal.WeekDays & bm ) {
260 if( cnt )
261 oss << ",";
262 oss << WeekDays[i];
263 cnt++;
266 AddValue(attr, oss.str().c_str());
268 break;
270 default:
271 throw ConvertError("Unknown RecurringType in Barry Calendar object");
274 // add some common parameters
275 if( cal.Interval > 1 ) {
276 ostringstream oss;
277 oss << "INTERVAL=" << cal.Interval;
278 AddValue(attr, oss.str().c_str());
280 if( !cal.Perpetual ) {
281 ostringstream oss;
282 oss << "UNTIL=" << m_vtc.unix2vtime(&cal.RecurringEndTime.Time);
283 AddValue(attr, oss.str().c_str());
286 AddAttr(attr);
289 bool AllDayEvent;
292 /// Recurring data
294 /// Note: interval can be used on all of these recurring types to
295 /// make it happen "every other time" or more, etc.
298 bool Recurring;
299 RecurringCodeType RecurringType;
300 uint16_t Interval; // must be >= 1
301 time_t RecurringEndTime; // only pertains if Recurring is true
302 // sets the date and time when
303 // recurrence of this appointment
304 // should no longer occur
305 // If a perpetual appointment, this
306 // is 0xFFFFFFFF in the low level data
307 // Instead, set the following flag.
308 bool Perpetual; // if true, this will always recur
309 uint16_t TimeZoneCode; // the time zone originally used
310 // for the recurrence data...
311 // seems to have little use, but
312 // set to your current time zone
313 // as a good default
315 uint16_t // recurring details, depending on type
316 DayOfWeek, // 0-6
317 WeekOfMonth, // 1-5
318 DayOfMonth, // 1-31
319 MonthOfYear; // 1-12
320 unsigned char WeekDays; // bitmask, bit 0 = sunday
322 #define CAL_WD_SUN 0x01
323 #define CAL_WD_MON 0x02
324 #define CAL_WD_TUE 0x04
325 #define CAL_WD_WED 0x08
326 #define CAL_WD_THU 0x10
327 #define CAL_WD_FRI 0x20
328 #define CAL_WD_SAT 0x40
334 namespace {
335 struct tm GetConstLocalTime(const time_t &t)
337 struct tm datestruct;
338 localtime_r(&t, &datestruct);
339 return datestruct;
343 void vCalendar::RecurToBarryCal(vAttr& rrule, time_t starttime)
345 using namespace Barry;
346 using namespace std;
347 Barry::Calendar &cal = m_BarryCal;
348 // Trace trace("vCalendar::RecurToBarryCal");
349 std::map<std::string,unsigned char> pmap;
350 pmap["SU"] = CAL_WD_SUN;
351 pmap["MO"] = CAL_WD_MON;
352 pmap["TU"] = CAL_WD_TUE;
353 pmap["WE"] = CAL_WD_WED;
354 pmap["TH"] = CAL_WD_THU;
355 pmap["FR"] = CAL_WD_FRI;
356 pmap["SA"] = CAL_WD_SAT;
358 const struct tm datestruct = GetConstLocalTime(starttime);
360 int i=0;
361 unsigned int count=0;
362 string val;
363 ArgMapType args;
364 do {
365 val=rrule.GetValue(i++);
366 if(val.length()==0) {
367 break;
369 string n=val.substr(0,val.find("="));
370 string v=val.substr(val.find("=")+1);
371 args[n]=v;
372 // trace.logf("RecurToBarryCal: |%s|%s|",n.c_str(),v.c_str());
373 } while(1);
375 // now process the interval.
376 cal.Recurring=TRUE;
378 if(args.find(string("INTERVAL"))!=args.end()) {
379 int interval = atoi(args["INTERVAL"].c_str());
380 if( interval < 1 ) {
381 // force to at least 1, for math below
382 interval = 1;
384 cal.Interval = interval;
386 else {
387 // default to 1, for the math below.
388 // RecurBase::Clear() does this for us as well, but
389 // best to be safe
390 cal.Interval = 1;
392 if(args.find(string("UNTIL"))!=args.end()) {
393 cal.Perpetual = FALSE;
394 cal.RecurringEndTime.Time = m_vtc.vtime2unix(args["UNTIL"].c_str());
395 if( cal.RecurringEndTime.Time == (time_t)-1 ) {
396 // trace.logf("osync_time_vtime2unix() failed: UNTIL = %s, zoneoffset = %d", args["UNTIL"].c_str(), zoneoffset);
398 } else {
399 // if we do not also have COUNT, then we must be forerver
400 if(args.find(string("COUNT"))==args.end()) {
401 cal.Perpetual=TRUE;
402 } else {
403 // we do have COUNT. This means we won't have UNTIL.
404 // So we need to process the RecurringEndTime from
405 // the current start date. Set the count level to
406 // something other than zero to indicate we need
407 // to process it as the exact end date will
408 // depend upon the frequency.
409 count=atoi(args["COUNT"].c_str());
410 if( count == 0 ) {
411 throw std::runtime_error("Invalid COUNT in recurring rule: " + args["COUNT"]);
416 // we need these if COUNT is true, or if we are a yearly job.
418 // TO-DO: we must process COUNT in terms of an end date if we have it.
420 // warn the user about unsupported arguments
421 CheckUnsupportedArg(args, "BYSETPOS");// FIXME - theorectically supportable
422 CheckUnsupportedArg(args, "BYYEARDAY");
423 CheckUnsupportedArg(args, "BYWEEKNO");
424 CheckUnsupportedArg(args, "WKST");
425 CheckUnsupportedArg(args, "BYSECOND");
426 CheckUnsupportedArg(args, "BYMINUTE");
427 CheckUnsupportedArg(args, "BYHOUR");
429 // Now deal with the freq
431 if(args.find(string("FREQ"))==args.end()) {
432 // trace.logf("RecurToBarryCal: No frequency specified!");
433 return;
436 if(args["FREQ"]==string("DAILY")) {
437 cal.RecurringType=Calendar::Day;
439 if(count) {
440 // add count-1*interval days to find the end time:
441 // i.e. if starting on 2012/01/01 and going
442 // for 3 days, then the last day will be
443 // 2012/01/03.
445 // For intervals, the count is every interval days,
446 // so interval of 2 means 2012/01/01, 2012/01/03, etc.
447 // and the calculation still works.
448 cal.RecurringEndTime.Time =
449 starttime + (count-1) * cal.Interval * 24*60*60;
451 } else if(args["FREQ"]==string("WEEKLY")) {
452 cal.RecurringType=Calendar::Week;
453 // we must have a dayofweek entry
454 if(args.find(string("BYDAY"))!=args.end()) {
455 std::vector<std::string> v=Tokenize(args["BYDAY"]);
456 // iterate along our vector and convert
457 for(unsigned int idx=0;idx<v.size();idx++) {
458 cal.WeekDays|=pmap[v[idx]];
460 } else {
461 // we must have at least one day selected, and if no
462 // BYDAY is selected, use the start time's day
463 cal.WeekDays = pmap[WeekDays[datestruct.tm_wday]];
465 barrylog("Warning: WEEKLY VEVENT without a day selected. Assuming day of start time.");
466 barryverbose("Record data so far:\n" << cal);
469 if(count) {
470 // need to process end date. This is easy
471 // for weeks, as a number of weeks can be
472 // reduced to seconds simply.
473 cal.RecurringEndTime.Time =
474 starttime + (count-1) * cal.Interval * 60*60*24*7;
476 } else if(args["FREQ"]=="MONTHLY") {
477 if(args.find(string("BYMONTHDAY"))!=args.end()) {
478 cal.RecurringType=Calendar::MonthByDate;
479 cal.DayOfMonth=GetDayOfMonthFromBYMONTHDAY(args);
480 } else {
481 if(args.find(string("BYDAY"))!=args.end()) {
482 cal.RecurringType=Calendar::MonthByDay;
483 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
484 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
485 } else {
486 // must have a recurring type, so assume
487 // that monthly means every day on the day
488 // of month specified by starttime
489 cal.RecurringType = Calendar::MonthByDate;
490 cal.DayOfMonth = datestruct.tm_mday;
491 barrylog("Warning: MONTHLY VEVENT without a day type specified (no BYMONTHDAY nor BYDAY). Assuming BYMONTHDAY, using day of start time.");
492 barryverbose("Record data so far:\n" << cal);
495 if(count) {
496 // Nasty. We need to convert to struct tm,
497 // do some modulo-12 addition then back
498 // to time_t
499 struct tm tempdate = datestruct;
501 // now do some modulo-12 on the month and year
502 // We could end up with an illegal date if
503 // the day of month is >28 and the resulting
504 // month falls on a February. We don't need
505 // to worry about day of week as mktime()
506 // clobbers it.
508 // We let mktime() normalize any out of range values.
509 int add = (count-1) * cal.Interval;
510 tempdate.tm_year += (tempdate.tm_mon+add)/12;
511 tempdate.tm_mon = (tempdate.tm_mon+add)%12;
513 // Just in case we're crossing DST boundaries,
514 // add an hour, to make sure we reach the ending
515 // month, in the case of intervals
516 tempdate.tm_hour++;
517 cal.RecurringEndTime.Time = mktime(&tempdate);
519 } else if(args["FREQ"]=="YEARLY") {
520 bool need_assumption = true;
521 if(args.find(string("BYMONTH"))!=args.end()) {
522 cal.MonthOfYear=atoi(args["BYMONTH"].c_str());
523 if( cal.MonthOfYear < 1 || cal.MonthOfYear > 12 ) {
524 // doh... default to starttime's month
525 cal.MonthOfYear = datestruct.tm_mon + 1;
527 if(args.find(string("BYMONTHDAY"))!=args.end()) {
528 cal.RecurringType=Calendar::YearByDate;
529 cal.DayOfMonth=GetDayOfMonthFromBYMONTHDAY(args, cal.MonthOfYear);
530 need_assumption = false;
531 } else {
532 if(args.find(string("BYDAY"))!=args.end()) {
533 cal.RecurringType=Calendar::YearByDay;
534 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
535 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
536 need_assumption = false;
537 } else {
538 // fall through to assumption below...
543 if( need_assumption ) {
544 // otherwise use the start date and translate
545 // to a BYMONTHDAY.
546 // cal.StartTime has already been processed
547 // when we get here we need month of year,
548 // and day of month.
549 cal.RecurringType=Calendar::YearByDate;
550 cal.MonthOfYear=datestruct.tm_mon;
551 cal.DayOfMonth=datestruct.tm_mday;
552 barrylog("Warning: YEARLY VEVENT without a day type specified (no BYMONTHDAY nor BYDAY). Assuming BYMONTHDAY, using day and month of start time.");
553 barryverbose("Record data so far:\n" << cal);
555 if(count) {
556 // convert to struct tm, then simply add to the year.
558 // Note: intervals do work in the device firmware,
559 // but not all of the devices allow you to edit it
560 // with their GUI... hmmm... oh well, allow it
561 // anyway, and do the multiplication below.
562 struct tm tempdate = datestruct;
563 tempdate.tm_year += (count-1) * cal.Interval;
564 cal.RecurringEndTime.Time = mktime(&tempdate);
568 // unsigned char WeekDays; // bitmask, bit 0 = sunday
570 // #define CAL_WD_SUN 0x01
571 // #define CAL_WD_MON 0x02
572 // #define CAL_WD_TUE 0x04
573 // #define CAL_WD_WED 0x08
574 // #define CAL_WD_THU 0x10
575 // #define CAL_WD_FRI 0x20
576 // #define CAL_WD_SAT 0x40
579 // Main conversion routine for converting from Barry::Calendar to
580 // a vCalendar string of data.
581 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
583 // Trace trace("vCalendar::ToVCal");
584 std::ostringstream oss;
585 cal.Dump(oss);
586 // trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
588 // start fresh
589 Clear();
590 SetFormat( b_vformat_new() );
591 if( !Format() )
592 throw ConvertError("resource error allocating vformat");
594 // store the Barry object we're working with
595 m_BarryCal = cal;
597 // RFC section 4.8.7.2 requires DTSTAMP in all VEVENT, VTODO,
598 // VJOURNAL, and VFREEBUSY calendar components, and it must be
599 // in UTC. DTSTAMP holds the timestamp of when the iCal object itself
600 // was created, not when the object was created in the device or app.
601 // So, find out what time it is "now".
602 time_t now = time(NULL);
604 // begin building vCalendar data
605 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
606 AddAttr(NewAttr("BEGIN", "VEVENT"));
607 AddAttr(NewAttr("DTSTAMP", m_vtc.unix2vtime(&now).c_str())); // see note above
608 AddAttr(NewAttr("SEQUENCE", "0"));
609 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
610 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
611 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
613 string start(m_vtc.unix2vtime(&cal.StartTime.Time));
614 string end(m_vtc.unix2vtime(&cal.EndTime.Time));
615 string notify(m_vtc.unix2vtime(&cal.NotificationTime.Time));
617 // if an all day event, only print the date parts of the string
618 if( cal.AllDayEvent && start.find('T') != string::npos ) {
619 // truncate start date
620 start = start.substr(0, start.find('T'));
622 // create end date 1 day in future
623 time_t end_t = cal.StartTime.Time + 24 * 60 * 60;
624 end = m_vtc.unix2vtime(&end_t);
625 end = end.substr(0, end.find('T'));
628 AddAttr(NewAttr("DTSTART", start.c_str()));
629 AddAttr(NewAttr("DTEND", end.c_str()));
630 // FIXME - add a truly globally unique "UID" string?
633 AddAttr(NewAttr("BEGIN", "VALARM"));
634 AddAttr(NewAttr("ACTION", "AUDIO"));
636 // notify must be UTC, when specified in DATE-TIME
637 vAttrPtr trigger = NewAttr("TRIGGER", notify.c_str());
638 AddParam(trigger, "VALUE", "DATE-TIME");
639 AddAttr(trigger);
641 AddAttr(NewAttr("END", "VALARM"));
644 if( cal.Recurring ) {
645 RecurToVCal();
648 AddAttr(NewAttr("END", "VEVENT"));
650 // generate the raw VCALENDAR data
651 m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20);
652 m_vCalData = m_gCalData;
654 // trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
655 return m_vCalData;
658 // Main conversion routine for converting from vCalendar data string
659 // to a Barry::Calendar object.
660 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
662 using namespace std;
664 // Trace trace("vCalendar::ToBarry");
665 // trace.logf("ToBarry, working on vcal data: %s", vcal);
667 // we only handle vCalendar data with one vevent block
668 if( HasMultipleVEvents() )
669 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
671 // start fresh
672 Clear();
674 // store the vCalendar raw data
675 m_vCalData = vcal;
677 // create format parser structures
678 SetFormat( b_vformat_new_from_string(vcal) );
679 if( !Format() )
680 throw ConvertError("resource error allocating vformat");
682 string start = GetAttr("DTSTART", "/vevent");
683 // trace.logf("DTSTART attr retrieved: %s", start.c_str());
684 string end = GetAttr("DTEND", "/vevent");
685 // trace.logf("DTEND attr retrieved: %s", end.c_str());
686 string subject = GetAttr("SUMMARY", "/vevent");
687 // trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
688 if( subject.size() == 0 ) {
689 subject = "<blank subject>";
690 // trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
692 vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
694 string location = GetAttr("LOCATION", "/vevent");
695 // trace.logf("LOCATION attr retrieved: %s", location.c_str());
697 string notes = GetAttr("DESCRIPTION", "/vevent");
698 // trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
700 vAttr rrule = GetAttrObj("RRULE",0,"/vevent");
704 // Now, run checks and convert into Barry object
708 // FIXME - we are assuming that any non-UTC timestamps
709 // in the vcalendar record will be in the current timezone...
710 // This is wrong! fix this later.
712 // Also, we currently ignore any time zone
713 // parameters that might be in the vcalendar format... this
714 // must be fixed.
716 Barry::Calendar &rec = m_BarryCal;
717 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
719 if( !start.size() )
720 throw ConvertError("Blank DTSTART");
721 rec.StartTime.Time = m_vtc.vtime2unix(start.c_str());
723 if( !end.size() ) {
724 // DTEND is actually optional! According to the
725 // RFC, a DTSTART with no DTEND should be treated
726 // like a "special day" like an anniversary, which occupies
727 // no time.
729 // Since the Blackberry doesn't really map well to this
730 // case, we'll set the end time to 1 day past start.
732 rec.EndTime.Time = rec.StartTime.Time + 24 * 60 * 60;
734 else {
735 rec.EndTime.Time = m_vtc.vtime2unix(end.c_str());
738 // check for "all day event" which is specified by a DTSTART
739 // and a DTEND with no times, and one day apart
740 if( start.find('T') == string::npos && end.size() &&
741 end.find('T') == string::npos &&
742 (rec.EndTime.Time - rec.StartTime.Time) == 24 * 60 * 60 )
744 rec.AllDayEvent = true;
747 rec.Subject = subject;
748 rec.Location = location;
749 rec.Notes = notes;
751 if(rrule.Get()) {
752 RecurToBarryCal(rrule, rec.StartTime.Time);
755 // convert trigger time into notification time
756 // assume no notification, by default
757 rec.NotificationTime.Time = 0;
758 if( trigger_obj.Get() ) {
759 string trigger_type = trigger_obj.GetParam("VALUE");
760 string trigger = trigger_obj.GetValue();
762 if( trigger.size() == 0 ) {
763 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
765 else if( trigger_type == "DATE-TIME" ) {
766 rec.NotificationTime.Time = m_vtc.vtime2unix(trigger.c_str());
768 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
769 // default is DURATION (RFC 4.8.6.3)
770 string related = trigger_obj.GetParam("RELATED");
772 // default to relative to start time
773 time_t *relative = &rec.StartTime.Time;
774 if( related == "END" )
775 relative = &rec.EndTime.Time;
777 rec.NotificationTime.Time = *relative + m_vtc.alarmduration2sec(trigger.c_str());
779 else {
780 throw ConvertError("Unknown TRIGGER VALUE");
783 else {
784 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
787 std::ostringstream oss;
788 m_BarryCal.Dump(oss);
789 // trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
790 return m_BarryCal;
793 // Transfers ownership of m_gCalData to the caller.
794 char* vCalendar::ExtractVCal()
796 char *ret = m_gCalData;
797 m_gCalData = 0;
798 return ret;
801 void vCalendar::Clear()
803 vBase::Clear();
804 m_vCalData.clear();
805 m_BarryCal.Clear();
807 if( m_gCalData ) {
808 g_free(m_gCalData);
809 m_gCalData = 0;
813 }} // namespace Barry::Sync