dtcm: Resolve CID 87822
[oweals/cde.git] / cde / programs / dtcm / server / reclotick.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: reclotick.c /main/6 1996/11/21 19:45:29 drk $ */
24 /*
25  *  (c) Copyright 1993, 1994 Hewlett-Packard Company
26  *  (c) Copyright 1993, 1994 International Business Machines Corp.
27  *  (c) Copyright 1993, 1994 Novell, Inc.
28  *  (c) Copyright 1993, 1994 Sun Microsystems, Inc.
29  */
30
31 #define XOS_USE_NO_LOCKING
32 #define X_INCLUDE_TIME_H
33 #if defined(linux)
34 #undef SVR4
35 #endif
36 #include <X11/Xos_r.h>
37
38 #include <stdlib.h>
39 #include "rerule.h"
40 #include "repeat.h"
41 #include "reutil.h"
42
43 static Tick DoMinute(const Tick, const Tick, const RepeatEvent *,
44                         RepeatEventState *);
45 static Tick DoDay(const Tick, const Tick, const RepeatEvent *,
46                         RepeatEventState *);
47 static Tick DoWeek(const Tick, const Tick, const RepeatEvent *,
48                         RepeatEventState *);
49 static Tick DoMonthDay(const Tick, const Tick, const RepeatEvent *,
50                         RepeatEventState *);
51 static Tick DoMonthPos(const Tick, const Tick, const RepeatEvent *, 
52                         RepeatEventState *);
53 static Tick DoYearByMonth(const Tick, const Tick, const RepeatEvent *, 
54                         RepeatEventState *);
55 static Tick DoYearByDay(const Tick, const Tick, const RepeatEvent *, 
56                         RepeatEventState *);
57 static void DoDSTAdjustment(const Tick, struct tm *);
58 static RepeatEventState *InitRepeatEventState(const RepeatEvent *);
59 static Tick DSTAdjustment(const struct tm *, const struct tm *);
60 static int GetMonthDiff(const struct tm *, const struct tm *);
61 static int MonthDayNumIntervals(struct tm *, struct tm *, 
62                                 const RepeatEvent *, const unsigned int *,
63                                 struct tm *);
64 static int MonthPosNumIntervals(struct tm *, struct tm *,
65                                 const RepeatEvent *, const WeekDayTime *,
66                                 const unsigned int, struct tm *);
67 void FillInRepeatEvent(const Tick, RepeatEvent *);
68
69 /*
70  * Return the closest time following or equal to the target time given a 
71  * recurrence rule.
72  */
73 Tick
74 ClosestTick(
75         const Tick               _target_time,
76         const Tick               start_time,
77         RepeatEvent             *re,
78         RepeatEventState        **res)
79 {
80         Tick                     closest_tick,
81                                  real_start_time,
82                                  target_time = _target_time;
83
84         if (!re) return (Tick)NULL;
85
86         FillInRepeatEvent(start_time, re);
87
88         if (!(*res = InitRepeatEventState(re)))
89                 return (Tick)NULL;
90
91         if (target_time < start_time)
92                 target_time = start_time;
93
94         switch (re->re_type) {
95         case RT_MINUTE:
96                 closest_tick = DoMinute(target_time, start_time, re, *res);
97                 break;
98         case RT_DAILY:
99                 closest_tick = DoDay(target_time, start_time, re, *res);
100                 break;
101         case RT_WEEKLY:
102                 closest_tick = DoWeek(target_time, start_time, re, *res);
103                 break;
104         case RT_MONTHLY_POSITION:
105                 /* Establish the real start time */
106                 real_start_time = DoMonthPos(start_time, start_time, re, *res);
107                 if (target_time < real_start_time) 
108                         target_time = real_start_time;
109                 if (target_time == real_start_time) {
110                         (*res)->res_duration = re->re_duration - 1;
111                         closest_tick = real_start_time;
112                 } else
113                         closest_tick = DoMonthPos(target_time, 
114                                                 real_start_time, re, *res);
115                 break;
116         case RT_MONTHLY_DAY:
117                 closest_tick = DoMonthDay(target_time, start_time, re, *res);
118                 break;
119         case RT_YEARLY_MONTH:
120                 closest_tick = DoYearByMonth(target_time, start_time, re, *res);
121                 break;
122         case RT_YEARLY_DAY:
123                 closest_tick = DoYearByDay(target_time, start_time, re, *res);
124                 break;
125         }
126
127         /*
128          * Make sure the closest time is not past the appt's end time.
129          */
130         if ((!closest_tick) ||
131             (re->re_end_date && re->re_end_date < closest_tick)) { 
132                 free (*res);
133                 *res = NULL;
134                 return (Tick)NULL;
135         }
136
137         /*
138          * If the duration was not set (thus strictly using the end date)
139          * reset the RepeatEventState duration back to not-set.  This is
140          * cleaner than having conditionals through out the code checking
141          * to see if the duration needs to be updated.
142          */
143         if (re->re_duration == RE_NOTSET)
144                 (*res)->res_duration == RE_NOTSET;
145
146         return closest_tick;
147 }
148
149 /*
150  * Example M60 #4
151  */
152 static Tick
153 DoMinute(
154         const Tick               target_time,
155         const Tick               start_time,
156         const RepeatEvent       *re,
157         RepeatEventState        *res)
158 {
159         int                      delta_seconds;
160         int                      num_intervals;
161         Tick                     closest_tick;
162
163         delta_seconds = target_time - start_time;
164
165                 /* The number of intervals required to span the time from the
166                  * start_time to the target_time given the interval size.
167                  * The interval size comes from the rule (e.g. M5 or 5 * 60)
168                  */
169         num_intervals = delta_seconds / (re->re_interval * 60) + 1;
170
171         if (num_intervals > re->re_duration) { 
172                 /* The Minute portion of rule does not allow us to reach
173                  * the target_time because of the duration limit.
174                  */
175                 closest_tick = re->re_duration * re->re_interval * 60;
176                 /* Pop the stack */
177         } else if (num_intervals < re->re_duration) {
178                 /* In this case we reached the target_time without using
179                  * up all of the intervals allotted to us by the duration.
180                  */
181                 closest_tick = num_intervals * re->re_interval * 60;
182                 /* res->res_duration -= (num_intervals + 1); */
183         } else {
184                 closest_tick = num_intervals * re->re_interval * 60;
185                 /* res->res_duration -= (num_intervals + 1); */
186         }
187
188         return closest_tick;
189 }
190
191 /*
192  * Example: D2 #4
193  */
194 static Tick
195 DoDay(
196         const Tick               target_time,
197         const Tick               start_time,
198         const RepeatEvent       *re,
199         RepeatEventState        *res)
200 {
201         int                      delta_seconds,
202                                  num_intervals,
203                                  dst_adj,
204                                  daysec = 60 * 60 * 24,
205                                  i;
206         struct tm                target_tm,
207                                  start_tm,
208                                  base_tm;
209         unsigned int             ntime = RE_DAILY(re)->dd_ntime;
210         Tick                     base_time,
211                                  next_time = 0;
212         Time                    *time_list = RE_DAILY(re)->dd_time;
213         _Xltimeparams            localtime_buf;
214
215         target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
216         start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
217         dst_adj = DSTAdjustment(&start_tm, &target_tm);
218
219         /* Normalize time to 00:00 */
220         start_tm.tm_sec = 0;
221         start_tm.tm_min = 0;
222         start_tm.tm_hour = 0;
223         start_tm.tm_isdst = -1;
224         base_time = mktime(&start_tm);
225
226         delta_seconds = target_time - base_time + dst_adj;
227
228                 /* The number of intervals required to span the time
229                  * from the start_time to the target_time given the
230                  * interval size.  The interval size comes from the
231                  * rule (e.g. D5 or 5 * daysec)
232                  */
233         num_intervals = delta_seconds / (re->re_interval * daysec);
234
235                 /* This brings us to the interval closest to the target
236                  * time, although it may not actually be the same day.
237                  */
238         base_time += num_intervals * (re->re_interval * daysec) - dst_adj;
239
240         if (!InTimeRange(num_intervals, re->re_duration)) {
241                 /* We hit the duration limit. */
242                 next_time = 0;
243                 goto done;
244         }
245
246         base_tm = *_XLocaltime(&base_time, localtime_buf);
247
248                 /* If we are not on the same day we need to move
249                  * forward one interval and take the earliest time.
250                  * XXX: This won't work with composite rules.
251                  */
252         if (!SAME_DAY(&base_tm, &target_tm)) { 
253                 /* Add one interval to the base_time. */
254                 base_time += 1 * (re->re_interval * daysec);
255                 num_intervals++;
256
257                 /* By moving ahead one day we might have crossed a dst border.*/
258                 DoDSTAdjustment(base_time, &base_tm);
259
260                 if (!InTimeRange(num_intervals, re->re_duration)) {
261                         /* We hit the duration limit. */
262                         next_time = 0;
263                         goto done;
264                 }
265         }
266                 /* Take into account any specific times that are a part
267                  * of this daily repeating rule: e.g. D2 0100 1000 1400 #3.
268                  * We walk through the times for this appointment looking for
269                  * one later than the target time.  
270                  */
271         for (i = 0; i < ntime; i++) {
272                         /* Add the hour that is to be tested to the normalized
273                          * time and see if it is later than the target time.
274                          */
275                 base_tm.tm_min = time_list[i]%100;
276                 base_tm.tm_hour = time_list[i]/100;
277                 base_tm.tm_isdst = -1;
278                 next_time = mktime(&base_tm);
279                 if (next_time >= target_time) {
280                         res->res_duration = re->re_duration - 
281                                                         (num_intervals + 1);
282                         RES_DSTATE(res).res_time = i;
283                         goto done;
284                 }
285         }
286
287                 /* The target time falls after the latest time on
288                  * this appt day.  We must move forward one interval 
289                  * and take the earliest time.
290                  * XXX: Composite rules issue.
291                  */
292         base_tm = *_XLocaltime(&base_time, localtime_buf);
293                 /* Add one interval to the base_time. */
294         base_time += 1 * (re->re_interval * daysec);
295         num_intervals++;
296
297         if (!InTimeRange(num_intervals, re->re_duration)) {
298                 /* We hit the duration limit. */
299                 next_time = 0;
300                 goto done;
301         }
302
303         /* By moving ahead one day we might have crossed a dst border.*/
304         DoDSTAdjustment(base_time, &base_tm);
305
306                 /* Add the hour that is to be tested to the normalized
307                  * time and see if it is later than the target time.
308                  */
309         base_tm.tm_min = time_list[0]%100;
310         base_tm.tm_hour = time_list[0]/100;
311         base_tm.tm_isdst = -1;
312         next_time = mktime(&base_tm);
313
314         res->res_duration = re->re_duration - (num_intervals + 1);
315         RES_DSTATE(res).res_time = 0;
316
317 done:
318         return (next_time);
319 }
320
321 /*
322  * Example: W2 MO WE FR #4
323  */
324 static Tick
325 DoWeek(
326         const Tick               _target_time,
327         const Tick               _start_time,
328         const RepeatEvent       *re,
329         RepeatEventState        *res)
330 {
331         int                      delta_seconds,
332                                  num_intervals,
333                                  dst_adj,
334                                  appt_time,
335                                  daysec = 60 * 60 * 24,
336                                  wksec = daysec * 7;
337         unsigned int             ntime = RE_WEEKLY(re)->wd_ndaytime;
338         struct tm                target_tm,
339                                  start_tm,
340                                  base_tm;
341         Tick                     target_time = _target_time,
342                                  start_time = _start_time,
343                                  base_time,
344                                  begin_time,
345                                  adj_start_time,
346                                  next_time = 0;
347         DayTime                 *day_list = RE_WEEKLY(re)->wd_daytime;
348         RepeatEventState        *unused;
349         _Xltimeparams            localtime_buf;
350
351         /* Make sure the start time is on the first real event slot. */
352         if (_target_time) {
353                 if (!(unused = InitRepeatEventState(re)))
354                         return (Tick)NULL;
355                 start_time = DoWeek(0, _start_time, re, unused);
356                 free(unused);
357                 if (_target_time < start_time)
358                         target_time = start_time;
359         } else
360                 target_time = _start_time;
361
362         target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
363         start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
364         appt_time = start_tm.tm_hour * 100 + start_tm.tm_min;
365         dst_adj = DSTAdjustment(&start_tm, &target_tm);
366
367         /* Normalize start time to the beginning of the week. */
368         start_tm.tm_mday -= start_tm.tm_wday;
369         start_tm.tm_sec = start_tm.tm_min = start_tm.tm_hour = 0;
370         start_tm.tm_isdst = -1;
371         begin_time = mktime(&start_tm);
372         start_tm = *_XLocaltime((const time_t *)&begin_time, localtime_buf);
373
374         delta_seconds = target_time - begin_time + dst_adj;
375
376                 /* The number of intervals required to span the time
377                  * from the start_time to the target_time given the
378                  * interval size.  The interval size comes from the
379                  * rule (e.g. W5 or 5 * wksec)
380                  */
381         num_intervals = delta_seconds / (re->re_interval * wksec);
382
383                 /* This brings us to the interval closest to the target
384                  * time, although it may not actually be the right week.
385                  */
386         base_time = begin_time + num_intervals * (re->re_interval * wksec);
387         base_tm = *_XLocaltime(&base_time, localtime_buf);
388         dst_adj = DSTAdjustment(&start_tm, &base_tm);
389
390         if (!InTimeRange(num_intervals, re->re_duration)) {
391                 /* We hit the duration limit. */
392                  goto done;
393         }
394
395         if (dst_adj) {
396                 base_time -= dst_adj;
397                 base_tm = *_XLocaltime(&base_time, localtime_buf);
398         }
399
400         if (same_week(&target_tm, &base_tm)) {
401                 int i;
402                 int event_wday = -1;
403                 /* Take the next event */
404                 for (i = 0; i < ntime; i++) {
405                         if (day_list[i].dt_day > target_tm.tm_wday) {
406                                 event_wday = day_list[i].dt_day;
407                                 break;
408                         } else if (day_list[i].dt_day == target_tm.tm_wday) {
409                                 /* If they are the same day, the day_list time
410                                  * must be later than the target time.
411                                  */
412                                 int day_time = (day_list[i].dt_time) ? 
413                                                 day_list[i].dt_time[0]:
414                                                 appt_time;
415                                 /* XXX: Must walk the time list too. */
416                                 if (TIME_OF_DAY(&target_tm) <= 
417                                                 HOURTOSEC(day_time)) {
418                                         event_wday = day_list[i].dt_day;
419                                         break;
420                                 }
421                         }
422                 }
423
424                 RES_WSTATE(res).res_daytime = i;
425                 RES_WSTATE(res).res_time = 0;
426
427                 /* The target date is on the same week, but falls after the
428                  * last weekday the event could happen on.
429                  */
430                 if (event_wday == -1) {
431                         /* XXX: Lose the goto. */
432                         goto nextinterval;
433                 }
434                 base_tm.tm_mday += GetWDayDiff(base_tm.tm_wday, event_wday);
435         } else {
436 nextinterval:
437                 /* We will need to go one more interval */
438                 if (!InTimeRange(++num_intervals, re->re_duration)) {
439                         /* We hit the duration limit. */
440                         next_time = 0;
441                         goto done;
442                 }
443                 /* Since the base_tm has been normalized to the beginning
444                  * of the week, we can assume we are on Sunday.
445                  */
446                 base_tm.tm_mday += re->re_interval * 7;
447                 /* If the target day is smaller than the base day then we
448                  * can take the first day in the next event week.
449                  */
450                 if (base_tm.tm_mday > target_tm.tm_mday) {
451                         base_tm.tm_mday += day_list[0].dt_day;
452                 }
453                 RES_WSTATE(res).res_daytime = 0;
454                 RES_WSTATE(res).res_time = 0;
455         }
456
457         base_tm.tm_hour = appt_time / 100;
458         base_tm.tm_min = appt_time % 100;
459         base_tm.tm_isdst = -1;
460         res->res_duration = re->re_duration - num_intervals;
461         next_time = mktime(&base_tm);
462
463 done:
464         return (next_time);
465 }
466
467 /*
468  * Example: MD2 1 10 20 30 #10
469  */
470 static Tick
471 DoMonthDay(
472         const Tick               target_time,
473         const Tick               start_time,
474         const RepeatEvent       *re,
475         RepeatEventState        *res)
476 {
477         int                      num_intervals,
478                                  event_day,
479                                  nmonths;
480         unsigned int             ndays = RE_MONTHLY(re)->md_nitems;
481         struct tm                target_tm,
482                                  start_tm,
483                                  base_tm;
484         Tick                     base_time,
485                                  next_time = 0;
486         unsigned int            *day_list = RE_MONTHLY(re)->md_days;
487         _Xltimeparams            localtime_buf;
488
489         target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
490         start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
491         event_day = day_list[0];
492         num_intervals = MonthDayNumIntervals(&start_tm, &target_tm, re,
493                                              day_list, &base_tm);
494
495         if (!InTimeRange(num_intervals, re->re_duration)) {
496                 /* We hit the duration limit. */
497                 goto done;
498         }
499
500         if (SAME_MONTH(&base_tm, &target_tm)) {
501                 int     next_interval = TRUE,
502                         i;
503
504                 for (i = 0; i < ndays; i++) {
505                         unsigned int    day;
506
507                         day = DayOfMonth(day_list[i], base_tm.tm_mon,
508                                                      base_tm.tm_year);
509
510                         if (day < target_tm.tm_mday)
511                                 continue;
512                         if (day == target_tm.tm_mday)
513                                 /* If it is on the same day, the event time
514                                  * must be later than the target time.
515                                  */
516                                 if (TIME_OF_DAY(&target_tm)
517                                                 > TIME_OF_DAY(&start_tm))
518                                         continue;
519                                 else {
520                                         event_day = day;
521                                         next_interval = FALSE;
522                                         break;
523                                 }
524                         if (day > target_tm.tm_mday) {
525                                         event_day = day;
526                                         next_interval = FALSE;
527                                         break;
528                         }
529                 }
530                 /* We are on the right month and we found a time after the
531                  * target time.
532                  */
533                 if (!next_interval) {
534                         base_tm.tm_mday = event_day;
535                         base_tm.tm_isdst = -1;
536                         next_time = mktime(&base_tm);
537                         /* If the day exists (e.g. 31st in July) we're done */
538                         if (DayExists(event_day, base_tm.tm_mon,
539                                                  base_tm.tm_year)) {
540                                 /* Update repeat state info */
541                                 res->res_duration = re->re_duration 
542                                                                 - num_intervals;
543                                 RES_MSTATE(res).res_day = i;
544                                 next_time = mktime(&base_tm);
545                                 goto done;
546                         }
547                 }
548         }
549
550         num_intervals++;
551
552         if (!InTimeRange(num_intervals, re->re_duration)) {
553                 /* We hit the duration limit. */
554                 next_time = 0;
555                 goto done;
556         }
557
558         /* Since we are moving to the next interval, use the first day */
559         event_day = day_list[0];
560         do {
561                 /* Event is in the next interval */
562                 base_tm.tm_mon += 1 * re->re_interval;
563                 base_tm.tm_mday = 1;
564                 base_tm.tm_isdst = -1;
565                 base_time = mktime(&base_tm);
566                 base_tm = *_XLocaltime(&base_time, localtime_buf);
567
568                 /* Stop when the day exists in that month */
569         } while (!DayExists(event_day, base_tm.tm_mon, base_tm.tm_year));
570
571         base_tm.tm_mday = DayOfMonth(event_day, base_tm.tm_mon,
572                                      base_tm.tm_year);
573         base_tm.tm_isdst = -1;
574         next_time = mktime(&base_tm);
575
576         res->res_duration = re->re_duration - num_intervals;
577         RES_MSTATE(res).res_day = 0;
578
579 done:
580         return (next_time);
581                 
582 }
583
584 /*
585  * Example: MP2 1+ MO TU 2- TH #3
586  */
587 static Tick
588 DoMonthPos(
589         const Tick               target_time,
590         const Tick               start_time,
591         const RepeatEvent       *re,
592         RepeatEventState        *res)
593 {
594         int                      num_intervals,
595                                  nmonths;
596         unsigned int             ndays = RE_MONTHLY(re)->md_nitems;
597         struct tm                target_tm,
598                                  start_tm,
599                                  base_tm;
600         Tick                     base_time = 0;
601         WeekDayTime             *wdt_list = RE_MONTHLY(re)->md_weektime;
602         _Xltimeparams            localtime_buf;
603
604         target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
605         start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
606         num_intervals = MonthPosNumIntervals(&start_tm, &target_tm, re,
607                                              wdt_list, ndays, &base_tm);
608
609         do {
610                 if (!InTimeRange(num_intervals, re->re_duration)) {
611                         /* We hit the duration limit. */
612                         base_time = 0;
613                         goto done;
614                 }
615                 base_tm.tm_isdst = -1;
616
617                 if (SAME_MONTH(&target_tm, &base_tm)) {
618                         base_time = mktime(&base_tm);
619                         base_tm = *_XLocaltime((const time_t *)&base_time, localtime_buf);
620                         base_time = WeekNumberToDay(base_time,
621                                                wdt_list[0].wdt_week[0],
622                                                wdt_list[0].wdt_day[0]);
623                         if (base_time >= target_time)
624                                 break;
625                         /* target_time came after the slot for this month */
626                         if (base_time)
627                                 num_intervals++;
628                 }
629
630                 base_tm.tm_mon += re->re_interval;
631                 base_tm.tm_isdst = -1;
632                 /* Move to the first interval after the target time */
633                 base_time = mktime(&base_tm);
634                 base_tm = *_XLocaltime((const time_t *)&base_time, localtime_buf);
635                 base_time = WeekNumberToDay(base_time,
636                                                wdt_list[0].wdt_week[0],
637                                                wdt_list[0].wdt_day[0]);
638         } while (!base_time); 
639
640         num_intervals++;
641
642         /* Update repeat state info */
643         res->res_duration = re->re_duration - num_intervals;
644         RES_MSTATE(res).res_weektime = 0;
645         RES_MSTATE(res).res_wday = 0;
646         RES_MSTATE(res).res_wtime = 0;
647         RES_MSTATE(res).res_wweek = 0;
648
649 done:
650         return (base_time);
651 }
652
653 /*
654  * Example: YM1 2 5 9 #4
655  */
656 static Tick
657 DoYearByMonth(
658         const Tick               _target_time,
659         const Tick               _start_time,
660         const RepeatEvent       *re,
661         RepeatEventState        *res)
662 {
663         int                      num_intervals,
664                                  nyears;
665         unsigned int             nitems = RE_YEARLY(re)->yd_nitems;
666         struct tm                target_tm,
667                                  start_tm,
668                                  base_tm;
669         Tick                     base_time = 0,
670                                  start_time = _start_time,
671                                  target_time = _target_time;
672         unsigned int            *month_list = RE_YEARLY(re)->yd_items;
673         RepeatEventState        *unused;
674         _Xltimeparams            localtime_buf;
675
676         /* Make sure the start time is on the first real event slot. */
677         if (_target_time) {
678                 if (!(unused = InitRepeatEventState(re)))
679                         return (Tick)NULL;
680                 start_time = DoYearByMonth(0, _start_time, re, unused);
681                 free(unused);
682                 if (_target_time < start_time)
683                         target_time = start_time;
684         } else
685                 target_time = _start_time;
686
687         target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
688         start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
689         nyears = target_tm.tm_year - start_tm.tm_year;
690         num_intervals = nyears / re->re_interval;
691
692         if (!InTimeRange(num_intervals, re->re_duration)) {
693                 /* We hit the duration limit. */
694                 goto done;
695         }
696
697         base_tm = start_tm;
698         base_tm.tm_year += num_intervals * re->re_interval;
699         base_tm.tm_isdst = -1;
700
701         base_time = mktime(&base_tm);
702         base_tm = *_XLocaltime((const time_t *)&base_time, localtime_buf);
703
704         if (base_tm.tm_year == target_tm.tm_year) {
705                 int i;
706
707                 /* Look for a month that is >= the target month */
708                 for (i = 0; i < nitems; i++) {
709                         /* If the months are equal the target time has to be
710                          * less than the next tick.
711                          */
712                         if (month_list[i] - 1 == target_tm.tm_mon) {
713                                 base_tm.tm_mon = month_list[i] - 1;
714                                 base_tm.tm_isdst = -1;
715                                 base_time = mktime(&base_tm);
716                                 base_tm = *_XLocaltime(&base_time, localtime_buf);
717                                 if (TIMEOFMONTH(&base_tm) >=
718                                                        TIMEOFMONTH(&target_tm)){
719                                         res->res_duration = re->re_duration - 
720                                                                 num_intervals;
721                                         RES_YSTATE(res).res_daymonth = i;
722                                         goto done;
723                                 }
724                         } else if (month_list[i] - 1 >= target_tm.tm_mon) {
725                                 base_tm.tm_mon = month_list[i] - 1;
726                                 base_tm.tm_isdst = -1;
727                                 base_time = mktime(&base_tm);
728                                 res->res_duration = re->re_duration - 
729                                                                 num_intervals;
730                                 RES_YSTATE(res).res_daymonth = i;
731                                 goto done;
732                         }
733                 }
734         }
735
736         /*
737          * The base year is greater than the target year, take the first
738          * month.
739          */
740         if (!InTimeRange(++num_intervals, re->re_duration)) {
741                 /* We hit the duration limit. */
742                 base_time = 0;
743                 goto done;
744         }
745
746         base_tm.tm_year += re->re_interval;
747         base_tm.tm_mon = month_list[0] - 1;
748         base_tm.tm_isdst = -1;
749         base_time = mktime(&base_tm);
750
751         res->res_duration = re->re_duration - num_intervals;
752         RES_YSTATE(res).res_daymonth = 0;
753 done:
754         return (base_time);
755 }
756
757 /*
758  * Example: YD1 100 200 300 #4
759  */
760 static Tick
761 DoYearByDay(
762         const Tick               _target_time,
763         const Tick               _start_time,
764         const RepeatEvent       *re,
765         RepeatEventState        *res)
766 {
767         int                      num_intervals,
768                                  nyears;
769         unsigned int             nitems = RE_YEARLY(re)->yd_nitems;
770         struct tm                target_tm,
771                                  start_tm,
772                                  base_tm;
773         Tick                     base_time = 0,
774                                  target_time = _target_time,
775                                  start_time = _start_time;
776         unsigned int            *day_list = RE_YEARLY(re)->yd_items;
777         RepeatEventState        *unused;
778         _Xltimeparams            localtime_buf;
779
780         /* Make sure the start time is on the first real event slot. */
781         if (_target_time) {
782                 if (!(unused = InitRepeatEventState(re)))
783                         return (Tick)NULL;
784                 start_time = DoYearByDay(0, _start_time, re, unused);
785                 free(unused);
786                 if (_target_time < start_time)
787                         target_time = start_time;
788         } else
789                 target_time = _start_time;
790
791         target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
792         start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
793         nyears = target_tm.tm_year - start_tm.tm_year;
794         num_intervals = nyears / re->re_interval;
795
796         if (!InTimeRange(num_intervals, re->re_duration)) {
797                 /* We hit the duration limit. */
798                 goto done;
799         }
800
801         /* 
802          * XXX: day_list == 366 is a special case...that is not supported
803          * right now.
804          */
805
806         base_tm = start_tm;
807         base_tm.tm_year += num_intervals * re->re_interval;
808
809         /* If the years are the same then go down the list of days looking
810          * for one later than the target time.
811          */
812         if (base_tm.tm_year == target_tm.tm_year) {
813                 int i;
814                 for (i = 0; i < nitems; i++) {
815                         base_tm.tm_mday = day_list[i];
816                         base_tm.tm_mon = 0;
817                         base_tm.tm_isdst = -1;
818                         base_time = mktime(&base_tm);
819
820                         /* We found the closest tick */
821                         if (base_time >= target_time) {
822                                 res->res_duration = re->re_duration - 
823                                                                 num_intervals;
824                                 RES_YSTATE(res).res_daymonth = i;
825                                 goto done;
826                         }
827                 }
828         }
829
830         /* 
831          * We either were not on the same year or the above fell through
832          * as we crossed into the next interval.
833          */
834
835         num_intervals++;
836
837         if (!InTimeRange(num_intervals, re->re_duration)) {
838                 /* We hit the duration limit. */
839                 base_time = 0;
840                 goto done;
841         }
842
843         base_tm.tm_year += 1 * re->re_interval;
844         base_tm.tm_mday = day_list[0];
845         base_tm.tm_mon = 0;
846         base_tm.tm_isdst = -1;
847         base_time = mktime(&base_tm);
848
849         res->res_duration = re->re_duration - num_intervals;
850         RES_YSTATE(res).res_daymonth = 0;
851
852 done:
853         return (base_time);
854 }
855
856 /* Calculate the number of months between two dates */
857 /* 3/20/90 - 1/2/94 = 46 months */ 
858 static int
859 GetMonthDiff(
860         const struct tm *start_tm,
861         const struct tm *end_tm)
862 {
863         return ((end_tm->tm_year - start_tm->tm_year + 1) * 12 -
864                 (start_tm->tm_mon + 1) - (12 - (end_tm->tm_mon + 1)));
865 }
866
867 static Tick
868 DSTAdjustment(
869         const struct tm *tm1,
870         const struct tm *tm2)
871 {
872         if (tm1->tm_isdst == -1 || tm2->tm_isdst == -1)
873                 return 0;
874
875         if (tm1->tm_isdst != tm2->tm_isdst) {
876                 if (tm1->tm_isdst)      /* From day light savings to standard */
877                         return -3600;
878                 else                    /* From standard to day light savings */
879                         return 3600;
880         }
881         return 0;
882 }
883
884 static void 
885 DoDSTAdjustment(
886         const Tick       begin_time,
887         struct tm       *end_time)      /* Return */
888 {
889         struct tm        next_day;
890         Tick             dst_adj,
891                          _begin_time = begin_time;
892         _Xltimeparams    localtime_buf;
893
894         /* By moving ahead one day we might have crossed a dst border.*/
895         next_day = *_XLocaltime(&begin_time, localtime_buf);
896         dst_adj = DSTAdjustment(end_time, &next_day);
897         if (dst_adj) {
898                 _begin_time -= dst_adj;
899                 *end_time = *_XLocaltime(&_begin_time, localtime_buf);
900         } else
901                 *end_time = next_day;
902 }
903
904 /*
905  * Initialize the RepeatEventState struct.
906  */
907 static RepeatEventState *
908 InitRepeatEventState(
909         const RepeatEvent       *re)
910 {
911         RepeatEventState        *res;
912
913         if (!(res = (RepeatEventState *)calloc(1, sizeof(RepeatEventState))))
914                 return (RepeatEventState *)NULL;
915
916         res->res_re = re;
917         res->res_duration = re->re_duration;
918
919         return res;
920 }
921
922
923 /*
924  * Determine the number of intervals between the start_tm and the target_tm,
925  * the base_tm which is returned is the last event generated before the
926  * target_tm.
927  */
928 static int
929 MonthDayNumIntervals(
930         struct tm               *start_tm,
931         struct tm               *target_tm,
932         const RepeatEvent       *re,
933         const unsigned int      *md_days,
934         struct tm               *base_tm)       /* Return */ 
935 {
936         int              num_intervals = 0;     
937         struct tm        cur_tm;
938         Tick             cur_time,
939                          base_time,
940                          last_time,
941                          target_time;
942         _Xltimeparams    localtime_buf;
943
944         /* The 28th - 31st may not exist in a given month thus if only these
945          * days are specified in a rule it is necessary to calculate the
946          * correct month by brute force versus using a mathematical calculation.
947          */
948         if (md_days[0] > 28) {  
949                 *base_tm = *start_tm;
950                 cur_tm = *start_tm;
951                 cur_tm.tm_mday = 1;
952                 cur_tm.tm_isdst = -1;
953                 cur_time = mktime(&cur_tm);
954                 target_tm->tm_isdst = -1;
955                 target_time = mktime((struct tm*)target_tm);
956                 last_time = cur_time;
957
958                 while (cur_time < target_time) {
959                         cur_tm.tm_mon += re->re_interval;
960                         cur_tm.tm_isdst = -1;
961                         cur_time = mktime(&cur_tm);
962                         cur_tm = *_XLocaltime((const time_t *)&cur_time, localtime_buf);
963
964                         if (DayExists(md_days[0], cur_tm.tm_mon,
965                                                         cur_tm.tm_year)) {
966                                 if (cur_time >= target_time) {
967                                         cur_time = last_time;
968                                         cur_tm = *_XLocaltime((const time_t *)
969                                                                     &last_time, localtime_buf);
970                                         break;
971                                 }
972                                 /* Remember the last time in case we need to
973                                  * back up one interval.
974                                  */
975                                 last_time = cur_time;
976                                 *base_tm = cur_tm;
977                                 num_intervals++;
978                         }
979
980                         if (!InTimeRange(num_intervals, re->re_duration))
981                                 break;
982
983                         if (SAME_MONTH(target_tm, &cur_tm)) break;
984                 }
985         } else {
986                 num_intervals = GetMonthDiff(start_tm, target_tm)
987                                                         / re->re_interval;
988                 *base_tm = *start_tm;
989                 base_tm->tm_isdst = -1;
990                 /* Move to the closest interval before the target time */
991                 base_tm->tm_mon += num_intervals * re->re_interval;
992                 base_time = mktime(base_tm);
993                 *base_tm = *_XLocaltime(&base_time, localtime_buf);
994         }
995
996         return (num_intervals);
997 }
998
999 /*
1000  * Count the number of intervals up to, but before the target time.  The
1001  * base time returned in the last valid interval before the target time.
1002  */
1003 static int
1004 MonthPosNumIntervals(
1005         struct tm               *start_tm,
1006         struct tm               *target_tm,
1007         const RepeatEvent       *re,
1008         const WeekDayTime       *wdt_list,
1009         const unsigned int       nwdt_list,
1010         struct tm               *base_tm)       /* Return */
1011 {
1012         int              num_intervals = 0,
1013                          brute_force = TRUE,
1014                          i, j;  
1015         struct tm        cur_tm;
1016         Tick             cur_time,
1017                          base_time,
1018                          target_time;
1019         _Xltimeparams    localtime_buf;
1020
1021         for (i = 0; i < nwdt_list; i++) {
1022                 for (j = 0; j < wdt_list[i].wdt_nweek; j++) {
1023                         if ((wdt_list[i].wdt_week[j] != WK_F5) &&
1024                             (wdt_list[i].wdt_week[j] != WK_L5)) {
1025                                 brute_force = FALSE;
1026                                 break;
1027                         }
1028                 }
1029                 if (brute_force == FALSE) break;
1030         }
1031
1032         /* The weekday associated with +5 or -5 may not exist in a given
1033          * month thus if only these weekdays are specified in a rule it is
1034          * necessary to calculate the correct month by brute force versus
1035          * using a mathematical calculation.
1036          */
1037         if (brute_force){
1038                 *base_tm = *start_tm;
1039                 cur_tm = *start_tm;
1040                 cur_tm.tm_isdst = -1;
1041                 cur_tm.tm_mday = 1;
1042                 cur_time = mktime(&cur_tm);
1043                 target_tm->tm_isdst = -1;
1044                 target_time = mktime((struct tm *)target_tm);
1045
1046                 /* Count the start_time */
1047                 if (cur_time < target_time)
1048                         num_intervals++;
1049
1050                 while (cur_time < target_time) {
1051                         if (SAME_MONTH(target_tm, &cur_tm)) break;
1052
1053                         cur_tm.tm_mon += re->re_interval;
1054                         cur_tm.tm_isdst = -1;
1055                         cur_time = mktime(&cur_tm);
1056                         cur_tm = *_XLocaltime((const time_t *)&cur_time, localtime_buf);
1057
1058                         if (OccurenceExists(wdt_list, nwdt_list, cur_time)) {
1059                                 num_intervals++;
1060                                 /* Only update the cur_tm if valid slot there */
1061                                 *base_tm = cur_tm;
1062                         }
1063
1064                         if (!InTimeRange(num_intervals, re->re_duration))
1065                                 break;
1066
1067                 }
1068         } else {
1069                 num_intervals = GetMonthDiff(start_tm, target_tm)
1070                                                         / re->re_interval;
1071                 *base_tm = *start_tm;
1072                 base_tm->tm_isdst = -1;
1073                 /* Move to the closest interval before the target time */
1074                 base_tm->tm_mon += num_intervals * re->re_interval;
1075                 base_time = mktime(base_tm);
1076                 *base_tm = *_XLocaltime(&base_time, localtime_buf);
1077         }
1078
1079         return (num_intervals);
1080 }
1081
1082 void
1083 FillInRepeatEvent(
1084         const Tick               start_time,
1085         RepeatEvent             *re)
1086 {
1087         struct tm               *start_tm;
1088         int                      i;
1089         _Xltimeparams            localtime_buf;
1090
1091         start_tm = _XLocaltime(&start_time, localtime_buf);
1092
1093         switch (re->re_type) {
1094         case RT_MINUTE:
1095                 break;
1096         case RT_DAILY:
1097                 if (!RE_DAILY(re)->dd_ntime) {
1098                         RE_DAILY(re)->dd_time = (Time *)calloc(1, sizeof(Time));
1099                         RE_DAILY(re)->dd_time[0] = start_tm->tm_hour * 100 + 
1100                                                    start_tm->tm_min;
1101                         RE_DAILY(re)->dd_ntime = 1;
1102                 }
1103                 break;
1104         case RT_WEEKLY:
1105                 if (!RE_WEEKLY(re)->wd_ndaytime) {
1106                         RE_WEEKLY(re)->wd_daytime = 
1107                                           (DayTime *)calloc(1, sizeof(DayTime));
1108                         RE_WEEKLY(re)->wd_daytime[0].dt_day = start_tm->tm_wday;
1109                         RE_WEEKLY(re)->wd_daytime[0].dt_ntime = 1;
1110                         RE_WEEKLY(re)->wd_daytime[0].dt_time =
1111                                           (Time *)calloc(1, sizeof(Time));
1112                         RE_WEEKLY(re)->wd_daytime[0].dt_time[0] =
1113                                                 start_tm->tm_hour * 100 +
1114                                                 start_tm->tm_min;
1115                         RE_WEEKLY(re)->wd_ndaytime = 1;
1116                 } else {
1117                         int i;
1118                         for (i = 0; i < RE_WEEKLY(re)->wd_ndaytime; i++) {
1119                                 if (!RE_WEEKLY(re)->wd_daytime[i].dt_ntime) {
1120                                         RE_WEEKLY(re)->wd_daytime[i].dt_ntime =
1121                                                                               1;
1122                                         RE_WEEKLY(re)->wd_daytime[i].dt_time =
1123                                                 (Time *)calloc(1, sizeof(Time));
1124                                         RE_WEEKLY(re)->wd_daytime[i].dt_time[0]=
1125                                                 start_tm->tm_hour * 100 +
1126                                                 start_tm->tm_min;
1127                                 }
1128                         }
1129                 }
1130                 break;
1131         case RT_MONTHLY_POSITION:
1132                 if (!RE_MONTHLY(re)->md_nitems) {
1133                         RE_MONTHLY(re)->md_weektime =
1134                                   (WeekDayTime *)calloc(1, sizeof(WeekDayTime));
1135                         RE_MONTHLY(re)->md_weektime[0].wdt_nday = 1;
1136                         RE_MONTHLY(re)->md_weektime[0].wdt_day =
1137                                         (WeekDay *)calloc(1, sizeof(WeekDay));
1138                         RE_MONTHLY(re)->md_weektime[0].wdt_day[0] =
1139                                                         start_tm->tm_wday;
1140                         RE_MONTHLY(re)->md_weektime[0].wdt_nweek = 1;
1141                         RE_MONTHLY(re)->md_weektime[0].wdt_week =
1142                                   (WeekNumber *)calloc(1, sizeof(WeekNumber));
1143                         RE_MONTHLY(re)->md_weektime[0].wdt_week[0] =
1144                                                 GetWeekNumber(start_time);
1145                         RE_MONTHLY(re)->md_nitems = 1;
1146                 }
1147                 break;
1148         case RT_MONTHLY_DAY:
1149                 if (!RE_MONTHLY(re)->md_nitems) {
1150                         RE_MONTHLY(re)->md_days = 
1151                                 (unsigned int *)calloc(1, sizeof(unsigned int));
1152                         RE_MONTHLY(re)->md_days[0] = start_tm->tm_mday; 
1153                         RE_MONTHLY(re)->md_nitems = 1;
1154                 }
1155                 break;
1156         case RT_YEARLY_MONTH:
1157                 if (!RE_YEARLY(re)->yd_nitems) {
1158                         RE_YEARLY(re)->yd_items = 
1159                                 (unsigned int *)calloc(1, sizeof(unsigned int));
1160                         RE_YEARLY(re)->yd_items[0] = start_tm->tm_mon + 1;
1161                         RE_YEARLY(re)->yd_nitems = 1;
1162                 }
1163                 break;
1164         case RT_YEARLY_DAY:
1165                 if (!RE_YEARLY(re)->yd_nitems) {
1166                         RE_YEARLY(re)->yd_items = 
1167                                 (unsigned int *)calloc(1, sizeof(unsigned int));
1168                         RE_YEARLY(re)->yd_items[0] = start_tm->tm_yday;
1169                         RE_YEARLY(re)->yd_nitems = 1;
1170                 }
1171                 break;
1172         }
1173 }