Runtime verified Test scripts included

Syntax

DateDiff(startDate, endDate, unitOfDifference)  →  number
3 arguments — exactly

Parameters

Name Type Required Description
startDate string | date Yes The starting date, either a real date value or a parseable date string
endDate string | date Yes The end date, either a real date value or a parseable date string
unitOfDifference string Yes One of Y, M, D, H or MI, in any capitalisation. Nothing else is accepted

Example

%%[
  VAR @start, @end, @days
  SET @start = "2026-01-10 08:00:00"
  SET @end = "2026-01-11 07:00:00"
  SET @days = DateDiff(@start, @end, "D")
]%%
%%=v(@days)=%%

Renders 1 — even though only 23 hours separate the two values, because the date changed once.

A later end date gives a positive number and an earlier one a negative number, which is what makes the result usable as a countdown or an overdue check:

%%[
  VAR @due, @today, @remaining
  SET @due = "2026-05-01 00:00:00"
  SET @today = "2026-04-24 00:00:00"
  SET @remaining = DateDiff(@today, @due, "D")

  IF @remaining > 0 THEN
]%%
<p>%%=v(@remaining)=%% days left.</p>
%%[ ELSE ]%%
<p>Past due.</p>
%%[ ENDIF ]%%

The order of the two dates is the whole sign convention — swap them and you get the same magnitude negated.

Return value

number — a whole number of unit boundaries, negative when the end date precedes the start date and 0 when both dates fall inside the same unit. It is a real number, not text: Add(DateDiff(@s, @e, "H"), 1) returned 24 where the inner call returned 23.

There is no closed set of sentinel values to test for, and no failure value either. Every rejected argument aborts the page with HTTP 422 rather than returning an error token, so a result that exists is always a real count.

Behaviour

The five units are the whole list, and they ignore case. Y, M, D, H and MI all work; y, m, d, h, mi, Mi and mI each returned exactly what their upper-case spelling returned. There is no seconds unit, so MI is the finest resolution available.

Anything else costs you the entire page. A seconds token, a weeks token, a quarters token, a milliseconds token, the spelled-out word for a day, an unknown two-letter token and an empty token string each returned HTTP 422 with no output at all, while a control call in the same deployment rendered normally. Nothing is written when the page aborts, so a bad unit is not something the caller can detect and recover from.

A date string and a real date value are interchangeable, in either position independently. ISO, US slash and spelled-out month forms all parsed, and the output of Now() and of DateAdd went straight in with no conversion. Mixing a value with a string in one call works in both directions. But an unparseable string, an empty string or a plain number aborts the page in either position — the same unforgiving behaviour as DateAdd, and the opposite of FormatDate.

The result counts boundaries, not elapsed time

Both dates are truncated to the requested unit and then subtracted. What comes back is how many unit boundaries lie between them.

Call Renders
DateDiff("2026-01-10 08:00:00", "2026-01-11 07:00:00", "D") 1
DateDiff("2026-01-10 00:00:00", "2026-01-10 23:59:00", "D") 0
DateDiff("2026-12-31 23:59:00", "2027-01-01 00:00:00", "Y") 1
DateDiff("2026-01-01 00:00:00", "2026-12-31 23:59:00", "Y") 0
DateDiff("2026-01-10 08:00:00", "2026-01-10 09:30:00", "H") 1
DateDiff("2026-01-10 08:00:59", "2026-01-10 08:01:00", "MI") 1

The first two rows are the pair worth remembering: 23 hours that cross midnight count as a day, and 23 hours that do not, count as nothing. The third row is the same rule at its most extreme — sixty seconds spanning New Year returns 1 for Y, M, D and MI simultaneously.

Anything finer than the requested unit is discarded rather than contributing a fraction: 59 seconds measured in MI gives 0, but one second across a minute boundary gives 1. So DateDiff(a, b, "D") == 1 means the date changed once, not that a day passed. See the differs-from-docs card for the full write-up.

