forked from bakineggs/recurring_events_for
-
Notifications
You must be signed in to change notification settings - Fork 3
/
generate_recurrences.sql
58 lines (56 loc) · 2.11 KB
/
generate_recurrences.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
CREATE OR REPLACE FUNCTION generate_recurrences(
duration INTERVAL,
original_start_date DATE,
original_end_date DATE,
range_start DATE,
range_end DATE,
repeat_month INT,
repeat_week INT,
repeat_day INT
)
RETURNS setof DATE
LANGUAGE plpgsql IMMUTABLE
AS $BODY$
DECLARE
start_date DATE := original_start_date;
next_date DATE;
intervals INT := FLOOR(intervals_between(original_start_date, range_start, duration));
current_month INT;
current_week INT;
BEGIN
IF repeat_month IS NOT NULL THEN
start_date := start_date + (((12 + repeat_month - cast(extract(month from start_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
start_date := start_date + (((7 + repeat_day - cast(extract(dow from start_date) as int)) % 7) || ' days')::interval;
ELSE
start_date := start_date + (repeat_day - extract(day from start_date) || ' days')::interval;
END IF;
END IF;
LOOP
next_date := start_date + duration * intervals;
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 repeat_week > 0 THEN
current_week := CEIL(extract(day from next_date) / 7);
ELSE
current_week := -CEIL((1 + days_in_month(next_date) - extract(day from next_date)) / 7);
END IF;
next_date := next_date + (repeat_week - current_week) * '7 days'::interval;
END IF;
EXIT WHEN next_date > range_end;
IF next_date >= range_start AND next_date >= original_start_date THEN
RETURN NEXT next_date;
END IF;
if original_end_date IS NOT NULL AND range_start >= original_start_date + (duration*intervals) AND range_start <= original_end_date + (duration*intervals) THEN
RETURN NEXT next_date;
END IF;
intervals := intervals + 1;
END LOOP;
END;
$BODY$;