mycroft-core/mycroft/util/format.py

359 lines
15 KiB
Python
Raw Normal View History

Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# Copyright 2017 Mycroft AI Inc.
#
Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from mycroft.util.lang.format_en import *
from mycroft.util.lang.format_pt import *
from mycroft.util.lang.format_it import *
from mycroft.util.lang.format_sv import *
from mycroft.util.lang.format_de import nice_number_de
from mycroft.util.lang.format_de import nice_time_de
from mycroft.util.lang.format_de import pronounce_number_de
from mycroft.util.lang.format_fr import nice_number_fr
from mycroft.util.lang.format_fr import nice_time_fr
from mycroft.util.lang.format_fr import pronounce_number_fr
from collections import namedtuple
import json
import os
import datetime
import re
NUMBER_TUPLE = namedtuple(
'number',
('x, xx, x0, x_in_x0, xxx, x00, x_in_x00, xx00, xx_in_xx00, x000, ' +
'x_in_x000, x0_in_x000'))
class DateTimeFormat:
def __init__(self, config_path):
self.lang_config = {}
self.config_path = config_path
def cache(self, lang):
if lang not in self.lang_config:
try:
with open(self.config_path + '/' + lang + '/date_time.json',
'r') as lang_config_file:
self.lang_config[lang] = json.loads(
lang_config_file.read())
except FileNotFoundError:
with open(self.config_path + '/en-us/date_time.json',
'r') as lang_config_file:
self.lang_config[lang] = json.loads(
lang_config_file.read())
for x in ['decade_format', 'hundreds_format', 'thousand_format',
'year_format']:
i = 1
while self.lang_config[lang][x].get(str(i)):
self.lang_config[lang][x][str(i)]['re'] = (
re.compile(self.lang_config[lang][x][str(i)]['match']
))
i = i + 1
def _number_strings(self, number, lang):
x = (self.lang_config[lang]['number'].get(str(number % 10)) or
str(number % 10))
xx = (self.lang_config[lang]['number'].get(str(number % 100)) or
str(number % 100))
x_in_x0 = self.lang_config[lang]['number'].get(
str(int(number % 100 / 10))) or str(int(number % 100 / 10))
x0 = (self.lang_config[lang]['number'].get(
str(int(number % 100 / 10) * 10)) or
str(int(number % 100 / 10) * 10))
xxx = (self.lang_config[lang]['number'].get(str(number % 1000)) or
str(number % 1000))
x00 = (self.lang_config[lang]['number'].get(str(int(
number % 1000 / 100) * 100)) or
str(int(number % 1000 / 100) * 100))
x_in_x00 = self.lang_config[lang]['number'].get(str(int(
number % 1000 / 100))) or str(int(number % 1000 / 100))
xx00 = self.lang_config[lang]['number'].get(str(int(
number % 10000 / 100) * 100)) or str(int(number % 10000 / 100) *
100)
xx_in_xx00 = self.lang_config[lang]['number'].get(str(int(
number % 10000 / 100))) or str(int(number % 10000 / 100))
x000 = (self.lang_config[lang]['number'].get(str(int(
number % 10000 / 1000) * 1000)) or
str(int(number % 10000 / 1000) * 1000))
x_in_x000 = self.lang_config[lang]['number'].get(str(int(
number % 10000 / 1000))) or str(int(number % 10000 / 1000))
x0_in_x000 = self.lang_config[lang]['number'].get(str(int(
number % 10000 / 1000)*10)) or str(int(number % 10000 / 1000)*10)
return NUMBER_TUPLE(
x, xx, x0, x_in_x0, xxx, x00, x_in_x00, xx00, xx_in_xx00, x000,
x_in_x000, x0_in_x000)
def _format_string(self, number, format_section, lang):
s = self.lang_config[lang][format_section]['default']
i = 1
while self.lang_config[lang][format_section].get(str(i)):
e = self.lang_config[lang][format_section][str(i)]
if e['re'].match(str(number)):
return e['format']
i = i + 1
return s
def _decade_format(self, number, number_tuple, lang):
s = self._format_string(number % 100, 'decade_format', lang)
return s.format(x=number_tuple.x, xx=number_tuple.xx,
x0=number_tuple.x0, x_in_x0=number_tuple.x_in_x0,
number=str(number % 100))
def _number_format_hundreds(self, number, number_tuple, lang,
formatted_decade):
s = self._format_string(number % 1000, 'hundreds_format', lang)
return s.format(xxx=number_tuple.xxx, x00=number_tuple.x00,
x_in_x00=number_tuple.x_in_x00,
formatted_decade=formatted_decade,
number=str(number % 1000))
def _number_format_thousand(self, number, number_tuple, lang,
formatted_decade, formatted_hundreds):
s = self._format_string(number % 10000, 'thousand_format', lang)
return s.format(x_in_x00=number_tuple.x_in_x00,
xx00=number_tuple.xx00,
xx_in_xx00=number_tuple.xx_in_xx00,
x000=number_tuple.x000,
x_in_x000=number_tuple.x_in_x000,
x0_in_x000=number_tuple.x0_in_x000,
formatted_decade=formatted_decade,
formatted_hundreds=formatted_hundreds,
number=str(number % 10000))
def date_format(self, dt, lang, now):
format_str = 'date_full'
if now:
if dt.year == now.year:
format_str = 'date_full_no_year'
if dt.month == now.month and dt.day > now.day:
format_str = 'date_full_no_year_month'
tomorrow = now + datetime.timedelta(days=1)
yesterday = now - datetime.timedelta(days=1)
if tomorrow.date() == dt.date():
format_str = 'tomorrow'
elif now.date() == dt.date():
format_str = 'today'
elif yesterday.date() = dt.date():
format_str = 'yesterday'
return self.lang_config[lang]['date_format'][format_str].format(
weekday=self.lang_config[lang]['weekday'][str(dt.weekday())],
month=self.lang_config[lang]['month'][str(dt.month)],
day=self.lang_config[lang]['date'][str(dt.day)],
formatted_year=self.year_format(dt, lang, False))
def date_time_format(self, dt, lang, now, use_24hour, use_ampm):
date_str = self.date_format(dt, lang, now)
time_str = nice_time(dt, lang, use_24hour=use_24hour,
use_ampm=use_ampm)
return self.lang_config[lang]['date_time_format']['date_time'].format(
formatted_date=date_str, formatted_time=time_str)
def year_format(self, dt, lang, bc):
number_tuple = self._number_strings(dt.year, lang)
formatted_bc = (
self.lang_config[lang]['year_format']['bc'] if bc else '')
formatted_decade = self._decade_format(
dt.year, number_tuple, lang)
formatted_hundreds = self._number_format_hundreds(
dt.year, number_tuple, lang, formatted_decade)
formatted_thousand = self._number_format_thousand(
dt.year, number_tuple, lang, formatted_decade, formatted_hundreds)
s = self._format_string(dt.year, 'year_format', lang)
return re.sub(' +', ' ',
s.format(
year=str(dt.year),
century=str(int(dt.year / 100)),
decade=str(dt.year % 100),
formatted_hundreds=formatted_hundreds,
formatted_decade=formatted_decade,
formatted_thousand=formatted_thousand,
bc=formatted_bc)).strip()
date_time_format = DateTimeFormat(
os.path.dirname(os.path.abspath(__file__)) + '/../res/text')
2017-05-23 21:30:36 +00:00
def nice_number(number, lang="en-us", speech=True, denominators=None):
"""Format a float to human readable functions
This function formats a float to human understandable functions. Like
4.5 becomes 4 and a half for speech and 4 1/2 for text
Args:
number (int or float): the float to format
lang (str): code for the language to use
speech (bool): format for speech (True) or display (False)
denominators (iter of ints): denominators to use, default [1 .. 20]
Returns:
(str): The formatted string.
"""
# Convert to spoken representation in appropriate language
lang_lower = str(lang).lower()
if lang_lower.startswith("en"):
return nice_number_en(number, speech, denominators)
elif lang_lower.startswith("pt"):
return nice_number_pt(number, speech, denominators)
elif lang_lower.startswith("it"):
return nice_number_it(number, speech, denominators)
elif lang_lower.startswith("fr"):
return nice_number_fr(number, speech, denominators)
elif lang_lower.startswith("sv"):
2018-02-15 12:02:45 +00:00
return nice_number_sv(number, speech, denominators)
elif lang_lower.startswith("de"):
return nice_number_de(number, speech, denominators)
# Default to the raw number for unsupported languages,
# hopefully the STT engine will pronounce understandably.
return str(number)
def nice_time(dt, lang="en-us", speech=True, use_24hour=False,
use_ampm=False):
"""
Format a time to a comfortable human format
For example, generate 'five thirty' for speech or '5:30' for
text display.
Args:
dt (datetime): date to format (assumes already in local timezone)
lang (str): code for the language to use
speech (bool): format for speech (default/True) or display (False)
use_24hour (bool): output in 24-hour/military or 12-hour format
use_ampm (bool): include the am/pm for 12-hour format
Returns:
(str): The formatted time string
"""
lang_lower = str(lang).lower()
if lang_lower.startswith("en"):
return nice_time_en(dt, speech, use_24hour, use_ampm)
elif lang_lower.startswith("it"):
return nice_time_it(dt, speech, use_24hour, use_ampm)
elif lang_lower.startswith("fr"):
return nice_time_fr(dt, speech, use_24hour, use_ampm)
elif lang_lower.startswith("de"):
return nice_time_de(dt, speech, use_24hour, use_ampm)
# TODO: Other languages
return str(dt)
2018-06-21 22:05:03 +00:00
def pronounce_number(number, lang="en-us", places=2, short_scale=True,
scientific=False):
"""
Convert a number to it's spoken equivalent
For example, '5' would be 'five'
Args:
number: the number to pronounce
2018-06-19 01:09:38 +00:00
short_scale (bool) : use short (True) or long scale (False)
https://en.wikipedia.org/wiki/Names_of_large_numbers
2018-06-21 22:05:03 +00:00
scientific (bool) : convert and pronounce in scientific notation
Returns:
(str): The pronounced number
"""
lang_lower = str(lang).lower()
if lang_lower.startswith("en"):
2018-06-19 01:09:38 +00:00
return pronounce_number_en(number, places=places,
2018-06-21 22:05:03 +00:00
short_scale=short_scale,
scientific=scientific)
elif lang_lower.startswith("it"):
return pronounce_number_it(number, places=places)
elif lang_lower.startswith("fr"):
return pronounce_number_fr(number, places=places)
elif lang_lower.startswith("de"):
return pronounce_number_de(number, places=places)
# Default to just returning the numeric value
return str(number)
def nice_date(dt, lang='en-us', now=None):
"""
Format a datetime to a pronounceable date
For example, generates 'tuesday, june the fifth, 2018'
Args:
dt (datetime): date to format (assumes already in local timezone)
lang (string): the language to use, use Mycroft default language if not
provided
now (datetime): Current date. If provided, the returned date for speech
will be shortened accordingly: No year is returned if now is in the
same year as td, no month is returned if now is in the same month
as td. If now and td is the same day, 'today' is returned.
Returns:
(str): The formatted date string
"""
date_time_format.cache(lang)
return date_time_format.date_format(dt, lang, now)
def nice_date_time(dt, lang='en-us', now=None, use_24hour=False,
use_ampm=False):
"""
Format a datetime to a pronounceable date and time
For example, generate 'tuesday, june the fifth, 2018 at five thirty'
Args:
dt (datetime): date to format (assumes already in local timezone)
lang (string): the language to use, use Mycroft default language if
not provided
now (datetime): Current date. If provided, the returned date for
speech will be shortened accordingly: No year is returned if
now is in the same year as td, no month is returned if now is
in the same month as td. If now and td is the same day, 'today'
is returned.
use_24hour (bool): output in 24-hour/military or 12-hour format
use_ampm (bool): include the am/pm for 12-hour format
Returns:
(str): The formatted date time string
"""
date_time_format.cache(lang)
return date_time_format.date_time_format(dt, lang, now, use_24hour,
use_ampm)
def nice_year(dt, lang='en-us', bc=False):
"""
Format a datetime to a pronounceable year
For example, generate 'nineteen-hundred and eighty-four' for year 1984
Args:
dt (datetime): date to format (assumes already in local timezone)
lang (string): the language to use, use Mycroft default language if
not provided
bc (bool) pust B.C. after the year (python does not support dates
B.C. in datetime)
Returns:
(str): The formatted year string
"""
date_time_format.cache(lang)
return date_time_format.year_format(dt, lang, bc)