Skip to main content

Extract datetimes, datetime ranges, and datetime lists from natural language text

Project description

timefhuman

PyPi Downloads per Month Coverage Status

Extract datetimes, datetime ranges, and datetime lists from natural language text. Supports Python3+^1.

✨ Try the demo or code online. No installation needed. ✨


Getting Started

Install with pip using

$ pip install timefhuman

Then, find natural language dates and times in any text.

# All these tests assume the current date is August 4, 2018 at 2 PM
>>> from timefhuman import timefhuman

>>> timefhuman("How does 5p mon sound? Or maybe 4p tu?")
[datetime.datetime(2018, 8, 6, 17, 0), datetime.datetime(2018, 8, 7, 16, 0)]

The text can contain not only datetimes but also ranges of datetimes or lists of datetimes.

>>> timefhuman('3p-4p')  # time range
[(datetime.datetime(2018, 8, 4, 15, 0), datetime.datetime(2018, 8, 4, 16, 0))]

>>> timefhuman('7/17 4PM to 7/17 5PM')  # range of datetimes
[(datetime.datetime(2018, 7, 17, 16, 0), datetime.datetime(2018, 7, 17, 17, 0))]

>>> timefhuman('Monday 3 pm or Tu noon')  # list of datetimes
[[datetime.datetime(2018, 8, 6, 15, 0), datetime.datetime(2018, 8, 7, 12, 0)]]

>>> timefhuman('7/17 4-5 or 5-6 PM')  # list of ranges of datetimes!
[[(datetime.datetime(2018, 7, 17, 16, 0), datetime.datetime(2018, 7, 17, 17, 0)),
  (datetime.datetime(2018, 7, 17, 17, 0), datetime.datetime(2018, 7, 17, 18, 0))]]

Durations are also supported.

>>> timefhuman('30 minutes')  # duration
[datetime.datetime(2018, 8, 4, 14, 30)]

>>> timefhuman('30-40 mins')  # range of durations
[(datetime.datetime(2018, 8, 4, 14, 30), datetime.datetime(2018, 8, 4, 14, 40))]

>>> timefhuman('30 or 40m')  # list of durations
[[datetime.datetime(2018, 8, 4, 14, 30), datetime.datetime(2018, 8, 4, 14, 40)]]

When possible, timefhuman will infer any missing information, using context from other datetimes.

>>> timefhuman('3-4p')  # infer "PM" for "3"
[(datetime.datetime(2018, 8, 4, 15, 0), datetime.datetime(2018, 8, 4, 16, 0))]

>>> timefhuman('7/17 4 or 5 PM')  # infer "PM" for "4" and infer "7/17" for "5 PM"
[[datetime.datetime(2018, 7, 17, 16, 0), datetime.datetime(2018, 7, 17, 17, 0)]]

>>> timefhuman('7/17, 7/18, 7/19 at 9')  # infer "9a" for "7/17", "7/18"
[[datetime.datetime(2018, 7, 17, 9, 0), datetime.datetime(2018, 7, 18, 9, 0),
  datetime.datetime(2018, 7, 19, 9, 0)]]

>>> timefhuman('3p -4p PDT')  # infer timezone "PDT" for "3p"
[(datetime.datetime(2018, 8, 4, 15, 0, tzinfo=<DstTzInfo 'US/Pacific' ...>),
  datetime.datetime(2018, 8, 4, 16, 0, tzinfo=<DstTzInfo 'US/Pacific' ...>))]

You can also use natural language descriptions of dates and times.

>>> timefhuman('next Monday')
[datetime.datetime(2018, 8, 6, 0, 0)]

>>> timefhuman('next next Monday')
[datetime.datetime(2018, 8, 13, 0, 0)]

>>> timefhuman('last Wednesday of December')
[datetime.datetime(2018, 12, 26, 0, 0)]

>>> timefhuman('afternoon')
[datetime.datetime(2018, 8, 4, 15, 0)]

>>> timefhuman('1 month ago')
[datetime.datetime(2018, 7, 5, 14, 0)]

See more examples in eval/short.py and tests/test_e2e.py.

Performance

Benchmarks were run on an Apple M3 MacBook Air with 16 GB RAM, macOS 26.3.1, Python 3.13.3. acc is member-level correctness for the dataset immediately to the left. dateparser* uses dateparser.parse for short inputs and dateparser.search_dates for document corpora.

parser short (ms) acc core (ms) acc sea_76k (ms) acc sea_560k (ms) acc
timefhuman 0.1 28/28 0.1 14/14 4.9 57/57 26.9 94/94
datefinder 0.3 10/28 0.2 9/14 38.6 54/57 485.4 37/94
dateparser* 56.1 15/28 109.6 9/14 332.7 53/57 >2s timeout

datefinder and dateparser* both pick up extra HTML and metadata false positives on these corpora, so speed alone overstates quality. Lower-accuracy baselines are omitted here for readability. Full results, grouped correctness, repro commands, and raw match dumps are in benchmarks/README.md.

Advanced Usage

