-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgchronos" to load this file. \quit CREATE OR REPLACE FUNCTION contains( tsr tstzrange[], ts timestampTz ) RETURNS boolean AS $$ select coalesce ( ( SELECT true FROM (select unnest(tsr) tr) tr where ts <@ tr.tr limit 1 ), false ); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION contains(tstzrange[], timestampTz) IS 'True if timestamp is contained in any range in the array'; CREATE OR REPLACE FUNCTION contains( ts timestamptz, tsr tstzrange[] ) RETURNS boolean AS $$ select contains(tsr,ts); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION contains(timestamptz, tstzrange[]) IS 'True if timestamp is contained in any range in the array'; CREATE OR REPLACE FUNCTION contains( d daterange[], dt date ) RETURNS boolean AS $$ select coalesce ( ( SELECT true FROM (select unnest(d) d) d where dt <@ d.d limit 1 ), false ); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION contains(daterange[], date) IS 'True if date is contained in any daterange in the array'; CREATE OR REPLACE FUNCTION contains( dt date, d daterange[] ) RETURNS boolean AS $$ select contains(d,dt); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION contains(date, daterange[]) IS 'True if date is contained in any daterange in the array'; CREATE OR REPLACE FUNCTION exists_adjacent_lower( ts tstzrange, tsr tstzrange[] ) RETURNS boolean AS $$ select coalesce ( ( SELECT true FROM unnest(tsr) r WHERE ts -|- r.r and r.r << ts LIMIT 1 ), false ); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION exists_adjacent_lower(ts tstzrange, tsr tstzrange[]) is 'True if a range exists in the array that is adjacent to the lower bound of the range operand'; CREATE OR REPLACE FUNCTION exists_adjacent_upper( ts tstzrange, tsr tstzrange[] ) RETURNS boolean AS $$ select coalesce ( ( SELECT true FROM unnest(tsr) r WHERE ts -|- r.r and r.r >> ts LIMIT 1 ), false ); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION exists_adjacent_upper(ts tstzrange, tsr tstzrange[]) is 'True if a range exists in the array that is adjacent to the upper bound of the range operand.'; CREATE OR REPLACE FUNCTION exists_upper( ts timestamptz, tsr tstzrange[] ) RETURNS boolean AS $$ select coalesce ( ( SELECT true FROM unnest(tsr) r WHERE upper(r) = ts LIMIT 1 ), false ); $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION exists_upper(ts timestamptz, tsr tstzrange[]) is 'True if a range exists in the array having its upper bound equal to the timestamp'; CREATE OR REPLACE FUNCTION difference( ts1 IN tstzrange[], ts2 IN tstzrange[] ) RETURNS tstzrange[] AS $$ SELECT array_agg(prd) FROM ( SELECT tstzrange((d_in).start_date, MIN((d_out).end_date)) AS prd FROM ( SELECT DISTINCT lower(d) AS start_date FROM unnest(ts1) d WHERE NOT contains(ts2, lower(d)) AND NOT exists_adjacent_lower(d,ts1) UNION SELECT DISTINCT upper(d) FROM unnest(ts2) d WHERE contains(ts1, upper(d)) AND NOT contains(ts2, upper(d)) ) d_in JOIN ( SELECT upper(d) AS end_date FROM unnest(ts1) d WHERE NOT contains(ts1, upper(d)) UNION ALL SELECT lower(d) FROM unnest(ts2) d WHERE contains(ts1, lower(d)) AND NOT exists_adjacent_lower(d,ts2) ) d_out ON d_in.start_date < d_out.end_date GROUP BY (d_in).start_date ORDER BY (d_in).start_date ) sub; $$ LANGUAGE 'sql' IMMUTABLE STRICT; comment on FUNCTION difference(tstzrange[], tstzrange[]) is 'Subtract from the first operand range segments that are occupied by the second operand.'; CREATE OR REPLACE FUNCTION difference( dr1 IN daterange[], dr2 IN daterange[] ) RETURNS daterange[] AS $$ SELECT array_agg(prd) FROM ( SELECT daterange((d_in).start_date, MIN((d_out).end_date)) AS prd FROM ( SELECT DISTINCT lower(d) AS start_date FROM unnest(dr1) d WHERE NOT contains(dr2,lower(d)) AND NOT contains(dr1, lower(d) - 1) UNION SELECT DISTINCT upper(d) FROM unnest(dr2) d WHERE contains(dr1, upper(d)) AND NOT contains(dr2, upper(d)) ) d_in JOIN ( SELECT upper(d) AS end_date FROM unnest(dr1) d WHERE NOT contains(dr1, upper(d)) UNION ALL SELECT lower(d) FROM unnest(dr2) d WHERE contains(dr1, lower(d)) AND NOT contains(dr2, lower(d) - 1) ) d_out ON d_in.start_date < d_out.end_date GROUP BY (d_in).start_date ORDER BY (d_in).start_date ) sub; $$ LANGUAGE 'sql' IMMUTABLE STRICT; comment on FUNCTION difference(daterange[], daterange[]) is 'Subtract from the first operand range segments that are occupied by the second operand.'; CREATE OR REPLACE FUNCTION intersection( dr1 IN tstzrange[], dr2 IN tstzrange[] ) RETURNS tstzrange[] AS $$ SELECT array_agg(t) FROM ( SELECT tstzrange(start_date, MIN(end_date)) AS t FROM ( SELECT DISTINCT lower(d) AS start_date FROM unnest(dr1) d WHERE NOT exists_adjacent_lower(d,dr1) AND contains(dr2, lower(d)) UNION SELECT DISTINCT lower(d) FROM unnest(dr2) d WHERE NOT exists_adjacent_lower(d,dr2) AND contains(dr1, lower(d)) ) AS t_in JOIN ( SELECT upper(d) AS end_date FROM unnest(dr1) d WHERE NOT contains(dr1, upper(d)) AND (contains(dr2, upper(d)) OR exists_upper(upper(d),dr2)) UNION ALL SELECT upper(d) FROM unnest(dr2) d WHERE NOT contains(dr2, upper(d)) AND (contains(dr1, upper(d)) OR exists_upper(upper(d),dr1)) ) AS t_out ON t_in.start_date < t_out.end_date GROUP BY t_in.start_date ORDER BY t_in.start_date ) sub; $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION intersection(tstzrange[], tstzrange[]) IS 'Return all range segments common to both operands'; CREATE OR REPLACE FUNCTION intersection( dr1 IN daterange[], dr2 IN daterange[] ) RETURNS daterange[] AS $$ SELECT array_agg(t) FROM ( SELECT daterange(start_date, MIN(end_date)) AS t FROM ( SELECT DISTINCT lower(d) AS start_date FROM unnest(dr1) d WHERE NOT contains(dr1, lower(d) - 1) AND contains(dr2, lower(d)) UNION SELECT DISTINCT lower(d) FROM unnest(dr2) d WHERE NOT contains(dr2, lower(d) - 1) AND contains(dr1, lower(d)) ) AS t_in JOIN ( SELECT upper(d) AS end_date FROM unnest(dr1) d WHERE NOT contains(dr1, upper(d)) AND contains(dr2, upper(d)-1) UNION ALL SELECT upper(d) FROM unnest(dr2) d WHERE NOT contains(dr2, upper(d)) AND contains(dr1, upper(d)-1) ) AS t_out ON t_in.start_date < t_out.end_date GROUP BY t_in.start_date ORDER BY t_in.start_date ) sub; $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION intersection(daterange[], daterange[]) IS 'Return all range segments common to both operands'; CREATE OR REPLACE FUNCTION reduce(dr tstzrange[]) RETURNS tstzrange[] AS $$ with sub as ( SELECT start_date, MIN(end_date) as end_date FROM ( SELECT DISTINCT lower(d) AS start_date FROM unnest(dr) d WHERE NOT exists_adjacent_lower(d,dr) ) AS t_in JOIN ( SELECT upper(d) AS end_date FROM unnest(dr) d WHERE NOT contains(dr, upper(d)) AND NOT exists_adjacent_upper(d,dr) ) AS t_out ON t_in.start_date < t_out.end_date GROUP BY t_in.start_date ) select array_agg(r) from ( select tstzrange(MIN(s2.start_date), s2.end_date) r from sub s1 join sub s2 on s1.end_date = s2.end_date and s1.start_date <> s2.start_date group by s2.end_date ) sub2 ; $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION reduce(tstzrange[]) IS 'Union overlapping and adjacent ranges into an array of non-overlapping and non-adjacent ranges'; CREATE OR REPLACE FUNCTION reduce(dr daterange[]) RETURNS daterange[] AS $$ SELECT array_agg(t) FROM ( SELECT daterange(start_date, MIN(end_date)) AS t FROM ( SELECT DISTINCT lower(d) AS start_date FROM unnest(dr) d WHERE NOT contains(dr, lower(d)-1) ) AS t_in JOIN ( SELECT upper(d) AS end_date FROM unnest(dr) d WHERE NOT contains(dr, upper(d)) ) AS t_out ON t_in.start_date < t_out.end_date GROUP BY t_in.start_date ORDER BY t_in.start_date ) sub; $$ LANGUAGE 'sql' IMMUTABLE STRICT; COMMENT ON FUNCTION reduce(daterange[]) IS 'Union overlapping and adjacent ranges into an array of non-overlapping and non-adjacent ranges'; CREATE OR REPLACE FUNCTION range_union( dr1 IN tstzrange[], dr2 IN tstzrange[] ) RETURNS tstzrange[] AS $$ SELECT reduce(dr1 || dr2); $$ LANGUAGE 'sql' IMMUTABLE; COMMENT ON FUNCTION range_union(tstzrange[], tstzrange[]) IS 'Union overlapping and adjacent ranges into an array of non-overlapping and non-adjacent ranges'; CREATE OR REPLACE FUNCTION range_union( dr1 IN daterange[], dr2 IN daterange[] ) RETURNS daterange[] AS $$ SELECT reduce(dr1 || dr2); $$ LANGUAGE 'sql' IMMUTABLE; COMMENT ON FUNCTION range_union(daterange[], daterange[]) IS 'Union overlapping and adjacent ranges into an array of non-overlapping and non-adjacent ranges'; CREATE OPERATOR @>( PROCEDURE = contains, LEFTARG = tstzrange[], RIGHTARG = timestamptz, commutator = <@ ); CREATE OPERATOR <@( PROCEDURE = contains, LEFTARG = timestamptz, RIGHTARG = tstzrange[], commutator = @> ); CREATE OPERATOR @>( PROCEDURE = contains, LEFTARG = daterange[], RIGHTARG = date, commutator = <@ ); CREATE OPERATOR <@( PROCEDURE = contains, LEFTARG = date, RIGHTARG = daterange[], commutator = @> ); CREATE OPERATOR - ( PROCEDURE = difference, LEFTARG = daterange[], RIGHTARG = daterange[] ); CREATE OPERATOR - ( PROCEDURE = difference, LEFTARG = tstzrange[], RIGHTARG = tstzrange[] ); CREATE OPERATOR * ( PROCEDURE = intersection, LEFTARG = daterange[], RIGHTARG = daterange[] ); CREATE OPERATOR * ( PROCEDURE = intersection, LEFTARG = tstzrange[], RIGHTARG = tstzrange[] ); CREATE OPERATOR + ( PROCEDURE = range_union, LEFTARG = daterange[], RIGHTARG = daterange[] ); CREATE OPERATOR + ( PROCEDURE = range_union, LEFTARG = tstzrange[], RIGHTARG = tstzrange[] );