/* Copyright (c) 1993 by Sanjay Ghemawat */ #include #include #include #include "arrays.h" #include "Month.h" #include "WeekDay.h" #include "Year.h" #include "dateset.h" #include "lexer.h" #include "misc.h" // Helper routines static void advance_months(Month&, int&, int); static void append_date(charArray* buffer, Date date) { int day, year; WeekDay wday; Month month; date.BreakDown(day, wday, month, year); format(buffer, "%s %s %d, %d", wday.Name(), month.Name(), day, year); } static void append_int(charArray* buffer, int i) { // Generate appropriate suffix based on value of "i" char const* suff = "th"; if (((i/10) % 10) != 1) { // Second last digit is not "1". Special case on the last digit. switch (i % 10) { case 1: suff = "st"; break; case 2: suff = "nd"; break; case 3: suff = "rd"; break; } } format(buffer, "%d%s", i, suff); } class DateSetRep { public: DateSetRep(); virtual ~DateSetRep(); virtual DateSetRep* copy() const = 0; virtual DateSet::RepeatType type() const = 0; virtual void describe(charArray*) const = 0; virtual int contains(Date) const = 0; virtual int search(Date, Date&) const = 0; virtual int read(Lexer*) = 0; virtual void write(charArray*) const = 0; virtual int search(Date anchor, Date& result, Date start, Date finish, DateList const& deleted) const; /* * modifies result * effects Find smallest d such that d occurs in *this and d >= anchor * and d is in [start..finish] and d is not in deleted. * Set result to d and return 1 if successful. * If no d could be found, return 0 without modifying result. */ /* Default normalization does nothing */ virtual DateSetRep* normalize(Date& start, Date& finish, DateList& deleted) const; }; /* * DateSet(rep) = {} */ class EmptyDateSetRep : public DateSetRep { public: EmptyDateSetRep(); virtual ~EmptyDateSetRep(); virtual DateSetRep* copy() const; virtual DateSet::RepeatType type() const; virtual void describe(charArray*) const; virtual int contains(Date) const; virtual int search(Date, Date&) const; virtual int read(Lexer*); virtual void write(charArray*) const; }; /* * DateSet(rep) = if (deleted) then {} else { date } */ class SingleDateSetRep : public DateSetRep { public: SingleDateSetRep(Date); virtual ~SingleDateSetRep(); virtual DateSetRep* copy() const; virtual DateSet::RepeatType type() const; virtual void describe(charArray*) const; virtual int contains(Date) const; virtual int search(Date, Date&) const; virtual int read(Lexer*); virtual void write(charArray*) const; private: Date date; /* Actual date */ int deleted; /* Has date been deleted? */ }; /* * DateSet(rep) = { d | d = (anchor + k*interval) where k is an integer } */ class DayBasedDateSetRep : public DateSetRep { public: DayBasedDateSetRep(int, Date); virtual ~DayBasedDateSetRep(); virtual DateSetRep* copy() const; virtual DateSet::RepeatType type() const; virtual void describe(charArray*) const; virtual int contains(Date) const; virtual int search(Date, Date&) const; virtual int read(Lexer*); virtual void write(charArray*) const; private: int interval; Date anchor; }; /* * For "k"th day|workday|weekday of each month (optionally counting * from end of month) */ enum MType { ByDay, ByWorkDay, ByWeek }; // Complex month repetition class class MonthBasedDateSetRep : public DateSetRep { public: // More complex repetition MonthBasedDateSetRep(int c, int i, Date a, MType t, WeekDay w, int b); virtual ~MonthBasedDateSetRep(); virtual DateSetRep* copy() const; virtual DateSet::RepeatType type() const; virtual void describe(charArray*) const; virtual int contains(Date) const; virtual int search(Date, Date&) const; virtual int read(Lexer*); virtual void write(charArray*) const; protected: int find_in_month(Month m, int y) const; int count; // Count of days|workdays|weeks int interval; // Month interval Date anchor; // Anchor date Month anchorMonth; // Anchor month int anchorYear; // Anchor year MType mtype; // Repetition type WeekDay weekday; // Week day for repeat by week int backward; // Search from end of month? }; // Simple month repetition class class MonthDateSetRep : public MonthBasedDateSetRep { public: MonthDateSetRep(int i, Date a); // Most routines are inherited from MonthBasedDatSetRep; // Special routines so that we can leave format unchanged // from Ical charArray format 1.6. virtual DateSetRep* copy() const; virtual int read(Lexer*); virtual void write(charArray*) const; }; /* * DateSet(rep) = { d | wday(d) in days and month(d) in months * and in_range(d) and !is_deleted(d). } */ class WeekSetDateSetRep : public DateSetRep { public: WeekSetDateSetRep(SmallIntSet, SmallIntSet); virtual ~WeekSetDateSetRep(); virtual DateSetRep* copy() const; virtual DateSet::RepeatType type() const; virtual void describe(charArray*) const; virtual int contains(Date) const; virtual int search(Date, Date&) const; virtual int read(Lexer*); virtual void write(charArray*) const; virtual DateSetRep* normalize(Date&, Date&, DateList&) const; private: SmallIntSet days; SmallIntSet months; }; /* * DateSet(rep) = { d | mday(d) in days and month(d) in months * and in_range(d) and !contains_date(deleted, d). } */ class MonthSetDateSetRep : public DateSetRep { public: MonthSetDateSetRep(SmallIntSet, SmallIntSet); virtual ~MonthSetDateSetRep(); virtual DateSetRep* copy() const; virtual DateSet::RepeatType type() const; virtual void describe(charArray*) const; virtual int contains(Date) const; virtual int search(Date, Date&) const; virtual int read(Lexer*); virtual void write(charArray*) const; virtual DateSetRep* normalize(Date&, Date&, DateList&) const; private: SmallIntSet days; SmallIntSet months; }; static int contains_date(DateList const& list, Date d) { for (int i = 0; i < list.size(); i++) { if (list[i] == d) { return 1; } } return 0; } DateSet::DateSet() { normalized = 1; rep = new EmptyDateSetRep; start = Date::First(); finish = Date::Last(); deleted.clear(); } DateSet::DateSet(DateSet const& d) { d.normalize(); normalized = 1; rep = d.rep->copy(); start = Date::First(); finish = Date::Last(); deleted = d.deleted; } DateSet::~DateSet() { delete rep; } void DateSet::operator = (DateSet const& d) { d.normalize(); normalized = 1; DateSetRep* oldrep = rep; rep = d.rep->copy(); delete oldrep; start = d.start; finish = d.finish; deleted = d.deleted; } int DateSet::contains(Date d) const { normalize(); return (rep->contains(d) && (d >= start) && (d <= finish) && !contains_date(deleted, d)); } int DateSet::repeats() const { normalize(); return ((rep->type() != DateSet::None) && !empty()); } int DateSet::empty() const { normalize(); Date d; return (!first(d)); } int DateSet::get_range(Date& s, Date& f) const { normalize(); // Fail if no occurrences Date d; if (!first(d)) return 0; // Singleton range for singleton item if (!repeats()) { s = d; f = d; return 1; } // Otherwise use the clipping range s = start; f = finish; return 1; } int DateSet::first(Date& d) const { return (rep->search(start, d, start, finish, deleted)); } int DateSet::next(Date d, Date& result) const { if (d == Date::Last()) { return 0; } else { return (rep->search(d+1, result, start, finish, deleted)); } } int DateSet::occurs_before(DateSet const& d1, DateSet const& d2) { Date x1, x2; if (! d1.first(x1)) { /* No way d1 occurs before d2 */ return 0; } if (! d2.first(x2)) { /* D1 has occurrence, D2 does not */ return 1; } return (x1 < x2); } int DateSet::occurs_compare(DateSet const& d1, DateSet const& d2) { Date x1, x2; if (! d1.first(x1)) { if (d2.empty()) { /* Both d1 and d2 are empty */ return 0; } /* d1 is empty, d2 is not */ return 1; } if (! d2.first(x2)) { /* d1 occurs, but d2 does not */ return -1; } return ((x1 < x2) ? -1 : ((x1 > x2) ? 1 : 0)); } DateSet::RepeatType DateSet::type() const { normalize(); return (rep->type()); } void DateSet::describe(charArray* buffer) const { normalize(); rep->describe(buffer); } void DateSet::set_empty() { start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new EmptyDateSetRep; normalized = 0; } void DateSet::set_date(Date d) { start = d; finish = d; deleted.clear(); delete rep; rep = new SingleDateSetRep(d); normalized = 0; } void DateSet::set_day_based_repeat(int i, Date a) { assert(i > 0); start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new DayBasedDateSetRep(i, a); normalized = 0; } void DateSet::set_month_based_repeat(int i, Date a) { assert (i > 0); start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new MonthDateSetRep(i, a); normalized = 0; } void DateSet::set_week_set(SmallIntSet days, SmallIntSet months) { start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new WeekSetDateSetRep(days, months); normalized = 0; } void DateSet::set_month_set(SmallIntSet days, SmallIntSet months) { start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new MonthSetDateSetRep(days, months); normalized = 0; } void DateSet::set_monthly_by_days(int count, int i, Date a, int back) { start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; // Check for simple case if (!back && (count > 0) && (count <= a.GetMonth().Size(a.GetYear()))) { a = Date(count, a.GetMonth(), a.GetYear()); rep = new MonthDateSetRep(i, a); } else { rep = new MonthBasedDateSetRep(count, i, a, ByDay, WeekDay::First(), back); } normalized = 0; } void DateSet::set_monthly_by_workdays(int count, int i, Date a, int back) { start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new MonthBasedDateSetRep(count, i, a, ByWorkDay, WeekDay::First(), back); normalized = 0; } void DateSet::set_monthly_by_weeks(int count, WeekDay w, int i, Date a, int back) { start = Date::First(); finish = Date::Last(); deleted.clear(); delete rep; rep = new MonthBasedDateSetRep(count, i, a, ByWeek, w, back); normalized = 0; } void DateSet::set_start(Date s) { start = s; normalized = 0; } void DateSet::set_finish(Date f) { finish = f; normalized = 0; } void DateSet::delete_occurrence(Date d) { deleted.append(d); normalized = 0; } void DateSet::normalize() const { int i; if (! normalized) { /* Cast away const-ness */ DateSet* self = (DateSet*) this; self->normalized = 1; /* Common normalizations */ Date f, s; if (! first(f)) { delete rep; self->rep = new EmptyDateSetRep; self->start = Date::First(); self->finish = Date::Last(); self->deleted.clear(); } else { if (! next(f, s)) { delete rep; self->rep = new SingleDateSetRep(f); self->start = Date::First(); self->finish = Date::Last(); self->deleted.clear(); } } /* Type-specific normalizations */ DateSetRep* newRep; while ((newRep = self->rep->normalize(self->start, self->finish, self->deleted)) != 0) { delete self->rep; self->rep = newRep; } /* Normalize start date */ if (start != Date::First()) { Date d; if (!rep->search(Date::First(), d) || (d >= start)) { /* Nothing exists before start anyway */ self->start = Date::First(); } } /* Normalize finish date */ if (finish != Date::Last()) { Date d; if (!rep->search(finish+1, d)) { /* Nothing exists after finish anyway */ self->finish = Date::Last(); } } // Trim delete list by setting "start" date int dcount = 0; for (i = 0; i < deleted.size(); i++) if (deleted[i] < f) dcount++; if (dcount > 0) { // At least one of the deleted dates occurs before // the current first date. We can clip off all deleted // dates before "f" by setting "start" to "f". self->start = f; i = 0; while (i < deleted.size()) { if (deleted[i] < f) { deleted[i] = deleted.high(); self->deleted.remove(); continue; } i++; } } } } /* * String format is * * -> [Start ] [Finish ] [Deleted ] End * -> Empty * | Single * | Days * | Months * | WeekDays Months * | MonthDays Months * * -> // * -> * -> * -> */ static int read_date(Lexer*, Date&); static void write_date(charArray*, Date); void DateSet::write(charArray* output) const { normalize(); rep->write(output); if (start != Date::First()) { append_string(output, "\nStart "); write_date(output, start); } if (finish != Date::Last()) { append_string(output, "\nFinish "); write_date(output, finish); } for (int i = 0; i < deleted.size(); i++) { append_string(output, "\nDeleted "); write_date(output, deleted[i]); } append_string(output, " End\n"); } int DateSet::read(Lexer* lexer) { char const* keyword; if (! lexer->SkipWS() || ! lexer->GetId(keyword)) { return 0; } Date date = Date::First(); SmallIntSet set; set.Clear(); DateSetRep* newRep; if (strcmp(keyword, "Empty") == 0) { newRep = new EmptyDateSetRep; } else if (strcmp(keyword, "Single") == 0) { newRep = new SingleDateSetRep(date); } else if (strcmp(keyword, "Days") == 0) { newRep = new DayBasedDateSetRep(1, date); } else if (strcmp(keyword, "Months") == 0) { newRep = new MonthDateSetRep(1, date); } else if (strcmp(keyword, "ComplexMonths") == 0) { newRep = new MonthBasedDateSetRep(1,1,date,ByDay,WeekDay::First(),0); } else if (strcmp(keyword, "WeekDays") == 0) { newRep = new WeekSetDateSetRep(set, set); } else if (strcmp(keyword, "MonthDays") == 0) { newRep = new MonthSetDateSetRep(set, set); } else { return 0; } if (! newRep->read(lexer)) { delete newRep; return 0; } delete rep; rep = newRep; normalized = 0; /* Read the rest of the spec */ while (1) { char const* keyword; if (! lexer->SkipWS() || ! lexer->GetId(keyword)) { return 0; } if (strcmp(keyword, "End") == 0) { return 1; } if (strcmp(keyword, "Start") == 0) { Date s; if (! lexer->SkipWS() || ! read_date(lexer, s)) { return 0; } start = s; continue; } if (strcmp(keyword, "Finish") == 0) { Date f; if (! lexer->SkipWS() || ! read_date(lexer, f)) { return 0; } finish = f; continue; } if (strcmp(keyword, "Deleted") == 0) { Date d; if (! lexer->SkipWS() || ! read_date(lexer, d)) { return 0; } deleted.append(d); continue; } return 0; } return 1; } /* * Helper routines. */ /* * effects Returns |x| */ static inline int ABS(int x) { return (x > 0) ? x : -x; } static int count_ranges(SmallIntSet set) { int in_range; int count; count = 0; in_range = 0; for (int i = 0; i <= SISetLargestMember; i++) { if (set.Member(i)) { if (! in_range) { /* Starting new range */ count++; in_range = 1; } } else { if (in_range) { /* Ending range */ in_range = 0; } } } return count; } /* * requires year(start) == year(finish) * months contains exactly one contiguous range * modifies start, finish * effects Clips start and finish to conform to month range. */ void set_month_range(Date& start, Date& finish, SmallIntSet months) { int year = start.GetYear(); /* Find range of months */ int a, b; /* Empty range to begin with */ a = 12; b = 11; for (int i = 1; i <= 12; i++) { if (months.Member(i)) { a = i; while ((i <= 12) && months.Member(i)) { i++; } b = i-1; break; } } Month aMonth = Month::First() + a - 1; Month bMonth = Month::First() + b - 1; start = Date(1, aMonth, year); finish = Date(bMonth.Size(year), bMonth, year); } static int read_date(Lexer* lexer, Date& date) { int d, m, y; if (! lexer->SkipWS() || ! lexer->GetNumber(d) || ! lexer->Skip('/') || ! lexer->GetNumber(m) || ! lexer->Skip('/') || ! lexer->GetNumber(y) || (y < Year::First()) || (y > Year::Last()) || (m < 1) || (m > 12) || (d < 1) || (d > (Month::First() + (m - 1)).Size(y))) { return 0; } date = Date(d, Month::First() + (m - 1), y); return 1; } static void write_date(charArray* output, Date date) { int d; Month m; WeekDay w; int y; date.BreakDown(d, w, m, y); format(output, "%d/%d/%d", d, m.Index(), y); } /* * Base operations. */ DateSetRep::DateSetRep() { } DateSetRep::~DateSetRep() { } int DateSetRep::search(Date anchor, Date& result, Date start, Date finish, DateList const& deleted) const { Date d = anchor; if (d < start) { d = start; } while (search(d, d) && (d <= finish)) { if (!contains_date(deleted, d)) { result = d; return 1; } d += 1; } return 0; } DateSetRep* DateSetRep::normalize(Date&, Date&, DateList&) const { return 0; } /* * Empty rep. */ EmptyDateSetRep::EmptyDateSetRep() { } EmptyDateSetRep::~EmptyDateSetRep() { } DateSetRep* EmptyDateSetRep::copy() const { return new EmptyDateSetRep; } DateSet::RepeatType EmptyDateSetRep::type() const { return DateSet::Other; } void EmptyDateSetRep::describe(charArray* buffer) const { append_string(buffer, "Empty"); } int EmptyDateSetRep::contains(Date) const { return 0; } int EmptyDateSetRep::search(Date, Date&) const { return 0; } int EmptyDateSetRep::read(Lexer* lexer) { return 1; } void EmptyDateSetRep::write(charArray* output) const { append_string(output, "Empty"); } /* * SingleDateSetRep. */ SingleDateSetRep::SingleDateSetRep(Date d) { date = d; deleted = 0; } SingleDateSetRep::~SingleDateSetRep() { } DateSetRep* SingleDateSetRep::copy() const { return new SingleDateSetRep(date); } DateSet::RepeatType SingleDateSetRep::type() const { return DateSet::None; } void SingleDateSetRep::describe(charArray* buffer) const { append_date(buffer, date); } int SingleDateSetRep::contains(Date d) const { return (!deleted && (d == date)); } int SingleDateSetRep::search(Date d, Date& result) const { if (!deleted && (d <= date)) { result = date; return 1; } return 0; } int SingleDateSetRep::read(Lexer* lexer) { return (lexer->SkipWS() && read_date(lexer, date)); } void SingleDateSetRep::write(charArray* output) const { append_string(output, "Single "); write_date(output, date); } /* * DayBasedDateSetRep. */ DayBasedDateSetRep::DayBasedDateSetRep(int i, Date a) { interval = i; anchor = a; } DayBasedDateSetRep::~DayBasedDateSetRep() { } DateSetRep* DayBasedDateSetRep::copy() const { return new DayBasedDateSetRep(interval, anchor); } DateSet::RepeatType DayBasedDateSetRep::type() const { switch (interval) { case 1: return DateSet::Daily; case 7: return DateSet::Weekly; case 14: return DateSet::BiWeekly; case 21: return DateSet::ThreeWeekly; case 28: return DateSet::FourWeekly; default: return DateSet::Other; } } void DayBasedDateSetRep::describe(charArray* buffer) const { switch (interval) { case 1: append_string(buffer, "Daily"); break; case 7: append_string(buffer, "Every "); append_string(buffer, anchor.GetWDay().Name()); break; case 14: case 21: case 28: append_string(buffer, anchor.GetWDay().Name()); append_string(buffer, " Every "); append_int(buffer, (interval / 7)); append_string(buffer, " Week"); break; default: append_string(buffer, "Every "); append_int(buffer, interval); append_string(buffer, " Day From "); append_date(buffer, anchor); break; } } int DayBasedDateSetRep::contains(Date d) const { int diff = ABS(d - anchor); return ((diff % interval) == 0); } int DayBasedDateSetRep::search(Date d, Date& result) const { /* Adjust d to match anchor */ int diff = anchor - d; if (diff >= 0) { /* Anchor occurs on or after d */ d += diff % interval; } else { /* Anchor occurs before d */ d += interval - ((-1 - diff) % interval) - 1; } result = d; return 1; } int DayBasedDateSetRep::read(Lexer* lexer) { return (lexer->SkipWS() && read_date(lexer, anchor) && lexer->SkipWS() && lexer->GetNumber(interval) && (interval > 0)); } void DayBasedDateSetRep::write(charArray* output) const { append_string(output, "Days "); write_date(output, anchor); format(output, " %d", interval); } /* * MonthBasedDateSetRep. */ MonthBasedDateSetRep::MonthBasedDateSetRep(int c, int i, Date a, MType t, WeekDay w, int b) { int junk1; WeekDay junk2; a.BreakDown(junk1, junk2, anchorMonth, anchorYear); anchor = a; count = c; interval = i; mtype = t; weekday = w; backward = b; } MonthBasedDateSetRep::~MonthBasedDateSetRep() { } DateSetRep* MonthBasedDateSetRep::copy() const { return new MonthBasedDateSetRep(count, interval, anchor, mtype, weekday, backward); } DateSet::RepeatType MonthBasedDateSetRep::type() const { switch (interval) { case 1: return DateSet::Monthly; case 2: return DateSet::TwoMonthly; case 3: return DateSet::ThreeMonthly; case 4: return DateSet::FourMonthly; case 6: return DateSet::SixMonthly; case 12: return DateSet::Annual; default: return DateSet::Other; } } void MonthBasedDateSetRep::describe(charArray* buffer) const { if (backward) { if (count != 1) { append_int(buffer, count); append_string(buffer, "-"); } append_string(buffer, "Last"); } else { append_int(buffer, count); } switch (mtype) { case ByDay: append_string(buffer, " of"); break; case ByWorkDay: append_string(buffer, " Working Day"); break; case ByWeek: append_string(buffer, " "); append_string(buffer, weekday.Name()); break; } switch (interval) { case 1: append_string(buffer, " Every Month"); break; case 12: append_string(buffer, " Every "); append_string(buffer, anchor.GetMonth().Name()); break; default: append_string(buffer, " Every "); append_int(buffer, interval); append_string(buffer, " Month"); break; } } int MonthBasedDateSetRep::contains(Date d) const { int dDay, dYear; Month dMonth; WeekDay dWDay; d.BreakDown(dDay, dWDay, dMonth, dYear); if (find_in_month(dMonth, dYear) != dDay) return 0; int diff = ABS((dYear - anchorYear) * 12 + (dMonth - anchorMonth)); return ((diff % interval) == 0); } int MonthBasedDateSetRep::search(Date d, Date& result) const { // Special checks for empty date set if (count < 1) return 0; switch (mtype) { case ByDay: if (count > 31) return 0; break; case ByWorkDay: if (count > 23) return 0; break; case ByWeek: if (count > 5) return 0; break; } int dDay, dYear; Month dMonth; WeekDay dWDay; d.BreakDown(dDay, dWDay, dMonth, dYear); /* Adjust to match anchor */ if (dDay > find_in_month(dMonth, dYear)) advance_months(dMonth, dYear, 1); /* Adjust based on interval */ int diff = (anchorYear - dYear) * 12 + (anchorMonth - dMonth); if (diff > 0) { /* Anchor occurs after d */ advance_months(dMonth, dYear, diff % interval); } else if (diff < 0) { /* Anchor occurs before d */ advance_months(dMonth, dYear, (interval - ((-1 - diff) % interval) - 1)); } while (1) { int x = find_in_month(dMonth, dYear); if (x > 0) { result = Date(x, dMonth, dYear); return 1; } advance_months(dMonth, dYear, interval); } return 0; } void MonthBasedDateSetRep::write(charArray* output) const { format(output, "ComplexMonths %d %d ", interval, count); write_date(output, anchor); append_string(output, (backward ? " Backward " : " Forward ")); switch (mtype) { case ByDay: append_string(output, "ByDay"); break; case ByWorkDay: append_string(output, "ByWorkDay"); break; case ByWeek: format(output, "ByWeek %d", weekday.Index()); break; } } int MonthBasedDateSetRep::read(Lexer* lexer) { if (!(lexer->SkipWS() && lexer->GetNumber(interval) && lexer->SkipWS() && lexer->GetNumber(count) && lexer->SkipWS() && read_date(lexer, anchor))) return 0; int junk1; WeekDay junk2; anchor.BreakDown(junk1, junk2, anchorMonth, anchorYear); char const* key; if (!(lexer->SkipWS() && lexer->GetId(key))) return 0; if (strcmp(key, "Backward") == 0) backward = 1; else if (strcmp(key, "Forward") == 0) backward = 0; else return 0; if (!(lexer->SkipWS() && lexer->GetId(key))) return 0; if (strcmp(key, "ByDay") == 0) mtype = ByDay; else if (strcmp(key, "ByWorkDay") == 0) mtype = ByWorkDay; else if (strcmp(key, "ByWeek") == 0) { int wday; if (!(lexer->SkipWS() && lexer->GetNumber(wday))) return 0; if ((wday < 1) || (wday > 7)) return 0; mtype = ByWeek; weekday = WeekDay::First() + (wday - 1); } else return 0; return 1; } // effects Return occurrence for this set in specified "m/y". // Returns number < 1 if no such occurrence. int MonthBasedDateSetRep::find_in_month(Month m, int y) const { int msize = m.Size(y); if (mtype == ByDay) { if ((count < 1) || (count > msize)) return 0; // Simple case? if (!backward) return count; // Backward search return (msize - count + 1); } WeekDay firstweekday = Date(1, m, y).GetWDay(); if (mtype == ByWorkDay) { if (!backward) { WeekDay w = firstweekday; int x = 1; int left = count; while (x <= msize) { if ((w != WeekDay::Saturday()) && (w != WeekDay::Sunday())) left--; if (left <= 0) return x; x++; w += 1; } return 0; } // Backward search int x = msize; WeekDay w = firstweekday + (msize - 1); int left = count; while (x > 0) { if ((w != WeekDay::Saturday()) && (w != WeekDay::Sunday())) left--; if (left <= 0) return x; x--; w -= 1; } return 0; } // By week... if (!backward) { int x = 1; WeekDay w = firstweekday; int left = count; // Advance to first occurrence of "weekday" while (w != weekday) { x++; w += 1; } // Now search while (x <= msize) { left--; if (left <= 0) return x; x += 7; } return 0; } int x = msize; WeekDay w = firstweekday + (msize - 1); int left = count; // Find last occurrence of "weekday" while (w != weekday) { x--; w -= 1; } // Now search while (x > 0) { left--; if (left <= 0) return x; x -= 7; } return 0; } MonthDateSetRep::MonthDateSetRep(int i, Date a) : MonthBasedDateSetRep(a.GetMDay(), i, a, ByDay, WeekDay::First(), 0) { } DateSetRep* MonthDateSetRep::copy() const { return new MonthDateSetRep(interval, anchor); } int MonthDateSetRep::read(Lexer* lexer) { if (lexer->SkipWS() && read_date(lexer, anchor)) { WeekDay junk; anchor.BreakDown(count, junk, anchorMonth, anchorYear); mtype = ByDay; backward = 0; return (lexer->SkipWS() && lexer->GetNumber(interval) && (interval > 0)); } return 0; } void MonthDateSetRep::write(charArray* output) const { assert(mtype == ByDay); assert(!backward); assert(anchor.GetMDay() == count); append_string(output, "Months "); write_date(output, anchor); format(output, " %d", interval); } /* * WeekSetDateSetRep. */ WeekSetDateSetRep::WeekSetDateSetRep(SmallIntSet d, SmallIntSet m) { days = d; months = m; } WeekSetDateSetRep::~WeekSetDateSetRep() { } DateSetRep* WeekSetDateSetRep::copy() const { return new WeekSetDateSetRep(days, months); } DateSet::RepeatType WeekSetDateSetRep::type() const { return DateSet::Other; } void WeekSetDateSetRep::describe(charArray* buffer) const { append_string(buffer, "Every"); char const* sep = " "; for (int i = 1; i <= 7; i++) { if (days.Member(i)) { append_string(buffer, sep); append_string(buffer, (WeekDay::First() + (i-1)).Name()); sep = ", "; } } } int WeekSetDateSetRep::contains(Date date) const { int d, y; Month m; WeekDay w; date.BreakDown(d, w, m, y); return (days.Member(w.Index()) && months.Member(m.Index())); } int WeekSetDateSetRep::search(Date date, Date& result) const { int d, y; Month m; WeekDay wd; date.BreakDown(d, wd, m, y); int last = Year::Last(); while (y <= last) { /* Search in month m */ int mSize = m.Size(y); if (months.Member(m.Index())) { /* Search for day match */ while (d <= mSize) { if (days.Member(wd.Index())) { Date xdate = Date(d, m, y); result = xdate; return 1; } d++; wd += 1; } } /* Advance to next month */ if (m == Month::Last()) { m = Month::First(); y++; } else { m += 1; } wd += mSize - d + 1; d = 1; } /* No occurrence */ return 0; } int WeekSetDateSetRep::read(Lexer* lexer) { char const* keyword; return (lexer->SkipWS() && days.Read(lexer) && lexer->SkipWS() && lexer->GetId(keyword) && (strcmp(keyword, "Months") == 0) && lexer->SkipWS() && months.Read(lexer)); } void WeekSetDateSetRep::write(charArray* output) const { append_string(output, "WeekDays "); days.Write(output); append_string(output, " Months "); months.Write(output); } DateSetRep* WeekSetDateSetRep::normalize(Date& start, Date& finish, DateList& deleted) const { Date firstDate; if (! DateSetRep::search(Date::First(),firstDate,start,finish,deleted)) { return new EmptyDateSetRep; } int years = finish.GetYear() - start.GetYear() + 1; if ((months.Size() == 0) || (days.Size() == 0)) { /* Empty */ return new EmptyDateSetRep; } if (days.Size() == 1) { if (months.Size() == 12) { /* Weekly */ return new DayBasedDateSetRep(7, firstDate); } if ((years == 1) && (count_ranges(months) == 1)) { /* Weekly in range */ set_month_range(start, finish, months); return new DayBasedDateSetRep(7, firstDate); } } if (days.Size() == 7) { if (months.Size() == 12) { /* Daily */ return new DayBasedDateSetRep(1, firstDate); } if ((years == 1) && (count_ranges(months) == 1)) { /* Daily in range */ set_month_range(start, finish, months); return new DayBasedDateSetRep(1, firstDate); } } return 0; } /* * MonthSetDateSetRep. */ MonthSetDateSetRep::MonthSetDateSetRep(SmallIntSet d, SmallIntSet m) { days = d; months = m; } MonthSetDateSetRep::~MonthSetDateSetRep() { } DateSetRep* MonthSetDateSetRep::copy() const { return new MonthSetDateSetRep(days, months); } DateSet::RepeatType MonthSetDateSetRep::type() const { return DateSet::Other; } void MonthSetDateSetRep::describe(charArray* buffer) const { append_string(buffer, "Complex Monthly Repetition"); } int MonthSetDateSetRep::contains(Date date) const { int d, y; Month m; WeekDay w; date.BreakDown(d, w, m, y); return (days.Member(d) && months.Member(m.Index())); } int MonthSetDateSetRep::search(Date date, Date& result) const { int d, y; Month m; WeekDay wd; date.BreakDown(d, wd, m, y); int last = Year::Last(); while (y <= last) { /* Search in month m */ int mSize = m.Size(y); if (months.Member(m.Index())) { /* Search for day match */ while (d <= mSize) { if (days.Member(d)) { result = Date(d, m, y); return 1; } d++; } } /* Advance to next month */ if (m == Month::Last()) { m = Month::First(); y++; } else { m += 1; } d = 1; } /* No occurrence */ return 0; } int MonthSetDateSetRep::read(Lexer* lexer) { char const* keyword; return (lexer->SkipWS() && days.Read(lexer) && lexer->SkipWS() && lexer->GetId(keyword) && (strcmp(keyword, "Months") == 0) && lexer->SkipWS() && months.Read(lexer)); } void MonthSetDateSetRep::write(charArray* output) const { append_string(output, "MonthDays "); days.Write(output); append_string(output, " Months "); months.Write(output); } DateSetRep* MonthSetDateSetRep::normalize(Date& start, Date& finish, DateList& deleted) const { Date firstDate; if (! DateSetRep::search(Date::First(),firstDate,start,finish,deleted)) { return new EmptyDateSetRep; } int years = finish.GetYear() - start.GetYear() + 1; if ((months.Size() == 0) || (days.Size() == 0)) { /* Empty */ return new EmptyDateSetRep; } if (days.Size() == 1) { if (months.Size() == 1) { /* Annual */ return new MonthDateSetRep(12, firstDate); } if (months.Size() == 12) { /* Monthly */ return new MonthDateSetRep(1, firstDate); } if ((years == 1) && (count_ranges(months) == 1)) { /* Monthly in range */ set_month_range(start, finish, months); return new MonthDateSetRep(1, firstDate); } } else if (days.Size() == 31) { if (months.Size() == 12) { /* Daily */ return new DayBasedDateSetRep(1, firstDate); } if ((years == 1) && (count_ranges(months) == 1)) { /* Daily in range */ set_month_range(start, finish, months); return new DayBasedDateSetRep(1, firstDate); } } else if ((years==1) && (months.Size()==1) && (count_ranges(days)==1)) { /* Find first and last days in range */ int i; int a = 1; int b = 0; for (i = 1; i <= 31; i++) { if (days.Member(i)) { a = i; while ((i <= 31) && days.Member(i)) { i++; } b = i - 1; break; } } /* Find month */ Month m = Month::First(); /* Should not be necessary */ for (i = 1; i <= 12; i++) { if (months.Member(i)) { m = Month::First() + i - 1; break; } } /* Find new range */ int year = start.GetYear(); Date newStart = Date(a, m, year); Date newFinish = Date(b, m, year); /* Daily with range */ start = newStart; finish = newFinish; return new DayBasedDateSetRep(1, firstDate); } return 0; } implementArray(DateList,Date) static void advance_months(Month& month, int& year, int delta) { /* Advance by years */ year += (delta / 12); delta = delta % 12; /* Advance remaining months */ if ((Month::Last() - month) < delta) { /* Have to advance to next year */ delta -= (Month::Last() - month) + 1; year++; month = Month::First(); } month += delta; }