libdev 0.93__tar.gz → 0.95__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.
Files changed (41) hide show
  1. {libdev-0.93 → libdev-0.95}/PKG-INFO +1 -1
  2. {libdev-0.93 → libdev-0.95}/libdev/__init__.py +1 -1
  3. libdev-0.95/libdev/num.py +437 -0
  4. {libdev-0.93 → libdev-0.95}/libdev/time.py +27 -0
  5. {libdev-0.93 → libdev-0.95}/libdev.egg-info/PKG-INFO +1 -1
  6. {libdev-0.93 → libdev-0.95}/tests/test_num.py +76 -0
  7. {libdev-0.93 → libdev-0.95}/tests/test_time.py +9 -0
  8. libdev-0.93/libdev/num.py +0 -330
  9. {libdev-0.93 → libdev-0.95}/LICENSE +0 -0
  10. {libdev-0.93 → libdev-0.95}/README.md +0 -0
  11. {libdev-0.93 → libdev-0.95}/libdev/cfg.py +0 -0
  12. {libdev-0.93 → libdev-0.95}/libdev/check.py +0 -0
  13. {libdev-0.93 → libdev-0.95}/libdev/codes.py +0 -0
  14. {libdev-0.93 → libdev-0.95}/libdev/crypt.py +0 -0
  15. {libdev-0.93 → libdev-0.95}/libdev/dev.py +0 -0
  16. {libdev-0.93 → libdev-0.95}/libdev/doc.py +0 -0
  17. {libdev-0.93 → libdev-0.95}/libdev/fin.py +0 -0
  18. {libdev-0.93 → libdev-0.95}/libdev/gen.py +0 -0
  19. {libdev-0.93 → libdev-0.95}/libdev/img.py +0 -0
  20. {libdev-0.93 → libdev-0.95}/libdev/lang.py +0 -0
  21. {libdev-0.93 → libdev-0.95}/libdev/log.py +0 -0
  22. {libdev-0.93 → libdev-0.95}/libdev/req.py +0 -0
  23. {libdev-0.93 → libdev-0.95}/libdev/s3.py +0 -0
  24. {libdev-0.93 → libdev-0.95}/libdev.egg-info/SOURCES.txt +0 -0
  25. {libdev-0.93 → libdev-0.95}/libdev.egg-info/dependency_links.txt +0 -0
  26. {libdev-0.93 → libdev-0.95}/libdev.egg-info/requires.txt +0 -0
  27. {libdev-0.93 → libdev-0.95}/libdev.egg-info/top_level.txt +0 -0
  28. {libdev-0.93 → libdev-0.95}/setup.cfg +0 -0
  29. {libdev-0.93 → libdev-0.95}/setup.py +0 -0
  30. {libdev-0.93 → libdev-0.95}/tests/test_cfg.py +0 -0
  31. {libdev-0.93 → libdev-0.95}/tests/test_check.py +0 -0
  32. {libdev-0.93 → libdev-0.95}/tests/test_codes.py +0 -0
  33. {libdev-0.93 → libdev-0.95}/tests/test_crypt.py +0 -0
  34. {libdev-0.93 → libdev-0.95}/tests/test_dev.py +0 -0
  35. {libdev-0.93 → libdev-0.95}/tests/test_doc.py +0 -0
  36. {libdev-0.93 → libdev-0.95}/tests/test_gen.py +0 -0
  37. {libdev-0.93 → libdev-0.95}/tests/test_img.py +0 -0
  38. {libdev-0.93 → libdev-0.95}/tests/test_lang.py +0 -0
  39. {libdev-0.93 → libdev-0.95}/tests/test_log.py +0 -0
  40. {libdev-0.93 → libdev-0.95}/tests/test_req.py +0 -0
  41. {libdev-0.93 → libdev-0.95}/tests/test_s3.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libdev
3
- Version: 0.93
3
+ Version: 0.95
4
4
  Summary: Set of standard functions for development
5
5
  Home-page: https://github.com/chilleco/lib
6
6
  Author: Alex Poloz
@@ -2,6 +2,6 @@
2
2
  Initializing the Python package
3
3
  """
4
4
 
5
- __version__ = "0.93"
5
+ __version__ = "0.95"
6
6
 
7
7
  __all__ = ("__version__",)
@@ -0,0 +1,437 @@
1
+ """
2
+ Numbers functionality
3
+ """
4
+
5
+ import re
6
+ import math
7
+ from decimal import Decimal, InvalidOperation
8
+
9
+
10
+ _SUBSCRIPTS = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
11
+
12
+
13
+ def is_float(value: str) -> bool:
14
+ """Check value for float"""
15
+
16
+ try:
17
+ float(value)
18
+ except (ValueError, TypeError):
19
+ return False
20
+
21
+ return True
22
+
23
+
24
+ def to_num(value) -> bool:
25
+ """Convert value to int or float"""
26
+
27
+ if value is None:
28
+ return None
29
+
30
+ if isinstance(value, str):
31
+ value = float(value.strip())
32
+
33
+ if not value % 1:
34
+ value = int(value)
35
+
36
+ return value
37
+
38
+
39
+ def to_int(value) -> int:
40
+ """Choose only decimal"""
41
+
42
+ if not value:
43
+ return 0
44
+
45
+ return int(re.sub(r"\D", "", str(value)))
46
+
47
+
48
+ def get_float(value) -> list:
49
+ """Get a list of floats"""
50
+
51
+ if value is None:
52
+ return []
53
+
54
+ numbers = re.findall(r"[-+]?\d*\.\d+|[-+]?\d+", value)
55
+ return [float(number) for number in numbers]
56
+
57
+
58
+ def find_decimals(value):
59
+ """Get count of decimal"""
60
+
61
+ if isinstance(value, str):
62
+ while value[-1] == "0":
63
+ value = value[:-1]
64
+
65
+ return abs(Decimal(str(value)).as_tuple().exponent)
66
+
67
+
68
+ def get_whole(value):
69
+ """Get whole view of a number"""
70
+
71
+ if isinstance(value, int) or (isinstance(value, str) and "." not in value):
72
+ # NOTE: to remove 0 in the start of the string
73
+ return str(int(value))
74
+
75
+ # NOTE: float for add . to int & support str
76
+ value = float(value)
77
+
78
+ # NOTE: to avoid the exponential form of the number
79
+ return f"{value:.{find_decimals(value)}f}"
80
+
81
+
82
+ def simplify_value(value, decimals=4):
83
+ """Get the significant part of a number"""
84
+
85
+ if value is None:
86
+ return None
87
+
88
+ value = get_whole(value)
89
+ if "." not in value:
90
+ value += "."
91
+
92
+ whole, fractional = value.split(".")
93
+
94
+ if value[0] == "-":
95
+ sign = "-"
96
+ whole = whole[1:]
97
+ else:
98
+ sign = ""
99
+
100
+ if whole != "0":
101
+ digit = len(whole)
102
+ value = whole + "." + fractional[: max(0, decimals - digit)]
103
+
104
+ else:
105
+ offset = 0
106
+ while fractional and fractional[0] == "0":
107
+ offset += 1
108
+ fractional = fractional[1:]
109
+
110
+ value = "0." + "0" * offset + fractional[:decimals]
111
+
112
+ while value[-1] == "0":
113
+ value = value[:-1]
114
+
115
+ if value[-1] == ".":
116
+ value = value[:-1]
117
+
118
+ return sign + value
119
+
120
+
121
+ def pretty(
122
+ value,
123
+ decimals=None,
124
+ sign=False,
125
+ symbol="’",
126
+ zeros=4,
127
+ compress=None,
128
+ ):
129
+ """Decorate the number beautifully"""
130
+
131
+ if value is None:
132
+ return None
133
+
134
+ # Handle decimals parameter first (takes precedence)
135
+ if decimals is not None:
136
+ # Use the original decimals logic for backward compatibility
137
+ s = to_plain(abs(value))
138
+ if "." in s:
139
+ int_part = s.split(".", 1)[0]
140
+ cur = len(int_part)
141
+ target_decimals = max(0, decimals - cur)
142
+ # Apply rounding first
143
+ rounded_value = round(float(value), target_decimals)
144
+ # If target_decimals is 0, convert to int for proper formatting
145
+ if target_decimals == 0:
146
+ rounded_value = int(rounded_value)
147
+ # Then use compress_zeros without round parameter
148
+ data = compress_zeros(rounded_value, zeros=zeros)
149
+ else:
150
+ data = compress_zeros(value, zeros=zeros)
151
+ elif zeros is None and compress is None:
152
+ # No compression or special formatting requested, use plain representation
153
+ data = to_plain(value)
154
+ else:
155
+ # Use compress_zeros with specified parameters
156
+ compress_zeros_args = {}
157
+
158
+ if zeros is not None:
159
+ compress_zeros_args["zeros"] = zeros
160
+
161
+ if compress is not None:
162
+ compress_zeros_args["round"] = compress
163
+
164
+ data = compress_zeros(value, **compress_zeros_args)
165
+
166
+ if data == "0":
167
+ return "0"
168
+
169
+ # Remove trailing zeros after decimal point for cleaner formatting
170
+ if "." in data and data.rsplit(".", maxsplit=1)[-1] == "0":
171
+ data = data.split(".", maxsplit=1)[0]
172
+
173
+ if symbol:
174
+ data = add_radix(data, symbol)
175
+
176
+ if sign:
177
+ if data[0] != "-":
178
+ data = "+" + data
179
+
180
+ return data
181
+
182
+
183
+ def add_sign(value):
184
+ """Add sign to a number"""
185
+
186
+ if value is None:
187
+ return None
188
+
189
+ sign = ""
190
+
191
+ if float(value) > 0:
192
+ sign = "+"
193
+ elif value == 0:
194
+ value = abs(value)
195
+
196
+ return f"{sign}{get_whole(value)}"
197
+
198
+
199
+ def add_radix(value, symbol="’"):
200
+ """Add radix to a number"""
201
+
202
+ if value is None:
203
+ return None
204
+
205
+ value = str(value)
206
+
207
+ if "." in value:
208
+ integer, fractional = value.split(".")
209
+ else:
210
+ integer = value
211
+ fractional = ""
212
+
213
+ if integer[0] == "-":
214
+ sign = "-"
215
+ integer = integer[1:]
216
+ # elif integer[0] == '+':
217
+ # sign = '+'
218
+ # integer = integer[1:]
219
+ else:
220
+ sign = ""
221
+
222
+ data = ""
223
+ ind = 0
224
+ for i in integer[::-1]:
225
+ if ind and ind % 3 == 0:
226
+ data = symbol + data
227
+ ind += 1
228
+ data = i + data
229
+
230
+ data = sign + data
231
+ if fractional:
232
+ data += "." + fractional
233
+
234
+ return data
235
+
236
+
237
+ def mul(x, y):
238
+ """Multiply fractions correctly"""
239
+ if x is None or y is None:
240
+ return None
241
+ return float(Decimal(str(x)) * Decimal(str(y)))
242
+
243
+
244
+ def div(x, y):
245
+ """Divide fractions correctly"""
246
+ if x is None or y is None:
247
+ return None
248
+ return float(Decimal(str(x)) / Decimal(str(y)))
249
+
250
+
251
+ def add(x, y):
252
+ """Subtract fractions correctly"""
253
+ if x is None or y is None:
254
+ return None
255
+ return float(Decimal(str(x)) + Decimal(str(y)))
256
+
257
+
258
+ def sub(x, y):
259
+ """Subtract fractions correctly"""
260
+ if x is None or y is None:
261
+ return None
262
+ return float(Decimal(str(x)) - Decimal(str(y)))
263
+
264
+
265
+ def to_step(value, step=1, side=False):
266
+ """Change value step"""
267
+
268
+ if value is None:
269
+ return None
270
+
271
+ value = div(value, step)
272
+ if side:
273
+ value = math.ceil(value)
274
+ else:
275
+ value = math.floor(value)
276
+ value = mul(value, step)
277
+
278
+ if step >= 1:
279
+ value = int(value)
280
+
281
+ return value
282
+
283
+
284
+ def to_plain(value) -> str:
285
+ if value is None:
286
+ return None
287
+ try:
288
+ if isinstance(value, str):
289
+ d = Decimal(value)
290
+ elif isinstance(value, float):
291
+ d = Decimal(str(value))
292
+ else:
293
+ d = Decimal(value)
294
+
295
+ s = format(d.normalize(), "f")
296
+
297
+ if "." in s:
298
+ s = s.rstrip("0").rstrip(".")
299
+ if s == "-0":
300
+ s = "0"
301
+ return s
302
+ except (InvalidOperation, ValueError, TypeError):
303
+ return str(value)
304
+
305
+
306
+ def _round_to_decimals(x, decimals):
307
+ """Helper function to round to specified decimal places"""
308
+ if decimals <= 0:
309
+ return float(int(x))
310
+ return round(float(x), decimals)
311
+
312
+
313
+ def compress_zeros(x, zeros=2, round=None) -> str:
314
+ """
315
+ 0.000012 -> '0.0₄12'
316
+ 1.000045 -> '1.0₄45'
317
+ round: number of digits after the zero block (rounds).
318
+ zeros: minimum count of consecutive zeros to compress (default: 2).
319
+ """
320
+
321
+ if x is None:
322
+ return None
323
+
324
+ # Store original string representation for rounding calculations
325
+ original_str = None
326
+ if isinstance(x, str):
327
+ original_str = x.strip()
328
+ x = original_str
329
+ # Remove trailing zeros from string
330
+ if "." in x:
331
+ x = x.rstrip("0").rstrip(".")
332
+ # Convert to appropriate numeric type
333
+ try:
334
+ if "." in x:
335
+ x = float(x)
336
+ else:
337
+ x = int(x)
338
+ except ValueError:
339
+ return str(x)
340
+
341
+ # Determine if original was float or int to preserve format
342
+ is_float_type = isinstance(x, float) or (isinstance(x, str) and "." in str(x))
343
+
344
+ # Handle rounding if specified
345
+ if round is not None:
346
+ # For rounding, use original string if available, otherwise convert to plain string
347
+ if original_str and "." in original_str:
348
+ s = original_str.lstrip("-")
349
+ else:
350
+ # Use to_plain to avoid scientific notation
351
+ s = to_plain(abs(x))
352
+
353
+ if "." in s:
354
+ int_part, frac_part = s.split(".")
355
+ # Count leading zeros in fractional part
356
+ leading_zeros = 0
357
+ for c in frac_part:
358
+ if c == "0":
359
+ leading_zeros += 1
360
+ else:
361
+ break
362
+
363
+ # Count trailing zeros in fractional part
364
+ trailing_zeros = 0
365
+ for c in reversed(frac_part):
366
+ if c == "0":
367
+ trailing_zeros += 1
368
+ else:
369
+ break
370
+
371
+ # Apply rounding logic
372
+ if leading_zeros > 0:
373
+ # If there are leading zeros, round after them (regardless of compression)
374
+ total_decimals = leading_zeros + round
375
+ x = _round_to_decimals(x, total_decimals)
376
+ else:
377
+ # No leading zeros, apply normal rounding
378
+ x = _round_to_decimals(x, round)
379
+
380
+ # Convert to string representation
381
+ if isinstance(x, int) and not is_float_type:
382
+ s = str(x)
383
+ else:
384
+ # For floats, use format that preserves trailing decimals when needed
385
+ if x == int(x) and is_float_type:
386
+ s = f"{int(x)}.0"
387
+ else:
388
+ s = str(float(x))
389
+ # Remove scientific notation if present
390
+ if "e" in s.lower():
391
+ s = f"{float(x):.15f}".rstrip("0")
392
+ if s.endswith("."):
393
+ s += "0"
394
+
395
+ # Handle negative sign
396
+ negative = s.startswith("-")
397
+ if negative:
398
+ s = s[1:]
399
+
400
+ # Process compression
401
+ if "." not in s:
402
+ result = s
403
+ else:
404
+ int_part, frac_part = s.split(".")
405
+
406
+ # Compress leading zeros in fractional part
407
+ leading_zeros = 0
408
+ for c in frac_part:
409
+ if c == "0":
410
+ leading_zeros += 1
411
+ else:
412
+ break
413
+
414
+ # Compress trailing zeros in fractional part
415
+ trailing_zeros = 0
416
+ for c in reversed(frac_part):
417
+ if c == "0":
418
+ trailing_zeros += 1
419
+ else:
420
+ break
421
+
422
+ # Apply compression
423
+ if leading_zeros >= zeros:
424
+ # Compress leading zeros
425
+ remaining_frac = frac_part[leading_zeros:]
426
+ result = f"{int_part}.0{str(leading_zeros).translate(_SUBSCRIPTS)}{remaining_frac}"
427
+ elif trailing_zeros >= zeros and leading_zeros == 0:
428
+ # Compress trailing zeros (but not if there are leading zeros)
429
+ remaining_frac = frac_part[:-trailing_zeros]
430
+ if remaining_frac:
431
+ result = f"{int_part}.{remaining_frac}0{str(trailing_zeros).translate(_SUBSCRIPTS)}"
432
+ else:
433
+ result = f"{int_part}.0{str(trailing_zeros).translate(_SUBSCRIPTS)}"
434
+ else:
435
+ result = s
436
+
437
+ return f"-{result}" if negative else result
@@ -300,6 +300,27 @@ def get_month_start(timestamp=None, tz=0):
300
300
  return int(start_month.timestamp())
301
301
 
302
302
 
303
+ def get_week_start(timestamp=None, tz=0):
304
+ """
305
+ Get the start of the week (midnight on Monday) for a given timestamp in a specified timezone.
306
+
307
+ Args:
308
+ timestamp (float): The original timestamp (in seconds since epoch). Defaults to the current time if None.
309
+ tz (int): The timezone offset in hours (e.g., 3 for UTC+3).
310
+
311
+ Returns:
312
+ float: The timestamp for the start of the week (Monday at midnight) in the specified timezone.
313
+ """
314
+ if timestamp is None:
315
+ timestamp = time.time()
316
+ dt_local = datetime.datetime.fromtimestamp(timestamp, tz=to_tz(tz))
317
+ # Calculate days to subtract to get to Monday (weekday() returns 0 for Monday, 6 for Sunday)
318
+ days_since_monday = dt_local.weekday()
319
+ start_week = dt_local - datetime.timedelta(days=days_since_monday)
320
+ start_week = start_week.replace(hour=0, minute=0, second=0, microsecond=0)
321
+ return int(start_week.timestamp())
322
+
323
+
303
324
  def get_next_day(timestamp=None, tz=0):
304
325
  """
