An Arrow to the Heart of Python Date/Time Pain?
December 1, 2024•597 words
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.