Table of Contents
		Temporal types are based on four time types: the timestamptz type provided by PostgreSQL and three new types which are period, timestampset, and periodset.
	
		The period type is a specialized version of the tstzrange (short for timestamp with time zone range) type provided by PostgreSQL. Type period has similar functionality as type tstzrange but has a more efficient implementation, in particular it is of fixed length while the tstzrange type is of variable length. Furthermore, empty periods and infinite bounds are not allowed in period values, while they are allowed in tstzrange values.
	
		A value of the period type has two bounds, the lower bound and the upper bound, which are timestamptz values. The bounds can be inclusive or exclusive. An inclusive bound means that the boundary instant is included in the period, while an exclusive bound means that the boundary instant is not included in the period. In the text form of a period value, inclusive and exclusive lower bounds are represented, respectively, by “[” and “(”. Likewise, inclusive and exclusive upper bounds are represented, respectively, by “]” and “)”. In a period value, the lower bound must be less than or equal to the upper bound. A period value with equal and inclusive bounds is called an instantaneous period and corresponds to a timestamptz value. Examples of period values are as follows:
	
SELECT period '[2012-01-01 08:00:00, 2012-01-03 09:30:00)'; -- Instant period SELECT period '[2012-01-01 08:00:00, 2012-01-01 08:00:00]'; -- Erroneous period: invalid bounds SELECT period '[2012-01-01 08:10:00, 2012-01-01 08:00:00]'; -- Erroneous period: empty period SELECT period '[2012-01-01 08:00:00, 2012-01-01 08:00:00)';
		The timestampset type represents a set of different timestamptz values. A timestampset value must contain at least one element, in which case it corresponds to a timestamptz value. The elements composing a timestampset value must be ordered. Examples of timestampset values are as follows:
	
SELECT timestampset '{2012-01-01 08:00:00, 2012-01-03 09:30:00}';
-- Singleton timestampset
SELECT timestampset '{2012-01-01 08:00:00}';
-- Erroneous timestampset: unordered elements
SELECT timestampset '{2012-01-01 08:10:00, 2012-01-01 08:00:00}';
-- Erroneous timestampset: duplicate elements
SELECT timestampset '{2012-01-01 08:00:00, 2012-01-01 08:00:00}';
		Finally, the periodset type represents a set of disjoint period values. A periodset value must contain at least one element, in which case it corresponds to a period value. The elements composing a periodset value must be ordered. Examples of periodset values are as follows:
	
SELECT periodset '{[2012-01-01 08:00:00, 2012-01-01 08:10:00],
  [2012-01-01 08:20:00, 2012-01-01 08:40:00]}';
-- Singleton periodset
SELECT periodset '{[2012-01-01 08:00:00, 2012-01-01 08:10:00]}';
-- Erroneous periodset: unordered elements
SELECT periodset '{[2012-01-01 08:20:00, 2012-01-01 08:40:00],
  [2012-01-01 08:00:00, 2012-01-01 08:10:00]}';
-- Erroneous periodset: overlapping elements
SELECT periodset '{[2012-01-01 08:00:00, 2012-01-01 08:10:00],
  [2012-01-01 08:05:00, 2012-01-01 08:15:00]}';
		Values of the periodset type are converted into normal form so that equivalent values have identical representations. For this, consecutive adjacent period values are merged when possible. An example of transformation into normal form is as follows:
	
SELECT periodset '{[2012-01-01 08:00:00, 2012-01-01 08:10:00),
  [2012-01-01 08:10:00, 2012-01-01 08:10:00], (2012-01-01 08:10:00, 2012-01-01 08:20:00]}';
-- "{[2012-01-01 08:00:00+00,2012-01-01 08:20:00+00]}"
		Besides the built-in range types provided by PostgreSQL, MobilityDB defines two additional range types: intrange (another name for int4range) and floatrange.
	
We present next the functions and operators for time and range types. These functions and operators are polymorphic, that is, their arguments may be of several types, and the result type may depend on the type of the arguments. To express this in the signature of the operators, we use the following notation:
A set of types such as {period,timestampset,periodset} represents any of the types listed,
time represents any time type, that is, timestamptz, period, timestampset, or periodset,
number represents any number type, that is, integer or float,
range represents any number range type, that is, intrange or floatrange.
type[] represents an array of type.
			As an example, the signature of the contains operator (@>) is as follows:
		
{timestampset, period, periodset} @> time: boolean
			In the following, for conciseness, the time part of the timestamps is omitted in the examples. Recall that in that case PostgreSQL assumes the time 00:00:00.
		
The period type has a constructor function that accepts two or four arguments. The two-argument form constructs a period in normal form, that is, with inclusive lower bound and exclusive upper bound. The four-argument form constructs a period with bounds specified by the third and fourth arguments, which are Boolean values stating, respectively, whether the left and right bounds are inclusive or not.
			
Constructor for period
period(timestamptz,timestamptz,left_inc=true,right_inc=false): period
-- Period defined with two arguments
SELECT period('2012-01-01 08:00:00', '2012-01-03 08:00:00');
-- [2012-01-01 08:00:00+01, 2012-01-03 08:00:00+01)
-- Period defined with four arguments
SELECT period('2012-01-01 08:00:00', '2012-01-03 09:30:00', false, true);
-- (2012-01-01 08:00:00+01, 2012-01-03 09:30:00+01]
The timestampset type has a constructor function that accepts a single argument which is an array of timestamptz values.
			
The periodset type has a constructor function that accepts a single argument which is an array of period values.
			
Values of the timestamptz, tstzrange, or the time types can be converted to one another using the function CAST or using the :: notation.
			
Cast a timestamptz to another time type
timestamptz::timestampset
timestamptz::period
timestamptz::periodset
SELECT CAST(timestamptz '2012-01-01 08:00:00' AS timestampset);
-- "{2012-01-01 08:00:00+01}"
SELECT CAST(timestamptz '2012-01-01 08:00:00' AS period);
-- "[2012-01-01 08:00:00+01, 2012-01-01 08:00:00+01]"
SELECT CAST(timestamptz '2012-01-01 08:00:00' AS periodset);
-- "{[2012-01-01 08:00:00+01, 2012-01-01 08:00:00+01]}"
Cast a timestampset to a periodset
timestampset::periodset
SELECT CAST(timestampset '{2012-01-01 08:00:00, 2012-01-01 08:15:00,
  2012-01-01 08:25:00}' AS periodset);
-- "{[2012-01-01 08:00:00+01, 2012-01-01 08:00:00+01],
  [2012-01-01 08:15:00+01, 2012-01-01 08:15:00+01],
  [2012-01-01 08:25:00+01, 2012-01-01 08:25:00+01]}"
Cast a period to another time type
period::periodset
period::tstzrange
SELECT period '[2012-01-01 08:00:00, 2012-01-01 08:30:00)'::periodset;
-- "{[2012-01-01 08:00:00+01, 2012-01-01 08:30:00+01)}"
SELECT period '[2012-01-01 08:00:00, 2012-01-01 08:30:00)'::tstzrange;
-- "["2012-01-01 08:00:00+01","2012-01-01 08:30:00+01")"
Cast a tstzrange to a period
tstzrange::period
SELECT tstzrange '[2012-01-01 08:00:00, 2012-01-01 08:30:00)'::period; -- "[2012-01-01 08:00:00+01, 2012-01-01 08:30:00+01)"
Get the memory size in bytes
memSize({timestampset,periodset}): integer
SELECT memSize(timestampset '{2012-01-01, 2012-01-02, 2012-01-03}');
-- 104
SELECT memSize(periodset '{[2012-01-01, 2012-01-02], [2012-01-03, 2012-01-04],
  [2012-01-05, 2012-01-06]}');
-- 136
Get the lower bound
lower(period): timestamptz
SELECT lower(period '[2011-01-01, 2011-01-05)'); -- "2011-01-01"
Get the upper bound
upper(period): timestamptz
SELECT upper(period '[2011-01-01, 2011-01-05)'); -- "2011-01-05"
Is the lower bound inclusive?
lower_inc(period): boolean
SELECT lower_inc(period '[2011-01-01, 2011-01-05)'); -- true
Is the upper bound inclusive?
upper_inc(period): boolean
SELECT upper_inc(period '[2011-01-01, 2011-01-05)'); -- false
Get the duration
duration({period,periodset}): interval
SELECT duration(period '[2012-01-01, 2012-01-03)');
-- "2 days"
SELECT duration(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-05)}');
-- "3 days"
Get the timespan ignoring the potential time gaps
timespan({timestampset,periodset}): interval
SELECT timespan(timestampset '{2012-01-01, 2012-01-03}');
-- "2 days"
SELECT timespan(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-05)}');
-- "4 days"
Get the period on which the timestamp set or period set is defined ignoring the potential time gaps
period({timestampset,periodset}): period
SELECT period(timestampset '{2012-01-01, 2012-01-03, 2012-01-05}');
-- "[2012-01-01, 2012-01-05]"
SELECT period(periodset '{[2012-01-01, 2012-01-02), [2012-01-03, 2012-01-04)}');
-- "[2012-01-01, 2012-01-04)"
Get the number of different timestamps
numTimestamps({timestampset,periodset}): integer
SELECT numTimestamps(timestampset '{2012-01-01, 2012-01-03, 2012-01-04}');
-- 3
SELECT numTimestamps(periodset '{[2012-01-01, 2012-01-03), (2012-01-03, 2012-01-05)}');
-- 3
Get the start timestamp
startTimestamp({timestampset,periodset}): timestamptz
The function does not take into account whether the bounds are inclusive or not.
SELECT startTimestamp(periodset '{[2012-01-01, 2012-01-03), (2012-01-03, 2012-01-05)}');
-- "2012-01-01"
Get the end timestamp
endTimestamp({timestampset,periodset}): timestamptz
The function does not take into account whether the bounds are inclusive or not.
SELECT endTimestamp(periodset '{[2012-01-01, 2012-01-03), (2012-01-03, 2012-01-05)}');
-- "2012-01-05"
Get the n-th different timestamp
timestampN({timestampset,periodset},integer): timestamptz
The function does not take into account whether the bounds are inclusive or not.
SELECT timestampN(periodset '{[2012-01-01, 2012-01-03), (2012-01-03, 2012-01-05)}', 3);
-- "2012-01-04"
Get the different timestamps
timestamps({timestampset,periodset}): timestampset
The function does not take into account whether the bounds are inclusive or not.
SELECT timestamps(periodset '{[2012-01-01, 2012-01-03), (2012-01-03, 2012-01-05)}');
-- "{"2012-01-01", "2012-01-03", "2012-01-05"}"
Get the number of periods
numPeriods(periodset): integer
SELECT numPeriods(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-04],
  [2012-01-05, 2012-01-06)}');
-- 3
Get the start period
startPeriod(periodset): period
SELECT startPeriod(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-04],
  [2012-01-05, 2012-01-06)}');
-- "[2012-01-01,2012-01-03)"
Get the end period
endPeriod(periodset): period
SELECT endPeriod(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-04],
  [2012-01-05, 2012-01-06)}');
-- "[2012-01-05,2012-01-06)"
Get the n-th period
periodN(periodset,integer): period
SELECT periodN(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-04],
  [2012-01-05, 2012-01-06)}', 2);
-- "[2012-01-04,2012-01-04]"
Get the periods
periods(periodset): period[]
SELECT periods(periodset '{[2012-01-01, 2012-01-03), [2012-01-04, 2012-01-04],
  [2012-01-05, 2012-01-06)}');
