diff --git a/docs/internals/frontends.rst b/docs/internals/frontends.rst index 0441caa53..b4ebf5eb1 100644 --- a/docs/internals/frontends.rst +++ b/docs/internals/frontends.rst @@ -209,9 +209,9 @@ Standard output *stdout* is different and more command-dependent than logging. Commands like :ref:`borg_info`, :ref:`borg_create` and :ref:`borg_list` implement a ``--json`` option which turns their regular output into a single JSON object. -Dates are formatted according to ISO-8601 in local time. Neither an explicit time zone nor microseconds -are specified *at this time* (subject to change). The equivalent strftime format string is '%Y-%m-%dT%H:%M:%S', -e.g. 2017-08-07T12:27:20. +Dates are formatted according to ISO 8601 in local time. No explicit time zone is specified *at this time* +(subject to change). The equivalent strftime format string is '%Y-%m-%dT%H:%M:%S.%f', +e.g. ``2017-08-07T12:27:20.123456``. The root object at least contains a *repository* key with an object containing: @@ -268,7 +268,7 @@ Example *borg info* output:: }, "repository": { "id": "0cbe6166b46627fd26b97f8831e2ca97584280a46714ef84d2b668daf8271a23", - "last_modified": "2017-08-07T12:27:20", + "last_modified": "2017-08-07T12:27:20.789123", "location": "/home/user/testrepo" }, "security_dir": "/home/user/.config/borg/security/0cbe6166b46627fd26b97f8831e2ca97584280a46714ef84d2b668daf8271a23", @@ -329,7 +329,7 @@ Example of a simple archive listing (``borg list --last 1 --json``):: { "id": "80cd07219ad725b3c5f665c1dcf119435c4dee1647a560ecac30f8d40221a46a", "name": "host-system-backup-2017-02-27", - "start": "2017-08-07T12:27:20" + "start": "2017-08-07T12:27:20.789123" } ], "encryption": { @@ -337,7 +337,7 @@ Example of a simple archive listing (``borg list --last 1 --json``):: }, "repository": { "id": "0cbe6166b46627fd26b97f8831e2ca97584280a46714ef84d2b668daf8271a23", - "last_modified": "2017-08-07T12:27:20", + "last_modified": "2017-08-07T12:27:20.789123", "location": "/home/user/repository" } } @@ -355,14 +355,14 @@ The same archive with more information (``borg info --last 1 --json``):: ], "comment": "", "duration": 5.641542, - "end": "2017-02-27T12:27:20", + "end": "2017-02-27T12:27:20.789123", "hostname": "host", "id": "80cd07219ad725b3c5f665c1dcf119435c4dee1647a560ecac30f8d40221a46a", "limits": { "max_archive_size": 0.0001330855110409714 }, "name": "host-system-backup-2017-02-27", - "start": "2017-02-27T12:27:20", + "start": "2017-02-27T12:27:20.789123", "stats": { "compressed_size": 1880961894, "deduplicated_size": 2791, @@ -388,7 +388,7 @@ The same archive with more information (``borg info --last 1 --json``):: }, "repository": { "id": "0cbe6166b46627fd26b97f8831e2ca97584280a46714ef84d2b668daf8271a23", - "last_modified": "2017-08-07T12:27:20", + "last_modified": "2017-08-07T12:27:20.789123", "location": "/home/user/repository" } } @@ -406,8 +406,8 @@ Refer to the *borg list* documentation for the available keys and their meaning. Example (excerpt) of ``borg list --json-lines``:: - {"type": "d", "mode": "drwxr-xr-x", "user": "user", "group": "user", "uid": 1000, "gid": 1000, "path": "linux", "healthy": true, "source": "", "linktarget": "", "flags": null, "isomtime": "2017-02-27T12:27:20", "size": 0} - {"type": "d", "mode": "drwxr-xr-x", "user": "user", "group": "user", "uid": 1000, "gid": 1000, "path": "linux/baz", "healthy": true, "source": "", "linktarget": "", "flags": null, "isomtime": "2017-02-27T12:27:20", "size": 0} + {"type": "d", "mode": "drwxr-xr-x", "user": "user", "group": "user", "uid": 1000, "gid": 1000, "path": "linux", "healthy": true, "source": "", "linktarget": "", "flags": null, "mtime": "2017-02-27T12:27:20.023407", "size": 0} + {"type": "d", "mode": "drwxr-xr-x", "user": "user", "group": "user", "uid": 1000, "gid": 1000, "path": "linux/baz", "healthy": true, "source": "", "linktarget": "", "flags": null, "mtime": "2017-02-27T12:27:20.585407", "size": 0} .. _msgid: diff --git a/src/borg/helpers.py b/src/borg/helpers.py index 4b68eabe3..64ee4c82a 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -715,16 +715,19 @@ def safe_timestamp(item_timestamp_ns): return datetime.fromtimestamp(t_ns / 1e9) -def format_time(t): - """use ISO-8601-like date and time format (human readable, with wkday and blank date/time separator) +def format_time(ts: datetime): """ - return t.strftime('%a, %Y-%m-%d %H:%M:%S') + Convert *ts* to a human-friendly format with textual weekday. + """ + return ts.strftime('%a, %Y-%m-%d %H:%M:%S') -def isoformat_time(t): - """use ISO-8601 date and time format (machine readable, no wkday, no microseconds either) +def isoformat_time(ts: datetime): """ - return t.strftime('%Y-%m-%dT%H:%M:%S') # note: first make all datetime objects tz aware before adding %z here. + Format *ts* according to ISO 8601. + """ + # note: first make all datetime objects tz aware before adding %z here. + return ts.strftime('%Y-%m-%dT%H:%M:%S.%f') def format_timedelta(td): @@ -756,9 +759,11 @@ class OutputTimestamp: def __str__(self): return '{}'.format(self) - def to_json(self): + def isoformat(self): return isoformat_time(self.ts) + to_json = isoformat + def format_file_size(v, precision=2, sign=False): """Format file size into a human friendly format @@ -1895,7 +1900,7 @@ class ItemFormatter(BaseFormatter): return OutputTimestamp(safe_timestamp(item.get(key) or item.mtime)) def format_iso_time(self, key, item): - return self.format_time(key, item).to_json() + return self.format_time(key, item).isoformat() class ChunkIteratorFileWrapper: diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index a32cd6f8d..a333eca17 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -60,7 +60,7 @@ from . import key src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -ISO_FORMAT = '%Y-%m-%dT%H:%M:%S' +ISO_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' def exec_cmd(*args, archiver=None, fork=False, exe=None, input=b'', binary_output=False, **kw): @@ -1863,7 +1863,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): file1 = items[1] assert file1['path'] == 'input/file1' assert file1['size'] == 81920 - assert datetime.strptime(file1['isomtime'], ISO_FORMAT) # must not raise + assert datetime.strptime(file1['mtime'], ISO_FORMAT) # must not raise list_archive = self.cmd('list', '--json-lines', '--format={sha256}', self.repository_location + '::test') items = [json.loads(s) for s in list_archive.splitlines()]