1515
1616from .collections import Counter , FrozenCounter
1717from .conversions import try_conversion , parse_bool
18- from .format import format_int , format_repr , format_sample
1918from .xml import ElementFactory , xml , merge_siblings
19+ from .format import (
20+ format_int ,
21+ format_repr ,
22+ format_sample ,
23+ format_timestamp_numrepr ,
24+ )
2025
2126
2227tag = ElementFactory ()
@@ -905,11 +910,15 @@ def from_strings(cls, iterable, pattern, bad_threshold=0):
905910 pattern = pattern )
906911
907912 @classmethod
908- def from_numbers (cls , pattern ):
913+ def from_numbers (cls , pattern , offset = 0 , scale = 1 ):
909914 """
910915 Class method for constructing an instance wrapped in a :class:`NumRepr`
911916 to indicate a numeric representation of a set of timestamps (e.g. day
912- offset from the UNIX epoch).
917+ offset from the UNIX epoch). A different epoch may be specified as a
918+ numeric *offset*, and a different epoch *scale* as a numeric number of
919+ seconds). The default offset and scale are 0 and 1 which is equivalent
920+ to a seconds offset from the UNIX epoch (i.e. a traditional UNIX
921+ timestamp).
913922
914923 Constructed with an *sample* of number, a *pattern* (which can be a
915924 :class:`StrRepr` instance if the numbers are themselves represented as
@@ -923,8 +932,10 @@ def from_numbers(cls, pattern):
923932 num_pattern = pattern
924933 dt_counter = Counter ()
925934 for value , count in num_pattern .values .sample .items ():
926- dt_counter [datetime .fromtimestamp (value )] = count
927- result = NumRepr (cls (dt_counter ), pattern = num_pattern .__class__ )
935+ dt_value = datetime .utcfromtimestamp ((value * scale ) + offset )
936+ dt_counter [dt_value ] = count
937+ result = NumRepr (cls (dt_counter ), pattern = (
938+ num_pattern .__class__ , scale , offset ))
928939 if isinstance (pattern , StrRepr ):
929940 return pattern .with_content (result )
930941 else :
@@ -1174,12 +1185,12 @@ def validate(self, value):
11741185 value = parse_bool (value , false , true )
11751186 elif isinstance (self .content , Int ) or (
11761187 isinstance (self .content , NumRepr ) and
1177- self .content .pattern is Int
1188+ self .content .pattern [ 0 ] is Int
11781189 ):
11791190 value = int (value , base = self .int_bases [self .pattern ])
11801191 elif isinstance (self .content , Float ) or (
11811192 isinstance (self .content , NumRepr ) and
1182- self .content .pattern is Float
1193+ self .content .pattern [ 0 ] is Float
11831194 ):
11841195 assert self .pattern == 'f'
11851196 value = float (value )
@@ -1199,36 +1210,52 @@ class NumRepr(Repr):
11991210 __slots__ = ()
12001211
12011212 def __str__ (self ):
1202- if self .pattern is Int :
1203- template = 'int of {self.content}'
1204- elif self .pattern is Float :
1205- template = 'float of {self.content}'
1206- else :
1213+ type_ , scale , offset = self .pattern
1214+ try :
1215+ type_name = {Int : 'int' , Float : 'float' }[type_ ]
1216+ except KeyError :
12071217 assert False , 'str(num-repr) of {self.content!r}' .format (self = self )
1208- return template .format (self = self )
1218+ return '{type_name} {numrepr} of {self.content}' .format (
1219+ self = self , type_name = type_name ,
1220+ numrepr = format_timestamp_numrepr (offset , scale ))
12091221
12101222 def __xml__ (self ):
1211- if self .pattern is Int :
1212- return tag .intof (xml (self .content ))
1213- elif self .pattern is Float :
1214- return tag .floatof (xml (self .content ))
1223+ type_ , scale , offset = self .pattern
1224+ if type_ is Int :
1225+ return tag .intof (xml (self .content ), scale = scale , offset = offset )
1226+ elif type_ is Float :
1227+ return tag .floatof (xml (self .content ), scale = scale , offset = offset )
12151228 else :
12161229 assert False , 'xml(num-repr) of {self.content!r}' .format (self = self )
12171230
12181231 def __add__ (self , other ):
12191232 if self == other :
1220- if self .pattern is Float or other .pattern is Float :
1221- pattern = Float
1233+ self_type , self_scale , self_offset = self .pattern
1234+ other_type , other_scale , other_offset = other .pattern
1235+ if self_type is Float or other_type is Float :
1236+ add_type = Float
12221237 else :
1223- pattern = Int
1224- return NumRepr (self .content + other .content , pattern )
1238+ add_type = Int
1239+ return NumRepr (
1240+ self .content + other .content ,
1241+ (add_type , self_scale , self_offset ))
12251242 return NotImplemented
12261243
1244+ def __eq__ (self , other ):
1245+ if not isinstance (other , NumRepr ):
1246+ return NotImplemented
1247+ if super ().__eq__ (other ) is not True :
1248+ return False
1249+ self_type , self_scale , self_offset = self .pattern
1250+ other_type , other_scale , other_offset = other .pattern
1251+ return (self_scale == other_scale ) and (self_offset == other_offset )
1252+
12271253 def validate (self , value ):
12281254 if not isinstance (value , Real ):
12291255 raise TypeError ('{value!r} is not a number' .format (value = value ))
12301256 if isinstance (self .content , DateTime ):
1231- value = datetime .fromtimestamp (value )
1257+ type_ , scale , offset = self .pattern
1258+ value = datetime .utcfromtimestamp ((value * scale ) + offset )
12321259 else :
12331260 assert False , (
12341261 'validating num-repr of {self.content!r}' .format (self = self ))
0 commit comments