77import math
88from copy import copy
99from numbers import Real
10- from datetime import datetime
1110from textwrap import indent , shorten
11+ from datetime import datetime , timedelta
1212from functools import partial , total_ordering
1313from collections .abc import Mapping
1414from operator import attrgetter
@@ -905,12 +905,14 @@ def from_strings(cls, iterable, pattern, bad_threshold=0):
905905 pattern = pattern )
906906
907907 @classmethod
908- def from_numbers (cls , pattern , epoch = None ):
908+ def from_numbers (cls , pattern , epoch = datetime .utcfromtimestamp (0 ),
909+ unit = timedelta (seconds = 1 )):
909910 """
910911 Class method for constructing an instance wrapped in a :class:`NumRepr`
911912 to indicate a numeric representation of a set of timestamps (e.g. day
912913 offset from the UNIX epoch; a different *epoch* may be specified as
913- a :class:`~datetime.datetime`).
914+ a :class:`~datetime.datetime`, and a different *unit* as a
915+ :class:`~datetime.timedelta`, which defaults to 1 second).
914916
915917 Constructed with an *sample* of number, a *pattern* (which can be a
916918 :class:`StrRepr` instance if the numbers are themselves represented as
@@ -923,14 +925,14 @@ def from_numbers(cls, pattern, epoch=None):
923925 else :
924926 num_pattern = pattern
925927 dt_counter = Counter ()
926- if epoch is None :
927- offset = 0
928- else :
929- unix_epoch = datetime .utcfromtimestamp (0 )
930- offset = (unix_epoch - epoch ).total_seconds () / 86400
928+ unix_epoch = datetime .utcfromtimestamp (0 )
929+ offset = (epoch - unix_epoch ).total_seconds ()
930+ scale = unit .total_seconds ()
931931 for value , count in num_pattern .values .sample .items ():
932- dt_counter [datetime .utcfromtimestamp (value - offset )] = count
933- result = NumRepr (cls (dt_counter ), pattern = num_pattern .__class__ )
932+ dt_value = datetime .utcfromtimestamp ((value * scale ) + offset )
933+ dt_counter [dt_value ] = count
934+ result = NumRepr (cls (dt_counter ), pattern = (
935+ num_pattern .__class__ , scale , offset ))
934936 if isinstance (pattern , StrRepr ):
935937 return pattern .with_content (result )
936938 else :
@@ -1205,36 +1207,65 @@ class NumRepr(Repr):
12051207 __slots__ = ()
12061208
12071209 def __str__ (self ):
1208- if self .pattern is Int :
1209- template = 'int of {self.content}'
1210- elif self .pattern is Float :
1211- template = 'float of {self.content}'
1210+ type_ , scale , offset = self .pattern
1211+ delta = timedelta (seconds = scale )
1212+ unit = ', ' .join (
1213+ '{value}{prop}' .format (
1214+ value = '{value}*' if value != 1 else '' ,
1215+ prop = prop )
1216+ for prop in ('days' , 'seconds' , 'microseconds' )
1217+ for value in (getattr (delta , prop ),)
1218+ )
1219+ if not epoch % 86400 :
1220+ epoch = datetime .utcfromtimestamp (offset ).date ().isoformat ()
12121221 else :
1213- assert False , 'str(num-repr) of {self.content!r}' .format (self = self )
1214- return template .format (self = self )
1222+ epoch = datetime .utcfromtimestamp (offset ).isoformat ()
1223+ if type_ is Int :
1224+ template = 'int {unit} after {epoch} of {self.content}'
1225+ elif type_ is Float :
1226+ template = 'float {unit} after {epoch} of {self.content}'
1227+ else :
1228+ assert False , 'str(num-repr) of {self.content!r}' .format (
1229+ self = self , unit = unit , epoch = epoch )
1230+ return template .format (self = self , unit = unit , epoch = epoch )
12151231
12161232 def __xml__ (self ):
1217- if self .pattern is Int :
1218- return tag .intof (xml (self .content ))
1219- elif self .pattern is Float :
1220- return tag .floatof (xml (self .content ))
1233+ type_ , scale , offset = self .pattern
1234+ if type_ is Int :
1235+ return tag .intof (xml (self .content ), scale = scale , offset = offset )
1236+ elif type_ is Float :
1237+ return tag .floatof (xml (self .content ), scale = scale , offset = offset )
12211238 else :
12221239 assert False , 'xml(num-repr) of {self.content!r}' .format (self = self )
12231240
12241241 def __add__ (self , other ):
12251242 if self == other :
1226- if self .pattern is Float or other .pattern is Float :
1227- pattern = Float
1243+ self_type , self_scale , self_offset = self .pattern
1244+ other_type , other_scale , other_offset = other .pattern
1245+ if self_type is Float or other_type is Float :
1246+ add_type = Float
12281247 else :
1229- pattern = Int
1230- return NumRepr (self .content + other .content , pattern )
1248+ add_type = Int
1249+ return NumRepr (
1250+ self .content + other .content ,
1251+ (add_type , self_scale , self_offset ))
12311252 return NotImplemented
12321253
1254+ def __eq__ (self , other ):
1255+ if not isinstance (other , NumRepr ):
1256+ return NotImplemented
1257+ if super ().__eq__ (other ) is not True :
1258+ return False
1259+ self_type , self_scale , self_offset = self .pattern
1260+ other_type , other_scale , other_offset = other .pattern
1261+ return (self_scale == other_scale ) and (self_offset == other_offset )
1262+
12331263 def validate (self , value ):
12341264 if not isinstance (value , Real ):
12351265 raise TypeError ('{value!r} is not a number' .format (value = value ))
12361266 if isinstance (self .content , DateTime ):
1237- value = datetime .utcfromtimestamp (value )
1267+ type_ , scale , offset = self .pattern
1268+ value = datetime .utcfromtimestamp ((value * scale ) + offset )
12381269 else :
12391270 assert False , (
12401271 'validating num-repr of {self.content!r}' .format (self = self ))
0 commit comments