File: | libsynthesis/src/sysync/rrules.cpp |
Warning: | line 1837, column 3 Value stored to 'endIndex' is never read |
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 | |
24 | using namespace sysync; |
25 | |
26 | namespace sysync { |
27 | |
28 | |
29 | // Names of weekdays in RRULEs |
30 | const 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 |
45 | int 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 |
350 | bool 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; |
465 | incompat: |
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 |
473 | bool 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; |
590 | incompat: |
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 |
598 | bool 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; |
820 | norep: |
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 |
828 | store: |
829 | return true; // ok |
830 | incompat: |
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) |
839 | bool 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 |
1076 | static 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 |
1087 | static char buf[100]; |
1088 | char *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 |
1100 | bool 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 |
1195 | void 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 |
1250 | static 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 | |
1267 | static 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 |
1282 | static void incCursor(TRRuleExpandStatus &es) |
1283 | { |
1284 | es.cursor += linearDateToTimeFactor; |
1285 | es.cursorWDay += 1; |
1286 | if (es.cursorWDay>=DaysPerWk) es.cursorWDay=0; |
1287 | } |
1288 | |
1289 | |
1290 | static 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 | |
1305 | static sInt16 monthDiff(sInt16 y1, sInt16 m1, sInt16 y2, sInt16 m2) |
1306 | { |
1307 | return (y1-y2)*12 + (m1-m2); |
1308 | } |
1309 | |
1310 | |
1311 | static 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 |
1326 | lineartime_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); |
1596 | done: |
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) |
1614 | bool 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 |
1663 | bool 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)) { |
1707 | // failed |
1708 | goto incompat; |
1709 | } |
1710 | // analyze freq |
1711 | if (temp == "FREQ=YEARLY") { |
1712 | freq='Y'; |
1713 | } |
1714 | else if (temp == "FREQ=MONTHLY") { |
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)) { |
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; |
Value stored to 'endIndex' is never read | |
1838 | startIndex = 0; |
1839 | switch (freq) { |
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()) |
1898 | goto incompat; |
1899 | // we don't support byday and bymonthday at the same time |
1900 | if (!byday.empty() && !bymonthday.empty()) |
1901 | goto incompat; |
1902 | // check if bymonthday |
1903 | if (!bymonthday.empty()) { |
1904 | freqmod = 'D'; |
1905 | // search separator ',' |
1906 | while ((endIndex = bymonthday.find(",", startIndex)) != string::npos) { |
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()) { |
1917 | endIndex = bymonthday.length(); |
1918 | if (!setMonthDay(bymonthday, firstmask, lastmask, startIndex, endIndex)) |
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; |
2003 | norep: |
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 |
2011 | store: |
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 |
2020 | incompat: |
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. |
2029 | void 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 |
2055 | bool 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 |
2107 | bool 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 |
2149 | bool 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 |
2204 | sInt16 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 |
2221 | bool 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] == '-') { |
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) { |
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) { |
2257 | lastmask |= ((uInt64)1<<(val - 1)); |
2258 | } |
2259 | else { |
2260 | firstmask |= ((uInt64)1<<(val - 1)); |
2261 | } |
2262 | return true; |
2263 | } // setMonthDay |
2264 | |
2265 | } // namespace sysync |
2266 | |
2267 | /* eof */ |