305
326
  Get the start of the next day (midnight) for a given timestamp in a specified timezone.
@@ -359,3 +380,9 @@ def get_next_month(timestamp=None, tz=0):
359
380
  )
360
381
 
361
382
  return int(next_month.timestamp())
383
+
384
+
385
+ def get_delta_days(start, end, digits=0):
386
+ """Get the number of days between two timestamps"""
387
+ delta = round((end - start) / (24 * 60 * 60), digits)
388
+ return delta if delta % 1 else int(delta)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libdev
3
- Version: 0.93
3
+ Version: 0.95
4
4
  Summary: Set of standard functions for development
5
5
  Home-page: https://github.com/chilleco/lib
6
6
  Author: Alex Poloz
@@ -11,6 +11,7 @@ from libdev.num import (
11
11
  to_step,
12
12
  add,
13
13
  pretty,
14
+ to_plain,
14
15
  compress_zeros,
15
16
  )
16
17
 
@@ -156,6 +157,69 @@ def test_pretty():
156
157
  assert pretty(123.456, 1) == "123"
157
158
  assert pretty(123.456, 1, True) == "+123"
158
159
  assert pretty(12345.6, 3, True) == "+12’346"
160
+ assert pretty(-0.000000235235, zeros=None, compress=None) == "-0.000000235235"
161
+ assert pretty(-0.000000235235, zeros=4, compress=2) == "-0.0₆24"
162
+
163
+
164
+ def test_to_plain():
165
+ assert to_plain(None) == None
166
+ assert to_plain(0) == "0"
167
+ # assert to_plain(0.0) == "0.0"
168
+ # assert to_plain(1.0) == "1.0"
169
+ assert to_plain(1.1) == "1.1"
170
+ assert to_plain(0.000000235235) == "0.000000235235"
171
+ assert to_plain(-0.000000235235) == "-0.000000235235"
172
+ assert to_plain("0.000000235235") == "0.000000235235"
173
+ assert to_plain(2.35235e-07) == "0.000000235235"
174
+ assert to_plain("1e-12") == "0.000000000001"
175
+ assert to_plain("123.4500") == "123.45"
176
+
177
+
178
+ def test_pretty_with_compression():
179
+ # Test pretty function with compression parameters
180
+
181
+ # Test zeros parameter (minimum zeros to compress)
182
+ assert pretty(0.00012, zeros=2) == "0.0₃12" # Default behavior, compress >=2 zeros
183
+ assert pretty(0.01, zeros=1) == "0.0₁1" # Compress single zero with zeros=1
184
+ assert pretty(0.01, zeros=3) == "0.01" # Don't compress with zeros=3 (only 1 zero)
185
+ assert pretty(0.0001, zeros=3) == "0.0₃1" # Compress with zeros=3 (3 zeros)
186
+
187
+ # Test compress parameter (round after zero block)
188
+ assert (
189
+ pretty(0.00012345, zeros=2, compress=2) == "0.0₃12"
190
+ ) # Round to 2 digits after zeros
191
+ assert (
192
+ pretty(0.00012345, zeros=4, compress=4) == "0.0001234"
193
+ ) # No compression (3 < 4 zeros), 4 digits after 3 zeros
194
+ assert pretty(0.00012345, zeros=4, compress=8) == "0.00012345"
195
+ assert (
196
+ pretty(0.00012345, zeros=2, compress=3) == "0.0₃123"
197
+ ) # Round to 3 digits after zeros
198
+ assert (
199
+ pretty(-0.0010959999999999997522, compress=3) == "-0.0011"
200
+ ) # Negative with rounding
201
+
202
+ # Test both parameters together
203
+ assert (
204
+ pretty("0.0000012345", zeros=4, compress=2) == "0.0₅12"
205
+ ) # 5 zeros, round to 2 digits
206
+ assert (
207
+ pretty("0.00012345", zeros=4, compress=2) == "0.00012"
208
+ ) # Only 3 zeros, no compression, 2 digits after 3 zeros
209
+
210
+ # Test that other pretty parameters still work with compression
211
+ assert pretty(0.00012, zeros=2, sign=True) == "+0.0₃12" # With sign
212
+ assert pretty(12000.00012, zeros=2, symbol="'") == "12'000.0₃12" # With radix
213
+ assert (
214
+ pretty(-0.00012, zeros=2, sign=True, symbol="'") == "-0.0₃12"
215
+ ) # Negative with sign and radix
216
+
217
+ # Test edge cases
218
+ assert pretty(0, zeros=1) == "0" # Zero value
219
+ assert pretty(None, zeros=2) == None # None value
220
+ assert (
221
+ pretty(1.0, zeros=2) == "1"
222
+ ) # No fractional zeros to compress, trailing zero removed for clean formatting
159
223
 
