Source code for dk.ttcal.month

# -*- coding: utf-8 -*-
import re
import calendar
import datetime
from .day import Day, Days
from .week import Week
from .calfns import chop, rangecmp, rangetuple


[docs]class Month(object): """A calendar month. """ month_name = ['', 'Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'] year = None month = None @classmethod
[docs] def from_idtag(cls, tag): """Parse idtag into `class`:Month. """ # m20082 y = int(tag[1:5]) m = int(tag[5:]) return cls(year=y, month=m)
@classmethod
[docs] def from_date(cls, d): """Create a Month from the date ``d``. """ return cls(year=d.year, month=d.month)
[docs] def rangetuple(self): return self.first.datetime(), (self.last + 1).datetime()
@classmethod
[docs] def parse(cls, txt): """Parse a textual representation into a Month object. Format YYYY-MM? """ if not txt: return None mnth_matcher = re.compile(r""" (?P<year>\d{4})-?(?P<month>\d{1,2}) """, re.VERBOSE) m = mnth_matcher.match(txt) if not m: raise ValueError( (u"Ugyldig format, må være åååå-mm, ikke %r." % txt).encode('u8')) mnth_groups = m.groupdict() return cls(int(mnth_groups["year"]), int(mnth_groups["month"]))
def __init__(self, year=None, month=None, date=None): super(Month, self).__init__() if date is not None: self.year = date.year self.month = date.month elif year is month is date is None: td = datetime.date.today() self.year = td.year self.month = td.month else: assert None not in (year, month) self.year = year self.month = month if not (1 <= self.month <= 12): raise ValueError("Month must be in 1..12.") self.calendar = calendar.Calendar() self.name = self.month_name[self.month] self.short_name = self.name[:3] # self.short_name = calendar.month_abbr[self.month] self.weeks = [Week(days, self.month) for days in self._weeks()] # self.day = 1 def __call__(self, daynum=None): """Return the given Day for this year. Usage:: return ttcal.Year().december(23) """ if daynum is None: # pragma:nocover return self # for when django tries to do value = value() *sigh* return Day(self.year, self.month, daynum) def __reduce__(self): """Deepcopy helper. """ return Month, (self.year, self.month) def __unicode__(self): return u"%04d-%02d" % (self.year, self.month) # @property # def Year(self): # """Return a Year object for the year-part of this month. # """ # return Year(self.year) @property def Month(self): return self def __hash__(self): return self.year * 100 + self.month # def __eq__(self, other): # noinspection PyBroadException # try: # return self.year == other.year and self.month == other.month # except: # return False def __len__(self): _, n = calendar.monthrange(self.year, self.month) return n
[docs] def datetuple(self): """First date in month. """ return self.year, self.month, 1
def __lt__(self, other): othr = rangetuple(other) if othr is other: return False return rangecmp(self.rangetuple(), othr) < 0 def __le__(self, other): othr = rangetuple(other) if othr is other: return False return rangecmp(self.rangetuple(), othr) <= 0 def __eq__(self, other): othr = rangetuple(other) if othr is other: return False return rangecmp(self.rangetuple(), othr) == 0 def __gt__(self, other): othr = rangetuple(other) if othr is other: return False return rangecmp(self.rangetuple(), othr) > 0 def __ge__(self, other): othr = rangetuple(other) if othr is other: return False return rangecmp(self.rangetuple(), othr) >= 0
[docs] def numdays(self): # for use in template """The number of days in the month. """ return len(self)
def __add__(self, n): """Add n months to self. """ me = self.year * 12 + (self.month - 1) me += n q, r = divmod(me, 12) return Month(q, r + 1) def __radd__(self, n): return self + n def __sub__(self, n): if isinstance(n, Month): first, last = min(self, n), max(self, n) ydiff = last.year - first.year mdiff = last.month - first.month res = 12 * ydiff + mdiff if self > n: return res return -res return self + (-n) # rsub doesn't make sense def __repr__(self): return 'Month(%s, %s)' % (self.year, self.month) def __str__(self): return '%04d-%02d' % (self.year, self.month) # NOTE: Django's query engine calls both __call__ and __iter__ on values # that are passed in, and uses the return values instead of the value # itself (i.e. with the implementation below, the queryset would get # a list of Week objects instead of a Month object). # NB: W:\srv\venv\dev\Lib\site-packages\django\db\models\sql\where.py # NB: temp comment # NB: if is_iterator(value): # NB: # Consume any generators immediately, so that we can determine # NB: # emptiness and transform any non-empty values correctly. # NB: value = list(value) # def __iter__(self): # return iter(self.weeks)
[docs] def dayiter(self): for wk in iter(self.weeks): for day in wk: yield day
[docs] def days(self): """Return a list of days (`class`:ttcal.Day) in this month. """ res = [] for wk in iter(self.weeks): for day in wk: if day.month == self.month: res.append(day) # yield day return res
[docs] def idtag(self): """Return a text representation that is parsable by the from_idtag function (above), and is useable as part of an url. """ return 'm%d%d' % (self.year, self.month)
@property def daycount(self): """The number of days in this month (as an int). """ n = calendar.mdays[self.month] if self.month == 2 and calendar.isleap(self.year): n += 1 return n
[docs] def prev(self): """Previous month. """ return self - 1
[docs] def next(self): """Next month. """ return self + 1
@property def first(self): """First day in month. """ return Day(self.year, self.month, 1) @property def last(self): """Last day in month. """ return Day(self.year, self.month, self.daycount) def _weeks(self): c = self.calendar return chop(c.itermonthdates(self.year, self.month), 7) def __contains__(self, date): return self.year == date.year and self.month == date.month def __getitem__(self, day): for wk in self.weeks: for d in wk: if d.compare(day) == 'day': return d raise KeyError
[docs] def mark(self, d, value='mark', method='replace'): try: day = self[d] if method == 'replace': day.mark = value elif method == 'append': if hasattr(day, 'mark'): day.mark += value else: day.mark = value except KeyError: # pragma:nocover pass
[docs] def marked_days(self): for wk in self.weeks: for d in wk: if hasattr(d, 'mark'): yield d
def _format(self, fmtchars): # http://blog.tkbe.org/archive/date-filter-cheat-sheet/ for ch in fmtchars: if ch == 'y': yield str(self.year)[-2:] elif ch == 'Y': yield str(self.year) elif ch == 'n': yield str(self.month) elif ch == 'm': yield '%02d' % self.month elif ch == 'b': yield self.name[:3].lower() elif ch == 'M': yield self.name[:3] elif ch == 'N': # should be AP style, but doesn't make sense outside US. yield self.name[:3] elif ch == 'F': yield self.name else: yield ch
[docs] def format(self, fmt=None): """Format according to format string. Default format is monthname, four-digit-year. """ if fmt is None: fmt = "F, Y" tmp = list(self._format(list(fmt))) return ''.join(tmp)
[docs] def range(self): """Return an iterator for the range of `self`. """ # if hasattr(self, 'dayiter'): # return self.dayiter() return Days(self.first, self.last)
[docs] def between_tuple(self): # pylint:disable=E0213 """Return a tuple of datetimes that is convenient for sql `between` queries. """ return (self.first.datetime(), (self.last + 1).datetime() - datetime.timedelta(seconds=1))
@property def middle(self): """Return the day that splits the date range in half. """ middle = (self.first.toordinal() + self.last.toordinal()) // 2 return Day.fromordinal(middle)
[docs] def timetuple(self): """Create timetuple from datetuple. (to interact with datetime objects). """ d = datetime.date(*self.datetuple()) t = datetime.time() return datetime.datetime.combine(d, t)
# noinspection PyPep8Naming def _Month(self): """Return a Month object representing the month `self` belongs to. """ return Month(self.year, self.month) Day.Month = property(_Month)