Runtime verified Test scripts included

Syntax

DateAdd(date, amountToAdd, unitToAdd)  →  date
3 arguments — exactly

Parameters

Name Type Required Description
date string | date Yes The date to adjust, either a real date value or a parseable date string
amountToAdd string | number Yes A whole number of intervals — negative subtracts, and a decimal aborts the page
unitToAdd string Yes One of Y, M, D, H or MI, in any capitalisation. Nothing else is accepted

Example

%%[
  VAR @d, @due
  SET @d = "2026-03-04 13:52:07"
  SET @due = DateAdd(@d, 14, "D")
]%%
%%=v(@due)=%%

Renders 3/18/2026 1:52:07 PM.

The amount may arrive as a string, which is what happens when it comes out of a data extension field — no conversion step is needed:

%%[
  VAR @d, @a, @b
  SET @d = "2026-03-04 13:52:07"
  SET @a = DateAdd(@d, 1, "D")
  SET @b = DateAdd(@d, "1", "D")
]%%
<p>Number: %%=v(@a)=%%</p>
<p>String: %%=v(@b)=%%</p>

Both lines render 3/5/2026 1:52:07 PM.

Return value

date — the adjusted date, which the other date functions accept directly without formatting or re-parsing. DateAdd(DateAdd(@d, 1, "D"), 1, "D") works, and so does passing the result straight into FormatDate.

There is no failure value to test for. Every rejected argument aborts the page with HTTP 422 instead of returning an empty string, so a result that exists is always a real date.

Behaviour

The five units are the whole list, and they ignore case. Y, M, D, H and MI each advance exactly one field and leave the rest alone. Capitalisation makes no difference anywhere: y, m, d, h, mi, Mi and mI all matched their upper-case spelling exactly.

Anything else costs you the entire page. There is no seconds unit, no weeks unit, no quarters, no milliseconds, and no spelled-out long forms — the word for a day is rejected just as an unknown two-letter token is. Each of those returned HTTP 422 with no output at all, while a control call in the same deployment rendered normally. Since nothing is written when the page aborts, a bad unit is not something the caller can detect and recover from; validate it before the call. For seconds, adjust in minutes or use a different approach entirely.

The amount must be whole. Negative subtracts, zero returns the date unchanged, and a large amount rolls the year over correctly. A numeric string is fine in either sign. A decimal is not — neither 1.5 nor "1.5" is rounded or truncated, and both abort, as does a non-numeric word.

A date string and a real date value are interchangeable. ISO, US slash and spelled-out month forms all parsed, and the output of Now() was accepted with no conversion. But unlike FormatDate, which quietly returns an empty string for input it cannot read, DateAdd aborts: an unparseable string, an empty string and a plain number each killed the page. Two neighbouring functions, opposite failure modes.

Month arithmetic clamps, and the clamp is not reversible

Call Renders
DateAdd("2026-01-31 13:52:07", 1, "M") 2/28/2026 1:52:07 PM
DateAdd("2026-01-31 13:52:07", 2, "M") 3/31/2026 1:52:07 PM
DateAdd(DateAdd("2026-01-31 13:52:07", 1, "M"), -1, "M") 1/28/2026 1:52:07 PM
DateAdd("2028-02-29 09:00:00", 1, "Y") 2/28/2029 9:00:00 AM

Adding a month to the 31st of a month pulls the day back to the last valid one rather than spilling into the following month. The second row shows the clamp is applied to each result rather than carried forward — two months from the same base restores the 31st.

The third row is the one that bites. Stepping forward a month and back again lands on the 28th, three days from where you started, so any loop that walks a date across month boundaries and expects to return to its origin will drift. If you need month-end semantics, compute the day of month yourself.

