Posts Home Assistant Template Macros: Date and Time
Post
Cancel

Home Assistant Template Macros: Date and Time

Disclosure: This article may contain affiliate links. If you decide to make a purchase, I'll make a small commission at no extra cost to you.

Over time I have created a large library of date and time manipulation code which are used in my automations and scripts. I plan to update this post with the snippets as I add to my library.

Date

Standard Examples

1
2
3
4
5
6
7
8
9
10
{% set date = as_timestamp(now())|timestamp_custom("%A %B %-d, %Y") %}
{% set datetime = as_timestamp(now()) | timestamp_custom("%I:%M:%S %p %b/%d/%Y", true) %}
{% set now_string = now().strftime("%Y-%m-%d") %}

{% set month_name = now().strftime("%B") %}
{% set day_name = now().strftime("%A")|lower %}

{% set this_month = now().month %}
{% set this_day = now().day %}
{% set this_year = now().year %}

timedelta()

1
2
3
4
5
# Calculate fifteen days ago.
{{ ( now() - timedelta(days=15) ).date() }}

# Calculate one week from today.
{{ ( now() + timedelta(weeks=1) ).date() }}

dayofweek_number(dayofweek)

ex. Get the number which represents Thursday. {{ dayofweek_number("Thursday") }}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{%- macro dayofweek_number(dayofweek) -%}
  {%- if dayofweek == "Sunday" or dayofweek == "Sun" -%}
    0
  {%- elif dayofweek == "Monday" or dayofweek == "Mon" -%}
    1
  {%- elif dayofweek == "Tuesday" or dayofweek == "Tue" -%}
    2
  {%- elif dayofweek == "Wednesday" or dayofweek == "Wed" -%}
    3
  {%- elif dayofweek == "Thursday" or dayofweek == "Thu" -%}
    4
  {%- elif dayofweek == "Friday" or dayofweek == "Fri" -%}
    5
  {%- elif dayofweek == "Saturday" or dayofweek == "Sat" -%}
    6
  {%- endif -%}
{%- endmacro -%}

last_dayofmonth(month, year)

ex. Get the last day of February 2020. {{ last_dayofmonth(2, 2020) }}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{%- macro last_dayofmonth(month, year) -%}
  {%- set daysinmonths = [31,28,31,30,31,30,31,31,30,31,30,31] -%}
  {%- set month = month|int -%}
  {%- set year = year|int -%}

  {# Simplified leap year calculation. See https://www.mathsisfun.com/leap-years.html #}
  {%- set isleapyear = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) -%}

  {%- set monthindex = month-1 -%}
  {%- if month == 2 and isleapyear -%}
    {{ daysinmonths[monthindex]+1 }}
  {%- else -%}
    {{ daysinmonths[monthindex] }}
  {%- endif -%}
{%- endmacro -%}

nth_dayofmonth(nth, dayofweek, month, year)

ex. Get the nth Monday of May.
1st: {{ nth_dayofmonth(1, "Monday", 5) }}
2nd: {{ nth_dayofmonth(2, "Monday", 5, 2020) }}
Last: {{ nth_dayofmonth("last", "Monday", 5) }}

