Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/isodate/duration.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:26e78fe6e8c4 |
---|---|
1 ############################################################################## | |
2 # Copyright 2009, Gerhard Weis | |
3 # All rights reserved. | |
4 # | |
5 # Redistribution and use in source and binary forms, with or without | |
6 # modification, are permitted provided that the following conditions are met: | |
7 # | |
8 # * Redistributions of source code must retain the above copyright notice, | |
9 # this list of conditions and the following disclaimer. | |
10 # * Redistributions in binary form must reproduce the above copyright notice, | |
11 # this list of conditions and the following disclaimer in the documentation | |
12 # and/or other materials provided with the distribution. | |
13 # * Neither the name of the authors nor the names of its contributors | |
14 # may be used to endorse or promote products derived from this software | |
15 # without specific prior written permission. | |
16 # | |
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
18 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
21 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 # CONTRACT, STRICT LIABILITY, OR TORT | |
26 ############################################################################## | |
27 ''' | |
28 This module defines a Duration class. | |
29 | |
30 The class Duration allows to define durations in years and months and can be | |
31 used as limited replacement for timedelta objects. | |
32 ''' | |
33 from datetime import timedelta | |
34 from decimal import Decimal, ROUND_FLOOR | |
35 | |
36 | |
37 def fquotmod(val, low, high): | |
38 ''' | |
39 A divmod function with boundaries. | |
40 | |
41 ''' | |
42 # assumes that all the maths is done with Decimals. | |
43 # divmod for Decimal uses truncate instead of floor as builtin | |
44 # divmod, so we have to do it manually here. | |
45 a, b = val - low, high - low | |
46 div = (a / b).to_integral(ROUND_FLOOR) | |
47 mod = a - div * b | |
48 # if we were not usig Decimal, it would look like this. | |
49 # div, mod = divmod(val - low, high - low) | |
50 mod += low | |
51 return int(div), mod | |
52 | |
53 | |
54 def max_days_in_month(year, month): | |
55 ''' | |
56 Determines the number of days of a specific month in a specific year. | |
57 ''' | |
58 if month in (1, 3, 5, 7, 8, 10, 12): | |
59 return 31 | |
60 if month in (4, 6, 9, 11): | |
61 return 30 | |
62 if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0): | |
63 return 29 | |
64 return 28 | |
65 | |
66 | |
67 class Duration(object): | |
68 ''' | |
69 A class which represents a duration. | |
70 | |
71 The difference to datetime.timedelta is, that this class handles also | |
72 differences given in years and months. | |
73 A Duration treats differences given in year, months separately from all | |
74 other components. | |
75 | |
76 A Duration can be used almost like any timedelta object, however there | |
77 are some restrictions: | |
78 * It is not really possible to compare Durations, because it is unclear, | |
79 whether a duration of 1 year is bigger than 365 days or not. | |
80 * Equality is only tested between the two (year, month vs. timedelta) | |
81 basic components. | |
82 | |
83 A Duration can also be converted into a datetime object, but this requires | |
84 a start date or an end date. | |
85 | |
86 The algorithm to add a duration to a date is defined at | |
87 http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes | |
88 ''' | |
89 | |
90 def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0, | |
91 minutes=0, hours=0, weeks=0, months=0, years=0): | |
92 ''' | |
93 Initialise this Duration instance with the given parameters. | |
94 ''' | |
95 if not isinstance(months, Decimal): | |
96 months = Decimal(str(months)) | |
97 if not isinstance(years, Decimal): | |
98 years = Decimal(str(years)) | |
99 self.months = months | |
100 self.years = years | |
101 self.tdelta = timedelta(days, seconds, microseconds, milliseconds, | |
102 minutes, hours, weeks) | |
103 | |
104 def __getstate__(self): | |
105 return self.__dict__ | |
106 | |
107 def __setstate__(self, state): | |
108 self.__dict__.update(state) | |
109 | |
110 def __getattr__(self, name): | |
111 ''' | |
112 Provide direct access to attributes of included timedelta instance. | |
113 ''' | |
114 return getattr(self.tdelta, name) | |
115 | |
116 def __str__(self): | |
117 ''' | |
118 Return a string representation of this duration similar to timedelta. | |
119 ''' | |
120 params = [] | |
121 if self.years: | |
122 params.append('%d years' % self.years) | |
123 if self.months: | |
124 fmt = "%d months" | |
125 if self.months <= 1: | |
126 fmt = "%d month" | |
127 params.append(fmt % self.months) | |
128 params.append(str(self.tdelta)) | |
129 return ', '.join(params) | |
130 | |
131 def __repr__(self): | |
132 ''' | |
133 Return a string suitable for repr(x) calls. | |
134 ''' | |
135 return "%s.%s(%d, %d, %d, years=%d, months=%d)" % ( | |
136 self.__class__.__module__, self.__class__.__name__, | |
137 self.tdelta.days, self.tdelta.seconds, | |
138 self.tdelta.microseconds, self.years, self.months) | |
139 | |
140 def __hash__(self): | |
141 ''' | |
142 Return a hash of this instance so that it can be used in, for | |
143 example, dicts and sets. | |
144 ''' | |
145 return hash((self.tdelta, self.months, self.years)) | |
146 | |
147 def __neg__(self): | |
148 """ | |
149 A simple unary minus. | |
150 | |
151 Returns a new Duration instance with all it's negated. | |
152 """ | |
153 negduration = Duration(years=-self.years, months=-self.months) | |
154 negduration.tdelta = -self.tdelta | |
155 return negduration | |
156 | |
157 def __add__(self, other): | |
158 ''' | |
159 Durations can be added with Duration, timedelta, date and datetime | |
160 objects. | |
161 ''' | |
162 if isinstance(other, Duration): | |
163 newduration = Duration(years=self.years + other.years, | |
164 months=self.months + other.months) | |
165 newduration.tdelta = self.tdelta + other.tdelta | |
166 return newduration | |
167 try: | |
168 # try anything that looks like a date or datetime | |
169 # 'other' has attributes year, month, day | |
170 # and relies on 'timedelta + other' being implemented | |
171 if (not(float(self.years).is_integer() and | |
172 float(self.months).is_integer())): | |
173 raise ValueError('fractional years or months not supported' | |
174 ' for date calculations') | |
175 newmonth = other.month + self.months | |
176 carry, newmonth = fquotmod(newmonth, 1, 13) | |
177 newyear = other.year + self.years + carry | |
178 maxdays = max_days_in_month(newyear, newmonth) | |
179 if other.day > maxdays: | |
180 newday = maxdays | |
181 else: | |
182 newday = other.day | |
183 newdt = other.replace(year=newyear, month=newmonth, day=newday) | |
184 # does a timedelta + date/datetime | |
185 return self.tdelta + newdt | |
186 except AttributeError: | |
187 # other probably was not a date/datetime compatible object | |
188 pass | |
189 try: | |
190 # try if other is a timedelta | |
191 # relies on timedelta + timedelta supported | |
192 newduration = Duration(years=self.years, months=self.months) | |
193 newduration.tdelta = self.tdelta + other | |
194 return newduration | |
195 except AttributeError: | |
196 # ignore ... other probably was not a timedelta compatible object | |
197 pass | |
198 # we have tried everything .... return a NotImplemented | |
199 return NotImplemented | |
200 | |
201 __radd__ = __add__ | |
202 | |
203 def __mul__(self, other): | |
204 if isinstance(other, int): | |
205 newduration = Duration( | |
206 years=self.years * other, | |
207 months=self.months * other) | |
208 newduration.tdelta = self.tdelta * other | |
209 return newduration | |
210 return NotImplemented | |
211 | |
212 __rmul__ = __mul__ | |
213 | |
214 def __sub__(self, other): | |
215 ''' | |
216 It is possible to subtract Duration and timedelta objects from Duration | |
217 objects. | |
218 ''' | |
219 if isinstance(other, Duration): | |
220 newduration = Duration(years=self.years - other.years, | |
221 months=self.months - other.months) | |
222 newduration.tdelta = self.tdelta - other.tdelta | |
223 return newduration | |
224 try: | |
225 # do maths with our timedelta object .... | |
226 newduration = Duration(years=self.years, months=self.months) | |
227 newduration.tdelta = self.tdelta - other | |
228 return newduration | |
229 except TypeError: | |
230 # looks like timedelta - other is not implemented | |
231 pass | |
232 return NotImplemented | |
233 | |
234 def __rsub__(self, other): | |
235 ''' | |
236 It is possible to subtract Duration objecs from date, datetime and | |
237 timedelta objects. | |
238 | |
239 TODO: there is some weird behaviour in date - timedelta ... | |
240 if timedelta has seconds or microseconds set, then | |
241 date - timedelta != date + (-timedelta) | |
242 for now we follow this behaviour to avoid surprises when mixing | |
243 timedeltas with Durations, but in case this ever changes in | |
244 the stdlib we can just do: | |
245 return -self + other | |
246 instead of all the current code | |
247 ''' | |
248 if isinstance(other, timedelta): | |
249 tmpdur = Duration() | |
250 tmpdur.tdelta = other | |
251 return tmpdur - self | |
252 try: | |
253 # check if other behaves like a date/datetime object | |
254 # does it have year, month, day and replace? | |
255 if (not(float(self.years).is_integer() and | |
256 float(self.months).is_integer())): | |
257 raise ValueError('fractional years or months not supported' | |
258 ' for date calculations') | |
259 newmonth = other.month - self.months | |
260 carry, newmonth = fquotmod(newmonth, 1, 13) | |
261 newyear = other.year - self.years + carry | |
262 maxdays = max_days_in_month(newyear, newmonth) | |
263 if other.day > maxdays: | |
264 newday = maxdays | |
265 else: | |
266 newday = other.day | |
267 newdt = other.replace(year=newyear, month=newmonth, day=newday) | |
268 return newdt - self.tdelta | |
269 except AttributeError: | |
270 # other probably was not compatible with data/datetime | |
271 pass | |
272 return NotImplemented | |
273 | |
274 def __eq__(self, other): | |
275 ''' | |
276 If the years, month part and the timedelta part are both equal, then | |
277 the two Durations are considered equal. | |
278 ''' | |
279 if isinstance(other, Duration): | |
280 if (((self.years * 12 + self.months) == | |
281 (other.years * 12 + other.months) and | |
282 self.tdelta == other.tdelta)): | |
283 return True | |
284 return False | |
285 # check if other con be compared against timedelta object | |
286 # will raise an AssertionError when optimisation is off | |
287 if self.years == 0 and self.months == 0: | |
288 return self.tdelta == other | |
289 return False | |
290 | |
291 def __ne__(self, other): | |
292 ''' | |
293 If the years, month part or the timedelta part is not equal, then | |
294 the two Durations are considered not equal. | |
295 ''' | |
296 if isinstance(other, Duration): | |
297 if (((self.years * 12 + self.months) != | |
298 (other.years * 12 + other.months) or | |
299 self.tdelta != other.tdelta)): | |
300 return True | |
301 return False | |
302 # check if other can be compared against timedelta object | |
303 # will raise an AssertionError when optimisation is off | |
304 if self.years == 0 and self.months == 0: | |
305 return self.tdelta != other | |
306 return True | |
307 | |
308 def totimedelta(self, start=None, end=None): | |
309 ''' | |
310 Convert this duration into a timedelta object. | |
311 | |
312 This method requires a start datetime or end datetimem, but raises | |
313 an exception if both are given. | |
314 ''' | |
315 if start is None and end is None: | |
316 raise ValueError("start or end required") | |
317 if start is not None and end is not None: | |
318 raise ValueError("only start or end allowed") | |
319 if start is not None: | |
320 return (start + self) - start | |
321 return end - (end - self) |