Show test script
%%[
  VAR @b, @d, @j
  SET @b = RequestParameter("b")
  SET @d = "2026-03-04 13:52:07"
  SET @j = "2026-01-31 13:52:07"

  /* the five accepted units, plus a numeric-string amount */
  IF @b == "safe" THEN
    OutputLine(Concat("--- safe start ---"))
    OutputLine(Concat("BASE=[", @d, "]"))
    OutputLine(Concat("Y=[", DateAdd(@d, 1, "Y"), "]"))
    OutputLine(Concat("M=[", DateAdd(@d, 1, "M"), "]"))
    OutputLine(Concat("D=[", DateAdd(@d, 1, "D"), "]"))
    OutputLine(Concat("H=[", DateAdd(@d, 1, "H"), "]"))
    OutputLine(Concat("MI=[", DateAdd(@d, 1, "MI"), "]"))
    OutputLine(Concat("STRAMT=[", DateAdd(@d, "1", "D"), "]"))
    OutputLine(Concat("STRAMTNEG=[", DateAdd(@d, "-1", "D"), "]"))
    OutputLine(Concat("--- safe done ---"))
  ENDIF

  /* the unit token ignores case in every spelling */
  IF @b == "case" THEN
    OutputLine(Concat("--- case start ---"))
    OutputLine(Concat("y=[", DateAdd(@d, 1, "y"), "]"))
    OutputLine(Concat("m=[", DateAdd(@d, 1, "m"), "]"))
    OutputLine(Concat("d=[", DateAdd(@d, 1, "d"), "]"))
    OutputLine(Concat("h=[", DateAdd(@d, 1, "h"), "]"))
    OutputLine(Concat("mi=[", DateAdd(@d, 1, "mi"), "]"))
    OutputLine(Concat("Mi=[", DateAdd(@d, 1, "Mi"), "]"))
    OutputLine(Concat("mI=[", DateAdd(@d, 1, "mI"), "]"))
    OutputLine(Concat("--- case done ---"))
  ENDIF

  /* negative, zero and large amounts */
  IF @b == "amt" THEN
    OutputLine(Concat("--- amt start ---"))
    OutputLine(Concat("NEG=[", DateAdd(@d, -1, "D"), "]"))
    OutputLine(Concat("ZERO=[", DateAdd(@d, 0, "D"), "]"))
    OutputLine(Concat("NEGH=[", DateAdd(@d, -3, "H"), "]"))
    OutputLine(Concat("BIG=[", DateAdd(@d, 400, "D"), "]"))
    OutputLine(Concat("--- amt done ---"))
  ENDIF

  /* month-end clamping, and the fact that it is not reversible */
  IF @b == "roll" THEN
    OutputLine(Concat("--- roll start ---"))
    OutputLine(Concat("JAN31M=[", DateAdd(@j, 1, "M"), "]"))
    OutputLine(Concat("JAN31M2=[", DateAdd(@j, 2, "M"), "]"))
    OutputLine(Concat("JAN31BACK=[", DateAdd(DateAdd(@j, 1, "M"), -1, "M"), "]"))
    OutputLine(Concat("LEAPY=[", DateAdd("2028-02-29 09:00:00", 1, "Y"), "]"))
    OutputLine(Concat("MAR31MINUS=[", DateAdd("2026-03-31 09:00:00", -1, "M"), "]"))
    OutputLine(Concat("--- roll done ---"))
  ENDIF

  /* date strings and real date values are interchangeable */
  IF @b == "dtype" THEN
    OutputLine(Concat("--- dtype start ---"))
    OutputLine(Concat("ISOSTR=[", DateAdd("2026-01-01", 1, "D"), "]"))
    OutputLine(Concat("USSTR=[", DateAdd("1/1/2026", 1, "D"), "]"))
    OutputLine(Concat("WORDSTR=[", DateAdd("1 January 2026", 1, "D"), "]"))
    OutputLine(Concat("NOWVAL=[", FormatDate(DateAdd(Now(), 0, "D"), "yyyy"), "]"))
    OutputLine(Concat("CHAIN=[", DateAdd(DateAdd(@d, 1, "D"), 1, "D"), "]"))
    OutputLine(Concat("--- dtype done ---"))
  ENDIF

  /* the control - fetch this whenever a branch below renders nothing */
  IF @b == "ctrl" THEN
    OutputLine(Concat("--- ctrl start ---"))
    OutputLine(Concat("CTRL=[", DateAdd(@d, 1, "D"), "]"))
    OutputLine(Concat("CTRLJ=[", DateAdd(@j, 1, "M"), "]"))
    OutputLine(Concat("--- ctrl done ---"))
  ENDIF

  /* every branch below aborts the page - each renders NOTHING at all */
  IF @b == "sec" THEN
    OutputLine(Concat("--- sec start ---"))
    OutputLine(Concat("S=[", DateAdd(@d, 30, "S"), "]"))
  ENDIF

  IF @b == "wk" THEN
    OutputLine(Concat("--- wk start ---"))
    OutputLine(Concat("W=[", DateAdd(@d, 1, "W"), "]"))
  ENDIF

  IF @b == "longd" THEN
    OutputLine(Concat("--- longd start ---"))
    OutputLine(Concat("LONGD=[", DateAdd(@d, 1, "day"), "]"))
  ENDIF

  IF @b == "bogus" THEN
    OutputLine(Concat("--- bogus start ---"))
    OutputLine(Concat("BOGUS=[", DateAdd(@d, 1, "ZZ"), "]"))
  ENDIF

  IF @b == "emptytok" THEN
    OutputLine(Concat("--- emptytok start ---"))
    OutputLine(Concat("EMPTYTOK=[", DateAdd(@d, 1, ""), "]"))
  ENDIF

  IF @b == "frac" THEN
    OutputLine(Concat("--- frac start ---"))
    OutputLine(Concat("FRAC=[", DateAdd(@d, 1.5, "D"), "]"))
  ENDIF

  IF @b == "fracstr" THEN
    OutputLine(Concat("--- fracstr start ---"))
    OutputLine(Concat("FRACSTR=[", DateAdd(@d, "1.5", "D"), "]"))
  ENDIF

  IF @b == "amtword" THEN
    OutputLine(Concat("--- amtword start ---"))
    OutputLine(Concat("AMTWORD=[", DateAdd(@d, "two", "D"), "]"))
  ENDIF

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

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

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

  IF @b == "a2" THEN
    OutputLine(Concat("--- a2 start ---"))
    OutputLine(Concat("A2=[", DateAdd(@d, 1), "]"))
  ENDIF

  IF @b == "a4" THEN
    OutputLine(Concat("--- a4 start ---"))
    OutputLine(Concat("A4=[", DateAdd(@d, 1, "D", "extra"), "]"))
  ENDIF
]%%

Availability

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

See also