为什么 postgres 为相同的间隔值显示两种不同的格式?

Why postgres show two different format for same interval value?

我正在帮助 尝试更改间隔的格式。

from '01 day 22:10:37'  to  '46:10:37'

我给出了一个字符串操作的解决方案。但后来我发现 postgres 可以在两种不同的格式上显示相同的间隔。

SELECT '2016-01-27 08:51:02'::timestamp - '2016-01-25 10:40:25'::timestamp end_date,
       '46:10:37'::interval interval_date;

有趣的事情。有一个function做逆过程

 justify_hours('46:10:37'::interval) --> '1 day 22:10:37'

所以我想知道是否有直接的方法来解决这个问题。以及为什么相同的区间值有两个不同的结果。

pgAdmin 输出:

来自https://www.postgresql.org/docs/9.5/static/functions-formatting.html

to_char(interval) formats HH and HH12 as shown on a 12-hour clock, i.e. zero hours and 36 hours output as 12, while HH24 outputs the full hour value, which can exceed 23 for intervals.

来自https://www.postgresql.org/docs/8.2/static/functions-formatting.html

to_char(interval) formats HH and HH12 as hours in a single day, while HH24 can output hours exceeding a single day, e.g. >24.

我猜第一个使用基于减法结果的隐式格式,第二个使用基于输入的显式格式。

当一个间隔是两个时间戳之间的差异时,它总是被调整为小时(即它具有标准格式)。示例:

select
    '2015-01-01 13:0:0'::timestamp - '2014-01-01 23:0:0'::timestamp, --> 364 days 14:00:00
    '2015-01-01 13:0:0'::timestamp - '2014-01-01 03:0:0'::timestamp, --> 365 days 10:00:00
    '2015-01-01 13:0:0'::timestamp - '2015-01-01 03:0:0'::timestamp; --> 10:00:00

间隔的计算是在日期部分和时间部分分别执行的,因此它们可能会导致奇怪的格式。示例:

select 
    '2 day 1:00:00'::interval- '1 day 2:00:00'::interval,    --> 1 day -01:00:00 (!!)
    '2 day 100:00:00'::interval+ '1 day 60:00:00'::interval, --> 3 days 160:00:00
    '2 day 100:00:00'::interval- '2 day 60:00:00'::interval; --> 40:00:00

对于这种情况,Postgres 开发人员为 格式标准化提供了适当的功能:

select 
    justify_hours('1 day -01:00:00'),  --> 23:00:00
    justify_hours('3 days 160:00:00'), --> 9 days 16:00:00
    justify_hours('40:00:00');         --> 1 day 16:00:00

不过他们认为不需要反向操作。在 中,我提出了一个将时间间隔的日期部分转换为小时的函数。我认为它可以(有一些小的变化)某种 反向函数 for justify_hours():

create or replace function unjustify_hours(interval)
returns interval language sql as $$
    select format('%s:%s',
        (extract (epoch from ) / 3600)::int,
        to_char(, 'mi:ss'))::interval;
$$;

select 
    unjustify_hours('23:00:00'),        --> 23:00:00
    unjustify_hours('9 days 16:00:00'), --> 232:00:00
    unjustify_hours('1 day 16:00:00');  --> 40:00:00

函数to_char(interval, text)在这里没有帮助,因为

select 
    to_char(interval '23:00:00', 'hh24:mi:ss'),        --> 23:00:00
    to_char(interval '9 days 16:00:00', 'hh24:mi:ss'), --> 16:00:00 (!)
    to_char(interval '1 day 16:00:00',  'hh24:mi:ss'); --> 16:00:00 (!)

请注意,间隔可以通过多种方式正确格式化:

select 
    justify_hours('100:00:00'),        --> 4 days 04:00:00
    justify_hours('1 days 76:00:00'),  --> 4 days 04:00:00
    justify_hours('2 days 52:00:00'),  --> 4 days 04:00:00
    justify_hours('5 days -20:00:00'); --> 4 days 04:00:00

根据 the documentation:

According to the SQL standard all fields of an interval value must have the same sign, so a leading negative sign applies to all fields; for example the negative sign in the interval literal '-1 2:03:04' applies to both the days and hour/minute/second parts. PostgreSQL allows the fields to have different signs, and traditionally treats each field in the textual representation as independently signed, so that the hour/minute/second part is considered positive in this example. If IntervalStyle is set to sql_standard then a leading sign is considered to apply to all fields (but only if no additional signs appear). Otherwise the traditional PostgreSQL interpretation is used. To avoid ambiguity, it's recommended to attach an explicit sign to each field if any field is negative.

Internally interval values are stored as months, days, and seconds. This is done because the number of days in a month varies, and a day can have 23 or 25 hours if a daylight savings time adjustment is involved. The months and days fields are integers while the seconds field can store fractions. Because intervals are usually created from constant strings or timestamp subtraction, this storage method works well in most cases. Functions justify_days and justify_hours are available for adjusting days and hours that overflow their normal ranges.

坦率地说,klin 提供的答案不正确,因为将数字四舍五入为 int。如果它四舍五入,您将获得 +1 小时的返回值。正确的方法是截断它。这是我的版本:

create or replace function unjustify_hours(interval)
    returns interval language sql as $$
select format('%s:%s',
              trunc(extract (epoch from ) / 3600),
              to_char(, 'mi:ss'))::interval;
$$;

P.S。由于我是新用户并且还没有足够的声誉,我不能在答案下发表评论。