def time(match): group = match.groupdict() data = {k: int(v or 0) for k, v in group.items() if k != 'sign'} sign = group.get('sign') if sign: hours = data['offset_hour'] minutes = data['offset_minute'] if hours == minutes == 0: raise ValueError('Invalid timezone offset {0}00:00.'.format(sign)) if sign == '+': tz = timezone(hours=hours, minutes=minutes) else: tz = -timezone(hours=hours, minutes=minutes) explicit_tz = True else: explicit_tz = False tz = utc hour, minute = data['hour'], data['minute'] second, millisecond = data['second'], data['millisecond'] day = 0 if hour == 24 and minute == second == millisecond == 0: hour = 0 day = 1 return (time_(hour, minute, second, 1000 * millisecond, tz), day, explicit_tz)
def to_time(dt, tzinfo=None, format=None): """ Convert a datetime to time with tzinfo """ d = to_datetime(dt, tzinfo, format) if not d: return d return time_(d.hour, d.minute, d.second, d.microsecond, tzinfo=d.tzinfo)
def edit_date(date): s = date.split('T') yyyymmdd = s[0].split('-') ddmmyyyy = yyyymmdd[2] + "." + yyyymmdd[1] + "." + yyyymmdd[0] temp = s[1] my_date = ddmmyyyy.split('.') my_time = temp[:8].split(':') new_date = dt(int(my_date[2]), int(my_date[1]), int(my_date[0])) new_time = time_(int(my_time[0]), int(my_time[1]), int(my_time[2])) return new_date, new_time
def datetime_builder_partial(string, delta=None, other=None, strict=False): def resolve_tz(value, other, explicit=True): """If end does not have a tzinfo attribute, but start does, modify end to have a tzinfo attribute. """ if (not explicit or value.tzinfo == None) and (other and other.tzinfo != None): return value.replace(tzinfo=other.tzinfo) return value # select builder if strict: selected_builder = date_builder_strict else: selected_builder = date_builder_partial # date and time components components = string.split('T') length = len(components) if length == 2: try: [date, time] = components (t, extra_day, explicit_tz) = time_builder(time) print('EXPLICIT: %s' % explicit_tz) t = resolve_tz(t, other, explicit_tz) d = selected_builder(date, other) + timedelta(days=extra_day) return datetime_.combine(d, t) except: raise ValueError('Parsing failed.') elif length == 1: # only date component try: if delta: d = selected_builder(string, other) + delta else: d = selected_builder(string, other) return resolve_tz(datetime_.combine(d, time_(0, tzinfo=utc)), other) # only time component except: try: (t, extra_day, explicit_tz) = time_builder(string) t = resolve_tz(t, other, explicit_tz) return datetime_.combine(other.date(), t) except: raise ValueError('Parsing failed.') else: raise ValueError('Parsing failed.')
def update_schedule(name, days, hours): """ Create or update and return a schedule, assuming days as a list of numbers enumerating the days of the week (0-6) and hours a list of tuples of hours and minutes [(h m), ...]. """ # Get schedule = DB.query(Schedule).filter(Schedule.name == name).first() # Or create if schedule is None: schedule = Schedule() schedule.name = name DB.add(schedule) DB.flush() # Days of the week schedule.monday = True if 0 in days else False schedule.tuesday = True if 1 in days else False schedule.wednesday = True if 2 in days else False schedule.thursday = True if 3 in days else False schedule.friday = True if 4 in days else False schedule.saturday = True if 5 in days else False schedule.sunday = True if 6 in days else False # New hours DB.query(Time).filter(Time.schedule_id == schedule.id).delete() for h_m in hours: hour = Time() hour.time = time_(int(h_m[0]), int(h_m[1])) hour.schedule_id = schedule.id DB.add(hour) # Save DB.commit() return schedule
def calendar_add(caldav_conn, args): cal = Calendar() cal.add( 'prodid', '-//{author_short}//{product}//{language}'.format( author_short=__author_short__, product=__product__, language=args.language)) cal.add('version', '2.0') event = Event() ## read timestamps from arguments event_spec = args.event_time.split('+') if len(event_spec) > 3: raise ValueError( 'Invalid event time "%s" - can max contain 2 plus-signs' % args.event_time) elif len(event_spec) == 3: event_time = '%s+%s' % tuple(event_spec[0:2]) event_duration = event_spec[2] elif len(event_spec) == 2 and not event_spec[1][-1:] in time_units: event_time = '%s+%s' % tuple(event_spec[0:2]) event_duration = '1h' elif len(event_spec) == 2: event_time = '%s' % event_spec[0] event_duration = event_spec[1] else: event_time = event_spec[0] event_duration = '1h' ## TODO: error handling event_duration_secs = float( event_duration[:-1]) * time_units[event_duration[-1:]] dtstart = dateutil.parser.parse(event_spec[0], ignoretz=True) if (args.whole_day or (event_duration_secs % (60 * 60 * 24) == 0 and dtstart.time() == time_(0, 0))): ## allowing 1 second off due to leap seconds if (event_duration_secs + 1) % (60 * 60 * 24) > 2: raise ValueError( 'Duration of whole-day event must be multiple of 1d') duration = event_duration_secs // 60 // 60 // 24 dtend = dtstart + timedelta(days=duration) event.add('dtstart', _date(dtstart.date())) event.add('dtend', _date(dtend.date())) else: dtstart = _tz(args.timezone).localize(dtstart) event.add('dtstart', dtstart) ## TODO: handle duration and end-time as options. default 3600s by now. event.add('dtend', dtstart + timedelta(0, event_duration_secs)) if (args.private): event.add('class', 'PRIVATE') event.add('dtstamp', _now()) uid = uuid.uuid1() event.add('uid', str(uid)) for attr in vcal_txt_one + vcal_txt_many: if attr == 'summary': continue val = getattr(args, 'set_' + attr) if val: event.add(attr, val) event.add('summary', ' '.join(args.summary)) event.add( 'description', ' '.join(args.description.replace('\\n', '\n').replace('\\t', '\t'))) cal.add_component(event) ## workaround for getting RFC-compliant ical data, ## ref https://github.com/collective/icalendar/issues/272#issuecomment-640204031 ical_data = vobject.readOne(cal.to_ical().decode('utf-8')).serialize() _calendar_addics(caldav_conn, ical_data, uid, args) print("Added event with uid=%s" % uid)
time_: lambda value: 'm_%s' % _time_encode(value), timedelta: lambda value: 'e_%i.%i.%i' % (value.days, value.seconds, value.microseconds), tuple: lambda value: 'l_' + '_'.join(map(lambda a: _escape(encode(a)), value)), list: lambda value: 'l_' + '_'.join(map(lambda a: _escape(encode(a)), value)), dict: lambda value: 'h_' + '_'.join(map(lambda a: '%s:%s' % (_escape(encode(a[0])), _escape(encode(a[1]))), value.items())), } decodings = { 'N': lambda value: None, 'i': int, 'f': float, 'b': lambda value: bool(int(value)), 'd': Decimal, 'u': lambda value: _unescape(value).decode('base-64').decode('utf-8'), # NOTE: Read this stackoverflow for more info on why this needs to be done this way. # This is just needs to use utcfromtimestamp since we are using utcfromtimestamp above # https://stackoverflow.com/questions/8777753/converting-datetime-date-to-utc-timestamp-in-python 't': lambda value: datetime.utcfromtimestamp(float(value)), 'a': lambda value: date.fromtimestamp(float(value)), 'm': lambda value: time_(*map(int, value.split('.'))), 'e': lambda value: timedelta(*map(int, value.split('.'))), 'l': lambda value: map(decode, map(_unescape, value.split('_'))), 'h': lambda value: dict(map(lambda a: map(decode, map(_unescape, a.split(':'))), value.split('_'))), } def _escape(value): return value.replace('|', '||').replace('\n', '|n').replace('_', '|u') def _unescape(value): return value.replace('||', '|').replace('|n', '\n').replace('|u', '_')
def test_time(): with pytest.raises(ValueError): parsers.time('1234a') with pytest.raises(ValueError): parsers.time('1234a') with pytest.raises(ValueError): parsers.time('12:30:40.05+0:15') with pytest.raises(ValueError): parsers.time('1230401.05+10:15') with pytest.raises(ValueError): parsers.time('24:00:00.0001') assert parsers.time('24:00:00') == parsers.time('00:00:00') assert parsers.time('12') == time_(hour=12, tzinfo=utc) assert parsers.time('12+05:10') == time_(hour=12, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('12-05:10') == time_(hour=12, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('13:15') == time_(hour=13, minute=15, tzinfo=utc) assert parsers.time('13:15+05:10') == time_(hour=13, minute=15, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('13:15-05:10') == time_(hour=13, minute=15, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('14:20:50') == time_(hour=14, minute=20, second=50, tzinfo=utc) assert parsers.time('14:20:50+05:10') == time_(hour=14, minute=20, second=50, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('14:20:50-05:10') == time_(hour=14, minute=20, second=50, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('14:20:50+05') == time_(hour=14, minute=20, second=50, tzinfo=timezone(hours=5)) assert parsers.time('14:20:50-05') == time_(hour=14, minute=20, second=50, tzinfo=-timezone(hours=5)) assert parsers.time('14:20:50+0510') == time_(hour=14, minute=20, second=50, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('14:20:50-0510') == time_(hour=14, minute=20, second=50, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('12:30:40.05') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=utc) assert parsers.time('12:30:40.05Z') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=utc) assert parsers.time('12:30:40.05+10:15') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=timezone(hours=10, minutes=15)) assert parsers.time('12:30:40.05-08:45') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=-timezone(hours=8, minutes=45)) assert parsers.time('1315') == time_(hour=13, minute=15, tzinfo=utc) assert parsers.time('1315+05:10') == time_(hour=13, minute=15, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('1315-05:10') == time_(hour=13, minute=15, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('142050'), time_(hour=14, minute=20, second=50, tzinfo=utc) assert parsers.time('142050+05:10') == time_(hour=14, minute=20, second=50, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('142050-05:10') == time_(hour=14, minute=20, second=50, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('142050+05') == time_(hour=14, minute=20, second=50, tzinfo=timezone(hours=5)) assert parsers.time('142050-05') == time_(hour=14, minute=20, second=50, tzinfo=-timezone(hours=5)) assert parsers.time('142050+0510') == time_(hour=14, minute=20, second=50, tzinfo=timezone(hours=5, minutes=10)) assert parsers.time('142050-0510') == time_(hour=14, minute=20, second=50, tzinfo=-timezone(hours=5, minutes=10)) assert parsers.time('123040.05') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=utc) assert parsers.time('123040.05Z') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=utc) assert parsers.time('123040.05+10:15') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=timezone(hours=10, minutes=15)) assert parsers.time('123040.05-08:45') == time_(hour=12, minute=30, second=40, microsecond=5000, tzinfo=-timezone(hours=8, minutes=45))
def process_queue(): """ First update all schedules and data from the watch list files, then tweet queued post based on the schedules. One at a time. """ # Update data from the watch list print(f"Updating data from files in the watch list...") watch = DB.query(Watch).all() for w in watch: update_from_file(w.path) # What day are we? today = datetime.today() strday = str(get_schedule_column(today.weekday())).replace("Schedule.", "") print( f"\nQueue processing started " f"({strday.title()} {today.date()} {today.time().replace(microsecond=0)})" ) # Get all the schedules for today todaysched = DB.query(Schedule).filter(get_schedule_column( today.weekday())).all() if not todaysched: print("No schedules today") # Look for an hour that fits, less than current hour, bigger that the # application start time hour if we are on the same day for tsc in todaysched: if START.date() == today.date(): startime = START.time().replace(second=0, microsecond=0) else: startime = time_() hour = DB.query(Time).filter( and_(Time.schedule_id == tsc.id, Time.used < today.date(), Time.time <= today.time(), Time.time >= startime)).first() print(f"\nSchedule '{tsc.name}'") if hour: print(f"At {hour.time}") # Get the first unpublished post of the schedule in the hour that # isn't an error post = DB.query(Post).filter( and_(Post.schedule_id == hour.schedule_id, Post.published == 0, Post.error == 0)).first() if post: # Announce print( f"Trying to tweet:\n{post.text} {post.image_url if post.image_url else ''}" ) # Twitter auth and tokens validation tokens = json.load(open(TOKENS_FILE, 'r')) auth = tweepy.OAuthHandler(tokens[tsc.name]['consumer_key'], tokens[tsc.name]['consumer_secret']) auth.set_access_token(tokens[tsc.name]['oauth_token'], tokens[tsc.name]['oauth_secret']) if not tokens[tsc.name]['consumer_key']: print( f"The schedule '{tsc.name}' doesn't have the Twitter tokens, add them to the tokens file!" ) continue # Tweet else: api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True) try: if post.image_url: api.update_with_media(post.image_url, post.text) else: api.update_status(post.text) print(f"Done!") # Mark the post as published, and register the hour used time post.published = True DB.add(post) hour.used = datetime.now() DB.add(hour) DB.commit() except tweepy.error.TweepError as err: print(f"Skipped, error: {err}") post.error = True DB.add(post) DB.commit() else: print("The queue is empty!") else: print(f"No pending hours!")