Skip to content

Module 7: Dates and Date Formatting

Dates in Liquid are not a separate data type — they’re strings that the date filter knows how to interpret. When you apply the date filter, Liquid parses the string into a date, then reformats it according to the pattern you specify.

Excel analogy: This is like using =TEXT(A1, "MMMM DD, YYYY") to change how a date cell is displayed. The underlying date value doesn’t change — you’re just choosing a different way to show it.

Date fields from CRMs typically arrive in formats like "2026-01-15" or "2026-01-15T09:00:00Z". These are accurate but not customer-friendly. The date filter lets you transform them into natural, readable formats like “January 15, 2026” for display in narrations and on slides.


The date Filter — Formatting Dates for Display

Section titled “The date Filter — Formatting Dates for Display”
{% raw %}
{{ some_date | date: "FORMAT_STRING" }}
{% endraw %}

The format string uses special codes that start with % to represent different parts of a date:

Code Meaning Example
%B Full month name January
%b Abbreviated month name Jan
%m Month number (01–12) 01
%d Day of month (01–31) 15
%e Day of month (1–31, no leading zero) 5
%Y 4-digit year 2026
%y 2-digit year 26
%A Full weekday name Thursday
%a Abbreviated weekday name Thu
%H Hour in 24-hour format (00–23) 14
%I Hour in 12-hour format (01–12) 02
%M Minutes (00–59) 30
%S Seconds (00–59) 00
%p AM/PM PM
%s Unix timestamp (seconds since 1970-01-01) 1737000000
%w Day of week (0=Sunday, 6=Saturday) 4

Any text in the format string that is not a % code is included literally — so commas, spaces, slashes, and words like “of” pass through unchanged.

{% raw %}
{{ renewal_date | date: "%B %d, %Y" }} → January 15, 2026
{{ renewal_date | date: "%b %d, %Y" }} → Jan 15, 2026
{{ renewal_date | date: "%m/%d/%Y" }} → 01/15/2026
{{ renewal_date | date: "%Y-%m-%d" }} → 2026-01-15
{{ renewal_date | date: "%B %Y" }} → January 2026
{{ renewal_date | date: "%A, %B %d" }} → Thursday, January 15
{{ renewal_date | date: "%b %e, %Y" }} → Jan 5, 2026 (no leading zero on day)
{% endraw %}
{% raw %}
{%- assign renewal = renewal_date | default: "" -%}
Your contract renews on {{ renewal | date: "%B %d, %Y" }}.
{% else %}
We don't have a renewal date on file.
{% endraw %}

If renewal_date is "2026-09-15": Output: Your contract renews on September 15, 2026.

Mixing up %m (month number) and %M (minutes):

{% raw %}
{{ renewal_date | date: "%B %d, %Y at %m:%S" }}
{% endraw %}

%m gives you the month number (like 09), not the minutes. Use %M for minutes.

Use %M for minutes, %m for month:

{% raw %}
{{ renewal_date | date: "%B %d, %Y at %H:%M" }} → September 15, 2026 at 14:30
{% endraw %}

Forgetting quotes around the format string:

{% raw %}
{{ renewal_date | date: %B %d, %Y }}
{% endraw %}

Always quote the format string:

{% raw %}
{{ renewal_date | date: "%B %d, %Y" }}
{% endraw %}

Applying date to an empty or nil field without a guard:

{% raw %}
{{ renewal_date | date: "%B %d, %Y" }}
{% endraw %}

If renewal_date is nil, this may produce unexpected output or error in strict mode.

Guard with a blank check or default:

{% raw %}
{%- assign renewal = renewal_date | default: "" -%}
{% unless renewal == "" %}
Renews on {{ renewal | date: "%B %d, %Y" }}.
{% endunless %}
{% endraw %}

Working with the Current Date — "now" and "today"

Section titled “Working with the Current Date — "now" and "today"”

Liquid recognizes two special input values for the date filter:

  • "now" — the current date and time
  • "today" — the current date (equivalent to "now" in most implementations)
{% raw %}
{{ "now" | date: "%B %d, %Y" }} → March 23, 2026
{{ "today" | date: "%Y-%m-%d" }} → 2026-03-23
{{ "now" | date: "%A" }} → Monday
{% endraw %}

Real Cast example — benchmark date reference

Section titled “Real Cast example — benchmark date reference”
{% raw %}
{%- assign benchmarkMMYYYY = "now" | date: "%B %Y" -%}
Here are your metrics for the {{ benchmarkMMYYYY }} benchmark.
{% endraw %}

Output: Here are your metrics for the March 2026 benchmark.

Section titled “Real Cast example — dynamic copyright or timestamp”
{% raw %}
Data as of {{ "now" | date: "%B %d, %Y" }}.
{% endraw %}

Output: Data as of March 23, 2026.


A Unix timestamp is the number of seconds that have elapsed since January 1, 1970. It’s a single number (like 1711152000) that represents a specific moment in time. Unix timestamps are how Liquid performs date arithmetic — because once dates are numbers, you can subtract them, add to them, and divide the result to get days, hours, or months.

Excel analogy: Excel stores dates as numbers internally (days since January 1, 1900). When you subtract two dates, you get a number of days. Unix timestamps work the same way, but in seconds instead of days.

Liquid doesn’t have built-in “date difference” or “days between” functions. To calculate time differences — days until renewal, months since contract start, duration of a support ticket — you convert both dates to Unix timestamps, subtract, and then convert the difference in seconds to the unit you need.

Convert any date to a Unix timestamp:

{% raw %}
{{ "2026-01-15" | date: "%s" }} → 1736899200 (or similar)
{{ "now" | date: "%s" }} → current Unix timestamp
{% endraw %}

Convert seconds to useful units:

Seconds Equals
60 1 minute
3,600 1 hour
86,400 1 day
604,800 1 week
2,592,000 ~1 month (30 days)
31,536,000 ~1 year (365 days)

Date Math — Calculating Days Until Renewal

Section titled “Date Math — Calculating Days Until Renewal”

Here’s the most common date arithmetic pattern in Cast:

{% raw %}
{%- assign renewal_ts = renewal_date | date: "%s" | plus: 0 -%}
{%- assign now_ts = "now" | date: "%s" | plus: 0 -%}
{%- assign diff_seconds = renewal_ts | minus: now_ts -%}
{%- assign days_until = diff_seconds | divided_by: 86400 -%}
Your renewal is in {{ days_until }} days.
{% elsif days_until == 0 %}
Your renewal is today!
{% else %}
Your renewal was {{ days_until | abs }} days ago.
{% endraw %}

Let’s trace through this step by step:

  1. renewal_date | date: "%s" — converts the renewal date to a Unix timestamp (a number in seconds)
  2. "now" | date: "%s" — gets the current moment as a Unix timestamp
  3. renewal_ts | minus: now_ts — subtracts to get the difference in seconds
  4. diff_seconds | divided_by: 86400 — divides by 86,400 (seconds per day) to get days

If renewal_date is "2026-06-15" and today is March 23, 2026:

  • The difference is about 84 days
  • Output: Your renewal is in 84 days.

If the renewal date is in the past:

  • days_until is negative
  • We use | abs to make it positive for the “X days ago” message

The date: "%s" filter returns a string (e.g., "1736899200"). You must convert it to a number with | plus: 0 before doing arithmetic. Without this step, minus would operate on strings.


Date Math — Calculating Months and Years

Section titled “Date Math — Calculating Months and Years”

For longer time spans, divide by the appropriate number of seconds:

{% raw %}
{%- assign start_ts = contract_start_date | date: "%s" | plus: 0 -%}
{%- assign now_ts = "now" | date: "%s" | plus: 0 -%}
{%- assign diff = now_ts | minus: start_ts -%}
{%- assign days = diff | divided_by: 86400 -%}
{%- assign months = days | divided_by: 30 -%}
{%- assign years = days | divided_by: 365 -%}
You've been a customer for over {{ years }} years.
{% elsif months >= 1 %}
You've been a customer for {{ months }} months.
{% else %}
You've been a customer for {{ days }} days.
{% endraw %}

💡 These are approximate calculations (30 days per month, 365 days per year). For a more precise and polished version, see the CustomerSince snippet in Module 17.


Not all CRM date fields contain valid dates. A field might be empty, contain garbage text, or have a default value that looks like a date but isn’t meaningful.

The date: "%s" filter on an invalid input may produce the value -2208988800 (which represents the date December 30, 1899 — a common default for blank dates in some systems). The CustomerSince snippet checks for this:

{% raw %}
{%- assign dateStart = contract_start_date | date: '%s' -%}
{%- # Date is valid — proceed with calculations -%}
{% else %}
{%- assign timeFrame = 'Invalid Date Value' -%}
{% endraw %}

💡 Best practice: Always validate date fields before performing date math. Check for blank, nil, and the -2208988800 sentinel value.


For easy reference, here are all the formats you’re likely to use in Cast narrations:

Format String Output When to Use
"%B %d, %Y" January 15, 2026 Formal narration text
"%b %d, %Y" Jan 15, 2026 Shorter version for slides
"%m/%d/%Y" 01/15/2026 US-standard date format
"%d/%m/%Y" 15/01/2026 International date format
"%Y-%m-%d" 2026-01-15 ISO format (sorting, data)
"%B %Y" January 2026 Month-year only (benchmarks)
"%A, %B %d" Thursday, January 15 Day of week included
"%b %e, %Y" Jan 5, 2026 No leading zero on day
"%s" 1736899200 Unix timestamp (for math)
"%w" 4 Day of week number (for logic)

Try It Yourself — Module 7 Capstone Exercise

Section titled “Try It Yourself — Module 7 Capstone Exercise”

Exercise: You have two fields: qbr_date (value: "2026-05-10") and you want to display:

  1. The QBR date formatted as “May 10, 2026”
  2. How many days from today (March 23, 2026) until the QBR
  3. A message: “Your next QBR is in X days — that’s about Y weeks away.”
Click to reveal the answer
{% raw %}
{%- assign qbr = qbr_date | default: "" -%}
{% unless qbr == "" %}
{%- assign qbr_ts = qbr | date: "%s" | plus: 0 -%}
{%- assign now_ts = "now" | date: "%s" | plus: 0 -%}
{%- assign diff = qbr_ts | minus: now_ts -%}
{%- assign days_until = diff | divided_by: 86400 -%}
{%- assign weeks_until = days_until | divided_by: 7 -%}
Your next QBR is on {{ qbr | date: "%B %e, %Y" }}.
That's in {{ days_until }} days — about {{ weeks_until }} weeks away.
{% else %}
No QBR date on file.
{% endunless %}
{% endraw %}

Output: Your next QBR is on May 10, 2026. That's in 48 days — about 6 weeks away.

Key details:

  • date: "%s" | plus: 0 converts both dates to numeric timestamps
  • Subtracting gives seconds, dividing by 86400 gives days
  • Dividing days by 7 gives weeks (integer division rounds down)
  • The blank check prevents errors if no date is on file

You now know how to format, compare, and calculate with dates. In Module 8, you’ll learn about cast_titlecase — Cast’s custom filter for intelligently normalizing company names from CRM data.


📖 Official documentation: