reykit 1.0.0__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.
reykit/rtime.py ADDED
@@ -0,0 +1,678 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-05 14:11:50
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Time methods.
9
+ """
10
+
11
+
12
+ from typing import Any, TypedDict, Literal, Optional, Union, overload, NoReturn
13
+ from collections.abc import Callable
14
+ from pandas import (
15
+ DataFrame,
16
+ Timestamp as pd_timestamp,
17
+ Timedelta as pd_timedelta
18
+ )
19
+ from time import (
20
+ struct_time as time_struct_time,
21
+ strftime as time_strftime,
22
+ time as time_time,
23
+ sleep as time_sleep
24
+ )
25
+ from datetime import (
26
+ datetime as datetime_datetime,
27
+ date as datetime_date,
28
+ time as datetime_time,
29
+ timedelta as datetime_timedelta
30
+ )
31
+
32
+ from .rexception import throw
33
+ from .rnumber import digits
34
+ from .rrandom import randn
35
+ from .rregex import search
36
+ from .rstdout import echo
37
+
38
+
39
+ __all__ = (
40
+ 'now',
41
+ 'time_to',
42
+ 'text_to_time',
43
+ 'to_time',
44
+ 'sleep',
45
+ 'wait',
46
+ 'RTimeMark'
47
+ )
48
+
49
+
50
+ RecordData = TypedDict('RecordData', {'timestamp': int, 'datetime': datetime_datetime, 'timedelta': Optional[datetime_timedelta], 'note': Optional[str]})
51
+
52
+
53
+ @overload
54
+ def now(format_: Literal['datetime'] = 'datetime') -> datetime_datetime: ...
55
+
56
+ @overload
57
+ def now(format_: Literal['date'] = 'datetime') -> datetime_date: ...
58
+
59
+ @overload
60
+ def now(format_: Literal['time'] = 'datetime') -> datetime_time: ...
61
+
62
+ @overload
63
+ def now(format_: Literal['datetime_str', 'date_str', 'time_str'] = 'datetime') -> str: ...
64
+
65
+ @overload
66
+ def now(format_: Literal['timestamp'] = 'datetime') -> int: ...
67
+
68
+ @overload
69
+ def now(format_: Any) -> NoReturn: ...
70
+
71
+ def now(
72
+ format_: Literal[
73
+ 'datetime',
74
+ 'date',
75
+ 'time',
76
+ 'datetime_str',
77
+ 'date_str',
78
+ 'time_str',
79
+ 'timestamp'
80
+ ] = 'datetime'
81
+ ) -> Union[
82
+ datetime_datetime,
83
+ datetime_date,
84
+ datetime_time,
85
+ str,
86
+ int
87
+ ]:
88
+ """
89
+ Get the now time.
90
+
91
+ Parameters
92
+ ----------
93
+ format_ : Format type.
94
+ - `Literal['datetime']`: Return datetime object of datetime package.
95
+ - `Literal['date']`: Return date object of datetime package.
96
+ - `Literal['time']`: Return time object of datetime package.
97
+ - `Literal['datetime_str']`: Return string in format `'%Y-%m-%d %H:%M:%S'`.
98
+ - `Literal['date_str']`: Return string in format `'%Y-%m-%d'`.
99
+ - `Literal['time_str']`: Return string in foramt `'%H:%M:%S'`.
100
+ - `Literal['timestamp']`: Return time stamp in milliseconds.
101
+
102
+ Returns
103
+ -------
104
+ The now time.
105
+ """
106
+
107
+ # Return.
108
+ match format_:
109
+ case 'datetime':
110
+ return datetime_datetime.now()
111
+ case 'date':
112
+ return datetime_datetime.now().date()
113
+ case 'time':
114
+ return datetime_datetime.now().time()
115
+ case 'datetime_str':
116
+ return datetime_datetime.now().strftime('%Y-%m-%d %H:%M:%S')
117
+ case 'date_str':
118
+ return datetime_datetime.now().strftime('%Y-%m-%d')
119
+ case 'time_str':
120
+ return datetime_datetime.now().strftime('%H:%M:%S')
121
+ case 'timestamp':
122
+ return int(time_time() * 1000)
123
+ case _:
124
+ throw(ValueError, format_)
125
+
126
+
127
+ @overload
128
+ def time_to(
129
+ obj: Union[
130
+ datetime_datetime,
131
+ datetime_date,
132
+ datetime_time,
133
+ datetime_timedelta,
134
+ time_struct_time,
135
+ pd_timestamp,
136
+ pd_timedelta
137
+ ],
138
+ decimal: bool = False,
139
+ raising: bool = True
140
+ ) -> str: ...
141
+
142
+ @overload
143
+ def time_to(
144
+ obj: Any,
145
+ decimal: bool = False,
146
+ raising: Literal[True] = True
147
+ ) -> NoReturn: ...
148
+
149
+ @overload
150
+ def time_to(
151
+ obj: Any,
152
+ decimal: bool = False,
153
+ raising: Literal[False] = True
154
+ ) -> Any: ...
155
+
156
+ def time_to(
157
+ obj: Any,
158
+ decimal: bool = False,
159
+ raising: bool = True
160
+ ) -> Any:
161
+ """
162
+ Convert time object to text.
163
+
164
+ Parameters
165
+ ----------
166
+ obj : Time object.
167
+ - `datetime`: Text format is `'%Y-%m-%d %H:%M:%S'`.
168
+ - `date`: Text format is `'%Y-%m-%d'`.
169
+ - `time`: Text format is `'%H:%M:%S'`.
170
+ - `struct_time`: Text format is `'%Y-%m-%d %H:%M:%S'`.
171
+ decimal : Whether with decimal, precision to microseconds.
172
+ raising : When parameter `obj` value error, whether throw exception, otherwise return original value.
173
+
174
+ Returns
175
+ -------
176
+ Converted text.
177
+ """
178
+
179
+ match obj:
180
+
181
+ # Type 'datetime'.
182
+ case datetime_datetime() | pd_timestamp():
183
+ if decimal:
184
+ format_ = '%Y-%m-%d %H:%M:%S.%f'
185
+ else:
186
+ format_ = '%Y-%m-%d %H:%M:%S'
187
+ text = obj.strftime(format_)
188
+
189
+ # Type 'date'.
190
+ case datetime_date():
191
+ text = obj.strftime('%Y-%m-%d')
192
+
193
+ # Type 'time'.
194
+ case datetime_time():
195
+ if decimal:
196
+ format_ = '%H:%M:%S.%f'
197
+ else:
198
+ format_ = '%H:%M:%S'
199
+ text = obj.strftime(format_)
200
+
201
+ # Type 'timedelta'.
202
+ case datetime_timedelta() | pd_timedelta():
203
+ timestamp = obj.seconds + obj.microseconds / 1000_000
204
+ if timestamp >= 0:
205
+ timestamp += 57600
206
+ time = datetime_datetime.fromtimestamp(timestamp).time()
207
+ if decimal:
208
+ format_ = '%H:%M:%S.%f'
209
+ else:
210
+ format_ = '%H:%M:%S'
211
+ text = time.strftime(format_)
212
+ if obj.days != 0:
213
+ text = f'{obj.days}day ' + text
214
+
215
+ ## Throw exception.
216
+ elif raising:
217
+ throw(ValueError, obj)
218
+
219
+ ## Not raise.
220
+ else:
221
+ return obj
222
+
223
+ # Type 'struct_time'.
224
+ case time_struct_time():
225
+ if decimal:
226
+ format_ = '%Y-%m-%d %H:%M:%S.%f'
227
+ else:
228
+ format_ = '%Y-%m-%d %H:%M:%S'
229
+ text = time_strftime(format_, obj)
230
+
231
+ # Throw exception.
232
+ case _ if raising:
233
+ throw(TypeError, obj)
234
+
235
+ # Not raise.
236
+ case _:
237
+ return obj
238
+
239
+ return text
240
+
241
+
242
+ def text_to_time(
243
+ string: str
244
+ ) -> Optional[
245
+ Union[
246
+ datetime_datetime,
247
+ datetime_date,
248
+ datetime_time
249
+ ]
250
+ ]:
251
+ """
252
+ Convert text to time object.
253
+
254
+ Parameters
255
+ ----------
256
+ string : String.
257
+
258
+ Returns
259
+ -------
260
+ Object or null.
261
+ """
262
+
263
+ # Get parameter.
264
+ time_obj = None
265
+ str_len = len(string)
266
+
267
+ # Extract.
268
+
269
+ ## Standard.
270
+ if 14 <= str_len <= 19:
271
+ try:
272
+ time_obj = datetime_datetime.strptime(string, '%Y-%m-%d %H:%M:%S')
273
+ except ValueError:
274
+ pass
275
+ else:
276
+ return time_obj
277
+ if 8 <= str_len <= 10:
278
+ try:
279
+ time_obj = datetime_datetime.strptime(string, '%Y-%m-%d').date()
280
+ except ValueError:
281
+ pass
282
+ else:
283
+ return time_obj
284
+ if 5 <= str_len <= 8:
285
+ try:
286
+ time_obj = datetime_datetime.strptime(string, '%H:%M:%S').time()
287
+ except ValueError:
288
+ pass
289
+ else:
290
+ return time_obj
291
+
292
+ ## Regular.
293
+
294
+ ### Type 'datetime'.
295
+ if 14 <= str_len <= 21:
296
+ pattern = r'^(\d{4})\S(\d{1,2})\S(\d{1,2})\S?.(\d{1,2})\S(\d{1,2})\S(\d{1,2})\S?$'
297
+ result = search(pattern, string)
298
+ if result is not None:
299
+ year, month, day, hour, minute, second = [
300
+ int(value)
301
+ for value in result
302
+ ]
303
+ time_obj = datetime_datetime(year, month, day, hour, minute, second)
304
+ return time_obj
305
+
306
+ ### Type 'date'.
307
+ if 8 <= str_len <= 11:
308
+ pattern = r'^(\d{4})\S(\d{1,2})\S(\d{1,2})\S?$'
309
+ result = search(pattern, string)
310
+ if result is not None:
311
+ year, month, day = [
312
+ int(value)
313
+ for value in result
314
+ ]
315
+ time_obj = datetime_date(year, month, day)
316
+ return time_obj
317
+
318
+ ### Type 'time'.
319
+ if 5 <= str_len <= 9:
320
+ pattern = r'^(\d{1,2})\S(\d{1,2})\S(\d{1,2})\S?$'
321
+ result = search(pattern, string)
322
+ if result is not None:
323
+ hour, minute, second = [
324
+ int(value)
325
+ for value in result
326
+ ]
327
+ time_obj = datetime_time(hour, minute, second)
328
+ return time_obj
329
+
330
+
331
+ @overload
332
+ def to_time(
333
+ obj: str,
334
+ raising: bool = True
335
+ ) -> Union[datetime_datetime, datetime_date, datetime_time]: ...
336
+
337
+ @overload
338
+ def to_time(
339
+ obj: Union[time_struct_time, float],
340
+ raising: bool = True
341
+ ) -> datetime_datetime: ...
342
+
343
+ @overload
344
+ def to_time(
345
+ obj: Any,
346
+ raising: Literal[True] = True
347
+ ) -> NoReturn: ...
348
+
349
+ @overload
350
+ def to_time(
351
+ obj: Any,
352
+ raising: Literal[False] = True
353
+ ) -> Any: ...
354
+
355
+ def to_time(
356
+ obj: Any,
357
+ raising: bool = True
358
+ ) -> Any:
359
+ """
360
+ Convert object to time object.
361
+
362
+ Parameters
363
+ ----------
364
+ obj : Object.
365
+ raising : When parameter `obj` value error, whether throw exception, otherwise return original value.
366
+
367
+ Returns
368
+ -------
369
+ Time object.
370
+ """
371
+
372
+ match obj:
373
+
374
+ # Type 'str'.
375
+ case str():
376
+ time_obj = text_to_time(obj)
377
+
378
+ # Type 'struct_time'.
379
+ case time_struct_time():
380
+ time_obj = datetime_datetime(
381
+ obj.tm_year,
382
+ obj.tm_mon,
383
+ obj.tm_mday,
384
+ obj.tm_hour,
385
+ obj.tm_min,
386
+ obj.tm_sec
387
+ )
388
+
389
+ # Type 'float'.
390
+ case int() | float():
391
+ int_len, _ = digits(obj)
392
+ match int_len:
393
+ case 10:
394
+ time_obj = datetime_datetime.fromtimestamp(obj)
395
+ case 13:
396
+ time_obj = datetime_datetime.fromtimestamp(obj / 1000)
397
+ case _:
398
+ time_obj = None
399
+
400
+ # No time object.
401
+ if time_obj is None:
402
+
403
+ ## Throw exception.
404
+ if raising:
405
+ throw(ValueError, obj)
406
+
407
+ ## Not raise.
408
+ else:
409
+ return obj
410
+
411
+ return time_obj
412
+
413
+
414
+ def sleep(
415
+ *thresholds: float,
416
+ precision: Optional[int] = None
417
+ ) -> float:
418
+ """
419
+ Sleep random seconds.
420
+
421
+ Parameters
422
+ ----------
423
+ thresholds : Low and high thresholds of random range, range contains thresholds.
424
+ - When `length is 0`, then low and high thresholds is `0` and `10`.
425
+ - When `length is 1`, then sleep this value.
426
+ - When `length is 2`, then low and high thresholds is `thresholds[0]` and `thresholds[1]`.
427
+
428
+ precision : Precision of random range, that is maximum decimal digits of sleep seconds.
429
+ - `None`: Set to Maximum decimal digits of element of parameter `thresholds`.
430
+ - `int`: Set to this value.
431
+
432
+ Returns
433
+ -------
434
+ Random seconds.
435
+ - When parameters `precision` is `0`, then return int.
436
+ - When parameters `precision` is `greater than 0`, then return float.
437
+ """
438
+
439
+ # Handle parameter.
440
+ if len(thresholds) == 1:
441
+ second = float(thresholds[0])
442
+ else:
443
+ second = randn(*thresholds, precision=precision)
444
+
445
+ # Sleep.
446
+ time_sleep(second)
447
+
448
+ return second
449
+
450
+
451
+ def wait(
452
+ func: Callable[..., bool],
453
+ *args: Any,
454
+ _interval: float = 1,
455
+ _timeout: Optional[float] = None,
456
+ **kwargs: Any
457
+ ) -> float:
458
+ """
459
+ Wait success, timeout throw exception.
460
+
461
+ Parameters
462
+ ----------
463
+ func : Function to be decorated, must return `bool` value.
464
+ args : Position arguments of decorated function.
465
+ _interval : Interval seconds.
466
+ _timeout : Timeout seconds, timeout throw exception.
467
+ - `None`: Infinite time.
468
+ - `float`: Use this time.
469
+ kwargs : Keyword arguments of decorated function.
470
+
471
+ Returns
472
+ -------
473
+ Total spend seconds.
474
+ """
475
+
476
+ # Set parameter.
477
+ rtm = RTimeMark()
478
+ rtm()
479
+
480
+ # Not set timeout.
481
+ if _timeout is None:
482
+
483
+ ## Wait.
484
+ while True:
485
+ success = func(*args, **kwargs)
486
+ if success: break
487
+ sleep(_interval)
488
+
489
+ # Set timeout.
490
+ else:
491
+
492
+ ## Wait.
493
+ while True:
494
+ success = func(*args, **kwargs)
495
+ if success: break
496
+
497
+ ## Timeout.
498
+ rtm()
499
+ if rtm.total_spend > _timeout:
500
+ throw(TimeoutError, _timeout)
501
+
502
+ ## Sleep.
503
+ sleep(_interval)
504
+
505
+ ## Return.
506
+ rtm()
507
+ return rtm.total_spend
508
+
509
+
510
+ class RTimeMark():
511
+ """
512
+ Rey`s `time mark` type.
513
+ """
514
+
515
+
516
+ def __init__(self) -> None:
517
+ """
518
+ Build `time mark` attributes.
519
+ """
520
+
521
+ # Record table.
522
+ self.record: dict[int, RecordData] = {}
523
+
524
+
525
+ def mark(self, note: Optional[str] = None) -> int:
526
+ """
527
+ Marking now time.
528
+
529
+ Parameters
530
+ ----------
531
+ note : Mark note.
532
+
533
+ Returns
534
+ -------
535
+ Mark index.
536
+ """
537
+
538
+ # Get parametes.
539
+
540
+ # Mark.
541
+ index = len(self.record)
542
+ now_timestamp = now('timestamp')
543
+ now_datetime = now('datetime')
544
+ record = {
545
+ 'timestamp': now_timestamp,
546
+ 'datetime': now_datetime,
547
+ 'timedelta': None,
548
+ 'note': note
549
+ }
550
+
551
+ ## Not first.
552
+ if index != 0:
553
+ last_index = index - 1
554
+ last_datetime = self.record[last_index]['datetime']
555
+ record['timedelta'] = now_datetime - last_datetime
556
+
557
+ ## Record.
558
+ self.record[index] = record
559
+
560
+ return index
561
+
562
+
563
+ def report(self, title: Optional[str] = None) -> DataFrame:
564
+ """
565
+ Print and return time mark information table.
566
+
567
+ Parameters
568
+ ----------
569
+ title : Print title.
570
+ - `None`: Not print.
571
+ - `str`: Print and use this title.
572
+
573
+ Returns
574
+ -------
575
+ Time mark information table
576
+ """
577
+
578
+ # Get parameter.
579
+ record_len = len(self.record)
580
+ data = [
581
+ info.copy()
582
+ for info in self.record.values()
583
+ ]
584
+ indexes = [
585
+ index
586
+ for index in self.record
587
+ ]
588
+
589
+ # Generate report.
590
+
591
+ ## No record.
592
+ if record_len == 0:
593
+ row: RecordData = dict.fromkeys(('timestamp', 'datetime', 'timedelta', 'note'))
594
+ data = [row]
595
+ indexes = [0]
596
+
597
+ ## Add total row.
598
+ if record_len > 2:
599
+ row: RecordData = dict.fromkeys(('timestamp', 'datetime', 'timedelta', 'note'))
600
+ max_index = record_len - 1
601
+ total_timedelta = self.record[max_index]['datetime'] - self.record[0]['datetime']
602
+ row['timedelta'] = total_timedelta
603
+ data.append(row)
604
+ indexes.append('total')
605
+
606
+ ## Convert.
607
+ for row in data:
608
+ if row['timestamp'] is not None:
609
+ row['timestamp'] = str(row['timestamp'])
610
+ if row['datetime'] is not None:
611
+ row['datetime'] = str(row['datetime'])[:-3]
612
+ if row['timedelta'] is not None:
613
+ if row['timedelta'].total_seconds() == 0:
614
+ timedelta_str = '00:00:00.000'
615
+ else:
616
+ timedelta_str = str(row['timedelta'])[:-3]
617
+ timedelta_str = timedelta_str.rsplit(' ', 1)[-1]
618
+ if timedelta_str[1] == ':':
619
+ timedelta_str = '0' + timedelta_str
620
+ if row['timedelta'].days != 0:
621
+ timedelta_str = '%sday %s' % (
622
+ row['timedelta'].days,
623
+ timedelta_str
624
+ )
625
+ row['timedelta'] = timedelta_str
626
+ df_info = DataFrame(data, index=indexes)
627
+ df_info.fillna('-', inplace=True)
628
+
629
+ # Print.
630
+ if title is not None:
631
+ echo(df_info, title=title)
632
+
633
+ return df_info
634
+
635
+
636
+ @property
637
+ def total_spend(self) -> float:
638
+ """
639
+ Get total spend seconds.
640
+
641
+ Returns
642
+ -------
643
+ Total spend seconds.
644
+ """
645
+
646
+ # Break.
647
+ if len(self.record) <= 1: return 0.0
648
+
649
+ # Get parameter.
650
+ first_timestamp = self.record[0]['timestamp']
651
+ max_index = max(self.record)
652
+ last_timestamp = self.record[max_index]['timestamp']
653
+
654
+ # Calculate.
655
+ seconds = round((last_timestamp - first_timestamp) / 1000, 3)
656
+
657
+ return seconds
658
+
659
+
660
+ def __str__(self) -> str:
661
+ """
662
+ Convert to string.
663
+
664
+ Returns
665
+ -------
666
+ Converted string.
667
+ """
668
+
669
+ # Get.
670
+ report = self.report()
671
+
672
+ # Convert.
673
+ string = str(report)
674
+
675
+ return string
676
+
677
+
678
+ __call__ = mark