Bug Summary

File:libsynthesis/src/sysync/rrules.cpp
Warning:line 2257, column 27
The result of the '<<' expression is undefined

Annotated Source Code

1/*
2 * File: rrules.cpp
3 *
4 * Author: Lukas Zeller (luz@plan44.ch)
5 *
6 * Parser/Generator routines for vCalendar RRULES
7 *
8 * Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
9 *
10 * 2004-11-23 : luz : created from exctracts from vcalendaritemtype.cpp
11 *
12 */
13#include "prefix_file.h"
14
15#include <stdarg.h>
16
17#include "rrules.h"
18
19
20#if defined(SYSYNC_TOOL)
21 #include "syncappbase.h" // for CONSOLEPRINTF
22#endif
23
24using namespace sysync;
25
26namespace sysync {
27
28
29// Names of weekdays in RRULEs
30const char* const RRULE_weekdays[ DaysOfWeek ] = {
31 "SU",
32 "MO",
33 "TU",
34 "WE",
35 "TH",
36 "FR",
37 "SA"
38};
39
40
41// Support for SySync Diagnostic Tool
42#ifdef SYSYNC_TOOL
43
44// convert between RRULE and internal format
45int rruleConv(int argc, const char *argv[])
46{
47 if (argc<0) {
48 // help requested
49 CONSOLEPRINTF((" rrule <input mode> <output mode> <input> ..."))SySync_ConsolePrintf(stderr, "SYSYNC " " rrule <input mode> <output mode> <input> ..."
"\n")
;
50 CONSOLEPRINTF((" Convert between RRULE representations:"))SySync_ConsolePrintf(stderr, "SYSYNC " " Convert between RRULE representations:"
"\n")
;
51 CONSOLEPRINTF((" mode \"i\" : internal format: startdate,freq,freqmod,interval,firstmask,lastmask,until"))SySync_ConsolePrintf(stderr, "SYSYNC " " mode \"i\" : internal format: startdate,freq,freqmod,interval,firstmask,lastmask,until"
"\n")
;
52 CONSOLEPRINTF((" (firstmask/lastmask in decimal or hex, until in ISO8601 format)"))SySync_ConsolePrintf(stderr, "SYSYNC " " (firstmask/lastmask in decimal or hex, until in ISO8601 format)"
"\n")
;
53 CONSOLEPRINTF((" mode \"t\" : output only, like \"i\" but also shows table of first 10 recurrence dates"))SySync_ConsolePrintf(stderr, "SYSYNC " " mode \"t\" : output only, like \"i\" but also shows table of first 10 recurrence dates"
"\n")
;
54 CONSOLEPRINTF((" mode \"1\" : startdate,RRULE according to vCalendar 1.0 specs"))SySync_ConsolePrintf(stderr, "SYSYNC " " mode \"1\" : startdate,RRULE according to vCalendar 1.0 specs"
"\n")
;
55 CONSOLEPRINTF((" mode \"2\" : startdate,RRULE according to iCalendar (RFC2445) specs"))SySync_ConsolePrintf(stderr, "SYSYNC " " mode \"2\" : startdate,RRULE according to iCalendar (RFC2445) specs"
"\n")
;
56 return EXIT_SUCCESS0;
57 }
58
59 // check for argument
60 if (argc!=3) {
61 CONSOLEPRINTF(("3 arguments required"))SySync_ConsolePrintf(stderr, "SYSYNC " "3 arguments required"
"\n")
;
62 return EXIT_FAILURE1;
63 }
64 // mode
65 char inmode,outmode;
66 // internal representation
67 lineartime_t start;
68 char freq;
69 char freqmod;
70 sInt16 interval;
71 fieldinteger_t firstmask;
72 fieldinteger_t lastmask;
73 lineartime_t until;
74 // time zone stuff
75 timecontext_t rulecontext=TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ));
76 timecontext_t untilcontext;
77 GZones zones;
78 // RRULE
79 char isodate[50];
80 char rulebuf[200];
81 string rrule,iso;
82 // get input mode
83 inmode=tolower(*(argv[0]));
84 if (inmode!='i' && inmode!='1' && inmode!='2') {
85 CONSOLEPRINTF(("invalid input mode, must be i, 1 or 2"))SySync_ConsolePrintf(stderr, "SYSYNC " "invalid input mode, must be i, 1 or 2"
"\n")
;
86 return EXIT_FAILURE1;
87 }
88 // get output mode
89 outmode=tolower(*(argv[1]));
90 if (outmode!='i' && outmode!='t' && outmode!='1' && outmode!='2') {
91 CONSOLEPRINTF(("invalid output mode, must be i, t, 1 or 2"))SySync_ConsolePrintf(stderr, "SYSYNC " "invalid output mode, must be i, t, 1 or 2"
"\n")
;
92 return EXIT_FAILURE1;
93 }
94 CONSOLEPRINTF((""))SySync_ConsolePrintf(stderr, "SYSYNC " "" "\n");
95 // reset params
96 freq='0'; // frequency = none
97 freqmod=' '; // frequency modifier
98 interval=0; // interval
99 firstmask=0; // day mask counted from the first day of the period
100 lastmask=0; // day mask counted from the last day of the period
101 until= noLinearTime; // last day
102 // get input and convert to internal format (if needed)
103 if (inmode=='i') {
104 if (sscanf(argv[2],
105 "%[^,],%c,%c,%hd,%lld,%lld,%s",
106 rulebuf,
107 &freq,
108 &freqmod,
109 &interval,
110 &firstmask,
111 &lastmask,
112 isodate
113 )!=7) {
114 CONSOLEPRINTF(("invalid internal format input"))SySync_ConsolePrintf(stderr, "SYSYNC " "invalid internal format input"
"\n")
;
115 return EXIT_FAILURE1;
116 }
117 // - convert dates
118 ISO8601StrToTimestamp(rulebuf,start,rulecontext); // Start determines rule context
119 ISO8601StrToTimestamp(isodate,until,untilcontext); // until will be converted to same context
120 TzConvertTimestamp(until,untilcontext,rulecontext,&zones);
121 }
122 else if (inmode=='1') {
123 if (sscanf(argv[2],"%[^,],%[^\n]",isodate,rulebuf)!=2) {
124 CONSOLEPRINTF(("error, expected: startdate,RRULE"))SySync_ConsolePrintf(stderr, "SYSYNC " "error, expected: startdate,RRULE"
"\n")
;
125 return EXIT_FAILURE1;
126 }
127 ISO8601StrToTimestamp(isodate,start,rulecontext);
128 if (!RRULE1toInternal(
129 rulebuf, // RRULE string to be parsed
130 start, // reference date for parsing RRULE
131 rulecontext, // time context of RRULE
132 freq,
133 freqmod,
134 interval,
135 firstmask,
136 lastmask,
137 until,
138 untilcontext,
139 NULL__null
140 )) {
141 CONSOLEPRINTF(("invalid/unsupported RRULE type 1 specification"))SySync_ConsolePrintf(stderr, "SYSYNC " "invalid/unsupported RRULE type 1 specification"
"\n")
;
142 return EXIT_FAILURE1;
143 }
144 }
145 else if (inmode=='2') {
146 if (sscanf(argv[2],"%[^,],%[^\n]",isodate,rulebuf)!=2) {
147 CONSOLEPRINTF(("error, expected: startdate,RRULE"))SySync_ConsolePrintf(stderr, "SYSYNC " "error, expected: startdate,RRULE"
"\n")
;
148 return EXIT_FAILURE1;
149 }
150 ISO8601StrToTimestamp(isodate,start,rulecontext);
151 if (!RRULE2toInternal(
152 rulebuf, // RRULE string to be parsed
153 start, // reference date for parsing RRULE
154 rulecontext, // time context of RRULE
155 freq,
156 freqmod,
157 interval,
158 firstmask,
159 lastmask,
160 until,
161 untilcontext,
162 NULL__null
163 )) {
164 CONSOLEPRINTF(("invalid/unsupported RRULE type 2 specification"))SySync_ConsolePrintf(stderr, "SYSYNC " "invalid/unsupported RRULE type 2 specification"
"\n")
;
165 return EXIT_FAILURE1;
166 }
167 }
168 // convert to rrule (if needed) and output
169 TimestampToISO8601Str(iso,start,rulecontext,true);
170 if (outmode=='i' || outmode=='t') {
171 TimestampToISO8601Str(rrule,until,TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)),true);
172 CONSOLEPRINTF((SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
173 "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s",SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
174 iso.c_str(),SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
175 freq,SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
176 freqmod,SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
177 interval,SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
178 firstmask,SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
179 lastmask,SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
180 rrule.c_str()SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
181 ))SySync_ConsolePrintf(stderr, "SYSYNC " "Internal format (start,freq,freqmod,interval,firstmask,lastmask,until:\n%s,%c,%c,%hd,0x%llX,0x%llX,%s"
"\n", iso.c_str(), freq, freqmod, interval, firstmask, lastmask
, rrule.c_str())
;
182 // extra info if we have 't' table mode
183 lineartime_t occurrence;
184 if (outmode=='t') {
185 sInt16 cnt;
186 for (cnt=1; cnt<=10; cnt++) {
187 // calculate date of cnt-th recurrence
188 if (!endDateFromCount(
189 occurrence,
190 start,
191 freq, freqmod,
192 interval,
193 firstmask, lastmask,
194 cnt, true, // counting occurrences
195 NULL__null
196 ))
197 break;
198 // see if limit already reached
199 if (occurrence>until) {
200 cnt=0;
201 break;
202 }
203 // show this recurrence
204 TimestampToISO8601Str(iso,occurrence,rulecontext,true);
205 CONSOLEPRINTF((SySync_ConsolePrintf(stderr, "SYSYNC " "%3d. occurrence at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
206 "%3d. occurrence at %s (%s)",SySync_ConsolePrintf(stderr, "SYSYNC " "%3d. occurrence at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
207 cnt,SySync_ConsolePrintf(stderr, "SYSYNC " "%3d. occurrence at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
208 iso.c_str(),SySync_ConsolePrintf(stderr, "SYSYNC " "%3d. occurrence at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
209 RRULE_weekdays[lineartime2weekday(occurrence)]SySync_ConsolePrintf(stderr, "SYSYNC " "%3d. occurrence at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
210 ))SySync_ConsolePrintf(stderr, "SYSYNC " "%3d. occurrence at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
;
211 }
212 // show end date if not all recurrences shown already
213 if (cnt>10) {
214 // calculate occurrence count
215 if (countFromEndDate(
216 cnt, true, // counting occurrences
217 start,
218 freq, freqmod,
219 interval,
220 firstmask, lastmask,
221 until,
222 NULL__null
223 )) {
224 if (cnt==0) {
225 CONSOLEPRINTF((SySync_ConsolePrintf(stderr, "SYSYNC " "last occurrence: <none> (repeating infinitely)"
"\n", cnt, iso.c_str())
226 "last occurrence: <none> (repeating infinitely)",SySync_ConsolePrintf(stderr, "SYSYNC " "last occurrence: <none> (repeating infinitely)"
"\n", cnt, iso.c_str())
227 cnt,SySync_ConsolePrintf(stderr, "SYSYNC " "last occurrence: <none> (repeating infinitely)"
"\n", cnt, iso.c_str())
228 iso.c_str()SySync_ConsolePrintf(stderr, "SYSYNC " "last occurrence: <none> (repeating infinitely)"
"\n", cnt, iso.c_str())
229 ))SySync_ConsolePrintf(stderr, "SYSYNC " "last occurrence: <none> (repeating infinitely)"
"\n", cnt, iso.c_str())
;
230 }
231 else {
232 // convert back to date
233 if (endDateFromCount(
234 occurrence,
235 start,
236 freq, freqmod,
237 interval,
238 firstmask, lastmask,
239 cnt, true, // counting occurrences
240 NULL__null
241 )) {
242 TimestampToISO8601Str(iso,occurrence,rulecontext,true);
243 CONSOLEPRINTF((SySync_ConsolePrintf(stderr, "SYSYNC " "%3d./LAST occ. at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
244 "%3d./LAST occ. at %s (%s)",SySync_ConsolePrintf(stderr, "SYSYNC " "%3d./LAST occ. at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
245 cnt,SySync_ConsolePrintf(stderr, "SYSYNC " "%3d./LAST occ. at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
246 iso.c_str(),SySync_ConsolePrintf(stderr, "SYSYNC " "%3d./LAST occ. at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
247 RRULE_weekdays[lineartime2weekday(occurrence)]SySync_ConsolePrintf(stderr, "SYSYNC " "%3d./LAST occ. at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
248 ))SySync_ConsolePrintf(stderr, "SYSYNC " "%3d./LAST occ. at %s (%s)"
"\n", cnt, iso.c_str(), RRULE_weekdays[lineartime2weekday(occurrence
)])
;
249 }
250 }
251 }
252 }
253 }
254 }
255 else if (outmode=='1') {
256 if (!internalToRRULE1(
257 rrule, // receives RRULE string
258 freq,
259 freqmod,
260 interval,
261 firstmask,
262 lastmask,
263 until,
264 rulecontext,
265 NULL__null
266 )) {
267 CONSOLEPRINTF(("Cannot show as RRULE type 1"))SySync_ConsolePrintf(stderr, "SYSYNC " "Cannot show as RRULE type 1"
"\n")
;
268 return EXIT_FAILURE1;
269 }
270 CONSOLEPRINTF(("RRULE type 1 format (start,rrule):\n%s,%s",iso.c_str(),rrule.c_str()))SySync_ConsolePrintf(stderr, "SYSYNC " "RRULE type 1 format (start,rrule):\n%s,%s"
"\n",iso.c_str(),rrule.c_str())
;
271 }
272 else if (outmode=='2') {
273 if (!internalToRRULE2(
274 rrule, // receives RRULE string
275 freq,
276 freqmod,
277 interval,
278 firstmask,
279 lastmask,
280 until,
281 rulecontext,
282 NULL__null
283 )) {
284 CONSOLEPRINTF(("Cannot show as RRULE type 2"))SySync_ConsolePrintf(stderr, "SYSYNC " "Cannot show as RRULE type 2"
"\n")
;
285 return EXIT_FAILURE1;
286 }
287 CONSOLEPRINTF(("RRULE type 2 format (start,rrule):\n%s,%s",iso.c_str(),rrule.c_str()))SySync_ConsolePrintf(stderr, "SYSYNC " "RRULE type 2 format (start,rrule):\n%s,%s"
"\n",iso.c_str(),rrule.c_str())
;
288 }
289 return EXIT_SUCCESS0;
290} // rruleConv
291
292#endif // SYSYNC_TOOL
293
294
295
296/*
297 // sample of a field block:
298
299 Offset=0: <field name="RR_FREQ" type="string" compare="conflict"/>
300 - frequency codes:
301 0 = none,
302 s = secondly,
303 m = minutely,
304 h = hourly,
305 D = daily,
306 W = weekly,
307 M = monthly,
308 Y = yearly
309 - frequency modifiers
310 space = none
311 s = by second list
312 m = by minute list
313 h = by hour list
314 W = by weekday list
315 D = by monthday list
316 Y = by yearday list
317 N = by weeknumber list
318 M = by monthlist
319 P = by setposlist
320
321 Offset=1: <field name="RR_INTERVAL" type="integer" compare="conflict"/>
322 - interval
323
324 Offset=2: <field name="RR_FMASK" type="integer" compare="conflict"/>
325 - bit-coded list of items according to frequency modifier,
326 relative to the beginning of the interval
327 - for seconds, minutes, hours: Bit0=first, Bit1=second....
328 - for weekly: Bit0=Sun, Bit6=Sat
329 - for monthly by weekday: Bit0=first Sun, Bit7=2nd Sun... Bit35=5th Sun
330 - for monthly by day: Bit0=1st, Bit1=2nd, Bit31=31st.
331 - for yearly by month: Bit0=jan, Bit1=feb,...
332 - for yearly by weeknumber: Bit0=weekno1,....
333
334 Offset=3: <field name="RR_LMASK" type="integer" compare="conflict"/>
335 - bit coded list of items according to frequency modifier,
336 relative to the end of the interval:
337 - Bit0=last, Bit1=second last, Bit2=third last....
338 - for weekly: Bit0=last Sun, Bit1=last Mon,... Bit7=second last Sun,...
339
340 Offset=4: <field name="RR_END" type="timestamp" compare="conflict"/>
341 - end date. This is calculated from DTSTART and count if incoming rule does
342 not specify an end date
343
344 Offset=5: <field name="DTSTART" type="timestamp" compare="always"/>
345 - for reference only
346
347*/
348
349// Converts internal recurrence into vCalendar 1.0 RRULE string
350bool internalToRRULE1(
351 string &aString, // receives RRULE string
352 char freq,
353 char freqmod,
354 sInt16 interval,
355 fieldinteger_t firstmask,
356 fieldinteger_t lastmask,
357 lineartime_t until,
358 timecontext_t untilcontext,
359 TDebugLogger *aLogP
360)
361{
362 // Now do the conversion
363 string s;
364 sInt16 i,j,k;
365 fieldinteger_t m;
366 bool repshown;
367 aString.erase();
368 // frequency and modifier
369 switch (freq) {
370 case '0' : return true; // no repetition
371 case 'D' : aString+='D'; break;
372 case 'W' : aString+='W'; break;
373 case 'M' :
374 aString+='M';
375 if (freqmod=='W') aString+='P'; // by-(weekday)-position
376 else if (freqmod=='D') aString+='D'; // by (month)day
377 else goto incompat;
378 break;
379 case 'Y' :
380 aString+='Y';
381 if (freqmod=='M') aString+='M'; // by month
382 // else if (freqmod=='Y') aString+='D'; // by yearday, %%%% not supported
383 else goto incompat;
384 break;
385 default :
386 goto incompat;
387 } // switch freq
388 // add interval
389 if (interval<1) goto incompat;
390 StringObjAppendPrintf(aString,"%hd",interval);
391 // add modifiers
392 switch (freqmod) {
393 case 'W' :
394 if (freq=='M') {
395 // Monthly by weekday
396 m = firstmask;
397 for (i=0; i<2; i++) {
398 // - from start and from end
399 for (j=0; j<WeeksOfMonth; j++) {
400 // - repetition
401 repshown=false;
402 // - append weekdays for this repetition
403 for (k=0; k<DaysOfWeek; k++) {
404 if (m & ((uInt64)1<<(k+(DaysOfWeek*j)))) {
405 // show repetion before first day item
406 if (!repshown) {
407 repshown=true;
408 StringObjAppendPrintf(aString," %d",j+1);
409 // - show if relative to beginning or end of month
410 if (i>0) aString+='-'; else aString+='+';
411 }
412 // show day
413 aString+=' '; aString+=RRULE_weekdays[k];
414 }
415 }
416 }
417 // - switch to those that are relative to the end of the month
418 m = lastmask;
419 }
420 }
421 else {
422 // weekly by weekday
423 for (k=0; k<DaysOfWeek; k++) {
424 if (firstmask & ((uInt64)1<<k)) { aString+=' '; aString+=RRULE_weekdays[k]; }
425 }
426 }
427 break;
428 case 'D' :
429 case 'M' :
430 // monthly by day number or
431 // yearly by month number
432 m = firstmask;
433 for (i=0; i<2; i++) {
434 // - show day numbers
435 for (k=0; k<32; k++) {
436 if (m & ((uInt64)1<<k)) {
437 StringObjAppendPrintf(aString," %d",k+1);
438 // show if relative to the end
439 if (i>0) aString+='-';
440 }
441 }
442 // - switch to those that are relative to the end of the month / year
443 m = lastmask;
444 }
445 break;
446 case ' ' :
447 // no modifiers, that's ok as well
448 break;
449 default :
450 goto incompat;
451 } // switch freqmod
452 // add end date (or #0 if no end date = endless)
453 if (until!=noLinearTime) {
454 // there is an end date, use it
455 aString+=' ';
456 TimestampToISO8601Str(s, until, untilcontext);
457 aString+=s;
458 }
459 else {
460 // there is no end date, repeat forever
461 aString+=" #0";
462 }
463 // genereated a RRULE
464 return true;
465incompat:
466 // incompatible, cannot be shown as vCal 1.0 RRULE
467 aString.erase();
468 return false; // no value generated
469} // internalToRRULE1
470
471
472// Converts internal recurrence into vCalendar 2.0 RRULE string
473bool internalToRRULE2(
474 string &aString, // receives RRULE string
475 char freq,
476 char freqmod,
477 sInt16 interval,
478 fieldinteger_t firstmask,
479 fieldinteger_t lastmask,
480 lineartime_t until,
481 timecontext_t untilcontext,
482 TDebugLogger *aLogP
483)
484{
485 LOGDEBUGPRINTFX(aLogP,DBG_EXOTIC+DBG_GEN,({ if ((aLogP) && ((0x80000000 +0x00000400) & (aLogP
)->getMask()) == (0x80000000 +0x00000400)) (aLogP)->setNextMask
(0x80000000 +0x00000400).DebugPrintfLastMask ( "InternalToRRULE2(): expanding freq=%c, freqmod=%c, interval=%hd, firstmask=%llX, lastmask=%llX"
, freq, freqmod, interval, (long long)firstmask, (long long)lastmask
); }
486 "InternalToRRULE2(): expanding freq=%c, freqmod=%c, interval=%hd, firstmask=%llX, lastmask=%llX",{ if ((aLogP) && ((0x80000000 +0x00000400) & (aLogP
)->getMask()) == (0x80000000 +0x00000400)) (aLogP)->setNextMask
(0x80000000 +0x00000400).DebugPrintfLastMask ( "InternalToRRULE2(): expanding freq=%c, freqmod=%c, interval=%hd, firstmask=%llX, lastmask=%llX"
, freq, freqmod, interval, (long long)firstmask, (long long)lastmask
); }
487 freq, freqmod, interval, (long long)firstmask, (long long)lastmask{ if ((aLogP) && ((0x80000000 +0x00000400) & (aLogP
)->getMask()) == (0x80000000 +0x00000400)) (aLogP)->setNextMask
(0x80000000 +0x00000400).DebugPrintfLastMask ( "InternalToRRULE2(): expanding freq=%c, freqmod=%c, interval=%hd, firstmask=%llX, lastmask=%llX"
, freq, freqmod, interval, (long long)firstmask, (long long)lastmask
); }
488 )){ if ((aLogP) && ((0x80000000 +0x00000400) & (aLogP
)->getMask()) == (0x80000000 +0x00000400)) (aLogP)->setNextMask
(0x80000000 +0x00000400).DebugPrintfLastMask ( "InternalToRRULE2(): expanding freq=%c, freqmod=%c, interval=%hd, firstmask=%llX, lastmask=%llX"
, freq, freqmod, interval, (long long)firstmask, (long long)lastmask
); }
;
489
490 // Now do the conversion
491 string s;
492 sInt16 i,j,k;
493 fieldinteger_t m;
494 bool repshown;
495 cAppCharP sep;
496 aString.erase();
497 // frequency and modifier
498 switch (freq) {
499 case '0' : return true; // no repetition
500 case 'D' : aString+="FREQ=DAILY"; break;
501 case 'W' : aString+="FREQ=WEEKLY"; break;
502 case 'M' : aString+="FREQ=MONTHLY"; break;
503 case 'Y' : aString+="FREQ=YEARLY"; break;
504 default :
505 goto incompat;
506 } // switch freq
507 // add interval
508 if (interval<1) goto incompat;
509 StringObjAppendPrintf(aString,";INTERVAL=%hd",interval);
510 // add modifiers
511 switch (freqmod) {
512 case 'W' :
513 sep=";BYDAY=";
514 if (freq=='M') {
515 // Monthly by weekday
516 m = firstmask;
517 for (i=0; i<2; i++) {
518 // - from start and from end
519 for (j=0; j<WeeksOfMonth; j++) {
520 // - repetition
521 repshown=false;
522 // - append weekdays for this repetition
523 for (k=0; k<DaysOfWeek; k++) {
524 if (m & ((uInt64)1<<(k+(DaysOfWeek*j)))) {
525 // show repetion before first day item
526 aString+=sep;
527 sep=",";
528 if (!repshown) {
529 repshown=true;
530 if (i>0) aString+='-';
531 StringObjAppendPrintf(aString,"%d",j+1);
532 // - show if relative to beginning or end of month
533 }
534 // show day
535 aString+=RRULE_weekdays[k];
536 }
537 }
538 }
539 // - switch to those that are relative to the end of the month
540 m = lastmask;
541 }
542 }
543 else {
544 // weekly by weekday
545 for (k=0; k<DaysOfWeek; k++) {
546 if (firstmask & ((uInt64)1<<k)) {
547 aString+=sep;
548 sep=",";
549 aString+=RRULE_weekdays[k];
550 }
551 }
552 }
553 break;
554 case 'D' :
555 // monthly by day number
556 appendMaskAsNumbers(";BYMONTHDAY=", aString, firstmask, lastmask);
557 break;
558 case 'Y' :
559 // yearday by day number
560 appendMaskAsNumbers(";BYYEARDAY=", aString, firstmask, lastmask);
561 break;
562 case 'N' :
563 // yearly by week number
564 appendMaskAsNumbers(";BYWEEKNO=", aString, firstmask, lastmask);
565 break;
566 case 'M' :
567 // yearly by month number
568 appendMaskAsNumbers(";BYMONTH=", aString, firstmask, lastmask);
569 break;
570 case ' ' :
571 // no modifiers, that's ok as well
572 break;
573 default :
574 goto incompat;
575 } // switch freqmod
576 // add end date
577 // Note: for correct iCalendar 2.0, this should be (and is, in mimedir) called with untilcontext=UTC unless it's date-only
578 if (until!=noLinearTime) {
579 // there is an end date, use it
580 aString+=";UNTIL=";
581 TimestampToISO8601Str(s, until, untilcontext);
582 aString+=s;
583 }
584
585 // do output
586 LOGDEBUGPRINTFX(aLogP,DBG_GEN,("generated rrule %s", aString.c_str())){ if ((aLogP) && ((0x00000400) & (aLogP)->getMask
()) == (0x00000400)) (aLogP)->setNextMask(0x00000400).DebugPrintfLastMask
("generated rrule %s", aString.c_str()); }
;
587
588 // genereated a RRULE
589 return true;
590incompat:
591 // incompatible, cannot be shown as vCal 1.0 RRULE
592 aString.erase();
593 return false; // no value generated
594} // internalToRRULE2
595
596
597// Converts vCalendar 1.0 RRULE string into internal recurrence representation
598bool RRULE1toInternal(
599 const char *aText, // RRULE string to be parsed
600 lineartime_t dtstart, // reference date for parsing RRULE
601 timecontext_t startcontext, // context of reference date
602 char &freq,
603 char &freqmod,
604 sInt16 &interval,
605 fieldinteger_t &firstmask,
606 fieldinteger_t &lastmask,
607 lineartime_t &until,
608 timecontext_t &untilcontext,
609 TDebugLogger *aLogP
610)
611{
612 string s;
613 const char *p;
614 sInt16 startwday;
615 sInt16 startyear,startmonth,startday;
616 bool fromstart;
617 sInt16 cnt;
618
619 // get elements of start point
620 startwday=lineartime2weekday(dtstart); // get starting weekday
621 lineartime2date(dtstart,&startyear,&startmonth,&startday); // year, month, day-in-month
622 // do the conversion here
623 p=aText;
624 char c,c2;
625 sInt16 j,k;
626 fieldinteger_t m;
627 // get frequency and modifier
628 do c=*p++; while(c==' '); // next non-space
629 if (c==0) goto incompat; // no frequency: incompatible -> parse error
630 switch (c) {
631 case 'D' : freq='D'; break;
632 case 'W' : freq='W'; freqmod='W'; break;
633 case 'M' :
634 freq='M';
635 // get modifier
636 c=*p++;
637 if (c=='P') freqmod='W'; // by weekday
638 else if (c=='D') freqmod='D'; // by monthday
639 else goto norep; // unknown modifier: cancel repetition (but no error)
640 break;
641 case 'Y' :
642 freq='Y';
643 // get modifier
644 c=*p++;
645 if (c=='M') freqmod='M'; // by monthlist
646 else if (c=='D') freqmod='Y'; // by day-of-year list %%% not supported
647 else goto norep; // unknown modifier: cancel repetition (but no error)
648 break;
649 default :
650 goto incompat;
651 }
652 // get interval
653 while (isdigit(*p)) { interval=interval*10+((*p++)-'0'); }
654 if (interval==0) goto incompat; // no interval is incompatible -> parse error
655 // get modifier(s) if any
656 switch (freq) {
657 case 'W' :
658 // weekly may or may not have modifiers
659 do {
660 do c=*p++; while(c==' '); // next non-space
661 if (isalpha(c)) {
662 c2=*p++;
663 if (!isalpha(c2)) goto incompat; // bad weekday syntax -> parse error
664 for (k=0;k<DaysOfWeek;k++) if (toupper(c)==RRULE_weekdays[k][0] && toupper(c2)==RRULE_weekdays[k][1]) break;
665 if (k==DaysOfWeek) goto incompat; // bad weekday -> parse error
666 firstmask = firstmask | ((uInt64)1<<k); // add weekday
667 }
668 else {
669 p--;
670 break;
671 }
672 } while(true);
673 // make sure start day is always in mask
674 if (dtstart) firstmask |= ((uInt64)1<<startwday);
675 break;
676 case 'M' :
677 fromstart=true; // default to specification relative to start
678 if (freqmod=='W') {
679 // monthly by weekday (position)
680 j=0; // default to first occurrence until otherwise set
681 do {
682 do c=*p++; while(c==' '); // next non-space
683 if (isdigit(c) && !isdigit(*p)) { // only single digit, otherwise it might be end date
684 // get occurrence specs
685 if (c>'5' || c<'1') goto incompat; // no more than 5 weeks in a month! -> parse error
686 j=c-'1'; // first occurrence=0
687 // check for '+' or '-'
688 if (*p=='+') p++; // simply skip
689 else if (!(fromstart=!(*p=='-'))) p++; // skip
690 }
691 else if (isalpha(c)) {
692 // get weekday spec
693 c2=*p++;
694 if (!isalpha(c2)) goto incompat; // bad weekday syntax -> parse error
695 for (k=0;k<DaysOfWeek;k++) if (toupper(c)==RRULE_weekdays[k][0] && toupper(c2)==RRULE_weekdays[k][1]) break;
696 if (k==DaysOfWeek) goto incompat; // bad weekday -> parse error
697 m=(uInt64)1<<(DaysOfWeek*j+k);
698 if (fromstart) firstmask |= m; else lastmask |= m; // add weekday rep
699 }
700 else {
701 // end of modifiers
702 p--;
703 break;
704 }
705 } while(true);
706 // check if we need defaults
707 if (!(firstmask | lastmask)) {
708 if (!dtstart) goto norep; // cannot set, no start date (but no error)
709 // determine the repetition in the month of this weekday
710 j=(startday-1) / DaysOfWeek;
711 firstmask = ((uInt64)1<<(DaysOfWeek*j+startwday)); // set nth repetition of current weekday
712 }
713 }
714 else {
715 // must be 'D' = monthly by day of month
716 do {
717 fromstart=true;
718 do c=*p++; while(c==' '); // next non-space
719 if (c=='L' && *p=='D') {
720 // special case: "LD" means last day of month
721 lastmask |= 1;
722 }
723 else if (isdigit(c) && (*p) && (!isdigit(*p) || !isdigit(*(p+1)))) { // more than two digits are end date
724 // get day number
725 k=c-'0';
726 if (isdigit(*p)) k=k*10+((*p++)-'0');
727 // check for '+' or '-'
728 if (*p=='+') p++; // simply skip plus
729 if (!(fromstart=!(*p=='-'))) p++; // skip minus and set fromstart to false
730 if (k==0) goto incompat; // 0 is not allowed -> parse error
731 // set mask
732 k--; // bits are 0 based
733 m = (uInt64)1<<k;
734 if (fromstart) firstmask |= m; else lastmask |= m; // add day
735 }
736 else {
737 p--;
738 break;
739 }
740 } while(true);
741 // check if we need defaults
742 if (!(firstmask | lastmask)) {
743 if (!dtstart) goto norep; // cannot set, no start date (no error)
744 // set current day number
745 firstmask = ((uInt64)1<<(startday-1)); // set bit of current day-in-month
746 }
747 } // by day of month
748 // monthly modifiers done
749 break;
750 case 'Y' :
751 if (freqmod=='M') {
752 // by monthlist
753 do {
754 do c=*p++; while (c==' '); // next non-space
755 if (isdigit(c) && (*p) && (!isdigit(*p) || !isdigit(*(p+1)))) { // more than two digits are end date
756 // get month number
757 k=c-'0';
758 if (isdigit(*p)) k=k*10+((*p++)-'0');
759 if (k==0) goto incompat; // 0 is not allowed -> parse error
760 // set mask
761 k--; // bits are 0 based
762 firstmask |= (uInt64)1<<k; // add month
763 }
764 else {
765 p--;
766 break;
767 }
768 } while(true);
769 // check if we need defaults
770 if (!firstmask) {
771 if (!dtstart) goto norep; // cannot set, no start date (but no error)
772 // set current month number
773 firstmask = ((uInt64)1<<(startmonth-1)); // set bit of current day-in-month
774 }
775 }
776 else {
777 // by day of year, only supported if no list follows
778 // - check if list follows
779 do c=*p++; while (c==' '); // next non-space
780 if (isdigit(c)) {
781 if (*p) {
782 if (!isdigit(*p)) goto norep; // single digit, is list, abort (but no error)
783 if (*(p+1)) {
784 if (!isdigit(*(p+1))) goto norep; // dual digit, is list, abort (but no error)
785 if (*(p+2)) {
786 if (!isdigit(*(p+2))) goto norep; // three digit, is list, abort (but no error)
787 }
788 }
789 }
790 }
791 // if we get so far, it's either end date (>= 4 digits) or # or end of string
792 --p; // we have fetched one to many
793 // - make best try to convert to by-monthlist
794 freqmod='M';
795 firstmask = ((uInt64)1<<(startmonth-1)); // set bit of current day-in-month
796 }
797 // yearly modifiers done
798 break;
799 } // switch for modifier reading
800 // get count or end date
801 do c=*p++; while (c==' '); // next non-space
802 cnt=2; // default to repeat once (=applied two times)
803 if (c==0) goto calcenddate;
804 else if (c=='#') {
805 // count specified
806 if (!StrToShort(p,cnt)) goto incompat; // bad count -> parse error
807 calcenddate:
808 if (!endDateFromCount(until,dtstart,freq,freqmod,interval,firstmask,lastmask,cnt,false,aLogP))
809 goto norep;
810 untilcontext = startcontext; // until is in same context as start
811 } // count specified
812 else {
813 // must be end date, or spec is bad
814 p--;
815 if (ISO8601StrToTimestamp(p, until, untilcontext)==0)
816 goto incompat; // bad end date -> parse error
817 } // end date specified
818 // parsed ok, now store it
819 goto store;
820norep:
821 // no repetition (but no parse error generated)
822 freq='0'; // frequency = none
823 freqmod=' '; // frequency modifier
824 interval=0; // interval
825 firstmask=0; // day mask counted from the first day of the period
826 lastmask=0; // day mask counted from the last day of the period
827 until= noLinearTime; // last day
828store:
829 return true; // ok
830incompat:
831 // incompatible, value cannot be parsed usefully
832 return false; // no value generated
833} // RRULE1toInternal
834
835
836/// @brief calculate end date of RRULE when count is specified
837/// @return true if repeating, false if not repeating at all
838/// @note returns until=noLinearTime for endless repeat (count=0)
839bool endDateFromCount(
840 lineartime_t &until,
841 lineartime_t dtstart,
842 char freq, char freqmod,
843 sInt16 interval,
844 fieldinteger_t firstmask,fieldinteger_t lastmask,
845 sInt16 cnt, bool countsoccurrences,
846 TDebugLogger *aLogP
847)
848{
849 // count<=0 means endless
850 if (cnt<=0) {
851 until= noLinearTime; // forever, we don't need a start date for this
852 return true; // ok
853 }
854 // check no-rep cases
855 if (cnt<0) return false; // negative count, no repeat
856 if (dtstart==noLinearTime) return false; // no start date, cannot calc end date -> no rep (but no error)
857 if (interval<=0) return false; // interval=0 means no recurrence (but no error)
858 // default to dtstart
859 until = dtstart;
860 // calculate elements of start point
861 sInt16 startwday;
862 sInt16 startyear,startmonth,startday;
863 lineartime_t starttime;
864 starttime = lineartime2timeonly(dtstart); // start time of day
865 startwday = lineartime2weekday(dtstart); // get starting weekday
866 lineartime2date(dtstart,&startyear,&startmonth,&startday); // year, month, day-in-month
867 // calculate interval repetitions (which is what is needed for daily and RRULE v1 calculation)
868 sInt16 ivrep = (cnt-1)*interval;
869 // check if daily
870 if (freq == 'D') {
871 // Daily recurrence is same for occurrence and interval counts
872 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: daily calc - same in all cases")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: daily calc - same in all cases"
); }
;
873 until = dtstart+(ivrep*linearDateToTimeFactor);
874 return true;
875 }
876 else if (!countsoccurrences) {
877 // RRULE v1 interpretation of count (=number of repetitions of interval, not number of occurrences)
878 // - determine end of recurrence interval (which is usually NOT a occurrence precisely)
879 switch (freq)
880 {
881 case 'W':
882 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: simple weekly calc")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: simple weekly calc"
); }
;
883 // v1 end date calc, we need to take into account possible masks, so result must be end of interval, not just start date+interval
884 // weekly: end date is last day of target week
885 until=dtstart+((ivrep*DaysOfWeek-startwday+6)*linearDateToTimeFactor);
886 return true;
887 case 'M':
888 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: simple monthly calc")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: simple monthly calc"
); }
;
889 startmonth--; // make 0 based
890 startmonth += ivrep+1; // add number of months plus one (as we want next month, and then go one day back to last day of month)
891 startyear += startmonth / 12; // update years
892 startmonth = startmonth % 12 + 1; // update month and make 1 based again
893 // - calculate last day in month of occurrence
894 until = (date2lineardate(startyear,startmonth,1)-1)*linearDateToTimeFactor+starttime;
895 return true;
896 case 'Y':
897 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: simple yearly calc")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: simple yearly calc"
); }
;
898 // yearly: end date is end of end year
899 until = (date2lineardate(startyear+ivrep,12,31))*linearDateToTimeFactor+starttime;
900 return true;
901 }
902 }
903 else {
904 // v2 type counting, means that count specifies number of occurrences, not interval repetitions
905 // requires more elaborate expansion
906 // NOTE: this does not work without masks set, so we need to calculate the default masks if none are explicitly set
907 // - we need the number of days in the month in most cases
908 sInt16 lastday = getMonthDays(lineartime2dateonly(dtstart)); // number of days in this month
909 sInt16 newYearsPassed;
910 switch (freq)
911 {
912 case 'W':
913 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: full expansion weekly calc")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: full expansion weekly calc"
); }
;
914 // - make sure we have a mask
915 if (firstmask==0 && lastmask==0)
916 firstmask = 1<<startwday; // set start day in mask
917 // now calculate
918 while (firstmask) {
919 if (firstmask & ((uInt64)1<<startwday)) {
920 // found an occurrence
921 cnt--; // count it
922 if (cnt<=0) break; // found all
923 }
924 // increment day
925 until+=linearDateToTimeFactor;
926 startwday++;
927 if (startwday>6) {
928 // new week starts
929 startwday=0;
930 // skip part of interval which has no occurrence
931 until+=(interval-1)*7*linearDateToTimeFactor;
932 }
933 }
934 return true;
935 case 'M':
936 if (freqmod=='W') {
937 // monthly by weekday
938 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: full expansion of monthly by weekday")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: full expansion of monthly by weekday"
); }
;
939 // - make sure we have a mask
940 if (firstmask==0 && lastmask==0)
941 firstmask = (uInt64)1<<(startwday+7*((startday-1)/7)); // set start day in mask
942 // - now calculate
943 while (firstmask || lastmask) {
944 // calculate which weeks we are in
945 sInt16 fwk=(startday-1) / 7; // start is nth week of the month
946 sInt16 lwk=(lastday-startday) / 7; // start is nth-last week of the month
947 if (
948 (firstmask & ((uInt64)1<<(startwday+7*fwk))) || // nth occurrence of weekday in month
949 (lastmask & ((uInt64)1<<(startwday+7*lwk))) // nth-last occurrence of weekday in month
950 ) {
951 // found an occurrence
952 cnt--; // count it
953 if (cnt<=0) break; // found all
954 }
955 // increment day
956 until+=linearDateToTimeFactor;
957 startday++; // next day in month
958 startwday++; if (startwday>6) startwday=0; // next day in the week
959 // check for new month
960 if (startday>lastday) {
961 // new month starts
962 sInt16 i=interval;
963 while (true) {
964 lastday = getMonthDays(lineartime2dateonly(until)); // number of days in next month
965 startday = 1; // start at 1st of month again
966 if (--i == 0) break; // done
967 // skip entire next month
968 until+=lastday*linearDateToTimeFactor; // advance by number of days in this month
969 }
970 // now recalculate weekday
971 startwday=lineartime2weekday(until); // calculation continues here
972 }
973 }
974 }
975 else {
976 // everything else, including no modifier, is treated as monthly by monthday
977 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: full expansion of monthly by monthday")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: full expansion of monthly by monthday"
); }
;
978 // - make sure we have a mask
979 if (firstmask==0 && lastmask==0)
980 firstmask = (uInt64)1<<(startday-1); // set start day in mask
981 // - now calculate
982 while (firstmask || lastmask) {
983 if (
984 (firstmask & ((uInt64)1<<(startday-1))) || // nth day in month
985 (lastmask & ((uInt64)1<<(lastday-startday))) // nth-last day in month
986 ) {
987 // found an occurrence
988 cnt--; // count it
989 if (cnt<=0) break; // found all
990 }
991 // increment day
992 until+=linearDateToTimeFactor;
993 startday++; // next day in month
994 if (startday>lastday) {
995 // new month starts
996 sInt16 i=interval;
997 while (true) {
998 lastday = getMonthDays(lineartime2dateonly(until)); // number of days in next month
999 startday = 1; // start at 1st of month again
1000 if (--i == 0) break; // done
1001 // skip entire next month
1002 until+=lastday*linearDateToTimeFactor; // advance by number of days in this month
1003 }
1004 }
1005 }
1006 }
1007 return true;
1008 case 'Y':
1009 if (freqmod=='M') {
1010 // Yearly by month
1011 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: full expansion of yearly by month")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: full expansion of yearly by month"
); }
;
1012 // - make sure we have a mask
1013 if (firstmask==0 && lastmask==0)
1014 firstmask = (uInt64)1<<(startmonth-1); // set start month in mask
1015 // - do entire calculation on 1st of month such that we can be sure that day exists (unlike a Feb 30th or April 31th)
1016 until -= (startday-1)*linearDateToTimeFactor;
1017 // - now calculate
1018 newYearsPassed=0;
1019 // occurrence interval can be at most 4 years in the future (safety abort)
1020 while (newYearsPassed<=4) {
1021 if (firstmask & ((uInt64)1<<(startmonth-1))) {
1022 // possibly found an occurrence
1023 // - is an occurrence only if that day exists in the month
1024 if (startday<=lastday) {
1025 cnt--; // count it
1026 newYearsPassed = 0;
1027 if (cnt<=0) break; // found all
1028 }
1029 }
1030 // go to same day in next month (and skip months that don't have that day, like an 31st April or 30Feb
1031 until+=lastday*linearDateToTimeFactor;
1032 startmonth++;
1033 if (startmonth>12) {
1034 // new year starts
1035 startmonth=1;
1036 newYearsPassed++;
1037 // skip additional years (in month steps)
1038 for (sInt16 i=(interval-1)*12; i>0; i--) {
1039 lastday = getMonthDays(lineartime2dateonly(until)); // number of days in next month
1040 // skip month
1041 until+=lastday*linearDateToTimeFactor; // advance by number of days in this month
1042 }
1043 }
1044 // get size of next month to check
1045 lastday = getMonthDays(lineartime2dateonly(until)); // number of days in next month
1046 }
1047 // move back to start day
1048 until += (startday-1)*linearDateToTimeFactor;
1049 }
1050 else {
1051 // everything else, including no modifier, is treated as yearly on the same date (multiple occurrences per year not supported)
1052 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("endDateFromCount: full expansion of yearly by yearday - NOT SUPPORTED with more than one day")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("endDateFromCount: full expansion of yearly by yearday - NOT SUPPORTED with more than one day"
); }
;
1053 until = (date2lineardate(startyear+ivrep,startmonth,startday))*linearDateToTimeFactor+starttime;
1054 }
1055 return true;
1056 } // switch
1057 }
1058 // no recurrence
1059 return false;
1060} // endDateFromCount
1061
1062
1063
1064#ifdef DEBUG
1065/*
1066// %%%% hack debug
1067#define SYNTHESIS_UNIT_TEST 1
1068#define UNIT_TEST_TITLE(a)
1069#define UNIT_TEST_CALL(x,p,t,v)
1070*/
1071#endif
1072
1073#ifdef SYNTHESIS_UNIT_TEST
1074
1075// helper to create lineartime parameters
1076static lineartime_t t(char *aTime)
1077{
1078 if (!aTime || *aTime==0)
1079 return noLinearTime;
1080 timecontext_t tctx;
1081 lineartime_t res;
1082 ISO8601StrToTimestamp(aTime, res, tctx);
1083 return res;
1084}
1085
1086// helper to show lineartime results
1087static char buf[100];
1088char *s(lineartime_t aTime)
1089{
1090 if (aTime==noLinearTime)
1091 return "<none>";
1092 string str;
1093 TimestampToISO8601Str(str, aTime, TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)), true, false);
1094 strcpy(buf,str.c_str());
1095 return buf;
1096}
1097
1098
1099// RRULE expansion tests
1100bool test_expand_rrule(void)
1101{
1102 bool ok=true;
1103 lineartime_t lt;
1104 TRRuleExpandStatus es;
1105
1106 {
1107 UNIT_TEST_TITLE("Birthday 1");
1108 UNIT_TEST_CALL(initRRuleExpansion(es,t("2008-03-31T00:00:00"),'Y','M',1,0x4,0x0,t("2009-03-31T00:00:00"),t("2009-04-01T00:00:00")),("-none-"),true,ok);
1109 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-31T00:00:00"),ok);
1110 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 3)
1111
1112 UNIT_TEST_TITLE("Birthday 2");
1113 UNIT_TEST_CALL(initRRuleExpansion(es,t("1979-03-06T00:00:00"),'Y','M',1,0x4,0x0,t("2009-03-31T00:00:00"),t("2009-04-01T00:00:00")),("-none-"),true,ok);
1114 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // no occurrences (checked 32)
1115
1116 UNIT_TEST_TITLE("Birthday 3");
1117 UNIT_TEST_CALL(initRRuleExpansion(es,t("2006-03-13T00:00:00"),'Y','M',1,0x4,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1118 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-13T00:00:00"),ok);
1119 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 5)
1120
1121 UNIT_TEST_TITLE("Pay day");
1122 UNIT_TEST_CALL(initRRuleExpansion(es,t("2006-06-20T00:00:00"),'M','D',1,0x80000,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1123 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-20T00:00:00"),ok);
1124 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 35)
1125
1126 UNIT_TEST_TITLE("St. Patrick's Day");
1127 UNIT_TEST_CALL(initRRuleExpansion(es,t("2006-03-17T00:00:00"),'Y','M',1,0x4,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1128 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-17T00:00:00"),ok);
1129 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 5)
1130
1131 // Expanding 'Change fish tank filter':
1132 UNIT_TEST_TITLE("Change fish tank filter");
1133 UNIT_TEST_CALL(initRRuleExpansion(es,t("2007-10-21T00:00:00"),'W','W',5,0x1,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1134 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-29T00:00:00"),ok);
1135 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 17)
1136
1137 // Expanding 'Start of British Summer Time':
1138 UNIT_TEST_TITLE("Start of British Summer Time");
1139 UNIT_TEST_CALL(initRRuleExpansion(es,t("2003-03-30T00:00:00"),'M','W',12,0x0,0x1,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1140 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-29T00:00:00"),ok);
1141 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 8)
1142
1143 UNIT_TEST_TITLE("Birthday 4");
1144 UNIT_TEST_CALL(initRRuleExpansion(es,t("1957-03-14T00:00:00"),'Y','M',1,0x4,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1145 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-14T00:00:00"),ok);
1146 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 54)
1147
1148 UNIT_TEST_TITLE("Buy Lottery tickets");
1149 UNIT_TEST_CALL(initRRuleExpansion(es,t("2007-08-11T00:00:00"),'W','W',3,0x40,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1150 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-02-28T00:00:00"),ok);
1151 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-21T00:00:00"),ok);
1152 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 30)
1153
1154 UNIT_TEST_TITLE("Recycling bin collection");
1155 UNIT_TEST_CALL(initRRuleExpansion(es,t("2007-08-17T00:00:00"),'W','W',2,0x20,0x0,t("2009-02-23T00:00:00"),t("2009-03-31T00:00:00")),("-none-"),true,ok);
1156 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-02-27T00:00:00"),ok);
1157 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-13T00:00:00"),ok);
1158 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-03-27T00:00:00"),ok);
1159 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok); // generated occurrences (checked 44)
1160
1161 UNIT_TEST_TITLE("test feb 29th");
1162 UNIT_TEST_CALL(initRRuleExpansion(es,t("2008-02-29T14:00:00"),'Y','M',1,0x0,0x0,t("2009-04-01T00:00:00"),t("2019-04-04T00:00:00")),("-none-"),true,ok);
1163 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2012-02-29T14:00:00"),ok);
1164 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2016-02-29T14:00:00"),ok);
1165 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok);
1166
1167 UNIT_TEST_TITLE("February and March 29th every year");
1168 UNIT_TEST_CALL(initRRuleExpansion(es,t("2008-02-29T14:00:00"),'Y','M',1,0x6,0x0,t("2009-04-01T00:00:00"),t("2016-04-04T00:00:00")),("-none-"),true,ok);
1169 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2010-03-29T14:00:00"),ok);
1170 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2011-03-29T14:00:00"),ok);
1171 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2012-02-29T14:00:00"),ok); // leap year
1172 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2012-03-29T14:00:00"),ok);
1173 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2013-03-29T14:00:00"),ok);
1174 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2014-03-29T14:00:00"),ok);
1175 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2015-03-29T14:00:00"),ok);
1176 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2016-02-29T14:00:00"),ok); // leap year
1177 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2016-03-29T14:00:00"),ok);
1178 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok);
1179
1180 UNIT_TEST_TITLE("suddenly expanding from start problem");
1181 UNIT_TEST_CALL(initRRuleExpansion(es,t("2007-08-17"),'W','W',2,0x20,0x0,t("2009-07-04"),t("2009-08-08")),("-none-"),true,ok);
1182 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-07-17"),ok);
1183 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t("2009-07-31"),ok);
1184 UNIT_TEST_CALL(lt = getNextOccurrence(es),("lt = %s",s(lt)),lt==t(""),ok);
1185
1186 }
1187 return ok;
1188}
1189
1190#endif // SYNTHESIS_UNIT_TEST
1191
1192
1193
1194/// @brief initialize expansion of RRule
1195void initRRuleExpansion(
1196 TRRuleExpandStatus &es,
1197 lineartime_t aDtstart,
1198 char aFreq, char aFreqmod,
1199 sInt16 aInterval,
1200 fieldinteger_t aFirstmask, fieldinteger_t aLastmask,
1201 lineartime_t aExpansionStart,
1202 lineartime_t aExpansionEnd
1203)
1204{
1205 // %%% hardcoded for now
1206 es.weekstart = 0; // starts on sunday for now
1207 // save recurrence parameters
1208 es.freq = aFreq;
1209 es.freqmod = aFreqmod;
1210 es.interval = aInterval;
1211 es.firstmask = aFirstmask;
1212 es.lastmask = aLastmask;
1213 // analyze bits
1214 es.singleFMaskBit = -1;
1215 if (es.lastmask==0 && es.firstmask) {
1216 fieldinteger_t m = es.firstmask;
1217 es.singleFMaskBit = 0;
1218 while((m & 1)==0) { m>>=1; es.singleFMaskBit++; } // calculate bit number of next set bit
1219 if (m & ~1) es.singleFMaskBit = -1; // more bits to come - reset again
1220 }
1221 // init expansion parameters
1222 // - no valid occurrence yet
1223 es.started = false;
1224 // - elements of start point
1225 es.starttime = lineartime2timeonly(aDtstart); // start time of day
1226 lineartime2date(aDtstart,&es.startyear,&es.startmonth,&es.startday); // year, month, day-in-month
1227 // - cursor, initialized to start point
1228 es.cursor = aDtstart;
1229 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1230 es.cursorWDay = lineartime2weekday(es.cursor); // get cursor weekday
1231 // save expansion range
1232 es.expansionEnd = aExpansionEnd; // can be noLinearTime if no end is set
1233 es.expansionStartDayOffset = 0;
1234 if (aExpansionStart!=noLinearTime) {
1235 // specified start of expansion period, apply if later than beginning of recurrence
1236 es.expansionStartDayOffset = (lineartime2dateonly(aExpansionStart)-lineartime2dateonly(es.cursor));
1237 if (es.expansionStartDayOffset<0)
1238 es.expansionStartDayOffset = 0;
1239 }
1240 #ifdef SYNTHESIS_UNIT_TEST
1241 sInt16 y,m,d,h,mi,s,ms;
1242 lineartime2date(aExpansionStart, &y, &m, &d);
1243 lineartime2time(aExpansionStart, &h, &mi, &s, &ms);
1244 printf(" aExpansionStart = %04hd-%02hd-%02hd %02hd:%02hd:%02hd - es.expansionStartDayOffset=%ld\n",y,m,d,h,mi,s,es.expansionStartDayOffset);
1245 #endif
1246}
1247
1248
1249// advance cursor by given number of days
1250static void adjustCursor(TRRuleExpandStatus &es, lineardate_t aDays)
1251{
1252 // now days = number of days to advance
1253 es.cursor += aDays*linearDateToTimeFactor;
1254 // adjust weekday
1255 es.cursorWDay += aDays % DaysPerWk;
1256 if (es.cursorWDay<0) es.cursorWDay += DaysPerWk;
1257 else if (es.cursorWDay>=DaysPerWk) es.cursorWDay -= DaysPerWk;
1258 #ifdef SYNTHESIS_UNIT_TEST
1259 sInt16 y,m,d,h,mi,s,ms;
1260 lineartime2date(es.cursor, &y, &m, &d);
1261 lineartime2time(es.cursor, &h, &mi, &s, &ms);
1262 printf(" es.cursor = %04hd-%02hd-%02hd %02hd:%02hd:%02hd / Day=%hd, lt=%lld\n",y,m,d,h,mi,s,es.cursorWDay,es.cursor);
1263 #endif
1264}
1265
1266
1267static lineardate_t makeMultiple(lineardate_t d, sInt16 aMultiple, sInt16 aMaxRemainder=0)
1268{
1269 if (aMultiple>1) {
1270 // we need to make sure remainder is 0 and advance extra
1271 lineardate_t r = d % aMultiple; // remainder
1272 if (r>aMaxRemainder) {
1273 // round up to next multiple
1274 d += aMultiple - r;
1275 }
1276 }
1277 return d;
1278}
1279
1280
1281// simple and fast cursor increment
1282static void incCursor(TRRuleExpandStatus &es)
1283{
1284 es.cursor += linearDateToTimeFactor;
1285 es.cursorWDay += 1;
1286 if (es.cursorWDay>=DaysPerWk) es.cursorWDay=0;
1287}
1288
1289
1290static bool expansionEnd(TRRuleExpandStatus &es)
1291{
1292 if (es.expansionEnd && es.cursor>=es.expansionEnd) {
1293 // end of expansion, reset cursor
1294 es.cursor = noLinearTime;
1295 return true;
1296 }
1297 // not yet end of expansion
1298 return false;
1299}
1300
1301
1302
1303
1304
1305static sInt16 monthDiff(sInt16 y1, sInt16 m1, sInt16 y2, sInt16 m2)
1306{
1307 return (y1-y2)*12 + (m1-m2);
1308}
1309
1310
1311static void monthAdd(sInt16 &y, sInt16 &m, sInt16 a)
1312{
1313 m += a;
1314 if (m>12) {
1315 sInt16 r = (m-1)/12; // years plus
1316 y += r; // add the years
1317 m -= (r*12); // remove from the months
1318 }
1319}
1320
1321
1322
1323
1324/// @brief get next occurrence
1325/// @return noLinearTime if no next occurrence exists, lineartime of next occurrence otherwise
1326lineartime_t getNextOccurrence(TRRuleExpandStatus &es)
1327{
1328 if (es.cursor==noLinearTime) return noLinearTime; // already done (or never started properly)
1329 if (es.interval<=0) return noLinearTime; // interval=0 means no recurrence (but no error)
1330 // if we are already started, we need to advance the cursor first, then check for match
1331 bool advanceFirst = es.started;
1332 // now calculate
1333 if (es.freq=='D') {
1334 // Daily: can be calculated directly
1335 if (!es.started) {
1336 // calculate first occurrence
1337 adjustCursor(es, makeMultiple(es.expansionStartDayOffset, es.interval)); // move cursor to first day
1338 }
1339 else {
1340 // calculate next occurrence
1341 adjustCursor(es, es.interval);
1342 }
1343 } // D
1344 else if (es.freq=='W') {
1345 // Note: interval of weekly recurrences starts at sunday,
1346 // so recurrence starting at a Thu, scheduled for every two weeks on Mo and Thu will have:
1347 // 1st week: Thu, 2nd week: nothing, 3rd week: Mo,Thu, 4rd week: nothing, 5th week: Mo,Thu...
1348 if (!es.started) {
1349 // make sure we have a mask
1350 if (es.firstmask==0)
1351 es.firstmask = 1<<es.cursorWDay; // set start day in mask
1352 // advance cursor to first day of expansion period
1353 // - go back to start of very first week covered by the recurrence
1354 sInt16 woffs = es.cursorWDay-es.weekstart; // how many days back to next week start from dtstart
1355 if (woffs<0) woffs+=DaysPerWk; // we want to go BACK to previous week start
1356 adjustCursor(es, -woffs); // back to previous start of week (=start of first week covered by recurrence)
1357 lineardate_t d = es.expansionStartDayOffset+woffs; // days from beginning of first week to beginning day of expansion
1358 // if we exceed reach of mask in current interval, round up to beginning of next interval
1359 d = makeMultiple(d,es.interval*DaysPerWk,DaysPerWk-1); // expand into next interval if needed
1360 adjustCursor(es, d); // now advance cursor to
1361 if (expansionEnd(es)) goto done; // could by beyond current expanding scope due to interval jump
1362 }
1363 // calculate first/next occurrence (if not first occurrence, do not check initially but advance first)
1364 while(advanceFirst || !(
1365 es.firstmask & ((uInt64)1<<es.cursorWDay)
1366 )) {
1367 advanceFirst = false; // next time we need to check
1368 // - next day is next candidate
1369 incCursor(es); // next day
1370 if (es.cursorWDay==es.weekstart) {
1371 // a new week has started, we need to skip non-active weeks first
1372 adjustCursor(es, (es.interval-1)*DaysPerWk);
1373 }
1374 }
1375 // found occurrence
1376 } // W
1377 else if (es.freq=='M') {
1378 if (!es.started) {
1379 // make sure we have a mask
1380 if (es.firstmask==0 && es.lastmask==0) {
1381 if (es.freqmod=='W') {
1382 es.firstmask = (uInt64)1<<(es.cursorWDay+DaysPerWk*((es.startday-1)/DaysPerWk)); // set start weekday in mask
1383 }
1384 else {
1385 es.firstmask = (uInt64)1<<(es.startday-1); // set start monthday in mask
1386 }
1387 }
1388 // For all monthly repeats: find first month to apply freqmod to
1389 if (es.expansionStartDayOffset) {
1390 // does not necessarily start in cursor month
1391 // - move cursor to where we want to begin earliest
1392 adjustCursor(es, es.expansionStartDayOffset);
1393 // - components of the expansion start
1394 sInt16 y,m;
1395 lineartime2date(es.cursor,&y,&m,&es.startday); // year, month, day-in-month
1396 // - how many months since start
1397 sInt16 numMonths = monthDiff(y,m,es.startyear,es.startmonth);
1398 sInt16 monthMod = numMonths % es.interval;
1399 if (monthMod>0) {
1400 // need to go into subsequent month
1401 numMonths += es.interval-monthMod; // months to next interval start
1402 // entire month is after expansion start, so begin with 1st
1403 es.startday = 1;
1404 }
1405 monthAdd(es.startyear,es.startmonth,numMonths);
1406 // startYear/month/day now on first candidate
1407 // - calculate linear start
1408 es.cursor = date2lineartime(es.startyear, es.startmonth, es.startday);
1409 if (expansionEnd(es)) goto done; // could by beyond current expanding scope due to interval jump
1410 // - calculate last day in this month
1411 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1412 // - make sure cursor weekday is correct
1413 es.cursorWDay = lineartime2weekday(es.cursor);
1414 }
1415 }
1416 // apply freqmod
1417 if (es.freqmod=='W') {
1418 // monthly by weekday
1419 // - calculate first/next occurrence (if not first occurrence, do not check initially but advance first)
1420 while(true) {
1421 // calculate which weeks we are in
1422 sInt16 fwk=(es.startday-1) / DaysPerWk; // start is nth week of the month
1423 sInt16 lwk=(es.cursorMLen-es.startday) / DaysPerWk; // start is nth-last week of the month
1424 // check if we found an occurrence
1425 if (!advanceFirst) {
1426 if (
1427 (es.firstmask & ((uInt64)1<<(es.cursorWDay+DaysPerWk*fwk))) || // nth occurrence of weekday in month
1428 (es.lastmask & ((uInt64)1<<(es.cursorWDay+DaysPerWk*lwk))) // nth-last occurrence of weekday in month
1429 )
1430 break;
1431 }
1432 advanceFirst = false; // next time we need to check
1433 // - next day-in-month is next candidate
1434 es.cursorWDay++; if (es.cursorWDay>=DaysPerWk) es.cursorWDay=0; // next day in the week
1435 es.startday++;
1436 if (es.startday>es.cursorMLen) {
1437 // end of month reached
1438 // - goto next relevant month
1439 monthAdd(es.startyear,es.startmonth,es.interval);
1440 // - first day
1441 es.startday=1;
1442 // - advance cursor into new month
1443 es.cursor = date2lineartime(es.startyear, es.startmonth, es.startday);
1444 // - get length of this month
1445 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1446 // - make sure cursor weekday is correct
1447 es.cursorWDay = lineartime2weekday(es.cursor);
1448 }
1449 }
1450 // found occurrence, update cursor
1451 es.cursor = date2lineartime(es.startyear, es.startmonth, es.startday)+es.starttime;
1452 } // MW
1453 else {
1454 // monthly by monthday list
1455 // - calculate first/next occurrence (if not first occurrence, do not check initially but advance first)
1456 while(advanceFirst || !(
1457 (es.firstmask & ((uInt64)1<<(es.startday-1))) || // nth day in month
1458 (es.lastmask & ((uInt64)1<<(es.cursorMLen-es.startday))) // nth-last day in month
1459 )) {
1460 advanceFirst = false; // next time we need to check
1461 if (es.singleFMaskBit>=0) {
1462 // - next candidate is indicated by single mask bit
1463 if (es.startday<es.singleFMaskBit+1)
1464 es.startday = es.singleFMaskBit+1; // jump to start day
1465 else
1466 es.startday = es.cursorMLen+1; // jump to end of month
1467 }
1468 else {
1469 // - next day-in-month is next candidate
1470 es.startday++;
1471 }
1472 if (es.startday>es.cursorMLen) {
1473 // end of month reached
1474 // - goto next relevant month
1475 monthAdd(es.startyear,es.startmonth,es.interval);
1476 // - first day
1477 es.startday=1;
1478 // - advance cursor into new month
1479 es.cursor = date2lineartime(es.startyear, es.startmonth, es.startday);
1480 // - get length of this month
1481 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1482 }
1483 }
1484 // found occurrence, update cursor
1485 es.cursor = date2lineartime(es.startyear, es.startmonth, es.startday)+es.starttime;
1486 } // MD
1487 } // M
1488 else if (es.freq=='Y') {
1489 if (!es.started) {
1490 // Make sure we have a mask or cancel freqmod entirely
1491 if (es.firstmask==0 && es.lastmask==0) {
1492 // no mask at all, only startmonth counts anyway -> revert to simple direct calculation (cancel freqmod)
1493 es.freqmod = 0;
1494 }
1495 else if (es.singleFMaskBit>=0 && es.singleFMaskBit+1==es.startmonth) {
1496 // mask with single bit on start date month -> can revert to simple diect calculation (cancel freqmod)
1497 es.freqmod = 0; // no month list parsing needed
1498 }
1499 // For all yearly repeats: find first year to apply freqmod to
1500 if (es.expansionStartDayOffset) {
1501 // does not necessarily start in cursor year
1502 // - move cursor to where we want to begin earliest
1503 adjustCursor(es, es.expansionStartDayOffset);
1504 // - year of the expansion start
1505 sInt16 y,m,d;
1506 lineartime2date(es.cursor,&y,&m,&d); // year, month, day-in-month
1507 // - make sure cursor can be a candidate (i.e. is not before start)
1508 if (es.freqmod=='M') {
1509 if (d>es.startday)
1510 monthAdd(y, m, 1); // no occurrence possible in this month, next candidate is in next month
1511 }
1512 else {
1513 if (m>es.startmonth || (m==es.startmonth && d>es.startday))
1514 y++; // no occurrence possible this year, next candidate is in next year
1515 }
1516 // - make sure we are in a year at the beginning of the interval
1517 sInt16 numYears = y-es.startyear;
1518 sInt16 yearMod = numYears % es.interval;
1519 if (yearMod>0) {
1520 // need to go into subsequent year
1521 numYears += es.interval-yearMod; // months to next interval start
1522 m = 1; // start with Jan again (only used below when checking monthlist i.e. freqmod==M)
1523 }
1524 es.startyear += numYears;
1525 if (es.freqmod=='M') {
1526 // month-by-month checking
1527 es.startmonth = m;
1528 }
1529 // startYear/month/day now on first candidate
1530 // - calculate linear start
1531 es.cursor = date2lineartime(es.startyear, es.startmonth, 1)+es.starttime;
1532 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1533 adjustCursor(es, es.startday-1);
1534 if (expansionEnd(es)) goto done; // could by beyond current expanding scope due to interval jump
1535 // - calculate last day in this month
1536 }
1537 }
1538 // apply freqmod
1539 if (es.freqmod=='M') {
1540 // yearly by month list (and we really have a month list, not only a single month bit)
1541 // - calculate first/next occurrence (if not first occurrence, do not check initially but advance first)
1542 int yearsscanned = 0;
1543 do {
1544 while(advanceFirst || !(
1545 (es.firstmask & ((uInt64)1<<(es.startmonth-1))) || // nth month
1546 (es.lastmask & ((uInt64)1<<(12-es.startmonth))) // nth-last month
1547 )) {
1548 advanceFirst = false; // next time we need to check
1549 // - next month is next candidate
1550 es.startmonth++;
1551 if (es.startmonth>12) {
1552 // end of year reached
1553 // - goto next relevant year
1554 es.startyear += es.interval;
1555 yearsscanned ++;
1556 // - first month again
1557 es.startmonth = 1;
1558 }
1559 }
1560 // found possible occurrence (could be a day that does not exist in the month), update cursor
1561 es.cursor = date2lineartime(es.startyear, es.startmonth, 1)+es.starttime;
1562 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1563 adjustCursor(es, es.startday-1);
1564 // repeat until day actually exists
1565 if (es.startday>es.cursorMLen) {
1566 // disable checking for match again
1567 advanceFirst = true;
1568 }
1569 } while(advanceFirst && yearsscanned<5);
1570 // found occurrence
1571 } // YM
1572 else {
1573 // yearly on same date
1574 while (advanceFirst || es.startday>es.cursorMLen ) {
1575 advanceFirst = false; // next time we need to check
1576 // just calculate next candiate
1577 es.startyear += es.interval;
1578 // update cursor and calculate
1579 es.cursor = date2lineartime(es.startyear, es.startmonth, 1)+es.starttime;
1580 es.cursorMLen = getMonthDays(lineartime2dateonly(es.cursor));
1581 adjustCursor(es, es.startday-1);
1582 }
1583 // found occurrence
1584 } // Yx
1585 } // Y
1586 else {
1587 // unknown = nonexpandable recurrence
1588 // - return start date as first occurrence to make sure it does not get invisible
1589 if (es.started) {
1590 // subsequent occurrence requested: not available
1591 es.cursor = noLinearTime;
1592 }
1593 }
1594 // stop expansion if end of expansion period reached
1595 expansionEnd(es);
1596done:
1597 // done
1598 es.started = true;
1599 return es.cursor;
1600} // getNthNextOccurrence
1601
1602
1603
1604
1605
1606
1607
1608
1609/// @brief calculate number of recurrences from specified end date of RRULE
1610/// @return true if count could be calculated, false otherwise
1611/// @param[in] dtstart, until : must be in the correct context to evaluate weekday rules
1612/// @param[in] countsoccurrences : if true, counting occurrences (rather than intervals)
1613/// @note returns until=noLinearTime for endless repeat (count=0)
1614bool countFromEndDate(
1615 sInt16 &cnt, bool countsoccurrences,
1616 lineartime_t dtstart,
1617 char freq, char freqmod,
1618 sInt16 interval,
1619 fieldinteger_t firstmask,fieldinteger_t lastmask,
1620 lineartime_t until,
1621 TDebugLogger *aLogP
1622)
1623{
1624 if (dtstart==noLinearTime || interval<1) return false; // no start date or interval, cannot calc count
1625 if (until==noLinearTime) {
1626 // endless repeat
1627 cnt=0;
1628 return true;
1629 }
1630 // iterate until end date is reached
1631 cnt=1;
1632 lineartime_t occurrence=noLinearTime;
1633 // break after 500 recurrences
1634 while (cnt<500) {
1635 if (!endDateFromCount(
1636 occurrence,
1637 dtstart,
1638 freq,freqmod,
1639 interval,
1640 firstmask,lastmask,
1641 cnt,
1642 countsoccurrences,
1643 aLogP
1644 ))
1645 return false; // error, cannot calc end date
1646 if (occurrence>until) {
1647 // no more occurrences
1648 cnt--;
1649 if (cnt==0) return false; // if not endless, but no occurrence found in range -> not repeating
1650 return true; // return count
1651 }
1652 // next occurrence
1653 cnt++;
1654 }
1655 // 500 and more recurrences count as endless
1656 cnt=0;
1657 return true;
1658} // countFromEndDate
1659
1660
1661
1662// Converts vCalendar 2.0 RRULE string into internal recurrence representation
1663bool RRULE2toInternal(
1664 const char *aText, // RRULE string to be parsed
1665 lineartime_t dtstart, // reference date for parsing RRULE
1666 timecontext_t startcontext, // context of reference date
1667 char &freq,
1668 char &freqmod,
1669 sInt16 &interval,
1670 fieldinteger_t &firstmask,
1671 fieldinteger_t &lastmask,
1672 lineartime_t &until,
1673 timecontext_t &untilcontext,
1674 TDebugLogger *aLogP,
1675 lineartime_t *aNewStartP
1676)
1677{
1678 #ifdef SYDEBUG2
1679 string abc;
1680 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("RRULE2toInternal(): start analyzing '%s'", aText)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("RRULE2toInternal(): start analyzing '%s'"
, aText); }
;
1681 #endif
1682
1683 string temp;
1684 const char *p;
1685 char s [50];
1686 sInt16 startwday;
1687 sInt16 startyear,startmonth,startday;
1688
1689 string::size_type endIndex = 0;
1690 string::size_type startIndex = 0;
1691 uInt16 cnt = 0;
1692 size_t pos;
1693 bool calculateEndDate = false; // indicator that end date calc is required as last step
1694 bool calculateFirstOccurrence = false; // indicator that aNewStartP should be adjusted to show the first occurrence
1695 string key,value,byday,bymonthday,bymonth;
1696
1697 // get elements of start point
1698 startwday=lineartime2weekday(dtstart); // get starting weekday
1699 lineartime2date(dtstart,&startyear,&startmonth,&startday); // year, month, day-in-month
1700 until= noLinearTime; // initialize end point, if not available
1701
1702 // do the conversion here
1703 p=aText;
1704 int start = 0;
1705 // get freq
1706 if (!getNextDirective(temp, p, start)) {
1
Taking false branch
1707 // failed
1708 goto incompat;
1709 }
1710 // analyze freq
1711 if (temp == "FREQ=YEARLY") {
2
Taking false branch
1712 freq='Y';
1713 }
1714 else if (temp == "FREQ=MONTHLY") {
3
Taking true branch
1715 freq='M';
1716 }
1717 else if (temp == "FREQ=WEEKLY") {
1718 freq='W';
1719 }
1720 else if (temp == "FREQ=DAILY") {
1721 freq='D';
1722 }
1723 else {
1724 goto incompat;
1725 }
1726
1727 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- found frequency %s which maps to freq %c", temp.c_str(), freq)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- found frequency %s which maps to freq %c"
, temp.c_str(), freq); }
;
1728
1729 // init vars
1730 cnt = 0;
1731
1732 // set interval to 1
1733 interval = 1;
1734 freqmod = ' ';
1735 firstmask = 0;
1736 lastmask = 0;
1737 // get next directives
1738 while (getNextDirective(temp, p, start)) {
4
Loop condition is false. Execution continues on line 1796
1739 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- found next directive %s", temp.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- found next directive %s"
, temp.c_str()); }
;
1740 // split
1741 pos = temp.find("=");
1742 if (pos == string::npos || pos == 0 || pos == (temp.length() - 1)) {
1743 goto incompat;
1744 }
1745 key = temp.substr(0, pos);
1746 value = temp.substr(pos + 1, temp.length() - 1);
1747 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted key/value %s/%s", key.c_str(), value.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted key/value %s/%s"
, key.c_str(), value.c_str()); }
;
1748 // check for until
1749 if (key == "UNTIL") {
1750 if (ISO8601StrToTimestamp(value.c_str(), until, untilcontext)==0)
1751 goto incompat; // invalid end date
1752 calculateEndDate = false;
1753 }
1754 // check for count
1755 else if (key == "COUNT") {
1756 // convert count
1757 for (int i = 0, ii = value.length(); i < ii; ++i) {
1758 if (isdigit(value[i])) {
1759 cnt = cnt * 10 + ((value[i]) - '0');
1760 }
1761 }
1762 // check if no count or one only
1763 if (cnt <= 1)
1764 goto norep; // no repetition
1765 // we need to recalculate the enddate from count
1766 calculateEndDate = true;
1767 }
1768 // check for interval
1769 else if (key == "INTERVAL") {
1770 // convert interval
1771 interval = 0;
1772 for (int i = 0, ii = value.length(); i < ii; ++i) {
1773 if (isdigit(value[i])) {
1774 interval = interval * 10 + ((value[i]) - '0');
1775 }
1776 }
1777 if (interval == 0)
1778 goto incompat; // invalid interval
1779 }
1780 // just copy all supported BYxxx rules into vars
1781 else if (key == "BYDAY")
1782 byday = value;
1783 else if (key == "BYMONTHDAY")
1784 bymonthday = value;
1785 else if (key == "BYMONTH")
1786 bymonth = value;
1787 // ignore week start
1788 else if (key == "WKST") {
1789 // nop
1790 }
1791 else
1792 goto incompat; // unknown directive
1793 } // while
1794
1795 #ifdef SYDEBUG2
1796 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- dtstart weekday is %i", startwday)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- dtstart weekday is %i"
, startwday); }
;
1797 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted interval %u", interval)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted interval %u"
, interval); }
;
1798 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted interval (2nd time) %u", interval)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted interval (2nd time) %u"
, interval); }
;
1799 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted count %u", cnt)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted count %u"
, cnt); }
;
1800 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- end date calc required? %s", calculateEndDate ? "true" : "false")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- end date calc required? %s"
, calculateEndDate ? "true" : "false"); }
;
1801 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted byday %s", byday.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted byday %s"
, byday.c_str()); }
;
1802 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted bymonthday %s", bymonthday.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted bymonthday %s"
, bymonthday.c_str()); }
;
1803 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted bymonth %s", bymonth.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted bymonth %s"
, bymonth.c_str()); }
;
1804 TimestampToISO8601Str(abc,until,startcontext,false,false);
1805 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- extracted until %s", abc.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- extracted until %s"
, abc.c_str()); }
;
1806 #endif
1807
1808 // Generally, we only support one BYxxx, not combinations. However, FREQ=YEARLY + BYMONTH + BYDAY is common
1809 // to express start and end times of DST in VTIMEZONEs, so we convert these into equivalent FREQ=MONTHLY
1810 if (freq=='Y' && !byday.empty() && !bymonth.empty() && bymonth.find(",")==string::npos) {
1811 // yearly by (single!) month and by day
1812 // - get month as indicated by BYMONTH
1813 sInt16 newStartMonth = 0;
1814 if (StrToShort(bymonth.c_str(), newStartMonth)>0 && newStartMonth) {
1815 // - check if BYMONTH is just redundant and references same month as start date
1816 bool convertToMonthly = startmonth>0 && startmonth==newStartMonth;
1817 if (!convertToMonthly && aNewStartP) {
1818 // not redundant, but we can return a new start date
1819 convertToMonthly = true;
1820 // calculate the new start date
1821 startmonth = newStartMonth;
1822 dtstart = date2lineartime(startyear, startmonth, startday) + lineartime2timeonly(dtstart);
1823 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- moved recurrence origin to %04hd-%02hd-%02hd",startyear, startmonth, startday)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- moved recurrence origin to %04hd-%02hd-%02hd"
,startyear, startmonth, startday); }
;
1824 // pass it back to caller
1825 calculateFirstOccurrence = true; // need to fine tune at the end
1826 *aNewStartP = dtstart;
1827 }
1828 if (convertToMonthly) {
1829 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- converted FREQ=YEARLY BYMONTH into MONTHLY")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- converted FREQ=YEARLY BYMONTH into MONTHLY"
); }
;
1830 freq = 'M'; // convert from YEARLY to MONTHLY
1831 interval = 12*interval; // but with 12 months interval -> equivalent
1832 bymonth.erase(); // forget BYMONTH clause
1833 }
1834 }
1835 } // if FREQ=YEARLY + BYMONTH + BYDAY
1836 // check freq
1837 endIndex = 0;
1838 startIndex = 0;
1839 switch (freq) {
5
Control jumps to 'case 77:' at line 1895
1840 case 'D' :
1841 // make weekly if byday and nothing else is set
1842 if (!(byday == "") && bymonth == "" && bymonthday == "") {
1843 freq='W';
1844 freqmod='W';
1845 // search separator ','
1846 while ((endIndex = byday.find(",", startIndex)) != string::npos) {
1847 // set masks for the specific day
1848 if (!setWeekday(byday, firstmask, lastmask, startIndex, endIndex, false))
1849 goto incompat;
1850 // set new start
1851 startIndex = endIndex + 1;
1852 if (startIndex >= byday.length())
1853 break;
1854 }
1855 // check if anything is behind endindex
1856 if (endIndex == string::npos && startIndex < byday.length()) {
1857 endIndex = byday.length();
1858 if (!setWeekday(byday, firstmask, lastmask, startIndex, endIndex, false))
1859 goto incompat;
1860 }
1861 }
1862 else {
1863 // we don't support byday and anything else at the same time
1864 if (!byday.empty())
1865 goto incompat;
1866 // ok, no mod
1867 freqmod = ' ';
1868 }
1869 break;
1870 case 'W' :
1871 // we don't support month or monthday within weekly
1872 if (!bymonth.empty() || !bymonthday.empty())
1873 goto incompat;
1874 // set weekly and days
1875 freqmod='W';
1876 if (!byday.empty()) {
1877 // search separator ','
1878 while ((endIndex = byday.find(",", startIndex)) != string::npos) {
1879 // set masks for the specific day
1880 if (!setWeekday(byday, firstmask, lastmask, startIndex, endIndex, false))
1881 goto incompat;
1882 // set new start
1883 startIndex = endIndex + 1;
1884 if (startIndex >= byday.length())
1885 break;
1886 }
1887 // check if anything is behind endindex
1888 if (endIndex == string::npos && startIndex < byday.length()) {
1889 endIndex = byday.length();
1890 if (!setWeekday(byday, firstmask, lastmask, startIndex, endIndex, false))
1891 goto incompat;
1892 }
1893 }
1894 break;
1895 case 'M' :
1896 // we don't support by month in monthly
1897 if (!bymonth.empty())
6
Assuming the condition is false
7
Taking false branch
1898 goto incompat;
1899 // we don't support byday and bymonthday at the same time
1900 if (!byday.empty() && !bymonthday.empty())
8
Assuming the condition is false
1901 goto incompat;
1902 // check if bymonthday
1903 if (!bymonthday.empty()) {
9
Assuming the condition is true
10
Taking true branch
1904 freqmod = 'D';
1905 // search separator ','
1906 while ((endIndex = bymonthday.find(",", startIndex)) != string::npos) {
11
Assuming the condition is false
12
Loop condition is false. Execution continues on line 1916
1907 // set masks for the specific day
1908 if (!setMonthDay(bymonthday, firstmask, lastmask, startIndex, endIndex))
1909 goto incompat;
1910 // set new start
1911 startIndex = endIndex + 1;
1912 if (startIndex >= bymonthday.length())
1913 break;
1914 }
1915 // check if anything is behind endindex
1916 if (endIndex == string::npos && startIndex < bymonthday.length()) {
13
Assuming the condition is true
14
Taking true branch
1917 endIndex = bymonthday.length();
1918 if (!setMonthDay(bymonthday, firstmask, lastmask, startIndex, endIndex))
15
Calling 'setMonthDay'
1919 goto incompat;
1920 }
1921 }
1922 // or if by weekday
1923 else if (!byday.empty()) {
1924 freqmod = 'W';
1925 // search separator ','
1926 while ((endIndex = byday.find(",", startIndex)) != string::npos) {
1927 // set masks for the specific day
1928 if (!setWeekday(byday, firstmask, lastmask, startIndex, endIndex, true))
1929 goto incompat;
1930 // set new start
1931 startIndex = endIndex + 1;
1932 if (startIndex >= byday.length())
1933 break;
1934 }
1935 // check if anything is behind endindex
1936 if (endIndex == string::npos && startIndex < byday.length()) {
1937 endIndex = byday.length();
1938 if (!setWeekday(byday, firstmask, lastmask, startIndex, endIndex, true))
1939 goto incompat;
1940 }
1941 }
1942 else {
1943 // fine, no mod
1944 freqmod = ' ';
1945 }
1946 break;
1947 case 'Y' :
1948 if (
1949 byday == "" ||
1950 (byday.length() == 2 && byday[0] == RRULE_weekdays[startwday][0] &&
1951 byday[1] == RRULE_weekdays[startwday][1])
1952 ) {
1953 temp.erase();
1954 sprintf(s, "%hd", startday);
1955 temp.append(s);
1956 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- bymonthday checking %s against %s", temp.c_str(), bymonthday.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- bymonthday checking %s against %s"
, temp.c_str(), bymonthday.c_str()); }
;
1957 if (bymonthday == "" || bymonthday == temp) {
1958 temp.erase();
1959 sprintf(s, "%hd", startmonth);
1960 temp.append(s);
1961 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- bymonth checking %s against %s", temp.c_str(), bymonth.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- bymonth checking %s against %s"
, temp.c_str(), bymonth.c_str()); }
;
1962 if (bymonth == "" || bymonth == temp) {
1963 // this should usually be ' ' but the vcard conversion has a bug and requires 'M'
1964 freqmod = 'M';
1965 lastmask = 0;
1966 firstmask = 0;
1967 }
1968 else
1969 goto incompat;
1970 }
1971 else
1972 goto incompat;
1973 }
1974 else
1975 goto incompat;
1976 break;
1977 default :
1978 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- strange self-set freq %c, rule was %s", freq, aText)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- strange self-set freq %c, rule was %s"
, freq, aText); }
;
1979 break;
1980 } // switch
1981
1982 // calc end date, assumption/make sure: cnt > 1
1983 if (calculateEndDate) {
1984 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- calculating end date now")){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- calculating end date now"
); }
;
1985 if (!endDateFromCount(until,dtstart,freq,freqmod,interval,firstmask,lastmask,cnt,true,aLogP))
1986 goto norep;
1987 untilcontext = startcontext; // until is in same context as start
1988 }
1989 // calculate exact date of first occurrence
1990 if (calculateFirstOccurrence && aNewStartP) {
1991 TRRuleExpandStatus es;
1992 initRRuleExpansion(es, dtstart, freq, freqmod, interval, firstmask, lastmask, dtstart, noLinearTime);
1993 dtstart = getNextOccurrence(es);
1994 if (LOGDEBUGTEST(aLogP,DBG_PARSE+DBG_EXOTIC)((aLogP) && (((0x00000200 +0x80000000) & (aLogP)->
getMask()) == (0x00000200 +0x80000000)))
) {
1995 sInt16 ny,nm,nd;
1996 lineartime2date(dtstart,&ny,&nm,&nd);
1997 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- adjusted first occurrence date to %04hd-%02hd-%02hd",ny, nm, nd)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- adjusted first occurrence date to %04hd-%02hd-%02hd"
,ny, nm, nd); }
;
1998 }
1999 *aNewStartP = dtstart;
2000 }
2001 // parsed ok, now store it
2002 goto store;
2003norep:
2004 // no repetition (but no parse error generated)
2005 freq='0'; // frequency = none
2006 freqmod=' '; // frequency modifier
2007 interval=0; // interval
2008 firstmask=0; // day mask counted from the first day of the period
2009 lastmask=0; // day mask counted from the last day of the period
2010 until= noLinearTime; // last day
2011store:
2012
2013 #ifdef SYDEBUG2
2014 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("- leaving with freq %c, freqmod %c, interval %hd, firstmask %ld, lastmask %ld", freq, freqmod, interval, (long)firstmask, (long)lastmask)){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("- leaving with freq %c, freqmod %c, interval %hd, firstmask %ld, lastmask %ld"
, freq, freqmod, interval, (long)firstmask, (long)lastmask); }
;
2015 TimestampToISO8601Str(abc,until,untilcontext,false,false);
2016 LOGDEBUGPRINTFX(aLogP,DBG_PARSE+DBG_EXOTIC,("RRULE2toInternal(): extracted until '%s'", abc.c_str())){ if ((aLogP) && ((0x00000200 +0x80000000) & (aLogP
)->getMask()) == (0x00000200 +0x80000000)) (aLogP)->setNextMask
(0x00000200 +0x80000000).DebugPrintfLastMask ("RRULE2toInternal(): extracted until '%s'"
, abc.c_str()); }
;
2017 #endif
2018
2019 return true; // ok
2020incompat:
2021 // incompatible, value cannot be parsed usefully
2022 return false; // no value generated
2023} // RRULE2toInternal
2024
2025
2026
2027// appends the firstmask and lastmask as numbers to the string.
2028// all values are increased by one prior to adding them to string.
2029void appendMaskAsNumbers(
2030 cAppCharP aPrefix, // if list is not empty, this will be appended in front of the list
2031 string &aString, // receives list string
2032 fieldinteger_t firstmask,
2033 fieldinteger_t lastmask
2034)
2035{
2036 fieldinteger_t m = firstmask;
2037 int i,k;
2038 for (i=0; i<2; i++) {
2039 // - show day numbers
2040 for (k=0; k<32; k++) {
2041 if (m & ((uInt64)1<<k)) {
2042 aString+=aPrefix;
2043 aPrefix=","; // prefix for further elements is now colon.
2044 if (i>0) aString+='-';
2045 StringObjAppendPrintf(aString,"%d",k+1);
2046 }
2047 }
2048 // - switch to those that are relative to the end of the month / year
2049 m = lastmask;
2050 }
2051} // appendMaskAsNumbers
2052
2053
2054// returns the next directive for the ical format
2055bool getNextDirective(
2056 string &aString,
2057 const char *aText,
2058 int &aStart
2059)
2060{
2061 // check
2062 if (*aText == 0) {
2063 return false;
2064 }
2065
2066 char c;
2067 // check start
2068 if (aStart > 0) {
2069 // skip to start
2070 for (int i = 0; (i < aStart) && ((c = *aText) != 0); ++i) aText++;
2071 }
2072 else {
2073 // skip all spaces
2074 while ((c = *aText) == ' ') {
2075 aText++;
2076 }
2077 }
2078 // erase string, set counter
2079 aString.erase();
2080 uInt16 counter = 0;
2081 // append text
2082 c = *aText++; aStart++;
2083 while (c != 0 && c != ';') {
2084 aString.append(1, c);
2085 ++counter;
2086 c = *aText++; aStart++;
2087 }
2088 // empty?
2089 if (counter == 0)
2090 return false;
2091 // remove trailing whitespace
2092 sInt16 i = counter - 1;
2093 while (i >= 0 && aString[i] == ' ') {
2094 aString.erase(i, 1);
2095 --i;
2096 }
2097 // check length
2098 if (i == -1)
2099 return false;
2100 // fine
2101 return true;
2102} // getNextDirective
2103
2104
2105
2106// maps the byday rule into the masks
2107bool setWeekday(
2108 const string &byday,
2109 fieldinteger_t &firstmask,
2110 fieldinteger_t &lastmask,
2111 string::size_type &startIndex,
2112 const string::size_type &endIndex,
2113 bool allowSpecial
2114)
2115{
2116 // remove leading spaces
2117 while (byday[startIndex] == ' ' && startIndex < endIndex) {
2118 ++startIndex;
2119 }
2120 // check if digit or sign
2121 if (isdigit(byday[startIndex]) || byday[startIndex] == '+' || byday[startIndex] == '-') {
2122 // check if numbers before week days are allowed
2123 if (!allowSpecial) {
2124 return false;
2125 }
2126 // special treatment
2127 return setSpecialWeekday(byday, firstmask, lastmask, startIndex, endIndex);
2128 }
2129 // get index of weekday array
2130 sInt16 weekdayIndex = getWeekdayIndex(byday, startIndex);
2131 if (weekdayIndex == -1) {
2132 return false;
2133 }
2134 // put into mask
2135 if (!allowSpecial) {
2136 firstmask |= ((uInt64)1<<weekdayIndex);
2137 return true;
2138 }
2139 for (int i= 0; i<WeeksOfMonth; i++) { // do it for all weeks of the month
2140 firstmask |= ((uInt64)1<<weekdayIndex);
2141 weekdayIndex+= DaysOfWeek;
2142 } // for
2143 return true;
2144} // setWeekday
2145
2146
2147
2148// maps a special weekday (+/- int WEEKDAY) into the masks
2149bool setSpecialWeekday(
2150 const string &byday,
2151 fieldinteger_t &firstmask,
2152 fieldinteger_t &lastmask,
2153 string::size_type &startIndex,
2154 const string::size_type &endIndex
2155)
2156{
2157 // indicator for negative values
2158 bool isNegative;
2159 // the value
2160 sInt16 val = 0;
2161
2162 // check sign
2163 if (byday[startIndex] == '-') {
2164 isNegative = true;
2165 ++startIndex;
2166 }
2167 else if (byday[startIndex] == '+') {
2168 isNegative = false;
2169 ++startIndex;
2170 }
2171 else {
2172 isNegative = false;
2173 }
2174 // convert to number
2175 while (isdigit(byday[startIndex])) {
2176 val = val * 10 + ((byday[startIndex++]) - '0');
2177 }
2178 // remove leading spaces
2179 while (byday[startIndex] == ' ' && startIndex < endIndex) {
2180 ++startIndex;
2181 }
2182 // make sure there's enough space
2183 if (startIndex >= endIndex - 1) {
2184 return false;
2185 }
2186 // get index for weekday
2187 sInt16 index = getWeekdayIndex(byday, startIndex);
2188 if (index == -1) {
2189 return false;
2190 }
2191 // put into mask
2192 if (isNegative) {
2193 lastmask |= (((uInt64)1<<index)<<((val - 1) * DaysOfWeek));
2194 }
2195 else {
2196 firstmask |= (((uInt64)1<<index)<<((val - 1) * DaysOfWeek));
2197 }
2198 return true;
2199} // setSpecialWeekday
2200
2201
2202// returns the index within the rrule weekday array for the next
2203// two chars of the supplied string starting at startIndex
2204sInt16 getWeekdayIndex(
2205 const string &byday,
2206 sInt16 startIndex
2207)
2208{
2209 // search
2210 for (sInt16 i = 0; i < DaysOfWeek; ++i)
2211 {
2212 if (RRULE_weekdays[i][0] == byday[startIndex] && RRULE_weekdays[i][1] == byday[startIndex + 1])
2213 return i;
2214 }
2215 // not found
2216 return -1;
2217} // getWeekdayIndex
2218
2219
2220// set a day in month
2221bool setMonthDay(
2222 const string &bymonthday,
2223 fieldinteger_t &firstmask,
2224 fieldinteger_t &lastmask,
2225 string::size_type &startIndex,
2226 const string::size_type &endIndex
2227)
2228{
2229 // indicator for negative values
2230 bool isNegative;
2231 // the value
2232 sInt16 val=0;
2233
2234 // check sign
2235 if (bymonthday[startIndex] == '-') {
16
Assuming the condition is true
17
Taking true branch
2236 isNegative = true;
2237 ++startIndex;
2238 }
2239 else if (bymonthday[startIndex] == '+') {
2240 isNegative = false;
2241 ++startIndex;
2242 }
2243 else {
2244 isNegative = false;
2245 }
2246 // convert to number
2247 for (string::size_type i = startIndex; i < endIndex; ++i) {
18
Assuming 'i' is >= 'endIndex'
19
Loop condition is false. Execution continues on line 2256
2248 if (isdigit(bymonthday[i])) {
2249 val = val * 10 + ((bymonthday[i]) - '0');
2250 }
2251 else {
2252 return false;
2253 }
2254 }
2255 // put into mask
2256 if (isNegative) {
20
Taking true branch
2257 lastmask |= ((uInt64)1<<(val - 1));
21
The result of the '<<' expression is undefined
2258 }
2259 else {
2260 firstmask |= ((uInt64)1<<(val - 1));
2261 }
2262 return true;
2263} // setMonthDay
2264
2265} // namespace sysync
2266
2267/* eof */