diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index d00d9dd..d1c458a 100755 --- a/py25/bacpypes/primitivedata.py +++ b/py25/bacpypes/primitivedata.py @@ -69,10 +69,8 @@ class Tag(object): def set(self, tclass, tnum, tlvt=0, tdata=''): """set the values of the tag.""" - if isinstance(tdata, bytearray): - tdata = bytes(tdata) - elif not isinstance(tdata, bytes): - raise TypeError("tag data must be bytes or bytearray") + if not isinstance(tdata, str): + raise TypeError("tag data must be str") self.tagClass = tclass self.tagNumber = tnum @@ -81,10 +79,8 @@ class Tag(object): def set_app_data(self, tnum, tdata): """set the values of the tag.""" - if isinstance(tdata, bytearray): - tdata = bytes(tdata) - elif not isinstance(tdata, bytes): - raise TypeError("tag data must be bytes or bytearray") + if not isinstance(tdata, str): + raise TypeError("tag data must be str") self.tagClass = Tag.applicationTagClass self.tagNumber = tnum @@ -174,7 +170,7 @@ class Tag(object): # application tagged boolean now has data if (self.tagNumber == Tag.booleanAppTag): - return ContextTag(context, bytearray([self.tagLVT])) + return ContextTag(context, chr(self.tagLVT)) else: return ContextTag(context, self.tagData) @@ -563,11 +559,11 @@ class Unsigned(Atomic): def encode(self, tag): # rip apart the number - data = bytearray(struct.pack('>L', self.value)) + data = struct.pack('>L', self.value) # reduce the value to the smallest number of octets - while (len(data) > 1) and (data[0] == 0): - del data[0] + while (len(data) > 1) and (data[0] == '\x00'): + data = data[1:] # encode the tag tag.set_app_data(Tag.unsignedAppTag, data) @@ -613,24 +609,24 @@ class Integer(Atomic): def encode(self, tag): # rip apart the number - data = bytearray(struct.pack('>I', self.value & 0xFFFFFFFF)) + data = struct.pack('>I', self.value & 0xFFFFFFFF) # reduce the value to the smallest number of bytes, be # careful about sign extension if self.value < 0: while (len(data) > 1): - if (data[0] != 255): + if (data[0] != '\xFF'): break - if (data[1] < 128): + if (data[1] < '\x80'): break - del data[0] + data = data[1:] else: while (len(data) > 1): - if (data[0] != 0): + if (data[0] != '\x00'): break - if (data[1] >= 128): + if (data[1] >= '\x80'): break - del data[0] + data = data[1:] # encode the tag tag.set_app_data(Tag.integerAppTag, data) @@ -640,7 +636,7 @@ class Integer(Atomic): raise ValueError("integer application tag required") # byte array easier to deal with - tag_data = bytearray(tag.tagData) + tag_data = [ord(c) for c in tag.tagData] # get the data rslt = tag_data[0] @@ -746,8 +742,8 @@ class OctetString(Atomic): pass elif isinstance(arg, Tag): self.decode(arg) - elif isinstance(arg, (bytes, bytearray)): - self.value = bytes(arg) + elif isinstance(arg, str): + self.value = arg elif isinstance(arg, OctetString): self.value = arg.value else: @@ -800,11 +796,10 @@ class CharacterString(Atomic): if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.characterStringAppTag): raise ValueError("character string application tag required") - # byte array easier to deal with - tag_data = bytearray(tag.tagData) + tag_data = tag.tagData # extract the data - self.strEncoding = tag_data[0] + self.strEncoding = ord(tag_data[0]) self.strValue = tag_data[1:] # normalize the value @@ -870,7 +865,7 @@ class BitString(Atomic): unused = used and (8 - used) or 0 # start with the number of unused bits - data = bytearray([unused]) + data = [unused] # build and append each packed octet bits = self.value + [0] * unused @@ -881,13 +876,13 @@ class BitString(Atomic): data.append(x) # encode the tag - tag.set_app_data(Tag.bitStringAppTag, data) + tag.set_app_data(Tag.bitStringAppTag, ''.join(chr(i) for i in data)) def decode(self, tag): if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.bitStringAppTag): raise ValueError("bit string application tag required") - tag_data = bytearray(tag.tagData) + tag_data = [ord(c) for c in tag.tagData] # extract the number of unused bits unused = tag_data[0] @@ -1055,11 +1050,11 @@ class Enumerated(Atomic): raise TypeError("%s is an invalid enumeration value datatype" % (type(self.value),)) # rip apart the number - data = bytearray(struct.pack('>L', value)) + data = struct.pack('>L', value) # reduce the value to the smallest number of octets - while (len(data) > 1) and (data[0] == 0): - del data[0] + while (len(data) > 1) and (data[0] == '\x00'): + data = data[1:] # encode the tag tag.set_app_data(Tag.enumeratedAppTag, data) @@ -1108,87 +1103,177 @@ def expand_enumerations(klass): # Date # +_mm = r'(?P0?[1-9]|1[0-4]|odd|even|255|[*])' +_dd = r'(?P[0-3]?\d|last|odd|even|255|[*])' +_yy = r'(?P\d{2}|255|[*])' +_yyyy = r'(?P\d{4}|255|[*])' +_dow = r'(?P[1-7]|mon|tue|wed|thu|fri|sat|sun|255|[*])' + +_special_mon = {'*': 255, 'odd': 13, 'even': 14, None: 255} +_special_mon_inv = {255: '*', 13: 'odd', 14: 'even'} + +_special_day = {'*': 255, 'last': 32, 'odd': 33, 'even': 34, None: 255} +_special_day_inv = {255: '*', 32: 'last', 33: 'odd', 34: 'even'} + +_special_dow = {'*': 255, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6, 'sun': 7} +_special_dow_inv = {255: '*', 1: 'mon', 2: 'tue', 3: 'wed', 4: 'thu', 5: 'fri', 6: 'sat', 7: 'sun'} + + +def _merge(*args): + """Create a composite pattern and compile it.""" + return re.compile(r'^' + r'[/-]'.join(args) + r'(?:\s+' + _dow + ')?$') + + +# make a list of compiled patterns +_date_patterns = [ + _merge(_yyyy, _mm, _dd), + _merge(_mm, _dd, _yyyy), + _merge(_dd, _mm, _yyyy), + _merge(_yy, _mm, _dd), + _merge(_mm, _dd, _yy), + _merge(_dd, _mm, _yy), + ] + + class Date(Atomic): - _app_tag = Tag.dateAppTag - _date_regex = re.compile(r"^([*]|\d+)[/]([*]|\d+)[/]([*]|\d+)(?:\s([*]|\w+))?$") - _day_names = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - - DONT_CARE = 255 - - def __init__(self, arg=None, year=255, month=255, day=255, dayOfWeek=255): - self.value = (year, month, day, dayOfWeek) - + def __init__(self, arg=None, year=255, month=255, day=255, day_of_week=255): + self.value = (year, month, day, day_of_week) + if arg is None: pass - elif isinstance(arg,Tag): + elif isinstance(arg, Tag): self.decode(arg) elif isinstance(arg, tuple): self.value = arg elif isinstance(arg, str): - date_match = Date._date_regex.match(arg) - if not date_match: - raise ValueError("invalid date pattern") - date_groups = date_match.groups() + # lower case everything + arg = arg.lower() - # day/month/year - tup_list = [] - for s in date_groups[:3]: - if s == '*': - tup_list.append(255) - elif s in None: - tup_list.append(0) - else: - tup_list.append(int(s)) + # make a list of the contents from matching patterns + matches = [] + for p in _date_patterns: + m = p.match(arg) + if m: + matches.append(m.groupdict()) - # clean up the year - if (tup_list[2] < 100): - tup_list[2] += 2000 - tup_list[2] -= 1900 + # try to find a good one + match = None + if not matches: + raise ValueError("unmatched") - # day-of-week madness - dow = date_groups[3] - if dow is None: - tup_list.append(0) - elif (dow == '*'): - tup_list.append(255) - elif dow.isdigit(): - tup_list.append(int(dow)) + # if there is only one, success + if len(matches) == 1: + match = matches[0] else: - dow = dow.title() - if dow not in Date._day_names: - raise ValueError("invalid day name") - tup_list.append(Date._day_names.index(dow)) + # check to see if they really are the same + for a, b in zip(matches[:-1],matches[1:]): + if a != b: + raise ValueError("ambiguous") + break + else: + match = matches[0] + + # extract the year and normalize + year = match['year'] + if (year == '*') or (not year): + year = 255 + else: + year = int(year) + if (year == 255): + pass + elif year < 35: + year += 2000 + elif year < 100: + year += 1900 + elif year < 1900: + raise ValueError("invalid year") + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if (month == 255): + pass + elif (month == 0) or (month > 14): + raise ValueError("invalid month") + + # extract the day and normalize + day = match['day'] + if day in _special_day: + day = _special_day[day] + else: + day = int(day) + if (day == 255): + pass + elif (day == 0) or (day > 34): + raise ValueError("invalid day") + + # extract the day-of-week and normalize + day_of_week = match['dow'] + if day_of_week in _special_dow: + day_of_week = _special_dow[day_of_week] + elif not day_of_week: + pass + else: + day_of_week = int(day_of_week) + if (day_of_week == 255): + pass + elif (day_of_week == 0) or (day_of_week > 7): + raise ValueError("invalid day of week") + + # year becomes the correct octet + if year != 255: + year -= 1900 + + # save the value + self.value = (year, month, day, day_of_week) + + # calculate the day of the week + if not day_of_week: + self.CalcDayOfWeek() - self.value = tuple(tup_list) elif isinstance(arg, Date): self.value = arg.value + else: raise TypeError("invalid constructor datatype") - def now(self): - tup = time.localtime() - - self.value = (tup[0]-1900, tup[1], tup[2], tup[6] + 1) - - return self - def CalcDayOfWeek(self): """Calculate the correct day of the week.""" # rip apart the value - year, month, day, dayOfWeek = self.value + year, month, day, day_of_week = self.value - # make sure all the components are defined - if (year != 255) and (month != 255) and (day != 255): - today = time.mktime( (year + 1900, month, day, 0, 0, 0, 0, 0, -1) ) - dayOfWeek = time.gmtime(today)[6] + 1 + # assume the worst + day_of_week = 255 + + # check for special values + if year == 255: + pass + elif month in _special_mon_inv: + pass + elif day in _special_day_inv: + pass + else: + try: + today = time.mktime( (year + 1900, month, day, 0, 0, 0, 0, 0, -1) ) + day_of_week = time.gmtime(today)[6] + 1 + except OverflowError: + pass # put it back together - self.value = (year, month, day, dayOfWeek) + self.value = (year, month, day, day_of_week) + + def now(self): + tup = time.localtime() + self.value = (tup[0]-1900, tup[1], tup[2], tup[6] + 1) + return self def encode(self, tag): # encode the tag - tag.set_app_data(Tag.dateAppTag, bytearray(self.value)) + tag.set_app_data(Tag.dateAppTag, ''.join(chr(i) for i in self.value)) def decode(self, tag): if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.dateAppTag): @@ -1198,28 +1283,21 @@ class Date(Atomic): self.value = tuple(ord(c) for c in tag.tagData) def __str__(self): + """String representation of the date.""" # rip it apart - year, month, day, dayOfWeek = self.value + year, month, day, day_of_week = self.value - rslt = "Date(" - if month == 255: - rslt += "*/" - else: - rslt += "%d/" % (month,) - if day == 255: - rslt += "*/" - else: - rslt += "%d/" % (day,) if year == 255: - rslt += "* " + year = "*" else: - rslt += "%d " % (year + 1900,) - if dayOfWeek == 255: - rslt += "*)" - else: - rslt += Date._day_names[dayOfWeek] + ")" + year = str(year + 1900) + + month = _special_mon_inv.get(month, str(month)) + day = _special_day_inv.get(day, str(day)) + day_of_week = _special_dow_inv.get(day_of_week, str(day_of_week)) + + return "%s(%s-%s-%s %s)" % (self.__class__.__name__, year, month, day, day_of_week) - return rslt # # Time @@ -1280,7 +1358,7 @@ class Time(Atomic): def encode(self, tag): # encode the tag - tag.set_app_data(Tag.timeAppTag, bytearray(self.value)) + tag.set_app_data(Tag.timeAppTag, ''.join(chr(c) for c in self.value)) def decode(self, tag): if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.timeAppTag): diff --git a/py27/bacpypes/primitivedata.py b/py27/bacpypes/primitivedata.py index d99fc69..7ef4057 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -824,7 +824,7 @@ class CharacterString(Atomic): self.value = '### unknown encoding: %d ###' % (self.strEncoding,) def __str__(self): - return "CharacterString(%d," % (self.strEncoding,) + repr(self.strValue) + ")" + return "CharacterString(%d,X'%s')" % (self.strEncoding, btox(self.strValue)) # # BitString @@ -1104,87 +1104,178 @@ def expand_enumerations(klass): # save the dictionary in the class setattr(klass, '_xlate_table', xlateTable) + # # Date # +_mm = r'(?P0?[1-9]|1[0-4]|odd|even|255|[*])' +_dd = r'(?P[0-3]?\d|last|odd|even|255|[*])' +_yy = r'(?P\d{2}|255|[*])' +_yyyy = r'(?P\d{4}|255|[*])' +_dow = r'(?P[1-7]|mon|tue|wed|thu|fri|sat|sun|255|[*])' + +_special_mon = {'*': 255, 'odd': 13, 'even': 14, None: 255} +_special_mon_inv = {255: '*', 13: 'odd', 14: 'even'} + +_special_day = {'*': 255, 'last': 32, 'odd': 33, 'even': 34, None: 255} +_special_day_inv = {255: '*', 32: 'last', 33: 'odd', 34: 'even'} + +_special_dow = {'*': 255, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6, 'sun': 7} +_special_dow_inv = {255: '*', 1: 'mon', 2: 'tue', 3: 'wed', 4: 'thu', 5: 'fri', 6: 'sat', 7: 'sun'} + + +def _merge(*args): + """Create a composite pattern and compile it.""" + return re.compile(r'^' + r'[/-]'.join(args) + r'(?:\s+' + _dow + ')?$') + + +# make a list of compiled patterns +_date_patterns = [ + _merge(_yyyy, _mm, _dd), + _merge(_mm, _dd, _yyyy), + _merge(_dd, _mm, _yyyy), + _merge(_yy, _mm, _dd), + _merge(_mm, _dd, _yy), + _merge(_dd, _mm, _yy), + ] + + class Date(Atomic): - _app_tag = Tag.dateAppTag - _date_regex = re.compile(r"^([*]|\d+)[/]([*]|\d+)[/]([*]|\d+)(?:\s([*]|\w+))?$") - _day_names = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - - DONT_CARE = 255 - - def __init__(self, arg=None, year=255, month=255, day=255, dayOfWeek=255): - self.value = (year, month, day, dayOfWeek) - + def __init__(self, arg=None, year=255, month=255, day=255, day_of_week=255): + self.value = (year, month, day, day_of_week) + if arg is None: pass - elif isinstance(arg,Tag): + elif isinstance(arg, Tag): self.decode(arg) elif isinstance(arg, tuple): self.value = arg elif isinstance(arg, str): - date_match = Date._date_regex.match(arg) - if not date_match: - raise ValueError("invalid date pattern") - date_groups = date_match.groups() + # lower case everything + arg = arg.lower() - # day/month/year - tup_list = [] - for s in date_groups[:3]: - if s == '*': - tup_list.append(255) - elif s in None: - tup_list.append(0) - else: - tup_list.append(int(s)) + # make a list of the contents from matching patterns + matches = [] + for p in _date_patterns: + m = p.match(arg) + if m: + matches.append(m.groupdict()) - # clean up the year - if (tup_list[2] < 100): - tup_list[2] += 2000 - tup_list[2] -= 1900 + # try to find a good one + match = None + if not matches: + raise ValueError("unmatched") - # day-of-week madness - dow = date_groups[3] - if dow is None: - tup_list.append(0) - elif (dow == '*'): - tup_list.append(255) - elif dow.isdigit(): - tup_list.append(int(dow)) + # if there is only one, success + if len(matches) == 1: + match = matches[0] else: - dow = dow.title() - if dow not in Date._day_names: - raise ValueError("invalid day name") - tup_list.append(Date._day_names.index(dow)) + # check to see if they really are the same + for a, b in zip(matches[:-1],matches[1:]): + if a != b: + raise ValueError("ambiguous") + break + else: + match = matches[0] + + # extract the year and normalize + year = match['year'] + if (year == '*') or (not year): + year = 255 + else: + year = int(year) + if (year == 255): + pass + elif year < 35: + year += 2000 + elif year < 100: + year += 1900 + elif year < 1900: + raise ValueError("invalid year") + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if (month == 255): + pass + elif (month == 0) or (month > 14): + raise ValueError("invalid month") + + # extract the day and normalize + day = match['day'] + if day in _special_day: + day = _special_day[day] + else: + day = int(day) + if (day == 255): + pass + elif (day == 0) or (day > 34): + raise ValueError("invalid day") + + # extract the day-of-week and normalize + day_of_week = match['dow'] + if day_of_week in _special_dow: + day_of_week = _special_dow[day_of_week] + elif not day_of_week: + pass + else: + day_of_week = int(day_of_week) + if (day_of_week == 255): + pass + elif day_of_week > 7: + raise ValueError("invalid day of week") + + # year becomes the correct octet + if year != 255: + year -= 1900 + + # save the value + self.value = (year, month, day, day_of_week) + + # calculate the day of the week + if not day_of_week: + self.CalcDayOfWeek() - self.value = tuple(tup_list) elif isinstance(arg, Date): self.value = arg.value + else: raise TypeError("invalid constructor datatype") - def now(self): - tup = time.localtime() - - self.value = (tup[0]-1900, tup[1], tup[2], tup[6] + 1) - - return self - def CalcDayOfWeek(self): """Calculate the correct day of the week.""" # rip apart the value - year, month, day, dayOfWeek = self.value + year, month, day, day_of_week = self.value - # make sure all the components are defined - if (year != 255) and (month != 255) and (day != 255): - today = time.mktime( (year + 1900, month, day, 0, 0, 0, 0, 0, -1) ) - dayOfWeek = time.gmtime(today)[6] + 1 + # assume the worst + day_of_week = 255 + + # check for special values + if year == 255: + pass + elif month in _special_mon_inv: + pass + elif day in _special_day_inv: + pass + else: + try: + today = time.mktime( (year + 1900, month, day, 0, 0, 0, 0, 0, -1) ) + day_of_week = time.gmtime(today)[6] + 1 + except OverflowError: + pass # put it back together - self.value = (year, month, day, dayOfWeek) + self.value = (year, month, day, day_of_week) + + def now(self): + tup = time.localtime() + self.value = (tup[0]-1900, tup[1], tup[2], tup[6] + 1) + return self def encode(self, tag): # encode the tag @@ -1198,28 +1289,21 @@ class Date(Atomic): self.value = tuple(ord(c) for c in tag.tagData) def __str__(self): + """String representation of the date.""" # rip it apart - year, month, day, dayOfWeek = self.value + year, month, day, day_of_week = self.value - rslt = "Date(" - if month == 255: - rslt += "*/" - else: - rslt += "%d/" % (month,) - if day == 255: - rslt += "*/" - else: - rslt += "%d/" % (day,) if year == 255: - rslt += "* " + year = "*" else: - rslt += "%d " % (year + 1900,) - if dayOfWeek == 255: - rslt += "*)" - else: - rslt += Date._day_names[dayOfWeek] + ")" + year = str(year + 1900) + + month = _special_mon_inv.get(month, str(month)) + day = _special_day_inv.get(day, str(day)) + day_of_week = _special_dow_inv.get(day_of_week, str(day_of_week)) + + return "%s(%s-%s-%s %s)" % (self.__class__.__name__, year, month, day, day_of_week) - return rslt # # Time diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index 1208cb3..b352ab9 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -472,7 +472,7 @@ class Atomic(object): return (self.value < other.value) def __eq__(self, other): - sys.stderr.write("__eq__ %r %r\n" % (self, other)) + # sys.stderr.write("__eq__ %r %r\n" % (self, other)) # hoop jump it if not isinstance(other, self.__class__): @@ -837,7 +837,7 @@ class CharacterString(Atomic): self.value = '### unknown encoding: %d ###' % (self.strEncoding,) def __str__(self): - return "CharacterString(%d," % (self.strEncoding,) + repr(self.value) + ")" + return "CharacterString(%d,X'%s')" % (self.strEncoding, btox(self.strValue)) # # BitString @@ -1026,7 +1026,7 @@ class Enumerated(Atomic): def keylist(self): """Return a list of names in order by value.""" items = self.enumerations.items() - items.sort(lambda a, b: cmp(a[1], b[1])) + items.sort(lambda a, b: self.cmp(a[1], b[1])) # last item has highest value rslt = [None] * (items[-1][1] + 1) @@ -1119,83 +1119,171 @@ def expand_enumerations(klass): # Date # +_mm = r'(?P0?[1-9]|1[0-4]|odd|even|255|[*])' +_dd = r'(?P[0-3]?\d|last|odd|even|255|[*])' +_yy = r'(?P\d{2}|255|[*])' +_yyyy = r'(?P\d{4}|255|[*])' +_dow = r'(?P[1-7]|mon|tue|wed|thu|fri|sat|sun|255|[*])' + +_special_mon = {'*': 255, 'odd': 13, 'even': 14, None: 255} +_special_mon_inv = {255: '*', 13: 'odd', 14: 'even'} + +_special_day = {'*': 255, 'last': 32, 'odd': 33, 'even': 34, None: 255} +_special_day_inv = {255: '*', 32: 'last', 33: 'odd', 34: 'even'} + +_special_dow = {'*': 255, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4, 'fri': 5, 'sat': 6, 'sun': 7} +_special_dow_inv = {255: '*', 1: 'mon', 2: 'tue', 3: 'wed', 4: 'thu', 5: 'fri', 6: 'sat', 7: 'sun'} + +def _merge(*args): + """Create a composite pattern and compile it.""" + return re.compile(r'^' + r'[/-]'.join(args) + r'(?:\s+' + _dow + ')?$') + +# make a list of compiled patterns +_date_patterns = [ + _merge(_yyyy, _mm, _dd), + _merge(_mm, _dd, _yyyy), + _merge(_dd, _mm, _yyyy), + _merge(_yy, _mm, _dd), + _merge(_mm, _dd, _yy), + _merge(_dd, _mm, _yy), + ] + + class Date(Atomic): - _app_tag = Tag.dateAppTag - _date_regex = re.compile(r"^([*]|\d+)[/]([*]|\d+)[/]([*]|\d+)(?:\s([*]|\w+))?$") - _day_names = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - - DONT_CARE = 255 - - def __init__(self, arg=None, year=255, month=255, day=255, dayOfWeek=255): - self.value = (year, month, day, dayOfWeek) - + def __init__(self, arg=None, year=255, month=255, day=255, day_of_week=255): + self.value = (year, month, day, day_of_week) + if arg is None: pass - elif isinstance(arg,Tag): + elif isinstance(arg, Tag): self.decode(arg) elif isinstance(arg, tuple): self.value = arg elif isinstance(arg, str): - date_match = Date._date_regex.match(arg) - if not date_match: - raise ValueError("invalid date pattern") - date_groups = date_match.groups() + # lower case everything + arg = arg.lower() - # day/month/year - tup_list = [] - for s in date_groups[:3]: - if s == '*': - tup_list.append(255) - elif s in None: - tup_list.append(0) - else: - tup_list.append(int(s)) + # make a list of the contents from matching patterns + matches = [] + for p in _date_patterns: + m = p.match(arg) + if m: + matches.append(m.groupdict()) - # clean up the year - if (tup_list[2] < 100): - tup_list[2] += 2000 - tup_list[2] -= 1900 + # try to find a good one + match = None + if not matches: + raise ValueError("unmatched") - # day-of-week madness - dow = date_groups[3] - if dow is None: - tup_list.append(0) - elif (dow == '*'): - tup_list.append(255) - elif dow.isdigit(): - tup_list.append(int(dow)) + # if there is only one, success + if len(matches) == 1: + match = matches[0] else: - dow = dow.title() - if dow not in Date._day_names: - raise ValueError("invalid day name") - tup_list.append(Date._day_names.index(dow)) + # check to see if they really are the same + for a, b in zip(matches[:-1],matches[1:]): + if a != b: + raise ValueError("ambiguous") + break + else: + match = matches[0] + + # extract the year and normalize + year = match['year'] + if (year == '*') or (not year): + year = 255 + else: + year = int(year) + if (year == 255): + pass + elif year < 35: + year += 2000 + elif year < 100: + year += 1900 + elif year < 1900: + raise ValueError("invalid year") + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if (month == 255): + pass + elif (month == 0) or (month > 14): + raise ValueError("invalid month") + + # extract the day and normalize + day = match['day'] + if day in _special_day: + day = _special_day[day] + else: + day = int(day) + if (day == 255): + pass + elif (day == 0) or (day > 34): + raise ValueError("invalid day") + + # extract the day-of-week and normalize + day_of_week = match['dow'] + if day_of_week in _special_dow: + day_of_week = _special_dow[day_of_week] + elif not day_of_week: + pass + else: + day_of_week = int(day_of_week) + if (day_of_week == 255): + pass + elif day_of_week > 7: + raise ValueError("invalid day of week") + + # year becomes the correct octet + if year != 255: + year -= 1900 + + # save the value + self.value = (year, month, day, day_of_week) + + # calculate the day of the week + if not day_of_week: + self.CalcDayOfWeek() - self.value = tuple(tup_list) elif isinstance(arg, Date): self.value = arg.value + else: raise TypeError("invalid constructor datatype") - def now(self): - tup = time.localtime() - - self.value = (tup[0]-1900, tup[1], tup[2], tup[6] + 1) - - return self - def CalcDayOfWeek(self): """Calculate the correct day of the week.""" # rip apart the value - year, month, day, dayOfWeek = self.value + year, month, day, day_of_week = self.value - # make sure all the components are defined - if (year != 255) and (month != 255) and (day != 255): - today = time.mktime( (year + 1900, month, day, 0, 0, 0, 0, 0, -1) ) - dayOfWeek = time.gmtime(today)[6] + 1 + # assume the worst + day_of_week = 255 + + # check for special values + if year == 255: + pass + elif month in _special_mon_inv: + pass + elif day in _special_day_inv: + pass + else: + try: + today = time.mktime( (year + 1900, month, day, 0, 0, 0, 0, 0, -1) ) + day_of_week = time.gmtime(today)[6] + 1 + except OverflowError: + pass # put it back together - self.value = (year, month, day, dayOfWeek) + self.value = (year, month, day, day_of_week) + + def now(self): + tup = time.localtime() + self.value = (tup[0]-1900, tup[1], tup[2], tup[6] + 1) + return self def encode(self, tag): # encode the tag @@ -1209,28 +1297,21 @@ class Date(Atomic): self.value = tuple(tag.tagData) def __str__(self): + """String representation of the date.""" # rip it apart - year, month, day, dayOfWeek = self.value + year, month, day, day_of_week = self.value - rslt = "Date(" - if month == 255: - rslt += "*/" - else: - rslt += "%d/" % (month,) - if day == 255: - rslt += "*/" - else: - rslt += "%d/" % (day,) if year == 255: - rslt += "* " + year = "*" else: - rslt += "%d " % (year + 1900,) - if dayOfWeek == 255: - rslt += "*)" - else: - rslt += Date._day_names[dayOfWeek] + ")" + year = str(year + 1900) + + month = _special_mon_inv.get(month, str(month)) + day = _special_day_inv.get(day, str(day)) + day_of_week = _special_dow_inv.get(day_of_week, str(day_of_week)) + + return "%s(%s-%s-%s %s)" % (self.__class__.__name__, year, month, day, day_of_week) - return rslt # # Time diff --git a/samples/date_string_patterns.py b/samples/date_string_patterns.py new file mode 100644 index 0000000..fdaf928 --- /dev/null +++ b/samples/date_string_patterns.py @@ -0,0 +1,33 @@ +#!/usr/bin/python + +from bacpypes.primitivedata import Date + +year_group = ('04', '75', '1929', '255', '*') +month_group = ('1', '12', 'odd', 'even', '255', '*') +day_group = ('1', '22', 'last', 'odd', 'even', '255', '*') +dow_group = ('1', 'mon', '255', '*') + +patterns = [ + "%(year)s-%(month)s-%(day)s %(day_of_week)s", + "%(month)s/%(day)s/%(year)s %(day_of_week)s", + "%(day)s/%(month)s/%(year)s %(day_of_week)s", + ] + +def permutation(**kwargs): + for pattern in patterns: + test_string = pattern % kwargs + try: + test_date = Date(test_string) + test_value = test_date.value + except Exception as why: + test_value = str(why) + print(test_string + '\t' + str(test_value)) + print() + +for year in year_group: + for month in month_group: + for day in day_group: + for day_of_week in dow_group: + permutation( + year=year, month=month, day=day, day_of_week=day_of_week, + ) diff --git a/tests/test_primitive_data/test_character_string.py b/tests/test_primitive_data/test_character_string.py index bfede20..9445f65 100644 --- a/tests/test_primitive_data/test_character_string.py +++ b/tests/test_primitive_data/test_character_string.py @@ -88,7 +88,7 @@ class TestCharacterString(unittest.TestCase): obj = CharacterString("hello") assert obj.value == "hello" - assert str(obj) == "CharacterString(0,'hello')" + assert str(obj) == "CharacterString(0,X'68656c6c6f')" def test_character_string_tag(self): if _debug: TestCharacterString._debug("test_character_string_tag") diff --git a/tests/test_primitive_data/test_date.py b/tests/test_primitive_data/test_date.py index e704ed4..00c851a 100644 --- a/tests/test_primitive_data/test_date.py +++ b/tests/test_primitive_data/test_date.py @@ -9,7 +9,7 @@ Test Primitive Data Date import unittest from bacpypes.debugging import bacpypes_debugging, ModuleLogger, xtob -from bacpypes.primitivedata import Date, Tag, DecodingError +from bacpypes.primitivedata import Date, Tag # some debugging _debug = 0 @@ -18,7 +18,7 @@ _log = ModuleLogger(globals()) @bacpypes_debugging def date_tag(x): - """Convert a hex string to an date application tag.""" + """Convert a hex string to a date application tag.""" if _debug: date_tag._debug("date_tag %r", x) b = xtob(x) @@ -41,7 +41,7 @@ def date_encode(obj): @bacpypes_debugging def date_decode(tag): - """Decode an date application tag into an date.""" + """Decode a date application tag into a date.""" if _debug: date_decode._debug("date_decode %r", tag) obj = Date(tag) @@ -70,7 +70,7 @@ def date_endec(v, x): class TestDate(unittest.TestCase): def test_date(self): - if _debug: TestDate._debug("test_date") + if _debug: TestInteger._debug("test_date") # default values is all dont care obj = Date() @@ -82,32 +82,16 @@ class TestDate(unittest.TestCase): Date(1.0) def test_date_tuple(self): - if _debug: TestDate._debug("test_date_tuple") + if _debug: TestInteger._debug("test_date_tuple") obj = Date((1,2,3,4)) - assert obj.value == (1, 2, 3, 4) - assert str(obj) == "Date(2/3/1901 Thu)" - - ### issue-48 - # obj = Date("1/2/3") - # assert obj.value == (1, 2, x, y) - - # obj = Date("*/2/3") - # assert obj.value == (255, 2, x, 255) - - # obj = Date("1/*/3") - # assert obj.value == (1, 255, x, 255) - - # obj = Date("1/2/*") - # assert obj.value == (1, 2, 255, 255) - - # obj = Date("1/2/3 *") - # assert obj.value == (1, 2, 3, 255) + assert obj.value == (1,2,3,4) + assert str(obj) == "Date(1901-2-3 thu)" def test_date_tag(self): - if _debug: TestDate._debug("test_date_tag") + if _debug: TestInteger._debug("test_date_tag") - tag = Tag(Tag.applicationTagClass, Tag.dateAppTag, 1, xtob('01020304')) + tag = Tag(Tag.applicationTagClass, Tag.dateAppTag, 4, xtob('01020304')) obj = Date(tag) assert obj.value == (1, 2, 3, 4) @@ -124,27 +108,68 @@ class TestDate(unittest.TestCase): Date(tag) def test_date_copy(self): - if _debug: TestDate._debug("test_date_copy") + if _debug: TestInteger._debug("test_date_copy") - date_value = (2, 3, 4, 5) - obj1 = Date(date_value) + value = (1, 2, 3, 4) + obj1 = Date(value) obj2 = Date(obj1) - assert obj2.value == date_value - - def test_date_now(self): - if _debug: TestDate._debug("test_date_now") - - # obj = Date().now() - ### how to test? + assert obj2.value == value def test_date_endec(self): - if _debug: TestDate._debug("test_date_endec") + if _debug: TestInteger._debug("test_date_endec") -# with self.assertRaises(DecodingError): +# with self.assertRaises(IndexError): # obj = Date(date_tag('')) - date_endec((0, 0, 0, 0), '00000000') - date_endec((1, 0, 0, 0), '01000000') - date_endec((0, 2, 0, 0), '00020000') - date_endec((0, 0, 3, 0), '00000300') - date_endec((0, 0, 0, 4), '00000004') + def old_tests(self): + self.test_values = [ + #('1/2/3', 1903, 2, 1, 0), + #('1/2/3', 1903, 2, 1, 0), + ("1/2/2003", 2003, 2, 1, 6), + ("1/20/2003", 2003, 1, 20, 1), + ("01/20/2004", 2004, 1, 20, 2), + ("11/12/2005", 2005, 12, 11, 7), + ("30/1/2006", 2006, 1, 30, 1), + ("30/1/1230", 1230, 1, 30, 255), + ("30/1/98", 1998, 1, 30, 5), + ("2015/8/31", 2015, 8, 31, 1), + ("2015/08/30", 2015, 8, 30, 7), + ("2015/*/30", 2015,255,30,255), + ("2015/1/*",2015,1,255,255), + ("*/1/*", 255,1,255,255), + ("*/*/*",255,255,255,255), + ("1-2-2003", 2003, 2, 1, 6), + ("1-20-2003", 2003, 1, 20, 1), + ("01-20-2004", 2004, 1, 20, 2), + ("11-12-2005", 2005, 12, 11, 7), + ("30-1-2006", 2006, 1, 30, 1), + ("30-1-1230", 1230, 1, 30, 255), + ("30-1-98", 1998, 1, 30, 5), + ("2015-8-31", 2015, 8, 31, 1), + ("2015-08-30", 2015, 8, 30, 7), + ("2015-*-30", 2015,255,30,255), + ("2015-1-*",2015,1,255,255), + ("*-1-*", 255,1,255,255), + ("*-*-*",255,255,255,255) + ] + + self.notEnoughPreciseOrWrong = [ + ('1/31/1'), + ('0/1/4'), + ('99/13/41'), + ("2015/30/*") + ] + + def test_Date_from_str(self): + for each in self.test_values: + new_date = Date(each[0]) + y, m, d, dow = new_date.value + self.assertEqual(y,each[1]) + self.assertEqual(m,each[2]) + self.assertEqual(d,each[3]) + self.assertEqual(dow,each[4]) + + def test_Wrong(self): + with self.assertRaises(ValueError): + for each in self.notEnoughPreciseOrWrong: + new_date = Date(each[0])