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) |
