Source code for dateparser._parts_of_day

from dataclasses import dataclass, fields
from datetime import time
from enum import Enum

import regex as re


[docs] class PartOfDay(str, Enum): """Part of the day that a date string refers to, e.g. ``"tonight"``. See :attr:`DateData.part_of_day <dateparser.date.DateData.part_of_day>`. .. versionadded:: 1.5.0 """ EARLY_MORNING = "early_morning" """Between midnight and dawn, e.g. ``"in the small hours"`` or Spanish ``"de madrugada"``.""" MORNING = "morning" AFTERNOON = "afternoon" EVENING = "evening" NIGHT = "night"
[docs] @dataclass(frozen=True) class PartsOfDay: """Time of the day to use for each :class:`PartOfDay`, for the ``PARTS_OF_DAY`` :ref:`setting <settings>`. .. versionadded:: 1.5.0 """ early_morning: time = time(3) morning: time = time(9) afternoon: time = time(15) evening: time = time(18) night: time = time(20) def __post_init__(self) -> None: for field in fields(self): value = getattr(self, field.name) if not isinstance(value, time): raise TypeError( f"{type(self).__name__}.{field.name} must be a datetime.time, " f"not {type(value).__name__}" )
_PART_OF_DAY_PATTERN = re.compile( r"(?<!\S)(" + "|".join(part.value for part in PartOfDay) + r")(?!\S)" ) _TIME_PATTERN = re.compile(r"\d\s*(?::|(?:am|pm)(?!\S))") _MONTHS = ( "january|february|march|april|may|june|july|august|september|october|" "november|december" ) _TRAILING_NUMBER_PATTERN = re.compile( rf"(?<![\d:/.-])(?P<month>(?<!\d\s*)(?:{_MONTHS})\s+)?" r"(?P<hour>\d{1,2})(?P<minutes>:\d{2})?\s*$" ) def _meridiem(part: PartOfDay, hour: int) -> str: if part in (PartOfDay.EARLY_MORNING, PartOfDay.MORNING): return "am" if part is PartOfDay.NIGHT and not 6 <= hour <= 11: return "am" return "pm" def _replace_part_of_day( translated: str, parts_of_day: PartsOfDay ) -> tuple[str, PartOfDay | None, bool]: """Return *translated* with its part-of-day token replaced by a time, the matching :class:`PartOfDay` (or ``None``), and whether that time came from *parts_of_day*. A number right before the part of the day is an hour, e.g. ``"8 in the evening"``, unless a month without a day precedes it, e.g. ``"January 8 in the evening"``. A part of the day on its own is left in place, so that parsing fails.""" match = _PART_OF_DAY_PATTERN.search(translated) if not match: return translated, None, False part = PartOfDay(match[1]) before, after = translated[: match.start()], translated[match.end() :] number = _TRAILING_NUMBER_PATTERN.search(before) if number and not number["month"]: hour = int(number["hour"]) if hour > 12: return translated, None, False time_string = f"{hour}{number['minutes'] or ':00'} {_meridiem(part, hour)}" return f"{before[: number.start()]}{time_string} {after}", part, False rest = f"{before} {after}" if not rest.strip(): return translated, None, False if _TIME_PATTERN.search(rest): return rest, part, False part_time: time = getattr(parts_of_day, part.value) return f"{rest} {part_time:%H:%M:%S}", part, True