Python datetimes with Arrow or Pendulum


Python datetimes with Arrow or Pendulum

I spent a little time looking for those small, less know libraries that people are not shouting about, but can really be of use. I found two libraries that replace/enhance the Python datetime module.

Now you may be thinking that the datetime module is fine and why would I want more, but take a look at what these two do. You might end up thumping your head knowing you spent time coding datetime functions and addons, only to find these do it better.

Python Arrow

Arrow is the first. It creates Arrow datetime objects, but these can easily be converted into native datetime objects, or created from them.

Now lets create a datetime object set to the us/pacific time zone.

from datetime import datetime
import pytz #needs to be installed, or you need to create a timezone class

datetime.now(pytz.timezone('US/Pacific')

Now with arrow

import arrow

arrow.now("US/Pacific")

Arrow has the time zone classes there for you.

Arrow datetime objects can be created using the get() method. You use this same method for multiple create techniques.

import datetime
import arrow
arrow.get(datetime.datetime.utcnow())
<Arrow [2024-06-30T08:14:18.887980+00:00]>
arrow.get(148823399993)
<Arrow [1974-09-19T11:49:59.993000+00:00]>
arrow.get(datetime.datetime(2020,10,5), "US/Pacific")
<Arrow [2020-10-05T00:00:00-07:00]>

This is ok, but lets get to the good bits

dt = arrow.get(datetime.datetime(2020,10,5), "US/Pacific")
dt.shift(weeks=+4)
<Arrow [2020-11-02T00:00:00-08:00]>
dt.replace(year=2024)
<Arrow [2024-10-05T00:00:00-07:00]>

Now we can see how Arrow can help us.

The last item to look at with Arrow is the humanize and dehumanize functions

dt = arrow.utcnow()
future = dt.shift(minutes=90)
future.humanize(dt, granularity="minute")
'in 90 minutes'
future.humanize(dt, granularity="hour")
'in an hour'
future.humanize(dt, granularity=["hour", "minute"])
'in an hour and 30 minutes'
dt_now = arrow.utcnow()
dt_now
<Arrow [2024-06-30T08:26:19.727496+00:00]>
dt_now.dehumanize("2 days ago")
<Arrow [2024-06-28T08:26:19.727496+00:00]>

Humanize also accepts language arguments

future.humanize(dt, granularity=["hour", "minute"], locale="ko-kr")
'한시간 30분 후'

There are also range functions

start = datetime.datetime(2021,5,5,12,45)
end = datetime.datetime(2021,5,6,1,15)
for t in arrow.Arrow.span_range("hour", start, end):
print(t)

(<Arrow [2021-05-05T12:00:00+00:00]>, <Arrow [2021-05-05T12:59:59.999999+00:00]>)
(<Arrow [2021-05-05T13:00:00+00:00]>, <Arrow [2021-05-05T13:59:59.999999+00:00]>)
(<Arrow [2021-05-05T14:00:00+00:00]>, <Arrow [2021-05-05T14:59:59.999999+00:00]>)
(<Arrow [2021-05-05T15:00:00+00:00]>, <Arrow [2021-05-05T15:59:59.999999+00:00]>)
(<Arrow [2021-05-05T16:00:00+00:00]>, <Arrow [2021-05-05T16:59:59.999999+00:00]>)
(<Arrow [2021-05-05T17:00:00+00:00]>, <Arrow [2021-05-05T17:59:59.999999+00:00]>)
(<Arrow [2021-05-05T18:00:00+00:00]>, <Arrow [2021-05-05T18:59:59.999999+00:00]>)
(<Arrow [2021-05-05T19:00:00+00:00]>, <Arrow [2021-05-05T19:59:59.999999+00:00]>)
(<Arrow [2021-05-05T20:00:00+00:00]>, <Arrow [2021-05-05T20:59:59.999999+00:00]>)
(<Arrow [2021-05-05T21:00:00+00:00]>, <Arrow [2021-05-05T21:59:59.999999+00:00]>)
(<Arrow [2021-05-05T22:00:00+00:00]>, <Arrow [2021-05-05T22:59:59.999999+00:00]>)
(<Arrow [2021-05-05T23:00:00+00:00]>, <Arrow [2021-05-05T23:59:59.999999+00:00]>)
(<Arrow [2021-05-06T00:00:00+00:00]>, <Arrow [2021-05-06T00:59:59.999999+00:00]>)
(<Arrow [2021-05-06T01:00:00+00:00]>, <Arrow [2021-05-06T01:59:59.999999+00:00]>)

As a web app developer, I can see Arrow making life a whole lot easier. There is more, but lets move on.

Python Pendulum

There are a lot of crossovers between Arrow, and Pendulum, but they are different enough to be considered two different approaches, with differences in functionality.

import datetime
import pendulum
datetime.datetime.utcnow()
datetime.datetime(2024, 6, 30, 8, 44, 51, 379215)
pendulum.now()
DateTime(2024, 6, 30, 15, 45, 2, 575776, tzinfo=Timezone('Asia/Vientiane'))

Note that Pendulum automatically detects and adds in time zones. I’m in a city called Phitsanulok, which is in the Vientiane/Bangkok time zone

Altering the timezone is no issue though

pendulum.now("US/Pacific")
DateTime(2024, 6, 30, 1, 48, 52, 72783, tzinfo=Timezone('US/Pacific'))

There is also a today, tomorrow and yesterday..

pendulum.today("Europe/London")
DateTime(2024, 6, 30, 0, 0, 0, tzinfo=Timezone('Europe/London'))
pendulum.tomorrow("Europe/London")
DateTime(2024, 7, 1, 0, 0, 0, tzinfo=Timezone('Europe/London'))
pendulum.yesterday("Europe/London")
DateTime(2024, 6, 29, 0, 0, 0, tzinfo=Timezone('Europe/London'))

There are from_timestamp() and from_format() methods, but they are fairly obvious, but parse() is something better

pendulum.parse("2014-05-21 16:00:00")
DateTime(2014, 5, 21, 16, 0, 0, tzinfo=Timezone('UTC'))

This cannot parse any datetime, but it does support a number of datetime standards. The full list is in the docs.

localization and humanization is also there, but somewhat different from Arrow.

pendulum.set_locale("ru")
pendulum.now().format('dddd DD MMMM YYYY')
'воскресенье 30 июня 2024'
future = pendulum.now().add(hours=5)
future.diff_for_humans(locale="fr")
'dans 4 heures'

Pendulum has a LOT of functionality when it comes to formatting datetimes. Way to many for me to list here, but worth some serious time looking into.

Pendulums answer to timedelta is different from arrow’s but very flexible

now = pendulum.now()
now
DateTime(2024, 6, 30, 16, 7, 59, 395219, tzinfo=Timezone('Asia/Vientiane'))
now.subtract(years=5)
DateTime(2019, 6, 30, 16, 7, 59, 395219, tzinfo=Timezone('Asia/Vientiane'))
now.subtract(years=4, months=3, days=2, hours=1)
DateTime(2020, 3, 28, 15, 7, 59, 395219, tzinfo=Timezone('Asia/Vientiane'))
now.add(months=1)
DateTime(2024, 7, 30, 16, 7, 59, 395219, tzinfo=Timezone('Asia/Vientiane'))
now.add(months=1, days=5)
DateTime(2024, 8, 4, 16, 7, 59, 395219, tzinfo=Timezone('Asia/Vientiane'))
now.add(months=1, days=5, hours=1)
DateTime(2024, 8, 4, 17, 7, 59, 395219, tzinfo=Timezone('Asia/Vientiane'))

From here on. Pendulum starts to be very different from Arrow.

now = pendulum.now()
dob = pendulum.datetime(1995, 11,2)
interval = now - dob
interval.years
28
interval.months
7
interval.in_months()
343
type(interval)
<class 'pendulum.interval.Interval'>
interval * 2
Duration(weeks=2990, days=6, hours=18, minutes=31, seconds=25, microseconds=31698)

Do notice that this has returned an Interval object. not a datetime object. Kinda great for working out distances past and future. We can perform arithmetic operations on this, but we get a different object type returned if we do.

One last item on Pendulum, and that is ranges

import pendulum
start = pendulum.datetime(2015,1,1)
end = pendulum.datetime(2016,1,1)
interval = pendulum.interval(start, end)
for t in interval.range("years"):
print(t)

2015-01-01 00:00:00+00:00
2016-01-01 00:00:00+00:00
for t in interval.range("months"):
print(t)

2015-01-01 00:00:00+00:00
2015-02-01 00:00:00+00:00
2015-03-01 00:00:00+00:00
2015-04-01 00:00:00+00:00
2015-05-01 00:00:00+00:00
2015-06-01 00:00:00+00:00
2015-07-01 00:00:00+00:00
2015-08-01 00:00:00+00:00
2015-09-01 00:00:00+00:00
2015-10-01 00:00:00+00:00
2015-11-01 00:00:00+00:00
2015-12-01 00:00:00+00:00
2016-01-01 00:00:00+00:00

Conclusion

Pendulum is way more powerful that Arrow, with tons of features, but I have to say, most of them I don’t think I would use that much. The Interval and Duration objects are great though and I think would be of great help, especially for saving datetimes to databases and tracking.

Arrow, is much simpler and easier to use. It adds features I like and ones I would use a lot.

Both, however, outshine the standard datetime module and will save a lot of coding and effort in many areas. I think Arrow will be added to my toolbox, with Pendulum used for more specific usecases.