mirror of
https://github.com/python/cpython
synced 2026-09-29 12:10:30 +03:00
gh-53502: add a new option aware_datetime in plistlib to loads or dumps aware datetime. (#113363)
* add options to loads and dumps aware datetime in plistlib
This commit is contained in:
parent
d0b0e3d2ef
commit
b4b2cc1012
4 changed files with 134 additions and 23 deletions
|
|
@ -140,7 +140,7 @@ def _decode_base64(s):
|
|||
_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z", re.ASCII)
|
||||
|
||||
|
||||
def _date_from_string(s):
|
||||
def _date_from_string(s, aware_datetime):
|
||||
order = ('year', 'month', 'day', 'hour', 'minute', 'second')
|
||||
gd = _dateParser.match(s).groupdict()
|
||||
lst = []
|
||||
|
|
@ -149,10 +149,14 @@ def _date_from_string(s):
|
|||
if val is None:
|
||||
break
|
||||
lst.append(int(val))
|
||||
if aware_datetime:
|
||||
return datetime.datetime(*lst, tzinfo=datetime.UTC)
|
||||
return datetime.datetime(*lst)
|
||||
|
||||
|
||||
def _date_to_string(d):
|
||||
def _date_to_string(d, aware_datetime):
|
||||
if aware_datetime:
|
||||
d = d.astimezone(datetime.UTC)
|
||||
return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
|
||||
d.year, d.month, d.day,
|
||||
d.hour, d.minute, d.second
|
||||
|
|
@ -171,11 +175,12 @@ def _escape(text):
|
|||
return text
|
||||
|
||||
class _PlistParser:
|
||||
def __init__(self, dict_type):
|
||||
def __init__(self, dict_type, aware_datetime=False):
|
||||
self.stack = []
|
||||
self.current_key = None
|
||||
self.root = None
|
||||
self._dict_type = dict_type
|
||||
self._aware_datetime = aware_datetime
|
||||
|
||||
def parse(self, fileobj):
|
||||
self.parser = ParserCreate()
|
||||
|
|
@ -277,7 +282,8 @@ class _PlistParser:
|
|||
self.add_object(_decode_base64(self.get_data()))
|
||||
|
||||
def end_date(self):
|
||||
self.add_object(_date_from_string(self.get_data()))
|
||||
self.add_object(_date_from_string(self.get_data(),
|
||||
aware_datetime=self._aware_datetime))
|
||||
|
||||
|
||||
class _DumbXMLWriter:
|
||||
|
|
@ -321,13 +327,14 @@ class _DumbXMLWriter:
|
|||
class _PlistWriter(_DumbXMLWriter):
|
||||
def __init__(
|
||||
self, file, indent_level=0, indent=b"\t", writeHeader=1,
|
||||
sort_keys=True, skipkeys=False):
|
||||
sort_keys=True, skipkeys=False, aware_datetime=False):
|
||||
|
||||
if writeHeader:
|
||||
file.write(PLISTHEADER)
|
||||
_DumbXMLWriter.__init__(self, file, indent_level, indent)
|
||||
self._sort_keys = sort_keys
|
||||
self._skipkeys = skipkeys
|
||||
self._aware_datetime = aware_datetime
|
||||
|
||||
def write(self, value):
|
||||
self.writeln("<plist version=\"1.0\">")
|
||||
|
|
@ -360,7 +367,8 @@ class _PlistWriter(_DumbXMLWriter):
|
|||
self.write_bytes(value)
|
||||
|
||||
elif isinstance(value, datetime.datetime):
|
||||
self.simple_element("date", _date_to_string(value))
|
||||
self.simple_element("date",
|
||||
_date_to_string(value, self._aware_datetime))
|
||||
|
||||
elif isinstance(value, (tuple, list)):
|
||||
self.write_array(value)
|
||||
|
|
@ -461,8 +469,9 @@ class _BinaryPlistParser:
|
|||
|
||||
see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
|
||||
"""
|
||||
def __init__(self, dict_type):
|
||||
def __init__(self, dict_type, aware_datetime=False):
|
||||
self._dict_type = dict_type
|
||||
self._aware_datime = aware_datetime
|
||||
|
||||
def parse(self, fp):
|
||||
try:
|
||||
|
|
@ -556,8 +565,11 @@ class _BinaryPlistParser:
|
|||
f = struct.unpack('>d', self._fp.read(8))[0]
|
||||
# timestamp 0 of binary plists corresponds to 1/1/2001
|
||||
# (year of Mac OS X 10.0), instead of 1/1/1970.
|
||||
result = (datetime.datetime(2001, 1, 1) +
|
||||
datetime.timedelta(seconds=f))
|
||||
if self._aware_datime:
|
||||
epoch = datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC)
|
||||
else:
|
||||
epoch = datetime.datetime(2001, 1, 1)
|
||||
result = epoch + datetime.timedelta(seconds=f)
|
||||
|
||||
elif tokenH == 0x40: # data
|
||||
s = self._get_size(tokenL)
|
||||
|
|
@ -629,10 +641,11 @@ def _count_to_size(count):
|
|||
_scalars = (str, int, float, datetime.datetime, bytes)
|
||||
|
||||
class _BinaryPlistWriter (object):
|
||||
def __init__(self, fp, sort_keys, skipkeys):
|
||||
def __init__(self, fp, sort_keys, skipkeys, aware_datetime=False):
|
||||
self._fp = fp
|
||||
self._sort_keys = sort_keys
|
||||
self._skipkeys = skipkeys
|
||||
self._aware_datetime = aware_datetime
|
||||
|
||||
def write(self, value):
|
||||
|
||||
|
|
@ -778,7 +791,12 @@ class _BinaryPlistWriter (object):
|
|||
self._fp.write(struct.pack('>Bd', 0x23, value))
|
||||
|
||||
elif isinstance(value, datetime.datetime):
|
||||
f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
|
||||
if self._aware_datetime:
|
||||
dt = value.astimezone(datetime.UTC)
|
||||
offset = dt - datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC)
|
||||
f = offset.total_seconds()
|
||||
else:
|
||||
f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
|
||||
self._fp.write(struct.pack('>Bd', 0x33, f))
|
||||
|
||||
elif isinstance(value, (bytes, bytearray)):
|
||||
|
|
@ -862,7 +880,7 @@ _FORMATS={
|
|||
}
|
||||
|
||||
|
||||
def load(fp, *, fmt=None, dict_type=dict):
|
||||
def load(fp, *, fmt=None, dict_type=dict, aware_datetime=False):
|
||||
"""Read a .plist file. 'fp' should be a readable and binary file object.
|
||||
Return the unpacked root object (which usually is a dictionary).
|
||||
"""
|
||||
|
|
@ -880,32 +898,36 @@ def load(fp, *, fmt=None, dict_type=dict):
|
|||
else:
|
||||
P = _FORMATS[fmt]['parser']
|
||||
|
||||
p = P(dict_type=dict_type)
|
||||
p = P(dict_type=dict_type, aware_datetime=aware_datetime)
|
||||
return p.parse(fp)
|
||||
|
||||
|
||||
def loads(value, *, fmt=None, dict_type=dict):
|
||||
def loads(value, *, fmt=None, dict_type=dict, aware_datetime=False):
|
||||
"""Read a .plist file from a bytes object.
|
||||
Return the unpacked root object (which usually is a dictionary).
|
||||
"""
|
||||
fp = BytesIO(value)
|
||||
return load(fp, fmt=fmt, dict_type=dict_type)
|
||||
return load(fp, fmt=fmt, dict_type=dict_type, aware_datetime=aware_datetime)
|
||||
|
||||
|
||||
def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False):
|
||||
def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False,
|
||||
aware_datetime=False):
|
||||
"""Write 'value' to a .plist file. 'fp' should be a writable,
|
||||
binary file object.
|
||||
"""
|
||||
if fmt not in _FORMATS:
|
||||
raise ValueError("Unsupported format: %r"%(fmt,))
|
||||
|
||||
writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys)
|
||||
writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys,
|
||||
aware_datetime=aware_datetime)
|
||||
writer.write(value)
|
||||
|
||||
|
||||
def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True):
|
||||
def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True,
|
||||
aware_datetime=False):
|
||||
"""Return a bytes object with the contents for a .plist file.
|
||||
"""
|
||||
fp = BytesIO()
|
||||
dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys)
|
||||
dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys,
|
||||
aware_datetime=aware_datetime)
|
||||
return fp.getvalue()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue