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