clockwork 0.1.2__tar.gz → 0.1.4__tar.gz
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.
- {clockwork-0.1.2 → clockwork-0.1.4}/PKG-INFO +3 -1
- clockwork-0.1.4/clockwork/__init__.py +4 -0
- clockwork-0.1.4/clockwork/_base.py +451 -0
- clockwork-0.1.4/clockwork/_month_end.py +51 -0
- clockwork-0.1.4/clockwork/_quarter_end.py +52 -0
- clockwork-0.1.4/clockwork/core.py +183 -0
- clockwork-0.1.4/clockwork/taskmaster/__init__.py +2 -0
- clockwork-0.1.4/clockwork/taskmaster/_scheduler.py +20 -0
- clockwork-0.1.4/clockwork/taskmaster/_task.py +235 -0
- clockwork-0.1.4/clockwork/taskmaster/file_monitor.py +86 -0
- clockwork-0.1.2/clockwork/chronicle.py → clockwork-0.1.4/clockwork/taskmaster/logger.py +30 -5
- clockwork-0.1.4/clockwork/taskmaster/taskmaster.py +223 -0
- clockwork-0.1.4/clockwork/taskmaster/utils.py +12 -0
- clockwork-0.1.4/clockwork/utils.py +159 -0
- {clockwork-0.1.2 → clockwork-0.1.4}/pyproject.toml +3 -1
- clockwork-0.1.2/clockwork/__init__.py +0 -4
- clockwork-0.1.2/clockwork/core.py +0 -770
- clockwork-0.1.2/clockwork/taskmaster.py +0 -537
- {clockwork-0.1.2 → clockwork-0.1.4}/LICENSE +0 -0
- {clockwork-0.1.2 → clockwork-0.1.4}/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: clockwork
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Toolkit for time-related operations including scheduling, logging, date manipulation, and more.
|
|
5
5
|
Home-page: https://github.com/zteinck/clockwork
|
|
6
6
|
License: MIT
|
|
@@ -16,6 +16,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Requires-Dist: holidays
|
|
18
18
|
Requires-Dist: iterlab
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: pandas
|
|
19
21
|
Requires-Dist: pathpilot
|
|
20
22
|
Requires-Dist: schedule
|
|
21
23
|
Requires-Dist: textwrap3
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import calendar
|
|
3
|
+
import time
|
|
4
|
+
from dateutil.relativedelta import relativedelta
|
|
5
|
+
import holidays as hd
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DateBase(object):
|
|
12
|
+
'''
|
|
13
|
+
Description
|
|
14
|
+
--------------------
|
|
15
|
+
date base class
|
|
16
|
+
|
|
17
|
+
Class Attributes
|
|
18
|
+
--------------------
|
|
19
|
+
factory : func
|
|
20
|
+
initializes new instances as the correct subclass
|
|
21
|
+
weekdays : dict
|
|
22
|
+
dictionary where keys are the days of the week and values
|
|
23
|
+
are the corresponding index values
|
|
24
|
+
holidays : holidays.countries.united_states.UnitedStates
|
|
25
|
+
comprehensive list of U.S. holidays
|
|
26
|
+
|
|
27
|
+
Instance Attributes
|
|
28
|
+
--------------------
|
|
29
|
+
datetime : datetime.datetime
|
|
30
|
+
date and time (if applicable)
|
|
31
|
+
'''
|
|
32
|
+
|
|
33
|
+
#╭-------------------------------------------------------------------------╮
|
|
34
|
+
#| Class Attributes |
|
|
35
|
+
#╰-------------------------------------------------------------------------╯
|
|
36
|
+
|
|
37
|
+
holidays = hd.UnitedStates()
|
|
38
|
+
weekdays = {k: i for i, k in enumerate(calendar.day_name)}
|
|
39
|
+
weekdays.update({k[:3]: v for k, v in weekdays.items()})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
#╭-------------------------------------------------------------------------╮
|
|
43
|
+
#| Initialize Instance |
|
|
44
|
+
#╰-------------------------------------------------------------------------╯
|
|
45
|
+
|
|
46
|
+
def __init__(self, arg=None):
|
|
47
|
+
self.datetime = arg
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
#╭-------------------------------------------------------------------------╮
|
|
51
|
+
#| Static Methods |
|
|
52
|
+
#╰-------------------------------------------------------------------------╯
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def to_timestamp(arg, **kwargs):
|
|
56
|
+
''' converts date in any format to timestamp '''
|
|
57
|
+
if not isinstance(arg, datetime.datetime):
|
|
58
|
+
arg = DateBase.to_datetime(arg, **kwargs)
|
|
59
|
+
return int(time.mktime(arg.timetuple()))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def to_datetime(arg, **kwargs):
|
|
64
|
+
''' converts date in any format to datetime.datetime '''
|
|
65
|
+
return pd.to_datetime(arg, **kwargs).to_pydatetime()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def n_days_in_month(year, month):
|
|
70
|
+
'''
|
|
71
|
+
Description
|
|
72
|
+
----------
|
|
73
|
+
Returns the # of days in a month for a given year.
|
|
74
|
+
For example, if year=2024 and month=2, 29 days is returned since it's a leap year
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
year : int
|
|
79
|
+
year in YYYY format
|
|
80
|
+
month : int
|
|
81
|
+
month
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
----------
|
|
85
|
+
out : int
|
|
86
|
+
number of days
|
|
87
|
+
'''
|
|
88
|
+
out = calendar.monthrange(year, month)[1]
|
|
89
|
+
return out
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
#╭-------------------------------------------------------------------------╮
|
|
93
|
+
#| Class Methods |
|
|
94
|
+
#╰-------------------------------------------------------------------------╯
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def spawn(cls, *args, **kwargs):
|
|
98
|
+
''' Spawn a new instance. The factory function assigns the correct subclass '''
|
|
99
|
+
return cls.factory(*args, **kwargs)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def last_day_of_month(cls, year, month):
|
|
104
|
+
'''
|
|
105
|
+
Description
|
|
106
|
+
----------
|
|
107
|
+
Returns date of the last day of the month for a given year and month
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
year : int
|
|
112
|
+
year in YYYY format
|
|
113
|
+
month : int
|
|
114
|
+
month
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
----------
|
|
118
|
+
clockwork.{MonthEnd}|{QuarterEnd}
|
|
119
|
+
'''
|
|
120
|
+
day = cls.n_days_in_month(year, month)
|
|
121
|
+
return cls.spawn(datetime.datetime(year, month, day))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
#╭-------------------------------------------------------------------------╮
|
|
125
|
+
#| Classes |
|
|
126
|
+
#╰-------------------------------------------------------------------------╯
|
|
127
|
+
|
|
128
|
+
class Decorators(object):
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def spawn(cls, func):
|
|
132
|
+
''' spawn new date instance '''
|
|
133
|
+
|
|
134
|
+
def wrapper(self, *args, **kwargs):
|
|
135
|
+
return self.spawn(func(self, *args, **kwargs))
|
|
136
|
+
|
|
137
|
+
return wrapper
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def other_to_dt(cls, func):
|
|
142
|
+
''' decorator converts other argument used in magic methods to datetime '''
|
|
143
|
+
|
|
144
|
+
def wrapper(self, other):
|
|
145
|
+
if hasattr(other, 'dt'):
|
|
146
|
+
other = other.dt
|
|
147
|
+
else:
|
|
148
|
+
other = DateBase.to_datetime(other)
|
|
149
|
+
return func(self, other)
|
|
150
|
+
|
|
151
|
+
return wrapper
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def arithmetic_other(cls, func):
|
|
156
|
+
''' decorator converts other argument used in magic methods to datetime '''
|
|
157
|
+
|
|
158
|
+
def wrapper(self, other):
|
|
159
|
+
try:
|
|
160
|
+
other = datetime.timedelta(float(other))
|
|
161
|
+
return self.spawn(func(self, other))
|
|
162
|
+
except:
|
|
163
|
+
other = self.spawn(other).dt
|
|
164
|
+
return func(self, other)
|
|
165
|
+
|
|
166
|
+
return wrapper
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def next_last(cls, func):
|
|
171
|
+
''' decorator performs next/last logic '''
|
|
172
|
+
|
|
173
|
+
def wrapper(self, weekday, delta=0):
|
|
174
|
+
'''
|
|
175
|
+
returns DateBase object representing the next or last day of the week relative to self. For example self.next('Mon')
|
|
176
|
+
would return the date of the following monday.
|
|
177
|
+
|
|
178
|
+
Attributes
|
|
179
|
+
-----------------------
|
|
180
|
+
weekday : str
|
|
181
|
+
Day of the week either fully spelled out or the first three characters (e.g. 'Friday' or 'Fri') not case-sensitive.
|
|
182
|
+
delta : int
|
|
183
|
+
Offset value +/- from the current week (e.g. 0 is the current week and -1 is last week).
|
|
184
|
+
'''
|
|
185
|
+
day, desired_day = self.dt.weekday(), self.weekdays[weekday.title()]
|
|
186
|
+
delta += func(self, day - desired_day)
|
|
187
|
+
return self.minus(days=day).plus(days=desired_day, weeks=delta)
|
|
188
|
+
|
|
189
|
+
return wrapper
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
#╭-------------------------------------------------------------------------╮
|
|
193
|
+
#| Properties |
|
|
194
|
+
#╰-------------------------------------------------------------------------╯
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def date(self):
|
|
198
|
+
''' returns normalized instance '''
|
|
199
|
+
return self.normalize()
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def d(self):
|
|
203
|
+
''' converts datetime.datetime to datetime.date '''
|
|
204
|
+
return self.datetime.date()
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def dt(self):
|
|
208
|
+
''' datetime alias '''
|
|
209
|
+
return self.datetime
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def pandas(self):
|
|
213
|
+
''' pandas format '''
|
|
214
|
+
return pd.to_datetime(self.dt)
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def ymd(self):
|
|
218
|
+
''' string in YYYY-MM-DD format '''
|
|
219
|
+
return self.str('%Y-%m-%d')
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def sql_server(self):
|
|
223
|
+
''' ymd alias '''
|
|
224
|
+
return self.ymd
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def oracle(self):
|
|
228
|
+
''' string in DD-%b-YY (e.g. 30-Sep-19) format '''
|
|
229
|
+
return self.str('%d-%b-%y')
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def timestamp(self):
|
|
233
|
+
''' integer '''
|
|
234
|
+
return self.to_timestamp(self.dt)
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def year(self):
|
|
238
|
+
return self.dt.year
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def month(self):
|
|
242
|
+
return self.dt.month
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def month_name(self):
|
|
246
|
+
return self.dt.strftime('%B')
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def day(self):
|
|
250
|
+
return self.dt.day
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def yesterday(self):
|
|
254
|
+
return self - 1
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def tomorrow(self):
|
|
258
|
+
return self + 1
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def month_start(self):
|
|
262
|
+
''' return date object representing the first day of the month '''
|
|
263
|
+
return self.spawn(datetime.datetime(self.year, self.month, 1))
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def month_end(self):
|
|
267
|
+
''' last day of the month as a MonthEnd instance '''
|
|
268
|
+
return self.last_day_of_month(self.year, self.month)
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def last_business_day_of_month(self):
|
|
272
|
+
dt = self.month_end
|
|
273
|
+
dt -= {'Saturday': 1, 'Sunday': 2}.get(dt.weekday, 0)
|
|
274
|
+
return dt
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def weekday(self):
|
|
278
|
+
return self.str('%A')
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def weekday_short(self):
|
|
282
|
+
return self.str('%a')
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def is_weekend(self):
|
|
286
|
+
''' returns True if date does not fall on a weekend '''
|
|
287
|
+
return self.weekday in ('Saturday','Sunday')
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def is_holiday(self):
|
|
291
|
+
''' returns True if date is a U.S. holiday '''
|
|
292
|
+
return self.ymd in self.holidays
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def is_business_day(self):
|
|
296
|
+
''' returns True if date does not fall on a weekend '''
|
|
297
|
+
return not (self.is_weekend or self.is_holiday)
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def is_business_hours(self):
|
|
301
|
+
''' returns True if date is within business hours (8am - 9pm) '''
|
|
302
|
+
set_hour = lambda hour: datetime.datetime(self.year, self.month, self.day, hour)
|
|
303
|
+
out = self.is_business_day and self.dt >= set_hour(8) and self.dt <= set_hour(21)
|
|
304
|
+
return out
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def is_today(self):
|
|
308
|
+
''' returns True if date is the current day '''
|
|
309
|
+
return self.ymd == self.spawn().ymd
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def holiday(self):
|
|
313
|
+
''' returns the current holiday, if applicable '''
|
|
314
|
+
return self.holidays.get(self.ymd)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
#╭-------------------------------------------------------------------------╮
|
|
318
|
+
#| Magic Methods |
|
|
319
|
+
#╰-------------------------------------------------------------------------╯
|
|
320
|
+
|
|
321
|
+
def __repr__(self):
|
|
322
|
+
return str(self)
|
|
323
|
+
|
|
324
|
+
def __str__(self):
|
|
325
|
+
components = ['%Y-%m-%d']
|
|
326
|
+
if self.dt.hour + self.dt.second + self.dt.microsecond > 0:
|
|
327
|
+
components.append('%I:%M:%S.%f %p')
|
|
328
|
+
return self.str(' '.join(components))
|
|
329
|
+
|
|
330
|
+
def __int__(self):
|
|
331
|
+
return self.timestamp
|
|
332
|
+
|
|
333
|
+
@Decorators.other_to_dt
|
|
334
|
+
def __eq__(self, other):
|
|
335
|
+
return self.dt == other
|
|
336
|
+
|
|
337
|
+
@Decorators.other_to_dt
|
|
338
|
+
def __ne__(self, other):
|
|
339
|
+
return self.dt != other
|
|
340
|
+
|
|
341
|
+
@Decorators.other_to_dt
|
|
342
|
+
def __lt__(self, other):
|
|
343
|
+
return self.dt < other
|
|
344
|
+
|
|
345
|
+
@Decorators.other_to_dt
|
|
346
|
+
def __gt__(self, other):
|
|
347
|
+
return self.dt > other
|
|
348
|
+
|
|
349
|
+
@Decorators.other_to_dt
|
|
350
|
+
def __le__(self, other):
|
|
351
|
+
return self.dt <= other
|
|
352
|
+
|
|
353
|
+
@Decorators.other_to_dt
|
|
354
|
+
def __ge__(self, other):
|
|
355
|
+
return self.dt >= other
|
|
356
|
+
|
|
357
|
+
@Decorators.arithmetic_other
|
|
358
|
+
def __add__(self, other):
|
|
359
|
+
''' if other is date-like then implements default behavior for adding datetimes otherwise
|
|
360
|
+
other is treated as timedelta '''
|
|
361
|
+
return self.dt + other
|
|
362
|
+
|
|
363
|
+
@Decorators.arithmetic_other
|
|
364
|
+
def __sub__(self, other):
|
|
365
|
+
''' if other is date-like then implements default behavior for subtracting datetimes otherwise
|
|
366
|
+
other is treated as timedelta '''
|
|
367
|
+
return self.dt - other
|
|
368
|
+
|
|
369
|
+
@Decorators.other_to_dt
|
|
370
|
+
def __contains__(self, item):
|
|
371
|
+
return self.normalize(item).dt <= self.dt < (self.normalize(item) + 1).dt
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
#╭-------------------------------------------------------------------------╮
|
|
375
|
+
#| Instance Methods |
|
|
376
|
+
#╰-------------------------------------------------------------------------╯
|
|
377
|
+
|
|
378
|
+
def skip_weekend(self, forward=True):
|
|
379
|
+
x = 2 if forward else -1
|
|
380
|
+
if self.weekday == 'Saturday':
|
|
381
|
+
return self + x
|
|
382
|
+
elif self.weekday == 'Sunday':
|
|
383
|
+
return self + (x - 1)
|
|
384
|
+
else:
|
|
385
|
+
return self
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def business_day_delta(self, delta):
|
|
389
|
+
out = self.spawn(self.dt)
|
|
390
|
+
sign = np.sign(delta)
|
|
391
|
+
|
|
392
|
+
counter = 0
|
|
393
|
+
while counter < abs(delta):
|
|
394
|
+
out += sign
|
|
395
|
+
while not out.is_business_day:
|
|
396
|
+
out += sign
|
|
397
|
+
counter += 1
|
|
398
|
+
|
|
399
|
+
return out
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def skip_holiday(self, forward=True):
|
|
403
|
+
if self.is_holiday:
|
|
404
|
+
return (self + (1 if forward else -1)).skip_holiday(forward=forward)
|
|
405
|
+
else:
|
|
406
|
+
return self
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def str(self, fmt):
|
|
410
|
+
''' strftime shortcut '''
|
|
411
|
+
return self.dt.strftime(fmt)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@Decorators.spawn
|
|
415
|
+
def shift(self, **kwargs):
|
|
416
|
+
return self.dt + relativedelta(**kwargs)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@Decorators.spawn
|
|
420
|
+
def replace(self, **kwargs):
|
|
421
|
+
return self.dt.replace(**kwargs)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@Decorators.spawn
|
|
425
|
+
def plus(self, **kwargs):
|
|
426
|
+
''' timedelta kwargs = weeks, days, hours, minutes, seconds, seconds, etc '''
|
|
427
|
+
return self.dt + datetime.timedelta(**kwargs)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@Decorators.spawn
|
|
431
|
+
def minus(self, **kwargs):
|
|
432
|
+
''' timedelta kwargs = weeks, days, hours, minutes, seconds, seconds, etc '''
|
|
433
|
+
return self.dt - datetime.timedelta(**kwargs)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@Decorators.next_last
|
|
437
|
+
def next(self, x):
|
|
438
|
+
''' see decorator for documentation '''
|
|
439
|
+
return +1 if x >= 0 else 0
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@Decorators.next_last
|
|
443
|
+
def last(self, x):
|
|
444
|
+
''' see decorator for documentation '''
|
|
445
|
+
return -1 if x <= 0 else 0
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@Decorators.spawn
|
|
449
|
+
def normalize(self):
|
|
450
|
+
''' the time component (hours, minutes, seconds, microseconds) is set to zero (midnight) '''
|
|
451
|
+
return datetime.datetime(self.year, self.month, self.day)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from ._base import DateBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MonthEnd(DateBase):
|
|
6
|
+
|
|
7
|
+
#╭-------------------------------------------------------------------------╮
|
|
8
|
+
#| Initialize Instance |
|
|
9
|
+
#╰-------------------------------------------------------------------------╯
|
|
10
|
+
|
|
11
|
+
def __init__(self, dt):
|
|
12
|
+
super().__init__(dt.replace(day=self.n_days_in_month(dt.year, dt.month)))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#╭-------------------------------------------------------------------------╮
|
|
16
|
+
#| Properties |
|
|
17
|
+
#╰-------------------------------------------------------------------------╯
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def long(self):
|
|
21
|
+
return self.str('%Y-%m-%d')
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def compact(self):
|
|
25
|
+
return self.str('%Y-%m')
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def short(self):
|
|
29
|
+
return self.str('%b')
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def last_quarter_end(self):
|
|
33
|
+
''' returns most recent quarter end '''
|
|
34
|
+
delta = 0
|
|
35
|
+
while True:
|
|
36
|
+
obj = self.offset(delta=delta)
|
|
37
|
+
if hasattr(obj, 'quarter'): return obj
|
|
38
|
+
delta -= 1
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
#╭-------------------------------------------------------------------------╮
|
|
42
|
+
#| Instance Methods |
|
|
43
|
+
#╰-------------------------------------------------------------------------╯
|
|
44
|
+
|
|
45
|
+
def offset(self, delta):
|
|
46
|
+
''' returns the month end 'delta' months away from the instance '''
|
|
47
|
+
if not isinstance(delta, int):
|
|
48
|
+
raise TypeError("'delta' argument must be an integer")
|
|
49
|
+
if delta == 0: return self
|
|
50
|
+
dt = self.shift(months=delta).dt
|
|
51
|
+
return self.__class__(dt)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from ._month_end import MonthEnd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QuarterEnd(MonthEnd):
|
|
6
|
+
|
|
7
|
+
#╭-------------------------------------------------------------------------╮
|
|
8
|
+
#| Initialize Instance |
|
|
9
|
+
#╰-------------------------------------------------------------------------╯
|
|
10
|
+
|
|
11
|
+
def __init__(self, dt, qtr, *args, **kwargs):
|
|
12
|
+
super().__init__(dt)
|
|
13
|
+
self.qtr = qtr
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
#╭-------------------------------------------------------------------------╮
|
|
17
|
+
#| Properties |
|
|
18
|
+
#╰-------------------------------------------------------------------------╯
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def long(self):
|
|
22
|
+
return f'{self.year}Q{self.qtr}'
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def compact(self):
|
|
26
|
+
return f'{self.qtr}Q' + self.str('%y')
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def short(self):
|
|
30
|
+
return f'Q{self.qtr}'
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def quarter(self):
|
|
34
|
+
return self.qtr
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#╭-------------------------------------------------------------------------╮
|
|
38
|
+
#| Instance Methods |
|
|
39
|
+
#╰-------------------------------------------------------------------------╯
|
|
40
|
+
|
|
41
|
+
def to_month_end(self):
|
|
42
|
+
return MonthEnd(self.dt)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def offset(self, delta):
|
|
46
|
+
''' returns the quarter end 'delta' quarters away from the instance '''
|
|
47
|
+
if not isinstance(delta, int):
|
|
48
|
+
raise TypeError("'delta' argument must be an integer")
|
|
49
|
+
if delta == 0: return self
|
|
50
|
+
dt = self.shift(months=delta * 3).dt
|
|
51
|
+
qtr = ((self.qtr - 1 + delta) % 4) + 1
|
|
52
|
+
return self.__class__(dt, qtr)
|