Date functions — Marketing Cloud AMPscript reference
AMPscript's date surface — Now / DateAdd / DateDiff / DatePart / FormatDate / DateParse. The timezone trap (MC system clock is CST regardless of where the tenant is), the month-math instability, and the patterns that survive at scale.
AMPscript's date functions are a thin functional layer over the .NET-style date model that Marketing Cloud runs on the server. Smaller than SQL's surface (no operators, no BETWEEN, no DATEPART shortcuts for weekday name), larger than SSJS's (which mostly inherits the brittle JavaScript Date). The seven functions on this page cover ~95% of personalization needs: Now, DateAdd, DateDiff, DatePart, FormatDate, DateParse, plus the timezone pair SystemDateToLocalDate / LocalDateToSystemDate.
The two traps that fire most often: MC's system clock is CST (Central Standard Time, US) regardless of where your tenant is provisioned, and DateAdd with month units is unstable around month boundaries. Both are covered below.
Official syntax
%%[
/* Current date/time — server time, CST */
SET @now = Now() /* → 2026-05-13 14:32:08 */
SET @sysNow = SystemDate() /* alias of Now() */
/* DateAdd — add or subtract units */
SET @yesterday = DateAdd(Now(), -1, "Day")
SET @nextWeek = DateAdd(Now(), 7, "Day")
SET @nextHour = DateAdd(Now(), 1, "Hour")
SET @inAYear = DateAdd(Now(), 1, "Year")
SET @monthOut = DateAdd(Now(), 1, "Month") /* see month-math trap below */
/* DateDiff — difference between two dates, in chosen unit */
SET @daysSince = DateDiff(@orderDate, Now(), "Day")
SET @hoursSince = DateDiff(@lastLogin, Now(), "Hour")
/* DateDiff is later - earlier; negative if you swap */
/* DatePart — extract a component */
SET @year = DatePart(Now(), "year") /* numeric */
SET @month = DatePart(Now(), "month") /* numeric */
SET @day = DatePart(Now(), "day") /* numeric */
SET @hour = DatePart(Now(), "hour") /* numeric, 24h */
SET @minute = DatePart(Now(), "minute") /* numeric */
SET @wday = DatePart(Now(), "weekday") /* STRING: "Wednesday" */
SET @mname = DatePart(Now(), "monthname") /* STRING: "May" */
/* FormatDate — date to formatted string */
SET @yyyymmdd = FormatDate(Now(), "yyyy-MM-dd")
SET @longDate = FormatDate(Now(), "MMMM d, yyyy", "en-US")
/* → "May 13, 2026" */
/* DateParse — string to date (returns a date value, not a string) */
SET @parsed = DateParse("2026-05-13")
SET @parsedTz = DateParse("2026-05-13 09:00:00", 1)
/* The optional 2nd arg (1) tells AMPscript the input is local-time;
otherwise it's interpreted as server CST. */
/* Timezone conversion — useful for personalizing in a recipient's TZ
when you have their offset stored. */
SET @localized = SystemDateToLocalDate(@cstDate)
SET @backToCst = LocalDateToSystemDate(@localDate)
]%%
<p>Today is %%=v(@longDate)=%%.</p>The supported set:
| Function | What it does | Notes |
|---|---|---|
| Now() | Current server date/time | CST, always (Marketing Cloud's system clock) |
| SystemDate() | Alias of Now() | Same return; explicit name when context matters |
| LocalDate() | Recipient's local time, if MC has TZ info | Falls back to CST when no TZ available |
| DateAdd(date, n, unit) | Add n units to date | Units: Day, Hour, Minute, Second, Month, Year |
| DateDiff(d1, d2, unit) | d2 - d1 in unit (rounds down) | Same unit names as DateAdd |
| DatePart(date, part) | Extract a component | Some parts return numeric, some return strings (see below) |
| FormatDate(date, fmt [, locale]) | Format as string | See string functions for locale gotcha |
| DateParse(str [, isLocal]) | Parse string → date | Loose parsing; pass 1 to mark string as local-time |
| SystemDateToLocalDate(d) | Convert CST → subscriber local | Needs TZ on the subscriber |
| LocalDateToSystemDate(d) | Convert local → CST | The reverse |
Reference:
What survives in production
Marketing Cloud's server clock is CST, regardless of tenant region
Now() and SystemDate() both return Central Standard Time (US), not the timezone of the tenant's geography. A tenant provisioned in EU returns 2026-05-13 14:32:08 as a CST timestamp — the same moment in EU local time is later.
%%[
/* Returns CST, even if your business is in Buenos Aires or Paris */
SET @now = Now()
/* If you need recipient-local time, convert AFTER the read */
SET @local = SystemDateToLocalDate(@now)
/* If the subscriber has no TZ on the profile, SystemDateToLocalDate
falls back to returning the CST value. Confirm TZ on the
sendable DE before relying on local-time personalization. */
]%%The trap surfaces most often when AMPscript runs a "is today the recipient's birthday" check using Now() directly — for a recipient in Asia, the CST date hasn't rolled over yet when their local date already has. Either store the comparison values in CST as well, or convert explicitly with SystemDateToLocalDate before comparing.
DateAdd with Month and Year units is unstable around boundaries
DateAdd(Now(), -3, "Month") is not "exactly 3 calendar months back, same day-of-month" — it's approximate. On the 31st of a 31-day month, three months back lands on the 28th, 30th, or 31st depending on the target month's length, and the rule isn't always intuitive. The same applies at year-end with DateAdd(d, 1, "Year") on a February 29 leap-year date.
%%[
/* STABLE — day arithmetic is exact */
SET @ninetyDaysAgo = DateAdd(Now(), -90, "Day")
/* UNSTABLE — calendar-month arithmetic shifts around month-ends */
SET @threeMonthsAgo = DateAdd(Now(), -3, "Month")
]%%Same trap as the SQL Date functions gotcha. Translate "last 3 months" into "last 90 days" at design time, and stay in day-arithmetic for any computation that flows into a comparison or a WHERE filter downstream.
DateDiff rounds down, not to the nearest unit
DateDiff(d1, d2, "Day") returns the integer number of complete units between two dates — it doesn't round to the nearest. A 23-hour-59-minute gap returns 0 days, not 1.
%%[
/* @lastOrder = 2026-05-13 09:00:00
@now = 2026-05-14 08:59:00 (one minute short of 24h) */
SET @daysSince = DateDiff(@lastOrder, @now, "Day")
/* → 0 — NOT 1 */
/* Use Hour for sub-day precision */
SET @hoursSince = DateDiff(@lastOrder, @now, "Hour")
/* → 23 */
]%%The conditional logic IF DateDiff(@lastLogin, Now(), "Day") > 30 THEN ... excludes any recipient whose 30-day mark falls within today — they're at 29 days, 23 hours, 50 minutes and the function returns 29. Pad the comparison with a smaller unit (> 30 days → > 720 hours) when the boundary is meaningful.
DatePart returns numbers for some parts, strings for others
%%[
/* Numeric parts */
SET @y = DatePart(Now(), "year") /* 2026 */
SET @m = DatePart(Now(), "month") /* 5 — NOT "May", NOT "05" */
SET @d = DatePart(Now(), "day") /* 13 */
SET @h = DatePart(Now(), "hour") /* 14 */
/* String parts — these return locale-aware names */
SET @wday = DatePart(Now(), "weekday") /* "Wednesday" */
SET @mname = DatePart(Now(), "monthname") /* "May" */
]%%The hand-off failure: a dev writes IF DatePart(@d, "month") == "May" and gets false-everywhere because the function returned the integer 5. Conversely, comparing the weekday number expecting a name returns the wrong shape.
The rule: use numeric parts (year, month, day, hour, minute) for arithmetic and comparisons, and string parts (weekday, monthname) only for display in personalization. Don't compare against string parts unless you've explicitly accounted for locale and you're not displaying them.
DateParse is forgiving, but the timezone flag matters
DateParse accepts many formats — "2026-05-13", "05/13/2026", "May 13, 2026 9:00 AM". The looseness is convenient until two systems disagree on which one means what.
%%[
/* Server-time interpretation (default) */
SET @d = DateParse("2026-05-13 09:00:00")
/* AMPscript treats this as 09:00 CST */
/* Local-time interpretation — the 2nd arg = 1 marks the input as local */
SET @dLocal = DateParse("2026-05-13 09:00:00", 1)
/* AMPscript treats this as 09:00 in the subscriber's local TZ if
present on profile, else falls back to CST */
]%%When the source string came from a system that emits a specific TZ (a Salesforce object that records LastModified in UTC, for example), parse with the right flag and convert explicitly. Don't rely on AMPscript guessing.
FormatDate returns a string — chaining downstream string functions reads odd
FormatDate(Now(), "yyyy-MM-dd") returns the string "2026-05-13", not a date value. This trips up code that expects the return to feed back into DateAdd or DateDiff — those need date values, not strings.
%%[
/* WRONG — FormatDate output can't feed DateAdd directly */
SET @str = FormatDate(Now(), "yyyy-MM-dd")
SET @plus = DateAdd(@str, 1, "Day") /* MAY work via implicit DateParse,
but the behavior isn't guaranteed */
/* CORRECT — keep date math on date values; format only for output */
SET @plus = DateAdd(Now(), 1, "Day")
SET @plusStr = FormatDate(@plus, "yyyy-MM-dd")
]%%The Cleon convention: do all date arithmetic on date values, then format once at the end into a string for interpolation. Don't pass strings between date functions even when the implicit conversion happens to work.
Personalization preview can use a different "now"
Email Studio's preview evaluates Now() at the moment the preview is rendered — not at the moment the production send will run. A "you have N hours left" countdown personalization can show "23 hours left" in preview at 11pm and "1 hour left" at the actual send-time of 9pm the next day.
The defense: when the email contains time-sensitive personalization, do a test send at a time near the production-send time, not just a preview. See gotchas — #9.
Quick decision
Use Now() when:
- You need a single timestamp anchor for the current render. Capture once, reuse.
Use DateAdd with day units (not month/year) when:
- The arithmetic crosses any boundary that matters. "Last 90 days" beats "last 3 months" every time.
Use DateDiff with the smallest meaningful unit when:
- The boundary matters. "More than 30 days" → use hours (720) if you care about the boundary.
Use DatePart with numeric parts (year, month, day, hour) when:
- Comparing or branching. String parts (
weekday,monthname) are for display only.
Use FormatDate when:
- The output is rendered into the email. Always confirm locale separately.
Use DateParse with explicit locale/TZ flag when:
- Parsing strings from another system. Don't trust default parsing across data sources.
Do the work in SQL instead when:
- Computing "days since X" for a large audience. Pre-compute as a numeric column in a
de_email_*DE upstream; AMPscript reads the column literally. - Comparing dates across timezones. SQL has explicit TZ conversion functions and clearer arithmetic.
- The same date math runs for every recipient on a 50k send. One SQL run beats 50,000 AMPscript runs.
Related
- Basics — supported language surface
- String functions —
FormatDatereturns a string; the locale rules live there - MC AMPscript gotchas — see #9 (preview ≠ send for time-sensitive personalization)
- MC SSJS — Date functions — the sibling surface; SSJS inherits the JavaScript
Datemodel and addsPlatform.Function.SystemDateToLocalDateetc. - MC SQL — Date functions — the upstream where most date arithmetic should happen for large audiences
More AMPscript reference pages incoming: Math · Validation · Data Extension · Subscriber/Profile · Cloud-write · Encoding/Hashing · Style Guide.