Permalink
Browse files

refactor

  • Loading branch information...
1 parent 58d65da commit a0ff4cd5949395e44ce9d6c83ebbaebb5d5fc620 @bakineggs bakineggs committed May 13, 2008
Showing with 101 additions and 211 deletions.
  1. +33 −39 generate_recurrences.sql
  2. +0 −4 interval_for.sql
  3. +16 −107 recurring_events_for.sql
  4. +52 −53 spec/generate_recurrences_spec.rb
  5. +0 −8 spec/interval_for_spec.rb
View
72 generate_recurrences.sql
@@ -1,54 +1,48 @@
CREATE OR REPLACE FUNCTION generate_recurrences(
- pattern_type TEXT,
duration INTERVAL,
- original_date DATE,
range_start DATE,
- range_end DATE
+ range_end DATE,
+ repeat_month INT,
+ repeat_week INT,
+ repeat_day INT
)
RETURNS setof DATE
LANGUAGE plpgsql IMMUTABLE
AS $BODY$
DECLARE
- next_date DATE;
+ next_date DATE := range_start;
+ current_month INT;
+ current_week INT;
BEGIN
- next_date := original_date + duration * CEIL(intervals_between(original_date, range_start, duration));
- IF pattern_type = 'positive_week_dow' OR pattern_type = 'negative_week_dow' THEN
- -- in case the first recurrence is before next_date but after range_start
- next_date := next_date - 2 * duration;
- LOOP
- -- prevent an infinite loop
- IF duration = '28 days'::interval AND extract(month from next_date + duration) = extract(month from next_date) THEN
- next_date := next_date + duration;
- END IF;
- next_date := next_date + duration;
-
- -- ensure month is correct for yearly events
- IF duration = '364 days'::interval THEN
- WHILE extract(month from next_date) != extract(month from original_date) LOOP
- next_date := next_date + cast(extract(month from original_date) - extract(month from next_date) as int) % 12 * '28 days'::interval;
- END LOOP;
+ IF repeat_month IS NOT NULL THEN
+ next_date := next_date + (((12 + repeat_month - cast(extract(month from next_date) as int)) % 12) || ' months')::interval;
+ END IF;
+ IF repeat_week IS NULL AND repeat_day IS NOT NULL THEN
+ IF duration = '7 days'::interval THEN
+ next_date := next_date + (((7 + repeat_day - cast(extract(dow from next_date) as int)) % 7) || ' days')::interval;
+ ELSE
+ next_date := next_date + (repeat_day - extract(day from next_date) || ' days')::interval;
+ END IF;
+ END IF;
+ LOOP
+ IF repeat_week IS NOT NULL AND repeat_day IS NOT NULL THEN
+ current_month := extract(month from next_date);
+ next_date := next_date + (((7 + repeat_day - cast(extract(dow from next_date) as int)) % 7) || ' days')::interval;
+ IF extract(month from next_date) != current_month THEN
+ next_date := next_date - '7 days'::interval;
END IF;
-
- IF pattern_type = 'positive_week_dow' THEN
- next_date := next_date
- + (CEIL(extract(day from original_date) / 7)
- - CEIL(extract(day from next_date) / 7))
- * '7 days'::interval;
+ IF repeat_week > 0 THEN
+ current_week := CEIL(extract(day from next_date) / 7);
ELSE
- next_date := next_date
- + (CEIL((1 + extract(day from next_date + '1 month'::interval - next_date) - extract(day from next_date)) / 7)
- - CEIL((1 + extract(day from original_date + '1 month'::interval - original_date) - extract(day from original_date)) / 7))
- * '7 days'::interval;
+ current_week := -CEIL((1 + extract(day from next_date + '1 month'::interval - next_date) - extract(day from next_date)) / 7);
END IF;
- EXIT WHEN next_date > range_end;
- CONTINUE WHEN next_date < range_start OR next_date < original_date; -- subtracting an extra duration could have put us before the range_start or original_date
+ next_date := next_date + (repeat_week - current_week) * '7 days'::interval;
+ END IF;
+ EXIT WHEN next_date > range_end;
+ IF next_date >= range_start THEN
RETURN NEXT next_date;
- END LOOP;
- ELSE
- WHILE next_date <= range_end LOOP
- RETURN NEXT next_date;
- next_date := next_date + duration;
- END LOOP;
- END IF;
+ END IF;
+ next_date := next_date + duration;
+ END LOOP;
END;
$BODY$;
View
4 interval_for.sql
@@ -13,10 +13,6 @@ BEGIN
RETURN '1 month'::interval;
ELSIF recurs = 'yearly' THEN
RETURN '1 year'::interval;
- ELSIF recurs = 'monthly_by_week_dow' THEN
- RETURN '28 days'::interval;
- ELSIF recurs = 'yearly_by_week_dow' THEN
- RETURN '364 days'::interval;
ELSE
RAISE EXCEPTION 'Recurrence % not supported by generate_recurrences()', recurs;
END IF;
View
123 recurring_events_for.sql
@@ -15,10 +15,8 @@ DECLARE
start_time TIME;
end_date TIMESTAMP;
next_date DATE;
- recurs TEXT;
- pattern_type TEXT;
+ recurrences_start TIMESTAMP;
recurrences_end TIMESTAMP;
- offset INTERVAL;
duration INTERVAL;
BEGIN
FOR row IN
@@ -58,120 +56,31 @@ BEGIN
END IF;
END IF;
- recurs := event.frequency;
- pattern_type := 'normal';
- offset := '00:00:00'::interval;
+ duration := interval_for(event.frequency);
- IF recurs = 'weekly' AND row.day IS NOT NULL THEN
- offset := offset + (((row.day-cast(extract(dow from start_date) as int))%7)||' days')::interval;
-
- ELSIF recurs = 'monthly' AND row.week IS NOT NULL AND row.day IS NOT NULL THEN
- offset := offset + (((row.day-cast(extract(dow from start_date) as int))%7)||' days')::interval;
-
- -- adjusting the day of week could have put us in a different month
- IF extract(month from start_date) != extract(month from start_date + offset) THEN
- IF cast(extract(month from start_date) - extract(month from start_date + offset) as int) % 12 = 1 THEN
- offset := offset + '7 days'::interval;
- ELSE
- offset := offset - '7 days'::interval;
- END IF;
- END IF;
-
- recurs := 'monthly_by_week_dow';
- offset := offset - '28 days'::interval;
- IF row.week > 0 THEN
- pattern_type := 'positive_week_dow';
- WHILE offset < 0 LOOP
- offset := offset + '28 days'::interval;
- offset := offset + (row.week - CEIL(extract(day from start_date + offset)/7)) * '7 days'::interval;
- END LOOP;
- ELSE
- pattern_type := 'negative_week_dow';
- WHILE offset < 0 LOOP
- offset := offset + '28 days'::interval;
- offset := offset
- + (CEIL((1 + extract(day from start_date + offset + '1 month'::interval - (start_date + offset)) - extract(day from start_date + offset)) / 7)
- + row.week)
- * '7 days'::interval;
- END LOOP;
- END IF;
-
- ELSIF recurs = 'monthly' AND row.day IS NOT NULL THEN
- offset := offset + ((row.day-cast(extract(day from start_date) as int))||' days')::interval;
-
- ELSIF recurs = 'yearly' AND row.week IS NOT NULL AND row.day IS NOT NULL THEN
- IF row.month IS NULL THEN
- row.month = extract(month from start_date);
- END IF;
- offset := offset + ((row.month-cast(extract(month from start_date) as int))||' months')::interval;
- offset := offset + (((row.day-cast(extract(dow from start_date+offset) as int))%7)||' days')::interval;
-
- -- adjusting the day of week could have put us in a different month
- IF extract(month from start_date) != extract(month from start_date + offset) THEN
- IF cast(extract(month from start_date) - extract(month from start_date + offset) as int) % 12 = 1 THEN
- offset := offset + '7 days'::interval;
- ELSE
- offset := offset - '7 days'::interval;
- END IF;
- END IF;
-
- recurs := 'yearly_by_week_dow';
- offset := offset - '364 days'::interval;
- IF row.week > 0 THEN
- pattern_type := 'positive_week_dow';
- WHILE start_date+offset < start_date LOOP
- offset := offset + '364 days'::interval;
- IF extract(month from start_date+offset) != row.month THEN
- offset := offset + '7 days'::interval;
- END IF;
- offset := offset + (row.week - CEIL(extract(day from start_date + offset)/7)) * '7 days'::interval;
- END LOOP;
- ELSE
- pattern_type := 'negative_week_dow';
- WHILE start_date+offset < start_date LOOP
- offset := offset + '364 days'::interval;
- IF extract(month from start_date+offset) != row.month THEN
- offset := offset + '7 days'::interval;
- END IF;
- offset := offset
- + (CEIL((1 + extract(day from start_date + offset + '1 month'::interval - (start_date + offset)) - extract(day from start_date + offset)) / 7)
- + row.week)
- * '7 days'::interval;
- END LOOP;
- END IF;
-
- ELSIF recurs = 'yearly' AND row.month IS NOT NULL OR row.day IS NOT NULL THEN
- IF row.month IS NOT NULL THEN
- offset := offset + ((row.month-cast(extract(month from start_date) as int))||' months')::interval;
- END IF;
- IF row.day IS NOT NULL THEN
- offset := offset + ((row.day-cast(extract(day from start_date) as int))||' days')::interval;
- END IF;
+ IF event.until IS NOT NULL AND event.until < range_end THEN
+ recurrences_end := event.until;
+ ELSIF event.count IS NOT NULL AND start_date + (event.count - 1) * duration < range_end THEN
+ recurrences_end := start_date + (event.count - 1) * duration;
+ ELSE
+ recurrences_end := range_end;
END IF;
- duration := interval_for(recurs);
- WHILE start_date + offset < start_date LOOP
- offset := offset + duration;
- END LOOP;
- start_date := start_date + offset;
- end_date := end_date + offset;
-
- IF event.until IS NOT NULL AND event.until < range_end THEN
- recurrences_end = event.until;
- ELSIF event.count IS NOT NULL AND start_date+(event.count-1)*duration < range_end THEN
- recurrences_end = start_date+(event.count-1)*duration;
+ IF start_date > range_start THEN
+ recurrences_start := start_date;
ELSE
- recurrences_end = range_end;
+ recurrences_start := start_date + FLOOR(intervals_between(start_date::date, range_start::date, duration)) * duration;
END IF;
FOR next_date IN
SELECT *
FROM generate_recurrences(
- pattern_type,
duration,
- start_date::date,
- (range_start - (end_date - start_date))::date,
- recurrences_end::date
+ recurrences_start::date,
+ recurrences_end::date,
+ row.month,
+ row.week,
+ row.day
)
LOOP
CONTINUE WHEN next_date = original_date;
View
105 spec/generate_recurrences_spec.rb
@@ -2,7 +2,7 @@
describe 'generate_recurrences' do
it "should return dates inside of the range" do
- executing("select * from generate_recurrences('normal', '1 day', '2008-04-20', '2008-04-25', '2008-04-29');").should == [
+ executing("select * from generate_recurrences('1 day', '2008-04-25', '2008-04-29', NULL, NULL, NULL);").should == [
['2008-04-25'],
['2008-04-26'],
['2008-04-27'],
@@ -12,7 +12,7 @@
end
it "should only include dates on the frequency specified" do
- executing("select * from generate_recurrences('normal', '7 days', '2008-04-01', '2008-04-01', '2008-04-29');").should == [
+ executing("select * from generate_recurrences('7 days', '2008-04-01', '2008-04-29', NULL, NULL, NULL);").should == [
['2008-04-01'],
['2008-04-08'],
['2008-04-15'],
@@ -21,79 +21,78 @@
]
end
- it "should not include dates before the original_date" do
- executing("select * from generate_recurrences('normal', '1 day', '2008-04-25', '2008-04-20', '2008-04-29');").should == [
- ['2008-04-25'],
- ['2008-04-26'],
- ['2008-04-27'],
- ['2008-04-28'],
- ['2008-04-29']
- ]
+ describe 'by day of week' do
+ it "should return dates on the requested day of week" do
+ executing("select * from generate_recurrences('7 days', '2008-05-12', '2008-05-23', NULL, NULL, 4);").should == [
+ ['2008-05-15'],
+ ['2008-05-22']
+ ]
+ end
end
- it "should return dates whole intervals away from original_date even if start_date isn't" do
- executing("select * from generate_recurrences('normal', '7 days', '2008-04-01', '2008-04-04', '2008-04-29');").should == [
- ['2008-04-08'],
- ['2008-04-15'],
- ['2008-04-22'],
- ['2008-04-29']
- ]
+ describe 'by month' do
+ it "should return the day in the requested month" do
+ executing("select * from generate_recurrences('1 year', '2008-04-15', '2010-06-15', 5, NULL, NULL);").should == [
+ ['2008-05-15'],
+ ['2009-05-15'],
+ ['2010-05-15']
+ ]
+ end
+ end
+
+ describe 'by day of month' do
+ it "should return the dates on the requested day of month" do
+ executing("select * from generate_recurrences('1 month', '2008-04-01', '2008-06-30', NULL, NULL, 15);").should == [
+ ['2008-04-15'],
+ ['2008-05-15'],
+ ['2008-06-15']
+ ]
+ end
+
+ describe 'by month' do
+ it "should return the correct day of month in the correct month" do
+ executing("select * from generate_recurrences('1 year', '2008-04-01', '2010-06-30', 5, NULL, 15);").should == [
+ ['2008-05-15'],
+ ['2009-05-15'],
+ ['2010-05-15']
+ ]
+ end
+ end
end
describe 'by week and day of week' do
it "should include the dates on the particular positive offset week's day of week" do
- executing("select * from generate_recurrences('positive_week_dow', '28 days', '2008-04-15', '2008-04-15', '2008-06-17');").should == [
+ executing("select * from generate_recurrences('1 month', '2008-04-01', '2008-06-30', NULL, 3, 2);").should == [
['2008-04-15'],
['2008-05-20'],
['2008-06-17']
]
end
it "should include the dates on the particular negative offset week's day of week" do
- executing("select * from generate_recurrences('negative_week_dow', '28 days', '2008-04-17', '2008-04-17', '2008-06-19');").should == [
+ executing("select * from generate_recurrences('1 month', '2008-04-01', '2008-06-30', NULL, -2, 4);").should == [
['2008-04-17'],
['2008-05-22'],
['2008-06-19']
]
end
- it "should not get stuck in an infinite loop when the duration does not put us in the next month" do
- executing("select * from generate_recurrences('positive_week_dow', '28 days', '2008-04-01', '2008-04-01', '2008-06-03');").should == [
- ['2008-04-01'],
- ['2008-05-06'],
- ['2008-06-03']
- ]
- end
-
- it "should not put the next recurrence in the wrong month if the duration does not put us in the right month next year" do
- executing("select * from generate_recurrences('positive_week_dow', '364 days', '2008-04-01', '2008-04-01', '2010-04-06');").should == [
- ['2008-04-01'],
- ['2009-04-07'],
- ['2010-04-06']
- ]
- end
-
- it "should not skip the first recurrence if the ceiled intervals to range_start puts us in the next month" do
- executing("select * from generate_recurrences('positive_week_dow', '28 days', '2008-05-08', '2008-06-10', '2008-08-14');").should == [
- ['2008-06-12'],
- ['2008-07-10'],
- ['2008-08-14']
+ it "should not skip a month if offsetting to the correct day of week puts us in the wrong month" do
+ executing("select * from generate_recurrences('1 month', '2008-05-01', '2008-07-31', NULL, -1, 5);").should == [
+ ['2008-05-30'],
+ ['2008-06-27'],
+ ['2008-07-25']
]
end
- it "should not include recurrences before the original_date" do
- executing("select * from generate_recurrences('positive_week_dow', '28 days', '2008-05-01', '2008-04-01', '2008-06-05');").should == [
- ['2008-05-01'],
- ['2008-06-05']
- ]
- end
-
- it "should return the correct week when the day of week is the same as that of the last day of the month" do
- executing("select * from generate_recurrences('negative_week_dow', '28 days', '2008-04-26', '2008-04-26', '2008-06-28');").should == [
- ['2008-04-26'],
- ['2008-05-31'],
- ['2008-06-28']
- ]
+ describe 'by month' do
+ it "should return the dates in the correct month" do
+ executing("select * from generate_recurrences('1 year', '2008-04-01', '2010-06-30', 5, 3, 2);").should == [
+ ['2008-05-20'],
+ ['2009-05-19'],
+ ['2010-05-18']
+ ]
+ end
end
end
end
View
8 spec/interval_for_spec.rb
@@ -13,15 +13,7 @@
executing("select interval_for('monthly');").should == [ ['1 mon'] ]
end
- it "should return '28 days' for 'monthly_by_week_dow'" do
- executing("select interval_for('monthly_by_week_dow');").should == [ ['28 days'] ]
- end
-
it "should return '1 year' for 'yearly'" do
executing("select interval_for('yearly');").should == [ ['1 year'] ]
end
-
- it "should return '364 days' for 'yearly_by_week_dow'" do
- executing("select interval_for('yearly_by_week_dow');").should == [ ['364 days'] ]
- end
end

0 comments on commit a0ff4cd

Please sign in to comment.