Project: YugabyteDB (App Definition and Development / Database)
File: typecasting-between-date-time-and-text.md

title: Typecasting between date-time values and text values [YSQL] headerTitle: Typecasting between date-time values and text values linkTitle: Typecasting between date-time and text-values description: Describes how to typecast date-time values to text values, and vice versa. [YSQL] menu: v2.18: identifier: typecasting-between-date-time-and-text parent: api-ysql-datatypes-datetime weight: 50 type: docs This section and its peer, , are placed, with respect to the sequential reading order of the overall date-time time data types section that the presents, before the main treatment of the because the code examples in those subsequent sections rely on typecasting between date-time values and text values and on setting the timezone, either as a session parameter or as part of a date-time expression with the at time zone operator. Typecasting between date-time values and text values, rather than using explicit built-in functions like tochar(), totimestamp(), or todate() allows the demonstration code to be uncluttered and easy to understand. However, as this section shows, the typecast semantics is sensitive to the current settings of the DateStyle and IntervalStyle_ session parameters. {{< note title="'Date-time' functions and operators in the PostgreSQL documentation." >}} PostgreSQL, and therefore YSQL, provide many functions and equivalent syntactical constructs that operate on, or produce, date-time values. These are documented in these dedicated sections within the main section and its children: . . . The following tochardemo() code example uses the totimestamp() function to produce a timestamptz value from a double precision value. The input represents the real number of seconds after, or before, the start of the Unix Epoch (a.k.a. the POSIX Epoch). See the Wikipedia article . The Unix Epoch begins at midnight on 1-January-1970 UTC_. Try this: ```plpgsql set datestyle = 'ISO, DMY'; set timezone = 'UTC'; with a as (select to_timestamp(0::double precision) as v) select pg_typeof(v) as "data type", v as "value" from a; ``` See the Wikipedia article . The explains the significance of the DateStyle session parameter. And the section explains the significance of the TimeZone session parameter. This is the result: ```output data type | value --+ timestamp with time zone | 1970-01-01 00:00:00+00 ``` The tochardemo() function casts the totimestamp() result to a plain timestamp_ value that represents what a wall-clock located on the Greenwich Meridian would read. The immediately following code example casts it to what a wall-clock in Paris would read. Try this: ```plpgsql set datestyle = 'ISO, DMY'; deallocate all; prepare stmt(timestamptz, text) as with a as (select $1 at time zone $2 as v) select pg_typeof(v) as "data type", v as "value" from a; execute stmt(to_timestamp(0::double precision), 'Europe/Paris'); ``` This is the result: ```output data type | value --+ timestamp without time zone | 1970-01-01 01:00:00 ``` The at time zone clause has function syntax equivalent: ```output timezone(timestamptz_value=>$1, timezone=>$2) ``` Create and execute the tochardemo() function like this: ```plpgsql drop function if exists tochardemo() cascade; create function tochardemo() returns table(z text) language plpgsql as $body$ declare -- Counted from midnight 1-Jan-1970 UTC. secs constant double precision not null := 94996411200.456789; t constant timestamp not null := to_timestamp(-secs) at time zone 'UTC'; fmt_1 constant text not null := 'TMDay / TMMonth'; fmt_2 constant text not null := 'TMDy dd-TMMon-yyyy

BC'; begin set lctime = 'enUS'; z := tochar(t, fmt1); return next; z := tochar(t, fmt2); return next; z := ''; return next; set lctime = 'itIT'; z := tochar(t, fmt1); return next; z := tochar(t, fmt2); return next; z := ''; return next; set lctime = 'fiFI'; z := tochar(t, fmt1); return next; z := tochar(t, fmt2); return next; z := ''; return next; end; $body$; select z from tochardemo(); ``` Because this uses the tochar() function, and not typecasting, the result is not sensitive to the DateStyle setting. PostgreSQL documents the various components, like 'TMDay', 'TMMonth', 'yyyy', dd, and so on that define the format that tochar() produces in . And because tochardemo() uses the at time zone operator, it is not sensitive to the current TimeZone setting. This is the result: ```output Friday / September Fri 07-Sep-1042 11:59:59.543216 BC Venerd / Settembre Ven 07-Set-1042 11:59:59.543216 BC Perjantai / Syyskuu Pe 07-Syy-1042 11:59:59.543216 BC ``` As you see, the lctime_ session parameter determines the national language that is used for the spellings of the short and long day and month names. The PostgreSQL documentation describes this parameter in the section . Notice that this section, in turn, references the section . In short, a setting like 'fiFI'_ is operating-system-dependent and may, or may not, be available according to what local support files have been installed. You can see what's available on a Unix-like system with this shell command: ```output locale -a ``` The 'TM' prefix, used in the function tochardemo() above, is documented as "print localized day and month names based on lctime" and so it works only in the tochar() output direction and not in the totimestamp()_ input direction. This example makes the point without it: ```plpgsql -- Setting to something other than 'ISO, DMY', here, just to hint at the effect. set datestyle = 'German, DMY'; select to_timestamp( '07-09-1042 11:59:59.543216 BC', 'dd-mm-yyyy hh24:mi:ss.us BC') at time zone 'UTC'; ``` This is the result: ```output 07.09.1042 11:59:59.543216 BC ``` {{< /note >}} Approach One: You can write the name of the target data type after the to-be-typecast value using the notation exemplified by ::timestamptz. Try these examples: ```postgresql drop table if exists t cascade; create table t( c1 text primary key, c2 timestamptz not null, c3 timestamp not null, c4 date not null, c5 time not null, c6 interval not null); insert into t(c1, c2, c3, c4, c5, c6) values ( to_timestamp(1577200000) ::text, '2019-12-24 16:42:47 UTC' ::timestamptz, '2019-12-24 16:42:47' ::timestamp, '2019-12-24' ::date, '16:42:47' ::time, '2 years 1 month' ::interval); ``` The test silently succeeds. Approach Two: You can write the bare name of the target data type before the to-be-typecast value. Try these examples: ```plpgsql insert into t(c1, c2, c3, c4, c5, c6) values ( text (to_timestamp(1577300000)), timestamptz '2019-12-24 16:42:47 UTC', timestamp '2019-12-24 16:42:47', date '2019-12-24', time '16:42:47', interval '2 years 1 month'); ``` Again, the test silently succeeds. Notice that the parentheses are necessary for the text example. Try this: ```plpgsql select text to_timestamp(1577200000); ``` It causes this error: ```output 42601: syntax error at or near "(" ``` Approach One is used consistently throughout the whole of the section. See the PostgreSQL documentation section . The DateStyle session parameter determines the format of the ::text typecast of a date-time value. It also, but in a subtle fashion, determines how a text value is interpreted when it's typecast to a date-time value. It has two orthogonal components: the style and the

The style has these legal values: ```output ISO SQL PostgreSQL German ``` And the substyle has these legal values: ```output DMY (with the synonyms Euro and European) MDY (with the synonyms NonEuro, NonEuropean, and US) YMD ``` The components can be set together, like this: ```plpgsql set datestyle = 'PostgreSQL, YMD'; show datestyle; ``` This is the result: ```output Postgres, YMD ``` Or they can be set separately like this: ```plpgsql set datestyle = 'German'; set datestyle = 'DMY'; show datestyle; ``` This is the result: ```output German, DMY ``` Create the DateStyle demo like this: ```plpgsql drop table if exists results; create table results( datestyle text primary key, tstampastext text not null, tstamp timestamp); drop procedure if exists datestyle_demo() cascade; create procedure datestyle_demo() language plpgsql as $body$ declare -- Counted from midnight 1-Jan-1970 UTC. secs constant double precision not null := 94996411200.456789; t constant timestamp not null := to_timestamp(-secs) at time zone 'UTC'; set_datestyle constant text not null := $$set datestyle = '%s, %s'$$; d text not null := ''; s text not null := ''; d_shown text not null := ''; styles constant text[] not null := array['ISO', 'SQL', 'PostgreSQL', 'German']; substyles constant text[] not null := array['DMY', 'MDY', 'YMD']; begin foreach d in array styles loop foreach s in array substyles loop execute format(set_datestyle, d, s); show datestyle into d_shown; insert into results(datestyle, tstampastext) values (d_shown, t::text); end loop; end loop; end; $body$; call datestyle_demo(); -- Set the same datestyle for the ::timestamp typecast of all the different text representations. set datestyle = 'ISO, DMY'; update results set tstamp = tstampastext::timestamp; select datestyle, tstampastext, tstamp::text from results order by datestyle; ``` This is the result: ```output German, DMY | 07.09.1042 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC German, MDY | 07.09.1042 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC German, YMD | 07.09.1042 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC ISO, DMY | 1042-09-07 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC ISO, MDY | 1042-09-07 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC ISO, YMD | 1042-09-07 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC Postgres, DMY | Fri 07 Sep 11:59:59.543216 1042 BC | 1042-09-07 11:59:59.543216 BC Postgres, MDY | Fri Sep 07 11:59:59.543216 1042 BC | 1042-09-07 11:59:59.543216 BC Postgres, YMD | Fri Sep 07 11:59:59.543216 1042 BC | 1042-09-07 11:59:59.543216 BC SQL, DMY | 07/09/1042 11:59:59.543216 BC | 1042-09-07 11:59:59.543216 BC SQL, MDY | 09/07/1042 11:59:59.543216 BC | 1042-07-09 11:59:59.543216 BC SQL, YMD | 09/07/1042 11:59:59.543216 BC | 1042-07-09 11:59:59.543216 BC ``` The blank lines were added manually to improve the readability. Notice the following: For each of the four different style values, the 'YMD' substyle has the identical effect as does the 'MDY' substyle. It is therefore pointless, and directly confusing, to use the 'YMD' substyle. Yugabyte recommends that you simply avoid doing this. For the two style values 'ISO' and German', the 'MDY' substyle has the identical effect as does the 'DMY' substyle in both the ::text direction and the ::timestamp direction. Yugabyte recommends that you always use the 'DMY' substyle in this scenario because this corresponds to the order that is actually produced. For the two style values 'Postgres' and SQL', the 'MDY' substyle has the effect that the mnemonic suggests in the ::text direction. However, for the Postgres style, it has no effect in the ::timestamp

This is the only feasible behavior because the Postgres style renders the day numerically and the month alphabeticallyso it's impossible to take 'Sep' as a day, even though '07' can be taken as a month. In contrast, because the 'SQL' style renders both the day and the month numerically, it's impossible to interpret '07/09/1042' and '09/07/1042' reliably unless 'DMY' or 'MDY' specify the rule. Try this: ```plpgsql -- Test One. set datestyle = 'SQL, MDY'; select 'The American exceptionalism way: '||to_char('07/09/2000 12:00:00'::timestamp, 'Mon-dd-yyyy'); ``` This is the result: ```output The American exceptionalism way: Jul-09-2000 ``` Now try it the other way round like this: ```plpgsql -- Test Two. set datestyle = 'SQL, DMY'; select 'The sensible way: '||to_char('07/09/2000 12:00:00'::timestamp, 'dd-Mon-yyyy'); ``` The result is now this: ```output The sensible way: 07-Sep-2000 ``` The operand of the ::timestamp typecast is spelled the same in Test One as it is in Test Two. But the resulting dates are differentin the famously confusing way. Even when a nominal month has an illegal number like '19', and this could be used for automatic disambiguation, the 'DMY' or 'MDY' substyle is taken as a non-negotiable directive. Try this: ```postgresql -- Test Three. set datestyle = 'SQL, MDY'; select to_char('19/09/2000 12:00:00'::timestamp, 'dd-mm-yyyy'); -- Test Four. set datestyle = 'SQL, DMY'; select to_char('07/19/2000 12:00:00'::timestamp, 'dd-mm-yyyy'); ``` Each of Test Three and Test Four produces the same error: ```output 22008: date/time field value out of range ... ``` {{< tip title="Never rely on typecasting between 'text' and 'date-time' values unless you set 'DateStyle' explicitly." >}} Yugabyte recommends that application code should convert between text values and date-time values using explicit conversion functions that use a format specification. You might be reading from a file that client-side code ingests that simply comes with a non-negotiable pre-determined format. Or you might be processing human input that comes from a UI that allows the user to choose the date-time format from a list. To produce a timestamptz value, use totimestamp(text, text)_. To produce a plain timestamp value, use totimestamp(text, text) with at time zone 'UTC'_. To produce a date value, use todate(text, text)_. To produce a plain time value, do what this code example models: ```plpgsql drop table if exists t cascade; create table t(k int primary key, t1 time not null, t2 time not null); insert into t(k, t1, t2) values(1, '00:00:00'::time, '00:00:00'::time); deallocate all; prepare s_1(text) as update t set t1 = to_timestamp($1, 'hh24:mi:ss')::time where k = 1; prepare s_2(text) as update t set t2 = to_timestamp($1, 'hh24:mi:ss')::time where k = 1; set timezone = 'UTC'; execute s_1('13:00:56'); set timezone = 'America/Los_Angeles'; execute s_2('13:00:56'); select (t1 = t2)::text from t where k = 1; ``` The result is true, showing that the method is insensitive to the current TimeZone setting. To convert a date-time value to a text value, use the appropriate tochar()_ overload as has been illustrated above. Of course, it's safe to use the typecasting approach in ad hoc tests where you can set DateStyle to whatever you want to without worrying that it might affect the behavior of existing application code that doesn't set the parameter explicitly. The same applies to small stand-alone code examples that support documentation. The YSQL documentation assumes that the DateStyle style component is set to 'ISO' unless it's explicitly set otherwise. (The substyle setting has no effect with the 'ISO'

{{< /tip >}} The IntervalStyle session parameter controls the format of the result of the ::text typecast operator on an interval value. It has no effect on the outcome of the ::interval typecast operator on a text value. There are just four legal choices. It's easy to see the list by making a deliberate error: ```postgresql set intervalstyle = 'oops, I did a typo'; ``` This is the result: ```output ERROR: 22023: invalid value for parameter "intervalstyle": "oops, I did a typo" HINT: Available values: postgres, postgresverbose, sqlstandard, iso_8601. ``` The IntervalStyle demo is a straight copy, paste, and massage derivative of the DateStyle demo. Create it like this: ```postgresql drop table if exists results; create table results( intervalstyle text primary key, iastext text not null, i interval); drop procedure if exists intervalstyle_demo() cascade; create procedure intervalstyle_demo() language plpgsql as $body$ declare i constant interval not null := make_interval( years => 1, months => 2, days => 3, hours => 4, mins => 5, secs => 6.345678); set_intervalstyle constant text not null := $$set intervalstyle = '%s'$$; s text not null := ''; s_shown text not null := ''; styles constant text[] not null := array['postgres', 'postgresverbose', 'sqlstandard', 'iso_8601']; begin foreach s in array styles loop execute format(set_intervalstyle, s); show intervalstyle into s_shown; insert into results(intervalstyle, iastext) values (s_shown, i::text); end loop; end; $body$; call intervalstyle_demo(); -- Set the same intervalstyle for the ::interval typecast of all the different text representations. set intervalstyle = 'postgres'; update results set i = iastext::interval; select intervalstyle, iastext, i::text from results order by intervalstyle; ``` This is the result: ```output intervalstyle | iastext | i +--+-- iso_8601 | P1Y2M3DT4H5M6.345678S | 1 year 2 mons 3 days 04:05:06.345678 postgres | 1 year 2 mons 3 days 04:05:06.345678 | 1 year 2 mons 3 days 04:05:06.345678 postgres_verbose | @ 1 year 2 mons 3 days 4 hours 5 mins 6.345678 secs | 1 year 2 mons 3 days 04:05:06.345678 sql_standard | +1-2 +3 +4:05:06.345678 | 1 year 2 mons 3 days 04:05:06.345678 ``` The results are consistent with the fact that the IntervalStyle setting has no effect on the outcome of the ::interval typecast operator on a text value. This is because the syntax rules for each of the four different IntervalStyle settings allow automatic disambiguation. {{< tip title="Never rely on typecasting from 'interval' values to 'text' values unless you set 'IntervalStyle' explicitly." >}} Yugabyte recommends that application code should convert between text values and interval values using explicit conversion functions. The makeinterval() built-in function creates an interval value using explicitly specified values in the units that you preferlike years, days, hours, or weeks. Yugabyte recommends always using this approach and never using the ::interval_ typecast. This advice rests on ideas developed in the section . The recommended approach is formalized in the section . The extract SQL functionality lets you assign values like the years or days components of an interval value to dedicated destinations. This approach is used in the definition of , described in the section. Of course, it's safe to use the typecasting approach in ad hoc tests where you can set IntervalStyle to whatever you want to without worrying that it might affect the behavior of existing application code that doesn't set the parameter explicitly. The same applies to small stand-alone code examples that support documentation. The YSQL documentation assumes that IntervalStyle is set to 'postgres' unless it's explicitly set otherwise. {{< /tip >}}