diff --git a/plugins/filter/time.py b/plugins/filter/time.py new file mode 100644 index 0000000000..d1728fba27 --- /dev/null +++ b/plugins/filter/time.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, René Moser +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +from ansible.errors import AnsibleFilterError + + +UNIT_FACTORS = { + 'ms': [], + 's': [1000], + 'm': [1000, 60], + 'h': [1000, 60, 60], + 'd': [1000, 60, 60, 24], + 'w': [1000, 60, 60, 24, 7], + 'mo': [1000, 60, 60, 24, 30], + 'y': [1000, 60, 60, 24, 365], +} + + +UNIT_TO_SHORT_FORM = { + 'millisecond': 'ms', + 'milliseconds': 'ms', + 'msec': 'ms', + 'msecs': 'ms', + 'msecond': 'ms', + 'mseconds': 'ms', + 'sec': 's', + 'secs': 's', + 'second': 's', + 'seconds': 's', + 'hour': 'h', + 'hours': 'h', + 'min': 'm', + 'mins': 'm', + 'minute': 'm', + 'minutes': 'm', + 'day': 'd', + 'days': 'd', + 'week': 'w', + 'weeks': 'w', + 'month': 'mo', + 'months': 'mo', + 'year': 'y', + 'years': 'y', +} + + +def multiply(factors): + result = 1 + for factor in factors: + result = result * factor + return result + + +def divide(divisors): + result = 1 + for divisor in divisors: + result = result / divisor + return result + + +def to_time_unit(human_time, unit='ms'): + ''' Return a time unit from a human readable string ''' + unit = UNIT_TO_SHORT_FORM.get(unit, unit) + if unit not in UNIT_FACTORS: + available_units = sorted(list(UNIT_FACTORS.keys()) + list(UNIT_TO_SHORT_FORM.keys())) + raise AnsibleFilterError("to_time_unit() can not convert to the following unit: %s. " + "Available units: %s" % (unit, ', '.join(available_units))) + result = 0 + for h_time_string in human_time.split(): + res = re.match(r'(-?\d+)(\w+)', h_time_string) + if not res: + raise AnsibleFilterError( + "to_time_unit() can not interpret following string: %s" % human_time) + + h_time_int = int(res.group(1)) + h_time_unit = res.group(2) + + h_time_unit = UNIT_TO_SHORT_FORM.get(h_time_unit, h_time_unit) + if h_time_unit not in UNIT_FACTORS: + raise AnsibleFilterError( + "to_time_unit() can not interpret following string: %s" % human_time) + + time_in_milliseconds = h_time_int * multiply(UNIT_FACTORS[h_time_unit]) + result += time_in_milliseconds + return round(result * divide(UNIT_FACTORS[unit]), 12) + + +def to_milliseconds(human_time): + ''' Return milli seconds from a human readable string ''' + return to_time_unit(human_time, 'ms') + + +def to_seconds(human_time): + ''' Return seconds from a human readable string ''' + return to_time_unit(human_time, 's') + + +def to_minutes(human_time): + ''' Return minutes from a human readable string ''' + return to_time_unit(human_time, 'm') + + +def to_hours(human_time): + ''' Return hours from a human readable string ''' + return to_time_unit(human_time, 'h') + + +def to_days(human_time): + ''' Return days from a human readable string ''' + return to_time_unit(human_time, 'd') + + +def to_weeks(human_time): + ''' Return weeks from a human readable string ''' + return to_time_unit(human_time, 'w') + + +def to_months(human_time): + ''' Return months from a human readable string ''' + return to_time_unit(human_time, 'mo') + + +def to_years(human_time): + ''' Return years from a human readable string ''' + return to_time_unit(human_time, 'y') + + +class FilterModule(object): + ''' Ansible time jinja2 filters ''' + + def filters(self): + filters = { + 'to_time_unit': to_time_unit, + 'to_milliseconds': to_milliseconds, + 'to_seconds': to_seconds, + 'to_minutes': to_minutes, + 'to_hours': to_hours, + 'to_days': to_days, + 'to_weeks': to_weeks, + 'to_months': to_months, + 'to_years': to_years, + } + + return filters diff --git a/tests/integration/targets/filter_to_time_unit/aliases b/tests/integration/targets/filter_to_time_unit/aliases new file mode 100644 index 0000000000..f04737b845 --- /dev/null +++ b/tests/integration/targets/filter_to_time_unit/aliases @@ -0,0 +1,2 @@ +shippable/posix/group2 +skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/tests/integration/targets/filter_to_time_unit/tasks/main.yml b/tests/integration/targets/filter_to_time_unit/tasks/main.yml new file mode 100644 index 0000000000..355fc3b8e6 --- /dev/null +++ b/tests/integration/targets/filter_to_time_unit/tasks/main.yml @@ -0,0 +1,82 @@ +--- +- name: test to_milliseconds filter + assert: + that: + - "('1000ms' | community.general.to_milliseconds) == 1000" + - "('1s' | community.general.to_milliseconds) == 1000" + - "('1m' | community.general.to_milliseconds) == 60000" + +- name: test to_seconds filter + assert: + that: + - "('1000msecs' | community.general.to_seconds) == 1" + - "('1ms' | community.general.to_seconds) == 0.001" + - "('12m' | community.general.to_seconds) == 720" + - "('300minutes' | community.general.to_seconds) == 18000" + - "('3h 12m' | community.general.to_seconds) == 11520" + - "('2days 3hours 12mins 15secs' | community.general.to_seconds) == 184335" + - "('2d -2d -12s' | community.general.to_seconds) == -12" + +- name: test to_minutes filter + assert: + that: + - "('30s' | community.general.to_minutes) == 0.5" + - "('12m' | community.general.to_minutes) == 12" + - "('3h 72m' | community.general.to_minutes) == 252" + - "('300s' | community.general.to_minutes) == 5" + +- name: test to_hours filter + assert: + that: + - "('30m' | community.general.to_hours) == 0.5" + - "('3h 119m 61s' | community.general.to_hours) > 5" + +- name: test to_days filter + assert: + that: + - "('1year' | community.general.to_days) == 365" + - "('1week' | community.general.to_days) == 7" + - "('2weeks' | community.general.to_days) == 14" + - "('1mo' | community.general.to_days) == 30" + +- name: test to_weeks filter + assert: + that: + - "('1y' | community.general.to_weeks | int) == 52" + - "('7d' | community.general.to_weeks) == 1" + +- name: test to_months filter + assert: + that: + - "('30d' | community.general.to_months) == 1" + - "('1year' | community.general.to_months | int) == 12" + +- name: test to_years filter + assert: + that: + - "('365d' | community.general.to_years | int) == 1" + - "('12mo' | community.general.to_years | round(0, 'ceil')) == 1" + +- name: test fail unknown unit + debug: + msg: "{{ '1s' | community.general.to_time_unit('lightyears') }}" + ignore_errors: yes + register: res + +- name: verify test fail unknown unit + assert: + that: + - res is failed + - "'to_time_unit() can not convert to the following unit: lightyears' in res.msg" + +- name: test fail unknown string + debug: + msg: "{{ '1 s' | community.general.to_time_unit('s') }}" + ignore_errors: yes + register: res + +- name: test fail unknown string + assert: + that: + - res is failed + - "'to_time_unit() can not interpret following string' in res.msg"