parsedatetime: turning 'tomorrow' into a time tuple
Parse human-readable date/time strings
At a glance
- What is it?
- parsedatetime is a small Python library that reads a date and time out of ordinary English. A Calendar object takes a phrase such as 'tomorrow' and returns a struct that a datetime can be built from, or a timezone aware datetime directly. It is Apache 2.0 licensed, its test suite depends on PyICU, and its last release was v2.6 in May 2020.
- Who is it for?
- parsedatetime fits a Python service that accepts a date from a human, a chat command, a log line, or a config file, and that wants one well tested parser instead of a regex. It does not fit a project that needs a full natural language date library with locale aware month names beyond English, or one that needs to parse relative durations, since the library answers when a moment is rather than how long a period lasts.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One Calendar object, three ways to take the answer
parsedatetime parses human-readable date and time strings, and its entire public surface is small enough to describe in a paragraph. You create a Calendar, you hand it a phrase, and you get a time structure back.
The simplest form returns the pieces of a time as a tuple, which is the shape the standard library's time module understands.
import parsedatetime
cal = parsedatetime.Calendar()
cal.parse("tomorrow")The second form does the conversion for you and gives back a datetime object, which is what most callers actually want. The parse call returns two values, the time structure and a parse status, and the datetime is built from the first six fields of the structure.
from datetime import datetime
time_struct, parse_status = cal.parse("tomorrow")
datetime(*time_struct[:6])That two-value return is worth pausing on, because it is the answer to a question the API does not spell out. The status is not an exception. A phrase the parser does not understand produces a parse status that says so, and the time structure is filled with the current time. Nothing raises, so a caller that ignores the status will get a plausible datetime for a phrase that meant nothing, which is the failure mode to watch for when the input comes from a user.
The third form handles timezones, and it does so through a separate method that takes a timezone object rather than returning a naive datetime. The library does not implement timezones itself, it accepts one and attaches it, which is why the example imports pytz.
import parsedatetime
from pytz import timezone
cal = parsedatetime.Calendar()
datetime_obj, _ = cal.parseDT(datetimeString="tomorrow", tzinfo=timezone("US/Pacific"))The method name is different as well, parseDT rather than parse, and the arguments are keyword arguments here, which is the only place in the three examples where they are used that way.
Installing the package, and the environment the maintainer uses
The install is a single pip command.
pip install parsedatetimeThe package targets Python 3 and is tested with Python 3.9. Python 2.7 support is not a separate branch of the current code, it is the v2.6 release tag, which the project points at for anyone who still needs it, and the fact that this is stated as a release rather than a maintenance policy tells you how the project is being run now.
For development, the project uses a pipenv virtual environment, and the Makefile has a target that sets it up.
make envThat target does two installs. The first is the dev dependencies plus the production ones, pinned to Python 3.9, and the second installs the formatter with the pre-release flag, because the formatter was still listed as a beta library at the time and pipenv will refuse a pre-release version unless it is told to allow it. That detail is the kind of thing that ages badly, and it is a reminder that the development setup here was frozen at a particular moment.
The Makefile also has an info target that prints the pipenv version and the Python version inside the environment, and the dev target that installs the dev dependencies without pinning a Python version. Those small targets are the ones that save time when a virtual environment is behaving oddly.
The repository layout matches an older but tidy Python project. There is the package directory, a tests directory, a setup file, a setup configuration, a pytest configuration, a Pipfile with its lock file, a Makefile, an AUTHORS file, a CHANGES file, an epydoc configuration, a notes directory, an examples directory, and configuration for CircleCI, coverage, and Renovate. The presence of a Pipfile alongside a lock file is itself a dating signal, since pipenv was the tool of choice before the lock file became standard in pip itself.
The test suite and the PyICU dependency
The tests run through the Makefile, and they start by cleaning the working tree.
make testThe clean step removes the build and dist directories and deletes the compiled bytecode files, which is there so that a stale .pyc cannot make a test pass. The test target then runs pytest inside the pipenv environment.
The interesting part is the dependency behind those tests. They need PyICU, and the project installs it with the pyicu-binary package so that the source build step is skipped. PyICU in turn depends on icu4c, which is the C library holding the locale and calendar data. On macOS that library comes from homebrew.
brew install icu4cSo a contributor on a Mac has to install a native library before the tests will run, and the reason is not the parser, it is the internationalisation data that ICU provides. That is worth understanding before you go looking for a bug in the parsing code when the failure is really a missing shared library.
Running the suite against several Python versions is a separate target, and the output the project shows is a single line per interpreter.
$ make toxThe result line reads py39: commands succeeded, and the explanation given is that tox builds a separate virtual environment for each Python version and runs the tests inside it. The line is a marker of a Python 3.9 era project, since the target number in the name is the version the environment was pinned to.
The lint target is equally traditional. It writes the flake8 output to a violations file and appends the mypy output to the same file, so both tools report into one artefact, and it depends on the clean step for the same reason the tests do.
The packaging reads its own version out of the package
The setup file is unusual in one specific way, and the way it works is a small piece of engineering worth understanding.
Rather than repeating the version, the author, and the license as literals, the setup file opens the package's own init file and pulls those values out of it with a regular expression that looks for the dunder metadata attributes.
metadata = read(os.path.join(cwd, 'parsedatetime', '__init__.py'))
def extract_metaitem(meta):
# swiped from https://hynek.me 's attr package
meta_match = re.search(r"""^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]""".format(meta=meta),
metadata, re.MULTILINE)
if meta_match:
return meta_match.group(1)
raise RuntimeError('Unable to find __{meta}__ string.'.format(meta=meta))The setup call then takes the version, the author, the email, the URL, the download URL, the description, and the license straight from those lookups. The consequence is that the version number lives in exactly one place, the package itself, and the distribution metadata cannot drift away from the code it describes. The comment credits a different project, hynek.me's attr package, for the idea of pulling metadata out of the source rather than duplicating it.
The rest of the setup is conventional. The packages are found with find_packages, excluding the tests and the docs directory, and the long description is the README read from disk. The declared platforms are Any, and the classifiers give the release a status of 5, which is the Production and Stable label, with the audience given as developers, the license as Apache, the operating system as independent, the topic as text processing, and Python 3 and Python 3.9 listed as the supported versions.
The build itself goes through a check target that runs the linters and then the setup file's own check command, and the upload target calls twine against the dist directory.
The ptc constants and the Chandler history
There is one implementation note in the project, and it explains an object you will see if you read the source.
The Calendar class holds a member property named ptc, and it is created while the class is initialised, as an instance of the CalendarConstants class in the parsedatetime_consts module. In other words, every Calendar carries its own copy of the constant table, and the constants are the grammar the parser matches against, the words and the units and the modifiers that it knows how to interpret.
That design is what makes the library extendable without touching the parser, and it is also what a caller overrides when the default grammar is not the one they need.
The history section of the project is a first person account, and it is short. The code was first written in other languages, among them C, Clipper, and Delphi, as part of custom systems the author worked on, and none of that earlier code was open in any sense. The rewrite happened when the author joined the Open Source Applications Foundation and realised that the Chandler project could use the experience of parsing date and time text, so the library was started from scratch in Python with the intent of being genuinely open. The code then grew with help from people around Chandler, and one name is singled out.
That history explains a couple of things about the shape of the project. The mixture of a Calendar object with per-instance constants is not the design a library written from scratch in 2026 would usually choose, and the compatibility with the older, more procedural versions is deliberate.
The examples directory holds a basic script, a script that shows locale handling, and a readme for the directory, and the project points at it as the place with detailed usage. The generated documentation lives in the docs directory and is also published online, with the make docs target running epydoc against the configuration file in the repository root.
Editorial conclusion
parsedatetime fits a Python service that accepts a date from a human, a chat command, a log line, or a config file, and that wants one well tested parser instead of a regex. It does not fit a project that needs a full natural language date library with locale aware month names beyond English, or one that needs to parse relative durations, since the library answers when a moment is rather than how long a period lasts. Verify first that the phrases your users type are in the grammar the library implements, since a phrase it does not recognise returns a parse status rather than an error, and that your PyICU version matches the Python version you run, because the library depends on it for the locale work.
Frequently asked questions
How do I install parsedatetime?
With pip install parsedatetime. The package targets Python 3 and is tested with Python 3.9, and Python 2.7 users are pointed at the v2.6 release tag rather than at the current code.
What does parsedatetime return when it parses a string?
The parse method returns two values, a time structure and a parse status, and the time structure can be turned into a datetime with datetime(*time_struct[:6]). The separate parseDT method returns a datetime directly and takes a timezone object, which is how you get a timezone aware result.
How do I run the parsedatetime tests?
With make test from the source directory, which cleans the tree and runs pytest inside the pipenv environment. The tests need PyICU, installed with the pyicu-binary package, and on macOS that means installing icu4c with homebrew first.
Where does the parsedatetime version number live?
In the package itself. The setup file reads the init file of the package and extracts the dunder metadata attributes for the version, author, email, URL, description, and license with a regular expression, so the distribution metadata and the code cannot disagree about the version.
Can parsedatetime parse phrases it does not understand?
It reports that through the parse status rather than by raising an exception, so a caller that ignores the status receives a time structure filled from the current time. Checking the status is what separates a parsed phrase from an unrecognised one.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bear-parsedatetime)Was this useful?