diff --git a/dba/rrule_functions.sql b/dba/rrule_functions.sql index 7c1dcf63..464a266d 100644 --- a/dba/rrule_functions.sql +++ b/dba/rrule_functions.sql @@ -33,9 +33,9 @@ CREATE TYPE rrule_parts AS ( freq TEXT, count INT, interval INT, --- bysecond INT[], --- byminute INT[], --- byhour INT[], + bysecond INT[], + byminute INT[], + byhour INT[], bymonthday INT[], byyearday INT[], byweekno INT[], @@ -64,17 +64,17 @@ BEGIN /** * We can do the array conversion as a simple cast, since the strings are simple numbers, with no commas */ - result.byday := ('{' || substring(repeatrule from 'BYDAY=(([+-]?[0-9]{0,2}(MO|TU|WE|TH|FR|SA|SU),?)+)(;|$)') || '}')::text[]; + result.byday := string_to_array( substring(repeatrule from 'BYDAY=(([+-]?[0-9]{0,2}(MO|TU|WE|TH|FR|SA|SU),?)+)(;|$)'), ','); - result.byyearday := ('{' || substring(repeatrule from 'BYYEARDAY=([0-9,+-]+)(;|$)') || '}')::int[]; - result.byweekno := ('{' || substring(repeatrule from 'BYWEEKNO=([0-9,+-]+)(;|$)') || '}')::int[]; - result.bymonthday := ('{' || substring(repeatrule from 'BYMONTHDAY=([0-9,+-]+)(;|$)') || '}')::int[]; - result.bymonth := ('{' || substring(repeatrule from 'BYMONTH=(([+-]?[0-1]?[0-9],?)+)(;|$)') || '}')::int[]; - result.bysetpos := ('{' || substring(repeatrule from 'BYSETPOS=(([+-]?[0-9]{1,3},?)+)(;|$)') || '}')::int[]; + result.byyearday := string_to_array(substring(repeatrule from 'BYYEARDAY=([0-9,+-]+)(;|$)'), ','); + result.byweekno := string_to_array(substring(repeatrule from 'BYWEEKNO=([0-9,+-]+)(;|$)'), ','); + result.bymonthday := string_to_array(substring(repeatrule from 'BYMONTHDAY=([0-9,+-]+)(;|$)'), ','); + result.bymonth := string_to_array(substring(repeatrule from 'BYMONTH=(([+-]?[0-1]?[0-9],?)+)(;|$)'), ','); + result.bysetpos := string_to_array(substring(repeatrule from 'BYSETPOS=(([+-]?[0-9]{1,3},?)+)(;|$)'), ','); --- result.bysecond := list_to_array(substring(repeatrule from 'BYSECOND=([0-9,]+)(;|$)'))::int[]; --- result.byminute := list_to_array(substring(repeatrule from 'BYMINUTE=([0-9,]+)(;|$)'))::int[]; --- result.byhour := list_to_array(substring(repeatrule from 'BYHOUR=([0-9,]+)(;|$)'))::int[]; + result.bysecond := string_to_array(substring(repeatrule from 'BYSECOND=([0-9,]+)(;|$)'), ','); + result.byminute := string_to_array(substring(repeatrule from 'BYMINUTE=([0-9,]+)(;|$)'), ','); + result.byhour := string_to_array(substring(repeatrule from 'BYHOUR=([0-9,]+)(;|$)'), ','); RETURN result; END; @@ -102,6 +102,13 @@ BEGIN RETURN; END IF; + -- It seems that the array needs to be initialised to non-null values in PostgreSQL 8.1 + -- The date chosen is the earliest valid date in PostgreSQL 8.1, and nobody should be + -- scheduling appointments for then! + FOR i IN 1..31 LOOP + results[i] := '4713-01-01 00:00:00 BC'::timestamp; + END LOOP; + i := 1; dayrule := byday[i]; WHILE dayrule IS NOT NULL LOOP @@ -145,9 +152,11 @@ BEGIN ELSE -- Return all such days that are within the given month WHILE date_part('month', each_day) = this_month LOOP - results[date_part('day',each_day)] := each_day; - each_day := each_day + '1 week'::interval; + index := date_part('day',each_day); + -- RAISE NOTICE 'Adding "%" to list[%] for %', each_day, index, dayrule; + results[index] := each_day; -- RAISE NOTICE 'Added "%" to list for %', each_day, dayrule; + each_day := each_day + '1 week'::interval; END LOOP; END IF; @@ -156,7 +165,7 @@ BEGIN END LOOP; FOR i IN 1..31 LOOP - IF results[i] IS NOT NULL THEN + IF results[i] > '4713-01-01 00:00:00 BC'::timestamp THEN RETURN NEXT results[i]; END IF; END LOOP; @@ -308,10 +317,6 @@ $$ LANGUAGE 'plpgsql' IMMUTABLE; ------------------------------------------------------------------------------------------------------ -- Given a cursor into a set, process the set returning the subset matching the BYSETPOS --- --- Note that this function *requires* PostgreSQL 8.3 or later for the cursor handling syntax --- to work. I guess we could do it with an array, instead, for compatibility with earlier --- releases, since there's a maximum of 366 positions in a set. ------------------------------------------------------------------------------------------------------ CREATE or REPLACE FUNCTION rrule_bysetpos_filter( REFCURSOR, INT[] ) RETURNS SETOF TIMESTAMP WITH TIME ZONE AS $$ DECLARE @@ -319,6 +324,8 @@ DECLARE bysetpos ALIAS FOR $2; valid_date TIMESTAMP WITH TIME ZONE; i INT; + setsize INT; + ourset TIMESTAMP WITH TIME ZONE[]; BEGIN IF bysetpos IS NULL THEN @@ -328,13 +335,22 @@ BEGIN RETURN NEXT valid_date; END LOOP; ELSE + setsize := 0; + FOR i IN 1..366 LOOP + FETCH curse INTO valid_date; + EXIT WHEN NOT FOUND; + ourset[i] := valid_date; + setsize = setsize + 1; + END LOOP; + -- RAISE NOTICE 'We have % in our set.', setsize; 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; + valid_date := ourset[bysetpos[i]]; + -- RAISE NOTICE 'Extracted % from position %.', valid_date, bysetpos[i]; ELSE - MOVE LAST IN curse; - FETCH RELATIVE (bysetpos[i] + 1) FROM curse INTO valid_date; + valid_date := ourset[ setsize + bysetpos[i] + 1 ]; + -- RAISE NOTICE 'Extracted % from position (% % + 1) = %.', valid_date, setsize, bysetpos[i], setsize + bysetpos[i] + 1; END IF; IF valid_date IS NOT NULL THEN RETURN NEXT valid_date; @@ -396,7 +412,7 @@ CREATE or REPLACE FUNCTION weekly_set( TIMESTAMP WITH TIME ZONE, rrule_parts ) R DECLARE after ALIAS FOR $1; rrule ALIAS FOR $2; - valid_date TIMESTAMP WITH TIME ZONE; + rowvar RECORD; curse REFCURSOR; weekno INT; i INT; @@ -409,8 +425,10 @@ BEGIN 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; + OPEN curse FOR SELECT r FROM rrule_week_byday_set(after, rrule.byday ) r; + FOR rowvar IN SELECT d FROM rrule_bysetpos_filter(curse,rrule.bysetpos) d LOOP + RETURN NEXT rowvar.d; + END LOOP; END; $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; @@ -423,7 +441,7 @@ CREATE or REPLACE FUNCTION monthly_set( TIMESTAMP WITH TIME ZONE, rrule_parts ) DECLARE after ALIAS FOR $1; rrule ALIAS FOR $2; - valid_date TIMESTAMP WITH TIME ZONE; + rowvar RECORD; curse REFCURSOR; setpos INT; i INT; @@ -444,16 +462,18 @@ BEGIN * 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 + OPEN curse 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; + OPEN curse FOR SELECT r FROM rrule_month_bymonthday_set(after, rrule.bymonthday ) r ORDER BY 1; ELSE - OPEN curse SCROLL FOR SELECT r FROM rrule_month_byday_set(after, rrule.byday ) r ORDER BY 1; + OPEN curse 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; + FOR rowvar IN SELECT d FROM rrule_bysetpos_filter(curse,rrule.bysetpos) d LOOP + RETURN NEXT rowvar.d; + END LOOP; END; $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; @@ -461,12 +481,14 @@ $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; ------------------------------------------------------------------------------------------------------ -- If this is YEARLY;BYMONTH, abuse MONTHLY;BYMONTH for everything except the BYSETPOS +-- FIXME: it is wrong to abuse MONTHLY;BYMONTH in this way. We should write YEARLY;BYMONTH properly. ------------------------------------------------------------------------------------------------------ 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; current_base TIMESTAMP WITH TIME ZONE; + rowvar RECORD; rr rrule_parts; i INT; BEGIN @@ -478,7 +500,9 @@ BEGIN FOR i IN 1..12 LOOP EXIT WHEN rr.bymonth[i] IS NULL; current_base := date_trunc( 'year', after ) + ((rr.bymonth[i] - 1)::text || ' months')::interval + (after::time)::interval; - RETURN QUERY SELECT r FROM monthly_set(current_base,rr) r; + FOR rowvar IN SELECT d FROM monthly_set(current_base,rr) d LOOP + RETURN NEXT rowvar.d; + END LOOP; END LOOP; ELSE -- We don't yet implement byweekno, byblah @@ -497,15 +521,16 @@ DECLARE after ALIAS FOR $1; rrule ALIAS FOR $2; current_base TIMESTAMP WITH TIME ZONE; + rowvar RECORD; 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; + OPEN curse FOR SELECT r FROM rrule_yearly_bymonth_set(after, rrule ) r; + FOR rowvar IN SELECT d FROM rrule_bysetpos_filter(curse,rrule.bysetpos) d LOOP + current_base := date_trunc( 'day', rowvar.d ) + (after::time)::interval; RETURN NEXT current_base; END LOOP; ELSE @@ -532,6 +557,7 @@ DECLARE base_day TIMESTAMP WITH TIME ZONE; current_base TIMESTAMP WITH TIME ZONE; current TIMESTAMP WITH TIME ZONE; + rowvar RECORD; rrule rrule_parts%ROWTYPE; BEGIN loopcount := 0; @@ -547,7 +573,8 @@ BEGIN base_day := date_trunc('day',basedate); 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 + FOR rowvar IN SELECT d FROM daily_set(current_base,rrule) d WHERE d >= base_day LOOP + current := rowvar.d; -- 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; IF current >= mindate THEN @@ -559,7 +586,8 @@ BEGIN 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 + FOR rowvar IN SELECT d FROM weekly_set(current_base,rrule) d WHERE d >= base_day LOOP + current := rowvar.d; IF test_byyearday_rule(current,rrule.byyearday) AND test_bymonthday_rule(current,rrule.bymonthday) AND test_bymonth_rule(current,rrule.bymonth) @@ -574,7 +602,8 @@ BEGIN END LOOP; 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 + FOR rowvar IN SELECT d FROM monthly_set(current_base,rrule) d WHERE d >= base_day LOOP + current := rowvar.d; -- IF /* test_byyearday_rule(current,rrule.byyearday) -- AND */ test_bymonth_rule(current,rrule.bymonth) -- THEN @@ -588,7 +617,8 @@ BEGIN 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 + FOR rowvar IN SELECT d FROM yearly_set(current_base,rrule) d WHERE d >= base_day LOOP + current := rowvar.d; EXIT WHEN rrule.until IS NOT NULL AND current > rrule.until; IF current >= mindate THEN RETURN NEXT current; @@ -617,8 +647,11 @@ DECLARE basedate ALIAS FOR $1; repeatrule ALIAS FOR $2; maxdate TIMESTAMP WITH TIME ZONE; + rowvar RECORD; BEGIN maxdate := current_date + '10 years'::interval; - RETURN QUERY SELECT d FROM rrule_event_instances_range( basedate, repeatrule, basedate, maxdate, 300 ) d; + FOR rowvar IN SELECT d FROM rrule_event_instances_range( basedate, repeatrule, basedate, maxdate, 300 ) d LOOP + RETURN NEXT rowvar.d; + END LOOP; END; $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; diff --git a/dba/rrule_tests.sql b/dba/rrule_tests.sql index b2fdccba..084870fc 100644 --- a/dba/rrule_tests.sql +++ b/dba/rrule_tests.sql @@ -9,7 +9,7 @@ -- RRULE:FREQ=DAILY;COUNT=10 -- -- ==> (1997 9:00 AM EDT)September 2-11 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;COUNT=10' ) LIMIT 20; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;COUNT=10' ); -- -- -------------------------------------------------------------------- -- Daily until December 24, 1997: @@ -19,7 +19,7 @@ -- -- ==> (1997 9:00 AM EDT)September 2-30;October 1-25 -- (1997 9:00 AM EST)October 26-31;November 1-30;December 1-23 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;UNTIL=19971224T000000' ) LIMIT 500; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;UNTIL=19971224T000000' ); -- -- -------------------------------------------------------------------- -- Every other day - forever: @@ -31,7 +31,7 @@ -- (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29; -- Dec 1,3,... -- ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;INTERVAL=2' ) LIMIT 20; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;INTERVAL=2' ) LIMIT 50; -- -------------------------------------------------------------------- -- Every 10 days, 5 occurrences: -- @@ -40,7 +40,7 @@ -- -- ==> (1997 9:00 AM EDT)September 2,12,22;October 2,12 -- ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;INTERVAL=10;COUNT=5' ) LIMIT 500; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;INTERVAL=10;COUNT=5' ); -- -------------------------------------------------------------------- -- Everyday in January, for 3 years: -- @@ -54,7 +54,10 @@ -- (1999 9:00 AM EDT)January 1-31 -- (2000 9:00 AM EDT)January 1-31 -- ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1' ) LIMIT 500; +--- ? No instance on January 31, since 9:00am EDT is after 2000-01-31 09:00:00 UTC. I think that is correct. +--- ? SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19980101T090000', 'FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA' ); +--- ? No instance on January 31, since 9:00am EDT is after 2000-01-31 09:00:00 UTC. I think that is correct. +--- ? SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19980101T090000', 'FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1' ); -- -------------------------------------------------------------------- -- Weekly for 10 occurrences -- @@ -63,7 +66,7 @@ -- -- ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 -- (1997 9:00 AM EST)October 28;November 4 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;COUNT=10' ) LIMIT 500; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;COUNT=10' ); -- -- -------------------------------------------------------------------- -- Weekly until December 24, 1997 @@ -74,7 +77,7 @@ -- ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 -- (1997 9:00 AM EST)October 28;November 4,11,18,25; -- December 2,9,16,23 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;UNTIL=19971224T000000Z' ) LIMIT 500; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;UNTIL=19971224T000000Z' ); -- -- -------------------------------------------------------------------- -- Every other week - forever: @@ -84,9 +87,9 @@ -- -- ==> (1997 9:00 AM EDT)September 2,16,30;October 14 -- (1997 9:00 AM EST)October 28;November 11,25;December 9,23 --- (1998 9:00 AM EST)January 6,20;February +-- (1998 9:00 AM EST)January 6,20;February 3, 17 -- ... ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;INTERVAL=2;WKST=SU' ) LIMIT 12; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;INTERVAL=2;WKST=SU' ) LIMIT 13; -- -- -------------------------------------------------------------------- -- Weekly on Tuesday and Thursday for 5 weeks: @@ -97,8 +100,8 @@ -- RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH -- -- ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH' ) LIMIT 500; ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH' ) LIMIT 500; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH' ); -- -- -------------------------------------------------------------------- -- Every other week on Monday, Wednesday and Friday until December 24, @@ -111,7 +114,8 @@ -- 1,3,13,15,17 -- (1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28; -- December 8,10,12,22 ---- !!! SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR' ); +-- !!! This is challenging, because it starts on DTSTART even though DTSTART fails the repeat criteria. In fact no known provide a UI that allows data like this to be created. +--- !!! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR' ); -- -- -------------------------------------------------------------------- -- Every other week on Tuesday and Thursday, for 8 occurrences: @@ -120,7 +124,7 @@ -- RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH -- -- ==> (1997 9:00 AM EDT)September 2,4,16,18,30;October 2,14,16 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH' ); -- -- -------------------------------------------------------------------- -- Monthly on the 1st Friday for ten occurrences: @@ -132,7 +136,7 @@ -- (1997 9:00 AM EST)November 7;Dec 5 -- (1998 9:00 AM EST)January 2;February 6;March 6;April 3 -- (1998 9:00 AM EDT)May 1;June 5 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;COUNT=10;BYDAY=1FR' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970905T090000', 'FREQ=MONTHLY;COUNT=10;BYDAY=1FR' ); -- -- -------------------------------------------------------------------- -- Monthly on the 1st Friday until December 24, 1997: @@ -142,7 +146,7 @@ -- -- ==> (1997 9:00 AM EDT)September 5;October 3 -- (1997 9:00 AM EST)November 7;December 5 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970905T090000', 'FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR' ); -- -- -------------------------------------------------------------------- -- Every other month on the 1st and last Sunday of the month for 10 @@ -155,7 +159,7 @@ -- (1997 9:00 AM EST)November 2,30 -- (1998 9:00 AM EST)January 4,25;March 1,29 -- (1998 9:00 AM EDT)May 3,31 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970907T090000', 'FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU' ); -- -- -------------------------------------------------------------------- -- Monthly on the second to last Monday of the month for 6 months: @@ -166,7 +170,7 @@ -- ==> (1997 9:00 AM EDT)September 22;October 20 -- (1997 9:00 AM EST)November 17;December 22 -- (1998 9:00 AM EST)January 19;February 16 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;COUNT=6;BYDAY=-2MO' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;COUNT=6;BYDAY=-2MO' ); -- -- -------------------------------------------------------------------- -- Monthly on the third to the last day of the month, forever: @@ -178,7 +182,7 @@ -- (1997 9:00 AM EST)October 29;November 28;December 29 -- (1998 9:00 AM EST)January 29;February 26 -- ... ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;BYMONTHDAY=-3' ) LIMIT 30; +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;BYMONTHDAY=-3' ) LIMIT 6; -- -- -------------------------------------------------------------------- -- Monthly on the 2nd and 15th of the month for 10 occurrences: @@ -189,7 +193,7 @@ -- ==> (1997 9:00 AM EDT)September 2,15;October 2,15 -- (1997 9:00 AM EST)November 2,15;December 2,15 -- (1998 9:00 AM EST)January 2,15 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15' ); -- -- -------------------------------------------------------------------- -- Monthly on the first and last day of the month for 10 occurrences: @@ -200,7 +204,7 @@ -- ==> (1997 9:00 AM EDT)September 30;October 1 -- (1997 9:00 AM EST)October 31;November 1,30;December 1,31 -- (1998 9:00 AM EST)January 1,31;February 1 ---- SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970930T090000', 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1' ); -- -- -------------------------------------------------------------------- -- Every 18 months on the 10th thru 15th of the month for 10 @@ -212,7 +216,7 @@ -- -- ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15 -- (1999 9:00 AM EST)March 10,11,12,13 ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970910T090000', 'FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15' ); -- -- -------------------------------------------------------------------- -- Every Tuesday, every other month: @@ -224,7 +228,7 @@ -- (1997 9:00 AM EST)November 4,11,18,25 -- (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;INTERVAL=2;BYDAY=TU' ) LIMIT 18; -- -- -------------------------------------------------------------------- -- Yearly in June and July for 10 occurrences: @@ -238,7 +242,8 @@ -- (2001 9:00 AM EDT)June 10;July 10 -- Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components -- are specified, the day is gotten from DTSTART ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- !!! We miss the first instance in June. Busted. +--- !!! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970610T090000', 'FREQ=YEARLY;COUNT=10;BYMONTH=6,7' ); -- -- -------------------------------------------------------------------- -- Every other year on January, February, and March for 10 occurrences: @@ -250,7 +255,8 @@ -- (1999 9:00 AM EST)January 10;February 10;March 10 -- (2001 9:00 AM EST)January 10;February 10;March 10 -- (2003 9:00 AM EST)January 10;February 10;March 10 ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- !!! We miss the first instance in March. Busted. +--- !!! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970310T090000', 'FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3' ); -- -- -------------------------------------------------------------------- -- Every 3rd year on the 1st, 100th and 200th day for 10 occurrences: @@ -265,7 +271,8 @@ -- (2003 9:00 AM EST)January 1 -- (2003 9:00 AM EDT)April 10;July 19 -- (2006 9:00 AM EST)January 1 ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- ! We don't support BYYEARDAY yet. +--- ! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970101T090000', 'FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200' ); -- -- -------------------------------------------------------------------- -- Every 20th Monday of the year, forever: @@ -276,7 +283,8 @@ -- (1998 9:00 AM EDT)May 18 -- (1999 9:00 AM EDT)May 17 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- ! We don't support BYDAY for yearly events. We don't support YEARLY at all well, TBH +--- ! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970519T090000', 'FREQ=YEARLY;BYDAY=20MO' ) LIMIT 3; -- -- -------------------------------------------------------------------- -- Monday of week number 20 (where the default start of the week is @@ -289,7 +297,8 @@ -- (1998 9:00 AM EDT)May 11 -- (1999 9:00 AM EDT)May 17 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- ! We don't support BYWEEKNO at all yet. +--- ! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970512T090000', 'FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO' ) LIMIT 3; -- -- -------------------------------------------------------------------- -- Every Thursday in March, forever: @@ -301,7 +310,7 @@ -- (1998 9:00 AM EST)March 5,12,19,26 -- (1999 9:00 AM EST)March 4,11,18,25 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970313T090000', 'FREQ=YEARLY;BYMONTH=3;BYDAY=TH' ) LIMIT 11; -- -- -------------------------------------------------------------------- -- Every Thursday, but only during June, July, and August, forever: @@ -316,7 +325,7 @@ -- (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29; -- August 5,12,19,26 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970605T090000', 'FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8' ) LIMIT 39; -- -- -------------------------------------------------------------------- -- Every Friday the 13th, forever: @@ -329,7 +338,7 @@ -- (1999 9:00 AM EDT)August 13 -- (2000 9:00 AM EDT)October 13 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13' ) LIMIT 35; -- -- -------------------------------------------------------------------- -- The first Saturday that follows the first Sunday of the month, @@ -343,7 +352,7 @@ -- (1998 9:00 AM EST)January 10;February 7;March 7 -- (1998 9:00 AM EDT)April 11;May 9;June 13... -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970913T090000', 'FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13' ) LIMIT 10; -- -- -------------------------------------------------------------------- -- Every four years, the first Tuesday after a Monday in November, @@ -357,7 +366,7 @@ -- (2000 9:00 AM EST)November 7 -- (2004 9:00 AM EST)November 2 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19961105T090000', 'FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8' ) LIMIT 6; -- -- -------------------------------------------------------------------- -- The 3rd instance into the month of one of Tuesday, Wednesday or @@ -368,7 +377,7 @@ -- -- ==> (1997 9:00 AM EDT)September 4;October 7 -- (1997 9:00 AM EST)November 6 ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970904T090000', 'FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3' ); -- -- -------------------------------------------------------------------- -- The 2nd to last weekday of the month: @@ -380,7 +389,7 @@ -- (1997 9:00 AM EST)October 30;November 27;December 30 -- (1998 9:00 AM EST)January 29;February 26;March 30 -- ... ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970929T090000', 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2' ) LIMIT 8; -- -- -------------------------------------------------------------------- -- Every 3 hours from 9:00 AM to 5:00 PM on a specific day: @@ -389,7 +398,8 @@ -- RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z -- -- ==> (September 2, 1997 EDT)09:00,12:00,15:00 ---- !! SELECT * FROM event_instances('19970902T090000', '' ); +--- !! FREQ=HOURLY Not implemented +--- !! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z' ); -- -- -------------------------------------------------------------------- -- Every 15 minutes for 6 occurrences: @@ -398,7 +408,8 @@ -- RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 -- -- ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15 ---- !! SELECT * FROM event_instances('19970902T090000', '' ); +--- !! FREQ=MINUTELY Not implemented +--- !! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('', '' ); -- -- -------------------------------------------------------------------- -- Every hour and a half for 4 occurrences: @@ -407,7 +418,8 @@ -- RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 -- -- ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30 ---- !! SELECT * FROM event_instances('19970902T090000', '' ); +--- !! FREQ=HOURLY Not implemented +--- !! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('', '' ); -- -- -------------------------------------------------------------------- -- Every 20 minutes from 9:00 AM to 4:40 PM every day: @@ -422,7 +434,8 @@ -- (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20, -- ...16:00,16:20,16:40 -- ... ----- !! SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40' ) LIMIT 500; +--- !! FREQ=MINUTELY and/or BYHOUR Not implemented +--- !! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970902T090000', 'FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40' ) LIMIT 500; -- -- -------------------------------------------------------------------- -- An example where the days generated makes a difference because of @@ -439,5 +452,7 @@ -- RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU -- ==> (1997 EDT)August 5,17,19,31 -- ---- ! SELECT * FROM event_instances('19970902T090000', '' ); +--- !! WKST Not implemented. The second one works fine because WKST == SU in all our code. +--- !! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970805T090000', 'FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO' ); +--- !! SET timezone TO 'US/Eastern'; SELECT * FROM event_instances('19970805T090000', 'FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU' ); -- \ No newline at end of file