Показать следующий получасовой момент времени в функции Postgres

Я пытаюсь создать функцию, которая показывает время следующего получаса.

Поэтому, когда текущее время 13:40, я хочу, чтобы оно показывало 14:00, а не 13:30.

То, что я создал, выполняет свою работу, но в ближайшие полчаса, а не в ближайшее время:

CREATE OR REPLACE FUNCTION round_timestamp(
         ts timestamptz
         ,round_secs int
        ) RETURNS timestamptz AS $$
        DECLARE
                _mystamp timestamp;
                _round_secs decimal;
        BEGIN
    _round_secs := round_secs::decimal;
    _mystamp := timestamptz 'epoch' 
            + ROUND((EXTRACT(EPOCH FROM ts))::int / _round_secs) * _round_secs
            * INTERVAL '1 second';
    RETURN _mystamp;
END; $$ LANGUAGE plpgsql IMMUTABLE;

Любые идеи о том, как заставить эту работу отображать ближайший получасовой интервал в будущем?


person mallix    schedule 05.12.2014    source источник


Ответы (4)


Проще говоря, добавьте 1 к эпохе с округлением в меньшую сторону, прежде чем масштабировать ее обратно до отметки времени:

CREATE OR REPLACE FUNCTION round_timestamp(ts timestamptz, round_secs int)
    RETURNS timestamptz AS $$
BEGIN
    RETURN timestamptz 'epoch' 
            + (ROUND((EXTRACT(EPOCH FROM ts))::int / round_secs) + 1) * round_secs
            * INTERVAL '1 second';
END; $$ LANGUAGE plpgsql IMMUTABLE;

В локальных переменных нет необходимости.

person Patrick    schedule 05.12.2014
comment
если мое текущее время 15:25, делает ли эта функция 15:30? - person mallix; 05.12.2014
comment
Запустите его на своем сервере. Если вы укажете 1800 для round_secs, то да, будет. - person Patrick; 05.12.2014

Чтобы избежать ошибок при использовании арифметики эпох и с плавающей запятой, вы можете положиться на арифметику даты, с дополнительным преимуществом, которое проясняет, что происходит:

create or replace function round_tstz(ts timestamptz)
  returns timestamptz
as $$
  select date_trunc('hour', $1) +
         -- what hour will it be in 30 min?
         case date_trunc('hour', $1 + interval '30 min')
         -- the same: round to next half hour
         when date_trunc('hour', $1) then interval '30 min'
         -- not the same: round to next hour
         else interval '1 hour'
         end;
$$ language sql stable;

# select now()::timestamptz(0);
          now           
------------------------
 2014-12-05 14:34:30+01
(1 row)

# select round_tstz(now()), round_tstz(now() + interval '30 min');
       round_tstz       |       round_tstz       
------------------------+------------------------
 2014-12-05 15:00:00+01 | 2014-12-05 15:30:00+01
