有没有办法在添加间隔的同时保留一个月的最后一天?

Is there a way to preserve last day of month while adding interval?

当会计用户我们开具账单 31.03 时,我们将在 30.04 开具下一张账单,下一张 31.05 等(对于月初开具的账单:05.03, 下一张账单的日期将是 05.0405.05 等)

在 postgresql 上执行此操作会导致略有不同的结果:

tucha=> select '2020-03-31'::timestamptz +interval '1mon';
        ?column?        
------------------------
 2020-04-30 00:00:00+03
(1 row)

tucha=> select '2020-03-31'::timestamptz +interval '1mon' +interval '1mon';
        ?column?        
------------------------
 2020-05-30 00:00:00+03
(1 row)

tucha=> select '2020-03-31'::timestamptz +interval '2mon';
        ?column?        
------------------------
 2020-05-31 00:00:00+03
(1 row)

您还可以注意到,根据添加相同间隔的方式,您会得到不同的结果。

一些日期数学库甚至提供特殊参数。例如优秀的perlDateTime实现preserve参数:

In wrap mode, adding months or years that result in days beyond the end of the new month will roll over into the following month. For instance, adding one year to Feb 29 will result in Mar 1.

If you specify "end_of_month" mode as limit, the end of the month is never crossed. Thus, adding one year to Feb 29, 2000 will result in Feb 28, 2001. If you were to then add three more years this will result in Feb 28, 2004.

If you specify "end_of_month" mode as preserve, the same calculation is done as for limit except that if the original date is at the end of the month the new date will also be. For instance, adding one month to Feb 29, 2000 will result in Mar 31, 2000.

注意:此标志仅在一个月的最后几天有效,其他日期保持不变

postgresql 是否有类似的标志或可能像显示的 preserve 选项那样的特殊日期构造函数? (可能链接到有关实施的讨论欢迎)

您可以简单地将日期截断到月初,加上两个月并减去一天:

select date_trunc('month', '2020-03-31'::timestamptz) +interval '2 month -1 day'

优点是它总是 return 下个月的月底,无论您指定的月份是从哪一天开始:也就是说,它将 return 相同结果,3 月 7 日、25 日或 31 日。

这里发生了什么:

  • 通过截断日期,您可以获得月份的第一个日期。在我们的例子中是 2020-03-01.
  • 然后我们加一个月以返回截断的月份。
  • 第二个添加的月份是我们的下个月。在我们的例子中是 2020-05-01
  • 最后我们减去一天,这就是我们预期月份的最后一天。在我们的例子中是 2020-04-01

也许这个功能能让你开心:

CREATE OR REPLACE FUNCTION add_months_preserve_end(d date, i interval)
   RETURNS date
   LANGUAGE sql IMMUTABLE STRICT AS
$$SELECT
   CASE WHEN date_trunc('month', d) + INTERVAL '1 month' = d + INTERVAL '1 day'
             AND i = date_trunc('month', i)
        THEN (date_trunc('month', d) + i + INTERVAL '1 month -1 day')::date
        ELSE (d + i)::date
   END$$;

它以不同的方式处理月底的日子。

只需创建一些函数:


DROP function first_day_in_month(deet DATE) ;
CREATE function first_day_in_month(deet DATE) RETURNS date
AS
$func$
        SELECT date_trunc('mon', )::date;
$func$
        LANGUAGE sql
        ;


DROP function last_day_in_month(deet DATE) ;
CREATE function last_day_in_month(deet DATE) RETURNS date
AS
$func$
        SELECT (first_day_in_month() +interval '1 mon' -interval '1day' )::date;
$func$
        LANGUAGE sql
        ;

SELECT first_day_in_month( CURRENT_DATE );
SELECT last_day_in_month( CURRENT_DATE );
SELECT first_day_in_month( '2020-11-03' );
SELECT last_day_in_month( '2020-11-03' );

输出:


DROP FUNCTION
CREATE FUNCTION
DROP FUNCTION
CREATE FUNCTION
 first_day_in_month 
--------------------
 2020-10-01
(1 row)

 last_day_in_month 
-------------------
 2020-10-31
(1 row)

 first_day_in_month 
--------------------
 2020-11-01
(1 row)

 last_day_in_month 
-------------------
 2020-11-30
(1 row)