mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-08-29 18:26:05 +00:00
RRULE handling in SQL.
This commit is contained in:
parent
63b617f182
commit
342bf0a978
@ -9,16 +9,16 @@
|
||||
*
|
||||
* Coverage of this function set
|
||||
* - COUNT & UNTIL are handled, generally
|
||||
* - DAILY frequency, including BYDAY, BYMONTH, BYMONTHDAY
|
||||
* - WEEKLY frequency, including BYDAY, BYMONTH, BYMONTHDAY, BYSETPOS
|
||||
* - DAILY frequency, including BYDAY, BYMONTH, BYMONTHDAY, BYWEEKNO, BYMONTHDAY
|
||||
* - WEEKLY frequency, including BYDAY, BYMONTH, BYMONTHDAY, BYWEEKNO, BYSETPOS
|
||||
* - MONTHLY frequency, including BYDAY, BYMONTH, BYSETPOS
|
||||
* - YEARLY frequency, including ???
|
||||
* - YEARLY frequency, including BYMONTH, BYMONTHDAY, BYSETPOS, BYDAY
|
||||
*
|
||||
* Not covered as yet
|
||||
* - DAILY: BYYEARDAY, BYWEEKNO, BYMONTHDAY, BYSETPOS*
|
||||
* - WEEKLY: BYYEARDAY, BYWEEKNO
|
||||
* - DAILY: BYYEARDAY, BYSETPOS*
|
||||
* - WEEKLY: BYYEARDAY
|
||||
* - MONTHLY: BYYEARDAY, BYMONTHDAY, BYWEEKNO
|
||||
* - YEARLY: BYYEARDAY, BYDAY*, BYSETPOS
|
||||
* - YEARLY: BYYEARDAY
|
||||
* - SECONDLY
|
||||
* - MINUTELY
|
||||
* - HOURLY
|
||||
@ -241,23 +241,17 @@ $$ LANGUAGE 'sql' IMMUTABLE STRICT;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Test the weekday of this date against the array of weekdays from the byday rule
|
||||
-- Test the weekday of this date against the array of weekdays from the BYDAY rule (FREQ=WEEKLY or less)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION test_byday_rule( TIMESTAMP WITH TIME ZONE, TEXT[] ) RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
testme ALIAS FOR $1;
|
||||
byday ALIAS FOR $2;
|
||||
i INT;
|
||||
dow TEXT;
|
||||
BEGIN
|
||||
-- Note that this doesn't work for MONTHLY/YEARLY BYDAY clauses which might have numbers prepended
|
||||
-- so don't call it that way...
|
||||
IF byday IS NOT NULL THEN
|
||||
dow := substring( to_char( testme, 'DY') for 2 from 1);
|
||||
FOR i IN 1..7 LOOP
|
||||
IF byday[i] IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
EXIT WHEN dow = byday[i];
|
||||
END LOOP;
|
||||
RETURN ( substring( to_char( testme, 'DY') for 2 from 1) = ANY (byday) );
|
||||
END IF;
|
||||
RETURN TRUE;
|
||||
END;
|
||||
@ -271,17 +265,9 @@ CREATE or REPLACE FUNCTION test_bymonth_rule( TIMESTAMP WITH TIME ZONE, INT[] )
|
||||
DECLARE
|
||||
testme ALIAS FOR $1;
|
||||
bymonth ALIAS FOR $2;
|
||||
i INT;
|
||||
month INT;
|
||||
BEGIN
|
||||
IF bymonth IS NOT NULL THEN
|
||||
month := date_part( 'month', testme );
|
||||
FOR i IN 1..12 LOOP
|
||||
IF bymonth[i] IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
EXIT WHEN month = bymonth[i];
|
||||
END LOOP;
|
||||
RETURN ( date_part( 'month', testme) = ANY (bymonth) );
|
||||
END IF;
|
||||
RETURN TRUE;
|
||||
END;
|
||||
@ -289,29 +275,73 @@ $$ LANGUAGE 'plpgsql' IMMUTABLE;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Test the month of this date against the array of months from the rule
|
||||
-- Test the day in month of this date against the array of monthdays from the rule
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION test_bymonthday_rule( TIMESTAMP WITH TIME ZONE, INT[] ) RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
testme ALIAS FOR $1;
|
||||
bymonthday ALIAS FOR $2;
|
||||
i INT;
|
||||
dom INT;
|
||||
BEGIN
|
||||
IF bymonthday IS NOT NULL THEN
|
||||
dom := date_part( 'day', testme);
|
||||
FOR i IN 1..31 LOOP
|
||||
IF bymonthday[i] IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
EXIT WHEN dom = bymonthday[i];
|
||||
END LOOP;
|
||||
RETURN ( date_part( 'day', testme) = ANY (bymonthday) );
|
||||
END IF;
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Test the day in year of this date against the array of yeardays from the rule
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION test_byyearday_rule( TIMESTAMP WITH TIME ZONE, INT[] ) RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
testme ALIAS FOR $1;
|
||||
byyearday ALIAS FOR $2;
|
||||
BEGIN
|
||||
IF byyearday IS NOT NULL THEN
|
||||
RETURN ( date_part( 'doy', testme) = ANY (byyearday) );
|
||||
END IF;
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Given a cursor into a set, process the set returning the subset matching the BYSETPOS
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION rrule_bysetpos_filter( REFCURSOR, INT[] ) RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$
|
||||
DECLARE
|
||||
curse ALIAS FOR $1;
|
||||
bysetpos ALIAS FOR $2;
|
||||
valid_date TIMESTAMP WITH TIME ZONE;
|
||||
i INT;
|
||||
BEGIN
|
||||
|
||||
IF bysetpos IS NULL THEN
|
||||
LOOP
|
||||
FETCH curse INTO valid_date;
|
||||
EXIT WHEN NOT FOUND;
|
||||
RETURN NEXT valid_date;
|
||||
END LOOP;
|
||||
ELSE
|
||||
FOR i IN 1..366 LOOP
|
||||
EXIT WHEN bysetpos[i] IS NULL;
|
||||
IF bysetpos[i] > 0 THEN
|
||||
FETCH ABSOLUTE bysetpos[i] FROM curse INTO valid_date;
|
||||
ELSE
|
||||
MOVE LAST IN curse;
|
||||
FETCH RELATIVE (bysetpos[i] + 1) FROM curse INTO valid_date;
|
||||
END IF;
|
||||
IF valid_date IS NOT NULL THEN
|
||||
RETURN NEXT valid_date;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
CLOSE curse;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Return another day's worth of events
|
||||
------------------------------------------------------------------------------------------------------
|
||||
@ -320,8 +350,30 @@ DECLARE
|
||||
after ALIAS FOR $1;
|
||||
rrule ALIAS FOR $2;
|
||||
BEGIN
|
||||
-- Since we don't do BYHOUR, BYMINUTE or BYSECOND yet this becomes trivial
|
||||
|
||||
IF rrule.bymonth IS NOT NULL AND NOT date_part('month',after) = ANY ( rrule.bymonth ) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF rrule.byweekno IS NOT NULL AND NOT date_part('week',after) = ANY ( rrule.byweekno ) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF rrule.byyearday IS NOT NULL AND NOT date_part('doy',after) = ANY ( rrule.byyearday ) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF rrule.bymonthday IS NOT NULL AND NOT date_part('day',after) = ANY ( rrule.bymonthday ) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF rrule.byday IS NOT NULL AND NOT substring( to_char( after, 'DY') for 2 from 1) = ANY ( rrule.byday ) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Since we don't do BYHOUR, BYMINUTE or BYSECOND yet this becomes a trivial
|
||||
RETURN NEXT after;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
@ -335,37 +387,20 @@ DECLARE
|
||||
rrule ALIAS FOR $2;
|
||||
valid_date TIMESTAMP WITH TIME ZONE;
|
||||
curse REFCURSOR;
|
||||
setpos INT;
|
||||
weekno INT;
|
||||
i INT;
|
||||
BEGIN
|
||||
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_week_byday_set(after, rrule.byday ) r;
|
||||
|
||||
IF rrule.bysetpos IS NULL THEN
|
||||
LOOP
|
||||
FETCH curse INTO valid_date;
|
||||
EXIT WHEN NOT FOUND;
|
||||
RETURN NEXT valid_date;
|
||||
END LOOP;
|
||||
ELSE
|
||||
i := 1;
|
||||
setpos := rrule.bysetpos[i];
|
||||
WHILE setpos IS NOT NULL LOOP
|
||||
IF setpos > 0 THEN
|
||||
FETCH ABSOLUTE setpos FROM curse INTO valid_date;
|
||||
ELSE
|
||||
setpos := setpos + 1;
|
||||
MOVE LAST IN curse;
|
||||
FETCH RELATIVE setpos FROM curse INTO valid_date;
|
||||
END IF;
|
||||
IF next_base IS NOT NULL THEN
|
||||
RETURN NEXT valid_date;
|
||||
END IF;
|
||||
i := i + 1;
|
||||
setpos := rrule.bysetpos[i];
|
||||
END LOOP;
|
||||
IF rrule.byweekno IS NOT NULL THEN
|
||||
weekno := date_part('week',after);
|
||||
IF NOT weekno = ANY ( rrule.byweekno ) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_week_byday_set(after, rrule.byday ) r;
|
||||
RETURN QUERY SELECT d FROM rrule_bysetpos_filter(curse,rrule.bysetpos) d;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
@ -383,43 +418,40 @@ DECLARE
|
||||
i INT;
|
||||
BEGIN
|
||||
|
||||
-- Need to investigate whether it is legal to set both of these, and whether
|
||||
-- we are correct to UNION the results, or whether we should INTERSECT them.
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_month_byday_set(after, rrule.byday ) r
|
||||
UNION SELECT r FROM rrule_month_bymonthday_set(after, rrule.bymonthday ) r
|
||||
ORDER BY 1;
|
||||
|
||||
IF rrule.bysetpos IS NULL THEN
|
||||
LOOP
|
||||
FETCH curse INTO valid_date;
|
||||
EXIT WHEN NOT FOUND;
|
||||
RETURN NEXT valid_date;
|
||||
END LOOP;
|
||||
/**
|
||||
* Need to investigate whether it is legal to set both of these, and whether
|
||||
* we are correct to UNION the results, or whether we should INTERSECT them.
|
||||
* So at this point, we refer to the specification, which grants us this
|
||||
* wonderfully enlightening vision:
|
||||
*
|
||||
* If multiple BYxxx rule parts are specified, then after evaluating the
|
||||
* specified FREQ and INTERVAL rule parts, the BYxxx rule parts are
|
||||
* applied to the current set of evaluated occurrences in the following
|
||||
* order: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR,
|
||||
* BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are evaluated.
|
||||
*
|
||||
* My guess is that this means 'INTERSECT'
|
||||
*/
|
||||
IF rrule.byday IS NOT NULL AND rrule.bymonthday IS NOT NULL THEN
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_month_byday_set(after, rrule.byday ) r
|
||||
INTERSECT SELECT r FROM rrule_month_bymonthday_set(after, rrule.bymonthday ) r
|
||||
ORDER BY 1;
|
||||
ELSIF rrule.bymonthday IS NOT NULL THEN
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_month_bymonthday_set(after, rrule.bymonthday ) r ORDER BY 1;
|
||||
ELSE
|
||||
i := 1;
|
||||
setpos := rrule.bysetpos[i];
|
||||
WHILE setpos IS NOT NULL LOOP
|
||||
IF setpos > 0 THEN
|
||||
FETCH ABSOLUTE setpos FROM curse INTO valid_date;
|
||||
ELSE
|
||||
setpos := setpos + 1;
|
||||
MOVE LAST IN curse;
|
||||
FETCH RELATIVE setpos FROM curse INTO valid_date;
|
||||
END IF;
|
||||
IF valid_date IS NOT NULL THEN
|
||||
RETURN NEXT valid_date;
|
||||
END IF;
|
||||
i := i + 1;
|
||||
setpos := rrule.bysetpos[i];
|
||||
END LOOP;
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_month_byday_set(after, rrule.byday ) r ORDER BY 1;
|
||||
END IF;
|
||||
|
||||
RETURN QUERY SELECT d FROM rrule_bysetpos_filter(curse,rrule.bysetpos) d;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
|
||||
|
||||
CREATE or REPLACE FUNCTION yearly_set( TIMESTAMP WITH TIME ZONE, rrule_parts ) RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- If this is YEARLY;BYMONTH, abuse MONTHLY;BYMONTH for everything except the BYSETPOS
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION rrule_yearly_bymonth_set( TIMESTAMP WITH TIME ZONE, rrule_parts ) RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$
|
||||
DECLARE
|
||||
after ALIAS FOR $1;
|
||||
rrule ALIAS FOR $2;
|
||||
@ -429,7 +461,7 @@ DECLARE
|
||||
BEGIN
|
||||
|
||||
IF rrule.bymonth IS NOT NULL THEN
|
||||
-- As far as I can see there is extremely little difference between YEARLY;BYMONTH and MONTHLY;BYMONTH except the effect of BYSETPOS
|
||||
-- Ensure we don't pass BYSETPOS down
|
||||
rr := rrule;
|
||||
rr.bysetpos := NULL;
|
||||
FOR i IN 1..12 LOOP
|
||||
@ -445,44 +477,85 @@ END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
|
||||
CREATE or REPLACE FUNCTION event_instances( TIMESTAMP WITH TIME ZONE, TEXT )
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Return another year's worth of events
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION yearly_set( TIMESTAMP WITH TIME ZONE, rrule_parts ) RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$
|
||||
DECLARE
|
||||
after ALIAS FOR $1;
|
||||
rrule ALIAS FOR $2;
|
||||
current_base TIMESTAMP WITH TIME ZONE;
|
||||
curse REFCURSOR;
|
||||
curser REFCURSOR;
|
||||
i INT;
|
||||
BEGIN
|
||||
|
||||
IF rrule.bymonth IS NOT NULL THEN
|
||||
OPEN curse SCROLL FOR SELECT r FROM rrule_yearly_bymonth_set(after, rrule ) r;
|
||||
FOR current_base IN SELECT d FROM rrule_bysetpos_filter(curse,rrule.bysetpos) d LOOP
|
||||
current_base := date_trunc( 'day', current_base ) + (after::time)::interval;
|
||||
RETURN NEXT current_base;
|
||||
END LOOP;
|
||||
ELSE
|
||||
-- We don't yet implement byweekno, byblah
|
||||
RETURN NEXT after;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Combine all of that into something which we can use to generate a series from an arbitrary DTSTART/RRULE
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION rrule_event_instances_range( TIMESTAMP WITH TIME ZONE, TEXT, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH TIME ZONE, INT )
|
||||
RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$
|
||||
DECLARE
|
||||
basedate ALIAS FOR $1;
|
||||
repeatrule ALIAS FOR $2;
|
||||
loopcount INT;
|
||||
mindate ALIAS FOR $3;
|
||||
maxdate ALIAS FOR $4;
|
||||
max_count ALIAS FOR $5;
|
||||
loopmax INT;
|
||||
loopcount INT;
|
||||
base_day TIMESTAMP WITH TIME ZONE;
|
||||
current_base TIMESTAMP WITH TIME ZONE;
|
||||
current TIMESTAMP WITH TIME ZONE;
|
||||
rrule rrule_parts%ROWTYPE;
|
||||
BEGIN
|
||||
loopcount := 0;
|
||||
loopmax := 500;
|
||||
|
||||
SELECT * INTO rrule FROM parse_rrule_parts( basedate, repeatrule );
|
||||
IF rrule.count IS NOT NULL THEN
|
||||
loopmax := rrule.count;
|
||||
ELSE
|
||||
loopmax := max_count;
|
||||
END IF;
|
||||
|
||||
current_base := basedate;
|
||||
base_day := date_trunc('day',basedate);
|
||||
WHILE loopcount < loopmax LOOP
|
||||
WHILE loopcount < loopmax AND current_base <= maxdate LOOP
|
||||
IF rrule.freq = 'DAILY' THEN
|
||||
FOR current IN SELECT d FROM daily_set(current_base,rrule) d WHERE d >= base_day LOOP
|
||||
IF test_byday_rule(current,rrule.byday) AND test_bymonthday_rule(current,rrule.bymonthday) AND test_bymonth_rule(current,rrule.bymonth) THEN
|
||||
-- IF test_byday_rule(current,rrule.byday) AND test_bymonthday_rule(current,rrule.bymonthday) AND test_bymonth_rule(current,rrule.bymonth) THEN
|
||||
EXIT WHEN rrule.until IS NOT NULL AND current > rrule.until;
|
||||
RETURN NEXT current;
|
||||
IF current >= mindate THEN
|
||||
RETURN NEXT current;
|
||||
END IF;
|
||||
loopcount := loopcount + 1;
|
||||
EXIT WHEN loopcount >= loopmax;
|
||||
END IF;
|
||||
-- END IF;
|
||||
END LOOP;
|
||||
current_base := current_base + (rrule.interval::text || ' days')::interval;
|
||||
ELSIF rrule.freq = 'WEEKLY' THEN
|
||||
FOR current IN SELECT w FROM weekly_set(current_base,rrule) w WHERE w >= base_day LOOP
|
||||
IF test_bymonthday_rule(current,rrule.bymonthday) AND test_bymonth_rule(current,rrule.bymonth) THEN
|
||||
IF test_byyearday_rule(current,rrule.byyearday)
|
||||
AND test_bymonthday_rule(current,rrule.bymonthday)
|
||||
AND test_bymonth_rule(current,rrule.bymonth)
|
||||
THEN
|
||||
EXIT WHEN rrule.until IS NOT NULL AND current > rrule.until;
|
||||
RETURN NEXT current;
|
||||
IF current >= mindate THEN
|
||||
RETURN NEXT current;
|
||||
END IF;
|
||||
loopcount := loopcount + 1;
|
||||
EXIT WHEN loopcount >= loopmax;
|
||||
END IF;
|
||||
@ -490,18 +563,24 @@ BEGIN
|
||||
current_base := current_base + (rrule.interval::text || ' weeks')::interval;
|
||||
ELSIF rrule.freq = 'MONTHLY' THEN
|
||||
FOR current IN SELECT m FROM monthly_set(current_base,rrule) m WHERE m >= base_day LOOP
|
||||
IF test_bymonth_rule(current,rrule.bymonth) THEN
|
||||
-- IF /* test_byyearday_rule(current,rrule.byyearday)
|
||||
-- AND */ test_bymonth_rule(current,rrule.bymonth)
|
||||
-- THEN
|
||||
EXIT WHEN rrule.until IS NOT NULL AND current > rrule.until;
|
||||
RETURN NEXT current;
|
||||
IF current >= mindate THEN
|
||||
RETURN NEXT current;
|
||||
END IF;
|
||||
loopcount := loopcount + 1;
|
||||
EXIT WHEN loopcount >= loopmax;
|
||||
END IF;
|
||||
-- END IF;
|
||||
END LOOP;
|
||||
current_base := current_base + (rrule.interval::text || ' months')::interval;
|
||||
ELSIF rrule.freq = 'YEARLY' THEN
|
||||
FOR current IN SELECT y FROM yearly_set(current_base,rrule) y WHERE y >= base_day LOOP
|
||||
EXIT WHEN rrule.until IS NOT NULL AND current > rrule.until;
|
||||
RETURN NEXT current;
|
||||
IF current >= mindate THEN
|
||||
RETURN NEXT current;
|
||||
END IF;
|
||||
loopcount := loopcount + 1;
|
||||
EXIT WHEN loopcount >= loopmax;
|
||||
END LOOP;
|
||||
@ -517,3 +596,17 @@ END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- A simplified DTSTART/RRULE only interface which applies some performance assumptions
|
||||
------------------------------------------------------------------------------------------------------
|
||||
CREATE or REPLACE FUNCTION event_instances( TIMESTAMP WITH TIME ZONE, TEXT )
|
||||
RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$
|
||||
DECLARE
|
||||
basedate ALIAS FOR $1;
|
||||
repeatrule ALIAS FOR $2;
|
||||
maxdate TIMESTAMP WITH TIME ZONE;
|
||||
BEGIN
|
||||
maxdate := current_date + '10 years'::interval;
|
||||
RETURN QUERY SELECT d FROM rrule_event_instances_range( basedate, repeatrule, basedate, maxdate, 300 ) d;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user