def __init__(self, config_filename, schedule_filename): self.__command = None self.__user = None self.__days_to_schedule = None self.__tz_offset = None self.__latitude = None self.__longitude = None self.__recording_specs = [] # get a sun calculating object self.__sun = Sun() # create a config object around the config file self.__config = ConfigObj(config_filename) self.__config_timestamp = 0 self.updateConfigFromFile() # create a config object around the schedule file self.__schedule = ConfigObj(schedule_filename) self.__timestamp = -1 self.updateFromFile()
class ScheduleParser(object): def __init__(self, config_filename, schedule_filename): self.__command = None self.__user = None self.__days_to_schedule = None self.__tz_offset = None self.__latitude = None self.__longitude = None self.__recording_specs = [] # get a sun calculating object self.__sun = Sun() # create a config object around the config file self.__config = ConfigObj(config_filename) self.__config_timestamp = 0 self.updateConfigFromFile() # create a config object around the schedule file self.__schedule = ConfigObj(schedule_filename) self.__timestamp = -1 self.updateFromFile() @property def command(self): self.updateConfigFromFile() return self.__command @command.setter def command(self, command): self.updateConfigFromFile() self.__command = command def assertCommandIsDefined(self): if not self.__command: sys.stderr.write('%s must be specified in the %s section of "%s"\n' % (CONFIG_COMMAND, CONFIG_SECTION, self.__config.filename)) return False return True @property def user(self): self.updateConfigFromFile() return self.__user @user.setter def user(self, user): self.updateConfigFromFile() self.__user = user @property def days_to_schedule(self): self.updateConfigFromFile() return self.__days_to_schedule @days_to_schedule.setter def days_to_schedule(self, days_to_schedule): self.updateConfigFromFile() self.__days_to_schedule = days_to_schedule @property def tz_offset(self): self.updateConfigFromFile() return self.__tz_offset @tz_offset.setter def setTzOffset(self, tz_offset): self.updateConfigFromFile() self.__tz_offset = tz_offset @property def latitude(self): self.updateConfigFromFile() return self.__latitude @latitude.setter def latitude(self, latitude): self.updateConfigFromFile() self.__latitude = latitude @property def longitude(self): self.updateConfigFromFile() return self.__longitude @longitude.setter def longitude(self, longitude): self.updateConfigFromFile() self.__longitude = longitude @property def filename(self): return self.__schedule.filename def updateConfigFromFile(self): if self.__config_timestamp >= self.getConfigTimestamp(): return # read station information station_section = self.__config[STATION_SECTION] for key in station_section: if key == STATION_TZ_KEY: # get timezone from config self.__tz_offset = int(station_section[key]) elif key == STATION_LAT_KEY: # get latitude from config self.__latitude = float(station_section[key]) elif key == STATION_LONG_KEY: # get longitude from config self.__longitude = float(station_section[key]) # this information is required for now, (eventually ask GPS) if not self.__latitude or not self.__longitude: raise MissingConfigKeyError('%s and %s must be specified in the %s ' 'section of "%s" (at least until we can talk to the GPS)\n' % (CONFIG_LAT_KEY, CONFIG_LONG_KEY, CONFIG_SECTION, self.__config.filename)) # if timezone is not specified in config, then get it from python if not self.__tz_offset: # python tz offsets are negative and in seconds self.__tz_offset = -time.timezone / 3600 # read sound capture info config_section = self.__config[CONFIG_SECTION] for key in config_section: if key == CONFIG_COMMAND: self.__command = config_section[key] elif key == CONFIG_USER: self.__user = config_section[key] elif key == CONFIG_DAYS_KEY: self.__days_to_schedule = int(config_section[key]) def updateFromFile(self): file_timestamp = self.getTimestamp() if self.__timestamp >= file_timestamp: return self.__schedule.reload() # if this is a new file (or has been modified, put header back) if not self.__schedule.initial_comment: self.__schedule.initial_comment = SCHEDULE_HEADER.split('\n') self.__timestamp = file_timestamp self.updateFromConfigObj() def updateFromConfigObj(self): # parse the schedule to get the recording times self.__recording_specs = [] for key in self.__schedule: self.__recording_specs.append(self.parseRecordingSpec(key, self.__schedule[key])) # convert given string into a TimeSpec @staticmethod def parseTimeSpec(spec): assert type(spec) == str or type(spec) == unicode, ( 'time spec should be string, not %s' % type(spec)) match = TIME_RE.match(spec) if match: start_type, start_hour, start_minute = match.groups() return TimeSpec(TimeSpec.Type(start_type), int(start_hour), int(start_minute)) else: raise ValueError('"%s" is not a valid time spec' % spec) # convert given string into a RecordingSpec @classmethod def parseRecordingSpec(cls, title, spec_part1, spec_part2=None): if spec_part2 == None: # used when spec is a single string assert type(spec_part1) == str or type(spec_part1) == unicode, ( 'recording spec should be string, not %s' % (spec_part1)) match = SCHEDULE_RE.match(spec_part1) if match: (start_type, start_hour, start_minute, finish_type, finish_hour, finish_minute) = match.groups() return RecordingSpec(title, TimeSpec(TimeSpec.Type(start_type), int(start_hour), int(start_minute)), TimeSpec(TimeSpec.Type(finish_type), int(finish_hour), int(finish_minute))) else: raise ValueError('"%s" is not a valid recording spec (%s->%s)' % (spec, SCHEDULE_SECTION, title, spec)) else: # when the spec is in two parts (a start and a finish) return RecordingSpec(title, cls.parseTimeSpec(spec_part1), cls.parseTimeSpec(spec_part2)) def getSchedules(self, schedule_day=None, days_to_schedule=None): # update specs if file has been modified self.updateFromFile() if type(schedule_day) == datetime.datetime: schedule_day = schedule_day.replace(hour=0, minute=0, second=0, microsecond=0) elif type(schedule_day) == datetime.date: schedule_day = datetime.datetime(schedule_day.year, schedule_day.month, schedule_day.day) elif not schedule_day: schedule_day = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) else: raise TypeError('schedule_day must be datetime or date, not "%s"' % type(schedule_day)) if not days_to_schedule: days_to_schedule = self.__days_to_schedule assert type(days_to_schedule) == int, ('invalid type for ' 'days_to_schedule "%s"' % type(days_to_schedule)) recording_times = [] for i in range(days_to_schedule): day = schedule_day + datetime.timedelta(days=i) #TODO optimisation: skip sunrise/set calc if all times are absolute rise, set = [datetime.timedelta(hours=(t + self.tz_offset)) for t in self.__sun.sunRiseSet(day.year, day.month, day.day, self.longitude, self.latitude)] for recording_spec in self.__recording_specs: times = [] for time_spec in (recording_spec.start, recording_spec.finish): if time_spec.type.isSunriseRelative(): times.append(day + rise + time_spec.offset) elif time_spec.type.isSunsetRelative(): times.append(day + set + time_spec.offset) else: times.append(day + time_spec.offset) recording_times.append(RecordingTime(recording_spec.title, times[0], times[1])) # sort times by start time recording_times.sort(key=operator.attrgetter('start')) return recording_times def findSchedulesCovering(self, start, finish): '''Find all schedules that cover the given period. It may be that there are more than one, due to overlapping schedules, or one recording may overlap the boundary between two''' schedules = [] # (search 1 day either side) for schedule in self.getSchedules(start - datetime.timedelta(1), (finish - start).days + 2): # if either start or finish of recording file is in the schedule if (start in schedule or finish in schedule): schedules.append(schedule) return schedules def getScheduleSpecs(self): # update if file has been modified self.updateFromFile() # return a copy return deepcopy(self.__recording_specs) def getScheduleSpec(self, title): # update if file has been modified self.updateFromFile() return next((spec for spec in self.__recording_specs if spec.title == title), None) def setScheduleSpec(self, spec): assert isinstance(spec, RecordingSpec) self.__schedule[spec.title] = '%s - %s' % (spec.start, spec.finish) self.updateFromConfigObj() def deleteScheduleSpec(self, title): del(self.__schedule[title]) self.updateFromConfigObj() def clearScheduleSpecs(self): self.__schedule.clear() self.updateFromConfigObj() def getScheduleTitles(self): # update if file has been modified self.updateFromFile() return [deepcopy(spec.title) for spec in self.__recording_specs] def getTimestamp(self): if (self.__schedule.filename and os.path.exists(self.__schedule.filename)): return int(os.path.getmtime(self.__schedule.filename)) return 0 def getConfigTimestamp(self): if self.__config.filename and os.path.exists(self.__config.filename): return int(os.path.getmtime(self.__config.filename)) return 0 def saveToFile(self): try: # update file self.__schedule.write() self.__timestamp = self.getTimestamp(); except: # if this fails we should re-read the file to keep them synced self.__schedule.reload() self.__timestamp = self.getTimestamp(); raise def export(self, export_filename): with open(export_filename, 'w') as save_file: self.__schedule.write(save_file)
#!/usr/bin/env python # for comparison with sunrise & set times from math import floor from bowerbird.sun import Sun utc_offset = 10 year = 2009 latitude = -33 + 13.0/60.0 longitude = 150 + 50.0/60.0 m_days = [31,28,31,30,31,30,31,31,30,31,30,31] months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] def pp(time): hours = floor(time) minutes = floor((time - hours) * 60) return hours*100 + minutes s = Sun() for m in range(1,13): print "\t%s\n\trise\tset" % months[m-1] for d in range(1,m_days[m-1]+1): rise, set = [pp(t + utc_offset) for t in s.sunRiseSet(year, m, d, longitude, latitude)] print "%2d\t%04d\t%04d" % (d, rise, set)