Reference: bennadel.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{%- macro nth_dayofmonth(nth, dayofweek, month, year=now().year) -%}
  {%- set dayofweek = dayofweek_number(dayofweek)|int -%}
  {%- set firstdateofmonth = strptime(year ~"-"~ month ~"-1", "%Y-%m-%d") -%}
  {%- set firstdayofmonth = dayofweek_number(firstdateofmonth.strftime("%A"))|int -%}

  {# Determine the first occurrence of the day. #}
  {%- if firstdayofmonth == 1 -%}
    {%- set firstoccurrence = dayofweek -%}
  {%- elif firstdayofmonth < dayofweek -%}
    {%- set firstoccurrence = (dayofweek - dayofweek_number(firstdayofmonth)) -%}
  {%- else -%}
    {%- set firstoccurrence = (7 - firstdayofmonth + dayofweek) + 1 -%}
  {%- endif -%}

  {%- if nth is number -%}
    {# Determine the nth occurrence of the dayofweek. #}
    {%- set nthoccurrence = firstoccurrence + 7 * (nth-1) -%}
  {%- else -%}
    {#
    Determine the LAST occurrence of the dayofweek.

    Reference: https://cflib.org/udf/GetLastOccOfDayInMonth
    #}
    {%- set lastdayofmonth = last_dayofmonth(month, year)|int -%}
    {%- set lastdayname = strptime(year ~"-"~ month ~"-"~ lastdayofmonth, "%Y-%m-%d").strftime("%A") -%}
    {%- set lastdaynumber = dayofweek_number(lastdayname)|int -%}
    {%- set daydifference = lastdaynumber - dayofweek -%}

    {# Add a week if the result is negative. #}
    {%- if daydifference < 0 -%}
      {%- set daydifference = daydifference + 7 -%}
    {%- endif -%}

    {%- set nthoccurrence = lastdayofmonth - daydifference -%}
  {%- endif -%}

  {# Return the day with the month and year so it can be useful. #}
  {{ strptime(month ~"/"~ nthoccurrence ~"/"~ year, "%m/%d/%Y") }}
{%- endmacro -%}

Time

Standard Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{# Current time using templates instead of requiring sensor.time. #}
{% set current_time = "%02d:%02d:%02d"|format(now().hour, now().minute, now().second) %}

{# Convert datetime to a timestamp. #}
{% set current_time = as_timestamp(now())|timestamp_custom("%I:%M %p") %}
{% set unix_timestamp = as_timestamp(now())|int %}

{# 24 hour version #}
{% set hour = now().hour %}

{# 12 hour version #}
{% set hour = now().strftime("%I")|int %}
{% set hour = iif(now().hour>12, now().hour-12, now().hour) %}

{% set minutes = now().minute %}
{% set seconds = now().second %}
{% set ampm = now().strftime("%p") %}

Between hours

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Standard way
{% if 6 <= now().hour and now().hour < 12 %}
0.25
{% elif 12 <= now().hour and now().hour < 17 %}
0.40
{% else %}
0.20
{% endif %}

# Shorter way
{% if 6 <= now().hour < 12 %}
0.25
{% elif 12 <= now().hour < 17 %}
0.40
{% else %}
0.20
{% endif %}

Add or subtract time

1
2
3
4
5
# 15 minutes ago.
{{ as_timestamp(now()) - timedelta(minutes=15) }}

# 12 hours from now.
{{ as_timestamp(now()) + timedelta(hours=12) }}

Difference between two times

1
2
3
4
# How long has it been since the last update?
{% set seconds_difference = (as_timestamp(now()) - as_timestamp(last_update)) %}
{% set minutes_difference = (seconds_difference) / 60 %}
{% set hours_difference   = (seconds_difference) / 3600 %}

Add or subtract time

Pass a negative number to substract minutes instead of adding. add_minutes(start_time, -15)]

1
2
3
4
5
6
7
8
9
10
11
12
{% macro add_minutes(start_time, minutes_to_add) %}
  {%- set now_datetime = now() %}
  {%- set now_time = "%02d:%02d:%02d"|format(now_datetime.hour, now_datetime.minute, now_datetime.second) %}
  {%- set new_datetime = now_datetime | replace(now_time, start_time) %}
  {%- set new_datetime = as_datetime(new_datetime) + timedelta(minutes=minutes_to_add) %}
  {%- set new_time = "%02d:%02d:%02d"|format(new_datetime.hour, new_datetime.minute, new_datetime.second) %}
  {{ new_time }}
{% endmacro %}

{% set minutes_to_add = 5 %}
{% set boys = add_time(states("sensor.boys_room_next_alarm"), minutes_to_add) %}
{% set brian = add_time(states("sensor.wakeup_brian_time"), minutes_to_add) %}

Bonus: generate a list of dates

I use the template code below to generate a list of dates to add summer break to a school day sensor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{%- macro last_dayofmonth(month, year) -%}
  {%- set daysinmonths = [31,28,31,30,31,30,31,31,30,31,30,31] -%}
  {%- set month = month|default(0)|int -%}
  {%- set year = year|default(0)|int -%}

  {# Simplified leap year calculation. See https://www.mathsisfun.com/leap-years.html #}
  {%- set isleapyear = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) -%}

  {%- set monthindex = month-1 -%}
  {%- if month == 2 and isleapyear -%}
    {{ daysinmonths[monthindex]+1 }}
  {%- else -%}
    {{ daysinmonths[monthindex] }}
  {%- endif -%}
{%- endmacro -%}

{%- set year = now().year %}
{%- set months = [6,7,8] %}
{%- set days = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31] %}
{%- for month in months %}
  {%- set lastday = last_dayofmonth(month, year)|int %}
  {%- for day in days if day <= lastday %}
- "{{ year ~"-"~ "%02d"|format(month) ~"-"~ "%02d"|format(day) }}"
  {%- endfor %}
{%- endfor %}

configuration.yaml snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
binary_sensor:
  platform: workday
  name: School day
  country: US
  province: CA
  excludes: [sat, sun, holiday]
  remove_holidays:
    - Susan B. Anthony Day
  add_holidays:
    - "2022-04-04" # Spring recess
    - "2022-04-05"
    - "2022-04-06"
    - "2022-04-07"
    - "2022-04-08"
    - "2022-04-11"
    - "2022-04-12"
    - "2022-04-13"
    - "2022-04-14"
    - "2022-04-15"