Metadata-Version: 2.4
Name: ovos-date-parser
Version: 0.31.0a1
Summary: OpenVoiceOS's multilingual date and time parsing and formatting library
Author-email: JarbasAI <jarbasai@mailfence.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/OpenVoiceOS/ovos-date-parser
Project-URL: Source, https://github.com/OpenVoiceOS/ovos-date-parser
Project-URL: Issues, https://github.com/OpenVoiceOS/ovos-date-parser/issues
Keywords: dates,time,parsing,nlp,multilingual,ovos
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Text Processing :: Linguistic
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-dateutil~=2.6
Requires-Dist: quebra_frases>=0.3.7
Requires-Dist: ovos-number-parser<1.0.0,>=0.19.6a2
Requires-Dist: dateparser
Requires-Dist: ovos-config
Requires-Dist: ovos-spec-tools>=1.5.0a1
Requires-Dist: chronologia>=0.27.7a1
Dynamic: license-file

# ovos-date-parser

Multilingual parsing, extraction, and formatting of human date, time, and
duration expressions. The library is a two-way bridge between machine
timestamps and the way people speak and write about time.

- **Text to datetime**: pull a `datetime` out of "next friday at 3pm" or
  "amanhã às 15h30", and keep the leftover words.
- **Text to duration**: turn "two hours and thirty minutes" into a `timedelta`.
- **Datetime to speech**: render `2024-01-05 15:30` as "January fifth twenty
  twenty four at half past three".
- **Dozens of languages**, resolved by BCP-47 code, with an automatic
  [dateparser](https://dateparser.readthedocs.io) fallback for the rest.

The library powers date and time understanding in
[OpenVoiceOS](https://openvoiceos.org). It is a plain Python library with no
voice-assistant dependency at runtime. Use it in an NER pipeline, a TTS
front-end, an ASR post-processor, or a scheduling, calendar, or logging tool.

## Installation

```bash
pip install ovos-date-parser
# or
uv pip install ovos-date-parser
```

## 30-second quickstart

```python
from datetime import datetime
from ovos_date_parser import extract_datetime, extract_duration, nice_time, nice_duration

# 1. text -> datetime (+ the words left over)
when, leftover = extract_datetime("lets meet next friday at 8am", "en",
                                  anchorDate=datetime(2024, 1, 5))
print(when)      # 2024-01-12 08:00:00
print(leftover)  # 'lets meet'

# 2. text -> timedelta
delta, leftover = extract_duration("set a timer for 5 minutes", "en")
print(delta)     # 0:05:00

# 3. datetime -> speakable words
print(nice_time(datetime(2024, 1, 5, 15, 30), "en"))   # 'half past three'
print(nice_duration(3690, "en"))                       # 'one hour one minute thirty seconds'
```

Every snippet above and in [`examples/`](examples/) runs with nothing installed
but this package.

## Use it outside OVOS

The same handful of functions solve everyday text and speech problems that
have nothing to do with voice assistants.

### Temporal entity extraction (NER)

Tag dates, times, and durations in free text, and keep the non-temporal
remainder. This helps with log mining, ticket triage, and note-taking apps.

```python
from datetime import datetime
from ovos_date_parser import extract_datetime

text = "call the supplier next monday at 2pm about the delayed order"
when, rest = extract_datetime(text, "en", anchorDate=datetime(2024, 1, 5))
# when -> 2024-01-08 14:00, rest -> 'call supplier delayed order'
```

`anchorDate` is the "now" that relative phrases resolve against. Pass a fixed
value for reproducible extraction, or `datetime.now()` for live use. See
[`examples/ner_temporal.py`](examples/ner_temporal.py).

### TTS normalization

Speech engines mangle raw digits. Normalize a timestamp to words before
synthesis:

```python
from datetime import datetime
from ovos_date_parser import nice_date, nice_time

dt = datetime(2024, 1, 5, 15, 30)
spoken = f"{nice_date(dt, 'en')} at {nice_time(dt, 'en')}"
# 'friday, january fifth, twenty twenty four at half past three'
```

See [`examples/tts_normalization.py`](examples/tts_normalization.py).

### ASR post-processing

Speech-to-text emits words, but downstream logic needs structure. Convert a
transcript into a real datetime and an action payload:

```python
from datetime import datetime
from ovos_date_parser import extract_datetime

utterance = "remind me next tuesday at nine thirty to water the plants"
when, action = extract_datetime(utterance, "en", anchorDate=datetime(2024, 1, 5))
# when -> 2024-01-09 09:30, action -> 'remind me to water plants'
```

See [`examples/asr_postproc.py`](examples/asr_postproc.py).

### In an OVOS skill vs. standalone

The library behaves the same way in both settings. Only the caller changes.

```python
# In an OVOS skill: language and anchor come from the session
when, _ = extract_datetime(utterance, self.lang)

# Standalone scheduler / calendar / cron generator: you supply them
when, _ = extract_datetime(user_text, "en", anchorDate=datetime.now())
```

## Core API

| Function | Direction | Purpose |
|----------|-----------|---------|
| `extract_datetime(text, lang, anchorDate=None, default_time=None)` | text to datetime | Date/time from a phrase, plus leftover text |
| `extract_duration(text, lang, *, resolution=..., replace_token="")` | text to duration | `timedelta`/`relativedelta`/float, plus leftover text |
| `extract_datetime_spans(text, lang, anchor_date=None, default_time=None)` | text to spans | Every date/time expression with its code-point offsets |
| `extract_duration_spans(text, lang)` | text to spans | Every duration with its code-point offsets |
| `nice_time(dt, lang, speech=True, use_24hour=False, use_ampm=False, variant=None)` | datetime to text | Speakable or digit clock time |
| `nice_date(dt, lang, now=None, include_weekday=True)` | datetime to text | Speakable date, shortened against `now` |
| `nice_date_time(dt, lang, now=None, use_24hour=False, use_ampm=False)` | datetime to text | Date and time combined |
| `nice_day` / `nice_weekday` / `nice_month` / `nice_year` | datetime to text | Individual date components |
| `nice_span(span, lang="en-us")` | DateSpan to text | Label for a span, at the granularity its width carries |
| `nice_duration(duration, lang, speech=True)` | seconds/timedelta to text | Speakable timespan |
| `nice_relative_time(when, relative_to=None, lang="en-us")` | datetime to text | Short "N minutes/days" phrase |
| `get_date_strings(dt, lang, date_format=None, time_format="full")` | datetime to dict | Display strings for GUI clients |

Full signatures, return shapes, and examples: [docs/api.md](docs/api.md).

### Span-native formatting

A date phrase refers to a stretch of time, not to a single instant. "July 2026"
is a whole month, and "the 1980s" is a whole decade. `extract_timespan` returns
that stretch as a **`DateSpan`**, a half-open `[start, end)` interval whose two
endpoints are **`AstroDate`** points. An `AstroDate` is a datetime that is not
capped at years 1 to 9999, so BC and far-future dates work. The width of a span
is its precision.

`nice_span` is the inverse. Give it a `DateSpan`, and it picks the label from
the width. In English a one-day span reads as a date, a month-wide span as a
month, a decade as "the 1980s", a century as "the 19th century", and a BC span
by its era year. The decade and coarser labels are English-only, apart from
Arabic and Hebrew decades of the 20th century: no other language has a
construction for them, so `nice_span` raises `NotImplementedError` there
instead of answering in English.

In English the label round-trips from the day width up, for years from 1000 AD
onward and from 32 BC back: what `nice_span` writes, `extract_timespan` reads
straight back. Nearer the era boundary the year numeral is ambiguous with a
day-of-month, and a sub-day span reads as a spoken date and time. Those get a
correct label, but not the inverse.

```python
from ovos_date_parser import extract_timespan, nice_span

span, _ = extract_timespan("the 19th century", "en")   # a DateSpan
print(span.start, "..", span.end)   # 1800-01-01 .. 1900-01-01
print(nice_span(span, "en"))        # 'the 19th century'  (round-trips)

for text in ["July 21st, 2026", "July 2026", "2026", "the 1980s", "300 BC"]:
    span, _ = extract_timespan(text, "en")
    assert nice_span(span, "en") == text
```

The datetime formatters also accept an `AstroDate` directly. A point that fits
a datetime is projected to one, so you can format extracted points without
unwrapping them:

```python
from ovos_date_parser import nice_date, nice_time
from ovos_date_parser.astrodate import AstroDate

when = AstroDate(2026, 7, 21, 15, 30)
print(nice_date(when, "en"))   # 'tuesday, july twenty-first, twenty twenty six'
print(nice_time(when, "en"))   # 'half past three'
```

Other languages get labels in their own words for the day, week, month, and
year widths. Arabic and Hebrew also name decades of the 20th century, the only
century their bare decade word can express. The round-trip guarantee is for
English only. The span grammars for other languages are in the `chronologia`
reckoning core.

Dialects resolve by prefix. `"pt-BR"`, `"pt-PT"`, and `"pt"` all reach the
Portuguese implementation. A language with no implementation raises
`NotImplementedError`, except `extract_datetime`, which tries the `dateparser`
fallback first, and the `nice_*` formatters, which fall back to a generic word
table and need a locale resource file for the language.

## Language support

Twenty-plus languages have dedicated, idiomatic implementations. Extraction
for any other language falls back to `dateparser`. Formatting falls back to a
generic word table.

- Full: dedicated implementation
- Partial: partial or generic support (a language-agnostic helper or an external library)
- None: not available, raises `NotImplementedError`

**Parsing**

| Language | `extract_datetime` | `extract_duration` |
|----------|:---:|:---:|
| ar Arabic       | Full | Full |
| ast Asturian    | Full | Full |
| az Azerbaijani  | Full | Full |
| ca Catalan      | Full | Full |
| cs Czech        | Full | Full |
| da Danish       | Full | Full |
| de German       | Full | Full |
| en English      | Full | Full |
| es Spanish      | Full | Full |
| eu Basque       | Full | Full |
| fa Persian      | Full | Full |
| fr French       | Full | Full |
| gl Galician     | Partial | Full |
| hu Hungarian    | Partial | Full |
| it Italian      | Full | Full |
| kab Kabyle      | Full | Full |
| nb Norwegian Bokmål | Full | Full |
| nl Dutch        | Full | Full |
| nn Norwegian Nynorsk | Full | Full |
| oc Occitan      | Full | Full |
| pl Polish       | Full | Full |
| pt Portuguese   | Full | Full |
| ro Romanian     | Full | Full |
| ru Russian      | Full | Full |
| sl Slovenian    | Full | Full |
| sv Swedish      | Full | Full |
| uk Ukrainian    | Full | Full |

Any language not listed uses the `dateparser` fallback for `extract_datetime`.
This fallback is good at absolute dates and weak at conversational relative
phrases. The languages on the shared duration engine (all of the above except
ar, ast, fa, kab, sv) also support the `resolution` and `replace_token` options
of `extract_duration`. See [docs/api.md](docs/api.md).

**Formatting**

| Language | `nice_date` family | `nice_time` | `nice_duration` | `nice_relative_time` |
|----------|:---:|:---:|:---:|:---:|
| ar Arabic       | Partial | Full | Full | Partial |
| ast Asturian    | Full | Full | Full | Partial |
| az Azerbaijani  | Full | Full | Full | Partial |
| ca Catalan      | Full | Full | Full | Partial |
| cs Czech        | Full | Full | Full | Partial |
| da Danish       | Full | Full | Full | Partial |
| de German       | Full | Full | Full | Partial |
| en English      | Full | Full | Full | Partial |
| es Spanish      | Full | Full | Full | Partial |
| eu Basque       | Full | Full | Full | Full |
| fa Persian      | Full | Full | Full | Partial |
| fr French       | Full | Full | Full | Partial |
| gl Galician     | Full | Full | Full | Partial |
| hu Hungarian    | Full | Full | Full | Partial |
| it Italian      | Full | Full | Full | Partial |
| kab Kabyle      | Partial | Full | Full | Partial |
| nb Norwegian Bokmål | Full | Full | Full | Partial |
| nl Dutch        | Full | Full | Full | Partial |
| nn Norwegian Nynorsk | Full | Full | Full | Partial |
| oc Occitan      | Full | Full | Full | Partial |
| pl Polish       | Full | Full | Full | Partial |
| pt Portuguese   | Full | Full | Full | Partial |
| ro Romanian     | Full | Full | Full | Partial |
| ru Russian      | Full | Full | Full | Partial |
| sl Slovenian    | Full | None | Full | Partial |
| sv Swedish      | Full | Full | Full | Partial |
| uk Ukrainian    | Full | Full | Full | Partial |

`nice_relative_time` uses a shared implementation for every language except
Basque, which has a dedicated one. The shared version is functional but not
tuned to each language's idiom.

Per-language quirks (Catalan bell-tower time, Occitan quarter idioms, Romanian
"fără un sfert", Kabyle calendar names, Portuguese `15h30` style, and more) are
documented in [docs/languages.md](docs/languages.md).

## Examples

Runnable, dependency-free scripts live in [`examples/`](examples/):

| Script | Shows |
|--------|-------|
| [`ner_temporal.py`](examples/ner_temporal.py) | Extract date/time/duration entities from free text |
| [`tts_normalization.py`](examples/tts_normalization.py) | Render timestamps to speakable words before synthesis |
| [`asr_postproc.py`](examples/asr_postproc.py) | Turn spoken transcripts into structured datetimes |
| [`multilingual.py`](examples/multilingual.py) | Parse-then-render round trip across many languages |
| [`extract.py`](examples/extract.py) | Minimal extraction reference |
| [`format.py`](examples/format.py) | Minimal formatting reference |

```bash
python examples/ner_temporal.py
```

## Documentation

- [API reference](docs/api.md): every public function and its parameters
- [Language notes](docs/languages.md): per-language behavior and known gaps
- [Adding a language](docs/adding-a-language.md): implementation guide

## Related projects

- [ovos-number-parser](https://github.com/OpenVoiceOS/ovos-number-parser): numbers
- [ovos-lang-parser](https://github.com/OVOSHatchery/ovos-lang-parser): languages
- [ovos-color-parser](https://github.com/OVOSHatchery/ovos-color-parser): colors

## License

Apache 2.0.
