Commit | Line | Data |
---|---|---|
4c50ece1 CH |
1 | #!/usr/bin/env python3 |
2 | ||
3 | """ give a nice string representation for a duration | |
4 | ||
5 | Christian Herdtweck, Intra2net, November 2014 | |
6 | Licensed under GPL | |
7 | """ | |
8 | ||
9 | from datetime import timedelta | |
10 | ||
11 | # Version History | |
12 | # 24/11/14 Christian Herdtweck: started creation by copying strftimedelta | |
13 | ||
14 | ||
15 | def strftimedelta(delta, max_parts=2, only_up_to_hours=False): | |
16 | """ nice and length-adjustable text representation of a timespan | |
17 | ||
18 | will output the most significant non-zero time units, e.g. | |
19 | '2h23m50s' or '3d50s12ms' (skipping 0 minutes), or '3y63d12h' | |
20 | or for max_parts=2: '2h23m' / '3d50s' / '3y63d' | |
21 | ||
22 | Assumes that each year has 365 days, every day 24h, every hour 60 mins, ... | |
23 | not accounting for leap years / leap days, changes of timezones, etc | |
24 | ||
25 | @delta:timedelta or anything that can be converted to float; | |
26 | if numeric, this is interpreted as number of seconds | |
27 | @param max_parts: precision (i.e., length) of output (default: 2) | |
28 | set to None if always want precision down to microseconds | |
29 | @param only_up_to_hours: set to True to never give number of days/years | |
30 | (e.g. to get '40h' instead of '1d16h') | |
31 | default: False | |
32 | ||
33 | returns a string | |
34 | """ | |
35 | ||
36 | units = '\u00B5s', 'ms', 's', 'm', 'h', 'd', 'y' | |
37 | ||
38 | if (max_parts is None) or (max_parts < 1): | |
39 | max_parts = 100 | |
40 | ||
41 | # get number of seconds and sign | |
42 | if isinstance(delta, timedelta): | |
43 | seconds = delta.total_seconds() | |
44 | else: | |
45 | seconds = float(delta) | |
46 | negative = False | |
47 | if seconds < 0: | |
48 | negative = True | |
49 | seconds = abs(seconds) | |
50 | ||
51 | # split sub-second part | |
52 | seconds, second_part = divmod(seconds, 1) | |
53 | milliseconds, milli_part = divmod(second_part*1000, 1) | |
54 | microseconds = int(round(milli_part*1000)) | |
55 | seconds = int(seconds) # convert to int here --> all other variables will be int | |
56 | vals = [microseconds, int(milliseconds), ] | |
57 | ||
58 | # split longer units | |
59 | minutes = hours = days = years = 0 | |
60 | if seconds > 0: | |
61 | minutes, seconds = divmod(seconds, 60) | |
62 | if minutes > 0: | |
63 | hours, minutes = divmod(minutes, 60) | |
64 | if hours > 0 and not only_up_to_hours: | |
65 | days, hours = divmod(hours, 24) | |
66 | if days > 0: | |
67 | years, days = divmod(days, 365) | |
68 | vals.append(seconds) | |
69 | vals.append(minutes) | |
70 | vals.append(hours) | |
71 | vals.append(days) | |
72 | vals.append(years) | |
73 | ||
74 | # build string | |
75 | str_parts = [] | |
76 | if negative: | |
77 | str_parts.append('-') | |
78 | ||
79 | n_used = 0 | |
80 | for val,unit in zip( reversed(vals), reversed(units) ): | |
81 | if val == 0: | |
82 | continue | |
83 | str_parts.append(str(val)) | |
84 | str_parts.append(unit) | |
85 | n_used += 1 | |
86 | if n_used >= max_parts: | |
87 | break | |
88 | return ''.join(str_parts) | |
89 | ||
90 | #end: strftimedelta | |
91 | ||
92 | ||
93 | ||
94 | def main(): | |
95 | """ Main function, called when running file as script; currently raises a NotImplementedError | |
96 | """ | |
97 | raise NotImplementedError('nothing to run here -- only a lib!') | |
98 | #end: function main | |
99 | ||
100 | ||
101 | if __name__ == '__main__': | |
102 | main() | |
103 |