How to get difference of days/months/years (datediff) between two dates?

Sql ServerPostgresqlDateSql Date-Functions

Sql Server Problem Overview


I am looking for a way to implement the SQLServer-function datediff in PostgreSQL. That is, this function returns the count (as a signed integer value) of the specified datepart boundaries crossed between the specified start date and end date.

datediff(dd, '2010-04-01', '2012-03-05') = 704 // 704 changes of day in this interval
datediff(mm, '2010-04-01', '2012-03-05') = 23  // 23 changes of month
datediff(yy, '2010-04-01', '2012-03-05') = 2   // 2 changes of year

I know I could do 'dd' by simply using subtraction, but any idea about the other two?

Sql Server Solutions


Solution 1 - Sql Server

Simply subtract them:

SELECT ('2015-01-12'::date - '2015-01-01'::date) AS days;

The result:

 days
------
   11

Solution 2 - Sql Server

SELECT
  AGE('2012-03-05', '2010-04-01'),
  DATE_PART('year', AGE('2012-03-05', '2010-04-01')) AS years,
  DATE_PART('month', AGE('2012-03-05', '2010-04-01')) AS months,
  DATE_PART('day', AGE('2012-03-05', '2010-04-01')) AS days;

This will give you full years, month, days ... between two dates:

          age          | years | months | days
-----------------------+-------+--------+------
 1 year 11 mons 4 days |     1 |     11 |    4

More detailed datediff information.

Solution 3 - Sql Server

I spent some time looking for the best answer, and I think I have it.

This sql will give you the number of days between two dates as integer:

SELECT
    (EXTRACT(epoch from age('2017-6-15', now())) / 86400)::int

..which, when run today (2017-3-28), provides me with:

?column?
------------
77

The misconception about the accepted answer:

select age('2010-04-01', '2012-03-05'),
   date_part('year',age('2010-04-01', '2012-03-05')),
   date_part('month',age('2010-04-01', '2012-03-05')),
   date_part('day',age('2010-04-01', '2012-03-05'));

..is that you will get the literal difference between the parts of the date strings, not the amount of time between the two dates.

I.E:

Age(interval)=-1 years -11 mons -4 days;

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;

Solution 4 - Sql Server

Almost the same function as you needed (based on atiruz's answer, shortened version of UDF from http://www.sqlines.com/postgresql/how-to/datediff">here</a>;)

CREATE OR REPLACE FUNCTION datediff(type VARCHAR, date_from DATE, date_to DATE) RETURNS INTEGER LANGUAGE plpgsql
AS
$$
DECLARE age INTERVAL;
BEGIN
    CASE type
        WHEN 'year' THEN
            RETURN date_part('year', date_to) - date_part('year', date_from);
        WHEN 'month' THEN
            age := age(date_to, date_from);
            RETURN date_part('year', age) * 12 + date_part('month', age);
        ELSE
            RETURN (date_to - date_from)::int;
    END CASE;
END;
$$;

Usage:

/* Get months count between two dates */
SELECT datediff('month', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 10 */

/* Get years count between two dates */
SELECT datediff('year', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 1 */

/* Get days count between two dates */
SELECT datediff('day', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 323 */

/* Get months count between specified and current date */
SELECT datediff('month', '2015-02-14'::date, NOW()::date);
/* Result: 47 */

Solution 5 - Sql Server

SELECT date_part ('year', f) * 12
     + date_part ('month', f)
FROM age ('2015-06-12'::DATE, '2014-12-01'::DATE) f

Result: 6

Solution 6 - Sql Server

@WebWanderer 's answer is very close to the DateDiff using SQL server, but inaccurate. That is because of the usage of age() function.

e.g. days between '2019-07-29' and '2020-06-25' should return 332, however, using the age() function it will returns 327. Because the age() returns '10 mons 27 days" and it treats each month as 30 days which is incorrect.

You shold use the timestamp to get the accurate result. e.g.

ceil((select extract(epoch from (current_date::timestamp - <your_date>::timestamp)) / 86400))

Solution 7 - Sql Server

This question is full of misunderstandings. First lets understand the question fully. The asker wants to get the same result as for when running the MS SQL Server function DATEDIFF ( datepart , startdate , enddate ) where datepart takes dd, mm, or yy.

This function is defined by:

> This function returns the count (as a signed integer value) of the specified datepart boundaries crossed between the specified startdate and enddate.

That means how many day boundaries, month boundaries, or year boundaries, are crossed. Not how many days, months, or years it is between them. That's why datediff(yy, '2010-04-01', '2012-03-05') is 2, and not 1. There is less than 2 years between those dates, meaning only 1 whole year has passed, but 2 year boundaries have crossed, from 2010 to 2011, and from 2011 to 2012.

The following are my best attempt at replicating the logic correctly.

-- datediff(dd`, '2010-04-01', '2012-03-05') = 704 // 704 changes of day in this interval
select ('2012-03-05'::date - '2010-04-01'::date );
-- 704 changes of day

-- datediff(mm, '2010-04-01', '2012-03-05') = 23  // 23 changes of month
select (date_part('year', '2012-03-05'::date) - date_part('year', '2010-04-01'::date)) * 12 + date_part('month', '2012-03-05'::date) - date_part('month', '2010-04-01'::date)
-- 23 changes of month

-- datediff(yy, '2010-04-01', '2012-03-05') = 2   // 2 changes of year
select date_part('year', '2012-03-05'::date) - date_part('year', '2010-04-01'::date);
-- 2 changes of year

Solution 8 - Sql Server

I would like to expand on Riki_tiki_tavi's answer and get the data out there. I have created a datediff function that does almost everything sql server does. So that way we can take into account any unit.

create function datediff(units character varying, start_t timestamp without time zone, end_t timestamp without time zone) returns integer
language plpgsql
 as
 $$
DECLARE
 diff_interval INTERVAL; 
 diff INT = 0;
 years_diff INT = 0;
BEGIN
 IF units IN ('yy', 'yyyy', 'year', 'mm', 'm', 'month') THEN
   years_diff = DATE_PART('year', end_t) - DATE_PART('year', start_t);

   IF units IN ('yy', 'yyyy', 'year') THEN
     -- SQL Server does not count full years passed (only difference between year parts)
     RETURN years_diff;
   ELSE
     -- If end month is less than start month it will subtracted
     RETURN years_diff * 12 + (DATE_PART('month', end_t) - DATE_PART('month', start_t)); 
   END IF;
 END IF;

 -- Minus operator returns interval 'DDD days HH:MI:SS'  
 diff_interval = end_t - start_t;

 diff = diff + DATE_PART('day', diff_interval);

 IF units IN ('wk', 'ww', 'week') THEN
   diff = diff/7;
   RETURN diff;
 END IF;

 IF units IN ('dd', 'd', 'day') THEN
   RETURN diff;
 END IF;

 diff = diff * 24 + DATE_PART('hour', diff_interval); 

 IF units IN ('hh', 'hour') THEN
    RETURN diff;
 END IF;

 diff = diff * 60 + DATE_PART('minute', diff_interval);

 IF units IN ('mi', 'n', 'minute') THEN
    RETURN diff;
 END IF;

 diff = diff * 60 + DATE_PART('second', diff_interval);

 RETURN diff;
END;
$$;

Solution 9 - Sql Server

Here is a complete example with output. psql (10.1, server 9.5.10).

You get 58, not some value less than 30.
Remove age() function, solved the problem that previous post mentioned.

drop table t;
create table t(
    d1 date
);

insert into t values(current_date - interval '58 day');

select d1
, current_timestamp - d1::timestamp date_diff
, date_part('day', current_timestamp - d1::timestamp)
from t;

     d1     |        date_diff        | date_part
------------+-------------------------+-----------
 2018-05-21 | 58 days 21:41:07.992731 |        58

Solution 10 - Sql Server

One more solution, version for the 'years' difference:

SELECT count(*) - 1 FROM (SELECT distinct(date_trunc('year', generate_series('2010-04-01'::timestamp, '2012-03-05', '1 week')))) x

    2

(1 row)

And the same trick for the months:

SELECT count(*) - 1 FROM (SELECT distinct(date_trunc('month', generate_series('2010-04-01'::timestamp, '2012-03-05', '1 week')))) x

   23

(1 row)

In real life query there can be some timestamp sequences grouped by hour/day/week/etc instead of generate_series.

This 'count(distinct(date_trunc('month', ts)))' can be used right in the 'left' side of the select:

SELECT sum(a - b)/count(distinct(date_trunc('month', c))) FROM d

I used generate_series() here just for the brevity.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongefeiView Question on Stackoverflow
Solution 1 - Sql ServermehdiView Answer on Stackoverflow
Solution 2 - Sql ServerIhor RomanchenkoView Answer on Stackoverflow
Solution 3 - Sql ServerWebWandererView Answer on Stackoverflow
Solution 4 - Sql ServerRiki_tiki_taviView Answer on Stackoverflow
Solution 5 - Sql ServeratiruzView Answer on Stackoverflow
Solution 6 - Sql ServerShengfeng LiView Answer on Stackoverflow
Solution 7 - Sql ServerAndré C. AndersenView Answer on Stackoverflow
Solution 8 - Sql ServerDaniel L. VanDenBoschView Answer on Stackoverflow
Solution 9 - Sql ServerCharlie 木匠View Answer on Stackoverflow
Solution 10 - Sql ServerVladimir KunschikovView Answer on Stackoverflow