diff --git a/changelogs/fragments/filter_strftime.yml b/changelogs/fragments/filter_strftime.yml new file mode 100644 index 00000000000..ddb4c511d8f --- /dev/null +++ b/changelogs/fragments/filter_strftime.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - filter - Use datetime.strftime instead of time.strftime in strftime (https://github.com/ansible/ansible/issues/86260). diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index 96329d5b5b2..dc514dee2cc 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -130,18 +130,21 @@ def to_datetime(string, format="%Y-%m-%d %H:%M:%S"): return datetime.datetime.strptime(string, format) -def strftime(string_format, second=None, utc=False): - """ return a date string using string. See https://docs.python.org/3/library/time.html#time.strftime for format """ - if utc: - timefn = time.gmtime +def strftime(string_format: str, second: float | None = None, utc: bool = False) -> str: + """ return a date string using string. See https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime for format """ + if second is None: + second = time.time() else: - timefn = time.localtime - if second is not None: try: second = float(second) except Exception: - raise AnsibleFilterError('Invalid value for epoch value (%s)' % second) - return time.strftime(string_format, timefn(second)) + raise AnsibleFilterError(f'Invalid value for epoch value ({second})') + + if utc: + datetime_obj = datetime.datetime.fromtimestamp(second, tz=datetime.timezone.utc) + else: + datetime_obj = datetime.datetime.fromtimestamp(second) + return datetime_obj.strftime(string_format) def quote(a): diff --git a/lib/ansible/plugins/filter/strftime.yml b/lib/ansible/plugins/filter/strftime.yml index d51e5817dc9..11d1f23e2d2 100644 --- a/lib/ansible/plugins/filter/strftime.yml +++ b/lib/ansible/plugins/filter/strftime.yml @@ -1,30 +1,37 @@ DOCUMENTATION: name: strftime version_added: "2.4" - short_description: date formatting + short_description: Returns date and/or time description: - - Using Python's C(strftime) function, take a data formatting string and a date/time to create a formatted date. + - Using Python's L(strftime, https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime) function, take a data formatting string and a date/time to create a formatted date. notes: - - This is a passthrough to Python's C(stftime), for a complete set of formatting options go to https://strftime.org/. + - This is a passthrough to Python's C(datetime.datetime.strftime), for a complete set of formatting options go to L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). + - Before Ansible Core 2.21, this was based upon C(time.strftime) Python API. positional: _input, second, utc options: _input: description: - - A formatting string following C(stftime) conventions. + - A formatting string following C(strftime) conventions. + - Some additional directives are not supported by all platforms. See Python documentation for more details. - See L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) for a reference. type: str required: true second: - description: Datetime in seconds from C(epoch) to format, if not supplied C(gmttime/localtime) will be used. + description: + - A datetime in seconds from C(epoch) to format. + - If not supplied, the current time based on C(time.time()) will be used. type: int utc: - description: Whether time supplied is in UTC. + description: + - Whether time supplied is in UTC. + - If C(utc) is true, the time will be in UTC, otherwise it will be in the local timezone. + - Before Ansible Core 2.21, this was based upon C(time.strftime) Python API. type: bool default: false version_added: '2.14' EXAMPLES: | - # for a complete set of features go to https://strftime.org/ + # for a complete set of features go to https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes # Display year-month-day {{ '%Y-%m-%d' | strftime }} diff --git a/test/integration/targets/filter_core/tasks/main.yml b/test/integration/targets/filter_core/tasks/main.yml index a438c458a02..4f538cf12ca 100644 --- a/test/integration/targets/filter_core/tasks/main.yml +++ b/test/integration/targets/filter_core/tasks/main.yml @@ -384,7 +384,8 @@ that: - '"%Y-%m-%d"|strftime(1585247522) == "2020-03-26"' - '"%Y-%m-%d"|strftime("1585247522.0") == "2020-03-26"' - - '"%Y-%m-%d"|strftime("1585247522.0", utc=True) == "2020-03-26"' + - '"%Y-%m-%d%z"|strftime("1585247522.0", utc=True) == "2020-03-26+0000"' + - '"%:z"|strftime(None, utc=True) == "+00:00"' - '("%Y"|strftime(None)).startswith("20")' # Current date, can't check much there. - strftime_fail is failed - '"Invalid value for epoch value" in strftime_fail.msg'