|
@@ -39,6 +39,12 @@ import msgpack.fallback
|
|
|
|
|
|
import socket
|
|
|
|
|
|
+# never use datetime.isoformat(), it is evil. always use one of these:
|
|
|
+# datetime.strftime(ISO_FORMAT) # output always includes .microseconds
|
|
|
+# datetime.strftime(ISO_FORMAT_NO_USECS) # output never includes microseconds
|
|
|
+ISO_FORMAT_NO_USECS = '%Y-%m-%dT%H:%M:%S'
|
|
|
+ISO_FORMAT = ISO_FORMAT_NO_USECS + '.%f'
|
|
|
+
|
|
|
# 20 MiB minus 41 bytes for a Repository header (because the "size" field in the Repository includes
|
|
|
# the header, and the total size was set to 20 MiB).
|
|
|
MAX_DATA_SIZE = 20971479
|
|
@@ -306,11 +312,11 @@ class Manifest:
|
|
|
self.config[b'tam_required'] = True
|
|
|
# self.timestamp needs to be strictly monotonically increasing. Clocks often are not set correctly
|
|
|
if self.timestamp is None:
|
|
|
- self.timestamp = datetime.utcnow().isoformat()
|
|
|
+ self.timestamp = datetime.utcnow().strftime(ISO_FORMAT)
|
|
|
else:
|
|
|
- prev_ts = datetime.strptime(self.timestamp, "%Y-%m-%dT%H:%M:%S.%f")
|
|
|
- incremented = (prev_ts + timedelta(microseconds=1)).isoformat()
|
|
|
- self.timestamp = max(incremented, datetime.utcnow().isoformat())
|
|
|
+ prev_ts = parse_timestamp(self.timestamp, tzinfo=None)
|
|
|
+ incremented = (prev_ts + timedelta(microseconds=1)).strftime(ISO_FORMAT)
|
|
|
+ self.timestamp = max(incremented, datetime.utcnow().strftime(ISO_FORMAT))
|
|
|
# include checks for limits as enforced by limited unpacker (used by load())
|
|
|
assert len(self.archives) <= MAX_ARCHIVES
|
|
|
assert all(len(name) <= 255 for name in self.archives)
|
|
@@ -485,12 +491,13 @@ def to_localtime(ts):
|
|
|
return datetime(*time.localtime((ts - datetime(1970, 1, 1, tzinfo=timezone.utc)).total_seconds())[:6])
|
|
|
|
|
|
|
|
|
-def parse_timestamp(timestamp):
|
|
|
+def parse_timestamp(timestamp, tzinfo=timezone.utc):
|
|
|
"""Parse a ISO 8601 timestamp string"""
|
|
|
- if '.' in timestamp: # microseconds might not be present
|
|
|
- return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f').replace(tzinfo=timezone.utc)
|
|
|
- else:
|
|
|
- return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc)
|
|
|
+ fmt = ISO_FORMAT if '.' in timestamp else ISO_FORMAT_NO_USECS
|
|
|
+ dt = datetime.strptime(timestamp, fmt)
|
|
|
+ if tzinfo is not None:
|
|
|
+ dt = dt.replace(tzinfo=tzinfo)
|
|
|
+ return dt
|
|
|
|
|
|
|
|
|
def load_excludes(fh):
|