File: recurrence.py

package info (click to toggle)
taskcoach 1.4.1-4
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 32,496 kB
  • ctags: 17,810
  • sloc: python: 72,170; makefile: 254; ansic: 120; xml: 29; sh: 16
file content (152 lines) | stat: -rw-r--r-- 6,306 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
'''
Task Coach - Your friendly task manager
Copyright (C) 2004-2014 Task Coach developers <developers@taskcoach.org>

Task Coach is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Task Coach is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

import calendar
import timedelta
import dateandtime as date


class Recurrence(object):
    ''' Class representing a recurring date. '''
    
    units = ('daily', 'weekly', 'monthly', 'yearly', '')
    
    def __init__(self, unit='', amount=1, sameWeekday=False, maximum=0, count=0,
                 stop_datetime=None, recurBasedOnCompletion=False):  
        assert unit in self.units
        assert amount >= 1
        self.unit = unit
        self.amount = amount
        self.stop_datetime = stop_datetime or date.DateTime()
        self.sameWeekday = sameWeekday
        # Maximum number of recurrences we give out, 0 == infinite:
        self.max = maximum  
        self.count = count  # Actual number of recurrences given out so far
        self.recurBasedOnCompletion = recurBasedOnCompletion
                
    def __call__(self, *dateTimes, **kwargs):
        result = [self._nextDateTime(dateTime) for dateTime in dateTimes]
        if kwargs.get('next', True):
            # By default we expect our clients to call us once, but we allow
            # the client to tell us to expect more calls
            self.count += 1
            if self.__finished_recurring():
                self.unit = ''  # We're done with recurring
                return
        if len(result) > 1:
            return tuple(result)
        elif len(result) == 1:
            return result[0]
        else:
            return

    def __finished_recurring(self):
        ''' Return whether this recurrence is finished, either because the
            maximum number of recurrences has happened or because the end date
            for the reccurences has passed. '''
        return self.max != 0 and self.count >= self.max or \
            date.Now() > self.stop_datetime
        
    def _nextDateTime(self, dateTime, amount=0):
        if date.DateTime() == dateTime or not self.unit:
            return dateTime 
        amount = amount or self.amount
        if amount > 1:
            dateTime = self._nextDateTime(dateTime, amount - 1)
        if self.unit == 'yearly':
            return self._addYear(dateTime)
        elif self.unit == 'monthly':
            return self._addMonth(dateTime)
        else:
            return self._addDays(dateTime)

    def _addDays(self, dateTime):
        nrOfDays = dict(daily=1, weekly=7)[self.unit]
        return dateTime + timedelta.TimeDelta(days=nrOfDays)

    def _addMonth(self, dateTime):
        year, month, day = dateTime.year, dateTime.month, dateTime.day
        details = dateTime.hour, dateTime.minute, dateTime.second, dateTime.microsecond
        if month == 12:  # If December, move to January next year
            year += 1
            month = 1
        else:
            month += 1
        if self.sameWeekday:
            weekday = dateTime.weekday()
            weekNr = min(3, (day - 1) / 7)  # In what week of the month falls aDate, allowable range 0-3 
            day = weekNr * 7 + 1  # The earliest possible day that is on the same weekday as aDate
            result = date.DateTime(year, month, day, *details)
            while result.weekday() != weekday:
                day += 1
                result = date.DateTime(year, month, day, *details)
            return result
        else:
            while True:  # Find a valid date in the next month
                try:
                    return date.DateTime(year, month, day, *details)
                except ValueError:
                    day -= 1

    def _addYear(self, dateTime):
        if (calendar.isleap(dateTime.year) and dateTime.month <= 2 and dateTime.day <= 28) or \
           (calendar.isleap(dateTime.year + 1) and dateTime.month >= 3): 
            days = 366
        else:
            days = 365
        newDateTime = dateTime + timedelta.TimeDelta(days=days)
        if self.sameWeekday:
            # Find the nearest date in newDate's year that is on the right 
            # weekday:
            weekday, year = dateTime.weekday(), newDateTime.year
            newEarlierDateTime = newLaterDateTime = newDateTime
            while newEarlierDateTime.weekday() != weekday:
                newEarlierDateTime = newEarlierDateTime - timedelta.ONE_DAY
            while newLaterDateTime.weekday() != weekday:
                newLaterDateTime = newLaterDateTime + timedelta.ONE_DAY
            if newEarlierDateTime.year != year:
                newDateTime = newLaterDateTime
            else:
                newDateTime = newEarlierDateTime
        return newDateTime

    def copy(self):
        return self.__class__(self.unit, self.amount, self.sameWeekday, 
                              self.max, stop_datetime=self.stop_datetime,
                              recurBasedOnCompletion=self.recurBasedOnCompletion)
    
    def __eq__(self, other):
        try:
            return self.unit == other.unit and self.amount == other.amount and \
                   self.sameWeekday == other.sameWeekday and \
                   self.max == other.max and \
                   self.stop_datetime == other.stop_datetime and \
                   self.recurBasedOnCompletion == other.recurBasedOnCompletion
        except AttributeError:
            return False
 
    def __lt__(self, other):
        try:
            return self.units.index(self.unit) < self.units.index(other.unit) or \
                (self.units.index(self.unit) == self.units.index(other.unit) and \
                self.amount < other.amount)
        except AttributeError:
            return True
 
    def __nonzero__(self):
        return bool(self.unit)