3 // Conversion routines for vevents (VCALENDAR, etc)
7 Copyright (C) 2006-2010, 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.
35 namespace Barry
{ namespace Sync
{
37 //////////////////////////////////////////////////////////////////////////////
40 vCalendar::vCalendar(vTimeConverter
&vtc
)
46 vCalendar::~vCalendar()
53 const char *vCalendar::WeekDays
[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
55 unsigned short vCalendar::GetWeekDayIndex(const char *dayname
)
57 for( int i
= 0; i
< 7; i
++ ) {
58 if( strcasecmp(dayname
, WeekDays
[i
]) == 0 )
64 unsigned short vCalendar::GetMonthWeekNumFromBYDAY(const std::string
& ByDay
)
66 return atoi(ByDay
.substr(0,ByDay
.length()-2).c_str());
69 unsigned short vCalendar::GetWeekDayIndexFromBYDAY(const std::string
& ByDay
)
71 return GetWeekDayIndex(ByDay
.substr(ByDay
.length()-2).c_str());
75 bool vCalendar::HasMultipleVEvents() const
78 b_VFormat
*format
= const_cast<b_VFormat
*>(Format());
79 GList
*attrs
= format
? b_vformat_get_attributes(format
) : 0;
80 for( ; attrs
; attrs
= attrs
->next
) {
81 b_VFormatAttribute
*attr
= (b_VFormatAttribute
*) attrs
->data
;
82 if( strcasecmp(b_vformat_attribute_get_name(attr
), "BEGIN") == 0 &&
83 strcasecmp(b_vformat_attribute_get_nth_value(attr
, 0), "VEVENT") == 0 )
91 void vCalendar::RecurToVCal()
93 using namespace Barry
;
95 Barry::Calendar
&cal
= m_BarryCal
;
100 vAttrPtr attr
= NewAttr("RRULE");
102 switch( cal
.RecurringType
)
104 case Calendar::Day
: // eg. every day
105 AddValue(attr
,"FREQ=DAILY");
108 case Calendar::MonthByDate
: // eg. every month on the 12th
110 AddValue(attr
,"FREQ=MONTHLY");
113 oss
<< "BYMONTHDAY=" << cal
.DayOfMonth
;
114 AddValue(attr
, oss
.str().c_str());
118 case Calendar::MonthByDay
: // eg. every month on 3rd Wed
119 // see: DayOfWeek and WeekOfMonth
120 AddValue(attr
, "FREQ=MONTHLY");
121 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
123 oss
<< "BYDAY=" << cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
124 AddValue(attr
, oss
.str().c_str());
128 case Calendar::YearByDate
: // eg. every year on March 5
129 // see: DayOfMonth and MonthOfYear
130 AddValue(attr
, "FREQ=YEARLY");
133 oss
<< "BYMONTH=" << cal
.MonthOfYear
;
134 AddValue(attr
, oss
.str().c_str());
138 oss
<< "BYMONTHDAY=" << cal
.DayOfMonth
;
139 AddValue(attr
, oss
.str().c_str());
143 case Calendar::YearByDay
: // eg. every year on 3rd Wed of Jan
144 // see: DayOfWeek, WeekOfMonth, and
146 AddValue(attr
, "FREQ=YEARLY");
147 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
149 oss
<< "BYDAY=" << cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
150 AddValue(attr
, oss
.str().c_str());
153 oss
<< "BYMONTH=" << cal
.MonthOfYear
;
154 AddValue(attr
, oss
.str().c_str());
158 case Calendar::Week
: // eg. every week on Mon and Fri
160 AddValue(attr
, "FREQ=WEEKLY");
164 for( int i
= 0, bm
= 1, cnt
= 0; i
< 7; i
++, bm
<<= 1 ) {
165 if( cal
.WeekDays
& bm
) {
172 AddValue(attr
, oss
.str().c_str());
177 throw ConvertError("Unknown RecurringType in Barry Calendar object");
180 // add some common parameters
181 if( cal
.Interval
> 1 ) {
183 oss
<< "INTERVAL=" << cal
.Interval
;
184 AddValue(attr
, oss
.str().c_str());
186 if( !cal
.Perpetual
) {
188 oss
<< "UNTIL=" << m_vtc
.unix2vtime(&cal
.RecurringEndTime
);
189 AddValue(attr
, oss
.str().c_str());
200 /// Note: interval can be used on all of these recurring types to
201 /// make it happen "every other time" or more, etc.
205 RecurringCodeType RecurringType;
206 unsigned short Interval; // must be >= 1
207 time_t RecurringEndTime; // only pertains if Recurring is true
208 // sets the date and time when
209 // recurrence of this appointment
210 // should no longer occur
211 // If a perpetual appointment, this
212 // is 0xFFFFFFFF in the low level data
213 // Instead, set the following flag.
214 bool Perpetual; // if true, this will always recur
215 unsigned short TimeZoneCode; // the time zone originally used
216 // for the recurrence data...
217 // seems to have little use, but
218 // set to your current time zone
221 unsigned short // recurring details, depending on type
226 unsigned char WeekDays; // bitmask, bit 0 = sunday
228 #define CAL_WD_SUN 0x01
229 #define CAL_WD_MON 0x02
230 #define CAL_WD_TUE 0x04
231 #define CAL_WD_WED 0x08
232 #define CAL_WD_THU 0x10
233 #define CAL_WD_FRI 0x20
234 #define CAL_WD_SAT 0x40
240 void vCalendar::RecurToBarryCal(vAttr
& rrule
, time_t starttime
)
242 using namespace Barry
;
244 Barry::Calendar
&cal
= m_BarryCal
;
245 // Trace trace("vCalendar::RecurToBarryCal");
246 std::map
<std::string
,unsigned char> pmap
;
247 pmap
["SU"] = CAL_WD_SUN
;
248 pmap
["MO"] = CAL_WD_MON
;
249 pmap
["TU"] = CAL_WD_TUE
;
250 pmap
["WE"] = CAL_WD_WED
;
251 pmap
["TH"] = CAL_WD_THU
;
252 pmap
["FR"] = CAL_WD_FRI
;
253 pmap
["SA"] = CAL_WD_SAT
;
257 unsigned int count
=0;
259 std::map
<std::string
,std::string
> args
;
261 val
=rrule
.GetValue(i
++);
262 if(val
.length()==0) {
265 string n
=val
.substr(0,val
.find("="));
266 string v
=val
.substr(val
.find("=")+1);
268 // trace.logf("RecurToBarryCal: |%s|%s|",n.c_str(),v.c_str());
271 // now process the interval.
274 if(args
.find(string("INTERVAL"))!=args
.end()) {
275 cal
.Interval
= atoi(args
["INTERVAL"].c_str());
277 if(args
.find(string("UNTIL"))!=args
.end()) {
278 cal
.Perpetual
= FALSE
;
279 cal
.RecurringEndTime
=m_vtc
.vtime2unix(args
["UNTIL"].c_str());
280 if( cal
.RecurringEndTime
== (time_t)-1 ) {
281 // trace.logf("osync_time_vtime2unix() failed: UNTIL = %s, zoneoffset = %d", args["UNTIL"].c_str(), zoneoffset);
284 // if we do not also have COUNT, then we must be forerver
285 if(args
.find(string("COUNT"))==args
.end()) {
288 // we do have COUNT. This means we won't have UNTIL.
289 // So we need to process the RecurringEndTime from
290 // the current start date. Set the count level to
291 // something other than zero to indicate we need
292 // to process it as the exact end date will
293 // depend upon the frequency.
294 count
=atoi(args
["COUNT"].c_str());
296 throw std::runtime_error("Invalid COUNT in recurring rule: " + args
["COUNT"]);
301 // we need these if COUNT is true, or if we are a yearly job.
303 // TO-DO: we must process COUNT in terms of an end date if we have it.
305 // Now deal with the freq
307 if(args
.find(string("FREQ"))==args
.end()) {
308 // trace.logf("RecurToBarryCal: No frequency specified!");
312 if(args
["FREQ"]==string("DAILY")) {
313 cal
.RecurringType
=Calendar::Day
;
315 } else if(args
["FREQ"]==string("WEEKLY")) {
316 cal
.RecurringType
=Calendar::Week
;
317 // we must have a dayofweek entry
318 if(args
.find(string("BYDAY"))!=args
.end()) {
319 std::vector
<std::string
> v
=Tokenize(args
["BYDAY"]);
320 // iterate along our vector and convert
321 for(unsigned int idx
=0;idx
<v
.size();idx
++) {
322 cal
.WeekDays
|=pmap
[v
[idx
]];
326 // trace.logf("RecurToBarryCal: no BYDAY on weekly event");
329 // need to process end date. This is easy
330 // for weeks, as a number of weeks can be
331 // reduced to seconds simply.
332 cal
.RecurringEndTime
=starttime
+((count
-1)*60*60*24*7);
334 } else if(args
["FREQ"]=="MONTHLY") {
335 if(args
.find(string("BYMONTHDAY"))!=args
.end()) {
336 cal
.RecurringType
=Calendar::MonthByDate
;
337 cal
.DayOfMonth
=atoi(args
["BYMONTHDAY"].c_str());
339 if(args
.find(string("BYDAY"))!=args
.end()) {
340 cal
.RecurringType
=Calendar::MonthByDay
;
341 cal
.WeekOfMonth
=GetMonthWeekNumFromBYDAY(args
["BYDAY"]);
342 cal
.DayOfWeek
=GetWeekDayIndexFromBYDAY(args
["BYDAY"]);
344 // trace.logf("RecurToBarryCal: No qualifier on MONTHLY freq");
348 // Nasty. We need to convert to struct tm,
349 // do some modulo-12 addition then back
351 struct tm datestruct
;
352 localtime_r(&starttime
,&datestruct
);
353 // now do some modulo-12 on the month and year
354 // We could end up with an illegal date if
355 // the day of month is >28 and the resulting
356 // month falls on a February. We don't need
357 // to worry about day of week as mktime()
359 datestruct
.tm_year
+= (datestruct
.tm_mon
+count
)/12;
360 datestruct
.tm_mon
= (datestruct
.tm_mon
+count
)%12;
361 if(datestruct
.tm_mday
>28 && datestruct
.tm_mon
==1) {
362 // force it to 1st Mar
363 // TODO Potential bug on leap years
365 datestruct
.tm_mday
=1;
367 if(datestruct
.tm_mday
==31 && (datestruct
.tm_mon
==8 ||
368 datestruct
.tm_mon
==3 ||
369 datestruct
.tm_mon
==5 ||
370 datestruct
.tm_mon
==10)) {
371 datestruct
.tm_mon
+=1;
372 datestruct
.tm_mday
=1;
374 cal
.RecurringEndTime
=mktime(&datestruct
);
376 } else if(args
["FREQ"]=="YEARLY") {
377 if(args
.find(string("BYMONTH"))!=args
.end()) {
378 cal
.MonthOfYear
=atoi(args
["BYMONTH"].c_str());
379 if(args
.find(string("BYMONTHDAY"))!=args
.end()) {
380 cal
.RecurringType
=Calendar::YearByDate
;
381 cal
.DayOfMonth
=atoi(args
["BYMONTHDAY"].c_str());
383 if(args
.find(string("BYDAY"))!=args
.end()) {
384 cal
.RecurringType
=Calendar::YearByDay
;
385 cal
.WeekOfMonth
=GetMonthWeekNumFromBYDAY(args
["BYDAY"]);
386 cal
.DayOfWeek
=GetWeekDayIndexFromBYDAY(args
["BYDAY"]);
388 // trace.logf("RecurToBarryCal: No qualifier on YEARLY freq");
392 // otherwise use the start date and translate
394 // cal.StartTime has already been processed
395 // when we get here we need month of year,
397 struct tm datestruct
;
398 localtime_r(&starttime
,&datestruct
);
399 cal
.RecurringType
=Calendar::YearByDate
;
400 cal
.MonthOfYear
=datestruct
.tm_mon
;
401 cal
.DayOfMonth
=datestruct
.tm_mday
;
404 // convert to struct tm, then simply add to the year.
405 struct tm datestruct
;
406 localtime_r(&starttime
,&datestruct
);
407 datestruct
.tm_year
+= count
;
408 cal
.RecurringEndTime
=mktime(&datestruct
);
412 // unsigned char WeekDays; // bitmask, bit 0 = sunday
414 // #define CAL_WD_SUN 0x01
415 // #define CAL_WD_MON 0x02
416 // #define CAL_WD_TUE 0x04
417 // #define CAL_WD_WED 0x08
418 // #define CAL_WD_THU 0x10
419 // #define CAL_WD_FRI 0x20
420 // #define CAL_WD_SAT 0x40
423 // Main conversion routine for converting from Barry::Calendar to
424 // a vCalendar string of data.
425 const std::string
& vCalendar::ToVCal(const Barry::Calendar
&cal
)
427 // Trace trace("vCalendar::ToVCal");
428 std::ostringstream oss
;
430 // trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
434 SetFormat( b_vformat_new() );
436 throw ConvertError("resource error allocating vformat");
438 // store the Barry object we're working with
441 // begin building vCalendar data
442 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
443 AddAttr(NewAttr("BEGIN", "VEVENT"));
444 AddAttr(NewAttr("SEQUENCE", "0"));
445 AddAttr(NewAttr("SUMMARY", cal
.Subject
.c_str()));
446 AddAttr(NewAttr("DESCRIPTION", cal
.Notes
.c_str()));
447 AddAttr(NewAttr("LOCATION", cal
.Location
.c_str()));
449 string
start(m_vtc
.unix2vtime(&cal
.StartTime
));
450 string
end(m_vtc
.unix2vtime(&cal
.EndTime
));
451 string
notify(m_vtc
.unix2vtime(&cal
.NotificationTime
));
453 AddAttr(NewAttr("DTSTART", start
.c_str()));
454 AddAttr(NewAttr("DTEND", end
.c_str()));
455 // FIXME - add a truly globally unique "UID" string?
458 AddAttr(NewAttr("BEGIN", "VALARM"));
459 AddAttr(NewAttr("ACTION", "AUDIO"));
461 // notify must be UTC, when specified in DATE-TIME
462 vAttrPtr trigger
= NewAttr("TRIGGER", notify
.c_str());
463 AddParam(trigger
, "VALUE", "DATE-TIME");
466 AddAttr(NewAttr("END", "VALARM"));
469 if( cal
.Recurring
) {
473 AddAttr(NewAttr("END", "VEVENT"));
475 // generate the raw VCALENDAR data
476 m_gCalData
= b_vformat_to_string(Format(), VFORMAT_EVENT_20
);
477 m_vCalData
= m_gCalData
;
479 // trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
483 // Main conversion routine for converting from vCalendar data string
484 // to a Barry::Calendar object.
485 const Barry::Calendar
& vCalendar::ToBarry(const char *vcal
, uint32_t RecordId
)
489 // Trace trace("vCalendar::ToBarry");
490 // trace.logf("ToBarry, working on vcal data: %s", vcal);
492 // we only handle vCalendar data with one vevent block
493 if( HasMultipleVEvents() )
494 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
499 // store the vCalendar raw data
502 // create format parser structures
503 SetFormat( b_vformat_new_from_string(vcal
) );
505 throw ConvertError("resource error allocating vformat");
507 string start
= GetAttr("DTSTART", "/vevent");
508 // trace.logf("DTSTART attr retrieved: %s", start.c_str());
509 string end
= GetAttr("DTEND", "/vevent");
510 // trace.logf("DTEND attr retrieved: %s", end.c_str());
511 string subject
= GetAttr("SUMMARY", "/vevent");
512 // trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
513 if( subject
.size() == 0 ) {
514 subject
= "<blank subject>";
515 // trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
517 vAttr trigger_obj
= GetAttrObj("TRIGGER", 0, "/valarm");
519 string location
= GetAttr("LOCATION", "/vevent");
520 // trace.logf("LOCATION attr retrieved: %s", location.c_str());
522 string notes
= GetAttr("DESCRIPTION", "/vevent");
523 // trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
525 vAttr rrule
= GetAttrObj("RRULE",0,"/vevent");
529 // Now, run checks and convert into Barry object
533 // FIXME - we are assuming that any non-UTC timestamps
534 // in the vcalendar record will be in the current timezone...
535 // This is wrong! fix this later.
537 // Also, we currently ignore any time zone
538 // parameters that might be in the vcalendar format... this
541 Barry::Calendar
&rec
= m_BarryCal
;
542 rec
.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId
);
545 throw ConvertError("Blank DTSTART");
546 rec
.StartTime
= m_vtc
.vtime2unix(start
.c_str());
549 // DTEND is actually optional! According to the
550 // RFC, a DTSTART with no DTEND should be treated
551 // like a "special day" like an anniversary, which occupies
554 // Since the Blackberry doesn't really map well to this
555 // case, we'll set the end time to 1 day past start.
557 rec
.EndTime
= rec
.StartTime
+ 24 * 60 * 60;
560 rec
.EndTime
= m_vtc
.vtime2unix(end
.c_str());
563 rec
.Subject
= subject
;
564 rec
.Location
= location
;
568 RecurToBarryCal(rrule
, rec
.StartTime
);
571 // convert trigger time into notification time
572 // assume no notification, by default
573 rec
.NotificationTime
= 0;
574 if( trigger_obj
.Get() ) {
575 string trigger_type
= trigger_obj
.GetParam("VALUE");
576 string trigger
= trigger_obj
.GetValue();
578 if( trigger
.size() == 0 ) {
579 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
581 else if( trigger_type
== "DATE-TIME" ) {
582 rec
.NotificationTime
= m_vtc
.vtime2unix(trigger
.c_str());
584 else if( trigger_type
== "DURATION" || trigger_type
.size() == 0 ) {
585 // default is DURATION (RFC 4.8.6.3)
586 string related
= trigger_obj
.GetParam("RELATED");
588 // default to relative to start time
589 time_t *relative
= &rec
.StartTime
;
590 if( related
== "END" )
591 relative
= &rec
.EndTime
;
593 rec
.NotificationTime
= *relative
+ m_vtc
.alarmduration2sec(trigger
.c_str());
596 throw ConvertError("Unknown TRIGGER VALUE");
600 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
603 std::ostringstream oss
;
604 m_BarryCal
.Dump(oss
);
605 // trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
609 // Transfers ownership of m_gCalData to the caller.
610 char* vCalendar::ExtractVCal()
612 char *ret
= m_gCalData
;
617 void vCalendar::Clear()
629 }} // namespace Barry::Sync