@@ -140,7 +140,7 @@ def _decode_base64(s):
140140_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 )
141141
142142
143- def _date_from_string (s ):
143+ def _date_from_string (s , aware_datetime ):
144144 order = ('year' , 'month' , 'day' , 'hour' , 'minute' , 'second' )
145145 gd = _dateParser .match (s ).groupdict ()
146146 lst = []
@@ -149,10 +149,14 @@ def _date_from_string(s):
149149 if val is None :
150150 break
151151 lst .append (int (val ))
152+ if aware_datetime :
153+ return datetime .datetime (* lst , tzinfo = datetime .UTC )
152154 return datetime .datetime (* lst )
153155
154156
155- def _date_to_string (d ):
157+ def _date_to_string (d , aware_datetime ):
158+ if aware_datetime :
159+ d = d .astimezone (datetime .UTC )
156160 return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
157161 d .year , d .month , d .day ,
158162 d .hour , d .minute , d .second
@@ -171,11 +175,12 @@ def _escape(text):
171175 return text
172176
173177class _PlistParser :
174- def __init__ (self , dict_type ):
178+ def __init__ (self , dict_type , aware_datetime = False ):
175179 self .stack = []
176180 self .current_key = None
177181 self .root = None
178182 self ._dict_type = dict_type
183+ self ._aware_datetime = aware_datetime
179184
180185 def parse (self , fileobj ):
181186 self .parser = ParserCreate ()
@@ -277,7 +282,8 @@ def end_data(self):
277282 self .add_object (_decode_base64 (self .get_data ()))
278283
279284 def end_date (self ):
280- self .add_object (_date_from_string (self .get_data ()))
285+ self .add_object (_date_from_string (self .get_data (),
286+ aware_datetime = self ._aware_datetime ))
281287
282288
283289class _DumbXMLWriter :
@@ -321,13 +327,14 @@ def writeln(self, line):
321327class _PlistWriter (_DumbXMLWriter ):
322328 def __init__ (
323329 self , file , indent_level = 0 , indent = b"\t " , writeHeader = 1 ,
324- sort_keys = True , skipkeys = False ):
330+ sort_keys = True , skipkeys = False , aware_datetime = False ):
325331
326332 if writeHeader :
327333 file .write (PLISTHEADER )
328334 _DumbXMLWriter .__init__ (self , file , indent_level , indent )
329335 self ._sort_keys = sort_keys
330336 self ._skipkeys = skipkeys
337+ self ._aware_datetime = aware_datetime
331338
332339 def write (self , value ):
333340 self .writeln ("<plist version=\" 1.0\" >" )
@@ -360,7 +367,8 @@ def write_value(self, value):
360367 self .write_bytes (value )
361368
362369 elif isinstance (value , datetime .datetime ):
363- self .simple_element ("date" , _date_to_string (value ))
370+ self .simple_element ("date" ,
371+ _date_to_string (value , self ._aware_datetime ))
364372
365373 elif isinstance (value , (tuple , list )):
366374 self .write_array (value )
@@ -461,8 +469,9 @@ class _BinaryPlistParser:
461469
462470 see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
463471 """
464- def __init__ (self , dict_type ):
472+ def __init__ (self , dict_type , aware_datetime = False ):
465473 self ._dict_type = dict_type
474+ self ._aware_datime = aware_datetime
466475
467476 def parse (self , fp ):
468477 try :
@@ -556,8 +565,11 @@ def _read_object(self, ref):
556565 f = struct .unpack ('>d' , self ._fp .read (8 ))[0 ]
557566 # timestamp 0 of binary plists corresponds to 1/1/2001
558567 # (year of Mac OS X 10.0), instead of 1/1/1970.
559- result = (datetime .datetime (2001 , 1 , 1 ) +
560- datetime .timedelta (seconds = f ))
568+ if self ._aware_datime :
569+ epoch = datetime .datetime (2001 , 1 , 1 , tzinfo = datetime .UTC )
570+ else :
571+ epoch = datetime .datetime (2001 , 1 , 1 )
572+ result = epoch + datetime .timedelta (seconds = f )
561573
562574 elif tokenH == 0x40 : # data
563575 s = self ._get_size (tokenL )
@@ -629,10 +641,11 @@ def _count_to_size(count):
629641_scalars = (str , int , float , datetime .datetime , bytes )
630642
631643class _BinaryPlistWriter (object ):
632- def __init__ (self , fp , sort_keys , skipkeys ):
644+ def __init__ (self , fp , sort_keys , skipkeys , aware_datetime = False ):
633645 self ._fp = fp
634646 self ._sort_keys = sort_keys
635647 self ._skipkeys = skipkeys
648+ self ._aware_datetime = aware_datetime
636649
637650 def write (self , value ):
638651
@@ -778,7 +791,12 @@ def _write_object(self, value):
778791 self ._fp .write (struct .pack ('>Bd' , 0x23 , value ))
779792
780793 elif isinstance (value , datetime .datetime ):
781- f = (value - datetime .datetime (2001 , 1 , 1 )).total_seconds ()
794+ if self ._aware_datetime :
795+ dt = value .astimezone (datetime .UTC )
796+ offset = dt - datetime .datetime (2001 , 1 , 1 , tzinfo = datetime .UTC )
797+ f = offset .total_seconds ()
798+ else :
799+ f = (value - datetime .datetime (2001 , 1 , 1 )).total_seconds ()
782800 self ._fp .write (struct .pack ('>Bd' , 0x33 , f ))
783801
784802 elif isinstance (value , (bytes , bytearray )):
@@ -862,7 +880,7 @@ def _is_fmt_binary(header):
862880}
863881
864882
865- def load (fp , * , fmt = None , dict_type = dict ):
883+ def load (fp , * , fmt = None , dict_type = dict , aware_datetime = False ):
866884 """Read a .plist file. 'fp' should be a readable and binary file object.
867885 Return the unpacked root object (which usually is a dictionary).
868886 """
@@ -880,32 +898,36 @@ def load(fp, *, fmt=None, dict_type=dict):
880898 else :
881899 P = _FORMATS [fmt ]['parser' ]
882900
883- p = P (dict_type = dict_type )
901+ p = P (dict_type = dict_type , aware_datetime = aware_datetime )
884902 return p .parse (fp )
885903
886904
887- def loads (value , * , fmt = None , dict_type = dict ):
905+ def loads (value , * , fmt = None , dict_type = dict , aware_datetime = False ):
888906 """Read a .plist file from a bytes object.
889907 Return the unpacked root object (which usually is a dictionary).
890908 """
891909 fp = BytesIO (value )
892- return load (fp , fmt = fmt , dict_type = dict_type )
910+ return load (fp , fmt = fmt , dict_type = dict_type , aware_datetime = aware_datetime )
893911
894912
895- def dump (value , fp , * , fmt = FMT_XML , sort_keys = True , skipkeys = False ):
913+ def dump (value , fp , * , fmt = FMT_XML , sort_keys = True , skipkeys = False ,
914+ aware_datetime = False ):
896915 """Write 'value' to a .plist file. 'fp' should be a writable,
897916 binary file object.
898917 """
899918 if fmt not in _FORMATS :
900919 raise ValueError ("Unsupported format: %r" % (fmt ,))
901920
902- writer = _FORMATS [fmt ]["writer" ](fp , sort_keys = sort_keys , skipkeys = skipkeys )
921+ writer = _FORMATS [fmt ]["writer" ](fp , sort_keys = sort_keys , skipkeys = skipkeys ,
922+ aware_datetime = aware_datetime )
903923 writer .write (value )
904924
905925
906- def dumps (value , * , fmt = FMT_XML , skipkeys = False , sort_keys = True ):
926+ def dumps (value , * , fmt = FMT_XML , skipkeys = False , sort_keys = True ,
927+ aware_datetime = False ):
907928 """Return a bytes object with the contents for a .plist file.
908929 """
909930 fp = BytesIO ()
910- dump (value , fp , fmt = fmt , skipkeys = skipkeys , sort_keys = sort_keys )
931+ dump (value , fp , fmt = fmt , skipkeys = skipkeys , sort_keys = sort_keys ,
932+ aware_datetime = aware_datetime )
911933 return fp .getvalue ()
0 commit comments