如何将时间四舍五入到任意时间间隔的上倍数?

Gaj*_*jus 5 postgresql datetime interval date-math

例子:

  • 如果当前时间为 2018-05-17 22:45:30 且所需时间间隔为INTERVAL '5 minute',则所需输出为 2018-05-17 22:50:00。
  • 如果当前时间为 2018-05-17 22:45:30 且所需时间间隔为INTERVAL '10 minute',则所需输出为 2018-05-17 22:50:00。
  • 如果当前时间为 2018-05-17 22:45:30 并且所需的时间间隔为INTERVAL '1 hour',则所需的输出为 2018-05-17 23:00:00。
  • 如果当前时间是 2018-05-17 22:45:30 并且期望的间隔是INTERVAL '1 day',那么期望的输出是 2018-05-18 00:00:00。

Erw*_*ter 9

假设数据类型timestamp. 一些细节是不同的datetimestamptz

任何时间间隔的通用解决方案都可以根据纪元值和整数除法进行截断。涵盖您的所有示例。

你的任务的特殊难度:你想要天花板,而不是地板(这更常见)。小心使用下限和上限以避免角落案例错误:您不想增加确切的地板值。(或者我假设。)

对于内置的常见时间间隔date_trunc()(例如1 hour1 day在您的示例中),您可以使用快捷方式。天数的定义取决于会话的时区设置timestamptz(但不与timestamp)。

一个自然的选择是使用ceil(). 在我的测试中有点,但更干净

简短演示

-- short demo
WITH t(ts) AS (SELECT timestamp '2018-05-17 22:45:30')  -- your input timestamp
SELECT t2.*
FROM  (SELECT *, ts - interval '1 microsecond' AS ts1 FROM t) t1 -- subtract min time interval 1 µs
     , LATERAL (
   VALUES
      ('input timestamp' , ts)
    , ('5 min' , to_timestamp(trunc(extract(epoch FROM ts1))::int / 300 * 300 + 300) AT TIME ZONE 'UTC')
    , ('10 min', to_timestamp(ceil (extract(epoch FROM ts)/ 600) * 600) AT TIME ZONE 'UTC') -- based on unaltered ts!
    , ('hour'  , date_trunc('hour', ts1) + interval '1 hour')
    , ('day'   , date_trunc('day' , ts1) + interval '1 day')
   ) t2(interval, ceil_ts);
Run Code Online (Sandbox Code Playgroud)
间隔| ceil_ts            
:-------------- | :------------------
输入时间戳 | 2018-05-17 22:45:30
5 分钟 | 2018-05-17 22:50:00
10 分钟 | 2018-05-17 22:50:00
小时 | 2018-05-17 23:00:00
日 | 2018-05-18 00:00:00

'5 min'计算的“技巧”是在截断之前减去1 µs的最小时间间隔,然后添加相应的时间间隔以有效获得上限EXTRACT()返回时间戳中的秒数,一个double precision小数位数到微秒的数字。我们需要,trunc()因为普通的 cast tointeger四舍五入,而我们需要truncate

这样我们就可以避免增加恰好落在上限的时间戳。不过,它有点脏,因为最小时间间隔是当前 Postgres 版本的实现细节。非常不可能改变,虽然。有关的:

'10分钟的计算是简单的ceil(),我们并不需要减去1微秒转移界限。清洁工。但ceil()在我的测试中稍微贵一些。

扩展测试用例

WITH t(id, ts) AS (
   VALUES
     (1, timestamp '2018-05-17 22:45:30')  -- your input timestamps here
   , (2, timestamp '2018-05-20 00:00:00')
   , (3, timestamp '2018-05-20 00:00:00.000001')
   )
SELECT *
FROM  (SELECT *, ts - interval '1 microsecond' AS ts1 FROM t) t1  -- subtract min time interval 1 µs
     , LATERAL (
   VALUES
      ('input timestamp' , ts)
    , ('5 min'  , to_timestamp(trunc(extract(epoch FROM ts1))::int / 300 * 300 + 300) AT TIME ZONE 'UTC')
    , ('10 min' , to_timestamp(ceil (extract(epoch FROM ts)/ 600) * 600) AT TIME ZONE 'UTC') -- based on unaltered ts!
    , ('hour'   , date_trunc('hour', ts1) + interval '1 hour')
    , ('day'    , date_trunc('day' , ts1) + interval '1 day')
    , ('alt_day', ts1::date + 1)
   ) t2(interval, ceil_ts)
ORDER  BY id;
Run Code Online (Sandbox Code Playgroud)
身份证 | ts | ts1 | 间隔| ceil_ts                   
-: | :------------------------- | :------------------------- | :-------------- | :-------------------------
 1 | 2018-05-17 22:45:30 | 2018-05-17 22:45:29.999999 | 输入时间戳 | 2018-05-17 22:45:30       
 1 | 2018-05-17 22:45:30 | 2018-05-17 22:45:29.999999 | 5 分钟 | 2018-05-17 22:50:00       
 1 | 2018-05-17 22:45:30 | 2018-05-17 22:45:29.999999 | 10 分钟 | 2018-05-17 22:50:00       
 1 | 2018-05-17 22:45:30 | 2018-05-17 22:45:29.999999 | 小时 | 2018-05-17 23:00:00       
 1 | 2018-05-17 22:45:30 | 2018-05-17 22:45:29.999999 | 日 | 2018-05-18 00:00:00       
 1 | 2018-05-17 22:45:30 | 2018-05-17 22:45:29.999999 | alt_day | 2018-05-18 00:00:00       
 2 | 2018-05-20 00:00:00 | 2018-05-19 23:59:59.999999 | 输入时间戳 | 2018-05-20 00:00:00       
 2 | 2018-05-20 00:00:00 | 2018-05-19 23:59:59.999999 | 5 分钟 | 2018-05-20 00:00:00       
 2 | 2018-05-20 00:00:00 | 2018-05-19 23:59:59.999999 | 10 分钟 | 2018-05-20 00:00:00       
 2 | 2018-05-20 00:00:00 | 2018-05-19 23:59:59.999999 | 小时 | 2018-05-20 00:00:00       
 2 | 2018-05-20 00:00:00 | 2018-05-19 23:59:59.999999 | 日 | 2018-05-20 00:00:00       
 2 | 2018-05-20 00:00:00 | 2018-05-19 23:59:59.999999 | alt_day | 2018-05-20 00:00:00       
 3 | 2018-05-20 00:00:00.000001 | 2018-05-20 00:00:00 | 输入时间戳 | 2018-05-20 00:00:00.000001
 3 | 2018-05-20 00:00:00.000001 | 2018-05-20 00:00:00 | 5 分钟 | 2018-05-20 00:05:00       
 3 | 2018-05-20 00:00:00.000001 | 2018-05-20 00:00:00 | 10 分钟 | 2018-05-20 00:10:00       
 3 | 2018-05-20 00:00:00.000001 | 2018-05-20 00:00:00 | 小时 | 2018-05-20 01:00:00       
 3 | 2018-05-20 00:00:00.000001 | 2018-05-20 00:00:00 | 日 | 2018-05-21 00:00:00       
 3 | 2018-05-20 00:00:00.000001 | 2018-05-20 00:00:00 | alt_day | 2018-05-21 00:00:00       

db<>在这里摆弄

我添加了一个全天的替代快捷方式:ts1::date + 1. 演员要date截断到整整一天,我们可以添加integer1至增加一天。

函数包装器

您后来透露您使用的是timestamptz,因此我们可以AT TIME ZONE从表达式中删除。

在我的测试中,声明函数STABLE产生了最佳性能,因为它允许函数内联。我本来希望IMMUTABLE是最好的,但该声明对允许内联的内容更为挑剔。有关的:

在我的测试中快一点:

CREATE OR REPLACE FUNCTION f_tstz_interval_ceiling2(_tstz timestamptz, _int_seconds int)
  RETURNS timestamptz AS
$func$   
SELECT to_timestamp(trunc(extract(epoch FROM ($1 - interval '1 microsecond')))::int / $2 * $2 + $2)
$func$  LANGUAGE sql STABLE;
Run Code Online (Sandbox Code Playgroud)

更清洁的国际海事组织:

CREATE OR REPLACE FUNCTION f_tstz_interval_ceiling1(_tstz timestamptz, _int_seconds int)
  RETURNS timestamptz AS
$func$   
SELECT to_timestamp(ceil(extract(epoch FROM $1) / $2) * $2)
$func$  LANGUAGE sql STABLE;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT f_tstz_interval_ceiling1(my_tstz, 600);  -- 600 = seconds in 10 min
Run Code Online (Sandbox Code Playgroud)

为方便起见,您可以使用替代方法重载每个函数,将 ainterval作为$2

CREATE OR REPLACE FUNCTION f_tstz_interval_ceiling1(_tstz timestamptz, _interval interval)
  RETURNS timestamptz LANGUAGE sql STABLE AS
'SELECT f_tstz_interval_ceiling1($1, extract(epoch FROM $2)::int)';
Run Code Online (Sandbox Code Playgroud)

只需用提取的秒数调用第一个版本。然后你也可以调用:

SELECT f_tstz_interval_ceiling1(my_tstz, interval '10 min');
Run Code Online (Sandbox Code Playgroud)