#!/usr/bin/python import datetime import re import string _valid_keywords = ['last', 'this', 'next'] class Unit(object): def __str__(self): return self.ref.isoformat() def __init__(self, ref): self.ref = ref if ref else datetime.datetime.now() def seekb(self): return self._seek(-1) def seekf(self): return self._seek(1) def nseekb(self): return self._nseek(-1) def nseekf(self): return self._nseek(1) @staticmethod def make(name, ref=None): _valid = { 'day': Day, 'hour': Hour, 'minute': Minute, 'month': Month, 'second': Second, 'week': Week, 'year': Year, } if name not in _valid.keys(): raise ValueError("invalid unit name: %s" % name) return _valid[name](ref) def apply_offset(self, offset): new = self while offset: if offset > 0: new = new.seekf() offset = offset - 1 elif offset < 0: new = new.seekb() offset = offset + 1 return new class Second(Unit): def start(self): return Second(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, self.ref.minute, self.ref.second, )) def end(self): return Second(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, self.ref.minute, self.ref.second, )) def _seek(self, step): # # Seek to next/previous second # if step > 0: otherref = self.ref + datetime.timedelta(seconds=abs(step)) elif step < 0: otherref = self.ref - datetime.timedelta(seconds=abs(step)) else: otherref = self.ref return Second(ref=datetime.datetime( otherref.year, otherref.month, otherref.day, otherref.hour, otherref.minute, otherref.second, )) class Minute(Unit): def start(self): return Minute(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, self.ref.minute, datetime.datetime.min.second, )) def end(self): return Minute(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, self.ref.minute, datetime.datetime.max.second, )) def _seek(self, step): # # Seek to next/previous minute # if step > 0: otherref = self.ref + datetime.timedelta(seconds=abs(step) * 60) elif step < 0: otherref = self.ref - datetime.timedelta(seconds=abs(step) * 60) else: otherref = self.ref return Minute(ref=datetime.datetime( otherref.year, otherref.month, otherref.day, otherref.hour, otherref.minute, otherref.second, )) class Hour(Unit): def start(self): return Hour(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, datetime.datetime.min.minute, datetime.datetime.min.second, )) def end(self): return Hour(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, datetime.datetime.max.minute, datetime.datetime.max.second, )) def _seek(self, step): # # Seek to next/previous minute # if step > 0: otherref = self.ref + datetime.timedelta(seconds=abs(step) * 3600) elif step < 0: otherref = self.ref - datetime.timedelta(seconds=abs(step) * 3600) else: otherref = self.ref return Hour(ref=datetime.datetime( otherref.year, otherref.month, otherref.day, otherref.hour, otherref.minute, otherref.second, )) class Day(Unit): def start(self): return Day(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, datetime.datetime.min.hour, datetime.datetime.min.minute, datetime.datetime.min.second, )) def end(self): return Day(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, datetime.datetime.max.hour, datetime.datetime.max.minute, datetime.datetime.max.second, )) def _seek(self, step): # # Seek to next/previous day # bigdelta = datetime.timedelta(days=step) return Day(ref=self.ref + bigdelta) def _nseek(self, step): # # Almost seek to next/previous day # return Day(ref=datetime.datetime( self.ref.year, self.ref.month, self.ref.day, self.ref.hour, self.ref.minute, self.ref.second, )) class Week(Unit): def start(self): early = self.nseekb() return Week(ref=datetime.datetime( early.ref.year, early.ref.month, early.ref.day, datetime.datetime.min.hour, datetime.datetime.min.minute, datetime.datetime.min.second, )) def end(self): late = self.nseekf() return Week(ref=datetime.datetime( late.ref.year, late.ref.month, late.ref.day, datetime.datetime.max.hour, datetime.datetime.max.minute, datetime.datetime.max.second, )) def _seek(self, step): # # Seek to next/previous week # delta = datetime.timedelta(days=step) newref = self.ref while newref.isocalendar()[1] == self.ref.isocalendar()[1]: newref = newref + delta return Week(ref=newref) def _nseek(self, step): # # Almost seek to next/previous week # delta = datetime.timedelta(days=step) newref = self.ref while newref.isocalendar()[1] == self.ref.isocalendar()[1]: newref = newref + delta return Month(ref=newref - delta) class Month(Unit): def start(self): early = self.nseekb() return Month(ref=datetime.datetime( early.ref.year, early.ref.month, early.ref.day, datetime.datetime.min.hour, datetime.datetime.min.minute, datetime.datetime.min.second, )) def end(self): late = self.nseekf() return Month(ref=datetime.datetime( late.ref.year, late.ref.month, late.ref.day, datetime.datetime.max.hour, datetime.datetime.max.minute, datetime.datetime.max.second, )) def _seek(self, step): # # Seek to next/previous month # delta = datetime.timedelta(days=step) newref = self.ref while newref.month == self.ref.month: newref = newref + delta return Month(ref=newref) def _nseek(self, step): # # Almost seek to next/previous month # delta = datetime.timedelta(days=step) newref = self.ref while newref.month == self.ref.month: newref = newref + delta return Month(ref=newref - delta) class Year(Unit): def start(self): return Year(ref=datetime.datetime( self.ref.year, datetime.datetime.min.month, datetime.datetime.min.day, datetime.datetime.min.hour, datetime.datetime.min.minute, datetime.datetime.min.second, )) def end(self): return Year(ref=datetime.datetime( self.ref.year, datetime.datetime.max.month, datetime.datetime.max.day, datetime.datetime.max.hour, datetime.datetime.max.minute, datetime.datetime.max.second, )) def apply_offset(self, offset): return Year(ref=datetime.datetime( self.ref.year + offset, self.ref.month, self.ref.day, self.ref.hour, self.ref.minute, self.ref.second, )) def parse_offset(text): m = re.match('^(last|this|next)([+-]+|[+-][0-9]+)?$', text) if not m: raise ValueError('bad offset phrase: %s' % text) direction, offphrase = m.groups() if direction == 'this': amount = 0 elif direction == 'next': amount = 1 elif direction == 'last': amount = -1 if not offphrase: pass elif offphrase[-1] in string.digits: sign, number = re.match('([+-])([0-9]+)', offphrase).groups() if sign == '+': amount = amount + int(number) else: amount = amount - int(number) else: for char in offphrase: if char == '+': amount = amount + 1 else: amount = amount - 1 return amount class Range(object): def start(self): return self.unit.start() def end(self): return self.unit.end() def __init__(self, offset, unitname): self.unit = Unit.make(unitname).apply_offset(parse_offset(offset))