1 | """ |
---|
2 | Convert timestamps to abbreviated English text. |
---|
3 | |
---|
4 | Ported to Python 3. |
---|
5 | """ |
---|
6 | |
---|
7 | import re |
---|
8 | from datetime import timedelta |
---|
9 | |
---|
10 | HOUR = 3600 |
---|
11 | DAY = 24*3600 |
---|
12 | WEEK = 7*DAY |
---|
13 | MONTH = 30*DAY |
---|
14 | YEAR = 365*DAY |
---|
15 | |
---|
16 | def abbreviate_time(s): |
---|
17 | """ |
---|
18 | Given time in seconds (float or int) or timedelta, summarize as English by |
---|
19 | returning unicode string. |
---|
20 | """ |
---|
21 | postfix = '' |
---|
22 | if isinstance(s, timedelta): |
---|
23 | # this feels counter-intuitive that positive numbers in a |
---|
24 | # time-delta are "the past"; but if you "do math" on two |
---|
25 | # datetime instances as below, you get a "positve seconds" |
---|
26 | # timedelta instance: |
---|
27 | # a = datetime.utcnow() |
---|
28 | # time.sleep(1) |
---|
29 | # b = datetime.utcnow() |
---|
30 | # print(b - a) # 0:00:01.001203 |
---|
31 | s = s.total_seconds() |
---|
32 | if s >= 0.0: |
---|
33 | postfix = ' ago' |
---|
34 | else: |
---|
35 | postfix = ' in the future' |
---|
36 | s = -s |
---|
37 | def _plural(count, unit): |
---|
38 | count = int(count) |
---|
39 | if count == 1: |
---|
40 | return "%d %s%s" % (count, unit, postfix) |
---|
41 | return "%d %ss%s" % (count, unit, postfix) |
---|
42 | if s is None: |
---|
43 | return "unknown" |
---|
44 | if s < 120: |
---|
45 | return _plural(s, "second") |
---|
46 | if s < 3*HOUR: |
---|
47 | return _plural(s / 60, "minute") |
---|
48 | if s < 2*DAY: |
---|
49 | return _plural(s / HOUR, "hour") |
---|
50 | if s < 2*MONTH: |
---|
51 | return _plural(s / DAY, "day") |
---|
52 | if s < 4*YEAR: |
---|
53 | return _plural(s / MONTH, "month") |
---|
54 | return _plural(s / YEAR, "year") |
---|
55 | |
---|
56 | def abbreviate_space(s, SI=True): |
---|
57 | """ |
---|
58 | Given size in bytes summarize as English by returning unicode string. |
---|
59 | """ |
---|
60 | if s is None: |
---|
61 | return "unknown" |
---|
62 | if SI: |
---|
63 | U = 1000.0 |
---|
64 | isuffix = "B" |
---|
65 | else: |
---|
66 | U = 1024.0 |
---|
67 | isuffix = "iB" |
---|
68 | def r(count, suffix): |
---|
69 | return "%.2f %s%s" % (count, suffix, isuffix) |
---|
70 | |
---|
71 | if s < 1024: # 1000-1023 get emitted as bytes, even in SI mode |
---|
72 | return "%d B" % s |
---|
73 | if s < U*U: |
---|
74 | return r(s/U, "k") |
---|
75 | if s < U*U*U: |
---|
76 | return r(s/(U*U), "M") |
---|
77 | if s < U*U*U*U: |
---|
78 | return r(s/(U*U*U), "G") |
---|
79 | if s < U*U*U*U*U: |
---|
80 | return r(s/(U*U*U*U), "T") |
---|
81 | if s < U*U*U*U*U*U: |
---|
82 | return r(s/(U*U*U*U*U), "P") |
---|
83 | return r(s/(U*U*U*U*U*U), "E") |
---|
84 | |
---|
85 | def abbreviate_space_both(s): |
---|
86 | return "(%s, %s)" % (abbreviate_space(s, True), |
---|
87 | abbreviate_space(s, False)) |
---|
88 | |
---|
89 | def parse_abbreviated_size(s): |
---|
90 | if s is None or s == "": |
---|
91 | return None |
---|
92 | m = re.match(r"^(\d+)([KMGTPE]?[I]?[B]?)$", s.upper()) |
---|
93 | if not m: |
---|
94 | raise ValueError("unparseable value %s" % s) |
---|
95 | number, suffix = m.groups() |
---|
96 | if suffix.endswith("B"): |
---|
97 | suffix = suffix[:-1] |
---|
98 | multiplier = {"": 1, |
---|
99 | "I": 1, |
---|
100 | "K": 1000, |
---|
101 | "M": 1000 * 1000, |
---|
102 | "G": 1000 * 1000 * 1000, |
---|
103 | "T": 1000 * 1000 * 1000 * 1000, |
---|
104 | "P": 1000 * 1000 * 1000 * 1000 * 1000, |
---|
105 | "E": 1000 * 1000 * 1000 * 1000 * 1000 * 1000, |
---|
106 | "KI": 1024, |
---|
107 | "MI": 1024 * 1024, |
---|
108 | "GI": 1024 * 1024 * 1024, |
---|
109 | "TI": 1024 * 1024 * 1024 * 1024, |
---|
110 | "PI": 1024 * 1024 * 1024 * 1024 * 1024, |
---|
111 | "EI": 1024 * 1024 * 1024 * 1024 * 1024 * 1024, |
---|
112 | }[suffix] |
---|
113 | return int(number) * multiplier |
---|