Working with dates and times
Dates and times come up constantly in Home Assistant templates. “How long ago did the door open?” “Is it past sunset?” “How many days until my next bin collection?” This page gathers the tools and patterns you need, in one place.
If you only need the current moment, start with now(). If you’re converting or formatting, jump to the formatting section. If you’re working with a timestamp from a sensor, head to the conversion section.
Getting the current time
now() and utcnow()
now() returns the current date and time in your configured time zone. utcnow() returns it in UTC.
Local: {{ now() }}
UTC: {{ utcnow() }}
Local: 2026-04-04 14:30:00.123456+02:00
UTC: 2026-04-04 12:30:00.123456+00:00
Templates that use now(), utcnow(), today_at, relative_time, time_since, or time_until update at the start of every minute, even when none of their entities change. This is how clock-based templates stay current. It applies where Home Assistant watches a template for changes, such as in template entities, template triggers, and the template editor. A template in an action only runs when the action runs. If you need something to refresh more often, base it on a state change instead.
today_at
today_at gives you a specific time on the current day, handy for “is it past bedtime?” checks.
{{ today_at('22:00') }}
2026-04-04 22:00:00+02:00
Accessing parts of a datetime
Once you have a datetime, you can pull out individual parts with dots.
Year: {{ now().year }}
Month: {{ now().month }}
Day: {{ now().day }}
Hour: {{ now().hour }}
Minute: {{ now().minute }}
Weekday: {{ now().weekday() }}
Year: 2026
Month: 4
Day: 4
Hour: 14
Minute: 30
Weekday: 5
weekday() is 0 for Monday through 6 for Sunday. If you prefer the other convention, isoweekday() gives you 1 for Monday through 7 for Sunday.
Formatting with strftime
strftime turns a datetime into text, shaped the way you want. This is the single most useful method for displaying dates and times.
24-hour: {{ now().strftime('%H:%M') }}
12-hour: {{ now().strftime('%I:%M %p') }}
Weekday: {{ now().strftime('%A') }}
Short date: {{ now().strftime('%Y-%m-%d') }}
Long date: {{ now().strftime('%A, %B %-d, %Y') }}
Sentence: {{ now().strftime('It is %A at %H:%M') }}
24-hour: 14:30
12-hour: 02:30 PM
Weekday: Saturday
Short date: 2026-04-04
Long date: Saturday, April 4, 2026
Sentence: It is Saturday at 14:30
Common format codes
Here are the ones you will use most often:
-
%Y: four-digit year (for example,2026) -
%m: zero-padded month (for example,04) -
%d: zero-padded day (for example,04) -
%-d: day without leading zero (for example,4) -
%H: hour in 24-hour format (for example,14) -
%M: minute (for example,30) -
%S: second (for example,00) -
%I: hour in 12-hour format (for example,02) -
%p:AMorPM -
%A: full weekday name (for example,Saturday) -
%a: short weekday name (for example,Sat) -
%B: full month name (for example,April) -
%b: short month name (for example,Apr)
The Python documentation has the full list of format codes if you need something unusual.
Weekday and month names from strftime, like the ones produced by %A and %B, are always in English, regardless of the language set in your Home Assistant profile. There is currently no built-in way to render them in another language.
Parsing text into a datetime with strptime
strptime is the reverse of strftime. It takes a piece of text and a format string, and gives you back a datetime.
{% set event = strptime('2026-12-25 10:30', '%Y-%m-%d %H:%M') %}
{{ event }}
2026-12-25 10:30:00
See the strptime reference for the full signature.
Converting between types
Sensors, APIs, and MQTT messages deliver timestamps in many different shapes. These functions convert between them.
UNIX timestamp to datetime
A UNIX timestamp is the number of seconds since January 1, 1970. Many APIs use them.
{% set ts = 1710510600 %}
Local: {{ ts | timestamp_local }}
UTC: {{ ts | timestamp_utc }}
Custom: {{ ts | timestamp_custom('%H:%M on %B %d') }}
Local: 2024-03-15T14:50:00+01:00
UTC: 2024-03-15T13:50:00+00:00
Custom: 14:50 on March 15
See timestamp_local, timestamp_utc, and timestamp_custom.
Datetime to UNIX timestamp
Use as_timestamp:
{{ as_timestamp(now()) | int }}
1743768600
Text to datetime
as_datetime is a forgiving version of strptime that tries to figure out common formats on its own.
{{ as_datetime('2026-04-04 14:30:00') }}
2026-04-04 14:30:00
Time differences
When you subtract two datetimes, you get a timedelta that represents the duration between them. Timedeltas support their own calculations and methods.
How long ago did something happen?
Every state has a last_changed timestamp. Subtract it from now() to get a timedelta.
{% set since = now() - states.binary_sensor.front_door.last_changed %}
Total seconds: {{ since.total_seconds() | int }}
Total minutes: {{ (since.total_seconds() / 60) | int }}
Total seconds: 900
Total minutes: 15
For a human-readable version, reach for time_since instead:
{{ time_since(states.binary_sensor.front_door.last_changed) }} ago
15 minutes ago
If you already have a timedelta object and want to display it in a readable format, use timedelta_string:
{% set open_for = trigger.to_state.last_changed - trigger.from_state.last_changed %}
Open for {{ timedelta_string(open_for, 2) }}
Open for 15 minutes 30 seconds
Is it more than X minutes?
A common pattern: trigger an alert when something has been in a state too long.
{{
(now() - states.binary_sensor.front_door.last_changed)
.total_seconds() > 600
}}
True (when the door has been in its state for more than 10 minutes)
Counting down to a future date
time_until turns a future datetime into a human-readable duration.
{% set birthday = strptime('2026-12-25', '%Y-%m-%d') %}
Christmas: {{ time_until(birthday) }}
Christmas: 8 months
Time zones
Home Assistant stores state timestamps (last_changed, last_updated) in UTC. now() returns the current time in your configured time zone, while utcnow() returns it in UTC.
If you need to compare datetimes, either both or neither need a time zone. Datetimes with different time zones can be compared directly, but comparing a datetime that has a time zone with one that doesn’t causes an error. as_datetime returns a datetime without a time zone when the text doesn’t include one, and strptime does the same unless the format includes %z. To add your time zone, use as_local, or convert both sides with as_timestamp and compare the numbers. timestamp_local and timestamp_utc return text, not datetimes, so they aren’t suited for comparing.
Common gotchas
-
Text comparison vs time comparison. Comparing date-looking text with
<or>compares alphabetically, not chronologically. Convert both sides to datetimes first. -
timestamp_customis not the only option. For datetime values (likenow()), use.strftime(...)directly.timestamp_customis specifically for UNIX timestamps. -
Six functions make a template update every minute. These are
now(),utcnow(),today_at,relative_time,time_since, andtime_until. For example,{{ time_since(states.binary_sensor.door.last_changed) }}updates every minute, not only when the door changes state. Don’t use them for things that should update more often. -
Subtraction gives you a timedelta, not seconds. Call
.total_seconds()on the result when you need a number. -
Date math uses the clock time.
now() + timedelta(days=1)gives the same clock time on the next day, even when daylight saving time starts or ends overnight. That day has 23 or 25 hours, andtimedelta(hours=24)gives the same result. To add an exact number of hours, start fromutcnow()instead. Subtracting two datetimes that both come fromnow()also ignores the change. To get the exact time between them, subtract theiras_timestampvalues. This gives you the number of seconds.
Next steps
- See the full list of date and time functions in the reference.
- For common date/time recipes, see Common template patterns.
- The Python methods page covers
.strftime(),.weekday(), and similar datetime methods.