format_en.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright 2017 Mycroft AI Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17 
18 from mycroft.util.lang.format_common import convert_to_mixed_fraction
19 from mycroft.util.log import LOG
20 from mycroft.util.lang.common_data_en import _NUM_STRING_EN, \
21  _FRACTION_STRING_EN, _LONG_SCALE_EN, _SHORT_SCALE_EN
22 
23 
24 def nice_number_en(number, speech, denominators):
25  """ English helper for nice_number
26 
27  This function formats a float to human understandable functions. Like
28  4.5 becomes "4 and a half" for speech and "4 1/2" for text
29 
30  Args:
31  number (int or float): the float to format
32  speech (bool): format for speech (True) or display (False)
33  denominators (iter of ints): denominators to use, default [1 .. 20]
34  Returns:
35  (str): The formatted string.
36  """
37 
38  result = convert_to_mixed_fraction(number, denominators)
39  if not result:
40  # Give up, just represent as a 3 decimal number
41  return str(round(number, 3))
42 
43  whole, num, den = result
44 
45  if not speech:
46  if num == 0:
47  # TODO: Number grouping? E.g. "1,000,000"
48  return str(whole)
49  else:
50  return '{} {}/{}'.format(whole, num, den)
51 
52  if num == 0:
53  return str(whole)
54  den_str = _FRACTION_STRING_EN[den]
55  if whole == 0:
56  if num == 1:
57  return_string = 'a {}'.format(den_str)
58  else:
59  return_string = '{} {}'.format(num, den_str)
60  elif num == 1:
61  return_string = '{} and a {}'.format(whole, den_str)
62  else:
63  return_string = '{} and {} {}'.format(whole, num, den_str)
64  if num > 1:
65  return_string += 's'
66  return return_string
67 
68 
69 def pronounce_number_en(num, places=2, short_scale=True, scientific=False):
70  """
71  Convert a number to its spoken equivalent
72 
73  For example, '5.2' would return 'five point two'
74 
75  Args:
76  num(float or int): the number to pronounce (under 100)
77  places(int): maximum decimal places to speak
78  short_scale (bool) : use short (True) or long scale (False)
79  https://en.wikipedia.org/wiki/Names_of_large_numbers
80  scientific (bool): pronounce in scientific notation
81  Returns:
82  (str): The pronounced number
83  """
84  if scientific:
85  number = '%E' % num
86  n, power = number.replace("+", "").split("E")
87  power = int(power)
88  if power != 0:
89  # This handles negatives of powers separately from the normal
90  # handling since each call disables the scientific flag
91  return '{}{} times ten to the power of {}{}'.format(
92  'negative ' if float(n) < 0 else '',
93  pronounce_number_en(abs(float(n)), places, short_scale, False),
94  'negative ' if power < 0 else '',
95  pronounce_number_en(abs(power), places, short_scale, False))
96  if short_scale:
97  number_names = _NUM_STRING_EN.copy()
98  number_names.update(_SHORT_SCALE_EN)
99  else:
100  number_names = _NUM_STRING_EN.copy()
101  number_names.update(_LONG_SCALE_EN)
102 
103  digits = [number_names[n] for n in range(0, 20)]
104 
105  tens = [number_names[n] for n in range(10, 100, 10)]
106 
107  if short_scale:
108  hundreds = [_SHORT_SCALE_EN[n] for n in _SHORT_SCALE_EN.keys()]
109  else:
110  hundreds = [_LONG_SCALE_EN[n] for n in _LONG_SCALE_EN.keys()]
111 
112  # deal with negatives
113  result = ""
114  if num < 0:
115  result = "negative " if scientific else "minus "
116  num = abs(num)
117 
118  try:
119  # deal with 4 digits
120  # usually if it's a 4 digit num it should be said like a date
121  # i.e. 1972 => nineteen seventy two
122  if len(str(num)) == 4 and isinstance(num, int):
123  _num = str(num)
124  # deal with 1000, 2000, 2001, 2100, 3123, etc
125  # is skipped as the rest of the
126  # functin deals with this already
127  if _num[1:4] == '000' or _num[1:3] == '00' or int(_num[0:2]) >= 20:
128  pass
129  # deal with 1900, 1300, etc
130  # i.e. 1900 => nineteen hundred
131  elif _num[2:4] == '00':
132  first = number_names[int(_num[0:2])]
133  last = number_names[100]
134  return first + " " + last
135  # deal with 1960, 1961, etc
136  # i.e. 1960 => nineteen sixty
137  # 1961 => nineteen sixty one
138  else:
139  first = number_names[int(_num[0:2])]
140  if _num[3:4] == '0':
141  last = number_names[int(_num[2:4])]
142  else:
143  second = number_names[int(_num[2:3])*10]
144  last = second + " " + number_names[int(_num[3:4])]
145  return first + " " + last
146  # exception used to catch any unforseen edge cases
147  # will default back to normal subroutine
148  except Exception as e:
149  LOG.error('Exception in pronounce_number_en: {}' + repr(e))
150 
151  # check for a direct match
152  if num in number_names:
153  if num > 90:
154  result += "one "
155  result += number_names[num]
156  else:
157  def _sub_thousand(n):
158  assert 0 <= n <= 999
159  if n <= 19:
160  return digits[n]
161  elif n <= 99:
162  q, r = divmod(n, 10)
163  return tens[q - 1] + (" " + _sub_thousand(r) if r else "")
164  else:
165  q, r = divmod(n, 100)
166  return digits[q] + " hundred" + (
167  " and " + _sub_thousand(r) if r else "")
168 
169  def _short_scale(n):
170  if n >= max(_SHORT_SCALE_EN.keys()):
171  return "infinity"
172  n = int(n)
173  assert 0 <= n
174  res = []
175  for i, z in enumerate(_split_by(n, 1000)):
176  if not z:
177  continue
178  number = _sub_thousand(z)
179  if i:
180  number += " "
181  number += hundreds[i]
182  res.append(number)
183 
184  return ", ".join(reversed(res))
185 
186  def _split_by(n, split=1000):
187  assert 0 <= n
188  res = []
189  while n:
190  n, r = divmod(n, split)
191  res.append(r)
192  return res
193 
194  def _long_scale(n):
195  if n >= max(_LONG_SCALE_EN.keys()):
196  return "infinity"
197  n = int(n)
198  assert 0 <= n
199  res = []
200  for i, z in enumerate(_split_by(n, 1000000)):
201  if not z:
202  continue
203  number = pronounce_number_en(z, places, True, scientific)
204  # strip off the comma after the thousand
205  if i:
206  # plus one as we skip 'thousand'
207  # (and 'hundred', but this is excluded by index value)
208  number = number.replace(',', '')
209  number += " " + hundreds[i+1]
210  res.append(number)
211  return ", ".join(reversed(res))
212 
213  if short_scale:
214  result += _short_scale(num)
215  else:
216  result += _long_scale(num)
217 
218  # Deal with fractional part
219  if not num == int(num) and places > 0:
220  result += " point"
221  place = 10
222  while int(num * place) % 10 > 0 and places > 0:
223  result += " " + number_names[int(num * place) % 10]
224  place *= 10
225  places -= 1
226  return result
227 
228 
229 def nice_time_en(dt, speech=True, use_24hour=False, use_ampm=False):
230  """
231  Format a time to a comfortable human format
232 
233  For example, generate 'five thirty' for speech or '5:30' for
234  text display.
235 
236  Args:
237  dt (datetime): date to format (assumes already in local timezone)
238  speech (bool): format for speech (default/True) or display (False)=Fal
239  use_24hour (bool): output in 24-hour/military or 12-hour format
240  use_ampm (bool): include the am/pm for 12-hour format
241  Returns:
242  (str): The formatted time string
243  """
244  if use_24hour:
245  # e.g. "03:01" or "14:22"
246  string = dt.strftime("%H:%M")
247  else:
248  if use_ampm:
249  # e.g. "3:01 AM" or "2:22 PM"
250  string = dt.strftime("%I:%M %p")
251  else:
252  # e.g. "3:01" or "2:22"
253  string = dt.strftime("%I:%M")
254  if string[0] == '0':
255  string = string[1:] # strip leading zeros
256 
257  if not speech:
258  return string
259 
260  # Generate a speakable version of the time
261  if use_24hour:
262  speak = ""
263 
264  # Either "0 8 hundred" or "13 hundred"
265  if string[0] == '0':
266  speak += pronounce_number_en(int(string[0])) + " "
267  speak += pronounce_number_en(int(string[1]))
268  else:
269  speak = pronounce_number_en(int(string[0:2]))
270 
271  speak += " "
272  if string[3:5] == '00':
273  speak += "hundred"
274  else:
275  if string[3] == '0':
276  speak += pronounce_number_en(0) + " "
277  speak += pronounce_number_en(int(string[4]))
278  else:
279  speak += pronounce_number_en(int(string[3:5]))
280  return speak
281  else:
282  if dt.hour == 0 and dt.minute == 0:
283  return "midnight"
284  if dt.hour == 12 and dt.minute == 0:
285  return "noon"
286  # TODO: "half past 3", "a quarter of 4" and other idiomatic times
287 
288  if dt.hour == 0:
289  speak = pronounce_number_en(12)
290  elif dt.hour < 13:
291  speak = pronounce_number_en(dt.hour)
292  else:
293  speak = pronounce_number_en(dt.hour - 12)
294 
295  if dt.minute == 0:
296  if not use_ampm:
297  return speak + " o'clock"
298  else:
299  if dt.minute < 10:
300  speak += " oh"
301  speak += " " + pronounce_number_en(dt.minute)
302 
303  if use_ampm:
304  if dt.hour > 11:
305  speak += " p.m."
306  else:
307  speak += " a.m."
308 
309  return speak
def nice_time_en(dt, speech=True, use_24hour=False, use_ampm=False)
Definition: format_en.py:229
def nice_number_en(number, speech, denominators)
Definition: format_en.py:24
def pronounce_number_en(num, places=2, short_scale=True, scientific=False)
Definition: format_en.py:69
def convert_to_mixed_fraction(number, denominators)


mycroft_ros
Author(s):
autogenerated on Mon Apr 26 2021 02:35:40