160
224
 
161
225
  def test_compress_zeros():
@@ -170,4 +234,16 @@ def test_compress_zeros():
170
234
  assert compress_zeros(0.0123) == "0.0123"
171
235
  assert compress_zeros(0.0123456, round=3) == "0.0123"
172
236
  assert compress_zeros(1.000045) == "1.0₄45"
237
+ assert compress_zeros(0.0010859999999999997522, round=3) == "0.0₂109"
173
238
  assert compress_zeros(-0.0010959999999999997522, round=3) == "-0.0₂11"
239
+ assert compress_zeros("-0.012300") == "-0.0123"
240
+ assert compress_zeros(0.01, zeros=1) == "0.0₁1"
241
+ assert compress_zeros(0.001, zeros=1) == "0.0₂1"
242
+ assert compress_zeros("1.10", zeros=1) == "1.1"
243
+ assert compress_zeros(0.01, zeros=3) == "0.01"
244
+ assert compress_zeros(0.001, zeros=3) == "0.001"
245
+ assert compress_zeros(0.0001, zeros=3) == "0.0₃1"
246
+ assert compress_zeros(0.00001, zeros=3) == "0.0₄1"
247
+ assert compress_zeros(0.0000012, zeros=1) == "0.0₅12"
248
+ assert compress_zeros(0.0000012, zeros=5) == "0.0₅12"
249
+ assert compress_zeros(0.0000012, zeros=6) == "0.0000012"
@@ -15,6 +15,7 @@ from libdev.time import (
15
15
  get_month_start,
16
16
  get_next_day,
17
17
  get_next_month,
18
+ get_delta_days,
18
19
  )
