Here is a template sensor that contains the next daylight savings time. You can use it to get the next time change. Please let me know if it doesn’t work for your time zone, also please verify it against your current time changes. It should get the time change down to the minute.
No Internet Required
Template Sensor
Paste this code into configuration.yaml. If you already have a template section, remove the template: line when pasting.
template:
- trigger:
- platform: time
at: "00:00:00"
- platform: homeassistant
event: start
- platform: event
event_type: call_service
event_data:
domain: template
service: reload
sensor:
- unique_id: dst_next
name: "Daylight Savings: Next"
device_class: timestamp
state: >
{%- macro hms(t) %}
{%- set dststr = t.dst() | string if t.dst() is not none else "00:00:00" %}
{%- set h, m, s = dststr.split(':') | map('int') %}
{{- h * 60 * 60 + m * 60 + s }}
{%- endmacro %}
{%- macro is_dst(t) %}
{{- hms(t) | int != 0 }}
{%- endmacro %}
{%- macro finddst(t, kwarg, rng) %}
{%- set ns = namespace(previous=is_dst(t), found=none) %}
{%- for i in range(rng) %}
{%- set ts = t + timedelta(**{kwarg:i}) %}
{%- if ns.previous != is_dst(ts) and ns.found is none %}
{%- set ns.found = i %}
{%- endif %}
{%- endfor %}
{{- ns.found }}
{%- endmacro %}
{%- set t = now().replace(hour=0, minute=0, second=0, microsecond=0) %}
{%- set d = finddst(t, 'days', 366) | int - 1 %}
{%- set h = finddst(t + timedelta(days=d), 'hours', 25) | int - 1 %}
{%- set m = finddst(t + timedelta(days=d, hours=h), 'minutes', 61) | int %}
{{ (t + timedelta(days=d, hours=h, minutes=m)) }}
- unique_id: dst_phrase
name: "Daylight Savings: Phrase"
state: >
{%- macro hms(t) %}
{%- set dststr = t.dst() | string if t.dst() is not none else "00:00:00" %}
{%- set h, m, s = dststr.split(':') | map('int') %}
{{- h * 60 * 60 + m * 60 + s }}
{%- endmacro %}
{%- macro phrase(seconds, name, divisor, mod=None) %}
{%- set value = ((seconds | abs // divisor) % (mod if mod else divisor)) | int %}
{%- set end = 's' if value > 1 else '' %}
{{- '{} {}{}'.format(value, name, end) if value > 0 else '' }}
{%- endmacro %}
{%- macro total(seconds) %}
{%- set values = [
phrase(seconds, 'hour', 60*60, 60*60*24),
phrase(seconds, 'minute', 60, 60),
] | select('!=','') | list %}
{{- values[:-1] | join(', ') ~ ' and ' ~ values[-1] if values | length > 1 else values | first | default}}
{%- endmacro %}
{%- macro finddelta(t, kwarg, rng) %}
{%- set ns = namespace(previous=hms(t), found=none) %}
{%- for i in range(rng) %}
{%- set ts = t + timedelta(**{kwarg:i}) %}
{%- if ns.previous != hms(ts) and ns.found is none %}
{%- set before = hms(ts - timedelta(days=1)) | int %}
{%- set after = hms(ts) | int %}
{%- set ns.found = after - before %}
{%- endif %}
{%- endfor %}
{{ ns.found }}
{%- endmacro %}
{%- set t = now().replace(hour=0, minute=0, second=0, microsecond=0) %}
{%- set delta = finddelta(t, 'days', 365) | int %}
{% if delta > 0 %}
lose {{ total(delta | abs) }}
{% else %}
gain {{ total(delta | abs) }}
{% endif %}
- sensor:
- unique_id: dst_days
name: "Daylight Savings: Days Until"
unit_of_measurement: days
state: >
{% set next = states('sensor.daylight_savings_next') | as_datetime %}
{% set today = now().replace(hour=0, minute=0, second=0, microsecond=0) %}
{{ (next - today).days if next is not none else 0 }}
availability: >
{{ states('sensor.daylight_savings_next') not in ['unknown', 'unavailable'] }}
You can also watch my live changes to this sensor here.
Requires Internet
Special thanks to @nickrout for pointing out the resource.
Rest Sensors
rest:
- resource_template: "http://worldtimeapi.org/api/timezone/{{ now().tzinfo }}"
sensor:
- name: "Daylight Savings: Next"
value_template: >
{{ ((value_json.dst_from, value_json.dst_until) | map('as_datetime') | map('as_local') | select('>', now()) | first).isoformat() }}
device_class: timestamp
- name: "Daylight Savings: Days Until"
value_template: >
{%- set next = (value_json.dst_from, value_json.dst_until) | map('as_datetime') | map('as_local') | select('>', now()) | first %}
{{ (next-today_at()).days }}
unit_of_measurement: days
- name: "Daylight Savings: Phrase"
value_template: >
{%- macro phrase(seconds, name, divisor, mod=None) %}
{%- set value = ((seconds | abs // divisor) % (mod if mod else divisor)) | int %}
{%- set end = 's' if value > 1 else '' %}
{{- '{} {}{}'.format(value, name, end) if value > 0 else '' }}
{%- endmacro %}
{% set next = (value_json.dst_from, value_json.dst_until) | select('>', utcnow().isoformat()) | first %}
{% set name, value = value_json.items() | list | selectattr('1', '==', next) | first %}
{% set seconds = value_json.dst_offset %}
{%- set values = [
phrase(seconds, 'hour', 60*60, 60*60*24),
phrase(seconds, 'minute', 60, 60),
] | select('!=','') | list %}
{{ 'gain' if name == 'dst_until' else 'lose' }} {{ values[:-1] | join(', ') ~ ' and ' ~ values[-1] if values | length > 1 else values | first }}
Paired Automation
This will notify you 7 days before and 1 day before dst.
- alias: Timed Event - DST Warning
id: dst_warning
trigger:
- platform: time
at: '10:00:00'
- platform: time
at: '19:00:00'
condition:
- condition: template
value_template: >
{{ states('sensor.daylight_savings_days_until') | int(0) in [7,1] }}
action:
- service: persistent_notification.create
data:
message: >
{%- set days = states('sensor.daylight_savings_days_until') | int(0) %}
{%- set plural = 's' if days | int(0) > 1 else '' %}
Daylight savings in {{ days }} day{{plural}}, you will {{ states('sensor.daylight_savings_phrase') }}!