From 5c51cf1bc3b77932c8b22583cf8b5ef02a3c59d6 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Mon, 24 Aug 2015 22:04:10 -0400 Subject: [PATCH 01/18] fixed the bug, added wildcard patterns --- py25/bacpypes/primitivedata.py | 10 +++++++--- py27/bacpypes/primitivedata.py | 10 +++++++--- py34/bacpypes/primitivedata.py | 10 +++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index 156f6c8..6f0c62e 100755 --- a/py25/bacpypes/primitivedata.py +++ b/py25/bacpypes/primitivedata.py @@ -1248,11 +1248,15 @@ class Time(Atomic): raise ValueError("invalid time pattern") tup_list = [] - for s in tup_match: + tup_items = list(tup_match.groups()) + for s in tup_items: if s == '*': tup_list.append(255) - elif s in None: - tup_list.append(0) + elif s is None: + if '*' in tup_items: + tup_list.append(255) + else: + tup_list.append(0) else: tup_list.append(int(s)) diff --git a/py27/bacpypes/primitivedata.py b/py27/bacpypes/primitivedata.py index 62efed4..89b3dc6 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -1248,11 +1248,15 @@ class Time(Atomic): raise ValueError("invalid time pattern") tup_list = [] - for s in tup_match: + tup_items = list(tup_match.groups()) + for s in tup_items: if s == '*': tup_list.append(255) - elif s in None: - tup_list.append(0) + elif s is None: + if '*' in tup_items: + tup_list.append(255) + else: + tup_list.append(0) else: tup_list.append(int(s)) diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index 4298020..3fab7aa 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1259,11 +1259,15 @@ class Time(Atomic): raise ValueError("invalid time pattern") tup_list = [] - for s in tup_match: + tup_items = list(tup_match.groups()) + for s in tup_items: if s == '*': tup_list.append(255) - elif s in None: - tup_list.append(0) + elif s is None: + if '*' in tup_items: + tup_list.append(255) + else: + tup_list.append(0) else: tup_list.append(int(s)) From a0535f621ae5d980d92181944a0f055965992d90 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Mon, 24 Aug 2015 22:16:58 -0400 Subject: [PATCH 02/18] add some tests --- tests/test_primitive_data/test_time.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_primitive_data/test_time.py b/tests/test_primitive_data/test_time.py index 5cefa89..1af71f0 100644 --- a/tests/test_primitive_data/test_time.py +++ b/tests/test_primitive_data/test_time.py @@ -88,9 +88,12 @@ class TestTime(unittest.TestCase): assert obj.value == (1, 2, 3, 4) assert str(obj) == "Time(01:02:03.04)" - ### issue-47 - # obj = Time("01:02:03.04") - # assert obj.value == (1, 2, 3, 4) + assert Time("1:2").value == (1, 2, 0, 0) + assert Time("1:2:3").value == (1, 2, 3, 0) + assert Time("1:2:3.4").value == (1, 2, 3, 40) + assert Time("1:*").value == (1, 255, 255, 255) + assert Time("1:2:*").value == (1, 2, 255, 255) + assert Time("1:2:3.*").value == (1, 2, 3, 255) def test_time_tag(self): if _debug: TestTime._debug("test_time_tag") From d358a6246d3b2b7426a2144c90f1e97fb47b2dbf Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Mon, 24 Aug 2015 22:54:50 -0400 Subject: [PATCH 03/18] moved the isinstance check --- py25/bacpypes/primitivedata.py | 4 ++-- py27/bacpypes/primitivedata.py | 4 ++-- py34/bacpypes/primitivedata.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index 6f0c62e..d0fca6a 100755 --- a/py25/bacpypes/primitivedata.py +++ b/py25/bacpypes/primitivedata.py @@ -1397,12 +1397,12 @@ class ObjectIdentifier(Atomic): self.set_long(arg) elif isinstance(arg, tuple): self.set_tuple(*arg) + elif isinstance(arg, ObjectIdentifier): + self.value = arg.value else: raise TypeError("invalid constructor datatype") elif len(args) == 2: self.set_tuple(*args) - elif isinstance(arg, ObjectIdentifier): - self.value = arg.value else: raise ValueError("invalid constructor parameters") diff --git a/py27/bacpypes/primitivedata.py b/py27/bacpypes/primitivedata.py index 89b3dc6..5bdecad 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -1397,12 +1397,12 @@ class ObjectIdentifier(Atomic): self.set_long(arg) elif isinstance(arg, tuple): self.set_tuple(*arg) + elif isinstance(arg, ObjectIdentifier): + self.value = arg.value else: raise TypeError("invalid constructor datatype") elif len(args) == 2: self.set_tuple(*args) - elif isinstance(arg, ObjectIdentifier): - self.value = arg.value else: raise ValueError("invalid constructor parameters") diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index 3fab7aa..6d9d266 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1406,12 +1406,12 @@ class ObjectIdentifier(Atomic): self.set_long(arg) elif isinstance(arg, tuple): self.set_tuple(*arg) + elif isinstance(arg, ObjectIdentifier): + self.value = arg.value else: raise TypeError("invalid constructor datatype") elif len(args) == 2: self.set_tuple(*args) - elif isinstance(arg, ObjectIdentifier): - self.value = arg.value else: raise ValueError("invalid constructor parameters") From 053a8fd4180d004acdaac661de1fbdbb03aeb17a Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Mon, 24 Aug 2015 22:57:51 -0400 Subject: [PATCH 04/18] test passes, good to go --- tests/test_primitive_data/test_object_identifier.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_primitive_data/test_object_identifier.py b/tests/test_primitive_data/test_object_identifier.py index bcd780c..c1102eb 100644 --- a/tests/test_primitive_data/test_object_identifier.py +++ b/tests/test_primitive_data/test_object_identifier.py @@ -114,10 +114,9 @@ class TestObjectIdentifier(unittest.TestCase): def test_object_identifier_copy(self): if _debug: TestObjectIdentifier._debug("test_object_identifier_copy") - ### issue-49 - # obj1 = ObjectIdentifier(('analogInput', 1)) - # obj2 = ObjectIdentifier(obj1) - # assert obj2.value == ('analogInput', 1) + obj1 = ObjectIdentifier(('analogInput', 1)) + obj2 = ObjectIdentifier(obj1) + assert obj2.value == ('analogInput', 1) def test_object_identifier_endec(self): if _debug: TestObjectIdentifier._debug("test_object_identifier_endec") From 0b6544f20657d78ff927ec37ba9466bc98a0fdab Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Mon, 24 Aug 2015 23:33:15 -0400 Subject: [PATCH 05/18] adjusted encoding, it relies on tagLVT being zero and tagData being b'' for opening and closing tags --- py25/bacpypes/primitivedata.py | 14 +++++--------- py27/bacpypes/primitivedata.py | 14 +++++--------- py34/bacpypes/primitivedata.py | 14 +++++--------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index d0fca6a..3447fdb 100755 --- a/py25/bacpypes/primitivedata.py +++ b/py25/bacpypes/primitivedata.py @@ -92,17 +92,13 @@ class Tag(object): self.tagData = tdata def encode(self, pdu): - # check for special encoding of open and close tags - if (self.tagClass == Tag.openingTagClass): - pdu.put(((self.tagNumber & 0x0F) << 4) + 0x0E) - return - if (self.tagClass == Tag.closingTagClass): - pdu.put(((self.tagNumber & 0x0F) << 4) + 0x0F) - return - - # check for context encoding + # check for special encoding if (self.tagClass == Tag.contextTagClass): data = 0x08 + elif (self.tagClass == Tag.openingTagClass): + data = 0x0E + elif (self.tagClass == Tag.closingTagClass): + data = 0x0F else: data = 0x00 diff --git a/py27/bacpypes/primitivedata.py b/py27/bacpypes/primitivedata.py index 5bdecad..ee4c517 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -92,17 +92,13 @@ class Tag(object): self.tagData = tdata def encode(self, pdu): - # check for special encoding of open and close tags - if (self.tagClass == Tag.openingTagClass): - pdu.put(((self.tagNumber & 0x0F) << 4) + 0x0E) - return - if (self.tagClass == Tag.closingTagClass): - pdu.put(((self.tagNumber & 0x0F) << 4) + 0x0F) - return - - # check for context encoding + # check for special encoding if (self.tagClass == Tag.contextTagClass): data = 0x08 + elif (self.tagClass == Tag.openingTagClass): + data = 0x0E + elif (self.tagClass == Tag.closingTagClass): + data = 0x0F else: data = 0x00 diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index 6d9d266..cec1898 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -92,17 +92,13 @@ class Tag(object): self.tagData = tdata def encode(self, pdu): - # check for special encoding of open and close tags - if (self.tagClass == Tag.openingTagClass): - pdu.put(((self.tagNumber & 0x0F) << 4) + 0x0E) - return - if (self.tagClass == Tag.closingTagClass): - pdu.put(((self.tagNumber & 0x0F) << 4) + 0x0F) - return - - # check for context encoding + # check for special encoding if (self.tagClass == Tag.contextTagClass): data = 0x08 + elif (self.tagClass == Tag.openingTagClass): + data = 0x0E + elif (self.tagClass == Tag.closingTagClass): + data = 0x0F else: data = 0x00 From 0e0bc2a530715438058dfc13c5d98fc44bc09b3a Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Mon, 24 Aug 2015 23:34:51 -0400 Subject: [PATCH 06/18] enabled tests --- tests/test_primitive_data/test_tag.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_primitive_data/test_tag.py b/tests/test_primitive_data/test_tag.py index 3ba62c6..1bcfa84 100644 --- a/tests/test_primitive_data/test_tag.py +++ b/tests/test_primitive_data/test_tag.py @@ -444,7 +444,8 @@ class TestOpeningTag(unittest.TestCase): opening_endec(2, '2E') opening_endec(3, '3E') opening_endec(14, 'EE') -# opening_endec(15, 'FE0F') + opening_endec(15, 'FE0F') + opening_endec(254, 'FEFE') @bacpypes_debugging @@ -464,7 +465,8 @@ class TestClosingTag(unittest.TestCase): closing_endec(2, '2F') closing_endec(3, '3F') closing_endec(14, 'EF') -# closing_endec(15, 'FE0F') + closing_endec(15, 'FF0F') + closing_endec(254, 'FFFE') @bacpypes_debugging From 350333fb89cc5ab7d991099c58127965503deb18 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Tue, 25 Aug 2015 10:32:26 -0400 Subject: [PATCH 07/18] whip through the __mro__ looking for enumerations, polish the __str__ --- py25/bacpypes/primitivedata.py | 18 +++++++++++------- py27/bacpypes/primitivedata.py | 18 +++++++++++------- py34/bacpypes/primitivedata.py | 18 +++++++++++------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index 3447fdb..d00d9dd 100755 --- a/py25/bacpypes/primitivedata.py +++ b/py25/bacpypes/primitivedata.py @@ -1080,7 +1080,7 @@ class Enumerated(Atomic): self.value = rslt def __str__(self): - return "Enumerated(%s)" % (self.value,) + return "%s(%s)" % (self.__class__.__name__, self.value) # # expand_enumerations @@ -1089,13 +1089,17 @@ class Enumerated(Atomic): def expand_enumerations(klass): # build a value dictionary xlateTable = {} - for name, value in klass.enumerations.items(): - # save the results - xlateTable[name] = value - xlateTable[value] = name - # save the name in the class - setattr(klass, name, value) + for c in klass.__mro__: + enumerations = getattr(c, 'enumerations', {}) + if enumerations: + for name, value in enumerations.items(): + # save the results + xlateTable[name] = value + xlateTable[value] = name + + # save the name in the class + setattr(klass, name, value) # save the dictionary in the class setattr(klass, '_xlate_table', xlateTable) diff --git a/py27/bacpypes/primitivedata.py b/py27/bacpypes/primitivedata.py index ee4c517..d99fc69 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -1080,7 +1080,7 @@ class Enumerated(Atomic): self.value = rslt def __str__(self): - return "Enumerated(%s)" % (self.value,) + return "%s(%s)" % (self.__class__.__name__, self.value) # # expand_enumerations @@ -1089,13 +1089,17 @@ class Enumerated(Atomic): def expand_enumerations(klass): # build a value dictionary xlateTable = {} - for name, value in klass.enumerations.items(): - # save the results - xlateTable[name] = value - xlateTable[value] = name - # save the name in the class - setattr(klass, name, value) + for c in klass.__mro__: + enumerations = getattr(c, 'enumerations', {}) + if enumerations: + for name, value in enumerations.items(): + # save the results + xlateTable[name] = value + xlateTable[value] = name + + # save the name in the class + setattr(klass, name, value) # save the dictionary in the class setattr(klass, '_xlate_table', xlateTable) diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index cec1898..1208cb3 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1091,7 +1091,7 @@ class Enumerated(Atomic): self.value = rslt def __str__(self): - return "Enumerated(%s)" % (self.value,) + return "%s(%s)" % (self.__class__.__name__, self.value) # # expand_enumerations @@ -1100,13 +1100,17 @@ class Enumerated(Atomic): def expand_enumerations(klass): # build a value dictionary xlateTable = {} - for name, value in klass.enumerations.items(): - # save the results - xlateTable[name] = value - xlateTable[value] = name - # save the name in the class - setattr(klass, name, value) + for c in klass.__mro__: + enumerations = getattr(c, 'enumerations', {}) + if enumerations: + for name, value in enumerations.items(): + # save the results + xlateTable[name] = value + xlateTable[value] = name + + # save the name in the class + setattr(klass, name, value) # save the dictionary in the class setattr(klass, '_xlate_table', xlateTable) From dea0667930ff44c7a83bada997d9a23cb9c5beec Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Tue, 25 Aug 2015 10:33:12 -0400 Subject: [PATCH 08/18] line up the tests --- tests/test_primitive_data/test_object_type.py | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/test_primitive_data/test_object_type.py b/tests/test_primitive_data/test_object_type.py index 1aae7d6..648d627 100644 --- a/tests/test_primitive_data/test_object_type.py +++ b/tests/test_primitive_data/test_object_type.py @@ -97,12 +97,12 @@ class TestObjectType(unittest.TestCase): # known values are translated into strings obj = ObjectType(0) assert obj.value == 'analogInput' - assert str(obj) == "Enumerated(analogInput)" + assert str(obj) == "ObjectType(analogInput)" # unknown values are kept as integers obj = ObjectType(127) assert obj.value == 127 - assert str(obj) == "Enumerated(127)" + assert str(obj) == "ObjectType(127)" def test_object_type_str(self): if _debug: TestObjectType._debug("test_object_type_str") @@ -111,6 +111,30 @@ class TestObjectType(unittest.TestCase): obj = ObjectType('analogInput') assert obj.value == 'analogInput' + def test_extended_object_type_int(self): + if _debug: TestObjectType._debug("test_extended_object_type_int") + + # known values are translated into strings + obj = MyObjectType(0) + assert obj.value == 'analogInput' + assert str(obj) == "MyObjectType(analogInput)" + + # unknown values are kept as integers + obj = MyObjectType(128) + assert obj.value == 'myAnalogInput' + assert str(obj) == "MyObjectType(myAnalogInput)" + + def test_extended_object_type_str(self): + if _debug: TestObjectType._debug("test_extended_object_type_str") + + # known strings are accepted + obj = MyObjectType('myAnalogInput') + assert obj.value == 'myAnalogInput' + + # unknown strings are rejected + with self.assertRaises(ValueError): + MyObjectType('snork') + def test_object_type_tag(self): if _debug: TestObjectType._debug("test_object_type_tag") From 736187580b08b7ddc91481849bc500dca06dce41 Mon Sep 17 00:00:00 2001 From: "Christian Tremblay, ing" Date: Sat, 29 Aug 2015 21:29:18 -0400 Subject: [PATCH 09/18] Modifications to Date object to handle multiple string format Signed-off-by: Christian Tremblay, ing. --- py34/bacpypes/primitivedata.py | 61 ++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index ccbecfa..2db7234 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1132,17 +1132,20 @@ class Date(Atomic): """ _app_tag = Tag.dateAppTag - _date_regex_mmddyyyy = re.compile(r'[0-1]*\d[-/][0-3]*\d[-/]\d{4}$') - _date_regex_ddmmyyyy = re.compile(r'[0-3]*\d[-/][0-1]*\d[-/]\d{4}$') - _date_regex_mmddyy = re.compile(r'[0-1]*\d[-/][0-3]*\d[-/]\d{2}$') - _date_regex_ddmmyy = re.compile(r'[0-3]*\d[-/][0-1]*\d[-/]\d{2}$') + _date_regex_mmddyyyy = re.compile(r'([0-1]*\d)[-/]([0-3]*\d)[-/](\d{4}$)') + _date_regex_yyyymmdd = re.compile(r'(\d{4})[-/]([0-1]*\d)[-/]([0-3]*\d$)') + _date_regex_ddmmyyyy = re.compile(r'([0-3]*\d)[-/]([0-1]*\d)[-/](\d{4}$)') + _date_regex_mmddyy = re.compile(r'([0-1]*\d)[-/]([0-3]*\d)[-/](\d{2}$)') + _date_regex_ddmmyy = re.compile(r'([0-3]*\d)[-/]([0-1]*\d)[-/](\d{2}$)') + _date_regex_dmy = re.compile(r'(\d)[-/](\d)[-/](\d$)') _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) - + date_groups = [0,0,0,None] + if arg is None: pass elif isinstance(arg,Tag): @@ -1150,25 +1153,39 @@ class Date(Atomic): elif isinstance(arg, tuple): self.value = arg elif isinstance(arg, str): - if Date._date_regex_mmddyyyy.match(arg) and not Date._date_regex_ddmmyyyy.match(arg): - #Will be mmddyyyy - pass + if (Date._date_regex_mmddyyyy.match(arg) and not Date._date_regex_ddmmyyyy.match(arg)): + #Will be mmddyyyy + month, day, year = Date._date_regex_mmddyyyy.match(arg).groups() + + elif Date._date_regex_yyyymmdd.match(arg): + #will be yyyymmdd + year, month, day = Date._date_regex_yyyymmdd.match(arg).groups() + elif Date._date_regex_ddmmyyyy.match(arg) and not Date._date_regex_mmddyyyy.match(arg) : - #will be ddmmyyyy - pass + #will be ddmmyyyy + day, month, year = Date._date_regex_ddmmyyyy.match(arg).groups() + elif Date._date_regex_ddmmyyyy.match(arg) and Date._date_regex_mmddyyyy.match(arg) : - #will be ddmmyyyy - pass - elif Date._date_regex_mmddyy.match(arg) and not Date._date_regex_mmddyy.match(arg) : - pass - elif Date._date_regex_mmddyy.match(arg) and not Date._date_regex_mmddyy.match(arg): - pass - elif Date._date_regex_mmddyy.match(arg) and Date._date_regex_mmddyy.match(arg): - pass + #will be ddmmyyyy + day, month, year = Date._date_regex_ddmmyyyy.match(arg).groups() + + elif Date._date_regex_ddmmyy.match(arg) and not Date._date_regex_mmddyy.match(arg) : + day, month, year = Date._date_regex_ddmmyy.match(arg).groups() + + elif Date._date_regex_mmddyy.match(arg) and not Date._date_regex_ddmmyy.match(arg): + month, day, year = Date._date_regex_mmddyy.match(arg).groups() + + elif Date._date_regex_ddmmyy.match(arg) and Date._date_regex_mmddyy.match(arg): + day, month, year = Date._date_regex_ddmmyy.match(arg).groups() + elif Date._date_regex_dmy.match(arg): + day, month, year = Date._date_regex_dmy.match(arg).groups() else: raise ValueError("invalid date pattern") - date_groups = date_match.groups() + + date_groups[0] = year + date_groups[1] = month + date_groups[2] = day # day/month/year tup_list = [] for s in date_groups[:3]: @@ -1180,8 +1197,8 @@ class Date(Atomic): tup_list.append(int(s)) # clean up the year - if (tup_list[2] < 100): - tup_list[2] += 2000 + if (tup_list[0] < 100): + tup_list[0] += 1900 #tup_list[1] -= 1900 # day-of-week madness @@ -1251,7 +1268,7 @@ class Date(Atomic): if year == 255: rslt += "* " else: - rslt += "%d " % (year + 1900,) + rslt += "%d " % (year,) if dayOfWeek == 255: rslt += "*)" else: From 4a2770c5d685926ce736ec3e8774e59cb4b5b273 Mon Sep 17 00:00:00 2001 From: "Christian Tremblay, ing" Date: Mon, 31 Aug 2015 22:06:44 -0400 Subject: [PATCH 10/18] Tried to add a lot of flexibility to the Date object. Regex now recognize a lot of different formats Deal with errors when trying to find day of week test_date.py covers a lot of different formats but can be improved. Signed-off-by: Christian Tremblay, ing. --- py34/bacpypes/primitivedata.py | 45 +++++++++------ tests/test_primitive_data/test_date.py | 79 ++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 18 deletions(-) diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index 2db7234..d2d24de 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1037,7 +1037,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) @@ -1132,12 +1132,13 @@ class Date(Atomic): """ _app_tag = Tag.dateAppTag - _date_regex_mmddyyyy = re.compile(r'([0-1]*\d)[-/]([0-3]*\d)[-/](\d{4}$)') - _date_regex_yyyymmdd = re.compile(r'(\d{4})[-/]([0-1]*\d)[-/]([0-3]*\d$)') - _date_regex_ddmmyyyy = re.compile(r'([0-3]*\d)[-/]([0-1]*\d)[-/](\d{4}$)') - _date_regex_mmddyy = re.compile(r'([0-1]*\d)[-/]([0-3]*\d)[-/](\d{2}$)') - _date_regex_ddmmyy = re.compile(r'([0-3]*\d)[-/]([0-1]*\d)[-/](\d{2}$)') - _date_regex_dmy = re.compile(r'(\d)[-/](\d)[-/](\d$)') + _date_regex_mmddyyyy = re.compile(r'([0-1]*\d|[*])[-/]([0-3]*\d|[*])[-/](\d{4}$|[*]$)') + _date_regex_yyyymmdd = re.compile(r'(\d{4}|[*])[-/]([0-1]*\d|[*])[-/]([0-3]*\d$|[*]$)') + _date_regex_ddmmyyyy = re.compile(r'([0-3]*\d|[*])[-/]([0-1]*\d|[*])[-/](\d{4}$|[*]$)') + _date_regex_yymmdd = re.compile(r'(\d{2}|[*])[-/]([0-3]*\d|[*])[-/]([0-1]*\d$|[*]$)') + _date_regex_mmddyy = re.compile(r'([0-1]*\d|[*])[-/]([0-3]*\d|[*])[-/](\d{2}$|[*]$)') + _date_regex_ddmmyy = re.compile(r'([0-3]*\d|[*])[-/]([0-1]*\d|[*])[-/](\d{2}$|[*]$)') + _date_regex_dmy = re.compile(r'(\d|[*])[-/](\d|[*])[-/](\d|[*]$)') _day_names = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] DONT_CARE = 255 @@ -1160,6 +1161,10 @@ class Date(Atomic): elif Date._date_regex_yyyymmdd.match(arg): #will be yyyymmdd year, month, day = Date._date_regex_yyyymmdd.match(arg).groups() + + elif Date._date_regex_yymmdd.match(arg): + #will be ddmmyyyy + year, month, day = Date._date_regex_yymmdd.match(arg).groups() elif Date._date_regex_ddmmyyyy.match(arg) and not Date._date_regex_mmddyyyy.match(arg) : #will be ddmmyyyy @@ -1204,7 +1209,7 @@ class Date(Atomic): # day-of-week madness dow = date_groups[3] if dow is None: - tup_list.append(0) + tup_list.append(255) elif (dow == '*'): tup_list.append(255) elif dow.isdigit(): @@ -1216,6 +1221,7 @@ class Date(Atomic): tup_list.append(Date._day_names.index(dow)) self.value = tuple(tup_list) + self.CalcDayOfWeek() elif isinstance(arg, Date): self.value = arg.value else: @@ -1223,20 +1229,22 @@ class Date(Atomic): 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 - + # 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 + try: + today = time.mktime( (year, month, day, 0, 0, 0, 0, 0, -1) ) + dayOfWeek = time.gmtime(today)[6] + 1 + except OverflowError: + # If date before epoch... won't find dow + dayOfWeek = 255 # put it back together self.value = (year, month, day, dayOfWeek) @@ -1257,18 +1265,19 @@ class Date(Atomic): year, month, day, dayOfWeek = self.value rslt = "Date(" + if year == 255: + rslt += "*/" + else: + rslt += "%d/" % (year,) if month == 255: rslt += "*/" else: rslt += "%d/" % (month,) if day == 255: - rslt += "*/" - else: - rslt += "%d/" % (day,) - if year == 255: rslt += "* " else: - rslt += "%d " % (year,) + rslt += "%d " % (day,) + if dayOfWeek == 255: rslt += "*)" else: diff --git a/tests/test_primitive_data/test_date.py b/tests/test_primitive_data/test_date.py index e69de29..64df899 100644 --- a/tests/test_primitive_data/test_date.py +++ b/tests/test_primitive_data/test_date.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Test date +---------------------------- +""" + +import unittest + +from bacpypes.debugging import bacpypes_debugging, ModuleLogger +from bacpypes.primitivedata import Date + +# some debugging +_debug = 0 +_log = ModuleLogger(globals()) + + +@bacpypes_debugging +class TestDate(unittest.TestCase): + def setUp(self): + # test_values are tuple with str, year, month, date, dow + # year, month, day, dayOfWeek are expected reaulsts + + 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]) + + + \ No newline at end of file From b483a3608ba39cf5f64ae1f23c7dac978cb06a08 Mon Sep 17 00:00:00 2001 From: "Christian Tremblay, ing" Date: Mon, 31 Aug 2015 22:10:27 -0400 Subject: [PATCH 11/18] Removed files from branch... they should not be there Signed-off-by: Christian Tremblay, ing. --- tests/test_PDU.py | 50 ----------------------------------- tests/test_npdu.py | 25 ------------------ tests/test_primitivedata.py | 52 ------------------------------------- 3 files changed, 127 deletions(-) delete mode 100644 tests/test_PDU.py delete mode 100644 tests/test_npdu.py delete mode 100644 tests/test_primitivedata.py diff --git a/tests/test_PDU.py b/tests/test_PDU.py deleted file mode 100644 index 96c4a60..0000000 --- a/tests/test_PDU.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 17 23:13:19 2015 - -@author: CTremblay -""" -import unittest -from bacpypes.comm import PDU -from bacpypes.debugging import btox, xtob - -class TestPDU(unittest.TestCase): - def setUp(self): - self.pdu = PDU(b"hello") - self.pdu_source_dest = PDU(b"hello", source=1, destination=2) - self.hexHello = btox(b'hello','.') - self.pdu1 = PDU(b'hello!!') - - def test_simplePDU(self): - # parse command line options - self.assertEqual(self.pdu.pduData,b'hello') - - - def test_PDUWithAddress(self): - # parse command line options - self.assertEqual(self.pdu_source_dest.pduDestination,2) - self.assertEqual(self.pdu_source_dest.pduSource,1) - self.assertEqual(self.pdu.pduData,b'hello') - - - def test_strToHex(self): - self.assertEqual(self.hexHello,'68.65.6c.6c.6f') - - def test_HexToStr(self): - self.assertEqual(xtob('68.65.6c.6c.6f','.'),b'hello') - - def test_pduGetAndPut(self): - self.assertEqual(self.pdu1.get(),104) #get the first letter : h - #self.assertEqual(self.pdu1.get_short(),25964) - #self.assertEqual(self.pdu1.get_long(),1819222305) - self.pdu1.put(105) - self.assertEqual(self.pdu1.get_short(),25964) - self.assertEqual(self.pdu1.get_long(),1819222305) - - - #def test_HexToStr(self): - # self.assertEqual(_hex_to_str(self.hexHello),'hello') - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_npdu.py b/tests/test_npdu.py deleted file mode 100644 index e31886c..0000000 --- a/tests/test_npdu.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 17 23:13:19 2015 - -@author: CTremblay -""" -import unittest -from bacpypes.debugging import btox -from bacpypes.comm import PDU - -class TestNPDU(unittest.TestCase): - def setUp(self): - self.pdu = PDU(b"hello") - self.pdu_source_dest = PDU(b"hello", source=1, destination=2) - self.hexHello = btox(b'hello','.') - self.pdu1 = PDU(b'hello!!') - - - - def test_byteToHex(self): - self.assertEqual(self.hexHello,'68.65.6c.6c.6f') - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_primitivedata.py b/tests/test_primitivedata.py deleted file mode 100644 index 5e3c92b..0000000 --- a/tests/test_primitivedata.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 17 23:13:19 2015 - -@author: CTremblay -""" -import unittest -from bacpypes.comm import PDU -from bacpypes.primitivedata import TagList, Tag,Unsigned, Enumerated -from bacpypes.debugging import btox, xtob - -class TestPrimitive(unittest.TestCase): - def setUp(self): - self.hexHello = btox(b'hello','.') - self.tlist = TagList() - self.tg = Tag() - self.p = PDU(b'') - self.unsigned_1000 = Unsigned(1000) - self.unsigned_5 = Unsigned(5) - self.t_1000 = Tag() - self.t_5 = Tag() - self.enum = Enumerated(0) - - def test_strToHex(self): - self.assertEqual(self.hexHello,'68.65.6c.6c.6f') - def test_HexToStr(self): - self.assertEqual(xtob('68.65.6c.6c.6f','.'),b'hello') - - def test_Unsigned_Values(self): - self.assertEqual(self.unsigned_1000.value,1000) - self.assertEqual(self.unsigned_5.value,5) - - def test_Unsigned_Encoding(self): - self.unsigned_1000.encode(self.t_1000) - self.unsigned_5.encode(self.t_5) - self.assertEqual(self.t_1000.tagData,b'\x03\xe8') - self.assertEqual(self.t_5.tagData,b'\x05') - - def test_pdu_put_over256(self): - with self.assertRaises(ValueError): - self.p.put(257) - - def test_Enumerated(self): - self.assertEqual(self.enum.value,0) - def test_Enumerated_Encoding(self): - self.enum.encode(self.tg) - self.assertEqual(self.tg.tagData,b'\x00') - - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file From c89ffbc61f8cf1e3d872dcd45f67ba94be944cbb Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Thu, 3 Sep 2015 22:40:05 -0400 Subject: [PATCH 12/18] add an alternative to date parsing --- samples/date_alternative.py | 195 ++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 samples/date_alternative.py diff --git a/samples/date_alternative.py b/samples/date_alternative.py new file mode 100644 index 0000000..caa4dc9 --- /dev/null +++ b/samples/date_alternative.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +import time + +from bacpypes.primitivedata import Tag, Atomic + +_mm = r'(?P0?\d|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): + + 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): + self.decode(arg) + elif isinstance(arg, tuple): + self.value = arg + elif isinstance(arg, str): + # lower case everything + arg = arg.lower() + + # 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()) + + # try to find a good one + match = None + if not matches: + raise ValueError("unmatched") + + # if there is only one, success + if len(matches) == 1: + match = matches[0] + else: + # 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 + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if month > 14: + print("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 > 34: + print("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) + + # 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() + + elif isinstance(arg, Date): + self.value = arg.value + + else: + raise TypeError("invalid constructor datatype") + + def CalcDayOfWeek(self): + """Calculate the correct day of the week.""" + # rip apart the value + year, month, day, day_of_week = self.value + + # 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, 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)) + + def decode(self, tag): + if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.dateAppTag): + raise ValueError("date application tag required") + + # rip apart the data + self.value = tuple(tag.tagData) + + def __str__(self): + """String representation of the date.""" + # rip it apart + year, month, day, day_of_week = self.value + + if year == 255: + year = "*" + else: + 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" % (year, month, day, day_of_week) + + def __repr__(self): + return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) From af7e2e34a43e2c0d391344c9fe3b4c4ad94ebb38 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sat, 5 Sep 2015 18:02:17 -0400 Subject: [PATCH 13/18] date class rolled in --- py25/bacpypes/primitivedata.py | 218 ++++++++++++++------- py27/bacpypes/primitivedata.py | 217 ++++++++++++++------- py34/bacpypes/primitivedata.py | 260 ++++++++++++++----------- tests/test_primitive_data/test_date.py | 145 +++++++++++--- 4 files changed, 558 insertions(+), 282 deletions(-) diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index 83f4ebf..e6b2099 100755 --- a/py25/bacpypes/primitivedata.py +++ b/py25/bacpypes/primitivedata.py @@ -1108,83 +1108,163 @@ def expand_enumerations(klass): # Date # +_mm = r'(?P0?\d|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 + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if month > 14: + print("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 > 34: + print("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) + + # 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 @@ -1195,31 +1275,27 @@ class Date(Atomic): raise ValueError("date application tag required") # rip apart the data - self.value = tuple(ord(c) for c in tag.tagData) + 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" % (year, month, day, day_of_week) + + def __repr__(self): + return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) - return rslt # # Time diff --git a/py27/bacpypes/primitivedata.py b/py27/bacpypes/primitivedata.py index c9464a6..b0c5302 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -1104,87 +1104,168 @@ def expand_enumerations(klass): # save the dictionary in the class setattr(klass, '_xlate_table', xlateTable) + # # Date # +_mm = r'(?P0?\d|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 + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if month > 14: + print("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 > 34: + print("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) + + # 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 +1279,24 @@ 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" % (year, month, day, day_of_week) + + def __repr__(self): + return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) - return rslt # # Time diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index d2d24de..4b0d96b 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1126,129 +1126,162 @@ def expand_enumerations(klass): # Date # +_mm = r'(?P0?\d|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): - """ - Date object - """ - _app_tag = Tag.dateAppTag - _date_regex_mmddyyyy = re.compile(r'([0-1]*\d|[*])[-/]([0-3]*\d|[*])[-/](\d{4}$|[*]$)') - _date_regex_yyyymmdd = re.compile(r'(\d{4}|[*])[-/]([0-1]*\d|[*])[-/]([0-3]*\d$|[*]$)') - _date_regex_ddmmyyyy = re.compile(r'([0-3]*\d|[*])[-/]([0-1]*\d|[*])[-/](\d{4}$|[*]$)') - _date_regex_yymmdd = re.compile(r'(\d{2}|[*])[-/]([0-3]*\d|[*])[-/]([0-1]*\d$|[*]$)') - _date_regex_mmddyy = re.compile(r'([0-1]*\d|[*])[-/]([0-3]*\d|[*])[-/](\d{2}$|[*]$)') - _date_regex_ddmmyy = re.compile(r'([0-3]*\d|[*])[-/]([0-1]*\d|[*])[-/](\d{2}$|[*]$)') - _date_regex_dmy = re.compile(r'(\d|[*])[-/](\d|[*])[-/](\d|[*]$)') - _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) - date_groups = [0,0,0,None] + 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): - if (Date._date_regex_mmddyyyy.match(arg) and not Date._date_regex_ddmmyyyy.match(arg)): - #Will be mmddyyyy - month, day, year = Date._date_regex_mmddyyyy.match(arg).groups() - - elif Date._date_regex_yyyymmdd.match(arg): - #will be yyyymmdd - year, month, day = Date._date_regex_yyyymmdd.match(arg).groups() - - elif Date._date_regex_yymmdd.match(arg): - #will be ddmmyyyy - year, month, day = Date._date_regex_yymmdd.match(arg).groups() - - elif Date._date_regex_ddmmyyyy.match(arg) and not Date._date_regex_mmddyyyy.match(arg) : - #will be ddmmyyyy - day, month, year = Date._date_regex_ddmmyyyy.match(arg).groups() - - elif Date._date_regex_ddmmyyyy.match(arg) and Date._date_regex_mmddyyyy.match(arg) : - #will be ddmmyyyy - day, month, year = Date._date_regex_ddmmyyyy.match(arg).groups() - - elif Date._date_regex_ddmmyy.match(arg) and not Date._date_regex_mmddyy.match(arg) : - day, month, year = Date._date_regex_ddmmyy.match(arg).groups() - - elif Date._date_regex_mmddyy.match(arg) and not Date._date_regex_ddmmyy.match(arg): - month, day, year = Date._date_regex_mmddyy.match(arg).groups() - - elif Date._date_regex_ddmmyy.match(arg) and Date._date_regex_mmddyy.match(arg): - day, month, year = Date._date_regex_ddmmyy.match(arg).groups() - elif Date._date_regex_dmy.match(arg): - day, month, year = Date._date_regex_dmy.match(arg).groups() + # lower case everything + arg = arg.lower() + + # 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()) + + # try to find a good one + match = None + if not matches: + raise ValueError("unmatched") + + # if there is only one, success + if len(matches) == 1: + match = matches[0] else: - raise ValueError("invalid date pattern") - - - date_groups[0] = year - date_groups[1] = month - date_groups[2] = day - # day/month/year - tup_list = [] - for s in date_groups[:3]: - if s == '*': - tup_list.append(255) - elif s is None: - tup_list.append(0) + # 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: - tup_list.append(int(s)) + match = matches[0] - # clean up the year - if (tup_list[0] < 100): - tup_list[0] += 1900 - #tup_list[1] -= 1900 - - # day-of-week madness - dow = date_groups[3] - if dow is None: - tup_list.append(255) - elif (dow == '*'): - tup_list.append(255) - elif dow.isdigit(): - tup_list.append(int(dow)) + # extract the year and normalize + year = match['year'] + if (year == '*') or (not year): + year = 255 else: - dow = dow.title() - if dow not in Date._day_names: - raise ValueError("invalid day name") - tup_list.append(Date._day_names.index(dow)) + year = int(year) + if (year == 255): + pass + elif year < 35: + year += 2000 + elif year < 100: + year += 1900 + + # extract the month and normalize + month = match['month'] + if month in _special_mon: + month = _special_mon[month] + else: + month = int(month) + if month > 14: + print("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 > 34: + print("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) + + # 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) - self.CalcDayOfWeek() elif isinstance(arg, Date): self.value = arg.value + else: raise TypeError("invalid constructor datatype") + def CalcDayOfWeek(self): + """Calculate the correct day of the week.""" + # rip apart the value + year, month, day, day_of_week = self.value + + # 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, day_of_week) + 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 - - # make sure all the components are defined - if (year != 255) and (month != 255) and (day != 255): - try: - today = time.mktime( (year, month, day, 0, 0, 0, 0, 0, -1) ) - dayOfWeek = time.gmtime(today)[6] + 1 - except OverflowError: - # If date before epoch... won't find dow - dayOfWeek = 255 - - # put it back together - self.value = (year, month, day, dayOfWeek) - def encode(self, tag): # encode the tag tag.set_app_data(Tag.dateAppTag, bytearray(self.value)) @@ -1261,29 +1294,24 @@ 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 year == 255: - rslt += "*/" + year = "*" else: - rslt += "%d/" % (year,) - if month == 255: - rslt += "*/" - else: - rslt += "%d/" % (month,) - if day == 255: - rslt += "* " - else: - rslt += "%d " % (day,) + year = str(year + 1900) - if dayOfWeek == 255: - rslt += "*)" - else: - rslt += Date._day_names[dayOfWeek] + ")" + 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" % (year, month, day, day_of_week) + + def __repr__(self): + return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) - return rslt # # Time diff --git a/tests/test_primitive_data/test_date.py b/tests/test_primitive_data/test_date.py index 64df899..3deb10b 100644 --- a/tests/test_primitive_data/test_date.py +++ b/tests/test_primitive_data/test_date.py @@ -2,26 +2,125 @@ # -*- coding: utf-8 -*- """ -Test date ----------------------------- +Test Primitive Data Date +--------------------------- """ import unittest -from bacpypes.debugging import bacpypes_debugging, ModuleLogger -from bacpypes.primitivedata import Date +from bacpypes.debugging import bacpypes_debugging, ModuleLogger, xtob +from bacpypes.primitivedata import Date, Tag # some debugging _debug = 0 _log = ModuleLogger(globals()) +@bacpypes_debugging +def date_tag(x): + """Convert a hex string to a date application tag.""" + if _debug: date_tag._debug("date_tag %r", x) + + b = xtob(x) + tag = Tag(Tag.applicationTagClass, Tag.dateAppTag, len(b), b) + if _debug: date_endec._debug(" - tag: %r", tag) + + return tag + +@bacpypes_debugging +def date_encode(obj): + """Encode an Date object into a tag.""" + if _debug: date_encode._debug("date_encode %r", obj) + + tag = Tag() + obj.encode(tag) + if _debug: date_endec._debug(" - tag: %r", tag) + + return tag + + +@bacpypes_debugging +def date_decode(tag): + """Decode a date application tag into a date.""" + if _debug: date_decode._debug("date_decode %r", tag) + + obj = Date(tag) + if _debug: date_decode._debug(" - obj: %r", obj) + + return obj + + +@bacpypes_debugging +def date_endec(v, x): + """Pass the value to Date, construct a tag from the hex string, + and compare results of encode and decoding each other.""" + if _debug: date_endec._debug("date_endec %r %r", v, x) + + tag = date_tag(x) + if _debug: date_endec._debug(" - tag: %r, %r", tag, tag.tagData) + + obj = Date(v) + if _debug: date_endec._debug(" - obj: %r, %r", obj, obj.value) + + assert date_encode(obj) == tag + assert date_decode(tag) == obj + + @bacpypes_debugging class TestDate(unittest.TestCase): - def setUp(self): - # test_values are tuple with str, year, month, date, dow - # year, month, day, dayOfWeek are expected reaulsts - + + def test_date(self): + if _debug: TestInteger._debug("test_date") + + obj = Date() + assert obj.value == (255, 255, 255, 255) + + with self.assertRaises(ValueError): + Date("some string") + with self.assertRaises(TypeError): + Date(1.0) + + def test_date_tuple(self): + if _debug: TestInteger._debug("test_date_tuple") + + obj = Date((1,2,3,4)) + assert obj.value == (1,2,3,4) + assert str(obj) == "1901-2-3 thu" + + def test_date_tag(self): + if _debug: TestInteger._debug("test_date_tag") + + tag = Tag(Tag.applicationTagClass, Tag.dateAppTag, 4, xtob('01020304')) + obj = Date(tag) + assert obj.value == (1, 2, 3, 4) + + tag = Tag(Tag.applicationTagClass, Tag.booleanAppTag, 0, xtob('')) + with self.assertRaises(ValueError): + Date(tag) + + tag = Tag(Tag.contextTagClass, 0, 1, xtob('ff')) + with self.assertRaises(ValueError): + Date(tag) + + tag = Tag(Tag.openingTagClass, 0) + with self.assertRaises(ValueError): + Date(tag) + + def test_date_copy(self): + if _debug: TestInteger._debug("test_date_copy") + + value = (1, 2, 3, 4) + obj1 = Date(value) + obj2 = Date(obj1) + assert obj2.value == value + + def test_date_endec(self): + if _debug: TestInteger._debug("test_date_endec") + +# with self.assertRaises(IndexError): +# obj = Date(date_tag('')) + + def old_tests(self): self.test_values = [ #('1/2/3', 1903, 2, 1, 0), #('1/2/3', 1903, 2, 1, 0), @@ -52,28 +151,24 @@ class TestDate(unittest.TestCase): ("*-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: + + 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]) - - \ No newline at end of file + def test_Wrong(self): + with self.assertRaises(ValueError): + for each in self.notEnoughPreciseOrWrong: + new_date = Date(each[0]) From 5eb4f820be3df50d1e40dba11c2726340c8e69b0 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sat, 5 Sep 2015 22:27:41 -0400 Subject: [PATCH 14/18] it seems like I ripped the bytearray stuff out of py25 once already --- py25/bacpypes/primitivedata.py | 68 +++++++++++++++------------------- py27/bacpypes/primitivedata.py | 5 +-- py34/bacpypes/primitivedata.py | 5 +-- 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/py25/bacpypes/primitivedata.py b/py25/bacpypes/primitivedata.py index 23ff05d..f0348c8 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) @@ -1268,14 +1263,14 @@ class Date(Atomic): 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): raise ValueError("date application tag required") # rip apart the data - self.value = tuple(tag.tagData) + self.value = tuple(ord(c) for c in tag.tagData) def __str__(self): """String representation of the date.""" @@ -1291,10 +1286,7 @@ class Date(Atomic): 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" % (year, month, day, day_of_week) - - def __repr__(self): - return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) + return "%s(%s-%s-%s %s)" % (self.__class__.__name__, year, month, day, day_of_week) # @@ -1356,7 +1348,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 24a949f..24f0983 100755 --- a/py27/bacpypes/primitivedata.py +++ b/py27/bacpypes/primitivedata.py @@ -1292,10 +1292,7 @@ class Date(Atomic): 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" % (year, month, day, day_of_week) - - def __repr__(self): - return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) + return "%s(%s-%s-%s %s)" % (self.__class__.__name__, year, month, day, day_of_week) # diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index c637d6a..6fcd7cc 100755 --- a/py34/bacpypes/primitivedata.py +++ b/py34/bacpypes/primitivedata.py @@ -1300,10 +1300,7 @@ class Date(Atomic): 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" % (year, month, day, day_of_week) - - def __repr__(self): - return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self)) + return "%s(%s-%s-%s %s)" % (self.__class__.__name__, year, month, day, day_of_week) # From 725ea476b8cb3c4c768c9fd34cd231c1f9f36696 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sat, 5 Sep 2015 22:36:53 -0400 Subject: [PATCH 15/18] missed a conflict --- tests/test_primitive_data/test_date.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_primitive_data/test_date.py b/tests/test_primitive_data/test_date.py index dbb848f..e2345e7 100644 --- a/tests/test_primitive_data/test_date.py +++ b/tests/test_primitive_data/test_date.py @@ -9,11 +9,7 @@ Test Primitive Data Date import unittest from bacpypes.debugging import bacpypes_debugging, ModuleLogger, xtob -<<<<<<< HEAD from bacpypes.primitivedata import Date, Tag -======= -from bacpypes.primitivedata import Date, Tag, DecodingError ->>>>>>> stage # some debugging _debug = 0 From 2bdfcae2bfcd2bd24de995f6d0b3fe72eeb6000d Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sat, 5 Sep 2015 22:39:09 -0400 Subject: [PATCH 16/18] changing the str() function to more closely match the other primitive types --- tests/test_primitive_data/test_date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_primitive_data/test_date.py b/tests/test_primitive_data/test_date.py index e2345e7..00c851a 100644 --- a/tests/test_primitive_data/test_date.py +++ b/tests/test_primitive_data/test_date.py @@ -86,7 +86,7 @@ class TestDate(unittest.TestCase): obj = Date((1,2,3,4)) assert obj.value == (1,2,3,4) - assert str(obj) == "1901-2-3 thu" + assert str(obj) == "Date(1901-2-3 thu)" def test_date_tag(self): if _debug: TestInteger._debug("test_date_tag") From a6e035d5eea377af7b1bc1c540eb794f406ce870 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sun, 6 Sep 2015 00:12:01 -0400 Subject: [PATCH 17/18] wandering sys.stderr.write commented out --- py34/bacpypes/primitivedata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py34/bacpypes/primitivedata.py b/py34/bacpypes/primitivedata.py index 6fcd7cc..27504ae 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__): From c3b409041a69bd73507d31a2d65701731f21ebeb Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sun, 6 Sep 2015 00:37:27 -0400 Subject: [PATCH 18/18] alternative has been rolled in --- samples/date_alternative.py | 195 ------------------------------------ 1 file changed, 195 deletions(-) delete mode 100644 samples/date_alternative.py diff --git a/samples/date_alternative.py b/samples/date_alternative.py deleted file mode 100644 index caa4dc9..0000000 --- a/samples/date_alternative.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re -import time - -from bacpypes.primitivedata import Tag, Atomic - -_mm = r'(?P0?\d|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): - - 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): - self.decode(arg) - elif isinstance(arg, tuple): - self.value = arg - elif isinstance(arg, str): - # lower case everything - arg = arg.lower() - - # 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()) - - # try to find a good one - match = None - if not matches: - raise ValueError("unmatched") - - # if there is only one, success - if len(matches) == 1: - match = matches[0] - else: - # 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 - - # extract the month and normalize - month = match['month'] - if month in _special_mon: - month = _special_mon[month] - else: - month = int(month) - if month > 14: - print("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 > 34: - print("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) - - # 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() - - elif isinstance(arg, Date): - self.value = arg.value - - else: - raise TypeError("invalid constructor datatype") - - def CalcDayOfWeek(self): - """Calculate the correct day of the week.""" - # rip apart the value - year, month, day, day_of_week = self.value - - # 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, 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)) - - def decode(self, tag): - if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.dateAppTag): - raise ValueError("date application tag required") - - # rip apart the data - self.value = tuple(tag.tagData) - - def __str__(self): - """String representation of the date.""" - # rip it apart - year, month, day, day_of_week = self.value - - if year == 255: - year = "*" - else: - 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" % (year, month, day, day_of_week) - - def __repr__(self): - return "<%s(%s) at 0x%x>" % (self.__class__.__name__, str(self), id(self))