FormatDate
Formats a date according to a date pattern, a time pattern and a locale. Runtime-proven on a live Marketing Cloud Engagement CloudPage — including the fact that mm in the date pattern renders the month, not the minutes.
Syntax
FormatDate(dateString[, dateFormat, timeFormat, localeCode]) → string
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
dateString |
string | date | Yes | The date to format, either a real date value or a parseable date string |
dateFormat |
string | No | The date pattern, or a single-letter standard format such as D, G or s |
timeFormat |
string | No | The time pattern — the only argument in which mm means minutes |
localeCode |
string | No | The locale for month and day names, written with a hyphen or an underscore |
Example
%%[
VAR @d, @out
SET @d = "2026-03-04 13:52:07"
SET @out = FormatDate(@d, "yyyy-MM-dd", "HH:mm:ss")
]%%
%%=v(@out)=%%
Renders 2026-03-04 13:52:07.
The split across two arguments is not optional styling — it is what makes the time correct. Putting the whole pattern in the date argument, the way a single .NET format string would be written, silently returns the wrong minutes:
%%[
VAR @d, @wrong, @right
SET @d = "2026-03-04 13:52:07"
SET @wrong = FormatDate(@d, "yyyy-MM-dd HH:mm:ss")
SET @right = FormatDate(@d, "yyyy-MM-dd", "HH:mm:ss")
]%%
<p>One argument: %%=v(@wrong)=%%</p>
<p>Two arguments: %%=v(@right)=%%</p>
The first line renders 2026-03-04 13:03:07 and the second 2026-03-04 13:52:07. Why the minutes became 03 is the subject of the next chapter.
Return value
string — the formatted date.
There is no closed set of sentinel values: the output is whatever the pattern produced. The one value worth testing for is the empty string, which comes back whenever the first argument cannot be read as a date — an unparseable string, an empty string and a number all returned it.
Behaviour
The two pattern arguments are separate token sets. Whatever the second argument contains is resolved as a date pattern and whatever the third contains as a time pattern, and the same letters can mean different things in each. This is why a pattern that looks correct as a single .NET format string comes out wrong: split it across the two arguments instead.
Tokens ignore case entirely. YYYY rendered 2026 exactly as yyyy did, mmmm rendered March exactly as MMMM did, and DDDD matched dddd. Capitalisation is therefore never a way to disambiguate two meanings of the same letters.
Single-letter tokens select a standard format, not an unpadded number. For 2026-03-04 13:52:07, d rendered 3/4/2026 and M rendered March 4. In the time argument, h or H on its own does not render an hour at all — it aborts the page. Use dd, MM, hh and HH.
A standard-format letter can stand in for the whole pattern. G rendered 3/4/2026 1:52:07 PM, F rendered Wednesday, March 4, 2026 1:52:07 PM, r rendered Wed, 04 Mar 2026 13:52:07 GMT and o rendered 2026-03-04T13:52:07.0000000. Two are worth knowing about: D gave the short date 3/4/2026 rather than a long one, and U rendered Wednesday, March 4, 2026 7:52:07 PM — six hours ahead, because it treats the input as local and renders it as UTC.
The locale accepts either separator and falls back silently. fr-FR and fr_FR produced identical output. uk_UA rendered 04.03.2026 where en-US rendered 3/4/2026, and French month names come back lower-cased (mars). A locale code that does not exist is not rejected — a made-up one still rendered a date.
Bad input is not reported, it is swallowed. An unparseable date, an empty date and a number each returned an empty string at HTTP 200. An unrecognised format token is echoed back literally — qqqq rendered qqqq. A caller cannot tell a failed parse from a legitimately empty result, so validate the input before formatting it.
Input can be a real date value, not only a string. The output of DateAdd(Now(), 1, "D") formatted directly, with no conversion step, as did Now() itself.
The date pattern’s mm is the month, and the day names are shifted
| Call | Renders |
|---|---|
FormatDate(@d, "mm") |
03 — the month |
FormatDate(@d, "", "mm") |
52 — the minutes |
FormatDate(@d, "yyyy-MM-dd HH:mm:ss") |
2026-03-04 13:03:07 |
FormatDate(@d, "yyyy-MM-dd", "HH:mm:ss") |
2026-03-04 13:52:07 |
FormatDate(@d, "ddd") |
We4ne74a26 |
FormatDate(@d, "dddd") |
Wed |
FormatDate(@d, "ddddd") |
Wednesday |
All against @d = "2026-03-04 13:52:07", a Wednesday.
Minutes are simply not addressable from the date pattern: mm and MM are the same token there and both give the month. Move to the time pattern and both give the minutes. The day-name tokens are shifted by one repetition against what the reference promises, and ddd does not produce a day name at all — the digits in We4ne74a26 come from the date itself, and a December date produced ri25a26 the same way. Never use ddd. See the differs-from-docs card for the full comparison against the official reference.
Show test script
%%[
VAR @b, @d
SET @b = RequestParameter("b")
SET @d = "2026-03-04 13:52:07"
/* the documented custom tokens in the DATE pattern */
IF @b == "safe" THEN
OutputLine(Concat("--- safe start ---"))
OutputLine(Concat("DEF=[", FormatDate(@d), "]"))
OutputLine(Concat("yy=[", FormatDate(@d, "yy"), "]"))
OutputLine(Concat("yyyy=[", FormatDate(@d, "yyyy"), "]"))
OutputLine(Concat("M=[", FormatDate(@d, "M"), "]"))
OutputLine(Concat("MM=[", FormatDate(@d, "MM"), "]"))
OutputLine(Concat("MMM=[", FormatDate(@d, "MMM"), "]"))
OutputLine(Concat("MMMM=[", FormatDate(@d, "MMMM"), "]"))
OutputLine(Concat("d=[", FormatDate(@d, "d"), "]"))
OutputLine(Concat("dd=[", FormatDate(@d, "dd"), "]"))
/* the day-name tokens: ddd is corrupted, dddd is the abbreviation and
the full name needs a fifth d */
OutputLine(Concat("ddd=[", FormatDate(@d, "ddd"), "]"))
OutputLine(Concat("dddd=[", FormatDate(@d, "dddd"), "]"))
OutputLine(Concat("ddddd=[", FormatDate(@d, "ddddd"), "]"))
OutputLine(Concat("gg=[", FormatDate(@d, "gg"), "]"))
OutputLine(Concat("--- safe done ---"))
ENDIF
/* the tokens ignore case: each upper form matches its lower twin */
IF @b == "case" THEN
OutputLine(Concat("--- case start ---"))
OutputLine(Concat("Y=[", FormatDate(@d, "Y"), "]"))
OutputLine(Concat("YYYY=[", FormatDate(@d, "YYYY"), "]"))
OutputLine(Concat("mmmm=[", FormatDate(@d, "mmmm"), "]"))
OutputLine(Concat("mm=[", FormatDate(@d, "mm"), "]"))
OutputLine(Concat("m=[", FormatDate(@d, "m"), "]"))
OutputLine(Concat("DD=[", FormatDate(@d, "DD"), "]"))
OutputLine(Concat("DDDD=[", FormatDate(@d, "DDDD"), "]"))
OutputLine(Concat("--- case done ---"))
ENDIF
/* the headline: the minutes position of a one-argument pattern renders
the month, and no capitalisation changes it */
IF @b == "cross" THEN
OutputLine(Concat("--- cross start ---"))
OutputLine(Concat("REPRO=[", FormatDate(@d, "yyyy-MM-dd HH:mm:ss"), "]"))
OutputLine(Concat("REPROLOW=[", FormatDate(@d, "yyyy-mm-dd hh:mm:ss"), "]"))
OutputLine(Concat("dHH=[", FormatDate(@d, "HH"), "]"))
OutputLine(Concat("dhh=[", FormatDate(@d, "hh"), "]"))
OutputLine(Concat("dss=[", FormatDate(@d, "ss"), "]"))
OutputLine(Concat("dtt=[", FormatDate(@d, "tt"), "]"))
OutputLine(Concat("dzzz=[", FormatDate(@d, "zzz"), "]"))
OutputLine(Concat("--- cross done ---"))
ENDIF
/* the same tokens in the TIME pattern, where mm finally means minutes */
IF @b == "time" THEN
OutputLine(Concat("--- time start ---"))
OutputLine(Concat("thh=[", FormatDate(@d, "", "hh"), "]"))
OutputLine(Concat("tHH=[", FormatDate(@d, "", "HH"), "]"))
OutputLine(Concat("tmm=[", FormatDate(@d, "", "mm"), "]"))
OutputLine(Concat("tMM=[", FormatDate(@d, "", "MM"), "]"))
OutputLine(Concat("tss=[", FormatDate(@d, "", "ss"), "]"))
OutputLine(Concat("ttt=[", FormatDate(@d, "", "tt"), "]"))
OutputLine(Concat("tfff=[", FormatDate(@d, "", "fff"), "]"))
OutputLine(Concat("tcombo=[", FormatDate(@d, "", "HH:mm:ss"), "]"))
OutputLine(Concat("tboth=[", FormatDate(@d, "yyyy-MM-dd", "HH:mm:ss"), "]"))
OutputLine(Concat("--- time done ---"))
ENDIF
/* the single-letter standard formats */
IF @b == "std" THEN
OutputLine(Concat("--- std start ---"))
OutputLine(Concat("D=[", FormatDate(@d, "D"), "]"))
OutputLine(Concat("F=[", FormatDate(@d, "F"), "]"))
OutputLine(Concat("G=[", FormatDate(@d, "G"), "]"))
OutputLine(Concat("o=[", FormatDate(@d, "o"), "]"))
OutputLine(Concat("r=[", FormatDate(@d, "r"), "]"))
OutputLine(Concat("T=[", FormatDate(@d, "T"), "]"))
OutputLine(Concat("u=[", FormatDate(@d, "u"), "]"))
OutputLine(Concat("U=[", FormatDate(@d, "U"), "]"))
OutputLine(Concat("l=[", FormatDate(@d, "l"), "]"))
OutputLine(Concat("iso=[", FormatDate(@d, "iso"), "]"))
OutputLine(Concat("rfc=[", FormatDate(@d, "rfc"), "]"))
OutputLine(Concat("--- std done ---"))
ENDIF
/* the locale argument: hyphen and underscore are interchangeable */
IF @b == "locale" THEN
OutputLine(Concat("--- locale start ---"))
OutputLine(Concat("frD=[", FormatDate(@d, "D", "", "fr-FR"), "]"))
OutputLine(Concat("frU=[", FormatDate(@d, "D", "", "fr_FR"), "]"))
OutputLine(Concat("frMMMM=[", FormatDate(@d, "MMMM", "", "fr-FR"), "]"))
OutputLine(Concat("jaD=[", FormatDate(@d, "D", "", "ja-JP"), "]"))
OutputLine(Concat("ukS=[", FormatDate(@d, "S", "", "uk_UA"), "]"))
OutputLine(Concat("enS=[", FormatDate(@d, "S", "", "en-US"), "]"))
OutputLine(Concat("--- locale done ---"))
ENDIF
/* the accepted input forms, including a real date value */
IF @b == "parse" THEN
OutputLine(Concat("--- parse start ---"))
OutputLine(Concat("ISO=[", FormatDate("2026-03-04T13:52:07.123-0700", "yyyy-MM-dd", "HH:mm:ss"), "]"))
OutputLine(Concat("SDATE=[", FormatDate("2026-03-04", "yyyy-MM-dd", "HH:mm:ss"), "]"))
OutputLine(Concat("USNP=[", FormatDate("3/4/2026", "yyyy-MM-dd"), "]"))
OutputLine(Concat("WORDS=[", FormatDate("4 March 2026 1:52:07 PM", "yyyy-MM-dd", "HH:mm:ss"), "]"))
OutputLine(Concat("DATEVAL=[", FormatDate(DateAdd(Now(), 1, "D"), "yyyy-MM-dd"), "]"))
OutputLine(Concat("--- parse done ---"))
ENDIF
/* everything that returns empty or passes through instead of failing */
IF @b == "empties" THEN
OutputLine(Concat("--- empties start ---"))
OutputLine(Concat("BAD=[", FormatDate("not a date at all", "yyyy"), "]"))
OutputLine(Concat("EMPTY=[", FormatDate("", "yyyy"), "]"))
OutputLine(Concat("NUM=[", FormatDate(20260304, "yyyy"), "]"))
OutputLine(Concat("BADFMT=[", FormatDate(@d, "qqqq"), "]"))
OutputLine(Concat("BADLOC=[", FormatDate(@d, "D", "", "zz-ZZ"), "]"))
OutputLine(Concat("--- empties done ---"))
ENDIF
/* these two branches abort the page: the start marker never renders */
IF @b == "hsingle" THEN
OutputLine(Concat("--- hsingle start ---"))
OutputLine(Concat("th=[", FormatDate(@d, "", "h"), "]"))
ENDIF
IF @b == "Hsingle" THEN
OutputLine(Concat("--- Hsingle start ---"))
OutputLine(Concat("tH=[", FormatDate(@d, "", "H"), "]"))
ENDIF
IF @b == "a0" THEN
OutputLine(Concat("--- a0 start ---"))
OutputLine(Concat("A0=[", FormatDate(), "]"))
ENDIF
IF @b == "a5" THEN
OutputLine(Concat("--- a5 start ---"))
OutputLine(Concat("A5=[", FormatDate(@d, "yyyy", "HH", "en-US", "extra"), "]"))
ENDIF
]%%
OutputLine given a bare string literal renders an empty line. Wrap the argument in Concat() or your start and done markers vanish silently — which looks exactly like the function failing.
Availability
| Platform | Available |
|---|---|
| Marketing Cloud Engagement | Yes |
| Marketing Cloud Next | Yes, from API 67.0 |
Marketing Cloud Next uses a different pattern dialect — Java-style format strings rather than the .NET-style ones proven here — so a pattern written for Engagement is not portable as-is.
See also
- Now — the value most often passed into this function
- The differs-from-docs card — what the official reference claims and what the runtime does
- Official reference · ampscript.guide