Files
Home-Assistant-Configs/custom_templates/relative_time_plus.jinja

421 lines
19 KiB
Django/Jinja
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{#
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 abbriviated 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'],
'millisecond': ['millisecond', 'milliseconds', 'ms'],
'combine': 'and',
'error': 'Invalid date',
}
},
{
'language': 'pl',
'phrases': {
'year': ['rok', 'lat', 'r'],
'month': ['miesiąc', 'miesięcy', 'msc'],
'week': ['tydzień', 'tygodni', 'tyg'],
'day': ['dzień', 'dni', 'dzień'],
'hour': ['godzina', 'godzin', 'godz'],
'minute': ['minuta', 'minut', 'min'],
'second': ['sekunda', 'sekund', 'sek'],
'millisecond': ['milisekunda', 'milisekund', 'ms'],
'combine': 'i',
'error': 'Niepoprawna data',
}
},
{
'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'],
'millisecond': ['milliseconde', 'millisecondes', 'ms'],
'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'],
'millisecond': ['millisecondo', 'millisecondi', 'ms'],
'combine': 'e',
'error': 'Data non valida',
}
},
{
'language': 'nb',
'phrases': {
'year': ['år', 'år', 'år'],
'month': ['måned', 'måneder', 'mnd'],
'week': ['uke', 'uker', 'u'],
'day': ['dag', 'dager', 'd'],
'hour': ['time', 'timer', 't'],
'minute': ['minutt', 'minutter', 'min'],
'second': ['sekund', 'sekunder', 'sek'],
'millisecond': ['millisekund', 'millisekunder', 'ms'],
'combine': 'og',
'error': 'Ugyldig dato',
}
},
{
'language': 'nl',
'phrases': {
'year': ['jaar', 'jaar', 'jr'],
'month': ['maand', 'maanden', 'mnd'],
'week': ['week', 'weken', 'wk'],
'day': ['dag', 'dagen', 'dg'],
'hour': ['uur', 'uur', 'u'],
'minute': ['minuut', 'minuten', 'min'],
'second': ['seconde', 'seconden', 'sec'],
'millisecond': ['milliseconde', 'milliseconden', 'ms'],
'combine': 'en',
'error': 'Ongeldige datum',
}
},
{
'language': 'nn',
'phrases': {
'year': ['år', 'år', 'år'],
'month': ['månad', 'månader', 'mnd'],
'week': ['veke', 'veker', 'v'],
'day': ['dag', 'dagar', 'd'],
'hour': ['time', 'timar', 't'],
'minute': ['minutt', 'minutt', 'min'],
'second': ['sekund', 'sekund', 'sek'],
'millisecond': ['millisekund', 'millisekund', 'ms'],
'combine': 'og',
'error': 'Ugyldig dato',
}
},
{
'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.'],
'millisecond': ['Millisekunde', 'Millisekunden', 'ms'],
'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'],
'millisecond': ['millissegundo', 'millissegundos', 'ms'],
'combine': 'e',
'error': 'Data Inválida',
}
},
{
'language': 'dk',
'phrases': {
'year': ['år', 'år', 'år'],
'month': ['måned', 'måneder', 'mnd'],
'week': ['uge', 'uger', 'uge'],
'day': ['dag', 'dage', 'dag'],
'hour': ['time', 'timer', 't.'],
'minute': ['minut', 'minuter', 'min.'],
'second': ['sekund', 'sekunder', 'sek.'],
'millisecond': ['millisekund', 'millisekunder', 'ms.'],
'combine': 'og',
'error': 'Ugyldig dato',
}
},
{
'language': 'sv',
'phrases': {
'year': ['år', 'år', 'år'],
'month': ['månad', 'månader', 'mån'],
'week': ['vecka', 'veckor', 'v'],
'day': ['dag', 'dagar', 'dag'],
'hour': ['timme', 'timmar', 'tim'],
'minute': ['minut', 'minuter', 'min'],
'second': ['sekund', 'sekunder', 'sek'],
'millisecond': ['millisekund', 'millisekunder', 'ms'],
'combine': 'och',
'error': 'Ogiltigt datum',
}
},
{
'language': 'cs',
'phrases': {
'year': ['rok', 'roky', 'rok'],
'month': ['měsíc', 'měsíce', 'měs'],
'week': ['týden', 'týdny', 'týd'],
'day': ['den', 'dny', 'd'],
'hour': ['hodina', 'hodiny', 'hod'],
'minute': ['minuta', 'minuty', 'min'],
'second': ['sekunda', 'sekundy', 'sek'],
'millisecond': ['millisekunda', 'millisekundy', 'ms'],
'combine': 'a',
'error': 'špatný datum'
}
},
{
'language': 'fi',
'phrases': {
'year': ['vuosi', 'vuotta', 'v'],
'month': ['kuukausi', 'kuukautta', 'kk'],
'week': ['viikko', 'viikkoa', 'vk'],
'day': ['päivä', 'päivää', 'pv'],
'hour': ['tunti', 'tuntia', 't'],
'minute': ['minuutti', 'minuuttia', 'min'],
'second': ['sekunti', 'sekuntia', 's'],
'millisecond': ['millisekunti', 'millisekuntia', 'ms'],
'combine': 'ja',
'error': 'Väärä päivämäärä',
}
},
{
'language': 'ru',
'phrases': {
'year': ['год', 'года', 'г'],
'month': ['месяц', 'месяцы', 'м'],
'week': ['неделя', 'недели', 'н'],
'day': ['день', 'дни', 'д'],
'hour': ['час', 'часы', 'ч'],
'minute': ['минута', 'минут', 'м'],
'second': ['секунд', 'секунды', 'с'],
'millisecond': ['милисекунд', 'милисекунды', 'мс'],
'combine': 'и',
'error': 'Неверная дата',
}
},
{
'language': 'uk',
'phrases': {
'year': ['рік', 'років', 'р'],
'month': ['місяць', 'місяців', 'м'],
'week': ['тиждень', 'тижнів', 'тижд'],
'day': ['день', 'днів', 'дн'],
'hour': ['годину', 'годин', 'год'],
'minute': ['хвилину', 'хвилин', 'хв'],
'second': ['секунду', 'секунд', 'сек'],
'millisecond': ['мілісекунду', 'мілісекунд', 'мсек'],
'combine': 'та',
'error': 'Недійсна дата',
}
},
] -%}
{# macro to convert the abbreviated input for the not_use and always_show lists to the full time part names #}
{%- macro _abbr_to_full(input) -%}
{# determine not_use list #}
{%- set abbr_to_full = dict(yr='year', mth='month', wk='week', day='day', hr='hour', min='minute', sec='second', ms='millisecond') -%}
{%- set list = input if input is list else (input | replace(' ', '')).split(',') -%}
{%- if list | select('in', abbr_to_full) | list | count > 0 -%}
{%- set ns = namespace(output=[]) -%}
{%- for i in list -%}
{%- set ns.output = ns.output + [abbr_to_full[i] | default(i)] -%}
{%- endfor -%}
{%- set list = ns.output -%}
{%- endif -%}
{{- list | select('in', abbr_to_full.values()) | list | to_json -}}
{%- endmacro -%}
{#
macro to split a timedelta in years, months, weeks, days, hours, minutes, seconds and milliseconds
used by the relative time plus macro, set up as a seperate macro so it can be reused
#}
{%- macro time_split(date, parts=8, compare_date=now(), not_use=[], always_show=['all'], time=true, round_mode='floor') -%}
{#- set defaults for input if not entered #}
{%- set date = date if date is datetime else date | as_datetime('invalid') -%}
{%- set compare_date = compare_date if compare_date is datetime else compare_date | as_datetime('invalid') -%}
{%- set time = time | bool(true) -%}
{%- set parts = [parts | int(1), always_show | count] | max -%}
{# 1: check if date input is correct #}
{%- if date is datetime and compare_date is datetime -%}
{# convert date input to local or date only #}
{%- set date = date | as_local if time else (date | as_local).date() -%}
{%- set compare_date = compare_date | as_local if time else (compare_date | as_local).date() -%}
{# determine highest and lowest date #}
{%- set date_max = [compare_date, date] | max -%}
{%- set date_min = [compare_date, date] | min -%}
{#- set time periods in seconds #}
{%- set days_year = 365 + (1 if (compare_date.replace(month=3, day=1) - timedelta(days=1)).day == 29 else 0) -%}
{%- set days_month = ((compare_date.replace(day=20) + timedelta(days=20)).replace(day=1) - timedelta(days=1)).day -%}
{%- set dur = dict(
year=days_year * 86400000,
month=days_month * 86400000,
week=604800000,
day=86400000,
hour=3600000,
minute=60000,
second=1000,
millisecond=1,
)
-%}
{# make sure input for not_use and always_show is correct #}
{%- if not not_use in [['millisecond'], []] -%}
{%- set not_use = _abbr_to_full(not_use) | from_json -%}
{%- endif -%}
{%- set not_use = not_use + ['hour', 'minute', 'second', 'millisecond' ] if not time else not_use -%}
{%- if always_show in [['all'], 'all'] -%}
{%- set always_show = dur.keys() | list -%}
{%- elif always_show != [] -%}
{%- set always_show = _abbr_to_full(always_show) | from_json | reject('in' , not_use) | list -%}
{%- endif -%}
{%- set always_show = always_show | reject('in', not_use) | list -%}
{%- set do_use = dur.keys() | reject('in', not_use) | list -%}
{# determine number of milliseconds #}
{%- set ms = ((date_max - date_min).total_seconds() * 1000) | round(0, 'ceil')-%}
{# 2: Check if the number of milliseconds is more then the duration of the last time part to output #}
{%- if do_use and ms < dur[do_use|last] -%}
{{- {do_use | last: (ms / dur[do_use|last]) | round(0, round_mode)} | to_json -}}
{%- else -%}
{# check if it is needed to determine years #}
{%- if ms >= dur.day * 365 -%}
{#- set numer of years, and set highest date using this number of years #}
{%- set yrs = date_max.year - date_min.year - (1 if date_max.replace(year=date_min.year) < date_min else 0) -%}
{%- set date_max = date_max.replace(year=date_max.year - yrs) -%}
{%- set ms = (date_max - date_min).total_seconds() * 1000 -%}
{%- set ms_yrs = ms -%}
{%- endif -%}
{%- set yrs = yrs | default(0) -%}
{# check if it is needed to determine months #}
{%- set check_mth =
ms >= dur.day * 28
and 'month' in do_use
-%}
{%- if check_mth -%}
{#- set numer of months, and set highest date using this number of months #}
{%- set ns = namespace(dm=date_max, mth=0) -%}
{%- set dmd = ns.dm.day -%}
{%- for m in range(1, 12) -%}
{%- set dmm, dmy = ns.dm.month, ns.dm.year -%}
{%- set day_max = (ns.dm.replace(day=1) - timedelta(days=1)).day -%}
{%- set dm = ns.dm.replace(
month=12 if dmm == 1 else dmm - 1,
year=dmy - 1 if dmm == 1 else dmy,
day= [dmd, day_max] | min
)
-%}
{%- if dm < date_min -%}
{%- break -%}
{%- else -%}
{%- set ns.mth = ns.mth + 1 -%}
{%- set ns.dm = dm -%}
{%- endif -%}
{%- endfor -%}
{%- set date_max, mth = ns.dm, ns.mth -%}
{%- set mth = mth + yrs * 12 if 'year' in not_use else mth -%}
{%- set ms = (date_max - date_min).total_seconds() * 1000 -%}
{%- endif -%}
{%- set mth = mth | default(0) -%}
{# prepare for other time parts #}
{%- set date_max = date_max.replace(year=date_max.year + yrs) if 'year' in not_use and 'month' in not_use else date_max -%}
{#- set other time period variables #}
{%- set ms = ((date_max - date_min).total_seconds() * 1000 + extra_days | default(0) * dur.day) | round(0) -%}
{%- set wks = (ms // dur.week) | int -%}
{%- set days = ((ms % dur.week // dur.day) + (wks * dur.week / dur.day if 'week' in not_use else 0)) | int -%}
{%- set hrs = ((ms % dur.day // dur.hour) + (days * dur.day / dur.hour if 'day' in not_use else 0)) | int -%}
{%- set min = ((ms % dur.hour // dur.minute) + (hrs * dur.hour / dur.minute if 'hour' in not_use else 0)) | int -%}
{%- set sec = ((ms % dur.minute // dur.second) + (min * dur.minute / dur.second if 'minute' in not_use else 0)) | int -%}
{%- set mis = ((ms % dur.second) | round + (sec * dur.second if 'second' in not_use else 0)) | int -%}
{# prepare dict with ouput #}
{%- set output = dict(year=yrs, month=mth | default(0), week=wks, day=days, hour=hrs, minute=min, second=sec, millisecond=mis) -%}
{%- set output = dict(output.items() | selectattr('0', 'in', do_use)) -%}
{%- set keys = output.keys() | list -%}
{%- set with_value = output.items() | rejectattr('1', 'eq', 0) | map(attribute='0') | list -%}
{%- set first = with_value | first | default('millisecond') -%}
{%- set first = keys | select('in', always_show | default([], true) + [first]) | first -%}
{%- set to_use = keys[keys.index(first):] -%}
{%- set to_output = to_use[:parts] -%}
{%- set last = to_output | last |default('millisecond') -%}
{# 3: check if there is anything left to use #}
{%- if to_use -%}
{%- set to_output = to_use[:parts] -%}
{%- set always_return = to_output | last -%}
{# check if all values for always_show are included #}
{%- set as_check = always_show | reject('in', to_output) | list | count -%}
{%- if always_show and as_check > 0 -%}
{%- set to_reject = to_use | reject('in', to_output) | reject('in', always_show) | list -%}
{%- set not_use = not_use + to_output[as_check*-1:] + to_reject -%}
{%- set to_output = to_output | reject('in', not_use) | list + always_show -%}
{%- set to_output = keys | select('in', to_output) | list -%}
{%- set output = time_split(date, parts, compare_date, not_use, always_show, time, round_mode) | from_json -%}
{%- endif -%}
{# apply round if needed #}
{%- if round_mode in ['common', 'ceil'] and last != 'millisecond' -%}
{# determine first and last item with data #}
{%- set ms = ms_yrs if last == 'year' else ms -%}
{%- set remain = ms % dur[last] -%}
{%- set remain_part = remain / dur[last] -%}
{%- set to_round = 1 if remain_part >= 0.5 and round_mode == 'common' else remain_part | round(0, round_mode) -%}
{%- set sec_to_add = ((dur[last] + (dur.day if last in ['year', 'month'] else 1) - remain) | round(0, 'ceil') * to_round) / 1000 -%}
{%- set round_mode = 'floor' -%}
{%- set date_max = [compare_date, date] | max + timedelta(seconds=sec_to_add) -%}
{%- set date_min = [compare_date, date] | min -%}
{%- set output = time_split(date_max, parts, date_min, not_use, always_show, time, round_mode) | from_json -%}
{%- endif -%}
{# output result #}
{%- set zero_values = output.items() | selectattr('1', 'eq', 0) | map(attribute='0') | list -%}
{%- set reject_list = zero_values | reject('in', always_show) | list -%}
{{- dict(output.items() | selectattr('0', 'in', to_output) | rejectattr('0', 'in', reject_list)) | default({always_return: 0}, true) | to_json -}}
{%- else -%} {{- dict(error='No time parts left to output') | to_json -}}
{%- endif -%} {# 3 #}
{%- endif -%} {# 2 #}
{%- else -%} {{- dict(error='Invalid date input') | to_json -}}
{%- endif -%} {# 1 #}
{%- endmacro -%}
{# macro to output a timedelta in a readable format #}
{%- macro relative_time_plus(date, parts=1, abbr=false, language='en', compare_date=now(), not_use=['millisecond'], always_show=[], time=true, round_mode='floor') -%}
{#- select correct phrases bases on language input #}
{%- set phrases = _time_period_phrases -%}
{%- set languages = phrases | map(attribute='language') | list -%}
{%- set language = iif(language in languages, language, 'en') -%}
{%- set phr = phrases | selectattr('language', 'eq', language) | map(attribute='phrases') | list | first -%}
{%- set abbr = abbr | bool(false) -%}
{# split timedelta #}
{%- set time_parts = time_split(date, parts, compare_date, not_use, always_show, time, round_mode) | from_json -%}
{# check for error #}
{%- if 'error' in time_parts -%}
{{- time_parts['error'] -}}
{%- else -%}
{# convert to phrases #}
{%- set ns = namespace(phrases=[]) -%}
{%- for i in time_parts.keys() -%}
{%- set phr_abbr = phr[i][2] -%}
{%- set phr_verb = phr[i][1] if time_parts[i] != 1 else phr[i][0] -%}
{%- set phrase = '{} {}'.format(time_parts[i], phr_abbr if abbr else phr_verb) -%}
{%- set ns.phrases = ns.phrases + [phrase] -%}
{%- endfor -%}
{#- join phrases in a string, using phr.combine for the last item #}
{{- '{} {} {}'.format(ns.phrases[:-1] | join(', '), phr.combine, ns.phrases[-1]) if ns.phrases | count > 1 else ns.phrases | first -}}
{%- endif -%}
{%- endmacro -%}