@@ -140,7 +140,7 @@ def _decode_base64(s):
140
140
_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 )
141
141
142
142
143
- def _date_from_string (s ):
143
+ def _date_from_string (s , aware_datetime ):
144
144
order = ('year' , 'month' , 'day' , 'hour' , 'minute' , 'second' )
145
145
gd = _dateParser .match (s ).groupdict ()
146
146
lst = []
@@ -149,10 +149,14 @@ def _date_from_string(s):
149
149
if val is None :
150
150
break
151
151
lst .append (int (val ))
152
+ if aware_datetime :
153
+ return datetime .datetime (* lst , tzinfo = datetime .UTC )
152
154
return datetime .datetime (* lst )
153
155
154
156
155
- def _date_to_string (d ):
157
+ def _date_to_string (d , aware_datetime ):
158
+ if aware_datetime :
159
+ d = d .astimezone (datetime .UTC )
156
160
return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
157
161
d .year , d .month , d .day ,
158
162
d .hour , d .minute , d .second
@@ -171,11 +175,12 @@ def _escape(text):
171
175
return text
172
176
173
177
class _PlistParser :
174
- def __init__ (self , dict_type ):
178
+ def __init__ (self , dict_type , aware_datetime = False ):
175
179
self .stack = []
176
180
self .current_key = None
177
181
self .root = None
178
182
self ._dict_type = dict_type
183
+ self ._aware_datetime = aware_datetime
179
184
180
185
def parse (self , fileobj ):
181
186
self .parser = ParserCreate ()
@@ -277,7 +282,8 @@ def end_data(self):
277
282
self .add_object (_decode_base64 (self .get_data ()))
278
283
279
284
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 ))
281
287
282
288
283
289
class _DumbXMLWriter :
@@ -321,13 +327,14 @@ def writeln(self, line):
321
327
class _PlistWriter (_DumbXMLWriter ):
322
328
def __init__ (
323
329
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 ):
325
331
326
332
if writeHeader :
327
333
file .write (PLISTHEADER )
328
334
_DumbXMLWriter .__init__ (self , file , indent_level , indent )
329
335
self ._sort_keys = sort_keys
330
336
self ._skipkeys = skipkeys
337
+ self ._aware_datetime = aware_datetime
331
338
332
339
def write (self , value ):
333
340
self .writeln ("<plist version=\" 1.0\" >" )
@@ -360,7 +367,8 @@ def write_value(self, value):
360
367
self .write_bytes (value )
361
368
362
369
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 ))
364
372
365
373
elif isinstance (value , (tuple , list )):
366
374
self .write_array (value )
@@ -461,8 +469,9 @@ class _BinaryPlistParser:
461
469
462
470
see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
463
471
"""
464
- def __init__ (self , dict_type ):
472
+ def __init__ (self , dict_type , aware_datetime = False ):
465
473
self ._dict_type = dict_type
474
+ self ._aware_datime = aware_datetime
466
475
467
476
def parse (self , fp ):
468
477
try :
@@ -556,8 +565,11 @@ def _read_object(self, ref):
556
565
f = struct .unpack ('>d' , self ._fp .read (8 ))[0 ]
557
566
# timestamp 0 of binary plists corresponds to 1/1/2001
558
567
# (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 )
561
573
562
574
elif tokenH == 0x40 : # data
563
575
s = self ._get_size (tokenL )
@@ -629,10 +641,11 @@ def _count_to_size(count):
629
641
_scalars = (str , int , float , datetime .datetime , bytes )
630
642
631
643
class _BinaryPlistWriter (object ):
632
- def __init__ (self , fp , sort_keys , skipkeys ):
644
+ def __init__ (self , fp , sort_keys , skipkeys , aware_datetime = False ):
633
645
self ._fp = fp
634
646
self ._sort_keys = sort_keys
635
647
self ._skipkeys = skipkeys
648
+ self ._aware_datetime = aware_datetime
636
649
637
650
def write (self , value ):
638
651
@@ -778,7 +791,12 @@ def _write_object(self, value):
778
791
self ._fp .write (struct .pack ('>Bd' , 0x23 , value ))
779
792
780
793
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 ()
782
800
self ._fp .write (struct .pack ('>Bd' , 0x33 , f ))
783
801
784
802
elif isinstance (value , (bytes , bytearray )):
@@ -862,7 +880,7 @@ def _is_fmt_binary(header):
862
880
}
863
881
864
882
865
- def load (fp , * , fmt = None , dict_type = dict ):
883
+ def load (fp , * , fmt = None , dict_type = dict , aware_datetime = False ):
866
884
"""Read a .plist file. 'fp' should be a readable and binary file object.
867
885
Return the unpacked root object (which usually is a dictionary).
868
886
"""
@@ -880,32 +898,36 @@ def load(fp, *, fmt=None, dict_type=dict):
880
898
else :
881
899
P = _FORMATS [fmt ]['parser' ]
882
900
883
- p = P (dict_type = dict_type )
901
+ p = P (dict_type = dict_type , aware_datetime = aware_datetime )
884
902
return p .parse (fp )
885
903
886
904
887
- def loads (value , * , fmt = None , dict_type = dict ):
905
+ def loads (value , * , fmt = None , dict_type = dict , aware_datetime = False ):
888
906
"""Read a .plist file from a bytes object.
889
907
Return the unpacked root object (which usually is a dictionary).
890
908
"""
891
909
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 )
893
911
894
912
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 ):
896
915
"""Write 'value' to a .plist file. 'fp' should be a writable,
897
916
binary file object.
898
917
"""
899
918
if fmt not in _FORMATS :
900
919
raise ValueError ("Unsupported format: %r" % (fmt ,))
901
920
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 )
903
923
writer .write (value )
904
924
905
925
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 ):
907
928
"""Return a bytes object with the contents for a .plist file.
908
929
"""
909
930
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 )
911
933
return fp .getvalue ()
0 commit comments