pax_global_header00006660000000000000000000000064146636625230014527gustar00rootroot0000000000000052 comment=89262953329a0452af7f57a5b5aecdd5e0fa3ef6 pg_rrule-0.3.0/000077500000000000000000000000001466366252300133465ustar00rootroot00000000000000pg_rrule-0.3.0/.gitignore000066400000000000000000000006361466366252300153430ustar00rootroot00000000000000# C++ objects and libs *.slo *.lo *.o *.a *.la *.lai *.so *.dll *.dylib # Qt-es /.qmake.cache /.qmake.stash *.pro.user *.pro.user.* *.moc moc_*.cpp qrc_*.cpp ui_*.h Makefile* *-build-* # QtCreator *.autosave #QtCtreator Qml *.qmlproject.user *.qmlproject.user.* # Build build/ # PGXN results/ *.so tmp/ *.o regression.diffs regression.out /sql/ extension_name =>--* !/sql/<%= extension_name =>--*--*.sql pg_rrule-0.3.0/.template000066400000000000000000000000041466366252300151540ustar00rootroot00000000000000sql pg_rrule-0.3.0/LICENSE000066400000000000000000000020671466366252300143600ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 petropavel13 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pg_rrule-0.3.0/META.json000066400000000000000000000011001466366252300147570ustar00rootroot00000000000000{ "name": "pg_rrule", "abstract": "RRULE field type for PostgreSQL", "description": "", "version": "0.1.0", "maintainer": "petropavel", "license": "MIT", "provides": { "pg_rrule": { "abstract": "RRULE field type for PostgreSQL", "file": "sql/pg_rrule.sql", "docfile": "doc/pg_rrule.md", "version": "0.1.0" } }, "release_status": "beta", "generated_by": "petropavel", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" } } pg_rrule-0.3.0/Makefile000066400000000000000000000024161466366252300150110ustar00rootroot00000000000000EXTENSION = pg_rrule EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) DOCS = $(wildcard doc/*.md) TESTS = $(wildcard test/sql/*.sql) REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test # # Uncoment the MODULES line if you are adding C files # to your extention. # MODULE_big = pg_rrule OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) PG_CONFIG = pg_config PG91 = $(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes) ifeq ($(PG91),yes) all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql cp $< $@ DATA = $(wildcard sql/*--*.sql) sql/$(EXTENSION)--$(EXTVERSION).sql EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql endif PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) src/pg_rrule.o: CFLAGS += $(shell pkg-config --cflags libical) # for debug: -g3 -ggdb3 pg_rrule.so: SHLIB_LINK += $(shell pkg-config --libs libical) # Avoid copying the same file twice DATA := $(sort $(DATA)) sql/pg_rrule.sql: sql/pg_rrule.sql.in sed 's,MODULE_PATHNAME,$$libdir/$(@:sql/%.sql=%),g' $< >$@ pg_rrule-0.3.0/README.md000066400000000000000000000073571466366252300146410ustar00rootroot00000000000000pg_rrule ======== Usage Get RRULE parameter get_PARAMNAME. Example. Get freq param: ```sql SELECT get_freq('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z'::rrule); get_freq ---------- WEEKLY (1 row) ``` Example. Get byday param: ```sql SELECT get_byday('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=MO,TH,SU'::rrule); get_byday ----------- {2,5,1} (1 row) ``` Example. Expand RRULE with timezone: ```sql SELECT * FROM unnest( get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02+00'::timestamp with time zone) ); unnest ------------------------ 2019-12-07 10:51:02+00 2019-12-14 10:51:02+00 2019-12-21 10:51:02+00 2019-12-28 10:51:02+00 (4 rows) ``` Example. Expand RRULE without timezone: ```sql SELECT * FROM unnest( get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02'::timestamp) ); unnest --------------------- 2019-12-07 10:51:02 2019-12-14 10:51:02 2019-12-21 10:51:02 2019-12-28 10:51:02 (4 rows) ``` Simple building: -------- ```sh cd src/ qmake pg_rrule.pro make ``` Simple install (Linux): ```sh cp libpg_rrule.so /usr/lib/postgresql/pg_rrule.so cp pg_rrule.control /usr/share/postgresql/extension/ cp sql/pg_rrule.sql /usr/share/postgresql/extension/pg_rrule--0.2.0.sql ``` Simple install (OS X): ```sh #TODO ``` Install extension (Postgresql): ```sql CREATE EXTENSION pg_rrule; ``` Building (A long description (not tested)) To build it, just do this: make make installcheck make install If you encounter an error such as: "Makefile", line 8: Need an operator You need to use GNU make, which may well be installed on your system as `gmake`: gmake gmake install gmake installcheck If you encounter an error such as: make: pg_config: Command not found Be sure that you have `pg_config` installed and in your path. If you used a package management system such as RPM to install PostgreSQL, be sure that the `-devel` package is also installed. If necessary tell the build process where to find it: env PG_CONFIG=/path/to/pg_config make && make installcheck && make install And finally, if all that fails (and if you're on PostgreSQL 8.1 or lower, it likely will), copy the entire distribution directory to the `contrib/` subdirectory of the PostgreSQL source tree and try it there without `pg_config`: env NO_PGXS=1 make && make installcheck && make install If you encounter an error such as: ERROR: must be owner of database regression You need to run the test suite using a super user, such as the default "postgres" super user: make installcheck PGUSER=postgres Once pg_rrule is installed, you can add it to a database. If you're running PostgreSQL 9.1.0 or greater, it's a simple as connecting to a database as a super user and running: CREATE EXTENSION pg_rrule; If you've upgraded your cluster to PostgreSQL 9.1 and already had pg_rrule installed, you can upgrade it to a properly packaged extension with: CREATE EXTENSION pg_rrule FROM unpackaged; For versions of PostgreSQL less than 9.1.0, you'll need to run the installation script: psql -d mydb -f /path/to/pgsql/share/contrib/pg_rrule.sql If you want to install pg_rrule and all of its supporting objects into a specific schema, use the `PGOPTIONS` environment variable to specify the schema, like so: PGOPTIONS=--search_path=extensions psql -d mydb -f pg_rrule.sql Dependencies ------------ The `pg_rrule` has libical as dependency. Copyright and License --------------------- Copyright (c) 2014 petropavel. pg_rrule-0.3.0/doc/000077500000000000000000000000001466366252300141135ustar00rootroot00000000000000pg_rrule-0.3.0/doc/pg_rrule.md000066400000000000000000000025331466366252300162570ustar00rootroot00000000000000pg_rrule ======== Synopsis -------- RRULE field type for PostgreSQL Description ----------- Usage ----- SELECT 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule; rrule ------------------------------------------------------------------------------ FREQ=WEEKLY;UNTIL=20200101T045102Z;BYSECOND=2;BYMINUTE=51;BYHOUR=10;BYDAY=SA (1 row) SELECT * FROM unnest( rrule_get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02+00'::timestamp with time zone) ); unnest ------------------------ 2019-12-07 10:51:02+00 2019-12-14 10:51:02+00 2019-12-21 10:51:02+00 2019-12-28 10:51:02+00 (4 rows) SELECT * FROM unnest( rrule_get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02'::timestamp) ); unnest --------------------- 2019-12-07 10:51:02 2019-12-14 10:51:02 2019-12-21 10:51:02 2019-12-28 10:51:02 (4 rows) Support ------- There is issues tracker? Github? Put this information here. Author ------ petropavel Copyright and License --------------------- Copyright (c) 2014 petropavel. pg_rrule-0.3.0/pg_rrule.control000066400000000000000000000002231466366252300165640ustar00rootroot00000000000000# pg_rrule extension comment = 'RRULE field type for PostgreSQL' default_version = '0.3.0' relocatable = true module_pathname = '$libdir/pg_rrule' pg_rrule-0.3.0/sql/000077500000000000000000000000001466366252300141455ustar00rootroot00000000000000pg_rrule-0.3.0/sql/pg_rrule.sql.in000066400000000000000000000101001466366252300171020ustar00rootroot00000000000000/* * Author: petropavel * Created at: 2014-09-14 23:36:11 +0600 * */ -- -- This is a example code genereted automaticaly -- by pgxn-utils. SET client_min_messages = warning; CREATE TYPE rrule; CREATE OR REPLACE FUNCTION rrule_in(cstring) RETURNS rrule AS 'MODULE_PATHNAME', 'pg_rrule_in' LANGUAGE C IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION rrule_out(rrule) RETURNS cstring AS 'MODULE_PATHNAME', 'pg_rrule_out' LANGUAGE C IMMUTABLE STRICT; CREATE TYPE rrule ( input = rrule_in, output = rrule_out, internallength = 2896 -- CRITICAL: Must match the size of 'icalrecurrencetype' in the current version of libical. cf ASSERT in pg_rrule.c ); CREATE CAST (text AS rrule) WITH INOUT; CREATE CAST (varchar AS rrule) WITH INOUT; CREATE OR REPLACE FUNCTION get_occurrences(rrule, timestamp with time zone) RETURNS timestamp with time zone[] AS 'MODULE_PATHNAME', 'pg_rrule_get_occurrences_dtstart_tz' LANGUAGE C IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION get_occurrences(rrule, timestamp with time zone, timestamp with time zone) RETURNS timestamp with time zone[] AS 'MODULE_PATHNAME', 'pg_rrule_get_occurrences_dtstart_until_tz' LANGUAGE C IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION get_occurrences(rrule, timestamp) RETURNS timestamp[] AS 'MODULE_PATHNAME', 'pg_rrule_get_occurrences_dtstart' LANGUAGE C IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION get_occurrences(rrule, timestamp, timestamp) RETURNS timestamp[] AS 'MODULE_PATHNAME', 'pg_rrule_get_occurrences_dtstart_until' LANGUAGE C IMMUTABLE STRICT; /* FREQ */ CREATE OR REPLACE FUNCTION get_freq(rrule) RETURNS text AS 'MODULE_PATHNAME', 'pg_rrule_get_freq_rrule' LANGUAGE C IMMUTABLE STRICT; /* UNTIL */ CREATE OR REPLACE FUNCTION get_until(rrule) RETURNS timestamp AS 'MODULE_PATHNAME', 'pg_rrule_get_until_rrule' LANGUAGE C IMMUTABLE STRICT; /* UNTIL TZ */ CREATE OR REPLACE FUNCTION get_untiltz(rrule) RETURNS timestamp with time zone AS 'MODULE_PATHNAME', 'pg_rrule_get_untiltz_rrule' LANGUAGE C IMMUTABLE STRICT; /* COUNT */ CREATE OR REPLACE FUNCTION get_count(rrule) RETURNS int4 AS 'MODULE_PATHNAME', 'pg_rrule_get_count_rrule' LANGUAGE C IMMUTABLE STRICT; /* INTERVAL */ CREATE OR REPLACE FUNCTION get_interval(rrule) RETURNS int2 AS 'MODULE_PATHNAME', 'pg_rrule_get_interval_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYSECOND */ CREATE OR REPLACE FUNCTION get_bysecond(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_bysecond_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYMINUTE */ CREATE OR REPLACE FUNCTION get_byminute(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_byminute_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYHOUR */ CREATE OR REPLACE FUNCTION get_byhour(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_byhour_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYDAY */ CREATE OR REPLACE FUNCTION get_byday(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_byday_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYMONTHDAY */ CREATE OR REPLACE FUNCTION get_bymonthday(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_bymonthday_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYYEARDAY */ CREATE OR REPLACE FUNCTION get_byyearday(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_byyearday_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYWEEKNO */ CREATE OR REPLACE FUNCTION get_byweekno(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_byweekno_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYMONTH */ CREATE OR REPLACE FUNCTION get_bymonth(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_bymonth_rrule' LANGUAGE C IMMUTABLE STRICT; /* BYSETPOS */ CREATE OR REPLACE FUNCTION get_bysetpos(rrule) RETURNS int2[] AS 'MODULE_PATHNAME', 'pg_rrule_get_bysetpos_rrule' LANGUAGE C IMMUTABLE STRICT; /* WKST */ CREATE OR REPLACE FUNCTION get_wkst(rrule) RETURNS text AS 'MODULE_PATHNAME', 'pg_rrule_get_wkst_rrule' LANGUAGE C IMMUTABLE STRICT; pg_rrule-0.3.0/sql/uninstall_pg_rrule.sql000066400000000000000000000003371466366252300206010ustar00rootroot00000000000000/* * Author: petropavel * Created at: 2014-09-14 23:36:11 +0600 * */ -- -- This is a example code genereted automaticaly -- by pgxn-utils. SET client_min_messages = warning; BEGIN; DROP TYPE rrule CASCADE; COMMIT; pg_rrule-0.3.0/src/000077500000000000000000000000001466366252300141355ustar00rootroot00000000000000pg_rrule-0.3.0/src/pg_rrule.c000066400000000000000000000475721466366252300161370ustar00rootroot00000000000000#include "pg_rrule.h" #include #include #include // oids #include // get_typlenbyvalalign #include "utils/builtins.h" // cstring_to_text const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind); // no public definition in ical.h const char* icalrecur_weekday_to_string(icalrecurrencetype_weekday kind); // no public definition in ical.h Datum pg_rrule_in(PG_FUNCTION_ARGS) { const char* const rrule_str = PG_GETARG_CSTRING(0); struct icalrecurrencetype recurrence = icalrecurrencetype_from_string(rrule_str); const icalerrorenum err = icalerrno; if (err != ICAL_NO_ERROR) { icalerror_clear_errno(); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Can't parse RRULE. iCal error: %s. RRULE \"%s\".", icalerror_strerror(err), rrule_str), errhint("You need to omit \"RRULE:\" part of expression (if present)"))); } if (recurrence.freq == ICAL_NO_RECURRENCE) { // libical 1.0 won't round trip this, so we treat it as an error. ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid RRULE frequency. RRULE \"%s\".", rrule_str))); } // Need to copy result to palloc'd memory struct icalrecurrencetype* recurrence_ref = palloc(sizeof(struct icalrecurrencetype)); memcpy(recurrence_ref, &recurrence, sizeof(struct icalrecurrencetype)); // Be sure to copy the strings using the pstrdup function if (recurrence.rscale) { recurrence_ref->rscale = pstrdup(recurrence.rscale); } // TODO: do we also need to copy the zone? // Tried the below but 'icaltimezone' is an opaque type and we can't access the size // // if (recurrence.until.zone) { // struct icaltimezone* cloned_zone = palloc(sizeof(struct icaltimezone)); // memcpy(cloned_zone, recurrence.until.zone, sizeof(struct icaltimezone)); // recurrence_ref->until.zone = cloned_zone; // } // log_recurrence(recurrence_ref); // Below is a critical assert, // to be sure the size of icalrecurrencetype in the current version of libical // matches the size of the custom RRULE type defined in pg_rrule/sql/pg_rrule.sql.in // // CREATE TYPE rrule ( // input = rrule_in, // output = rrule_out, // internallength = 2896 // ); // // If a new version of libical changes the size of icalrecurrencetype, we need to fail here // so that we are aware of the change. assert(sizeof(struct icalrecurrencetype)==2896); PG_RETURN_POINTER(recurrence_ref); } Datum pg_rrule_out(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); char* const rrule_str = icalrecurrencetype_as_string(recurrence_ref); const icalerrorenum err = icalerrno; if (err != ICAL_NO_ERROR) { icalerror_clear_errno(); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Can't convert RRULE to string. iCal error: %s", icalerror_strerror(err)), errhint("Please create new issue here: https://github.com/petropavel13/pg_rrule/issues/new"))); } const size_t str_bytes = sizeof(char) * (strlen(rrule_str) + 1); char* const rrule_str_copy = palloc(str_bytes); memcpy(rrule_str_copy, rrule_str, str_bytes); PG_RETURN_CSTRING(rrule_str_copy); } Datum pg_rrule_get_occurrences_dtstart_tz(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); TimestampTz dtstart_ts = PG_GETARG_TIMESTAMPTZ(1); long int gmtoff = 0; icaltimezone* ical_tz = NULL; if (pg_get_timezone_offset(session_timezone, &gmtoff)) { ical_tz = icaltimezone_get_builtin_timezone_from_offset(gmtoff, pg_get_timezone_name(session_timezone)); } if (ical_tz == NULL) { elog(WARNING, "Can't get timezone from current session! Fallback to UTC."); ical_tz = icaltimezone_get_utc_timezone(); } pg_time_t dtstart_ts_pg_time_t = timestamptz_to_time_t(dtstart_ts); struct icaltimetype dtstart = icaltime_from_timet_with_zone((time_t)dtstart_ts_pg_time_t, 0, ical_tz); // it's safe ? time_t may be double, float, etc... return pg_rrule_get_occurrences_rrule(*recurrence_ref, dtstart, true); } Datum pg_rrule_get_occurrences_dtstart_until_tz(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); TimestampTz dtstart_ts = PG_GETARG_TIMESTAMPTZ(1); TimestampTz until_ts = PG_GETARG_TIMESTAMPTZ(2); long int gmtoff = 0; icaltimezone* ical_tz = NULL; if (pg_get_timezone_offset(session_timezone, &gmtoff)) { ical_tz = icaltimezone_get_builtin_timezone_from_offset(gmtoff, pg_get_timezone_name(session_timezone)); } if (ical_tz == NULL) { elog(WARNING, "Can't get timezone from current session! Fallback to UTC."); ical_tz = icaltimezone_get_utc_timezone(); } pg_time_t dtstart_ts_pg_time_t = timestamptz_to_time_t(dtstart_ts); pg_time_t until_ts_pg_time_t = timestamptz_to_time_t(until_ts); struct icaltimetype dtstart = icaltime_from_timet_with_zone((time_t)dtstart_ts_pg_time_t, 0, ical_tz); // it's safe ? time_t may be double, float, etc... struct icaltimetype until = icaltime_from_timet_with_zone((time_t)until_ts_pg_time_t, 0, ical_tz); // it's safe ? time_t may be double, float, etc... return pg_rrule_get_occurrences_rrule_until(*recurrence_ref, dtstart, until, true); } Datum pg_rrule_get_occurrences_dtstart(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); Timestamp dtstart_ts = PG_GETARG_TIMESTAMP(1); pg_time_t dtstart_ts_pg_time_t = timestamptz_to_time_t(dtstart_ts); struct icaltimetype dtstart = icaltime_from_timet_with_zone((time_t)dtstart_ts_pg_time_t, 0, icaltimezone_get_utc_timezone()); // it's safe ? time_t may be double, float, etc... return pg_rrule_get_occurrences_rrule(*recurrence_ref, dtstart, false); } Datum pg_rrule_get_occurrences_dtstart_until(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); Timestamp dtstart_ts = PG_GETARG_TIMESTAMP(1); Timestamp until_ts = PG_GETARG_TIMESTAMP(2); pg_time_t dtstart_ts_pg_time_t = timestamptz_to_time_t(dtstart_ts); pg_time_t until_ts_pg_time_t = timestamptz_to_time_t(until_ts); struct icaltimetype dtstart = icaltime_from_timet_with_zone((time_t)dtstart_ts_pg_time_t, 0, icaltimezone_get_utc_timezone()); // it's safe ? time_t may be double, float, etc... struct icaltimetype until = icaltime_from_timet_with_zone((time_t)until_ts_pg_time_t, 0, icaltimezone_get_utc_timezone()); // it's safe ? time_t may be double, float, etc... return pg_rrule_get_occurrences_rrule_until(*recurrence_ref, dtstart, until, false); } /* FREQ */ Datum pg_rrule_get_freq_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); if (recurrence_ref->freq == ICAL_NO_RECURRENCE) { PG_RETURN_NULL(); } const char* const freq_string = icalrecur_freq_to_string(recurrence_ref->freq); PG_RETURN_TEXT_P(cstring_to_text(freq_string)); } /* UNTIL */ Datum pg_rrule_get_until_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); if (icaltime_is_null_time(recurrence_ref->until)) { PG_RETURN_NULL(); } pg_time_t until_pg_time_t = (pg_time_t)icaltime_as_timet_with_zone(recurrence_ref->until, icaltimezone_get_utc_timezone()); // it's safe ? time_t may be double, float, etc... PG_RETURN_TIMESTAMP(time_t_to_timestamptz(until_pg_time_t)); } /* UNTIL TZ */ Datum pg_rrule_get_untiltz_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); if (icaltime_is_null_time(recurrence_ref->until)) { PG_RETURN_NULL(); } long int gmtoff = 0; icaltimezone* ical_tz = NULL; if (pg_get_timezone_offset(session_timezone, &gmtoff)) { ical_tz = icaltimezone_get_builtin_timezone_from_offset(gmtoff, pg_get_timezone_name(session_timezone)); } if (ical_tz == NULL) { elog(WARNING, "Can't get timezone from current session! Fallback to UTC."); ical_tz = icaltimezone_get_utc_timezone(); } pg_time_t until_pg_time_t = (pg_time_t)icaltime_as_timet_with_zone(recurrence_ref->until, ical_tz); // it's safe ? time_t may be double, float, etc... PG_RETURN_TIMESTAMP(time_t_to_timestamptz(until_pg_time_t)); } /* COUNT */ Datum pg_rrule_get_count_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); PG_RETURN_INT32(recurrence_ref->count); } /* INTERVAL */ Datum pg_rrule_get_interval_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); PG_RETURN_INT16(recurrence_ref->interval); } /* BYSECOND */ Datum pg_rrule_get_bysecond_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_SECOND_SIZE && recurrence_ref->by_second[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_second[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYMINUTE */ Datum pg_rrule_get_byminute_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_MINUTE_SIZE && recurrence_ref->by_minute[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_minute[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYHOUR */ Datum pg_rrule_get_byhour_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_HOUR_SIZE && recurrence_ref->by_hour[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_hour[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYDAY */ Datum pg_rrule_get_byday_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_DAY_SIZE && recurrence_ref->by_day[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_day[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYMONTHDAY */ Datum pg_rrule_get_bymonthday_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_MONTHDAY_SIZE && recurrence_ref->by_month_day[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_month_day[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYYEARDAY */ Datum pg_rrule_get_byyearday_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_YEARDAY_SIZE && recurrence_ref->by_year_day[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_year_day[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYWEEKNO */ Datum pg_rrule_get_byweekno_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_WEEKNO_SIZE && recurrence_ref->by_week_no[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_week_no[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYMONTH */ Datum pg_rrule_get_bymonth_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_MONTH_SIZE && recurrence_ref->by_month[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_month[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* BYSETPOS */ Datum pg_rrule_get_bysetpos_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); unsigned int cnt = 0; for (; cnt < ICAL_BY_SETPOS_SIZE && recurrence_ref->by_set_pos[cnt] != ICAL_RECURRENCE_ARRAY_MAX; ++cnt); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { datum_elems[i] = Int16GetDatum(recurrence_ref->by_set_pos[i]); } int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, INT2OID, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } /* WKST */ Datum pg_rrule_get_wkst_rrule(PG_FUNCTION_ARGS) { struct icalrecurrencetype* recurrence_ref = (struct icalrecurrencetype*)PG_GETARG_POINTER(0); if (recurrence_ref->week_start == ICAL_NO_WEEKDAY) { PG_RETURN_NULL(); } const char* const wkst_string = icalrecur_weekday_to_string(recurrence_ref->week_start); PG_RETURN_TEXT_P(cstring_to_text(wkst_string)); } Datum pg_rrule_get_occurrences_rrule(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, bool use_tz) { return pg_rrule_get_occurrences_rrule_until(recurrence, dtstart, icaltime_null_time(), use_tz); } Datum pg_rrule_get_occurrences_rrule_until(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, struct icaltimetype until, bool use_tz) { time_t* times_array = NULL; unsigned int cnt = 0; pg_rrule_rrule_to_time_t_array_until(recurrence, dtstart, until, ×_array, &cnt); pg_time_t* pg_times_array = palloc(sizeof(pg_time_t) * cnt); unsigned int i; for (i = 0; i < cnt; ++i) { pg_times_array[i] = (pg_time_t)times_array[i]; // it's safe ? time_t may be double, float, etc... } free(times_array); Datum* const datum_elems = palloc(sizeof(Datum) * cnt); if (use_tz) { for (i = 0; i < cnt; ++i) { datum_elems[i] = TimestampTzGetDatum(time_t_to_timestamptz(pg_times_array[i])); } } else { for (i = 0; i < cnt; ++i) { datum_elems[i] = TimestampGetDatum(time_t_to_timestamptz(pg_times_array[i])); } } pfree(pg_times_array); int16 typlen; bool typbyval; char typalign; const Oid ts_oid = use_tz ? TIMESTAMPTZOID : TIMESTAMPOID; get_typlenbyvalalign(ts_oid, &typlen, &typbyval, &typalign); ArrayType* result_array = construct_array(datum_elems, cnt, ts_oid, typlen, typbyval, typalign); PG_RETURN_ARRAYTYPE_P(result_array); } void pg_rrule_rrule_to_time_t_array(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, time_t** const out_array, unsigned int* const out_count) { pg_rrule_rrule_to_time_t_array_until(recurrence, dtstart, icaltime_null_time(), out_array, out_count); } void pg_rrule_rrule_to_time_t_array_until(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, struct icaltimetype until, time_t** const out_array, unsigned int* const out_count) { icalrecur_iterator* const recur_iterator = icalrecur_iterator_new(recurrence, dtstart); icalarray* const icaltimes_list = icalarray_new(sizeof(icaltimetype), 32); struct icaltimetype ical_time = icalrecur_iterator_next(recur_iterator); if (icaltime_is_null_time(until)) { while (icaltime_is_null_time(ical_time) == false) { icalarray_append(icaltimes_list, &ical_time); ical_time = icalrecur_iterator_next(recur_iterator); } } else { while (icaltime_is_null_time(ical_time) == false && icaltime_compare(ical_time, until) != 1 ) { // while ical_time <= until icalarray_append(icaltimes_list, &ical_time); ical_time = icalrecur_iterator_next(recur_iterator); } } icalrecur_iterator_free(recur_iterator); const unsigned int cnt = (*out_count) = icaltimes_list->num_elements; time_t* times_array = (*out_array) = malloc(sizeof(time_t) * cnt); unsigned int i = 0; for (i = 0; i < cnt; ++i) { ical_time = (*(icaltimetype*)icalarray_element_at(icaltimes_list, i)); times_array[i] = icaltime_as_timet_with_zone(ical_time, dtstart.zone); } icalarray_free(icaltimes_list); } pg_rrule-0.3.0/src/pg_rrule.h000066400000000000000000000070511466366252300161300ustar00rootroot00000000000000#ifndef PG_RRULE_H #define PG_RRULE_H #include #include #include PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(pg_rrule_in); Datum pg_rrule_in(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_rrule_out); Datum pg_rrule_out(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_rrule_get_occurrences_dtstart_tz); Datum pg_rrule_get_occurrences_dtstart_tz(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_rrule_get_occurrences_dtstart_until_tz); Datum pg_rrule_get_occurrences_dtstart_until_tz(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_rrule_get_occurrences_dtstart); Datum pg_rrule_get_occurrences_dtstart(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_rrule_get_occurrences_dtstart_until); Datum pg_rrule_get_occurrences_dtstart_until(PG_FUNCTION_ARGS); /* FREQ */ PG_FUNCTION_INFO_V1(pg_rrule_get_freq_rrule); Datum pg_rrule_get_freq_rrule(PG_FUNCTION_ARGS); /* UNTIL */ PG_FUNCTION_INFO_V1(pg_rrule_get_until_rrule); Datum pg_rrule_get_until_rrule(PG_FUNCTION_ARGS); /* UNTIL TZ */ PG_FUNCTION_INFO_V1(pg_rrule_get_untiltz_rrule); Datum pg_rrule_get_untiltz_rrule(PG_FUNCTION_ARGS); /* COUNT */ PG_FUNCTION_INFO_V1(pg_rrule_get_count_rrule); Datum pg_rrule_get_count_rrule(PG_FUNCTION_ARGS); /* INTERVAL */ PG_FUNCTION_INFO_V1(pg_rrule_get_interval_rrule); Datum pg_rrule_get_interval_rrule(PG_FUNCTION_ARGS); /* BYSECOND */ PG_FUNCTION_INFO_V1(pg_rrule_get_bysecond_rrule); Datum pg_rrule_get_bysecond_rrule(PG_FUNCTION_ARGS); /* BYMINUTE */ PG_FUNCTION_INFO_V1(pg_rrule_get_byminute_rrule); Datum pg_rrule_get_byminute_rrule(PG_FUNCTION_ARGS); /* BYHOUR */ PG_FUNCTION_INFO_V1(pg_rrule_get_byhour_rrule); Datum pg_rrule_get_byhour_rrule(PG_FUNCTION_ARGS); /* BYDAY */ PG_FUNCTION_INFO_V1(pg_rrule_get_byday_rrule); Datum pg_rrule_get_byday_rrule(PG_FUNCTION_ARGS); /* BYMONTHDAY */ PG_FUNCTION_INFO_V1(pg_rrule_get_bymonthday_rrule); Datum pg_rrule_get_bymonthday_rrule(PG_FUNCTION_ARGS); /* BYYEARDAY */ PG_FUNCTION_INFO_V1(pg_rrule_get_byyearday_rrule); Datum pg_rrule_get_byyearday_rrule(PG_FUNCTION_ARGS); /* BYWEEKNO */ PG_FUNCTION_INFO_V1(pg_rrule_get_byweekno_rrule); Datum pg_rrule_get_byweekno_rrule(PG_FUNCTION_ARGS); /* BYMONTH */ PG_FUNCTION_INFO_V1(pg_rrule_get_bymonth_rrule); Datum pg_rrule_get_bymonth_rrule(PG_FUNCTION_ARGS); /* BYSETPOS */ PG_FUNCTION_INFO_V1(pg_rrule_get_bysetpos_rrule); Datum pg_rrule_get_bysetpos_rrule(PG_FUNCTION_ARGS); /* WKST */ PG_FUNCTION_INFO_V1(pg_rrule_get_wkst_rrule); Datum pg_rrule_get_wkst_rrule(PG_FUNCTION_ARGS); Datum pg_rrule_get_occurrences_rrule(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, bool use_tz); Datum pg_rrule_get_occurrences_rrule_until(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, struct icaltimetype until, bool use_tz); void pg_rrule_rrule_to_time_t_array(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, time_t** const out_array, unsigned int* const out_count); void pg_rrule_rrule_to_time_t_array_until(struct icalrecurrencetype recurrence, struct icaltimetype dtstart, struct icaltimetype until, time_t** const out_array, unsigned int* const out_count); #endif // PG_RRULE_H pg_rrule-0.3.0/src/pg_rrule.pro000066400000000000000000000011631466366252300164770ustar00rootroot00000000000000TEMPLATE = lib CONFIG += console CONFIG -= app_bundle CONFIG -= qt macx { INCLUDEPATH += "/usr/local/include/server/" \ "/usr/local/include/" LIBS += -L/usr/local/lib/ -lical -lpq QMAKE_LFLAGS_SHLIB -= -dynamiclib QMAKE_LFLAGS_VERSION = "" QMAKE_LFLAGS_COMPAT_VERSION = "" QMAKE_LFLAGS_SONAME = "" QMAKE_LFLAGS = -bundle -flat_namespace -undefined suppress QMAKE_EXTENSION_SHLIB = so } unix:!macx { INCLUDEPATH += $$system(pg_config --includedir-server) LIBS += -lpq -lical QMAKE_CFLAGS = -fpic } SOURCES += \ pg_rrule.c HEADERS += \ pg_rrule.h pg_rrule-0.3.0/test/000077500000000000000000000000001466366252300143255ustar00rootroot00000000000000pg_rrule-0.3.0/test/expected/000077500000000000000000000000001466366252300161265ustar00rootroot00000000000000pg_rrule-0.3.0/test/expected/base.out000066400000000000000000000032541466366252300175750ustar00rootroot00000000000000\set ECHO errors SELECT 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z'::rrule; rrule ------------------------------------ FREQ=WEEKLY;UNTIL=20200101T045102Z (1 row) SELECT get_byday('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=MO,TH,SU'::rrule); get_byday ----------- {2,5,1} (1 row) SELECT get_freq('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z'::rrule); get_freq ---------- WEEKLY (1 row) SELECT * FROM unnest( get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02+00'::timestamp with time zone) ); WARNING: Can't get timezone from current session! Fallback to UTC. unnest ------------------------------ Sat Dec 07 02:51:02 2019 PST Sat Dec 14 02:51:02 2019 PST Sat Dec 21 02:51:02 2019 PST Sat Dec 28 02:51:02 2019 PST (4 rows) SELECT * FROM unnest( get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02'::timestamp) ); unnest -------------------------- Sat Dec 07 10:51:02 2019 Sat Dec 14 10:51:02 2019 Sat Dec 21 10:51:02 2019 Sat Dec 28 10:51:02 2019 (4 rows) WITH occurrences AS ( SELECT unnest( get_occurrences( 'FREQ=DAILY;BYHOUR=09;'::rrule, '2024-05-25 00:00:00'::timestamp, '2024-05-27 00:00:00'::timestamp ) ) as occ ) SELECT * FROM occurrences; occ -------------------------- Sat May 25 09:00:00 2024 Sun May 26 09:00:00 2024 (2 rows) ROLLBACK; pg_rrule-0.3.0/test/sql/000077500000000000000000000000001466366252300151245ustar00rootroot00000000000000pg_rrule-0.3.0/test/sql/base.sql000066400000000000000000000017641466366252300165670ustar00rootroot00000000000000\set ECHO errors BEGIN; \i sql/pg_rrule.sql \set ECHO all SELECT 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z'::rrule; SELECT get_byday('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=MO,TH,SU'::rrule); SELECT get_freq('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z'::rrule); SELECT * FROM unnest( get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02+00'::timestamp with time zone) ); SELECT * FROM unnest( get_occurrences('FREQ=WEEKLY;INTERVAL=1;WKST=MO;UNTIL=20200101T045102Z;BYDAY=SA;BYHOUR=10;BYMINUTE=51;BYSECOND=2'::rrule, '2019-12-07 10:51:02'::timestamp) ); WITH occurrences AS ( SELECT unnest( get_occurrences( 'FREQ=DAILY;BYHOUR=09;'::rrule, '2024-05-25 00:00:00'::timestamp, '2024-05-27 00:00:00'::timestamp ) ) as occ ) SELECT * FROM occurrences; ROLLBACK;