问题
select
(age('2012-11-30 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)),
(age('2012-12-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)),
(age('2013-01-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)),
(age('2013-02-28 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp))
which gives the followings:
0 years 0 mons 30 days 0 hours 0 mins 0.00 secs
0 years 2 mons 0 days 0 hours 0 mins 0.00 secs
0 years 3 mons 0 days 0 hours 0 mins 0.00 secs
0 years 3 mons 28 days 0 hours 0 mins 0.00 secs
But I want to have the following month definition , how can I do it?
0 years 1 mons 0 days 0 hours 0 mins 0.00 secs
0 years 2 mons 0 days 0 hours 0 mins 0.00 secs
0 years 3 mons 0 days 0 hours 0 mins 0.00 secs
0 years 4 mons 0 days 0 hours 0 mins 0.00 secs
回答1:
The expression
age('2012-11-30 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)
gives 30 days
. We are expecting 1 month
as both values point to last days of month. If we add 1 day to the values we shall get first days of next month and
age('2012-12-01 00:00:00'::timestamp, '2012-11-01 00:00:00'::timestamp)
will give us 1 month as expected. So let us check if we have two last days of month and in this case return age interval of the next days. In other cases we shall return age interval of original values:
create or replace function age_m (t1 timestamp, t2 timestamp)
returns interval language plpgsql immutable
as $$
declare
_t1 timestamp = t1+ interval '1 day';
_t2 timestamp = t2+ interval '1 day';
begin
if extract(day from _t1) = 1 and extract(day from _t2) = 1 then
return age(_t1, _t2);
else
return age(t1, t2);
end if;
end $$;
Some examples:
with my_table(date1, date2) as (
values
('2012-11-30 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp),
('2012-12-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp),
('2013-01-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp),
('2013-02-28 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)
)
select *, age(date1, date2), age_m(date1, date2)
from my_table
date1 | date2 | age | age_m
---------------------+---------------------+----------------+--------
2012-11-30 00:00:00 | 2012-10-31 00:00:00 | 30 days | 1 mon
2012-12-31 00:00:00 | 2012-10-31 00:00:00 | 2 mons | 2 mons
2013-01-31 00:00:00 | 2012-10-31 00:00:00 | 3 mons | 3 mons
2013-02-28 00:00:00 | 2012-10-31 00:00:00 | 3 mons 28 days | 4 mons
(4 rows)
回答2:
It seems like you always use the last day of the month. What you are trying to do works flawlessly with the first day of the month. So use that instead. You can always subtract a single day to get the last day of the previous month.
@klin's function is based on that. For dates (instead of timestamps), simplify:
_t1 date = t1 + 1;
_t2 date = t2 + 1;
One can just add / subtract integer values from dates (but not timestamps).
If you want to add "a month", don't just increase the month field, since this can fail like you have experienced. And there is also the wrap around at the end of the year. Add an interval '1 month'
instead.
SELECT (mydate + interval '1 month')::date AS mydate_next_month;
I cast back to date because the result of date + interval
is a timestamp
.
This "rounds down" automatically, if the last day of the next month is before the day in the original date. Note that it does not "round up" in the opposite case. If you want that, operate with the first of the month instead as explained above.
SQL Fiddle.
回答3:
This is a modified version of the time rounding function located on PostgreSQL's official wiki:
CREATE OR REPLACE FUNCTION interval_round(base_interval INTERVAL, round_interval INTERVAL) RETURNS INTERVAL AS $BODY$
SELECT justify_interval((EXTRACT(epoch FROM $1)::INTEGER + EXTRACT(epoch FROM $2)::INTEGER / 2)
/ EXTRACT(epoch FROM $2)::INTEGER * EXTRACT(epoch FROM $2)::INTEGER * INTERVAL '1 second');
$BODY$ LANGUAGE SQL STABLE;
You can call it with another interval to round to, f.ex.
SELECT interval_round(age('2013-02-28 00:00:00'::timestamp,
'2012-10-31 00:00:00'::timestamp), '1 month')
will return 4 mons
.
回答4:
From http://www.postgresql.org/docs/8.4/static/functions-datetime.html, can you swap the order of the timestamps?
"Note there can be ambiguity in the months returned by age because different months have a different number of days. PostgreSQL's approach uses the month from the earlier of the two dates when calculating partial months. For example, age('2004-06-01', '2004-04-30') uses April to yield 1 mon 1 day, while using May would yield 1 mon 2 days because May has 31 days, while April has only 30."
来源:https://stackoverflow.com/questions/23727543/find-out-number-of-months-between-2-dates