{# 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': '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': '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': ['Milliseconde', 'Milliseconden', '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' } } ] %} {# 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, compare_date=now(), time=true, not_use=[]) -%} {# set defaults for variables #} {%- set date = date | as_local if time else date.date()-%} {%- set time = time | bool(true) -%} {%- set comp_date = compare_date if time else compare_date.date() -%} {%- set date_max = [comp_date, date] | max -%} {%- set date_min = [comp_date, date] | min -%} {#- set time periods in seconds #} {%- set m, h, d, w = 60, 3600, 86400, 604800 -%} {#- set numer of years, and set lowest 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 numer of months, and set lowest date using this number of months #} {%- if 'month' not in not_use -%} {%- set mth = (date_max.month - date_min.month - (1 if date_max.day < date_min.day else 0) + 12) % 12 -%} {%- set month_new = (((date_max.month - mth) + 12) % 12) | default(12, true) -%} {%- set day_max = ((date_max.replace(day=1, month=month_new) + timedelta(days=31)).replace(day=1) - timedelta(days=1)).day -%} {%- set extra_days = [0, date_max.day - day_max] | max -%} {%- set date_temp = date_max.replace(month=month_new, day=[date_max.day, day_max]|min) -%} {%- set date_max = date_temp if date_temp <= date_max else date_temp.replace(year=date_max.year-1) -%} {%- set mth = mth + yrs * 12 if 'year' in not_use else mth -%} {%- endif -%} {%- 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 yrs = 0 if 'year' in not_use else yrs -%} {#- set other time period variables #} {%- set s = (date_max - date_min).total_seconds() + extra_days | default(0) * 86400 -%} {%- set wks = 0 if 'week' in not_use else (s // w) | int -%} {%- set day = 0 if 'day' in not_use else ((s - wks * w) // d) | int -%} {%- set hrs = 0 if 'hour' in not_use else ((s - wks * w - day * d) // h) | int -%} {%- set min = 0 if 'minute' in not_use else ((s - wks * w - day * d - hrs * h) // m) | int -%} {%- set sec = 0 if 'second' in not_use else (s - wks * w - day * d - hrs * h - min * m) | int -%} {%- set ms = (s % 1 * 1000) | round | int -%} {# output result #} {%- set output = dict(year=yrs, month=mth | default(0), week=wks, day=day, hour=hrs, minute=min, second=sec, millisecond=ms) %} {{- dict(output.items() | rejectattr('0', 'in', not_use)) | to_json -}} {%- endmacro -%} {# macro to output a timedelta in a readable format #} {%- macro relative_time_plus(date, parts=1, abbr=false, verbose=false, language='en', compare_date=now(), month=none, week=none, millisecond=none, not_use=['millisecond'], always_show=[], time=true) -%} {#- set defaults for input if not entered #} {%- set date = date if date is datetime else date | as_datetime -%} {%- set compare_date = compare_date if compare_date is datetime else compare_date | as_datetime -%} {#- 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 -%} {#- perform smart stuff #} {%- if date is datetime and compare_date is datetime -%} {# determine not_use list #} {%- set abbr_to_full = dict(yr='year', mth='month', wk='week', hr='hour', min='minute', sec='second', ms='millisecond') -%} {%- set add = [('month', month),('week',week),('millisecond',millisecond)] | selectattr('1', 'eq', false) | map(attribute='0') | list -%} {%- set not_use = not_use if not_use is list else (not_use | replace(' ', '')).split(',') -%} {%- set not_use = (not_use + add) | unique | list -%} {%- if not_use | select('in', abbr_to_full) | list | count > 0 -%} {%- set ns = namespace(not_use=[]) -%} {%- for i in not_use -%} {%- set ns.not_use = ns.not_use + [abbr_to_full[i] | default(i)] -%} {%- endfor -%} {%- set not_use = ns.not_use | unique | list -%} {%- endif -%} {# set variables #} {%- set date, compare_date = date | as_local, compare_date | as_local -%} {%- set parts = parts | int(1) -%} {%- set time = time | bool(true) -%} {%- set abbr = abbr | bool(false) or verbose | bool(false) -%} {# split timedelta #} {%- set time_parts = time_split(date, compare_date, time, not_use) | from_json -%} {#- find first non zero time period #} {%- set time_periods = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'] -%} {%- set do_use = time_periods | reject('in', not_use) | list -%} {# continue if there are still time periods to use #} {%- if do_use | count > 0 -%} {%- set always_return = do_use | last -%} {%- set always_show = always_show if always_show is list else (always_show | replace(' ', '')).split(',') -%} {%- if always_show | select('in', abbr_to_full) | list | count > 0 -%} {%- set ns = namespace(always_show=[]) -%} {%- for i in always_show -%} {%- set ns.always_show = ns.always_show + [abbr_to_full[i] | default(i)] -%} {%- endfor -%} {%- set always_show = ns.always_show | unique | list -%} {%- endif -%} {%- set parts = [parts, always_show | count] | max -%} {%- set to_show = (time_parts.items() | selectattr('1') | map(attribute='0') | list + always_show) | unique | list | default([always_return], true) -%} {%- set first = do_use | select('in', to_show) | first -%} {#-select itemw to show based on input #} {%- set index_first = (time_parts.keys() | list).index(first) -%} {%- set items = (time_parts.keys() | list)[index_first:index_first + parts] -%} {# convert to phrases #} {%- set ns = namespace(phrases=[]) -%} {%- for i in items if i in to_show or i == first -%} {%- 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 -}} {%- else -%} All time periods are excluded {%- endif -%} {%- else -%} {{- phr.error -}} {%- endif -%} {%- endmacro -%}