From a1021094b589b4e277b351a95cf6a115be6ceba1 Mon Sep 17 00:00:00 2001 From: Tony Stork Date: Wed, 3 May 2023 01:04:55 -0400 Subject: [PATCH] Add a couple of genius macro collections to help with time calculations Time is my arch-nemesis. Now, I have help! --- custom_templates/easy_time.jinja | 1136 +++++++++++++++++++++ custom_templates/relative_time_plus.jinja | 187 ++++ 2 files changed, 1323 insertions(+) create mode 100644 custom_templates/easy_time.jinja create mode 100644 custom_templates/relative_time_plus.jinja diff --git a/custom_templates/easy_time.jinja b/custom_templates/easy_time.jinja new file mode 100644 index 0000000..179b41c --- /dev/null +++ b/custom_templates/easy_time.jinja @@ -0,0 +1,1136 @@ +{%- set default_language = 'en' %} +{%- set languages = { + 'en':{ + '_language': 'English', + 'and': 'and', + 'in': 'in', + 'ago': 'ago', + 'now': 'now', + 'lose': 'lose', + 'gain': 'gain', + 'time':{ + 'format': '12-hr', + 'year': [ + 'yr', + 'year', + 'years', + ], + 'week': [ + 'wk', + 'week', + 'weeks', + ], + 'day': [ + 'd', + 'day', + 'days', + ], + 'hour': [ + 'hr', + 'hour', + 'hours', + ], + 'minute': [ + 'min', + 'minute', + 'minutes', + ], + 'second': [ + 'sec', + 'second', + 'seconds', + ], + }, + 'delta':{ + 'today': 'today', + 'tomorrow': 'tomorrow', + 'yesterday': 'yesterday', + 'next': 'next', + 'last': 'last', + }, + 'days':[ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ], + 'months':[ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ] + }, + 'nl':{ + '_language': 'Nederlands', + 'and': 'en', + 'in': 'over', + 'ago': 'geleden', + 'now': 'nu', + 'lose': 'verliezen', + 'gain': 'krijgen', + 'time':{ + 'format': '24-hr', + 'year': [ + 'jr', + 'jaar', + 'jaar', + ], + 'week': [ + 'wk', + 'week', + 'weken', + ], + 'day': [ + 'd', + 'dag', + 'dagen', + ], + 'hour': [ + 'u', + 'uur', + 'uren', + ], + 'minute': [ + 'min', + 'minuut', + 'minuten', + ], + 'second': [ + 'sec', + 'seconde', + 'seconden', + ], + }, + 'delta':{ + 'today': 'vandaag', + 'tomorrow': 'morgen', + 'yesterday': 'gisteren', + }, + 'days':[ + "maandag", + "dinsdag", + "woensdag", + "donderdag", + "vrijdag", + "zaterdag", + "zondag", + ], + 'months':[ + 'januari', + 'februari', + 'maart', + 'april', + 'mei', + 'juni', + 'juli', + 'augustus', + 'september', + 'oktober', + 'november', + 'december', + ] + }, + 'sv':{ + '_language': 'Svenska', + 'and': 'och', + 'in': 'i', + 'ago': 'sedan', + 'now': 'nu', + 'lose': 'förlora', + 'gain': 'få', + 'time':{ + 'format': '24-hr', + 'year': [ + 'Å', + 'år', + 'år', + ], + 'week': [ + 'v', + 'vecka', + 'veckor', + ], + 'day': [ + 'd', + 'dag', + 'dagar', + ], + 'hour': [ + 't', + 'timme', + 'timmar', + ], + 'minute': [ + 'min', + 'minut', + 'minuter', + ], + 'second': [ + 'sek', + 'sekund', + 'sekunder', + ], + }, + 'delta':{ + 'today': 'idag', + 'tomorrow': 'imorgon', + 'yesterday': 'igår', + }, + 'days':[ + "måndag", + "tisdag", + "onsdag", + "torsdag", + "fredag", + "lördag", + "söndag", + ], + 'months':[ + 'januari', + 'februari', + 'mars', + 'april', + 'maj', + 'juni', + 'juli', + 'augusti', + 'september', + 'oktober', + 'november', + 'december', + ] + }, + 'de':{ + '_language': 'Deutsch', + 'and': 'und', + 'in': 'in', + 'ago': 'vor', + 'now': 'jetzt', + 'lose': 'Du verlierst', + 'gain': 'Du gewinnst', + 'time':{ + 'format': '24-hr', + 'year': [ + 'J', + 'Jahr', + 'Jahre', + ], + 'week': [ + 'W', + 'Woche', + 'Wochen', + ], + 'day': [ + 'T', + 'Tag', + 'Tage', + ], + 'hour': [ + 'h', + 'Stunde', + 'Stunden', + ], + 'minute': [ + 'm', + 'Minute', + 'Minuten', + ], + 'second': [ + 's', + 'Sekunde', + 'Sekunden', + ], + }, + 'delta':{ + 'today': 'Heute', + 'tomorrow': 'Morgen', + 'yesterday': 'Gestern', + }, + 'days':[ + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + "Sonntag", + ], + 'months':[ + 'Januar', + 'Februar', + 'März', + 'April', + 'Mai', + 'Juni', + 'Juli', + 'August', + 'September', + 'October', + 'November', + 'Dezember', + ] + }, + 'da':{ + '_language': 'Dansk', + 'and': 'og', + 'in': 'om', + 'ago': 'siden', + 'now': 'nu', + 'lose': 'mister', + 'gain': 'tilføjer', + 'time':{ + 'format': '24-hr', + 'year': [ + 'Å', + 'år', + 'år', + ], + 'week': [ + 'u', + 'uge', + 'uger', + ], + 'day': [ + 'd', + 'dag', + 'dage', + ], + 'hour': [ + 't', + 'time', + 'timer', + ], + 'minute': [ + 'min', + 'minut', + 'minutter', + ], + 'second': [ + 'sek', + 'sekund', + 'sekunder', + ], + }, + 'delta':{ + 'today': 'i dag', + 'tomorrow': 'i morgen', + 'yesterday': 'i går', + }, + 'days':[ + "mandag", + "tirsdag", + "onsdag", + "torsdag", + "fredag", + "lørdag", + "søndag", + ], + 'months':[ + 'januar', + 'februar', + 'marts', + 'april', + 'maj', + 'juni', + 'juli', + 'august', + 'september', + 'oktober', + 'november', + 'december', + ] + }, + 'fr':{ + '_language': 'Français', + 'and': 'et', + 'in': 'dans', + 'ago': 'il y a %s', + 'now': 'maintenant', + 'lose': 'perdre', + 'gain': 'gagner', + 'time':{ + 'format': '24-hr', + 'year': [ + 'an', + 'an', + 'ans', + ], + 'week': [ + 'sem.', + 'semaine', + 'semaines', + ], + 'day': [ + 'j', + 'jour', + 'jours', + ], + 'hour': [ + 'h', + 'heure', + 'heures', + ], + 'minute': [ + 'min', + 'minute', + 'minutes', + ], + 'second': [ + 's', + 'seconde', + 'secondes', + ], + }, + 'delta':{ + 'today': "aujourd'hui", + 'tomorrow': 'demain', + 'yesterday': 'hier', + }, + 'days':[ + "lundi", + "mardi", + "mercredi", + "jeudi", + "vendredi", + "samedi", + "dimanche", + ], + 'months':[ + 'janvier', + 'février', + 'mars', + 'avril', + 'mai', + 'juin', + 'juillet', + 'août', + 'septembre', + 'octobre', + 'novembre', + 'décembre', + ] + }, + 'es':{ + '_language': 'Español', + 'and': 'y', + 'in': 'en', + 'ago': 'hace', + 'now': 'ahora', + 'lose': 'perder', + 'gain': 'ganar', + 'time':{ + 'format': '24-hr', + 'year': [ + 'a', + 'año', + 'años', + ], + 'week': [ + 'sem.', + 'semana', + 'semanas', + ], + 'day': [ + 'd', + 'día', + 'días', + ], + 'hour': [ + 'h', + 'hora', + 'horas', + ], + 'minute': [ + 'm', + 'minuto', + 'minutos', + ], + 'second': [ + 's', + 'segundo', + 'segundos', + ], + }, + 'delta':{ + 'today': 'hoy', + 'tomorrow': 'mañana', + 'yesterday': 'ayer', + }, + 'days':[ + 'lunes', + 'martes', + 'miércoles', + 'jueves', + 'viernes', + 'sábado', + 'domingo' + ], + 'months':[ + 'enero', + 'febrero', + 'marzo', + 'abril', + 'mayo', + 'junio', + 'julio', + 'agosto' + 'septiembre', + 'octubre', + 'noviembre', + 'diciembre' + ] + }, + 'it':{ + '_language': 'Italiano', + 'and': 'e', + 'in': 'in', + 'ago': 'fa', + 'now': 'ora', + 'lose': 'perdita', + 'gain': 'guadagno', + 'time':{ + 'format': '24-hr', + 'year': [ + 'a.', + 'anno', + 'anni', + ], + 'week': [ + 'set.', + 'settimana', + 'settimane', + ], + 'day': [ + 'g', + 'giorno', + 'giorni', + ], + 'hour': [ + 'h', + 'ora', + 'ore', + ], + 'minute': [ + 'min', + 'minuto', + 'minuti', + ], + 'second': [ + 's', + 'secondo', + 'secondi', + ], + }, + 'delta':{ + 'today': 'oggi', + 'tomorrow': 'domani', + 'yesterday': 'ieri', + }, + 'days':[ + "lunedì", + "martedì", + "mercoledì", + "giovedì", + "venerdì", + "sabato", + "domenica", + ], + 'months':[ + 'gennaio', + 'febbraio', + 'marzo', + 'aprile', + 'maggio', + 'giugno', + 'luglio', + 'agosto', + 'settembre', + 'ottobre', + 'novembre', + 'dicembre', + ] + }, + 'pt':{ + '_language': 'Português', + 'and': 'e', + 'in': 'em', + 'ago': 'antes', + 'now': 'agora', + 'lose': 'perde', + 'gain': 'ganha', + 'time':{ + 'format': '24-hr', + 'year': [ + 'aa', + 'ano', + 'anos', + ], + 'week': [ + 'sem', + 'semana', + 'semanas', + ], + 'day': [ + 'd', + 'dia', + 'dias', + ], + 'hour': [ + 'h', + 'hora', + 'horas', + ], + 'minute': [ + 'min', + 'minuto', + 'minutos', + ], + 'second': [ + 'seg', + 'segundo', + 'segundos', + ], + }, + 'delta':{ + 'today': 'hoje', + 'tomorrow': 'amanhã', + 'yesterday': 'ontem', + 'next': 'seguinte', + 'last': 'anterior', + }, + 'days':[ + "Segunda", + "Terça", + "Quarta", + "Quinta", + "Sexta", + "Sábado", + "Somingo", + ], + 'months':[ + 'Janeiro', + 'Fevereiro', + 'Março', + 'Abril', + 'Maio', + 'Junho', + 'Julho', + 'Agosto', + 'Setembro', + 'Outubro', + 'Novembro', + 'Dezembro', + ] + } +} %} + +{# DO NOT MODIFY BELOW THIS LINE #} + +{% set _bad_value = '?' %} +{% set _durations = { + 'year': 31536000, + 'week': 604800, + 'day': 86400, + 'hour': 3600, + 'minute': 60, + 'second': 1, +} %} +{% set _periods = { + 'week': 52, + 'day': 7, + 'hour': 24, + 'minute': 60, + 'second': 60, +} %} +{%- set _duration_sensor = { + 'd': 86400, + 'h': 3600, + 'min': 60, + 's': 1, +} %} + +{% set _time_formats = { + '12-hr': '%-I:%M %p', + '24-hr': '%H:%M', +} %} +{% set _default_time_format = _time_formats.get('24-hr') %} + +{% set _default_values = _durations.keys() | list %} + +{%- macro _get_language_time_format(override, language) %} +{%- set lang_fmat = languages.get(language, {}).get('time', {}).get('format', _bad_value) %} +{%- set fmat = _time_formats.get(override, _default_time_format) if override in _time_formats.keys() else _time_formats.get(lang_fmat, _default_time_format) %} +{{- fmat }} +{%- endmacro %} + +{%- macro _delta_seconds(a, b) %} +{{- (b - a).total_seconds() | round(0) }} +{%- endmacro %} + +{#- this function isn't needed, I only created it to keep the 'delta' consistent with _delta_seconds #} +{%- macro _delta_timedelta(a, b) %} +{{- b - a }} +{%- endmacro %} + +{%- macro _to_utc(dt) -%} +{%- set time = now() -%} +{%- set offset = time.utcoffset() - time.dst() if time.dst() else time.utcoffset() -%} +{{- dt.astimezone(utcnow().tzinfo) + offset -}} +{%- endmacro -%} + +{%- macro _check_for_duration_sensor(input) %} +{%- if input is string and input | regex_search('^(?!.+__)(?!_)[\da-z_]+(? 0 or raw else '' -}} +{% endmacro %} + +{%- macro _time_dict(uptime_seconds) %} +{%- set ns = namespace(ret=[]) %} +{%- for item in _default_values %} + {%- set ns.ret = ns.ret + [(item, _get_period_value(item, uptime_seconds) | int)] %} +{%- endfor %} +{{- dict.from_keys(ns.ret) | to_json }} +{%- endmacro %} + +{%- macro _phrases(seconds, values, language, raw) %} +{%- set ns = namespace(ret=[]) %} +{#- ensure we have a list of values #} +{%- set values = [ values ] if values is string else values %} +{#- filter values to only have items in _default_values #} +{%- set values = values | select('in', _default_values) | list %} +{#- this should be a good list of values if the previous 2 filters didn't remove anything #} +{%- set values = _default_values | select('in', values) | list if values else _default_values %} +{%- set biggest = values | first | default %} +{%- set index = values.index(biggest) %} +{%- for item in values[index:] %} + {%- set phrase = _phrase(item, seconds, language, loop.first, raw) %} + {%- if phrase %} + {%- set ns.ret = ns.ret + [phrase] %} + {%- endif %} +{%- endfor %} +{{- ns.ret | to_json }} +{%- endmacro %} + +{# macro to generate a friendly phrase #} +{%- macro _just_time(seconds, language, values, biggest, raw=False) %} +{%- if seconds < 1 %} + {{- languages.get(language, {}).get('now', _bad_value) }} +{%- else %} + {%- set phrases = _phrases(seconds, values, language, raw) | from_json %} + {%- if phrases | length > 0 %} + {%- if biggest %} + {{- phrases[0] }} + {%- else %} + {%- set word_for_and = languages.get(language, {}).get('and', _bad_value) %} + {{- phrases[:-1] | join(', ') ~ ' ' ~ word_for_and ~ ' ' ~ phrases[-1] if phrases | length > 1 else phrases | first | default }} + {%- endif %} + {%- else %} + {{- _just_time(seconds, language, values, biggest, True) }} + {%- endif %} +{%- endif %} +{%- endmacro %} + +{# macro to generate a friendly time phrase #} +{%- macro _time(input, attribute, language, values, biggest, utc) %} +{%- set duration_sensor = _check_for_duration_sensor(input) %} +{%- if duration_sensor %} + {%- set seconds = as_timedelta(duration_sensor).total_seconds() %} + {{- _just_time(seconds, language, values, biggest) }} +{%- else %} + {%- set uptime = _to_datetime(input, attribute, utc) | as_datetime %} + {%- if uptime %} + {%- set value = _delta_seconds(now(), uptime) | int %} + {%- set seconds = value | abs %} + {{- _just_time(seconds, language, values, biggest) }} + {%- else %} + {{- _bad_value }} + {%- endif %} +{%- endif %} +{%- endmacro %} + +{%- macro _time_between(func, input1, attr1, utc1, input2, attr2, utc2) -%} +{%- set t1 = _to_datetime(input1, attr1, utc1) | as_datetime -%} +{%- set t2 = _to_datetime(input2, attr2, utc2) | as_datetime -%} +{%- if t1 is not none and t2 is not none -%} + {{- func(t1, t2) -}} +{%- else -%} + {{- None -}} +{%- endif -%} +{%- endmacro -%} + +{# Returns the most significant time. e.g. 3 hours #} +{%- macro easy_time(uptime, attribute=None, language=default_language, utc=False, max_period='year') %} +{%- set values = _default_values[_default_values.index(max_period):] if max_period in _default_values else _default_values %} +{{- _time(uptime, attribute, language, values, True, utc) }} +{%- endmacro %} + +{# Returns a large time phrase without missing any detail. e.g. 3 hours, 4 minutes and 1 second #} +{%- macro big_time(uptime, attribute=None, language=default_language, utc=False, max_period='year') %} +{%- set values = _default_values[_default_values.index(max_period):] if max_period in _default_values else _default_values %} +{{- _time(uptime, attribute, language, values, False, utc) }} +{%- endmacro %} + +{# Returns a time phrase with only the details you care about most of the time. e.g. 3 hours and 4 minutes #} +{%- macro custom_time(uptime, values='', language=default_language, utc=False) %} +{%- set args = values.replace(' ','').split(',') | list if values is string else [] %} +{%- if args %} + {{- _time(uptime, None, language, args, False, utc) }} +{%- else %} + {{- _time(uptime, attribute, language, _default_values, False, utc) }} +{%- endif %} +{%- endmacro %} + +{%- macro custom_time_attr(uptime, attribute, values='', language=default_language, utc=False) %} +{%- set args = values.replace(' ','').split(',') | list if values is string else [] %} +{%- if args %} + {{- _time(uptime, attribute, language, args, False, utc) }} +{%- else %} + {{- _time(uptime, attribute, language, _default_values, False, utc) }} +{%- endif %} +{%- endmacro %} + +{# macro to generate a friendly relative time phrase #} +{%- macro _relative_time(input, attribute, language, values, biggest, utc) %} +{%- set uptime = _to_datetime(input, attribute, utc) | as_datetime %} +{%- if uptime %} + {%- set value = _delta_seconds(now(), uptime) | int %} + {%- set seconds = value | abs %} + {%- set future = value / seconds > 0 %} + {%- set items = _just_time(seconds, language, values, biggest) %} + {%- if future %} + {{- languages.get(language, {}).get('in', _bad_value) }} {{ items }} + {%- else %} + {%- set t = languages.get(language, {}).get('ago', _bad_value) %} + {%- if '%s' in t %} + {{ t % items }} + {%- else %} + {{- items }} {{ languages.get(language, {}).get('ago', _bad_value) }} + {%- endif %} + {%- endif %} +{%- else %} + {{- _bad_value }} +{%- endif %} +{%- endmacro %} + +{# Returns the most significant time. e.g. in 3 hours #} +{%- macro easy_relative_time(uptime, attribute=None, language=default_language, utc=False, max_period='year') %} +{%- set values = _default_values[_default_values.index(max_period):] if max_period in _default_values else _default_values %} +{{- _relative_time(uptime, attribute, language, values, True, utc) }} +{%- endmacro %} + +{# Returns a large time phrase without missing any detail. e.g. in 3 hours, 4 minutes and 1 second #} +{%- macro big_relative_time(uptime, attribute=None, language=default_language, utc=False, max_period='year') %} +{%- set values = _default_values[_default_values.index(max_period):] if max_period in _default_values else _default_values %} +{{- _relative_time(uptime, attribute, language, values, False, utc) }} +{%- endmacro %} + +{# Returns a time phrase with only the details you care about most of the time. e.g. in 3 hours and 4 minutes #} +{%- macro custom_relative_time(uptime, values='', language=default_language, utc=False) %} +{%- set args = values.replace(' ','').split(',') | list if values is string else [] %} +{%- if args %} + {{- _relative_time(uptime, None, language, args, False, utc) }} +{%- else %} + {{- _relative_time(uptime, attribute, language, _default_values, False, utc) }} +{%- endif %} +{%- endmacro %} + +{%- macro custom_relative_time_attr(uptime, attribute, values='', language=default_language, utc=False) %} +{%- set args = values.replace(' ','').split(',') | list if values is string else [] %} +{%- if args %} + {{- _relative_time(uptime, attribute, language, args, False, utc) }} +{%- else %} + {{- _relative_time(uptime, attribute, language, _default_values, False, utc) }} +{%- endif %} +{%- endmacro %} + +{# create an iso timestamp from month number, week number, day number #} +{# Thanksgiving is 11, 4, 3 #} +{%- macro month_week_day(month, week, weekday) -%} +{%- set today = today_at() %} +{%- set temp = today.replace(month=month, day=1) %} +{%- set adjust = (weekday - temp.isoweekday()) % 7 %} +{%- set temp = temp + timedelta(days=adjust) -%} +{{ (temp + timedelta(weeks = week - 1)).isoformat() }} +{%- endmacro %} + +{# create an iso timestamp from month number, day number #} +{%- macro month_day(month, day) -%} +{{ today_at().replace(month=month, day=day).isoformat() }} +{%- endmacro %} + +{# create an iso timestamp on the last day of month number #} +{%- macro last_day_in_month(month, weekday) -%} +{%- set today = today_at() %} +{%- set temp = today.replace(month=month, day=1) + timedelta(days=31) %} +{%- set temp = temp.replace(day=1) - timedelta(days=7) %} +{%- set adjust = (day - temp.isoweekday()) % 7 %} +{%- set temp = temp + timedelta(days=adjust) -%} +{{- (temp).isoformat() }} +{%- endmacro %} + +{%- macro _next_weekday(weekday, offset) %} +{%- set wd = (weekday - 1) % 7 %} +{%- set t = today_at() %} +{{- t + timedelta(days=(wd - t.weekday()) + offset) }} +{%- endmacro %} + +{%- macro next_weekday(weekday) %} +{{- _next_weekday(weekday, 7) }} +{%- endmacro %} + +{%- macro this_weekday(weekday) %} +{{- _next_weekday(weekday, 0) }} +{%- endmacro %} + +{%- macro last_weekday(weekday) %} +{{- _next_weekday(weekday, -7) }} +{%- endmacro %} + +{%- macro days_in_month(month=None) %} +{%- set today = today_at() %} +{%- set input = month if month is not none else today.month %} +{%- set input = (input - 1) % 12 + 1 %} +{%- set t = today.replace(day=1, month=input) %} +{%- set inc = t.month + 1 %} +{%- set next_month = inc if inc <= 12 else 1 %} +{%- set next_year = t.year + 1 if inc > 12 else t.year %} +{%- set next = t.replace(month=next_month, year=next_year) %} +{{- (next - t).days }} +{%- endmacro %} + +{%- macro _days_offset_month(offset) %} +{{- days_in_month(today_at().month + offset) }} +{%- endmacro %} + +{%- macro days_next_month(offset=1) %} +{{- _days_offset_month(offset) }} +{%- endmacro %} + +{%- macro days_last_month(offset=1) %} +{{- _days_offset_month(-1 * offset) }} +{%- endmacro %} + +{# easter day as an iso timestamp #} +{%- macro easter() %} +{%- set year = now().year %} +{%- set special_years = ['1954', '1981', '2049', '2076'] %} +{%- set special_year_sub = 7 %} +{%- set a = year % 19 %} +{%- set b = year % 4 %} +{%- set c = year % 7 %} +{%- set d = (19 * a + 24) % 30 %} +{%- set e = ((2 * b) + (4 * c) + (6 * d) + 5) % 7 %} +{%- set day = (22 + d + e) - special_year_sub if year in special_years else 22 + d + e %} +{%- set month = 4 if day > 31 else 3 %} +{%- set day = day - 31 if day > 31 else day -%} +{{ today_at().replace(month=month, day=day).isoformat() }} +{%- endmacro %} + +{%- 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 %} + +{# next daylight savings time #} +{%- macro next_dst() %} +{%- 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)).isoformat() }} +{%- endmacro%} + +{%- macro _find_delta(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 %} + +{# What will happen next dst? #} +{% macro next_dst_phrase(language=default_language) %} +{%- set t = now().replace(hour=0, minute=0, second=0, microsecond=0) %} +{%- set delta = _find_delta(t, 'days', 365) | int %} +{%- if delta > 0 %} + {{- languages.get(language, {}).get('lose', _bad_value) }} {{ easy_time(delta | abs) }} +{%- else %} + {{- languages.get(language, {}).get('gain', _bad_value) }} {{ easy_time(delta | abs) }} +{%- endif %} +{%- endmacro %} + +{# days until next dst #} +{%- macro days_until_dst() %} +{%- set next = next_dst() | as_datetime %} +{{- (next - today_at()).days if next is not none else 0 }} +{%- endmacro %} + +{%- macro clock(fmat=None, language=default_language) %} +{{- now().strftime(_get_language_time_format(fmat, language)) }} +{%- endmacro %} + +{%- set _clock_icon = ['twelve','one','two','three','four','five','six','seven','eight','nine','ten','eleven'] %} + +{%- macro clock_icon(hour=None) -%} +{%- if hour is none or hour is not integer -%} + {%- set t = now() -%} + {%- set h = (t.hour + t.minute // 30) % 12 %} + {{- 'mdi:clock-time-' ~ _clock_icon[h] -}} +{%- else -%} + {{- 'mdi:clock-time-' ~ _clock_icon[hour % 12] -}} +{%- endif -%} +{%- endmacro -%} + +{%- macro month(month=None, language=default_language) %} +{%- set idx = (month - 1) % 12 if month is integer and month > 0 else now().month - 1 %} +{{- languages.get(language, {}).get('months', [_bad_value]*12)[idx] }} +{%- endmacro %} + +{%- macro weekday(weekday=None, language=default_language) %} +{%- set idx = (weekday - 1) % 7 if weekday is integer and weekday > 0 else now().weekday() %} +{{- languages.get(language, {}).get('days', [_bad_value]*7)[idx] }} +{%- endmacro %} + +{%- macro count_the_days(input, attr, utc=False) %} +{%- set input = _to_datetime(input, attr, utc) | as_datetime %} +{%- set midnight = today_at() %} +{{ (input - midnight).days }} +{%- endmacro %} + +{%- macro speak_the_days(input, attr, language=default_language, utc=False) %} +{%- set lang_fmat = languages.get(language, {}).get('delta', {}) %} +{%- set _days = {'-1': lang_fmat.get('yesterday', _bad_value), '0': lang_fmat.get('today', _bad_value), '1': lang_fmat.get('tomorrow', _bad_value)} %} +{%- set midnight = today_at() %} +{%- set ns = namespace(days=[]) %} +{%- for i in range(-7, 14) %} + {%- set strindex = i | string %} + {%- set prefix = lang_fmat.get('last', _bad_value) ~ ' ' if i < -1 else lang_fmat.get('next', _bad_value) ~ ' ' if i > 6 else '' %} + {%- set ns.days = ns.days + [ (strindex, prefix ~ _days.get(strindex, weekday(midnight.weekday() + i + 1, language))) ] %} +{%- endfor %} +{%- set collection = dict.from_keys(ns.days) %} +{%- set days = count_the_days(input, attr, utc) %} +{%- if days in collection %} + {{- collection.get(days) }} +{%- else %} + {%- set days = days | int %} + {%- set ret = _phrase('day', days | abs * 86400, language, 'day', True) %} + {%- if days > 0 %} + {{- languages.get(language, {}).get('in', _bad_value) }} {{ ret }} + {%- else %} + {%- set t = languages.get(language, {}).get('ago', _bad_value) %} + {%- if '%s' in t %} + {{- t % ret }} + {%- else %} + {{- ret }} {{ languages.get(language, {}).get('ago', _bad_value) }} + {%- endif %} + {%- endif %} +{%- endif %} +{%- endmacro %} \ No newline at end of file diff --git a/custom_templates/relative_time_plus.jinja b/custom_templates/relative_time_plus.jinja new file mode 100644 index 0000000..98d4636 --- /dev/null +++ b/custom_templates/relative_time_plus.jinja @@ -0,0 +1,187 @@ +{# + set phrases to be used in the relative_time_period macro + one list item per language, each time fraction contains a list with the singular, plural and verbose phrase + combine contains the text to combine the last time fraction, and error the text to display on wrong date input +#} +{%- set time_period_phrases = [ + { + 'language': 'en', + 'phrases': + { + 'year': ['year', 'years', 'yr'], + 'month': ['month', 'months', 'mth'], + 'week': ['week', 'weeks', 'wk'], + 'day': ['day', 'days', 'day'], + 'hour': ['hour', 'hours', 'hr'], + 'minute': ['minute', 'minutes', 'min'], + 'second': ['second', 'seconds', 'sec'], + 'combine': ' and ', + 'error': 'Incorrect date' + } + }, + { + 'language': 'fr', + 'phrases': + { + 'year': ['année', 'années', 'an'], + 'month': ['mois', 'mois', 'mois'], + 'week': ['semaine', 'semaines', 'sem'], + 'day': ['jour', 'jours', 'j'], + 'hour': ['heure', 'heures', 'h'], + 'minute': ['minute', 'minutes', 'min'], + 'second': ['seconde', 'secondes', 'sec'], + 'combine': ' et ', + 'error': 'Date non valide' + } + }, + { + 'language': 'it', + 'phrases': + { + 'year': ['anno', 'anni', 'aa'], + 'month': ['mese', 'mesi', 'mm'], + 'week': ['settimana', 'settimane', 'set'], + 'day': ['giorno', 'giorni', 'gg'], + 'hour': ['ora', 'ore', 'h'], + 'minute': ['minuto', 'minuti', 'min'], + 'second': ['secondo', 'secondi', 'sec'], + 'combine': ' e ', + 'error': 'Data non valida' + } + }, + { + 'language': 'nl', + 'phrases': + { + 'year': ['jaar', 'jaar', 'jr'], + 'month': ['maand', 'maanden', 'mnd'], + 'week': ['week', 'weken', 'wk'], + 'day': ['dag', 'dagen', 'dg'], + 'hour': ['uur', 'uren', 'u'], + 'minute': ['minuut', 'minuten', 'min'], + 'second': ['seconde', 'seconden', 'sec'], + 'combine': ' en ', + 'error': 'Incorrecte datum' + } + }, + { + 'language': 'de', + 'phrases': + { + 'year': ['Jahr', 'Jahre', 'J.'], + 'month': ['Monat', 'Monate', 'M.'], + 'week': ['Woche', 'Wochen', 'Wo.'], + 'day': ['Tag', 'Tage', 'Tg.'], + 'hour': ['Stunde', 'Stunden', 'Std.'], + 'minute': ['Minute', 'Minuten', 'Min.'], + 'second': ['Sekunde', 'Sekunden', 'Sek.'], + 'combine': ' und ', + 'error': 'Falsches Datum' + } + }, + { + 'language': 'pt', + 'phrases': + { + 'year': ['ano', 'anos', 'aa'], + 'month': ['mês', 'meses', 'mm'], + 'week': ['semana', 'semanas', 'sem'], + 'day': ['dia', 'dias', 'd'], + 'hour': ['hora', 'horas', 'h'], + 'minute': ['minuto', 'minutos', 'min'], + 'second': ['segundo', 'segundos', 'seg'], + 'combine': ' e ', + 'error': 'Data Inválida' + } + } + ] +%} + +{# + macro to split a timedelta in years, months, weeks, days, hours, minutes, seconds + used by the relative time plus macro, set up as a seperate macro so it can be reused +#} +{%- macro time_split(date, time, compare_date) %} + {# set defaults for variables #} + {%- set date = date | as_local %} + {%- set time = time | default(true) | bool(true) %} + {%- set n = compare_date if compare_date is defined else now() %} + {%- set n = n if time else today_at() %} + {%- set a = [n, date] | max %} + {%- set b = [n, date] | min %} + {#- set time periods in seconds #} + {%- set m, h, d, w = 60, 3600, 86400, 604800 %} + {#- set numer of years, and set n to value using this number of years #} + {%- set yrs = a.year - b.year - (1 if a.replace(year=b.year) < b else 0) %} + {%- set a = a.replace(year=a.year - yrs) %} + {#- set numer of months, and set n to value using this number of months #} + {%- set mth = (a.month - b.month - (1 if a.day < b.day else 0) + 12) % 12 %} + {%- set month_new = (((a.month - mth) + 12) % 12) | default(12, true) %} + {%- set day_max = ((a.replace(day=1, month=month_new) + timedelta(days=31)).replace(day=1) - timedelta(days=1)).day %} + {%- set a_temp = a.replace(month=month_new, day=[a.day, day_max]|min) %} + {%- set a = a_temp if a_temp <= a else a_temp.replace(year=a.year-1) %} + {#- set other time period variables #} + {%- set s = (a - b).total_seconds() %} + {%- set wks = (s // w) | int %} + {%- set day = ((s - wks * w) // d) | int %} + {%- set hrs = ((s - wks * w - day * d) // h) | int %} + {%- set min = ((s - wks * w - day * d - hrs * h) // m) | int %} + {%- set sec = (s - wks * w - day * d - hrs * h - min * m) | int %} + {# output result #} + {{ dict(y=yrs, mo=mth, w=wks, d=day, h=hrs, m=min, s=sec) | to_json }} +{%- endmacro %} + +{# macro to output a timedelta in a readable format #} +{%- macro relative_time_plus(date, parts, week, time, verbose, language, compare_date) %} + {#- set defaults for input if not entered #} + {%- set date = date | as_datetime if date is string or date is number else date %} + {%- set compare_date = compare_date if compare_date is defined else now() %} + {%- set compare_date = compare_date | as_datetime if compare_date is string or compare_date is number else compare_date %} + {%- set phrases = time_period_phrases %} + {#- select correct phrases bases on language input #} + {%- set language = language | default() %} + {%- set languages = phrases | map(attribute='language') | list %} + {%- set language = iif(language in languages, language, languages | first) %} + {%- set phr = phrases | selectattr('language', 'eq', language) | map(attribute='phrases') | list | first %} + {#- check for valid datetime (using as_timestamp) #} + {%- if as_timestamp(date, default='error') != 'error' %} + {%- set date = date | as_local %} + {%- set parts = parts | default(1) | int(1) %} + {%- set week = week | default(true) | bool(true) %} + {%- set time = time | default(true) | bool(true) %} + {%- set verbose = verbose | default(false) | bool(false) %} + {%- set language = language | default('first') %} + {%- set date = date if time else today_at().replace(year=date.year, month=date.month, day=date.day) %} + {%- set tp = time_split(date, time, compare_date) | from_json %} + {#- create mapping #} + {%- set wk = tp.w if week else 0 %} + {%- set dy = tp.d if week else tp.d + tp.w * 7 %} + {%- set dur = dict( + yrs = dict(a=tp.y, d=phr.year[2] if verbose else phr.year[1] if tp.y > 1 else phr.year[0]), + mth = dict(a=tp.mo, d=phr.month[2] if verbose else phr.month[1] if tp.mo > 1 else phr.month[0]), + wks = dict(a=wk, d=phr.week[2] if verbose else phr.week[1] if wk > 1 else phr.week[0]), + day = dict(a=dy, d=phr.day[2] if verbose else phr.day[1] if dy > 1 else phr.day[0]), + hrs = dict(a=tp.h, d=phr.hour[2] if verbose else phr.hour[1] if tp.h > 1 else phr.hour[0]), + min = dict(a=tp.m, d=phr.minute[2] if verbose else phr.minute[1] if tp.m > 1 else phr.minute[0]), + sec = dict(a=tp.s, d=phr.second[2] if verbose else phr.second[1] if tp.s > 1 else phr.second[0]) + ) + %} + {#- find first non zero time period #} + {%- set first = dur.items() | rejectattr('1.a', 'eq', 0) | map(attribute='0') | first %} + {#- set variable to reject weeks if set and find index of first non zero time period #} + {%- set week_reject = 'wks' if not week %} + {%- set index = (dur.keys() | reject('eq', week_reject) | list).index(first) %} + {#-select non zero items based on input #} + {%- set items = (dur.keys() | reject('eq', week_reject) | list)[index:index + parts] %} + {%- set selection = dur.items() | selectattr('0', 'in', items) | rejectattr('1.a', 'eq', 0) | list %} + {#- create list of texts per selected time period #} + {%- set ns = namespace(text = []) %} + {%- for i in selection -%} + {%- set ns.text = ns.text + [ i[1].a ~ ' ' ~ i[1].d] %} + {%- endfor %} + {#- join texts in a string, using phr.combine for the last item #} + {{ ns.text[:-1] | join(', ') ~ phr.combine ~ ns.text[-1] if ns.text | count > 1 else ns.text | first }} + {%- else %} + {{ phr.error }} + {%- endif %} +{%- endmacro %} \ No newline at end of file