Show test script
%%[
  VAR @b, @s, @e, @p1, @p2
  SET @b = RequestParameter("b")
  SET @s = "2026-01-10 08:00:00"
  SET @e = "2026-01-11 07:00:00"
  SET @p1 = "2026-03-04 13:52:07"
  SET @p2 = "2027-05-09 09:15:31"

  /* the control - fetch this whenever a branch below renders nothing */
  IF @b == "ctrl" THEN
    OutputLine(Concat("--- ctrl start ---"))
    OutputLine(Concat("CTRL=[", DateDiff("2026-01-10 08:00:00", "2026-01-11 08:00:00", "D"), "]"))
    OutputLine(Concat("CTRLH=[", DateDiff(@s, @e, "H"), "]"))
    OutputLine(Concat("--- ctrl done ---"))
  ENDIF

  /* the five accepted units, the sign convention, and a same-instant pair */
  IF @b == "safe" THEN
    OutputLine(Concat("--- safe start ---"))
    OutputLine(Concat("SPAN_Y=[", DateDiff(@p1, @p2, "Y"), "]"))
    OutputLine(Concat("SPAN_M=[", DateDiff(@p1, @p2, "M"), "]"))
    OutputLine(Concat("SPAN_D=[", DateDiff(@p1, @p2, "D"), "]"))
    OutputLine(Concat("SPAN_H=[", DateDiff(@p1, @p2, "H"), "]"))
    OutputLine(Concat("SPAN_MI=[", DateDiff(@p1, @p2, "MI"), "]"))
    OutputLine(Concat("FWD_H=[", DateDiff(@s, @e, "H"), "]"))
    OutputLine(Concat("REV_H=[", DateDiff(@e, @s, "H"), "]"))
    OutputLine(Concat("SAME_D=[", DateDiff(@s, @s, "D"), "]"))
    OutputLine(Concat("ARITH=[", Add(DateDiff(@s, @e, "H"), 1), "]"))
    OutputLine(Concat("--- safe done ---"))
  ENDIF

  /* partial intervals are neither rounded nor truncated */
  IF @b == "trunc" THEN
    OutputLine(Concat("--- trunc start ---"))
    OutputLine(Concat("T23H_D=[", DateDiff(@s, @e, "D"), "]"))
    OutputLine(Concat("T90MI_H=[", DateDiff("2026-01-10 08:00:00", "2026-01-10 09:30:00", "H"), "]"))
    OutputLine(Concat("T59MI_H=[", DateDiff("2026-01-10 08:00:00", "2026-01-10 08:59:00", "H"), "]"))
    OutputLine(Concat("T364D_Y=[", DateDiff("2026-01-10 08:00:00", "2027-01-09 08:00:00", "Y"), "]"))
    OutputLine(Concat("T366D_Y=[", DateDiff("2026-01-10 08:00:00", "2027-01-11 08:00:00", "Y"), "]"))
    OutputLine(Concat("T29D_M=[", DateDiff("2026-01-10 08:00:00", "2026-02-08 08:00:00", "M"), "]"))
    OutputLine(Concat("TNEG23H_D=[", DateDiff(@e, @s, "D"), "]"))
    OutputLine(Concat("TNEG90MI_H=[", DateDiff("2026-01-10 09:30:00", "2026-01-10 08:00:00", "H"), "]"))
    OutputLine(Concat("TSEC_MI=[", DateDiff("2026-01-10 08:00:00", "2026-01-10 08:00:59", "MI"), "]"))
    OutputLine(Concat("--- trunc done ---"))
  ENDIF

  /* the result counts boundaries of the requested unit */
  IF @b == "bound" THEN
    OutputLine(Concat("--- bound start ---"))
    OutputLine(Concat("NYE_Y=[", DateDiff("2026-12-31 23:59:00", "2027-01-01 00:00:00", "Y"), "]"))
    OutputLine(Concat("NYE_M=[", DateDiff("2026-12-31 23:59:00", "2027-01-01 00:00:00", "M"), "]"))
    OutputLine(Concat("NYE_D=[", DateDiff("2026-12-31 23:59:00", "2027-01-01 00:00:00", "D"), "]"))
    OutputLine(Concat("NYE_MI=[", DateDiff("2026-12-31 23:59:00", "2027-01-01 00:00:00", "MI"), "]"))
    OutputLine(Concat("ALMOSTY=[", DateDiff("2026-01-01 00:00:00", "2026-12-31 23:59:00", "Y"), "]"))
    OutputLine(Concat("ALMOSTM=[", DateDiff("2026-01-01 00:00:00", "2026-01-31 23:59:00", "M"), "]"))
    OutputLine(Concat("ALMOSTD=[", DateDiff("2026-01-10 00:00:00", "2026-01-10 23:59:00", "D"), "]"))
    OutputLine(Concat("SECIGN=[", DateDiff("2026-01-10 08:00:59", "2026-01-10 08:01:00", "MI"), "]"))
    OutputLine(Concat("--- bound done ---"))
  ENDIF

  /* the unit token ignores case in every spelling */
  IF @b == "case" THEN
    OutputLine(Concat("--- case start ---"))
    OutputLine(Concat("cY=[", DateDiff(@p1, @p2, "y"), "]"))
    OutputLine(Concat("cM=[", DateDiff(@p1, @p2, "m"), "]"))
    OutputLine(Concat("cD=[", DateDiff(@p1, @p2, "d"), "]"))
    OutputLine(Concat("cH=[", DateDiff(@p1, @p2, "h"), "]"))
    OutputLine(Concat("cMI=[", DateDiff(@p1, @p2, "mi"), "]"))
    OutputLine(Concat("cMi=[", DateDiff(@p1, @p2, "Mi"), "]"))
    OutputLine(Concat("cmI=[", DateDiff(@p1, @p2, "mI"), "]"))
    OutputLine(Concat("--- case done ---"))
  ENDIF

  /* date strings and real date values are interchangeable in both positions */
  IF @b == "dtype" THEN
    OutputLine(Concat("--- dtype start ---"))
    OutputLine(Concat("BOTHSTR=[", DateDiff("2026-01-10", "2026-01-12", "D"), "]"))
    OutputLine(Concat("USSTR=[", DateDiff("1/10/2026", "1/12/2026", "D"), "]"))
    OutputLine(Concat("WORDSTR=[", DateDiff("10 January 2026", "12 January 2026", "D"), "]"))
    OutputLine(Concat("BOTHVAL=[", DateDiff(Now(), DateAdd(Now(), 2, "D"), "D"), "]"))
    OutputLine(Concat("MIXVAL1=[", DateDiff(DateAdd("2026-01-10", 0, "D"), "2026-01-12", "D"), "]"))
    OutputLine(Concat("MIXVAL2=[", DateDiff("2026-01-10", DateAdd("2026-01-10", 2, "D"), "D"), "]"))
    OutputLine(Concat("--- dtype done ---"))
  ENDIF

  /* every branch below aborts the page - each renders NOTHING at all */
  IF @b == "sec" THEN
    OutputLine(Concat("--- sec start ---"))
    OutputLine(Concat("SEC=[", DateDiff(@s, "2026-01-10 08:00:59", "S"), "]"))
  ENDIF

  IF @b == "wk" THEN
    OutputLine(Concat("--- wk start ---"))
    OutputLine(Concat("WK=[", DateDiff(@p1, @p2, "W"), "]"))
  ENDIF

  IF @b == "ms" THEN
    OutputLine(Concat("--- ms start ---"))
    OutputLine(Concat("MS=[", DateDiff(@p1, @p2, "MS"), "]"))
  ENDIF

  IF @b == "qt" THEN
    OutputLine(Concat("--- qt start ---"))
    OutputLine(Concat("QT=[", DateDiff(@p1, @p2, "Q"), "]"))
  ENDIF

  IF @b == "longd" THEN
    OutputLine(Concat("--- longd start ---"))
    OutputLine(Concat("LONGD=[", DateDiff(@p1, @p2, "day"), "]"))
  ENDIF

  IF @b == "bogus" THEN
    OutputLine(Concat("--- bogus start ---"))
    OutputLine(Concat("BOGUS=[", DateDiff(@p1, @p2, "ZZ"), "]"))
  ENDIF

  IF @b == "emptytok" THEN
    OutputLine(Concat("--- emptytok start ---"))
    OutputLine(Concat("EMPTYTOK=[", DateDiff(@p1, @p2, ""), "]"))
  ENDIF

  IF @b == "baddate" THEN
    OutputLine(Concat("--- baddate start ---"))
    OutputLine(Concat("BADDATE=[", DateDiff("not a date at all", @p2, "D"), "]"))
  ENDIF

  IF @b == "baddate2" THEN
    OutputLine(Concat("--- baddate2 start ---"))
    OutputLine(Concat("BADDATE2=[", DateDiff(@p1, "not a date at all", "D"), "]"))
  ENDIF

  IF @b == "emptydate" THEN
    OutputLine(Concat("--- emptydate start ---"))
    OutputLine(Concat("EMPTYDATE=[", DateDiff("", @p2, "D"), "]"))
  ENDIF

  IF @b == "numdate" THEN
    OutputLine(Concat("--- numdate start ---"))
    OutputLine(Concat("NUMDATE=[", DateDiff(20260304, @p2, "D"), "]"))
  ENDIF

  IF @b == "a2" THEN
    OutputLine(Concat("--- a2 start ---"))
    OutputLine(Concat("A2=[", DateDiff(@p1, @p2), "]"))
  ENDIF

  IF @b == "a4" THEN
    OutputLine(Concat("--- a4 start ---"))
    OutputLine(Concat("A4=[", DateDiff(@p1, @p2, "D", "extra"), "]"))
  ENDIF
]%%

Availability

Platform Available
Marketing Cloud Engagement Yes
Marketing Cloud Next Yes, from API 67.0

See also