eodash_catalog 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of eodash_catalog might be problematic. Click here for more details.

@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2024-present Daniel Santillan <daniel.santillan@eox.at>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "0.0.1"
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2024-present Daniel Santillan <daniel.santillan@eox.at>
2
+ #
3
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,315 @@
1
+ """
2
+ This module defines a Duration class.
3
+
4
+ The class Duration allows to define durations in years and months and can be
5
+ used as limited replacement for timedelta objects.
6
+ """
7
+ from datetime import timedelta
8
+ from decimal import Decimal, ROUND_FLOOR
9
+
10
+
11
+ def fquotmod(val, low, high):
12
+ """
13
+ A divmod function with boundaries.
14
+
15
+ """
16
+ # assumes that all the maths is done with Decimals.
17
+ # divmod for Decimal uses truncate instead of floor as builtin
18
+ # divmod, so we have to do it manually here.
19
+ a, b = val - low, high - low
20
+ div = (a / b).to_integral(ROUND_FLOOR)
21
+ mod = a - div * b
22
+ # if we were not using Decimal, it would look like this.
23
+ # div, mod = divmod(val - low, high - low)
24
+ mod += low
25
+ return int(div), mod
26
+
27
+
28
+ def max_days_in_month(year, month):
29
+ """
30
+ Determines the number of days of a specific month in a specific year.
31
+ """
32
+ if month in (1, 3, 5, 7, 8, 10, 12):
33
+ return 31
34
+ if month in (4, 6, 9, 11):
35
+ return 30
36
+ if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0):
37
+ return 29
38
+ return 28
39
+
40
+
41
+ class Duration:
42
+ """
43
+ A class which represents a duration.
44
+
45
+ The difference to datetime.timedelta is, that this class handles also
46
+ differences given in years and months.
47
+ A Duration treats differences given in year, months separately from all
48
+ other components.
49
+
50
+ A Duration can be used almost like any timedelta object, however there
51
+ are some restrictions:
52
+ * It is not really possible to compare Durations, because it is unclear,
53
+ whether a duration of 1 year is bigger than 365 days or not.
54
+ * Equality is only tested between the two (year, month vs. timedelta)
55
+ basic components.
56
+
57
+ A Duration can also be converted into a datetime object, but this requires
58
+ a start date or an end date.
59
+
60
+ The algorithm to add a duration to a date is defined at
61
+ http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ days=0,
67
+ seconds=0,
68
+ microseconds=0,
69
+ milliseconds=0,
70
+ minutes=0,
71
+ hours=0,
72
+ weeks=0,
73
+ months=0,
74
+ years=0,
75
+ ):
76
+ """
77
+ Initialise this Duration instance with the given parameters.
78
+ """
79
+ if not isinstance(months, Decimal):
80
+ months = Decimal(str(months))
81
+ if not isinstance(years, Decimal):
82
+ years = Decimal(str(years))
83
+ self.months = months
84
+ self.years = years
85
+ self.tdelta = timedelta(
86
+ days, seconds, microseconds, milliseconds, minutes, hours, weeks
87
+ )
88
+
89
+ def __getstate__(self):
90
+ return self.__dict__
91
+
92
+ def __setstate__(self, state):
93
+ self.__dict__.update(state)
94
+
95
+ def __getattr__(self, name):
96
+ """
97
+ Provide direct access to attributes of included timedelta instance.
98
+ """
99
+ return getattr(self.tdelta, name)
100
+
101
+ def __str__(self):
102
+ """
103
+ Return a string representation of this duration similar to timedelta.
104
+ """
105
+ params = []
106
+ if self.years:
107
+ params.append("%d years" % self.years)
108
+ if self.months:
109
+ fmt = "%d months"
110
+ if self.months <= 1:
111
+ fmt = "%d month"
112
+ params.append(fmt % self.months)
113
+ params.append(str(self.tdelta))
114
+ return ", ".join(params)
115
+
116
+ def __repr__(self):
117
+ """
118
+ Return a string suitable for repr(x) calls.
119
+ """
120
+ return "%s.%s(%d, %d, %d, years=%d, months=%d)" % (
121
+ self.__class__.__module__,
122
+ self.__class__.__name__,
123
+ self.tdelta.days,
124
+ self.tdelta.seconds,
125
+ self.tdelta.microseconds,
126
+ self.years,
127
+ self.months,
128
+ )
129
+
130
+ def __hash__(self):
131
+ """
132
+ Return a hash of this instance so that it can be used in, for
133
+ example, dicts and sets.
134
+ """
135
+ return hash((self.tdelta, self.months, self.years))
136
+
137
+ def __neg__(self):
138
+ """
139
+ A simple unary minus.
140
+
141
+ Returns a new Duration instance with all it's negated.
142
+ """
143
+ negduration = Duration(years=-self.years, months=-self.months)
144
+ negduration.tdelta = -self.tdelta
145
+ return negduration
146
+
147
+ def __add__(self, other):
148
+ """
149
+ Durations can be added with Duration, timedelta, date and datetime
150
+ objects.
151
+ """
152
+ if isinstance(other, Duration):
153
+ newduration = Duration(
154
+ years=self.years + other.years, months=self.months + other.months
155
+ )
156
+ newduration.tdelta = self.tdelta + other.tdelta
157
+ return newduration
158
+ try:
159
+ # try anything that looks like a date or datetime
160
+ # 'other' has attributes year, month, day
161
+ # and relies on 'timedelta + other' being implemented
162
+ if not (float(self.years).is_integer() and float(self.months).is_integer()):
163
+ raise ValueError(
164
+ "fractional years or months not supported" " for date calculations"
165
+ )
166
+ newmonth = other.month + self.months
167
+ carry, newmonth = fquotmod(newmonth, 1, 13)
168
+ newyear = other.year + self.years + carry
169
+ maxdays = max_days_in_month(newyear, newmonth)
170
+ if other.day > maxdays:
171
+ newday = maxdays
172
+ else:
173
+ newday = other.day
174
+ newdt = other.replace(
175
+ year=int(newyear), month=int(newmonth), day=int(newday)
176
+ )
177
+ # does a timedelta + date/datetime
178
+ return self.tdelta + newdt
179
+ except AttributeError:
180
+ # other probably was not a date/datetime compatible object
181
+ pass
182
+ try:
183
+ # try if other is a timedelta
184
+ # relies on timedelta + timedelta supported
185
+ newduration = Duration(years=self.years, months=self.months)
186
+ newduration.tdelta = self.tdelta + other
187
+ return newduration
188
+ except AttributeError:
189
+ # ignore ... other probably was not a timedelta compatible object
190
+ pass
191
+ # we have tried everything .... return a NotImplemented
192
+ return NotImplemented
193
+
194
+ __radd__ = __add__
195
+
196
+ def __mul__(self, other):
197
+ if isinstance(other, int):
198
+ newduration = Duration(years=self.years * other, months=self.months * other)
199
+ newduration.tdelta = self.tdelta * other
200
+ return newduration
201
+ return NotImplemented
202
+
203
+ __rmul__ = __mul__
204
+
205
+ def __sub__(self, other):
206
+ """
207
+ It is possible to subtract Duration and timedelta objects from Duration
208
+ objects.
209
+ """
210
+ if isinstance(other, Duration):
211
+ newduration = Duration(
212
+ years=self.years - other.years, months=self.months - other.months
213
+ )
214
+ newduration.tdelta = self.tdelta - other.tdelta
215
+ return newduration
216
+ try:
217
+ # do maths with our timedelta object ....
218
+ newduration = Duration(years=self.years, months=self.months)
219
+ newduration.tdelta = self.tdelta - other
220
+ return newduration
221
+ except TypeError:
222
+ # looks like timedelta - other is not implemented
223
+ pass
224
+ return NotImplemented
225
+
226
+ def __rsub__(self, other):
227
+ """
228
+ It is possible to subtract Duration objects from date, datetime and
229
+ timedelta objects.
230
+
231
+ TODO: there is some weird behaviour in date - timedelta ...
232
+ if timedelta has seconds or microseconds set, then
233
+ date - timedelta != date + (-timedelta)
234
+ for now we follow this behaviour to avoid surprises when mixing
235
+ timedeltas with Durations, but in case this ever changes in
236
+ the stdlib we can just do:
237
+ return -self + other
238
+ instead of all the current code
239
+ """
240
+ if isinstance(other, timedelta):
241
+ tmpdur = Duration()
242
+ tmpdur.tdelta = other
243
+ return tmpdur - self
244
+ try:
245
+ # check if other behaves like a date/datetime object
246
+ # does it have year, month, day and replace?
247
+ if not (float(self.years).is_integer() and float(self.months).is_integer()):
248
+ raise ValueError(
249
+ "fractional years or months not supported" " for date calculations"
250
+ )
251
+ newmonth = other.month - self.months
252
+ carry, newmonth = fquotmod(newmonth, 1, 13)
253
+ newyear = other.year - self.years + carry
254
+ maxdays = max_days_in_month(newyear, newmonth)
255
+ if other.day > maxdays:
256
+ newday = maxdays
257
+ else:
258
+ newday = other.day
259
+ newdt = other.replace(
260
+ year=int(newyear), month=int(newmonth), day=int(newday)
261
+ )
262
+ return newdt - self.tdelta
263
+ except AttributeError:
264
+ # other probably was not compatible with data/datetime
265
+ pass
266
+ return NotImplemented
267
+
268
+ def __eq__(self, other):
269
+ """
270
+ If the years, month part and the timedelta part are both equal, then
271
+ the two Durations are considered equal.
272
+ """
273
+ if isinstance(other, Duration):
274
+ if (self.years * 12 + self.months) == (
275
+ other.years * 12 + other.months
276
+ ) and self.tdelta == other.tdelta:
277
+ return True
278
+ return False
279
+ # check if other con be compared against timedelta object
280
+ # will raise an AssertionError when optimisation is off
281
+ if self.years == 0 and self.months == 0:
282
+ return self.tdelta == other
283
+ return False
284
+
285
+ def __ne__(self, other):
286
+ """
287
+ If the years, month part or the timedelta part is not equal, then
288
+ the two Durations are considered not equal.
289
+ """
290
+ if isinstance(other, Duration):
291
+ if (self.years * 12 + self.months) != (
292
+ other.years * 12 + other.months
293
+ ) or self.tdelta != other.tdelta:
294
+ return True
295
+ return False
296
+ # check if other can be compared against timedelta object
297
+ # will raise an AssertionError when optimisation is off
298
+ if self.years == 0 and self.months == 0:
299
+ return self.tdelta != other
300
+ return True
301
+
302
+ def totimedelta(self, start=None, end=None):
303
+ """
304
+ Convert this duration into a timedelta object.
305
+
306
+ This method requires a start datetime or end datetimem, but raises
307
+ an exception if both are given.
308
+ """
309
+ if start is None and end is None:
310
+ raise ValueError("start or end required")
311
+ if start is not None and end is not None:
312
+ raise ValueError("only start or end allowed")
313
+ if start is not None:
314
+ return (start + self) - start
315
+ return end - (end - self)