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