1174 lines
32 KiB
C
1174 lines
32 KiB
C
/*
|
|
* CDE - Common Desktop Environment
|
|
*
|
|
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
|
|
*
|
|
* These libraries and programs are free software; you can
|
|
* redistribute them and/or modify them under the terms of the GNU
|
|
* Lesser General Public License as published by the Free Software
|
|
* Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* These libraries and programs are distributed in the hope that
|
|
* they will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU Lesser General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with these librararies and programs; if not, write
|
|
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
|
|
* Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/* $XConsortium: reclotick.c /main/6 1996/11/21 19:45:29 drk $ */
|
|
/*
|
|
* (c) Copyright 1993, 1994 Hewlett-Packard Company
|
|
* (c) Copyright 1993, 1994 International Business Machines Corp.
|
|
* (c) Copyright 1993, 1994 Novell, Inc.
|
|
* (c) Copyright 1993, 1994 Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#define XOS_USE_NO_LOCKING
|
|
#define X_INCLUDE_TIME_H
|
|
#if defined(linux)
|
|
#undef SVR4
|
|
#endif
|
|
#include <X11/Xos_r.h>
|
|
|
|
#include <stdlib.h>
|
|
#include "rerule.h"
|
|
#include "repeat.h"
|
|
#include "reutil.h"
|
|
|
|
static Tick DoMinute(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static Tick DoDay(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static Tick DoWeek(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static Tick DoMonthDay(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static Tick DoMonthPos(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static Tick DoYearByMonth(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static Tick DoYearByDay(const Tick, const Tick, const RepeatEvent *,
|
|
RepeatEventState *);
|
|
static void DoDSTAdjustment(const Tick, struct tm *);
|
|
static RepeatEventState *InitRepeatEventState(const RepeatEvent *);
|
|
static Tick DSTAdjustment(const struct tm *, const struct tm *);
|
|
static int GetMonthDiff(const struct tm *, const struct tm *);
|
|
static int MonthDayNumIntervals(struct tm *, struct tm *,
|
|
const RepeatEvent *, const unsigned int *,
|
|
struct tm *);
|
|
static int MonthPosNumIntervals(struct tm *, struct tm *,
|
|
const RepeatEvent *, const WeekDayTime *,
|
|
const unsigned int, struct tm *);
|
|
void FillInRepeatEvent(const Tick, RepeatEvent *);
|
|
|
|
/*
|
|
* Return the closest time following or equal to the target time given a
|
|
* recurrence rule.
|
|
*/
|
|
Tick
|
|
ClosestTick(
|
|
const Tick _target_time,
|
|
const Tick start_time,
|
|
RepeatEvent *re,
|
|
RepeatEventState **res)
|
|
{
|
|
Tick closest_tick,
|
|
real_start_time,
|
|
target_time = _target_time;
|
|
|
|
if (!re) return (Tick)NULL;
|
|
|
|
FillInRepeatEvent(start_time, re);
|
|
|
|
if (!(*res = InitRepeatEventState(re)))
|
|
return (Tick)NULL;
|
|
|
|
if (target_time < start_time)
|
|
target_time = start_time;
|
|
|
|
switch (re->re_type) {
|
|
case RT_MINUTE:
|
|
closest_tick = DoMinute(target_time, start_time, re, *res);
|
|
break;
|
|
case RT_DAILY:
|
|
closest_tick = DoDay(target_time, start_time, re, *res);
|
|
break;
|
|
case RT_WEEKLY:
|
|
closest_tick = DoWeek(target_time, start_time, re, *res);
|
|
break;
|
|
case RT_MONTHLY_POSITION:
|
|
/* Establish the real start time */
|
|
real_start_time = DoMonthPos(start_time, start_time, re, *res);
|
|
if (target_time < real_start_time)
|
|
target_time = real_start_time;
|
|
if (target_time == real_start_time) {
|
|
(*res)->res_duration = re->re_duration - 1;
|
|
closest_tick = real_start_time;
|
|
} else
|
|
closest_tick = DoMonthPos(target_time,
|
|
real_start_time, re, *res);
|
|
break;
|
|
case RT_MONTHLY_DAY:
|
|
closest_tick = DoMonthDay(target_time, start_time, re, *res);
|
|
break;
|
|
case RT_YEARLY_MONTH:
|
|
closest_tick = DoYearByMonth(target_time, start_time, re, *res);
|
|
break;
|
|
case RT_YEARLY_DAY:
|
|
closest_tick = DoYearByDay(target_time, start_time, re, *res);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Make sure the closest time is not past the appt's end time.
|
|
*/
|
|
if ((!closest_tick) ||
|
|
(re->re_end_date && re->re_end_date < closest_tick)) {
|
|
free (*res);
|
|
*res = NULL;
|
|
return (Tick)NULL;
|
|
}
|
|
|
|
/*
|
|
* If the duration was not set (thus strictly using the end date)
|
|
* reset the RepeatEventState duration back to not-set. This is
|
|
* cleaner than having conditionals through out the code checking
|
|
* to see if the duration needs to be updated.
|
|
*/
|
|
if (re->re_duration == RE_NOTSET)
|
|
(*res)->res_duration == RE_NOTSET;
|
|
|
|
return closest_tick;
|
|
}
|
|
|
|
/*
|
|
* Example M60 #4
|
|
*/
|
|
static Tick
|
|
DoMinute(
|
|
const Tick target_time,
|
|
const Tick start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int delta_seconds;
|
|
int num_intervals;
|
|
Tick closest_tick;
|
|
|
|
delta_seconds = target_time - start_time;
|
|
|
|
/* The number of intervals required to span the time from the
|
|
* start_time to the target_time given the interval size.
|
|
* The interval size comes from the rule (e.g. M5 or 5 * 60)
|
|
*/
|
|
num_intervals = delta_seconds / (re->re_interval * 60) + 1;
|
|
|
|
if (num_intervals > re->re_duration) {
|
|
/* The Minute portion of rule does not allow us to reach
|
|
* the target_time because of the duration limit.
|
|
*/
|
|
closest_tick = re->re_duration * re->re_interval * 60;
|
|
/* Pop the stack */
|
|
} else if (num_intervals < re->re_duration) {
|
|
/* In this case we reached the target_time without using
|
|
* up all of the intervals allotted to us by the duration.
|
|
*/
|
|
closest_tick = num_intervals * re->re_interval * 60;
|
|
/* res->res_duration -= (num_intervals + 1); */
|
|
} else {
|
|
closest_tick = num_intervals * re->re_interval * 60;
|
|
/* res->res_duration -= (num_intervals + 1); */
|
|
}
|
|
|
|
return closest_tick;
|
|
}
|
|
|
|
/*
|
|
* Example: D2 #4
|
|
*/
|
|
static Tick
|
|
DoDay(
|
|
const Tick target_time,
|
|
const Tick start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int delta_seconds,
|
|
num_intervals,
|
|
dst_adj,
|
|
daysec = 60 * 60 * 24,
|
|
i;
|
|
struct tm target_tm,
|
|
start_tm,
|
|
base_tm;
|
|
unsigned int ntime = RE_DAILY(re)->dd_ntime;
|
|
Tick base_time,
|
|
next_time = 0;
|
|
Time *time_list = RE_DAILY(re)->dd_time;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
|
|
start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
|
|
dst_adj = DSTAdjustment(&start_tm, &target_tm);
|
|
|
|
/* Normalize time to 00:00 */
|
|
start_tm.tm_sec = 0;
|
|
start_tm.tm_min = 0;
|
|
start_tm.tm_hour = 0;
|
|
start_tm.tm_isdst = -1;
|
|
base_time = mktime(&start_tm);
|
|
|
|
delta_seconds = target_time - base_time + dst_adj;
|
|
|
|
/* The number of intervals required to span the time
|
|
* from the start_time to the target_time given the
|
|
* interval size. The interval size comes from the
|
|
* rule (e.g. D5 or 5 * daysec)
|
|
*/
|
|
num_intervals = delta_seconds / (re->re_interval * daysec);
|
|
|
|
/* This brings us to the interval closest to the target
|
|
* time, although it may not actually be the same day.
|
|
*/
|
|
base_time += num_intervals * (re->re_interval * daysec) - dst_adj;
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
next_time = 0;
|
|
goto done;
|
|
}
|
|
|
|
base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
|
|
/* If we are not on the same day we need to move
|
|
* forward one interval and take the earliest time.
|
|
* XXX: This won't work with composite rules.
|
|
*/
|
|
if (!SAME_DAY(&base_tm, &target_tm)) {
|
|
/* Add one interval to the base_time. */
|
|
base_time += 1 * (re->re_interval * daysec);
|
|
num_intervals++;
|
|
|
|
/* By moving ahead one day we might have crossed a dst border.*/
|
|
DoDSTAdjustment(base_time, &base_tm);
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
next_time = 0;
|
|
goto done;
|
|
}
|
|
}
|
|
/* Take into account any specific times that are a part
|
|
* of this daily repeating rule: e.g. D2 0100 1000 1400 #3.
|
|
* We walk through the times for this appointment looking for
|
|
* one later than the target time.
|
|
*/
|
|
for (i = 0; i < ntime; i++) {
|
|
/* Add the hour that is to be tested to the normalized
|
|
* time and see if it is later than the target time.
|
|
*/
|
|
base_tm.tm_min = time_list[i]%100;
|
|
base_tm.tm_hour = time_list[i]/100;
|
|
base_tm.tm_isdst = -1;
|
|
next_time = mktime(&base_tm);
|
|
if (next_time >= target_time) {
|
|
res->res_duration = re->re_duration -
|
|
(num_intervals + 1);
|
|
RES_DSTATE(res).res_time = i;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* The target time falls after the latest time on
|
|
* this appt day. We must move forward one interval
|
|
* and take the earliest time.
|
|
* XXX: Composite rules issue.
|
|
*/
|
|
base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
/* Add one interval to the base_time. */
|
|
base_time += 1 * (re->re_interval * daysec);
|
|
num_intervals++;
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
next_time = 0;
|
|
goto done;
|
|
}
|
|
|
|
/* By moving ahead one day we might have crossed a dst border.*/
|
|
DoDSTAdjustment(base_time, &base_tm);
|
|
|
|
/* Add the hour that is to be tested to the normalized
|
|
* time and see if it is later than the target time.
|
|
*/
|
|
base_tm.tm_min = time_list[0]%100;
|
|
base_tm.tm_hour = time_list[0]/100;
|
|
base_tm.tm_isdst = -1;
|
|
next_time = mktime(&base_tm);
|
|
|
|
res->res_duration = re->re_duration - (num_intervals + 1);
|
|
RES_DSTATE(res).res_time = 0;
|
|
|
|
done:
|
|
return (next_time);
|
|
}
|
|
|
|
/*
|
|
* Example: W2 MO WE FR #4
|
|
*/
|
|
static Tick
|
|
DoWeek(
|
|
const Tick _target_time,
|
|
const Tick _start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int delta_seconds,
|
|
num_intervals,
|
|
dst_adj,
|
|
appt_time,
|
|
daysec = 60 * 60 * 24,
|
|
wksec = daysec * 7;
|
|
unsigned int ntime = RE_WEEKLY(re)->wd_ndaytime;
|
|
struct tm target_tm,
|
|
start_tm,
|
|
base_tm;
|
|
Tick target_time = _target_time,
|
|
start_time = _start_time,
|
|
base_time,
|
|
begin_time,
|
|
adj_start_time,
|
|
next_time = 0;
|
|
DayTime *day_list = RE_WEEKLY(re)->wd_daytime;
|
|
RepeatEventState *unused;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
/* Make sure the start time is on the first real event slot. */
|
|
if (_target_time) {
|
|
if (!(unused = InitRepeatEventState(re)))
|
|
return (Tick)NULL;
|
|
start_time = DoWeek(NULL, _start_time, re, unused);
|
|
free(unused);
|
|
if (_target_time < start_time)
|
|
target_time = start_time;
|
|
} else
|
|
target_time = _start_time;
|
|
|
|
target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
|
|
start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
|
|
appt_time = start_tm.tm_hour * 100 + start_tm.tm_min;
|
|
dst_adj = DSTAdjustment(&start_tm, &target_tm);
|
|
|
|
/* Normalize start time to the beginning of the week. */
|
|
start_tm.tm_mday -= start_tm.tm_wday;
|
|
start_tm.tm_sec = start_tm.tm_min = start_tm.tm_hour = 0;
|
|
start_tm.tm_isdst = -1;
|
|
begin_time = mktime(&start_tm);
|
|
start_tm = *_XLocaltime((const time_t *)&begin_time, localtime_buf);
|
|
|
|
delta_seconds = target_time - begin_time + dst_adj;
|
|
|
|
/* The number of intervals required to span the time
|
|
* from the start_time to the target_time given the
|
|
* interval size. The interval size comes from the
|
|
* rule (e.g. W5 or 5 * wksec)
|
|
*/
|
|
num_intervals = delta_seconds / (re->re_interval * wksec);
|
|
|
|
/* This brings us to the interval closest to the target
|
|
* time, although it may not actually be the right week.
|
|
*/
|
|
base_time = begin_time + num_intervals * (re->re_interval * wksec);
|
|
base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
dst_adj = DSTAdjustment(&start_tm, &base_tm);
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
goto done;
|
|
}
|
|
|
|
if (dst_adj) {
|
|
base_time -= dst_adj;
|
|
base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
}
|
|
|
|
if (same_week(&target_tm, &base_tm)) {
|
|
int i;
|
|
int event_wday = -1;
|
|
/* Take the next event */
|
|
for (i = 0; i < ntime; i++) {
|
|
if (day_list[i].dt_day > target_tm.tm_wday) {
|
|
event_wday = day_list[i].dt_day;
|
|
break;
|
|
} else if (day_list[i].dt_day == target_tm.tm_wday) {
|
|
/* If they are the same day, the day_list time
|
|
* must be later than the target time.
|
|
*/
|
|
int day_time = (day_list[i].dt_time) ?
|
|
day_list[i].dt_time[0]:
|
|
appt_time;
|
|
/* XXX: Must walk the time list too. */
|
|
if (TIME_OF_DAY(&target_tm) <=
|
|
HOURTOSEC(day_time)) {
|
|
event_wday = day_list[i].dt_day;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
RES_WSTATE(res).res_daytime = i;
|
|
RES_WSTATE(res).res_time = 0;
|
|
|
|
/* The target date is on the same week, but falls after the
|
|
* last weekday the event could happen on.
|
|
*/
|
|
if (event_wday == -1) {
|
|
/* XXX: Lose the goto. */
|
|
goto nextinterval;
|
|
}
|
|
base_tm.tm_mday += GetWDayDiff(base_tm.tm_wday, event_wday);
|
|
} else {
|
|
nextinterval:
|
|
/* We will need to go one more interval */
|
|
if (!InTimeRange(++num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
next_time = 0;
|
|
goto done;
|
|
}
|
|
/* Since the base_tm has been normalized to the beginning
|
|
* of the week, we can assume we are on Sunday.
|
|
*/
|
|
base_tm.tm_mday += re->re_interval * 7;
|
|
/* If the target day is smaller than the base day then we
|
|
* can take the first day in the next event week.
|
|
*/
|
|
if (base_tm.tm_mday > target_tm.tm_mday) {
|
|
base_tm.tm_mday += day_list[0].dt_day;
|
|
}
|
|
RES_WSTATE(res).res_daytime = 0;
|
|
RES_WSTATE(res).res_time = 0;
|
|
}
|
|
|
|
base_tm.tm_hour = appt_time / 100;
|
|
base_tm.tm_min = appt_time % 100;
|
|
base_tm.tm_isdst = -1;
|
|
res->res_duration = re->re_duration - num_intervals;
|
|
next_time = mktime(&base_tm);
|
|
|
|
done:
|
|
return (next_time);
|
|
}
|
|
|
|
/*
|
|
* Example: MD2 1 10 20 30 #10
|
|
*/
|
|
static Tick
|
|
DoMonthDay(
|
|
const Tick target_time,
|
|
const Tick start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int num_intervals,
|
|
event_day,
|
|
nmonths;
|
|
unsigned int ndays = RE_MONTHLY(re)->md_nitems;
|
|
struct tm target_tm,
|
|
start_tm,
|
|
base_tm;
|
|
Tick base_time,
|
|
next_time = 0;
|
|
unsigned int *day_list = RE_MONTHLY(re)->md_days;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
|
|
start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
|
|
event_day = day_list[0];
|
|
num_intervals = MonthDayNumIntervals(&start_tm, &target_tm, re,
|
|
day_list, &base_tm);
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
goto done;
|
|
}
|
|
|
|
if (SAME_MONTH(&base_tm, &target_tm)) {
|
|
int next_interval = TRUE,
|
|
i;
|
|
|
|
for (i = 0; i < ndays; i++) {
|
|
unsigned int day;
|
|
|
|
day = DayOfMonth(day_list[i], base_tm.tm_mon,
|
|
base_tm.tm_year);
|
|
|
|
if (day < target_tm.tm_mday)
|
|
continue;
|
|
if (day == target_tm.tm_mday)
|
|
/* If it is on the same day, the event time
|
|
* must be later than the target time.
|
|
*/
|
|
if (TIME_OF_DAY(&target_tm)
|
|
> TIME_OF_DAY(&start_tm))
|
|
continue;
|
|
else {
|
|
event_day = day;
|
|
next_interval = FALSE;
|
|
break;
|
|
}
|
|
if (day > target_tm.tm_mday) {
|
|
event_day = day;
|
|
next_interval = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
/* We are on the right month and we found a time after the
|
|
* target time.
|
|
*/
|
|
if (!next_interval) {
|
|
base_tm.tm_mday = event_day;
|
|
base_tm.tm_isdst = -1;
|
|
next_time = mktime(&base_tm);
|
|
/* If the day exists (e.g. 31st in July) we're done */
|
|
if (DayExists(event_day, base_tm.tm_mon,
|
|
base_tm.tm_year)) {
|
|
/* Update repeat state info */
|
|
res->res_duration = re->re_duration
|
|
- num_intervals;
|
|
RES_MSTATE(res).res_day = i;
|
|
next_time = mktime(&base_tm);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
num_intervals++;
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
next_time = 0;
|
|
goto done;
|
|
}
|
|
|
|
/* Since we are moving to the next interval, use the first day */
|
|
event_day = day_list[0];
|
|
do {
|
|
/* Event is in the next interval */
|
|
base_tm.tm_mon += 1 * re->re_interval;
|
|
base_tm.tm_mday = 1;
|
|
base_tm.tm_isdst = -1;
|
|
base_time = mktime(&base_tm);
|
|
base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
|
|
/* Stop when the day exists in that month */
|
|
} while (!DayExists(event_day, base_tm.tm_mon, base_tm.tm_year));
|
|
|
|
base_tm.tm_mday = DayOfMonth(event_day, base_tm.tm_mon,
|
|
base_tm.tm_year);
|
|
base_tm.tm_isdst = -1;
|
|
next_time = mktime(&base_tm);
|
|
|
|
res->res_duration = re->re_duration - num_intervals;
|
|
RES_MSTATE(res).res_day = 0;
|
|
|
|
done:
|
|
return (next_time);
|
|
|
|
}
|
|
|
|
/*
|
|
* Example: MP2 1+ MO TU 2- TH #3
|
|
*/
|
|
static Tick
|
|
DoMonthPos(
|
|
const Tick target_time,
|
|
const Tick start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int num_intervals,
|
|
nmonths;
|
|
unsigned int ndays = RE_MONTHLY(re)->md_nitems;
|
|
struct tm target_tm,
|
|
start_tm,
|
|
base_tm;
|
|
Tick base_time = 0;
|
|
WeekDayTime *wdt_list = RE_MONTHLY(re)->md_weektime;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
|
|
start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
|
|
num_intervals = MonthPosNumIntervals(&start_tm, &target_tm, re,
|
|
wdt_list, ndays, &base_tm);
|
|
|
|
do {
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
base_time = 0;
|
|
goto done;
|
|
}
|
|
base_tm.tm_isdst = -1;
|
|
|
|
if (SAME_MONTH(&target_tm, &base_tm)) {
|
|
base_time = mktime(&base_tm);
|
|
base_tm = *_XLocaltime((const time_t *)&base_time, localtime_buf);
|
|
base_time = WeekNumberToDay(base_time,
|
|
wdt_list[0].wdt_week[0],
|
|
wdt_list[0].wdt_day[0]);
|
|
if (base_time >= target_time)
|
|
break;
|
|
/* target_time came after the slot for this month */
|
|
if (base_time)
|
|
num_intervals++;
|
|
}
|
|
|
|
base_tm.tm_mon += re->re_interval;
|
|
base_tm.tm_isdst = -1;
|
|
/* Move to the first interval after the target time */
|
|
base_time = mktime(&base_tm);
|
|
base_tm = *_XLocaltime((const time_t *)&base_time, localtime_buf);
|
|
base_time = WeekNumberToDay(base_time,
|
|
wdt_list[0].wdt_week[0],
|
|
wdt_list[0].wdt_day[0]);
|
|
} while (!base_time);
|
|
|
|
num_intervals++;
|
|
|
|
/* Update repeat state info */
|
|
res->res_duration = re->re_duration - num_intervals;
|
|
RES_MSTATE(res).res_weektime = 0;
|
|
RES_MSTATE(res).res_wday = 0;
|
|
RES_MSTATE(res).res_wtime = 0;
|
|
RES_MSTATE(res).res_wweek = 0;
|
|
|
|
done:
|
|
return (base_time);
|
|
}
|
|
|
|
/*
|
|
* Example: YM1 2 5 9 #4
|
|
*/
|
|
static Tick
|
|
DoYearByMonth(
|
|
const Tick _target_time,
|
|
const Tick _start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int num_intervals,
|
|
nyears;
|
|
unsigned int nitems = RE_YEARLY(re)->yd_nitems;
|
|
struct tm target_tm,
|
|
start_tm,
|
|
base_tm;
|
|
Tick base_time = 0,
|
|
start_time = _start_time,
|
|
target_time = _target_time;
|
|
unsigned int *month_list = RE_YEARLY(re)->yd_items;
|
|
RepeatEventState *unused;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
/* Make sure the start time is on the first real event slot. */
|
|
if (_target_time) {
|
|
if (!(unused = InitRepeatEventState(re)))
|
|
return (Tick)NULL;
|
|
start_time = DoYearByMonth(NULL, _start_time, re, unused);
|
|
free(unused);
|
|
if (_target_time < start_time)
|
|
target_time = start_time;
|
|
} else
|
|
target_time = _start_time;
|
|
|
|
target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
|
|
start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
|
|
nyears = target_tm.tm_year - start_tm.tm_year;
|
|
num_intervals = nyears / re->re_interval;
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
goto done;
|
|
}
|
|
|
|
base_tm = start_tm;
|
|
base_tm.tm_year += num_intervals * re->re_interval;
|
|
base_tm.tm_isdst = -1;
|
|
|
|
base_time = mktime(&base_tm);
|
|
base_tm = *_XLocaltime((const time_t *)&base_time, localtime_buf);
|
|
|
|
if (base_tm.tm_year == target_tm.tm_year) {
|
|
int i;
|
|
|
|
/* Look for a month that is >= the target month */
|
|
for (i = 0; i < nitems; i++) {
|
|
/* If the months are equal the target time has to be
|
|
* less than the next tick.
|
|
*/
|
|
if (month_list[i] - 1 == target_tm.tm_mon) {
|
|
base_tm.tm_mon = month_list[i] - 1;
|
|
base_tm.tm_isdst = -1;
|
|
base_time = mktime(&base_tm);
|
|
base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
if (TIMEOFMONTH(&base_tm) >=
|
|
TIMEOFMONTH(&target_tm)){
|
|
res->res_duration = re->re_duration -
|
|
num_intervals;
|
|
RES_YSTATE(res).res_daymonth = i;
|
|
goto done;
|
|
}
|
|
} else if (month_list[i] - 1 >= target_tm.tm_mon) {
|
|
base_tm.tm_mon = month_list[i] - 1;
|
|
base_tm.tm_isdst = -1;
|
|
base_time = mktime(&base_tm);
|
|
res->res_duration = re->re_duration -
|
|
num_intervals;
|
|
RES_YSTATE(res).res_daymonth = i;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The base year is greater than the target year, take the first
|
|
* month.
|
|
*/
|
|
if (!InTimeRange(++num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
base_time = 0;
|
|
goto done;
|
|
}
|
|
|
|
base_tm.tm_year += re->re_interval;
|
|
base_tm.tm_mon = month_list[0] - 1;
|
|
base_tm.tm_isdst = -1;
|
|
base_time = mktime(&base_tm);
|
|
|
|
res->res_duration = re->re_duration - num_intervals;
|
|
RES_YSTATE(res).res_daymonth = 0;
|
|
done:
|
|
return (base_time);
|
|
}
|
|
|
|
/*
|
|
* Example: YD1 100 200 300 #4
|
|
*/
|
|
static Tick
|
|
DoYearByDay(
|
|
const Tick _target_time,
|
|
const Tick _start_time,
|
|
const RepeatEvent *re,
|
|
RepeatEventState *res)
|
|
{
|
|
int num_intervals,
|
|
nyears;
|
|
unsigned int nitems = RE_YEARLY(re)->yd_nitems;
|
|
struct tm target_tm,
|
|
start_tm,
|
|
base_tm;
|
|
Tick base_time = 0,
|
|
target_time = _target_time,
|
|
start_time = _start_time;
|
|
unsigned int *day_list = RE_YEARLY(re)->yd_items;
|
|
RepeatEventState *unused;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
/* Make sure the start time is on the first real event slot. */
|
|
if (_target_time) {
|
|
if (!(unused = InitRepeatEventState(re)))
|
|
return (Tick)NULL;
|
|
start_time = DoYearByDay(NULL, _start_time, re, unused);
|
|
free(unused);
|
|
if (_target_time < start_time)
|
|
target_time = start_time;
|
|
} else
|
|
target_time = _start_time;
|
|
|
|
target_tm = *_XLocaltime((const time_t *)&target_time, localtime_buf);
|
|
start_tm = *_XLocaltime((const time_t *)&start_time, localtime_buf);
|
|
nyears = target_tm.tm_year - start_tm.tm_year;
|
|
num_intervals = nyears / re->re_interval;
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* XXX: day_list == 366 is a special case...that is not supported
|
|
* right now.
|
|
*/
|
|
|
|
base_tm = start_tm;
|
|
base_tm.tm_year += num_intervals * re->re_interval;
|
|
|
|
/* If the years are the same then go down the list of days looking
|
|
* for one later than the target time.
|
|
*/
|
|
if (base_tm.tm_year == target_tm.tm_year) {
|
|
int i;
|
|
for (i = 0; i < nitems; i++) {
|
|
base_tm.tm_mday = day_list[i];
|
|
base_tm.tm_mon = 0;
|
|
base_tm.tm_isdst = -1;
|
|
base_time = mktime(&base_tm);
|
|
|
|
/* We found the closest tick */
|
|
if (base_time >= target_time) {
|
|
res->res_duration = re->re_duration -
|
|
num_intervals;
|
|
RES_YSTATE(res).res_daymonth = i;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We either were not on the same year or the above fell through
|
|
* as we crossed into the next interval.
|
|
*/
|
|
|
|
num_intervals++;
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration)) {
|
|
/* We hit the duration limit. */
|
|
base_time = 0;
|
|
goto done;
|
|
}
|
|
|
|
base_tm.tm_year += 1 * re->re_interval;
|
|
base_tm.tm_mday = day_list[0];
|
|
base_tm.tm_mon = 0;
|
|
base_tm.tm_isdst = -1;
|
|
base_time = mktime(&base_tm);
|
|
|
|
res->res_duration = re->re_duration - num_intervals;
|
|
RES_YSTATE(res).res_daymonth = 0;
|
|
|
|
done:
|
|
return (base_time);
|
|
}
|
|
|
|
/* Calculate the number of months between two dates */
|
|
/* 3/20/90 - 1/2/94 = 46 months */
|
|
static int
|
|
GetMonthDiff(
|
|
const struct tm *start_tm,
|
|
const struct tm *end_tm)
|
|
{
|
|
return ((end_tm->tm_year - start_tm->tm_year + 1) * 12 -
|
|
(start_tm->tm_mon + 1) - (12 - (end_tm->tm_mon + 1)));
|
|
}
|
|
|
|
static Tick
|
|
DSTAdjustment(
|
|
const struct tm *tm1,
|
|
const struct tm *tm2)
|
|
{
|
|
if (tm1->tm_isdst == -1 || tm2->tm_isdst == -1)
|
|
return 0;
|
|
|
|
if (tm1->tm_isdst != tm2->tm_isdst) {
|
|
if (tm1->tm_isdst) /* From day light savings to standard */
|
|
return -3600;
|
|
else /* From standard to day light savings */
|
|
return 3600;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
DoDSTAdjustment(
|
|
const Tick begin_time,
|
|
struct tm *end_time) /* Return */
|
|
{
|
|
struct tm next_day;
|
|
Tick dst_adj,
|
|
_begin_time = begin_time;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
/* By moving ahead one day we might have crossed a dst border.*/
|
|
next_day = *_XLocaltime(&begin_time, localtime_buf);
|
|
dst_adj = DSTAdjustment(end_time, &next_day);
|
|
if (dst_adj) {
|
|
_begin_time -= dst_adj;
|
|
*end_time = *_XLocaltime(&_begin_time, localtime_buf);
|
|
} else
|
|
*end_time = next_day;
|
|
}
|
|
|
|
/*
|
|
* Initialize the RepeatEventState struct.
|
|
*/
|
|
static RepeatEventState *
|
|
InitRepeatEventState(
|
|
const RepeatEvent *re)
|
|
{
|
|
RepeatEventState *res;
|
|
|
|
if (!(res = (RepeatEventState *)calloc(1, sizeof(RepeatEventState))))
|
|
return (RepeatEventState *)NULL;
|
|
|
|
res->res_re = re;
|
|
res->res_duration = re->re_duration;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* Determine the number of intervals between the start_tm and the target_tm,
|
|
* the base_tm which is returned is the last event generated before the
|
|
* target_tm.
|
|
*/
|
|
static int
|
|
MonthDayNumIntervals(
|
|
struct tm *start_tm,
|
|
struct tm *target_tm,
|
|
const RepeatEvent *re,
|
|
const unsigned int *md_days,
|
|
struct tm *base_tm) /* Return */
|
|
{
|
|
int num_intervals = 0;
|
|
struct tm cur_tm;
|
|
Tick cur_time,
|
|
base_time,
|
|
last_time,
|
|
target_time;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
/* The 28th - 31st may not exist in a given month thus if only these
|
|
* days are specified in a rule it is necessary to calculate the
|
|
* correct month by brute force versus using a mathematical calculation.
|
|
*/
|
|
if (md_days[0] > 28) {
|
|
*base_tm = *start_tm;
|
|
cur_tm = *start_tm;
|
|
cur_tm.tm_mday = 1;
|
|
cur_tm.tm_isdst = -1;
|
|
cur_time = mktime(&cur_tm);
|
|
target_tm->tm_isdst = -1;
|
|
target_time = mktime((struct tm*)target_tm);
|
|
last_time = cur_time;
|
|
|
|
while (cur_time < target_time) {
|
|
cur_tm.tm_mon += re->re_interval;
|
|
cur_tm.tm_isdst = -1;
|
|
cur_time = mktime(&cur_tm);
|
|
cur_tm = *_XLocaltime((const time_t *)&cur_time, localtime_buf);
|
|
|
|
if (DayExists(md_days[0], cur_tm.tm_mon,
|
|
cur_tm.tm_year)) {
|
|
if (cur_time >= target_time) {
|
|
cur_time = last_time;
|
|
cur_tm = *_XLocaltime((const time_t *)
|
|
&last_time, localtime_buf);
|
|
break;
|
|
}
|
|
/* Remember the last time in case we need to
|
|
* back up one interval.
|
|
*/
|
|
last_time = cur_time;
|
|
*base_tm = cur_tm;
|
|
num_intervals++;
|
|
}
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration))
|
|
break;
|
|
|
|
if (SAME_MONTH(target_tm, &cur_tm)) break;
|
|
}
|
|
} else {
|
|
num_intervals = GetMonthDiff(start_tm, target_tm)
|
|
/ re->re_interval;
|
|
*base_tm = *start_tm;
|
|
base_tm->tm_isdst = -1;
|
|
/* Move to the closest interval before the target time */
|
|
base_tm->tm_mon += num_intervals * re->re_interval;
|
|
base_time = mktime(base_tm);
|
|
*base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
}
|
|
|
|
return (num_intervals);
|
|
}
|
|
|
|
/*
|
|
* Count the number of intervals up to, but before the target time. The
|
|
* base time returned in the last valid interval before the target time.
|
|
*/
|
|
static int
|
|
MonthPosNumIntervals(
|
|
struct tm *start_tm,
|
|
struct tm *target_tm,
|
|
const RepeatEvent *re,
|
|
const WeekDayTime *wdt_list,
|
|
const unsigned int nwdt_list,
|
|
struct tm *base_tm) /* Return */
|
|
{
|
|
int num_intervals = 0,
|
|
brute_force = TRUE,
|
|
i, j;
|
|
struct tm cur_tm;
|
|
Tick cur_time,
|
|
base_time,
|
|
target_time;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
for (i = 0; i < nwdt_list; i++) {
|
|
for (j = 0; j < wdt_list[i].wdt_nweek; j++) {
|
|
if ((wdt_list[i].wdt_week[j] != WK_F5) &&
|
|
(wdt_list[i].wdt_week[j] != WK_L5)) {
|
|
brute_force = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (brute_force == FALSE) break;
|
|
}
|
|
|
|
/* The weekday associated with +5 or -5 may not exist in a given
|
|
* month thus if only these weekdays are specified in a rule it is
|
|
* necessary to calculate the correct month by brute force versus
|
|
* using a mathematical calculation.
|
|
*/
|
|
if (brute_force){
|
|
*base_tm = *start_tm;
|
|
cur_tm = *start_tm;
|
|
cur_tm.tm_isdst = -1;
|
|
cur_tm.tm_mday = 1;
|
|
cur_time = mktime(&cur_tm);
|
|
target_tm->tm_isdst = -1;
|
|
target_time = mktime((struct tm *)target_tm);
|
|
|
|
/* Count the start_time */
|
|
if (cur_time < target_time)
|
|
num_intervals++;
|
|
|
|
while (cur_time < target_time) {
|
|
if (SAME_MONTH(target_tm, &cur_tm)) break;
|
|
|
|
cur_tm.tm_mon += re->re_interval;
|
|
cur_tm.tm_isdst = -1;
|
|
cur_time = mktime(&cur_tm);
|
|
cur_tm = *_XLocaltime((const time_t *)&cur_time, localtime_buf);
|
|
|
|
if (OccurenceExists(wdt_list, nwdt_list, cur_time)) {
|
|
num_intervals++;
|
|
/* Only update the cur_tm if valid slot there */
|
|
*base_tm = cur_tm;
|
|
}
|
|
|
|
if (!InTimeRange(num_intervals, re->re_duration))
|
|
break;
|
|
|
|
}
|
|
} else {
|
|
num_intervals = GetMonthDiff(start_tm, target_tm)
|
|
/ re->re_interval;
|
|
*base_tm = *start_tm;
|
|
base_tm->tm_isdst = -1;
|
|
/* Move to the closest interval before the target time */
|
|
base_tm->tm_mon += num_intervals * re->re_interval;
|
|
base_time = mktime(base_tm);
|
|
*base_tm = *_XLocaltime(&base_time, localtime_buf);
|
|
}
|
|
|
|
return (num_intervals);
|
|
}
|
|
|
|
void
|
|
FillInRepeatEvent(
|
|
const Tick start_time,
|
|
RepeatEvent *re)
|
|
{
|
|
struct tm *start_tm;
|
|
int i;
|
|
_Xltimeparams localtime_buf;
|
|
|
|
start_tm = _XLocaltime(&start_time, localtime_buf);
|
|
|
|
switch (re->re_type) {
|
|
case RT_MINUTE:
|
|
break;
|
|
case RT_DAILY:
|
|
if (!RE_DAILY(re)->dd_ntime) {
|
|
RE_DAILY(re)->dd_time = (Time *)calloc(1, sizeof(Time));
|
|
RE_DAILY(re)->dd_time[0] = start_tm->tm_hour * 100 +
|
|
start_tm->tm_min;
|
|
RE_DAILY(re)->dd_ntime = 1;
|
|
}
|
|
break;
|
|
case RT_WEEKLY:
|
|
if (!RE_WEEKLY(re)->wd_ndaytime) {
|
|
RE_WEEKLY(re)->wd_daytime =
|
|
(DayTime *)calloc(1, sizeof(DayTime));
|
|
RE_WEEKLY(re)->wd_daytime[0].dt_day = start_tm->tm_wday;
|
|
RE_WEEKLY(re)->wd_daytime[0].dt_ntime = 1;
|
|
RE_WEEKLY(re)->wd_daytime[0].dt_time =
|
|
(Time *)calloc(1, sizeof(Time));
|
|
RE_WEEKLY(re)->wd_daytime[0].dt_time[0] =
|
|
start_tm->tm_hour * 100 +
|
|
start_tm->tm_min;
|
|
RE_WEEKLY(re)->wd_ndaytime = 1;
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < RE_WEEKLY(re)->wd_ndaytime; i++) {
|
|
if (!RE_WEEKLY(re)->wd_daytime[i].dt_ntime) {
|
|
RE_WEEKLY(re)->wd_daytime[i].dt_ntime =
|
|
1;
|
|
RE_WEEKLY(re)->wd_daytime[i].dt_time =
|
|
(Time *)calloc(1, sizeof(Time));
|
|
RE_WEEKLY(re)->wd_daytime[i].dt_time[0]=
|
|
start_tm->tm_hour * 100 +
|
|
start_tm->tm_min;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case RT_MONTHLY_POSITION:
|
|
if (!RE_MONTHLY(re)->md_nitems) {
|
|
RE_MONTHLY(re)->md_weektime =
|
|
(WeekDayTime *)calloc(1, sizeof(WeekDayTime));
|
|
RE_MONTHLY(re)->md_weektime[0].wdt_nday = 1;
|
|
RE_MONTHLY(re)->md_weektime[0].wdt_day =
|
|
(WeekDay *)calloc(1, sizeof(WeekDay));
|
|
RE_MONTHLY(re)->md_weektime[0].wdt_day[0] =
|
|
start_tm->tm_wday;
|
|
RE_MONTHLY(re)->md_weektime[0].wdt_nweek = 1;
|
|
RE_MONTHLY(re)->md_weektime[0].wdt_week =
|
|
(WeekNumber *)calloc(1, sizeof(WeekNumber));
|
|
RE_MONTHLY(re)->md_weektime[0].wdt_week[0] =
|
|
GetWeekNumber(start_time);
|
|
RE_MONTHLY(re)->md_nitems = 1;
|
|
}
|
|
break;
|
|
case RT_MONTHLY_DAY:
|
|
if (!RE_MONTHLY(re)->md_nitems) {
|
|
RE_MONTHLY(re)->md_days =
|
|
(unsigned int *)calloc(1, sizeof(unsigned int));
|
|
RE_MONTHLY(re)->md_days[0] = start_tm->tm_mday;
|
|
RE_MONTHLY(re)->md_nitems = 1;
|
|
}
|
|
break;
|
|
case RT_YEARLY_MONTH:
|
|
if (!RE_YEARLY(re)->yd_nitems) {
|
|
RE_YEARLY(re)->yd_items =
|
|
(unsigned int *)calloc(1, sizeof(unsigned int));
|
|
RE_YEARLY(re)->yd_items[0] = start_tm->tm_mon + 1;
|
|
RE_YEARLY(re)->yd_nitems = 1;
|
|
}
|
|
break;
|
|
case RT_YEARLY_DAY:
|
|
if (!RE_YEARLY(re)->yd_nitems) {
|
|
RE_YEARLY(re)->yd_items =
|
|
(unsigned int *)calloc(1, sizeof(unsigned int));
|
|
RE_YEARLY(re)->yd_items[0] = start_tm->tm_yday;
|
|
RE_YEARLY(re)->yd_nitems = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|