19
20
 
20
21
 
@@ -128,3 +129,11 @@ def test_get_next_day():
128
129
  def test_get_next_month():
129
130
  assert get_next_month(1703980800) == 1704067200
130
131
  assert get_next_month(1703970000, tz=3) == 1704056400
132
+
133
+
134
+ def test_get_delta_days():
135
+ assert get_delta_days(1756670400, 1759262400) == 30
136
+ assert isinstance(get_delta_days(1756670400, 1759262400, 0), int)
137
+ assert isinstance(get_delta_days(1756670400, 1759262401, 1), int)
138
+ assert isinstance(get_delta_days(1756670400, 1759262401, 0), int)
139
+ assert isinstance(get_delta_days(1756670400, 1759263511, 2), float)
libdev-0.93/libdev/num.py DELETED
@@ -1,330 +0,0 @@
1
- """
2
- Numbers functionality
3
- """
4
-
5
- import re
6
- import math
7
- from decimal import Decimal, ROUND_HALF_UP
8
-
9
-
10
- _SUBSCRIPTS = {
11
- "0": "₀",
12
- "1": "₁",
13
- "2": "₂",
14
- "3": "₃",
15
- "4": "₄",
16
- "5": "₅",
17
- "6": "₆",
18
- "7": "₇",
19
- "8": "₈",
20
- "9": "₉",
21
- }
22
-
23
-
24
- def is_float(value: str) -> bool:
25
- """Check value for float"""
26
-
27
- try:
28
- float(value)
29
- except (ValueError, TypeError):
30
- return False
31
-
32
- return True
33
-
34
-
35
- def to_num(value) -> bool:
36
- """Convert value to int or float"""
37
-
38
- if value is None:
39
- return None
40
-
41
- if isinstance(value, str):
42
- value = float(value.strip())
43
-
44
- if not value % 1:
45
- value = int(value)
46
-
47
- return value
48
-
49
-
50
- def to_int(value) -> int:
51
- """Choose only decimal"""
52
-
53
- if not value:
54
- return 0
55
-
56
- return int(re.sub(r"\D", "", str(value)))
57
-
58
-
59
- def get_float(value) -> list:
60
- """Get a list of floats"""
61
-
62
- if value is None:
63
- return []
64
-
65
- numbers = re.findall(r"[-+]?\d*\.\d+|[-+]?\d+", value)
66
- return [float(number) for number in numbers]
67
-
68
-
69
- def find_decimals(value):
70
- """Get count of decimal"""
71
-
72
- if isinstance(value, str):
73
- while value[-1] == "0":
74
- value = value[:-1]
75
-
76
- return abs(Decimal(str(value)).as_tuple().exponent)
77
-
78
-
79
- def get_whole(value):
80
- """Get whole view of a number"""
81
-
82
- if isinstance(value, int) or (isinstance(value, str) and "." not in value):
83
- # NOTE: to remove 0 in the start of the string
84
- return str(int(value))
85
-
86
- # NOTE: float for add . to int & support str
87
- value = float(value)
88
-
89
- # NOTE: to avoid the exponential form of the number
90
- return f"{value:.{find_decimals(value)}f}"
91
-
92
-
93
- def simplify_value(value, decimals=4):
94
- """Get the significant part of a number"""
95
-
96
- if value is None:
97
- return None
98
-
99
- value = get_whole(value)
100
- if "." not in value:
101
- value += "."
102
-
103
- whole, fractional = value.split(".")
104
-
105
- if value[0] == "-":
106
- sign = "-"
107
- whole = whole[1:]
108
- else:
109
- sign = ""
110
-
111
- if whole != "0":
112
- digit = len(whole)
113
- value = whole + "." + fractional[: max(0, decimals - digit)]
114
-
115
- else:
116
- offset = 0
117
- while fractional and fractional[0] == "0":
118
- offset += 1
119
- fractional = fractional[1:]
120
-
121
- value = "0." + "0" * offset + fractional[:decimals]
122
-
123
- while value[-1] == "0":
124
- value = value[:-1]
125
-
126
- if value[-1] == ".":
127
- value = value[:-1]
128
-
129
- return sign + value
130
-
131
-
132
- def pretty(value, decimals=None, sign=False, symbol="’"):
133
- """Decorate the number beautifully"""
134
-
135
- if value is None:
136
- return None
137
-
138
- data = str(float(value))
139
-
140
- if decimals is not None:
141
- cur = len(data.split(".", maxsplit=1)[0])
142
- data = str(round(value, max(0, decimals - cur)))
143
-
144
- if data.rsplit(".", maxsplit=1)[-1] == "0":
145
- data = data.split(".", maxsplit=1)[0]
146
-
147
- if data == "0":
148
- return "0"
149
-
150
- if symbol:
151
- data = add_radix(data, symbol)
152
-
153
- if sign:
154
- if data[0] != "-":
155
- data = "+" + data
156
-
157
- return data
158
-
159
-
160
- def add_sign(value):
161
- """Add sign to a number"""
162
-
163
- if value is None:
164
- return None
165
-
166
- sign = ""
167
-
168
- if float(value) > 0:
169
- sign = "+"
170
- elif value == 0:
171
- value = abs(value)
172
-
173
- return f"{sign}{get_whole(value)}"
174
-
175
-
176
- def add_radix(value, symbol="’"):
177
- """Add radix to a number"""
178
-
179
- if value is None:
180
- return None
181
-
182
- value = str(value)
183
-
184
- if "." in value:
185
- integer, fractional = value.split(".")
186
- else:
187
- integer = value
188
- fractional = ""
189
-
190
- if integer[0] == "-":
191
- sign = "-"
192
- integer = integer[1:]
193
- # elif integer[0] == '+':
194
- # sign = '+'
195
- # integer = integer[1:]
196
- else:
197
- sign = ""
198
-
199
- data = ""
200
- ind = 0
201
- for i in integer[::-1]:
202
- if ind and ind % 3 == 0:
203
- data = symbol + data
204
- ind += 1
205
- data = i + data
206
-
207
- data = sign + data
208
- if fractional:
209
- data += "." + fractional
210
-
211
- return data
212
-
213
-
214
- def mul(x, y):
215
- """Multiply fractions correctly"""
216
- if x is None or y is None:
217
- return None
218
- return float(Decimal(str(x)) * Decimal(str(y)))
219
-
220
-
221
- def div(x, y):
222
- """Divide fractions correctly"""
223
- if x is None or y is None:
224
- return None
225
- return float(Decimal(str(x)) / Decimal(str(y)))
226
-
227
-
228
- def add(x, y):
229
- """Subtract fractions correctly"""
230
- if x is None or y is None:
231
- return None
232
- return float(Decimal(str(x)) + Decimal(str(y)))
233
-
234
-
235
- def sub(x, y):
236
- """Subtract fractions correctly"""
237
- if x is None or y is None:
238
- return None
239
- return float(Decimal(str(x)) - Decimal(str(y)))
240
-
241
-
242
- def to_step(value, step=1, side=False):
243
- """Change value step"""
244
-
245
- if value is None:
246
- return None
247
-
248
- value = div(value, step)
249
- if side:
250
- value = math.ceil(value)
251
- else:
252
- value = math.floor(value)
253
- value = mul(value, step)
254
-
255
- if step >= 1:
256
- value = int(value)
257
-
258
- return value
259
-
260
-
261
- def _to_subscript(n: int) -> str:
262
- """Convert an integer n to a string of subscript digits."""
263
- return "".join(_SUBSCRIPTS[d] for d in str(n))
264
-
265
-
266
- def compress_zeros(x: int | float, round: int | None = None) -> str | None:
267
- """
268
- Given a float or decimal‐string x, return a string where
269
- runs of leading zeros in the fraction are shown as:
270
- one '0' plus a subscript count of the zeros.
271
- If `round` is provided, the remaining digits after the zeros
272
- are rounded to that many places.
273
-
274
- Examples:
275
- compress_zeros(0.00012) -> '0.0₃12'
276
- compress_zeros(0.00012, round=2) -> '0.0₃12'
277
- compress_zeros(0.0123) -> '0.0123' (only one leading zero, so unchanged)
278
- compress_zeros(0.0123456, round=3)-> '0.0123'
279
- compress_zeros(1.000045) -> '1.0₄45'
280
- compress_zeros(-0.0010959999999999997522, round=3)
281
- -> '-0.0₂11'
282
- """
283
-
284
- if x is None:
285
- return None
286
-
287
- dec_x = Decimal(str(x))
288
- s = format(dec_x, "f")
289
-
290
- # no fractional part
291
- if "." not in s:
292
- return s
293
-
294
- int_part, frac = s.split(".", 1)
295
-
296
- # count leading zeros in the fractional part
297
- zero_run = len(frac) - len(frac.lstrip("0"))
298
-
299
- # If rounding is requested, do it first
300
- if round is not None:
301
- # ensure at least one zero is counted for quantization
302
- places = max(zero_run, 1) + round
303
- quant = Decimal(f"1e-{places}")
304
- dec_q = dec_x.quantize(quant, rounding=ROUND_HALF_UP)
305
- s_q = format(dec_q, "f")
306
-
307
- # if rounding eliminated fractional part
308
- if "." not in s_q:
309
- return s_q
310
-
311
- int_part_q, frac_q = s_q.split(".", 1)
312
- # for zero_run == 0 or 1, we just return the rounded string
313
- if zero_run <= 1:
314
- return s_q
315
-
316
- # strip any trailing zeros, then compress
317
- frac_q = frac_q.rstrip("0")
318
- if not frac_q:
319
- return int_part_q
320
- tail = frac_q[zero_run:]
321
- return f"{int_part_q}.0{_to_subscript(zero_run)}{tail}"
322
-
323
- # No rounding: only compress if more than one leading zero
324
- if zero_run <= 1:
325
- return s
326
-
327
- tail = frac[zero_run:]
328
-
329
- # build compressed form
330
- return f"{int_part}.0{_to_subscript(zero_run)}{tail}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes