Project: YugabyteDB (App Definition and Development / Database)
File: interval-utilities.md

title: User-defined interval utility functions [YSQL] headerTitle: User-defined interval utility functions linkTitle: Interval utility functions description: Presents copy-and-paste ready code to create utility functions that support learning about and using the interval data type. [YSQL] menu: v2.18: identifier: interval-utilities parent: type-interval weight: 100 type: docs {{< tip title="Download the '.zip' file to create the reusable code that supports the pedagogy of the overall 'date-time' major section." >}} Download and install the code as the instructions explain. The code that this page presents is included in the kit. {{< /tip >}} The code presented on this page defines two user-defined types, intervalparameterizationt and intervalmmddsst. They model, respectively, the conventional parameterization of an interval value as a tuple. Together with the interval data type itself, this makes three typesand therefore three possible pairs and six mutual transformation functions. Here is the summary: ```output type intervalparameterizationt as( type intervalmmddsst as( yy double precision, mm double precision, dd double precision, mm int, dd int, ss double precision(1000,6)) hh double precision, mi double precision, ss double precision) interval <-> intervalparameterizationt intervalmmddsst <-> intervalparameterizationt function intervalvalue( function intervalmmddss( p in intervalparameterizationt p in intervalparameterizationt) returns interval returns intervalmmddsst function parameterization( function parameterization( i in interval) i in intervalmmddsst) returns intervalparameterizationt returns intervalparameterizationt interval <> intervalmmddsst function intervalvalue( function intervalmmddss( i in intervalmmddsst) i in interval) returns interval returns intervalmmddsst ``` The code that the remainder of this section presents defines the intervalparameterizationt and intervalmmddsst types, and all but one of the six mutual transformation functions. The design of the five functions that are shown here is straightforwardbut it does depend on knowing that the internal representation of an interval value is a tuple that uses four-byte integers to record the months and days fields and an eight-byte integer to record the seconds field as a microseconds value. The section defines and tests the function that the present page doesn't show: function intervalmmddss(intervalparameterizationt). It implements, using PL/pgSQL, the rather complex algorithm that PostgreSQL, and therefore YSQL, use in their C code to transform a value, presented as a text literal, to the internal interval_ representation. Here's an example of such a text literal: ```output ' -9.123456 years, 18.123456 months, -700.123456 days, 97.123456 hours, -86.123456 minutes, 75.123456 seconds ' ``` PostgreSQL defines two fundamentally different data type families for representing real numbers and for supporting arithmetic using their values. Of course, YSQL inherits this regime. The PostgreSQL documentation uses the terms and for these two kinds of data type. The first kind is exemplified by the data type numeric. And the second kind is exemplified by double precision. There are other variants in each class. But the entire section uses only these two (and uses only the unconstraint numeric variant). Moreover, it uses numeric only under duress. For example, you can easily round a numeric value to some desired number of decimal places like this: ```plpgsql select round(12.3456789::numeric, 3::int); ``` It produces the result 12.346. Try the corresponding operation on a double precision value: ```plpgsql with c as( select

precision as v) select round(v, 3::int) from c; ``` It causes the 42883 error: ```output function round(double precision, integer) does not exist ``` And the hint tells you that you might need to do some typecasting, like this: ```plpgsql with c as( select 12.3456789::double precision as v) select round(v::numeric, 3::int)::double precision from c; ``` You'll see some examples of typecasting like this throughout the overall date-time major section. Here, briefly, is how the two kinds of real number representation differ: The numeric data type supports values that have a vast number of significant digits and very high accuracy. This is achieved by using an explicit software representation that differs from anything that underlying hardware might use. As a consequence, arithmetic that uses this representation has to be done using algorithms implemented ordinarily in softwarein the C code of the PostgreSQL implementation that YSQL inherits "as is". The double precision data type is essentially a direct exposure of the hardware representation and the corresponding hardware implementation of arithmetic that is specified by IEEE Standard 754 for Binary Floating-Point Arithmetic for double precision values. (The native scheme is augmented in software to handle nulls.) You can guess that, in general, the representation of a double precision value uses less space than that of a numeric value and that double precision arithmetic is faster than numeric arithmetic. This is probably why many SQL built-in functions, and certainly those that are relevant for the date-time data types, use double precision to deal with real number values. Here are four examples: The secs input formal parameter for makeinterval() is double precision_. The sec input formal parameter for maketimestamp() and maketimestamptz() is double precision. The anonymous input formal parameter for the single-parameter overload of totimestamp() is double precision_. All of the functions in the extract() family return a double precision value. For this reason, the user-defined interval utility functions that this page presents use double precision, and never numeric, for real number values. Notice that equality comparison, using the native `=` operator, between real number values does bring a risk of evaluating to false when you expect it to be true because, of course, of the effect of rounding errors. You can often use a simple equality test to confirm that two numeric values that you reason ought to be the same are indeed this. This is due to the data type's capacity for enormous precision and accuracy. The corresponding test that uses the double precision overload for `=` for a pair of putatively identical values will fail more often because the data type has a smaller capacity for precision and a corresponding intrinsic inaccuracy. However, you must accept that the native equality test, for values of both the numeric and the double precision data types, is unreliable. There's no native solution for this dilemma. But it's easy to implement your own operator. This is what does. You can easily use it as a model for a (numeric, numeric) overload should you need this. In the context of the date-time data types, double precision values always represent seconds. And the internal representations record these only with microsecond precision. It's therefore good enough to test for equality using a 0.1 microsecond

Create the function thus: ```plpgsql drop function if exists approx_equals(double precision, double precision) cascade; create function approx_equals(v1 in double precision, v2 in double precision) returns boolean language plpgsql as $body$ declare microseconds_diff constant double precision not null := abs(v1 - v2); pointonemicroseconds constant double precision not null := 0.0000001; eq constant boolean not null := microsecondsdiff < pointone_microseconds; begin return eq; end; $body$; ``` And map it to the `~=` operator thus: ```plpgsql drop operator if exists ~= (double precision, double precision) cascade; create operator ~= ( leftarg = double precision, rightarg = double precision, procedure = approx_equals); ``` Here's a simple test: ```plpgsql with c1 as( select 10::double precision as base, 234.567::double precision as v), c2 as( select base, v, log(v) as log_v from c1), c3 as( select v as orig, base^log_v as recovered from c2) select to_char(orig, '9999.999999999999999') as orig, to_char(recovered, '9999.999999999999999') as recovered, (recovered = orig)::text as native_equals, (recovered ~= orig)::text as approx_equals from c3; ``` This is the result: ```output orig | recovered | nativeequals | approxequals --+--++ 234.567000000000 | 234.567000000000 | false | true ``` It's rather strange that the tochar() renditions of the internally represented double precision values that the native `=` operator shows to be different are indistinguishable. Notice that the tochar() format mask asks for fifteen decimal digits but only twelve are rendered. This is a feature of tochar()_: if you ask to see more decimal digits than are supported, then you silently see the maximum supported number. The outcome of this test only serves to emphasize the importance of creating your own user-defined equality operator for the comparison of real number values. When you define an interval value using either the ::interval typecast of a text literal or the makeinterval() built-in function, you specify values for some or all of years, months, days, hours, minutes, and seconds. Try this, using the ::interval_ typecast approach: ```plpgsql select '5 years 4 months' ::interval as i1, '40 days' ::interval as i2, '9 hours 30 minutes 45 seconds' ::interval as i3; ``` This is the result: ```output i1 | i2 | i3 -++- 5 years 4 mons | 40 days | 09:30:45 ``` And try this, using the makeinterval()_ approach: ```plpgsql select make_interval(years=>5, months=>4) as i1, make_interval(days=>40) as i2, make_interval(hours=>9, mins=>30, secs=>45) as i3; ``` It produces the same result as using the ::interval typecast approach. Notice that the ::interval typecast approach allows real number values for each of the six parameters. In contrast, the makeinterval() function defines all of its formal parameters except for secs with the data type integer, and It defines secs with the data type double precision_. Each approach allows other parameters to specify, for example, centuries, weeks, or microseconds. But the six parameters shown above are the most-commonly used. They are jointly more than sufficient to define non-zero values for each of the three fields of the internal representation. The bare minimum that gets an obviously predictable result is just months, days, and seconds. (See the section .) Omitting one of the parameters has the same effect as specifying zero for it. Create the type intervalparameterizationt to represent this six-field

```plpgsql drop type if exists intervalparameterizationt cascade; create type intervalparameterizationt as( yy double precision, mm double precision, dd double precision, hh double precision, mi double precision, ss double precision); ``` Other functions, below, have a single formal parameter with data type intervalparameterizationt or are defined to return an instance of that type. You can't define default values for the fields of a user-defined type. To avoid verbose code when you want to specify non-zero values for only some, or especially just one, of the six fields, the helper function intervalparameterization()_ is defined with default values for its corresponding six formal parameters. ```plpgsql drop function if exists interval_parameterization( double precision, double precision, double precision, double precision, double precision, double precision); create function interval_parameterization( yy in double precision default 0, mm in double precision default 0, dd in double precision default 0, hh in double precision default 0, mi in double precision default 0, ss in double precision default 0) returns intervalparameterizationt language plpgsql as $body$ declare ok constant boolean := (yy is not null) and (mm is not null) and (dd is not null) and (hh is not null) and (mi is not null) and (ss is not null); p intervalparameterizationt not null := (yy, mm, dd, hh, mi, ss)::intervalparameterizationt; begin assert ok, 'No argument, when provided, may be null'; return p; end; $body$; ``` Test it like this: ```plpgsql select interval_parameterization(yy=>5, mm=>6)::text; ``` This is the result: ```output interval_parameterization (5,6,0,0,0,0) ``` This function constructs an interval value from an intervalparameterizationt instance. Create it thus: ```plpgsql drop function if exists intervalvalue(intervalparameterization_t) cascade; create function intervalvalue(p in intervalparameterization_t) returns interval language plpgsql as $body$ declare yy constant interval not null := p.yy::text ||' years'; mm constant interval not null := p.mm::text ||' months'; dd constant interval not null := p.dd::text ||' days'; hh constant interval not null := p.hh::text ||' hours'; mi constant interval not null := p.mi::text ||' minutes'; ss constant interval not null := p.ss::text ||' seconds'; begin return yy + mm + dd + hh + mi + ss; end; $body$; ``` It uses the ::interval typecast approach rather than the makeinterval()_ approach because the pedagogy of the section depends on being able to specify real number values for each of the six parameters. Test it like this: ```plpgsql select intervalvalue(intervalparameterization(mm=>2.345, dd=>3.456))::text; ``` This is the result: ```output interval_value 2 mons 13 days 19:20:38.4 ``` Create the function thus: ```plpgsql drop function if exists parameterization(interval) cascade; create function parameterization(i in interval) returns intervalparameterizationt language plpgsql as $body$ declare -- All but the seconds value are always integral. yy double precision not null := round(extract(years from i)); mm double precision not null := round(extract(months from i)); dd double precision not null := round(extract(days from i)); hh double precision not null := round(extract(hours from i)); mi double precision not null := round(extract(minutes from i)); ss double precision not null := extract(seconds from i); begin return (yy, mm, dd, hh, mi, ss)::intervalparameterizationt; end; $body$; ``` Test it like this: ```plpgsql select parameterization('2 months 13 days 19:20:38.4'::interval)::text; ``` This is the result: ```output parameterization -- (0,2,13,19,20,38.4) ``` Create a function to test a pair of "intervalparameterizationt values" for equality. The function parameterization(i in interval) uses extract() and this accesses the internal representation. There's a risk of rounding errors

For example, when the ss field corresponds 04:48:00, this might be extracted as 04:47,59.99999999.... The approxequals()_ implementation needs to accommodate this. ```plpgsql drop function if exists approxequals(intervalparameterizationt, intervalparameterization_t) cascade; create function approxequals(p1in in intervalparameterizationt, p2in in intervalparameterization_t) returns boolean language plpgsql as $body$ declare -- There's no need (for the present pedagogical purpose) to extend this to -- handle NULL inputs. It would be simple to do this. p1 constant intervalparameterizationt not null := p1_in; p2 constant intervalparameterizationt not null := p2_in; mons1 constant double precision not null := p1.yy*12.0 + p1.mm; mons2 constant double precision not null := p2.yy*12.0 + p2.mm; secs1 constant double precision not null := p1.hh60.060.0 + p1.mi*60.0 + p1.ss; secs2 constant double precision not null := p2.hh60.060.0 + p2.mi*60.0 + p2.ss; eq constant boolean not null := (mons1 ~= mons2) and (p1.dd ~= p2.dd) and (secs1 ~= secs2); begin return eq; end; $body$; ``` And map it to the `~=` operator thus: ```plpgsql drop operator if exists ~= (intervalparameterizationt, intervalparameterizationt) cascade; create operator ~= ( leftarg = intervalparameterizationt, rightarg = intervalparameterizationt, procedure = approx_equals); ``` The type intervalmmddsst models the internal representation of an interval value. It's central to the pedagogy of the sections and . It is also used to implement the user-defined domains described in the section . ```plpgsql drop type if exists intervalmmddsst cascade; create type intervalmmddsst as( mm int, dd int, ss double precision); ``` Create a function to create an intervalmmddsst value from an interval value: ```plpgsql drop function if exists intervalmmdd_ss(interval) cascade; create function intervalmmdd_ss(i in interval) returns intervalmmddsst language plpgsql as $body$ begin if i is null then return null; else declare mm constant int not null := (extract(years from i))*12 + extract(months from i); dd constant int not null := extract(days from i); ss constant double precision not null := (extract(hours from i))6060 + extract(minutes from i)*60 + extract(seconds from i); begin return (mm, dd, ss); end; end if; end; $body$; ``` Test it like this: ```plpgsql select intervalmmdd_ss('2.345 months 3.456 days 19:20:38.423456'::interval)::text; ``` This is the result: ```output (2,13,139276.823456) ``` The seconds field of the is declared as double precision. This means that the native `=` test on a pair of values of this type (it uses a nave field-by-field equality comparison) will be subject to the underlying challenge of . This implies the need for a user-defined approximate comparison operator for intervalmmddsst values. Create the function thus using the user-defined : ```plpgsql drop function if exists approxequals(intervalmmddsst, intervalmmddss_t) cascade; create function approxequals(i1in in intervalmmddsst, i2in in intervalmmddss_t) returns boolean language plpgsql as $body$ declare -- There's no need (for the present pedagogical purpose) to extend this to -- handle NULL inputs. It would be simple to do this. i1 constant intervalmmddsst not null := i1_in; i2 constant intervalmmddsst not null := i2_in; eq constant boolean not null := (i1.mm = i2.mm) and (i1.dd = i2.dd) and (i1.ss ~=

begin return eq; end; $body$; ``` And map it to the `~=` operator thus: ```plpgsql drop operator if exists ~= (intervalmmddsst, intervalmmddsst) cascade; create operator ~= ( leftarg = intervalmmddsst, rightarg = intervalmmddsst, procedure = approx_equals); ``` Create a function to create an interval value from an intervalmmddsst instance: ```plpgsql drop function if exists intervalvalue(intervalmmddss_t) cascade; create function intervalvalue(i in intervalmmddss_t) returns interval language plpgsql as $body$ begin return make_interval(months=>i.mm, days=>i.dd, secs=>i.ss); end; $body$; ``` Test it like this: ```plpgsql select intervalvalue((2,13,139276.823456)::intervalmmddss_t)::text; ``` This is the result: ```output 2 mons 13 days 38:41:16.823456 ``` Note: The implementation of the function parameterization(intervalmmddsst) documents the algorithm for calculating the text typecast of an interval value (or equivalently the result of using the extract functions ). Here is an example of using extract: ```plpgsql select extract(hours from '123 months 234 days 34567.123456 seconds'::interval) as "extracted hours"; ``` This is the result: ```output extracted hours -- 9 ``` The extract functions are used to implement the function parameterization() shown above. Create the function parameterization(intervalmmddsst) thus: ```plpgsql drop function if exists parameterization(intervalmmddsst) cascade; create function parameterization(i in intervalmmddsst) returns intervalparameterizationt language plpgsql as $body$ declare yy constant int := trunc(i.mm/12); mm constant int := i.mm - yy*12; dd constant int := i.dd; hh constant int := trunc(i.ss/(60.0*60.0)); mi constant int := trunc((i.ss - hh60.060)/60.0); ss constant double precision := i.ss - (hh60.060.0 + mi*60.0); begin return (yy, mm, dd, hh, mi, ss)::intervalparameterizationt; end; $body$; ``` Test it like this: ```plpgsql select parameterization((123, 234, 34567.123456)::intervalmmddsst)::text; ``` This is the result: ```output (10,3,234,9,36,7.12345600000117) ``` Notice the apparent inaccuracy brought by the use of double precision. This is inevitable. Compare the result with that from using parameterization(interval) on an actual interval value: ```plpgsql select parameterization('123 months 234 days 34567.123456 seconds'::interval)::text; ``` This is the result: ```output (10,3,234,9,36,7.123456) ``` This emphasizes the need for the user-defined values. Do this: ```plpgsql select ( parameterization((123, 234, 34567.123456)::intervalmmddsst) ~= parameterization('123 months 234 days 34567.123456 seconds'::interval) )::text; ``` The result is true. This function is discussed in the section . And the section relies on this function to model the implementation of the comparison algorithm. The semantics of the justifyinterval() built-in function (explained in the section suggests a scheme to map an interval value (a vector with three components) to a real number. The same rule of thumb that justifyinterval() uses to normalize the ss and the dd fields of the internal representation (24 hours is deemed to be the same as 1 day and 30 days is deemed to be the same as 1 month) can be used to compute a number of seconds from an interval value. You probably won't use this function in application code. But it's used in the section to model the implementation of the comparison algorithm. It also allows you to understand how it's possible to use an order by predicate with an interval table column (like, for example, pgtimezonenames.utcoffset_) and to create an index on such a column. Create the justifiedseconds()_ thus: ```plpgsql drop function if exists justified_seconds(interval) cascade; create function justified_seconds(i in interval) returns double precision language plpgsql as $body$ begin if i is null then return null; else declare secsprday constant double precision not null := 246060; secsprmonth constant double precision not null := secsprday*30; r constant intervalmmddsst not null := intervalmmdd_ss(i); ss constant double precision not null := r.ss + r.ddsecs_pr_day + r.mmsecsprmonth; rj constant intervalmmddsst not null := intervalmmddss(justifyinterval(i)); ssj constant double precision not null := rj.ss + rj.ddsecs_pr_day +

begin assert ss = ssj, 'justified_seconds(): assert failed'; return ss; end; end if; end; $body$; ``` Notice the assert statement that tests that the computed number of seconds from an "as is" interval value is identical to the computed number of seconds from an interval value that's normalized using justifyinterval(). You might argue, correctly, that what the assert statement tests can be proved algebraically. The assert_ is used, here, just as a documentation device. The in the section demonstrates its result for a selection of input values. {{< tip title="Use the 'strict equals' operator, '==', rather than the native '=', to compare 'interval' values." >}} Yugabyte staff members have carefully considered the practical value of the native interval-interval overload of the `=` operator that YSQL inherits from PostgreSQL. They believe that the use-cases where the functionality will be useful are rareand that, rather, a "strict equals" notion, that requires pairwise equality of the individual fields of the of the interval values that are compared, will generally be more valuable. {{< /tip >}} See the section for the larger discussion on this topic. Create the strictequals()_ function thus: ```plpgsql drop function if exists strict_equals(interval, interval) cascade; create function strict_equals(i1 in interval, i2 in interval) returns boolean language plpgsql as $body$ begin if i1 is null or i2 is null then return null; else declare mmddss1 constant intervalmmddsst not null := intervalmmddss(i1); mmddss2 constant intervalmmddsst not null := intervalmmddss(i2); begin return mmddss1 ~= mmddss2; end; end if; end; $body$; ``` And map it to the `==` operator thus: ```plpgsql drop operator if exists == (interval, interval) cascade; create operator == ( leftarg = interval, rightarg = interval, procedure = strict_equals); ``` Strangely, there is no native plain timestamp equivalent of the totimestamp()_ function. But it's easy to write your own. Notice that a pair of functions cannot be overload-distinguished when they differ only in the return type. So your implementation of the missing functionality must have a new unique name. ```plpgsql drop function if exists totimestampwithout_tz(double precision); create function totimestampwithouttz(ssfrom_epoch in double precision) returns / plain / timestamp language plpgsql as $body$ declare currenttz text not null := currentsetting('TimeZone'); begin assert length(current_tz) > 0, 'undefined time zone'; set timezone = 'UTC'; declare ttz constant timestamptz := totimestamp(ssfromepoch); t constant timestamp := t_tz at time zone 'UTC'; begin -- Restore the saved time zone setting. execute 'set timezone = '''||current_tz||''''; return t; end; end; $body$; ``` Test it like this: ```plpgsql select totimestampwithout_tz(42123.456789); ``` This is the result: ```output 1970-01-01 11:42:03.456789 ``` Strangely, there is no native totime() function to transform some number of seconds from midnight to a time_ value. But it's easy to write your own. ```plpgsql drop function if exists to_time(double precision) cascade; -- mod() doesn't have an overload for "double precision" arguments. create function to_time(ss in double precision) returns time language plpgsql as $body$ declare -- Notice the ss value can be bigger than ssperday. ssperday constant numeric not null := 24.060.060.0; ssfrommidnight constant double precision not null := mod(ss::numeric, ssperday); t constant time not null := makeinterval(secs=>ssfrom_midnight)::time; begin return t; end; $body$; ``` Test it like this: ```plpgsql select to_time((296060 + 17*60)::double precision + 42.123456::double precision); ``` This is the result: ```output

``` The three functions shown below aren't used anywhere else in the documentation of the interval data type. But they were used extensively for ad hoc experiments and as a tracing tool while the code examples that this overall interval section uses were being developed. You might find them useful for the same reason. The function intervalmmddssastext(interval) returns the same information that the function intervalmmddss(interval) returns, but as a text literal for the interval value so that it can be used directly in any context that needs this. Create it thus: ```plpgsql drop function if exists intervalmmddssas_text(interval) cascade; create function intervalmmddssas_text(i in interval) returns text language plpgsql as $body$ declare mmddss constant intervalmmddsst not null := intervalmmdd_ss(i); sstext constant text not null := ltrim(tochar(mmddss.ss, '9999999999990.999999')); begin return mmddss.mm::text||' months ' || mmddss.dd::text||' days ' || ss_text ||' seconds' ; end; $body$; ``` Test it like this: ```plpgsql select intervalmmddssas_text('2 years 3 months 999 days 77 hours 53 min 17.123456 secs'::interval); ``` This is the result: ```output 27 months 999 days 280397.123456 seconds ``` The function parameterizationastext(interval) returns the same information that the function parameterization(interval) returns, but as a text literal for the interval value so that it can be used directly in any context that needs this. Create it thus: ```plpgsql drop function if exists parameterizationastext(interval) cascade; create function parameterizationastext(i in interval) returns text language plpgsql as $body$ declare p constant intervalparameterizationt not null := parameterization(i); sstext constant text not null := ltrim(tochar(p.ss, '9999999999990.999999')); begin return p.yy::text||' years ' || p.mm::text||' months ' || p.dd::text||' days ' || p.hh::text||' hours ' || p.mi::text||' minutes ' || ss_text ||' seconds'; end; $body$; ``` Test it like this: ```plpgsql select parameterizationastext('42 months 999 days 77.12345 hours'::interval); ``` This is the result: ```output 3 years 6 months 999 days 77 hours 7 minutes 24.420000 seconds ``` Just as the function parameterizationastext(interval) does for an interval value, it can be useful, too, to present the same information that the function parameterization(intervalmmddsst) returns, again as a text literal for the interval value. The function parameterizationastext(intervalmmddsst) does this. Create it thus: ```plpgsql drop function if exists parameterizationastext(intervalmmddsst) cascade; create function parameterizationastext(i in intervalmmddsst) returns text language plpgsql as $body$ declare p constant intervalparameterizationt not null := parameterization(i); sstext constant text not null := ltrim(tochar(p.ss, '9999999999990.999999')); begin return p.yy::text||' years ' || p.mm::text||' months ' || p.dd::text||' days ' || p.hh::text||' hours ' || p.mi::text||' minutes ' || ss_text ||' seconds'; end; $body$; ``` Test it like this: ```plpgsql select parameterizationastext((77,13,139276.800000)::intervalmmddsst)::text; ``` This is the result: ```output 6 years 5 months 13 days 38 hours 41 minutes 16.800000 seconds ``` Compare this with the output of function parameterizationastext(interval) for the same input: ```plpgsql select parameterizationastext('77 months, 13 days, 139276.800000 secs'::interval )::text; ``` The result is identical. All this vividly makes the point that a particular actual interval value can be created using many different variants of the corresponding text literal. Try this: ```plpgsql select ( '2.345 months 3.456 days 19:20:38.4'::interval == '2 mons 13 days 38:41:16.8'::interval and '2.345 months 3.456 days 19:20:38.4'::interval == '2 months 13 days 139276.800000 seconds'::interval ) ::text as "all the same"; ``` This is the result: ```output all the same -- true ```