For more configuration options, simply create a tfhConfig object.

from timefhuman import tfhConfig
config = tfhConfig()

Return matched text: You can additionally grab the matched text from the input string, as well as the string indices of the matched substring. This is useful for modifying the input string, for example.

>>> config = tfhConfig(return_matched_text=True, now=datetime.datetime(2025, 2, 23))

>>> timefhuman('We could maybe do 3 PM, if you still have time', config=config)
[('3 PM', (18, 22), datetime.datetime(2025, 2, 23, 15, 0))]

Change 'Now': You can configure the default date that timefhuman uses to fill in missing information. This would be useful if you're extracting dates from an email sent a year ago.

>>> config = tfhConfig(now=datetime.datetime(2018, 8, 4, 0, 0))

>>> timefhuman('upcoming Monday noon', config=config)
[datetime.datetime(2018, 8, 6, 12, 0)]

You can also set a default timezone, by again using the config's now.

>>> config = tfhConfig(
...     now=datetime.datetime(2018, 8, 4, tzinfo=pytz.timezone('US/Pacific'))
... )

>>> timefhuman('Wed', config=config)
[datetime.datetime(2018, 8, 8, 0, 0, tzinfo=<DstTzInfo 'US/Pacific' ...>)]

>>> timefhuman('Wed EST', config=config)  # EST timezone in the input takes precedence
[datetime.datetime(2018, 8, 8, 0, 0, tzinfo=<DstTzInfo 'US/Michigan' ...>)]

Use explicit information only: Say you only want to extract dates OR times OR timedeltas. You don't want the library to infer information. You can disable most inference by setting infer_datetimes=False. Instead of always returning a datetime, timefhuman will be able to return date, time, or timedelta objects depending on what's provided.

>>> config = tfhConfig(infer_datetimes=False)

>>> timefhuman('3 PM', config=config)  # time
[datetime.time(15, 0)]

>>> timefhuman('12/18/18', config=config)  # date
[datetime.date(2018, 12, 18)]

>>> timefhuman('30 minutes', config=config)  # duration
[datetime.timedelta(seconds=1800)]

Past datetimes: By default, datetimes are assumed to occur in the future, so if "3pm" today has already passed, the returned datetime will be for tomorrow. However, if datetimes are assumed to have occurred in the past (e.g., from an old letter, talking about past events), you can configure the direction.

>>> from timefhuman import Direction
>>> config = tfhConfig(direction=Direction.previous, now=datetime.datetime(2018, 8, 4, 14))

>>> timefhuman('3PM')  # the default
[datetime.datetime(2018, 8, 4, 15, 0)]

>>> timefhuman('3PM', config=config)  # changing direction
[datetime.datetime(2018, 8, 3, 15, 0)]

Change global defaults: You can also modify the default configuration used by timefhuman, if you don't want to manually pass in configs everywhere.

>>> from timefhuman import DEFAULT_CONFIG
>>> DEFAULT_CONFIG.now = datetime.datetime(2025, 2, 23, 12, 0, 0)

>>> timefhuman('3PM')
[datetime.datetime(2025, 2, 23, 15, 0)]

Here is the full set of supported configuration options:

@dataclass
class tfhConfig:
    # Default to the next valid datetime or the previous one
    direction: Direction = Direction.next
    
    # Always produce datetime objects. If no date, use the current date. If no time,
    # use midnight. If timedelta, add it to the current datetime. Still allows ranges
    # (tuples) of datetimes and lists of datetimes.
    infer_datetimes: bool = True
    
    # The 'current' datetime, used if infer_datetimes is True
    now: datetime | None = None
    
    # Return the matched text from the input string
    return_matched_text: bool = False

Development

Install the development version.

$ pip install -e .\[test\]  # for zsh

To run tests and simultaneously generate a coverage report, use the following commands:

$ py.test --cov --cov-report=html
$ open htmlcov/index.html

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

timefhuman-0.1.5.tar.gz (41.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

timefhuman-0.1.5-py3-none-any.whl (40.6 kB view details)

Uploaded Python 3

File details

Details for the file timefhuman-0.1.5.tar.gz.

File metadata

  • Download URL: timefhuman-0.1.5.tar.gz
  • Upload date:
  • Size: 41.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for timefhuman-0.1.5.tar.gz
Algorithm Hash digest
SHA256 1a59dd6e62379a74d0dbfed410d67866cda2d225653cdb26e4a91448b02c304f
MD5 ff283cbe7a8d93cadf2f3ddda8fc153d
BLAKE2b-256 e8370887c1ad6630ee23ece7fc0e4a2643dfe590f65d56d58667ed8adef7c363

See more details on using hashes here.

File details

Details for the file timefhuman-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: timefhuman-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 40.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for timefhuman-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 2c61a16644e889fa12c947077b71b28ad68ab853580172cd83d7fdde015540c6
MD5 be73a8445840ab84033ddd3a9b4ec4c9
BLAKE2b-256 feb77c91421af4e50b47d988418048b5126ddc6854e5968a01bc53f450dcca99

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page