From 8b5bba6fa15cf0dc303b39c964b27267f52389b3 Mon Sep 17 00:00:00 2001 From: Hideki Saito Date: Mon, 18 May 2026 10:43:04 +0900 Subject: [PATCH] Merge pull request #626 from pablomh/datetime_format (#732) plugins/callback/profile_tasks.py: Add option to provide a different date/time format SUMMARY The new datetime_format key will offer the possibility of providing a different date/time format than the default one ('%A %d %B %Y %H:%M:%S %z'). The iso8601 value can be used as an '%Y-%m-%dT%H:%M:%S.%f' alias (format of the ISO 8601 date/time standard). The code has changed from using the time API to the datetime one in order to support sub-second precision (needed by the ISO 8601 format, for example). Fixes: #279 ISSUE TYPE Feature Pull Request COMPONENT NAME plugins/callback/profile_tasks.py ADDITIONAL INFORMATION Output with no key which keeps current behavior: TASK [Import subscription manifest] ******************************************************************************************************************************************************************************************************************************************************************* Thursday 10 April 2025 00:52:11 +0200 (0:00:17.416) 0:00:17.453 ******** changed: [localhost] Output with datetime_format = 'iso8601': TASK [Import subscription manifest] ******************************************************************************************************************************************************************************************************************************************************************* 2025-04-10T00:55:19.967718 (0:00:15.664) 0:00:15.691 ******************** changed: [localhost] Output with datetime_format = '%Y-%m-%dT%H:%M:%S.%f%z' (ISO 8601 with UTC offset information): TASK [Import subscription manifest] ******************************************************************************************************************************************************************************************************************************************************************* 2025-04-10T00:57:49.290347+0200 (0:00:16.265) 0:00:16.293 *************** changed: [localhost] Reviewed-by: Hideki Saito (cherry picked from commit 1994b2cf1c0b41bb03806e8f3a6be1c1695c97b1) Co-authored-by: softwarefactory-project-zuul[bot] <33884098+softwarefactory-project-zuul[bot]@users.noreply.github.com> --- .../626_profile_tasks_datetime_format.yml | 3 ++ plugins/callback/profile_tasks.py | 48 +++++++++++++------ 2 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 changelogs/fragments/626_profile_tasks_datetime_format.yml diff --git a/changelogs/fragments/626_profile_tasks_datetime_format.yml b/changelogs/fragments/626_profile_tasks_datetime_format.yml new file mode 100644 index 0000000..3a60e55 --- /dev/null +++ b/changelogs/fragments/626_profile_tasks_datetime_format.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - profile_tasks - Add option to provide a different date/time format (https://github.com/ansible-collections/ansible.posix/issues/279). diff --git a/plugins/callback/profile_tasks.py b/plugins/callback/profile_tasks.py index c5857ca..2945bf9 100644 --- a/plugins/callback/profile_tasks.py +++ b/plugins/callback/profile_tasks.py @@ -52,6 +52,17 @@ DOCUMENTATION = ''' - section: callback_profile_tasks key: summary_only version_added: 1.5.0 + datetime_format: + description: + - Datetime format, as expected by the C(strftime) and C(strptime) methods. + An C(iso8601) alias will be translated to C('%Y-%m-%dT%H:%M:%S.%f') if that datetime standard wants to be used. + default: '%A %d %B %Y %H:%M:%S %z' + env: + - name: PROFILE_TASKS_DATETIME_FORMAT + ini: + - section: callback_profile_tasks + key: datetime_format + version_added: 3.0.0 ''' EXAMPLES = ''' @@ -72,14 +83,15 @@ sample output: > ''' import collections -import time + +from datetime import datetime from ansible.module_utils.six.moves import reduce from ansible.plugins.callback import CallbackBase # define start time -t0 = tn = time.time() +dt0 = dtn = datetime.now().astimezone() def secondsToStr(t): @@ -104,17 +116,18 @@ def filled(msg, fchar="*"): def timestamp(self): if self.current is not None: - elapsed = time.time() - self.stats[self.current]['started'] + elapsed = (datetime.now().astimezone() - self.stats[self.current]['started']).total_seconds() self.stats[self.current]['elapsed'] += elapsed -def tasktime(): - global tn - time_current = time.strftime('%A %d %B %Y %H:%M:%S %z') - time_elapsed = secondsToStr(time.time() - tn) - time_total_elapsed = secondsToStr(time.time() - t0) - tn = time.time() - return filled('%s (%s)%s%s' % (time_current, time_elapsed, ' ' * 7, time_total_elapsed)) +def tasktime(self): + global dtn + cdtn = datetime.now().astimezone() + datetime_current = cdtn.strftime(self.datetime_format) + time_elapsed = secondsToStr((cdtn - dtn).total_seconds()) + time_total_elapsed = secondsToStr((cdtn - dt0).total_seconds()) + dtn = cdtn + return filled('%s (%s)%s%s' % (datetime_current, time_elapsed, ' ' * 7, time_total_elapsed)) class CallbackModule(CallbackBase): @@ -134,6 +147,7 @@ class CallbackModule(CallbackBase): self.sort_order = None self.summary_only = None self.task_output_limit = None + self.datetime_format = None super(CallbackModule, self).__init__() @@ -159,9 +173,14 @@ class CallbackModule(CallbackBase): else: self.task_output_limit = int(self.task_output_limit) + self.datetime_format = self.get_option('datetime_format') + if self.datetime_format is not None: + if self.datetime_format == 'iso8601': + self.datetime_format = '%Y-%m-%dT%H:%M:%S.%f' + def _display_tasktime(self): if not self.summary_only: - self._display.display(tasktime()) + self._display.display(tasktime(self)) def _record_task(self, task): """ @@ -176,10 +195,11 @@ class CallbackModule(CallbackBase): # with the same UUID is executed when `serial` is specified in a playbook. # elapsed: Elapsed time since the first serialized task was started self.current = task._uuid + dtn = datetime.now().astimezone() if self.current not in self.stats: - self.stats[self.current] = {'started': time.time(), 'elapsed': 0.0, 'name': task.get_name()} + self.stats[self.current] = {'started': dtn, 'elapsed': 0.0, 'name': task.get_name()} else: - self.stats[self.current]['started'] = time.time() + self.stats[self.current]['started'] = dtn if self._display.verbosity >= 2: self.stats[self.current]['path'] = task.get_path() @@ -193,7 +213,7 @@ class CallbackModule(CallbackBase): # Align summary report header with other callback plugin summary self._display.banner("TASKS RECAP") - self._display.display(tasktime()) + self._display.display(tasktime(self)) self._display.display(filled("", fchar="=")) timestamp(self)