-- "{"[2012-01-01,2012-01-03)", "[2012-01-04,2012-01-04]", "[2012-01-05,2012-01-06)"}"
Shift the time value by an interval
shift({timestampset,period,periodset}): {timestampset,period,periodset}
SELECT shift(timestampset '{2001-01-01, 2001-01-03, 2001-01-05}', '1 day'::interval);
-- "{2001-01-02, 2001-01-04, 2001-01-06}"
SELECT shift(period '[2001-01-01, 2001-01-03]', '1 day'::interval);
-- "[2001-01-02, 2001-01-04]"
SELECT shift(periodset '{[2001-01-01, 2001-01-03], [2001-01-04, 2001-01-05]}',
  '1 day'::interval);
-- "{[2001-01-02, 2001-01-04], [2001-01-05, 2001-01-06]}"
Round the bounds of a float range to a number of decimal places
setPrecision(floatrange,integer): floatrange
SELECT setPrecision(floatrange '[1.123456789,2.123456789)', 3); -- "[1.123,2.123)" SELECT setPrecision(floatrange '(,2.123456789]', 3); -- "(,2.123]" SELECT setPrecision(floatrange '[1.123456789, inf)', 3); -- "[1.123,Infinity)"
The comparison operators (=, <, and so on) require that the left and right arguments be of the same type. Excepted equality and inequality, the other comparison operators are not useful in the real world but allow B-tree indexes to be constructed on time types. For period values, the operators compare first the lower bound, then the upper bound. For timestamp set and period set values, the operators compare first the bounding periods, and if those are equal, they compare the first N instants or periods, where N is the minimum of the number of composing instants or periods of both values.
The comparison operators available for the time types are given next.
Are the time values equal?
time = time: boolean
SELECT period '[2012-01-01, 2012-01-04)' = period '[2012-01-01, 2012-01-04)'; -- true
Are the time values different?
time <> time: boolean
SELECT period '[2012-01-01, 2012-01-04)' <> period '[2012-01-03, 2012-01-05)'; -- true
Is the first time value less than the second one?
time < time: boolean
SELECT timestampset '{2012-01-01, 2012-01-04}' < timestampset '{2012-01-01, 2012-01-05}';
-- true
Is the first time value greater than the second one?
time > time: boolean
SELECT period '[2012-01-03, 2012-01-04)' > period '[2012-01-02, 2012-01-05)'; -- true
Is the first time value less than or equal to the second one?
time <= time: boolean
SELECT periodset '{[2012-01-01, 2012-01-04)}' <=
  periodset '{[2012-01-01, 2012-01-05), [2012-01-06, 2012-01-07)}';
-- true
Is the first time value greater than or equal to the second one?
time >= time: boolean
SELECT period '[2012-01-03, 2012-01-05)' >= period '[2012-01-03, 2012-01-04)'; -- true
The set operators available for the time types are given next.
Union of the time values
time + time: time
SELECT timestampset '{2011-01-01, 2011-01-03, 2011-01-05}' +
  timestampset '{2011-01-03, 2011-01-06}';
-- "{2011-01-01, 2011-01-03, 2011-01-05, 2011-01-06}"
SELECT period '[2011-01-01, 2011-01-05)' + period '[2011-01-03, 2011-01-07)';
-- "[2011-01-01, 2011-01-07)"
SELECT periodset '{[2011-01-01, 2011-01-03), [2011-01-04, 2011-01-05)}' +
  period '[2011-01-03, 2011-01-04)';
-- "{[2011-01-01, 2011-01-05)}"
Intersection of the time values
time * time: time
SELECT timestampset '{2011-01-01, 2011-01-03}' * timestampset '{2011-01-03, 2011-01-05}';
-- "{2011-01-03}"
SELECT period '[2011-01-01, 2011-01-05)' * period '[2011-01-03, 2011-01-07)';
-- "[2011-01-03, 2011-01-05)"
Difference of the time values
time - time: time
SELECT period '[2011-01-01, 2011-01-05)' - period '[2011-01-03, 2011-01-07)';
-- "[2011-01-01, 2011-01-03)"
SELECT period '[2011-01-01, 2011-01-05]' - period '[2011-01-03, 2011-01-04]'
-- "{[2011-01-01,2011-01-03), (2011-01-04,2011-01-05]}"
SELECT periodset '{[2011-01-01, 2011-01-06], [2011-01-07, 2011-01-10]}' -
  periodset '{[2011-01-02, 2011-01-03], [2011-01-04, 2011-01-05],
  [2011-01-08, 2011-01-09]}';
-- "{[2011-01-01,2011-01-02), (2011-01-03,2011-01-04), (2011-01-05,2011-01-06],
  [2011-01-07,2011-01-08), (2011-01-09,2011-01-10]}"
The topological operators available for the time types are given next.
Do the time values overlap (have instants in common)?
{timestampset,period,periodset} && {timestampset,period,periodset}: boolean
SELECT period '[2011-01-01, 2011-01-05)' && period '[2011-01-02, 2011-01-07)'; -- true
Does the first time value contain the second one?
{timestampset,period,periodset} @> time: boolean
SELECT period '[2011-01-01, 2011-05-01)' @> period '[2011-02-01, 2011-03-01)'; -- true SELECT period '[2011-01-01, 2011-05-01)' @> timestamptz '2011-02-01'; -- true
Is the first time value contained by the second one?
time <@ {timestampset,period,periodset}: boolean
SELECT period '[2011-02-01, 2011-03-01)' <@ period '[2011-01-01, 2011-05-01)'; -- true SELECT timestamptz '2011-01-10' <@ period '[2011-01-01, 2011-05-01)'; -- true
Is the first time value adjacent to the second one?
time -|- time: boolean
SELECT period '[2011-01-01, 2011-01-05)' -|- timestampset '{2011-01-05, 2011-01-07}';
-- true
SELECT periodset '{[2012-01-01, 2012-01-02]}' -|- period '[2012-01-02, 2012-01-03)';
-- false
In PostgreSQL, the range operators <<, &<, >>, &>, and -|- only accept ranges as left or right argument. We extended these operators for numeric ranges so that one argument may be an integer or a float.
The relative position operators available for the time types and range types are given next.
Is the first number or range value strictly left of the second one?
{number,range} << {number,range}: boolean
SELECT intrange '[15, 20)' << 20; -- true
Is the first number or range value strictly right of the second one?
{number,range} >> {number,range}: boolean
SELECT intrange '[15, 20)' >> 10; -- true
Is the first number or range value not to the right of the second one?
{number,range} &< {number,range}: boolean
SELECT intrange '[15, 20)' &< 18; -- false
Is the first number or range value not to the left of the second one?
{number,range} &> {number,range}: boolean
SELECT period '[2011-01-01, 2011-01-03)' &> period '[2011-01-01, 2011-01-05)'; -- true SELECT intrange '[15, 20)' &> 30; -- true
Is the first number or range value adjacent to the second one?
{number,range} -|- {number,range}: boolean
SELECT floatrange '[15, 20)' -|- 20; -- true
Is the first time value scritly before the second one?
time <<# time: boolean
SELECT period '[2011-01-01, 2011-01-03)' <<# timestampset '{2011-01-03, 2011-01-05}';
-- true
Is the first time value strictly after the second one?
time #>> time: boolean
SELECT period '[2011-01-04, 2011-01-05)' #>>
  periodset '{[2011-01-01, 2011-01-04), [2011-01-05, 2011-01-06)}';
-- true
Is the first time value not after the second one?
time &<# time: boolean
SELECT timestampset '{2011-01-02, 2011-01-05}' &<# period '[2011-01-01, 2011-01-05)';
-- false
Is the first time value not before the second one?
time #&> time: boolean
SELECT timestamp '2011-01-01' #&> period '[2011-01-01, 2011-01-05)'; -- true
The temporal aggregate functions generalize the traditional aggregate functions. Their semantics is that they compute the value of the function at every instant in the union of the temporal extents of the values to aggregate. In contrast, recall that all other functions manipulating time types compute the value of the function at every instant in the intersection of the temporal extents of the arguments.
The temporal aggregate functions are the following ones:
Function tcount generalizes the traditional function count. The temporal count can be used to compute at each point in time the number of available objects (for example, number of periods). Function tcount returns a temporal integer (see Chapter 3).
Function extent returns a bounding period that encloses a set of time values.
Similarly, there is an aggregate function for range types:
Function extent returns a bounding range that encloses a set of integer or float range values.
Union is a very useful operation for time types. As we have seen in Section 2.1.6, we can compute the union of two time values using the + operator. However, it is also very useful to have an aggregate version of the union operator for combining an arbitrary number of values. Function tunion can be used for this purpose.
			
Temporal count
tcount({timestampset,period,periodset}): {tinti,tints}
WITH times(ts) AS (
  SELECT timestampset '{2000-01-01, 2000-01-03, 2000-01-05}' UNION
  SELECT timestampset '{2000-01-02, 2000-01-04, 2000-01-06}' UNION
  SELECT timestampset '{2000-01-01, 2000-01-02}'
)
SELECT tcount(ts) FROM times;
-- "{2@2000-01-01, 2@2000-01-02, 1@2000-01-03, 1@2000-01-04, 1@2000-01-05, 1@2000-01-06}"
WITH periods(ps) AS (
  SELECT periodset '{[2000-01-01, 2000-01-02], [2000-01-03, 2000-01-04]}' UNION
  SELECT periodset '{[2000-01-01, 2000-01-04], [2000-01-05, 2000-01-06]}' UNION
  SELECT periodset '{[2000-01-02, 2000-01-06]}'
)
SELECT tcount(ps) FROM periods;
-- {[2@2000-01-01, 3@2000-01-02], (2@2000-01-02, 3@2000-01-03, 3@2000-01-04],
   (1@2000-01-04, 2@2000-01-05, 2@2000-01-06]}
Bounding period
extent({timestampset,period,periodset}): period
WITH times(ts) AS (
  SELECT timestampset '{2000-01-01, 2000-01-03, 2000-01-05}' UNION
  SELECT timestampset '{2000-01-02, 2000-01-04, 2000-01-06}' UNION
  SELECT timestampset '{2000-01-01, 2000-01-02}'
)
SELECT extent(ts) FROM times;
-- "[2000-01-01, 2000-01-06]"
WITH periods(ps) AS (
  SELECT periodset '{[2000-01-01, 2000-01-02], [2000-01-03, 2000-01-04]}' UNION
  SELECT periodset '{[2000-01-01, 2000-01-04], [2000-01-05, 2000-01-06]}' UNION
  SELECT periodset '{[2000-01-02, 2000-01-06]}'
)
SELECT extent(ps) FROM periods;
-- "[2000-01-01, 2000-01-06]"
Bounding range
extent(range): range
WITH ranges(r) AS ( SELECT floatrange '[1, 4)' UNION SELECT floatrange '(5, 8)' UNION SELECT floatrange '(7, 9)' ) SELECT extent(r) FROM ranges; -- "[1,9)"
Temporal union
tunion({timestampset,period,periodset}): {timestampset,periodset}
WITH times(ts) AS (
  SELECT timestampset '{2000-01-01, 2000-01-03, 2000-01-05}' UNION
  SELECT timestampset '{2000-01-02, 2000-01-04, 2000-01-06}' UNION
  SELECT timestampset '{2000-01-01, 2000-01-02}'
)
SELECT tunion(ts) FROM times;
-- "{2000-01-01, 2000-01-02, 2000-01-03, 2000-01-04, 2000-01-05, 2000-01-06}"
WITH periods(ps) AS (
  SELECT periodset '{[2000-01-01, 2000-01-02], [2000-01-03, 2000-01-04]}' UNION
  SELECT periodset '{[2000-01-02, 2000-01-03], [2000-01-05, 2000-01-06]}' UNION
  SELECT periodset '{[2000-01-07, 2000-01-08]}'
)
SELECT tunion(ps) FROM periods;
-- "{[2000-01-01, 2000-01-04], [2000-01-05, 2000-01-06], [2000-01-07, 2000-01-08]}"