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))