An Arrow to the Heart of Python Date/Time Pain?

Manipulation of dates & times has always been a thorny problem in programming, one that gets trickier the closer you examine it. Thus it's not surprising that programming languages don't always get it right first time.

Take Java, for instance. The original Date class from java.util, had numerous issues. The Calendar class added to that package in version 1.1 offered some improvements while bringing its own problems, such as a confusing API. Things didn't improve further (in the standard library, at least) until the introduction of the java.time package in Java 8, some seventeen years later! It can take a long time to get this stuff right.

What about Python? It has its own fair share of issues. Date & time manipulation is distributed across a number of different modules in the standard library, each with different styles of API. There are too many different types, and timezones could be handled better. There are also some significant gaps in functionality.

Enter Arrow, a module that promises to provide a sensible, human-friendly approach to creating, manipulating and formatting dates & times. Arrow provides a drop-in replacement for Python's datetime class, one that is timezone-aware by default. Much of what the disparate modules of the standard library can do can also be done in Arrow, but via a simpler API that draws inspiration from requests and moment.js.

You can create an Arrow object in various ways. For example, you can call the now() function to get the time now in your local time zone, or the utcnow() function to get the time now in UTC.

now() accepts an alternative time zone, specified as a string. You can also invoke the to() method on an existing Arrow object to convert it to a given time zone.

import arrow

local_time = arrow.now()
paris_time = arrow.now("Europe/Paris")

utc_time = arrow.utcnow()
nyc_time = utc_time.to("America/New_York")

If you want to create a date/time from numeric values or parse a date/time string, you can use the get() function. For example, you can create an Arrow object representing Christmas Day in either of these ways:

xmas = arrow.get(2024, 12, 25)
xmas = arrow.get("2024-12-25")

Note that get() understands only a limited number of string formats by default, so you may need to specify one explicitly, as a second argument.

Note also that if you print xmas from the example above, you'll see both date and time information, the latter set to midnight. If you genuinely want only a date, you can convert it to a datetime.date object easily enough:

xmas_date = xmas.date()

It is easy to create new times that are shifted relative to an existing time:

week_after_xmas = xmas.shift(days=7)
an_hour_ago = arrow.now().shift(hours=-1)

Arrow objects also provide a replace() method, with which you can swap out one or more of components of a date & time for new values.

For output of times as a string, you can use the format() method:

time = arrow.now()
print(time.format("HH:mm, DD MMMM YYYY"))

Interestingly, Arrow can produce human-friendly descriptions of times relative to a given time, with localization if desired, using the humanize() method:

time = arrow.now().shift(hours=-1)
print(time.humanize())               # prints "an hour ago"
print(time.humanize(locale="fr"))    # prints "il y a une heure"

Even more interestingly, it is possible to go the other way, via the dehumanize() function:

now = arrow.now()
later = now.dehumanize("in 2 days")

There's a lot more that I haven't described here, for example time span representation, or creating custom representations by subclassing Arrow. For all this and more, read the docs.

More from Nick
All posts