menu: added new Keywords tag to .desktop files
[barry.git] / src / vevent.cc
blob1a12361d545178901c0ddc86f38464cfea7c0859
1 ///
2 /// \file vevent.cc
3 /// Conversion routines for vevents (VCALENDAR, etc)
4 ///
6 /*
7 Copyright (C) 2006-2013, 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 "i18n.h"
25 #include "vevent.h"
26 //#include "trace.h"
27 #include "log.h"
28 #include "time.h"
29 #include "common.h"
30 #include <stdint.h>
31 #include <glib.h>
32 #include <strings.h>
33 #include <stdlib.h>
34 #include <sstream>
35 #include <string>
37 using namespace std;
39 namespace Barry { namespace Sync {
41 //////////////////////////////////////////////////////////////////////////////
42 // vCalendar
44 vCalendar::vCalendar(vTimeConverter &vtc)
45 : m_vtc(vtc)
46 , m_gCalData(0)
50 vCalendar::~vCalendar()
52 if( m_gCalData ) {
53 g_free(m_gCalData);
57 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
59 uint16_t vCalendar::GetWeekDayIndex(const char *dayname)
61 for( int i = 0; i < 7; i++ ) {
62 if( strcasecmp(dayname, WeekDays[i]) == 0 )
63 return i;
65 return 0;
68 void vCalendar::CheckUnsupportedArg(const ArgMapType &args,
69 const std::string &name)
71 if( args.find(name) != args.end() ) {
72 barrylog(string_vprintf(_("ERROR: recurrence rule contains %s, unsupported by Barry. MIME conversion will be incorrect."), name.c_str()));
73 barryverbose(_("Record data so far:\n") << m_BarryCal);
77 std::vector<std::string> vCalendar::SplitBYDAY(const std::string &ByDay)
79 std::vector<std::string> v = Tokenize(ByDay);
81 // BlackBerry recursion only supports one specification...
82 // i.e. only 3rd Wed of month, not 3rd Wed and 2nd Fri of month...
83 // if there's more than one item in v, warn the user, and just
84 // use the first item
85 if( v.size() > 1 ) {
86 barrylog(string_vprintf(_("Warning: multiple items in BYDAY, not supported by device (%s). Using only the first item."), ByDay.c_str()));
87 barryverbose(_("Record data so far:\n") << m_BarryCal);
90 return v;
93 uint16_t vCalendar::GetMonthWeekNumFromBYDAY(const std::string& ByDay)
95 std::vector<std::string> v = SplitBYDAY(ByDay);
97 if( !v.size() || v[0].size() < 2 ) {
98 return 0;
100 else {
101 int week = atoi(v[0].substr(0,v[0].length()-2).c_str());
102 if( week < 0 ) {
103 // assume 4 weeks per month
104 int pos_week = 4 + (week + 1);
105 if( pos_week < 1 || pos_week > 4 ) {
106 pos_week = 1;
109 barrylog(string_vprintf(_("Warning: negative week in BYDAY (%d), unsupported by device. Converting to positive week, based on 4 week months: %d."), week, pos_week));
110 barryverbose(_("Record data so far:\n") << m_BarryCal);
112 week = pos_week;
114 return week;
118 uint16_t vCalendar::GetWeekDayIndexFromBYDAY(const std::string& ByDay)
120 std::vector<std::string> v = SplitBYDAY(ByDay);
122 if( !v.size() || v[0].size() < 2 )
123 return 0;
124 return GetWeekDayIndex(v[0].substr(v[0].length()-2).c_str());
127 // month_override is specified in 1-12, or -1 to use m_BarryCal.StartTime
128 uint16_t vCalendar::GetDayOfMonthFromBYMONTHDAY(const ArgMapType &args,
129 int month_override)
131 time_t starttime = m_BarryCal.StartTime.Time;
132 struct tm datestruct;
133 localtime_r(&starttime,&datestruct);
134 if( month_override != -1 )
135 datestruct.tm_mon = month_override - 1;
136 int monthdays = DaysInMonth(datestruct);
138 ArgMapType::const_iterator vi = args.find("BYMONTHDAY");
139 if( vi == args.end() )
140 throw std::logic_error(_("Called GetDayOfMonthFromBYMONTHDAY() without a BYMONTHDAY"));
142 int val = atoi(vi->second.c_str());
143 if( val == 0 ) {
144 barryverbose(_("Warning: BYMONTHDAY of 0, assuming 1.\n")
145 << _("Record data so far:\n") << m_BarryCal);
146 val = 1;
148 else if( val > monthdays ) {
149 barryverbose(string_vprintf(_("Warning: BYMONTHDAY larger than month (%d days). Assuming 1.\n"), val));
150 barryverbose(_("Record data so far:\n") << m_BarryCal);
151 val = 1;
153 else if( val < 0 ) {
154 // See 4.3.10 RRULE in RFC 2445
155 // negative values mean "last n day of month", so
156 // convert to the fixed day, and then use that positive
157 // value instead, as an approximation
158 int pos_day = monthdays + (val + 1);
159 if( pos_day < 1 || pos_day > monthdays ) {
160 pos_day = 1;
162 barrylog(string_vprintf(_("Warning: negative BYMONTHDAY (%d), unsupported by device. Converting to positive day of month: %d."), val, pos_day));
163 barryverbose(_("Record data so far:\n") << m_BarryCal);
165 val = pos_day;
168 return val;
172 bool vCalendar::HasMultipleVEvents() const
174 int count = 0;
175 b_VFormat *format = const_cast<b_VFormat*>(Format());
176 GList *attrs = format ? b_vformat_get_attributes(format) : 0;
177 for( ; attrs; attrs = attrs->next ) {
178 b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data;
179 if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 &&
180 strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
182 count++;
185 return count > 1;
188 void vCalendar::RecurToVCal()
190 using namespace Barry;
191 using namespace std;
192 Barry::Calendar &cal = m_BarryCal;
194 if( !cal.Recurring )
195 return;
197 vAttrPtr attr = NewAttr("RRULE");
199 switch( cal.RecurringType )
201 case Calendar::Day: // eg. every day
202 AddValue(attr,"FREQ=DAILY");
203 break;
205 case Calendar::MonthByDate: // eg. every month on the 12th
206 // see: DayOfMonth
207 AddValue(attr,"FREQ=MONTHLY");
209 ostringstream oss;
210 oss << "BYMONTHDAY=" << cal.DayOfMonth;
211 AddValue(attr, oss.str().c_str());
213 break;
215 case Calendar::MonthByDay: // eg. every month on 3rd Wed
216 // see: DayOfWeek and WeekOfMonth
217 AddValue(attr, "FREQ=MONTHLY");
218 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
219 ostringstream oss;
220 oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
221 AddValue(attr, oss.str().c_str());
223 break;
225 case Calendar::YearByDate: // eg. every year on March 5
226 // see: DayOfMonth and MonthOfYear
227 AddValue(attr, "FREQ=YEARLY");
229 ostringstream oss;
230 oss << "BYMONTH=" << cal.MonthOfYear;
231 AddValue(attr, oss.str().c_str());
234 ostringstream oss;
235 oss << "BYMONTHDAY=" << cal.DayOfMonth;
236 AddValue(attr, oss.str().c_str());
238 break;
240 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
241 // see: DayOfWeek, WeekOfMonth, and
242 // MonthOfYear
243 AddValue(attr, "FREQ=YEARLY");
244 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
245 ostringstream oss;
246 oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
247 AddValue(attr, oss.str().c_str());
249 oss.str("");
250 oss << "BYMONTH=" << cal.MonthOfYear;
251 AddValue(attr, oss.str().c_str());
253 break;
255 case Calendar::Week: // eg. every week on Mon and Fri
256 // see: WeekDays
257 AddValue(attr, "FREQ=WEEKLY");
259 ostringstream oss;
260 oss << "BYDAY=";
261 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
262 if( cal.WeekDays & bm ) {
263 if( cnt )
264 oss << ",";
265 oss << WeekDays[i];
266 cnt++;
269 AddValue(attr, oss.str().c_str());
271 break;
273 default:
274 throw ConvertError(_("Unknown RecurringType in Barry Calendar object"));
277 // add some common parameters
278 if( cal.Interval > 1 ) {
279 ostringstream oss;
280 oss << "INTERVAL=" << cal.Interval;
281 AddValue(attr, oss.str().c_str());
283 if( !cal.Perpetual ) {
284 ostringstream oss;
285 oss << "UNTIL=" << m_vtc.unix2vtime(&cal.RecurringEndTime.Time);
286 AddValue(attr, oss.str().c_str());
289 AddAttr(attr);
292 bool AllDayEvent;
295 /// Recurring data
297 /// Note: interval can be used on all of these recurring types to
298 /// make it happen "every other time" or more, etc.
301 bool Recurring;
302 RecurringCodeType RecurringType;
303 uint16_t Interval; // must be >= 1
304 time_t RecurringEndTime; // only pertains if Recurring is true
305 // sets the date and time when
306 // recurrence of this appointment
307 // should no longer occur
308 // If a perpetual appointment, this
309 // is 0xFFFFFFFF in the low level data
310 // Instead, set the following flag.
311 bool Perpetual; // if true, this will always recur
312 uint16_t TimeZoneCode; // the time zone originally used
313 // for the recurrence data...
314 // seems to have little use, but
315 // set to your current time zone
316 // as a good default
318 uint16_t // recurring details, depending on type
319 DayOfWeek, // 0-6
320 WeekOfMonth, // 1-5
321 DayOfMonth, // 1-31
322 MonthOfYear; // 1-12
323 unsigned char WeekDays; // bitmask, bit 0 = sunday
325 #define CAL_WD_SUN 0x01
326 #define CAL_WD_MON 0x02
327 #define CAL_WD_TUE 0x04
328 #define CAL_WD_WED 0x08
329 #define CAL_WD_THU 0x10
330 #define CAL_WD_FRI 0x20
331 #define CAL_WD_SAT 0x40
337 namespace {
338 struct tm GetConstLocalTime(const time_t &t)
340 struct tm datestruct;
341 localtime_r(&t, &datestruct);
342 return datestruct;
346 void vCalendar::RecurToBarryCal(vAttr& rrule, time_t starttime)
348 using namespace Barry;
349 using namespace std;
350 Barry::Calendar &cal = m_BarryCal;
351 // Trace trace("vCalendar::RecurToBarryCal");
352 std::map<std::string,unsigned char> pmap;
353 pmap["SU"] = CAL_WD_SUN;
354 pmap["MO"] = CAL_WD_MON;
355 pmap["TU"] = CAL_WD_TUE;
356 pmap["WE"] = CAL_WD_WED;
357 pmap["TH"] = CAL_WD_THU;
358 pmap["FR"] = CAL_WD_FRI;
359 pmap["SA"] = CAL_WD_SAT;
361 const struct tm datestruct = GetConstLocalTime(starttime);
363 int i=0;
364 unsigned int count=0;
365 string val;
366 ArgMapType args;
367 do {
368 val=rrule.GetValue(i++);
369 if(val.length()==0) {
370 break;
372 string n=val.substr(0,val.find("="));
373 string v=val.substr(val.find("=")+1);
374 args[n]=v;
375 // trace.logf("RecurToBarryCal: |%s|%s|",n.c_str(),v.c_str());
376 } while(1);
378 // now process the interval.
379 cal.Recurring=TRUE;
381 if(args.find(string("INTERVAL"))!=args.end()) {
382 int interval = atoi(args["INTERVAL"].c_str());
383 if( interval < 1 ) {
384 // force to at least 1, for math below
385 interval = 1;
387 cal.Interval = interval;
389 else {
390 // default to 1, for the math below.
391 // RecurBase::Clear() does this for us as well, but
392 // best to be safe
393 cal.Interval = 1;
395 if(args.find(string("UNTIL"))!=args.end()) {
396 cal.Perpetual = FALSE;
397 cal.RecurringEndTime.Time = m_vtc.vtime2unix(args["UNTIL"].c_str());
398 if( cal.RecurringEndTime.Time == (time_t)-1 ) {
399 // trace.logf("osync_time_vtime2unix() failed: UNTIL = %s, zoneoffset = %d", args["UNTIL"].c_str(), zoneoffset);
401 } else {
402 // if we do not also have COUNT, then we must be forerver
403 if(args.find(string("COUNT"))==args.end()) {
404 cal.Perpetual=TRUE;
405 } else {
406 // we do have COUNT. This means we won't have UNTIL.
407 // So we need to process the RecurringEndTime from
408 // the current start date. Set the count level to
409 // something other than zero to indicate we need
410 // to process it as the exact end date will
411 // depend upon the frequency.
412 count=atoi(args["COUNT"].c_str());
413 if( count == 0 ) {
414 throw std::runtime_error(_("Invalid COUNT in recurring rule: ") + args["COUNT"]);
419 // we need these if COUNT is true, or if we are a yearly job.
421 // TO-DO: we must process COUNT in terms of an end date if we have it.
423 // warn the user about unsupported arguments
424 CheckUnsupportedArg(args, "BYSETPOS");// FIXME - theorectically supportable
425 CheckUnsupportedArg(args, "BYYEARDAY");
426 CheckUnsupportedArg(args, "BYWEEKNO");
427 CheckUnsupportedArg(args, "WKST");
428 CheckUnsupportedArg(args, "BYSECOND");
429 CheckUnsupportedArg(args, "BYMINUTE");
430 CheckUnsupportedArg(args, "BYHOUR");
432 // Now deal with the freq
434 if(args.find(string("FREQ"))==args.end()) {
435 // trace.logf("RecurToBarryCal: No frequency specified!");
436 return;
439 if(args["FREQ"]==string("DAILY")) {
440 cal.RecurringType=Calendar::Day;
442 if(count) {
443 // add count-1*interval days to find the end time:
444 // i.e. if starting on 2012/01/01 and going
445 // for 3 days, then the last day will be
446 // 2012/01/03.
448 // For intervals, the count is every interval days,
449 // so interval of 2 means 2012/01/01, 2012/01/03, etc.
450 // and the calculation still works.
451 cal.RecurringEndTime.Time =
452 starttime + (count-1) * cal.Interval * 24*60*60;
454 } else if(args["FREQ"]==string("WEEKLY")) {
455 cal.RecurringType=Calendar::Week;
456 // we must have a dayofweek entry
457 if(args.find(string("BYDAY"))!=args.end()) {
458 std::vector<std::string> v=Tokenize(args["BYDAY"]);
459 // iterate along our vector and convert
460 for(unsigned int idx=0;idx<v.size();idx++) {
461 cal.WeekDays|=pmap[v[idx]];
463 } else {
464 // we must have at least one day selected, and if no
465 // BYDAY is selected, use the start time's day
466 cal.WeekDays = pmap[WeekDays[datestruct.tm_wday]];
468 barrylog(_("Warning: WEEKLY VEVENT without a day selected. Assuming day of start time."));
469 barryverbose(_("Record data so far:\n") << cal);
472 if(count) {
473 // need to process end date. This is easy
474 // for weeks, as a number of weeks can be
475 // reduced to seconds simply.
476 cal.RecurringEndTime.Time =
477 starttime + (count-1) * cal.Interval * 60*60*24*7;
479 } else if(args["FREQ"]=="MONTHLY") {
480 if(args.find(string("BYMONTHDAY"))!=args.end()) {
481 cal.RecurringType=Calendar::MonthByDate;
482 cal.DayOfMonth=GetDayOfMonthFromBYMONTHDAY(args);
483 } else {
484 if(args.find(string("BYDAY"))!=args.end()) {
485 cal.RecurringType=Calendar::MonthByDay;
486 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
487 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
488 } else {
489 // must have a recurring type, so assume
490 // that monthly means every day on the day
491 // of month specified by starttime
492 cal.RecurringType = Calendar::MonthByDate;
493 cal.DayOfMonth = datestruct.tm_mday;
494 barrylog(_("Warning: MONTHLY VEVENT without a day type specified (no BYMONTHDAY nor BYDAY). Assuming BYMONTHDAY, using day of start time."));
495 barryverbose(_("Record data so far:\n") << cal);
498 if(count) {
499 // Nasty. We need to convert to struct tm,
500 // do some modulo-12 addition then back
501 // to time_t
502 struct tm tempdate = datestruct;
504 // now do some modulo-12 on the month and year
505 // We could end up with an illegal date if
506 // the day of month is >28 and the resulting
507 // month falls on a February. We don't need
508 // to worry about day of week as mktime()
509 // clobbers it.
511 // We let mktime() normalize any out of range values.
512 int add = (count-1) * cal.Interval;
513 tempdate.tm_year += (tempdate.tm_mon+add)/12;
514 tempdate.tm_mon = (tempdate.tm_mon+add)%12;
516 // Just in case we're crossing DST boundaries,
517 // add an hour, to make sure we reach the ending
518 // month, in the case of intervals
519 tempdate.tm_hour++;
520 cal.RecurringEndTime.Time = mktime(&tempdate);
522 } else if(args["FREQ"]=="YEARLY") {
523 bool need_assumption = true;
524 if(args.find(string("BYMONTH"))!=args.end()) {
525 cal.MonthOfYear=atoi(args["BYMONTH"].c_str());
526 if( cal.MonthOfYear < 1 || cal.MonthOfYear > 12 ) {
527 // doh... default to starttime's month
528 cal.MonthOfYear = datestruct.tm_mon + 1;
530 if(args.find(string("BYMONTHDAY"))!=args.end()) {
531 cal.RecurringType=Calendar::YearByDate;
532 cal.DayOfMonth=GetDayOfMonthFromBYMONTHDAY(args, cal.MonthOfYear);
533 need_assumption = false;
534 } else {
535 if(args.find(string("BYDAY"))!=args.end()) {
536 cal.RecurringType=Calendar::YearByDay;
537 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
538 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
539 need_assumption = false;
540 } else {
541 // fall through to assumption below...
546 if( need_assumption ) {
547 // otherwise use the start date and translate
548 // to a BYMONTHDAY.
549 // cal.StartTime has already been processed
550 // when we get here we need month of year,
551 // and day of month.
552 cal.RecurringType=Calendar::YearByDate;
553 cal.MonthOfYear=datestruct.tm_mon;
554 cal.DayOfMonth=datestruct.tm_mday;
555 barrylog(_("Warning: YEARLY VEVENT without a day type specified (no BYMONTHDAY nor BYDAY). Assuming BYMONTHDAY, using day and month of start time."));
556 barryverbose(_("Record data so far:\n") << cal);
558 if(count) {
559 // convert to struct tm, then simply add to the year.
561 // Note: intervals do work in the device firmware,
562 // but not all of the devices allow you to edit it
563 // with their GUI... hmmm... oh well, allow it
564 // anyway, and do the multiplication below.
565 struct tm tempdate = datestruct;
566 tempdate.tm_year += (count-1) * cal.Interval;
567 cal.RecurringEndTime.Time = mktime(&tempdate);
571 // unsigned char WeekDays; // bitmask, bit 0 = sunday
573 // #define CAL_WD_SUN 0x01
574 // #define CAL_WD_MON 0x02
575 // #define CAL_WD_TUE 0x04
576 // #define CAL_WD_WED 0x08
577 // #define CAL_WD_THU 0x10
578 // #define CAL_WD_FRI 0x20
579 // #define CAL_WD_SAT 0x40
582 // Main conversion routine for converting from Barry::Calendar to
583 // a vCalendar string of data.
584 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
586 // Trace trace("vCalendar::ToVCal");
587 std::ostringstream oss;
588 cal.Dump(oss);
589 // trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
591 // start fresh
592 Clear();
593 SetFormat( b_vformat_new() );
594 if( !Format() )
595 throw ConvertError(_("resource error allocating vformat"));
597 // store the Barry object we're working with
598 m_BarryCal = cal;
600 // RFC section 4.8.7.2 requires DTSTAMP in all VEVENT, VTODO,
601 // VJOURNAL, and VFREEBUSY calendar components, and it must be
602 // in UTC. DTSTAMP holds the timestamp of when the iCal object itself
603 // was created, not when the object was created in the device or app.
604 // So, find out what time it is "now".
605 time_t now = time(NULL);
607 // begin building vCalendar data
608 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
609 AddAttr(NewAttr("BEGIN", "VEVENT"));
610 AddAttr(NewAttr("DTSTAMP", m_vtc.unix2vtime(&now).c_str())); // see note above
611 AddAttr(NewAttr("SEQUENCE", "0"));
612 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
613 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
614 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
616 string start(m_vtc.unix2vtime(&cal.StartTime.Time));
617 string end(m_vtc.unix2vtime(&cal.EndTime.Time));
618 string notify(m_vtc.unix2vtime(&cal.NotificationTime.Time));
620 // if an all day event, only print the date parts of the string
621 if( cal.AllDayEvent && start.find('T') != string::npos ) {
622 // truncate start date
623 start = start.substr(0, start.find('T'));
625 // create end date 1 day in future
626 time_t end_t = cal.StartTime.Time + 24 * 60 * 60;
627 end = m_vtc.unix2vtime(&end_t);
628 end = end.substr(0, end.find('T'));
631 AddAttr(NewAttr("DTSTART", start.c_str()));
632 AddAttr(NewAttr("DTEND", end.c_str()));
633 // FIXME - add a truly globally unique "UID" string?
636 AddAttr(NewAttr("BEGIN", "VALARM"));
637 AddAttr(NewAttr("ACTION", "AUDIO"));
639 // notify must be UTC, when specified in DATE-TIME
640 vAttrPtr trigger = NewAttr("TRIGGER", notify.c_str());
641 AddParam(trigger, "VALUE", "DATE-TIME");
642 AddAttr(trigger);
644 AddAttr(NewAttr("END", "VALARM"));
647 if( cal.Recurring ) {
648 RecurToVCal();
651 AddAttr(NewAttr("END", "VEVENT"));
653 // generate the raw VCALENDAR data
654 m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20);
655 m_vCalData = m_gCalData;
657 // trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
658 return m_vCalData;
661 // Main conversion routine for converting from vCalendar data string
662 // to a Barry::Calendar object.
663 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
665 using namespace std;
667 // Trace trace("vCalendar::ToBarry");
668 // trace.logf("ToBarry, working on vcal data: %s", vcal);
670 // we only handle vCalendar data with one vevent block
671 if( HasMultipleVEvents() )
672 throw ConvertError(_("vCalendar data contains more than one VEVENT block, unsupported"));
674 // start fresh
675 Clear();
677 // store the vCalendar raw data
678 m_vCalData = vcal;
680 // create format parser structures
681 SetFormat( b_vformat_new_from_string(vcal) );
682 if( !Format() )
683 throw ConvertError(_("resource error allocating vformat"));
685 string start = GetAttr("DTSTART", "/vevent");
686 // trace.logf("DTSTART attr retrieved: %s", start.c_str());
687 string end = GetAttr("DTEND", "/vevent");
688 // trace.logf("DTEND attr retrieved: %s", end.c_str());
689 string subject = GetAttr("SUMMARY", "/vevent");
690 // trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
691 if( subject.size() == 0 ) {
692 subject = "<blank subject>";
693 // trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
695 vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
697 string location = GetAttr("LOCATION", "/vevent");
698 // trace.logf("LOCATION attr retrieved: %s", location.c_str());
700 string notes = GetAttr("DESCRIPTION", "/vevent");
701 // trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
703 vAttr rrule = GetAttrObj("RRULE",0,"/vevent");
707 // Now, run checks and convert into Barry object
711 // FIXME - we are assuming that any non-UTC timestamps
712 // in the vcalendar record will be in the current timezone...
713 // This is wrong! fix this later.
715 // Also, we currently ignore any time zone
716 // parameters that might be in the vcalendar format... this
717 // must be fixed.
719 Barry::Calendar &rec = m_BarryCal;
720 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
722 if( !start.size() )
723 throw ConvertError(_("Blank DTSTART"));
724 rec.StartTime.Time = m_vtc.vtime2unix(start.c_str());
726 if( !end.size() ) {
727 // DTEND is actually optional! According to the
728 // RFC, a DTSTART with no DTEND should be treated
729 // like a "special day" like an anniversary, which occupies
730 // no time.
732 // Since the Blackberry doesn't really map well to this
733 // case, we'll set the end time to 1 day past start.
735 rec.EndTime.Time = rec.StartTime.Time + 24 * 60 * 60;
737 else {
738 rec.EndTime.Time = m_vtc.vtime2unix(end.c_str());
741 // check for "all day event" which is specified by a DTSTART
742 // and a DTEND with no times, and one day apart
743 if( start.find('T') == string::npos && end.size() &&
744 end.find('T') == string::npos &&
745 (rec.EndTime.Time - rec.StartTime.Time) == 24 * 60 * 60 )
747 rec.AllDayEvent = true;
750 rec.Subject = subject;
751 rec.Location = location;
752 rec.Notes = notes;
754 if(rrule.Get()) {
755 RecurToBarryCal(rrule, rec.StartTime.Time);
758 // convert trigger time into notification time
759 // assume no notification, by default
760 rec.NotificationTime.Time = 0;
761 if( trigger_obj.Get() ) {
762 string trigger_type = trigger_obj.GetParam("VALUE");
763 string trigger = trigger_obj.GetValue();
765 if( trigger.size() == 0 ) {
766 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
768 else if( trigger_type == "DATE-TIME" ) {
769 rec.NotificationTime.Time = m_vtc.vtime2unix(trigger.c_str());
771 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
772 // default is DURATION (RFC 4.8.6.3)
773 string related = trigger_obj.GetParam("RELATED");
775 // default to relative to start time
776 time_t *relative = &rec.StartTime.Time;
777 if( related == "END" )
778 relative = &rec.EndTime.Time;
780 rec.NotificationTime.Time = *relative + m_vtc.alarmduration2sec(trigger.c_str());
782 else {
783 throw ConvertError(_("Unknown TRIGGER VALUE"));
786 else {
787 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
790 std::ostringstream oss;
791 m_BarryCal.Dump(oss);
792 // trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
793 return m_BarryCal;
796 // Transfers ownership of m_gCalData to the caller.
797 char* vCalendar::ExtractVCal()
799 char *ret = m_gCalData;
800 m_gCalData = 0;
801 return ret;
804 void vCalendar::Clear()
806 vBase::Clear();
807 m_vCalData.clear();
808 m_BarryCal.Clear();
810 if( m_gCalData ) {
811 g_free(m_gCalData);
812 m_gCalData = 0;
816 }} // namespace Barry::Sync