diff --git a/README.md b/README.md index 6cecc20e..ad8f5aab 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,8 @@ Get the time, date, day of the week ## About Get the local time or time for major cities around the world. Times are given in 12-hour (2:30 pm) or 24-hour format (14:30) based on the -Time Format setting at [Home](https://home.mycroft.ai/#/setting/basic) +Time Format setting in your `mycroft.conf` -Time can optionally be shown on a display, like a digital clock. See -the [Skill Setting](https://home.mycroft.ai/#/skill). ## Examples * "What time is it?" diff --git a/__init__.py b/__init__.py index 615dc098..e8ecfb34 100644 --- a/__init__.py +++ b/__init__.py @@ -19,13 +19,12 @@ import geocoder import pytz from lingua_franca.format import nice_date, nice_duration, nice_time, date_time_format -from lingua_franca.parse import extract_datetime, fuzzy_match, extract_number, normalize -from ovos_bus_client.message import Message +from lingua_franca.parse import extract_datetime, fuzzy_match, normalize from ovos_utils import classproperty -from ovos_workshop.intents import IntentBuilder from ovos_utils.process_utils import RuntimeRequirements -from ovos_utils.time import now_utc, now_local, to_local -from ovos_workshop.decorators import intent_handler, skill_api_method +from ovos_utils.time import now_local, get_next_leap_year +from ovos_workshop.decorators import intent_handler +from ovos_workshop.intents import IntentBuilder from ovos_workshop.skills import OVOSSkill from timezonefinder import TimezoneFinder @@ -51,6 +50,7 @@ class TimeSkill(OVOSSkill): @classproperty def runtime_requirements(self): + """this skill does not need internet""" return RuntimeRequirements(internet_before_load=False, network_before_load=False, gui_before_load=False, @@ -62,44 +62,40 @@ def runtime_requirements(self): no_gui_fallback=True) def initialize(self): - self.displayed_time = None - self.display_tz = None - self.answering_query = False - self.default_timezone = None + # pre-load lingua-franca date_time_format.cache(self.lang) - self.settings_change_callback = self.on_settings_changed - if self.settings.get("show_time"): - self.schedule_clock_rendering() - - def schedule_clock_rendering(self): - self.cancel_scheduled_event("clock") - # Start a callback that repeats every 10 seconds - now = datetime.datetime.now() - callback_time = (datetime.datetime(now.year, now.month, now.day, - now.hour, now.minute) + - datetime.timedelta(seconds=60)) - self.schedule_repeating_event(self.update_mk1_faceplate, - when=callback_time, - frequency=10, - name="clock") - - def on_settings_changed(self): - if self.settings.get("show_time"): - self.schedule_clock_rendering() - else: - self.cancel_scheduled_event("clock") - @property def use_24hour(self): - return self.config_core.get('time_format') == 'full' + # self.time_format is Session aware + return self.time_format == 'full' - def _get_timezone_from_builtins(self, locale): - if "/" not in locale: + ###################################################################### + # parsing + def _extract_location(self, utt) -> str: + # if "Location" in message.data: + # return message.data["Location"] + rx_file = self.find_resource('location.rx', 'regex') + if rx_file: + with open(rx_file) as f: + for pat in f.read().splitlines(): + pat = pat.strip() + if pat and pat[0] == "#": + continue + res = re.search(pat, utt) + if res: + try: + return res.group("Location") + except IndexError: + pass + return None + + def _get_timezone_from_builtins(self, location_string) -> datetime.tzinfo: + if "/" not in location_string: try: # This handles common city names, like "Dallas" or "Paris" # first get the lat / long. - g = geocoder.osm(locale) + g = geocoder.osm(location_string) # now look it up tf = TimezoneFinder() @@ -110,12 +106,12 @@ def _get_timezone_from_builtins(self, locale): try: # This handles codes like "America/Los_Angeles" - return pytz.timezone(locale) + return pytz.timezone(location_string) except Exception: pass return None - def _get_timezone_from_table(self, locale): + def _get_timezone_from_table(self, location_string) -> datetime.tzinfo: """Check lookup table for timezones. This can also be a translation layer. @@ -123,12 +119,12 @@ def _get_timezone_from_table(self, locale): """ timezones = self.translate_namedvalues("timezone.value") for timezone in timezones: - if locale.lower() == timezone.lower(): + if location_string.lower() == timezone.lower(): # assumes translation is correct return pytz.timezone(timezones[timezone].strip()) return None - def _get_timezone_from_fuzzymatch(self, locale): + def _get_timezone_from_fuzzymatch(self, location_string) -> datetime.tzinfo: """Fuzzymatch a location against the pytz timezones. The pytz timezones consists of @@ -140,7 +136,7 @@ def _get_timezone_from_fuzzymatch(self, locale): These are parsed and compared against the provided location. """ - target = locale.lower() + target = location_string.lower() best = None for name in pytz.all_timezones: # Separate at '/' @@ -169,231 +165,129 @@ def _get_timezone_from_fuzzymatch(self, locale): else: return None - def get_timezone(self, locale): + def get_timezone_in_location(self, location_string: str) -> datetime.tzinfo: """Get the timezone. This uses a variety of approaches to determine the intended timezone. If locale is the user defined locale, we save that timezone and cache it. """ - - # default timezone exists, so return it. - if str(self.default_timezone) == locale == self.location_timezone: - return self.default_timezone - - # no default timezone has either been requested or saved - timezone = self._get_timezone_from_builtins(locale) + timezone = self._get_timezone_from_builtins(location_string) if not timezone: - timezone = self._get_timezone_from_table(locale) + timezone = self._get_timezone_from_table(location_string) if not timezone: - timezone = self._get_timezone_from_fuzzymatch(locale) - - # if the current request is our default timezone, save it. - if locale == self.location_timezone: - self.default_timezone = timezone + timezone = self._get_timezone_from_fuzzymatch(location_string) return timezone - def get_local_datetime(self, location, dtUTC=None): - if not dtUTC: - dtUTC = now_utc() - if self.display_tz: - # User requested times be shown in some timezone - tz = self.display_tz - else: - tz = self.get_timezone(self.location_timezone) - + ###################################################################### + # utils + def get_datetime(self, location=None, + anchor_date: datetime.datetime = None) -> datetime.datetime: + """return anchor_date/now_local at location/session_tz""" if location: - tz = self.get_timezone(location) - if not tz: - self.speak_dialog("time.tz.not.found", {"location": location}) - return None - - return dtUTC.astimezone(tz) - - @skill_api_method - def get_display_date(self, day=None, location=None): - if not day: - day = self.get_local_datetime(location) - if self.config_core.get('date_format') == 'MDY': - return day.strftime("%-m/%-d/%Y") + tz = self.get_timezone_in_location(location) + if not tz: + return None # tz not found else: - return day.strftime("%Y/%-d/%-m") - - @skill_api_method - def get_display_current_time(self, location=None, dtUTC=None): - # Get a formatted digital clock time based on the user preferences - dt = self.get_local_datetime(location, dtUTC) - if not dt: - return None - - return nice_time(dt, self.lang, speech=False, - use_24hour=self.use_24hour) + # self.location_timezone comes from Session + tz = pytz.timezone(self.location_timezone) + if anchor_date: + dt = anchor_date.astimezone(tz) + else: + dt = now_local(tz) + return dt - def get_spoken_current_time(self, location=None, - dtUTC=None, force_ampm=False): + def get_spoken_time(self, location=None, force_ampm=False, + anchor_date: datetime.datetime = None): # Get a formatted spoken time based on the user preferences - dt = self.get_local_datetime(location, dtUTC) - if not dt: - return + dt = self.get_datetime(location, anchor_date) # speak AM/PM when talking about somewhere else say_am_pm = bool(location) or force_ampm - s = nice_time(dt, self.lang, speech=True, + s = nice_time(dt, lang=self.lang, speech=True, use_24hour=self.use_24hour, use_ampm=say_am_pm) # HACK: Mimic 2 has a bug with saying "AM". Work around it for now. if say_am_pm: s = s.replace("AM", "A.M.") - return s - def show_time(self, display_time=None): - display_time = display_time or self.get_display_current_time() - self.show_time_gui(display_time) - self.show_time_mark1(display_time) - - def show_time_mark1(self, display_time=None): - display_time = display_time or self.get_display_current_time() - # TODO - move to mark 1 plugin/gui extension, - # implement "homescreen" for mark1 - - # Map characters to the display encoding for a Mark 1 - # (4x8 except colon, which is 2x8) - code_dict = { - ':': 'CIICAA', - '0': 'EIMHEEMHAA', - '1': 'EIIEMHAEAA', - '2': 'EIEHEFMFAA', - '3': 'EIEFEFMHAA', - '4': 'EIMBABMHAA', - '5': 'EIMFEFEHAA', - '6': 'EIMHEFEHAA', - '7': 'EIEAEAMHAA', - '8': 'EIMHEFMHAA', - '9': 'EIMBEBMHAA', - } - - # clear screen (draw two blank sections, numbers cover rest) - if len(display_time) == 4: - # for 4-character times, 9x8 blank - self.enclosure.mouth_display(img_code="JIAAAAAAAAAAAAAAAAAA", - refresh=False) - self.enclosure.mouth_display(img_code="JIAAAAAAAAAAAAAAAAAA", - x=22, refresh=False) + def get_display_time(self, location=None, force_ampm=False, + anchor_date: datetime.datetime = None): + # Get a formatted spoken time based on the user preferences + dt = self.get_datetime(location, anchor_date) + # speak AM/PM when talking about somewhere else + say_am_pm = bool(location) or force_ampm + return nice_time(dt, lang=self.lang, + speech=False, + use_24hour=self.use_24hour, # session aware + use_ampm=say_am_pm) + + def get_display_date(self, location=None, + anchor_date: datetime.datetime = None): + dt = self.get_datetime(location, anchor_date) + fmt = self.date_format # Session aware + if fmt == 'MDY': + return dt.strftime("%-m/%-d/%Y") + elif fmt == 'YMD': + return dt.strftime("%-Y/%-m/%d") + elif fmt == 'YDM': + return dt.strftime("%-Y/%-d/%m") + elif fmt == 'DMY': + return dt.strftime("%d/%-m/%-Y") + + def nice_weekday(self, dt: datetime.datetime): + # TODO - move to lingua-franca + if self.lang in date_time_format.lang_config.keys(): + localized_day_names = list( + date_time_format.lang_config[self.lang]['weekday'].values()) + weekday = localized_day_names[dt.weekday()] else: - # for 5-character times, 7x8 blank - self.enclosure.mouth_display(img_code="HIAAAAAAAAAAAAAA", - refresh=False) - self.enclosure.mouth_display(img_code="HIAAAAAAAAAAAAAA", - x=24, refresh=False) - - # draw the time, centered on display - xoffset = (32 - (4 * (len(display_time)) - 2)) / 2 - for c in display_time: - if c in code_dict: - self.enclosure.mouth_display(img_code=code_dict[c], - x=xoffset, refresh=False) - if c == ":": - xoffset += 2 # colon is 1 pixels + a space - else: - xoffset += 4 # digits are 3 pixels + a space + weekday = dt.strftime("%A") + return weekday.capitalize() - if self._is_alarm_set(): - # Show a dot in the upper-left - self.enclosure.mouth_display(img_code="CIAACA", x=29, - refresh=False) + def nice_month(self, dt: datetime.datetime): + # TODO - move to lingua-franca + if self.lang in date_time_format.lang_config.keys(): + localized_month_names = date_time_format.lang_config[self.lang]['month'] + month = localized_month_names[str(int(dt.strftime("%m")))] else: - self.enclosure.mouth_display(img_code="CIAAAA", x=29, - refresh=False) - - def _is_alarm_set(self): - """Query the alarm skill if an alarm is set.""" - query = Message("private.mycroftai.has_alarm") - msg = self.bus.wait_for_response(query) - return msg and msg.data.get("active_alarms", 0) > 0 - - def show_time_gui(self, display_time=None): - display_time = display_time or self.get_display_current_time() - """ Display time on the Mycroft GUI. """ - self.gui.clear() - self.gui['time_string'] = display_time - self.gui['ampm_string'] = '' - self.gui['date_string'] = self.get_display_date() - self.gui.show_page('time') - - def _is_display_idle(self): - # check if the display is being used by another skill right now - # or _get_active() == "TimeSkill" - return self.enclosure.display_manager.get_active() == '' - - def update_mk1_faceplate(self, force=False): - # TODO - move to mark 1 plugin/gui extension, - # implement "homescreen" for mark1 - - # Don't show idle time when answering a query to prevent - # overwriting the displayed value. - if self.answering_query: - return - - if self.settings.get("show_time", False): - # user requested display of time while idle - # (Mark1 faceplate) - if force or self._is_display_idle(): - current_time = self.get_display_current_time() - if self.displayed_time != current_time: - self.displayed_time = current_time - self.show_time_mark1(current_time) - else: - self.displayed_time = None # another skill is using display + month = dt.strftime("%B") + month = month.capitalize() + if self.date_format == 'MDY': + return f"{month} {dt.strftime('%d')}" else: - # time display is not wanted - if self.displayed_time: - if self._is_display_idle(): - # erase the existing displayed time - self.enclosure.mouth_reset() - self.displayed_time = None - - def _extract_location(self, utt): - # if "Location" in message.data: - # return message.data["Location"] - rx_file = self.find_resource('location.rx', 'regex') - if rx_file: - with open(rx_file) as f: - for pat in f.read().splitlines(): - pat = pat.strip() - if pat and pat[0] == "#": - continue - res = re.search(pat, utt) - if res: - try: - return res.group("Location") - except IndexError: - pass - return None + return f"{dt.strftime('%d')} {month}" ###################################################################### # Time queries / display - - @intent_handler(IntentBuilder("").require("Query").require("Time"). - optionally("Location")) - def handle_query_time(self, message): - utt = message.data.get('utterance', "") - location = self._extract_location(utt) - current_time = self.get_spoken_current_time(location) - if not current_time: - return + def speak_time(self, dialog: str, location: str = None): + if location: + current_time = self.get_spoken_time(location) + if not current_time: + self.speak_dialog("time.tz.not.found", {"location": location}) + return + time_string = self.get_display_time(location) + else: + current_time = self.get_spoken_time() + time_string = self.get_display_time() # speak it - self.speak_dialog("time.current", {"time": current_time}) + self.speak_dialog(dialog, {"time": current_time}) # and briefly show the time - self.answering_query = True self.enclosure.deactivate_mouth_events() - self.show_time(self.get_display_current_time(location)) + self.show_time(time_string) time.sleep(5) self.enclosure.mouth_reset() self.enclosure.activate_mouth_events() - self.answering_query = False - self.displayed_time = None + + @intent_handler(IntentBuilder("").require("Query").require("Time"). + optionally("Location")) + def handle_query_time(self, message): + utt = message.data.get('utterance', "") + location = message.data.get("Location") or self._extract_location(utt) + # speak it + self.speak_time("time.current", location=location) @intent_handler("what.time.is.it.intent") def handle_current_time_simple(self, message): @@ -402,31 +296,14 @@ def handle_current_time_simple(self, message): @intent_handler("what.time.will.it.be.intent") def handle_query_future_time(self, message): utt = normalize(message.data.get('utterance', "").lower()) - extract = extract_datetime(utt) - dt = None - if extract: - dt, utt = extract - else: + dt, utt = extract_datetime(utt) or (None, None) + if not dt: self.handle_query_time(message) return - location = self._extract_location(utt) - future_time = self.get_spoken_current_time(location, dt, True) - if not future_time: - return - + location = message.data.get("Location") or self._extract_location(utt) # speak it - self.speak_dialog("time.future", {"time": future_time}) - - # and briefly show the time - self.answering_query = True - self.enclosure.deactivate_mouth_events() - self.show_time(self.get_display_current_time(location, dt)) - time.sleep(5) - self.enclosure.mouth_reset() - self.enclosure.activate_mouth_events() - self.answering_query = False - self.displayed_time = None + self.speak_time("time.future", location=location) @intent_handler(IntentBuilder("").optionally("Query"). require("Time").require("Future").optionally("Location")) @@ -436,83 +313,64 @@ def handle_future_time_simple(self, message): @intent_handler(IntentBuilder("").require("Display").require("Time"). optionally("Location")) def handle_show_time(self, message): - self.display_tz = None utt = message.data.get('utterance', "") - location = self._extract_location(utt) - if location: - tz = self.get_timezone(location) - if not tz: - self.speak_dialog("time.tz.not.found", {"location": location}) - return - else: - self.display_tz = tz - else: - self.display_tz = None - - # enable setting - self.settings["show_time"] = True + location = message.data.get("Location") or self._extract_location(utt) + time_string = self.get_display_time(location) # show time immediately - self.show_time() + self.show_time(time_string) + # enable setting + if not location: + self.settings["show_time"] = True ###################################################################### # Date queries - def handle_query_date(self, message, response_type="simple"): utt = message.data.get('utterance', "").lower() + now = self.get_datetime() try: - extract = extract_datetime(utt) + dt, utt = extract_datetime(utt, anchorDate=now) except Exception: self.speak_dialog('date.not.found') return - day = extract[0] if extract else now_local() - # check if a Holiday was requested, e.g. "What day is Christmas?" - year = extract_number(utt) - if not year or year < 1500 or year > 3000: # filter out non-years - year = day.year + # handle questions ~ "what is the day in sydney" + location_string = message.data.get("Location") or self._extract_location(utt) - location = self._extract_location(utt) - today = to_local(now_utc()) - if location: - # TODO: Timezone math! - if (day.year == today.year and day.month == today.month - and day.day == today.day): - day = now_utc() # for questions ~ "what is the day in sydney" - day = self.get_local_datetime(location, dtUTC=day) - if not day: - return # failed in timezone lookup - - speak_date = nice_date(day, lang=self.lang) + if location_string: + dt = self.get_datetime(location_string, anchor_date=dt) + if not dt: + self.speak_dialog("time.tz.not.found", + {"location": location_string}) + return # failed in timezone lookup + + speak_date = nice_date(dt, lang=self.lang) # speak it if response_type == "simple": self.speak_dialog("date", {"date": speak_date}) elif response_type == "relative": # remove time data to get clean dates - day_date = day.replace(hour=0, minute=0, - second=0, microsecond=0) - today_date = today.replace(hour=0, minute=0, - second=0, microsecond=0) + day_date = dt.replace(hour=0, minute=0, + second=0, microsecond=0) + today_date = now.replace(hour=0, minute=0, + second=0, microsecond=0) num_days = (day_date - today_date).days if num_days >= 0: - speak_num_days = nice_duration(num_days * 86400) + speak_num_days = nice_duration(num_days * 86400, lang=self.lang) self.speak_dialog("date.relative.future", {"date": speak_date, "num_days": speak_num_days}) else: # if in the past, make positive before getting duration - speak_num_days = nice_duration(num_days * -86400) + speak_num_days = nice_duration(num_days * -86400, lang=self.lang) self.speak_dialog("date.relative.past", {"date": speak_date, "num_days": speak_num_days}) # and briefly show the date - self.answering_query = True - self.show_date(location, day=day) + self.show_date(dt, location=location_string) time.sleep(10) self.enclosure.mouth_reset() self.enclosure.activate_mouth_events() - self.answering_query = False - self.displayed_time = None @intent_handler(IntentBuilder("").require("Query").require("Date"). optionally("Location")) @@ -543,10 +401,11 @@ def handle_date_future_weekend(self, message): # Strip year off nice_date as request is inherently close # Don't pass `now` to `nice_date` as a # request on Friday will return "tomorrow" - saturday_date = ', '.join(nice_date(extract_datetime( - 'this saturday', None, 'en-us')[0]).split(', ')[:2]) - sunday_date = ', '.join(nice_date(extract_datetime( - 'this sunday', None, 'en-us')[0]).split(', ')[:2]) + now = self.get_datetime() + dt = extract_datetime('this saturday', anchorDate=now, lang='en-us')[0] + saturday_date = ', '.join(nice_date(dt, lang=self.lang).split(', ')[:2]) + dt = extract_datetime('this sunday', anchorDate=now, lang='en-us')[0] + sunday_date = ', '.join(nice_date(dt, lang=self.lang).split(', ')[:2]) self.speak_dialog('date.future.weekend', { 'saturday_date': saturday_date, 'sunday_date': sunday_date @@ -557,10 +416,13 @@ def handle_date_last_weekend(self, message): # Strip year off nice_date as request is inherently close # Don't pass `now` to `nice_date` as a # request on Monday will return "yesterday" - saturday_date = ', '.join(nice_date(extract_datetime( - 'last saturday', None, 'en-us')[0]).split(', ')[:2]) - sunday_date = ', '.join(nice_date(extract_datetime( - 'last sunday', None, 'en-us')[0]).split(', ')[:2]) + now = self.get_datetime() + dt = extract_datetime('last saturday', + anchorDate=now, lang='en-us')[0] + saturday_date = ', '.join(nice_date(dt, lang=self.lang).split(', ')[:2]) + dt = extract_datetime('last sunday', + anchorDate=now, lang='en-us')[0] + sunday_date = ', '.join(nice_date(dt, lang=self.lang).split(', ')[:2]) self.speak_dialog('date.last.weekend', { 'saturday_date': saturday_date, 'sunday_date': sunday_date @@ -568,76 +430,92 @@ def handle_date_last_weekend(self, message): @intent_handler(IntentBuilder("").require("Query").require("LeapYear")) def handle_query_next_leap_year(self, message): - now = datetime.datetime.now() + now = self.get_datetime() leap_date = datetime.datetime(now.year, 2, 28) year = now.year if now <= leap_date else now.year + 1 - next_leap_year = self.get_next_leap_year(year) + next_leap_year = get_next_leap_year(year) self.speak_dialog('next.leap.year', {'year': next_leap_year}) - def show_date(self, location, day=None): - self.show_date_gui(location, day) - self.show_date_mark1(location, day) + ###################################################################### + # GUI / Faceplate + def show_date(self, dt: datetime.datetime, location: str): + self.show_date_gui(dt, location) + self.show_date_mark1(dt) - def show_date_mark1(self, location, day): - show = self.get_display_date(day, location) + def show_date_mark1(self, dt: datetime.datetime): + show = self.get_display_date(dt) self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(show) - @skill_api_method - def get_weekday(self, day=None, location=None): - if not day: - day = self.get_local_datetime(location) - if self.lang in date_time_format.lang_config.keys(): - localized_day_names = list( - date_time_format.lang_config[self.lang]['weekday'].values()) - weekday = localized_day_names[day.weekday()] - else: - weekday = day.strftime("%A") - return weekday.capitalize() - - @skill_api_method - def get_month_date(self, day=None, location=None): - if not day: - day = self.get_local_datetime(location) - if self.lang in date_time_format.lang_config.keys(): - localized_month_names = date_time_format.lang_config[self.lang]['month'] - month = localized_month_names[str(int(day.strftime("%m")))] - else: - month = day.strftime("%B") - month = month.capitalize() - if self.config_core.get('date_format') == 'MDY': - return "{} {}".format(month, day.strftime("%d")) - else: - return "{} {}".format(day.strftime("%d"), month) - - @skill_api_method - def get_year(self, day=None, location=None): - if not day: - day = self.get_local_datetime(location) - return day.strftime("%Y") - - def get_next_leap_year(self, year): - next_year = year + 1 - if self.is_leap_year(next_year): - return next_year - else: - return self.get_next_leap_year(next_year) - - def is_leap_year(self, year): - return (year % 400 == 0) or ((year % 4 == 0) and (year % 100 != 0)) - - def show_date_gui(self, location, day): + def show_date_gui(self, dt: datetime.datetime, location: str): self.gui.clear() - self.gui['date_string'] = self.get_display_date(day, location) - self.gui['weekday_string'] = self.get_weekday(day, location) - self.gui['daymonth_string'] = self.get_month_date(day, location) + self.gui['date_string'] = self.get_display_date(dt) + self.gui['weekday_string'] = self.nice_weekday(dt) + self.gui['daymonth_string'] = self.nice_month(dt) self.gui['location_string'] = str(location) - month_string = self.get_month_date(day, location).split(" ") - if self.config_core.get('date_format') == 'MDY': + month_string = self.nice_month(dt).split(" ") + if self.date_format == 'MDY': self.gui['day_string'] = month_string[1] self.gui['month_string'] = month_string[0] else: self.gui['day_string'] = month_string[0] self.gui['month_string'] = month_string[1] - self.gui['year_string'] = self.get_year(day, location) + self.gui['year_string'] = dt.strftime("%Y") self.gui.show_page('date') + + def show_time(self, display_time): + self.show_time_gui(display_time) + self.show_time_mark1(display_time) + + def show_time_mark1(self, display_time): + # Map characters to the display encoding for a Mark 1 + # (4x8 except colon, which is 2x8) + code_dict = { + ':': 'CIICAA', + '0': 'EIMHEEMHAA', + '1': 'EIIEMHAEAA', + '2': 'EIEHEFMFAA', + '3': 'EIEFEFMHAA', + '4': 'EIMBABMHAA', + '5': 'EIMFEFEHAA', + '6': 'EIMHEFEHAA', + '7': 'EIEAEAMHAA', + '8': 'EIMHEFMHAA', + '9': 'EIMBEBMHAA', + } + + # clear screen (draw two blank sections, numbers cover rest) + if len(display_time) == 4: + # for 4-character times, 9x8 blank + self.enclosure.mouth_display(img_code="JIAAAAAAAAAAAAAAAAAA", + refresh=False) + self.enclosure.mouth_display(img_code="JIAAAAAAAAAAAAAAAAAA", + x=22, refresh=False) + else: + # for 5-character times, 7x8 blank + self.enclosure.mouth_display(img_code="HIAAAAAAAAAAAAAA", + refresh=False) + self.enclosure.mouth_display(img_code="HIAAAAAAAAAAAAAA", + x=24, refresh=False) + + # draw the time, centered on display + xoffset = (32 - (4 * (len(display_time)) - 2)) / 2 + for c in display_time: + if c in code_dict: + self.enclosure.mouth_display(img_code=code_dict[c], + x=xoffset, refresh=False) + if c == ":": + xoffset += 2 # colon is 1 pixels + a space + else: + xoffset += 4 # digits are 3 pixels + a space + + self.enclosure.mouth_display(img_code="CIAAAA", x=29, + refresh=False) + + def show_time_gui(self, display_time): + """ Display time on the GUI. """ + self.gui.clear() + self.gui['time_string'] = display_time + self.gui['ampm_string'] = '' + self.gui['date_string'] = self.get_display_date() + self.gui.show_page('time')