(1 row)
person Denis de Bernardy    schedule 05.12.2014
comment
Я сомневаюсь, что этот подход будет иметь большое значение, учитывая, что сама метка времени является двойной. У него может быть даже худшая производительность из-за всей логики, которая входит в timestamp_trunc(), по сравнению с простым извлечением эпох и (часто) оптимизированным для оборудования округлением. - person Patrick; 05.12.2014
comment
@Patrick: Может быть ... Я бы предположил, что логика в timestamp_trunc (), которая находится в C, и которая будет оцениваться только дважды, потому что оптимизатор может пропустить один из вызовов функции, потому что он стабилен, стоит немного быстрее, чем выполнение всей арифметики с плавающей запятой в результате того, что он не был вызван (включая деления и многое другое). Во всяком случае, я не думаю, что это имеет большое значение в конце дня: я думаю, важно то, что предлагаемый мной подход во много раз более читабелен и понятен. Но, в конце концов, это всего лишь мое собственное мнение. :-) - person Denis de Bernardy; 05.12.2014
comment
Читая ваш пример, это только начало вашего дня ... Что вы делаете на SO в 4:26 утра ??? Помимо рабочих часов, все варианты timestamp_trunc () начинаются с большого оператора case для оценки интересующего периода; 'hour' в вашем примере. В задаче ОП лучше сразу перейти ко второй, не задумываясь со стороны PG. - person Patrick; 05.12.2014
comment
@ Патрик: Хе-хе. Сервер моего ноутбука был неправильно настроен. :-D В любом случае, я не проверял исходный код, поэтому могу только поверить вам на слово. Я все еще думаю, что лучше писать код, как если бы следующий парень был психопатом с топором, который знает, где вы живете; как бы я ни старался быть открытым для извлечения эпохи и тому подобного, я нахожу, что мое собственное предложение или то, что только что опубликовал Купер, делают происходящее более интуитивным. - person Denis de Bernardy; 05.12.2014
comment
Да, ясность кода важна. Однако есть одна сложность: в то время как OP говорит о получасовых интервалах, его пример кода использует произвольное количество секунд для округления до. Поэтому, если ему нужно 1864 секунды, ваш код не может округляться до 31 минуты 4 секунды. Но это всего лишь анал с моей стороны. - person Patrick; 05.12.2014
comment
Хе-хе. У циника во мне есть представление о том, почему там мог быть ненужный параметр ... ;-) Кстати, это aclexplode() решение, которое я предлагал вам несколько месяцев назад, в конечном итоге сработало так, как вы надеялись? - person Denis de Bernardy; 05.12.2014
comment
Позвольте нам продолжить это обсуждение в чате. - person Patrick; 05.12.2014

Вы можете использовать ceil() (или ceiling()) и floor(), чтобы округлить и вниз соответственно.

Если ваша функция настолько проста, вы также можете записать ее в LANGUAGE SQL, что сделает ее более простой / читаемой (и планируемой парсером):

CREATE OR REPLACE FUNCTION round_timestamp(ts timestamptz, round_secs int)
  RETURNS timestamptz
  LANGUAGE SQL
  IMMUTABLE
AS $func$
SELECT timestamptz 'epoch'
  + ceiling(EXTRACT(EPOCH FROM ts) / round_secs)
  * round_secs
  * INTERVAL '1 second'
$func$;

SQLFiddle

person pozs    schedule 05.12.2014

К текущему количеству минут M в метке времени необходимо добавить X минут, где:

X = 30 - M% 30 - 30 * ((M% 30) = 0) :: int

Вы вычитаете остаток от деления до 30 из 30, а затем, только если этот остаток равен 0, вычитаете еще 30, чтобы значения: 00 и: 30 сохранялись и не округлялись в большую сторону.

Например, если время 14:11, то M = 11, вам нужно добавить 19 минут, чтобы достичь 14:30 (30-11% 30 = 19), для 14:47, M = 47, вам нужно добавить 13 минут до 15:00 (30 - 47% 30 = 13). Единственным исключением из этого правила является случай, когда остаток равен 0, то есть часы, такие как 14:30, 15:00 и т. Д. - и именно здесь последний член в формуле вступает в игру - вычитая произведение 30 * ( 0 или 1, в зависимости от остатка от деления до 30).

CREATE FUNCTION round_up(timestamptz) RETURNS timestamptz AS $$
  SELECT
    $1 + '1 minute'::interval * (
      30 - (EXTRACT('minute' FROM $1)::int % 30)
         - 30 * ((EXTRACT('minute' FROM $1)::int % 30)=0)::int
    );
$$ LANGUAGE sql IMMUTABLE;
person Kouber Saparev    schedule 05.12.2014
comment
Поскольку timestamp имеет разрешение микросекунды, проблема округления значения в большую сторону сильно зависит от приложения, и я предполагаю, что это не имеет значения. - person Patrick; 05.12.2014