Python Arrow: Human-Friendly Dates & Times

Blog / Python · August 21, 2018 · Updated June 10, 2026 · 9 min read
Python Arrow: Human-Friendly Dates & Times

Arrow is a third-party Python library that makes working with dates, times and timestamps feel human-friendly by wrapping the standard library behind one tidy, chainable API. Instead of juggling datetime, timedelta, tzinfo and strftime, you call arrow.now(), .shift(days=+1), .to('Asia/Kolkata'), .format(...) and, best of all, .humanize() to turn a timestamp into a phrase like "an hour ago". Every Arrow object is timezone-aware and immutable by default, which removes two of the most common date bugs in Python.

This guide is written for modern Python (3.12 / 3.13 in 2026) and every example runs as-is. It is also deliberately balanced: since Python 3.9 the standard library ships zoneinfo (PEP 615) for real IANA timezones with no third-party dependency, the tiny humanize library does "5 minutes ago" on its own, and pendulum is a popular Arrow alternative. We will help you choose, not just sell you Arrow.

Key takeaways

  • Arrow gives you one fluent API for creating, parsing, shifting, converting and formatting dates and times.
  • .humanize() is the headline feature: it renders relative time such as "just now", "in 2 days" or "an hour ago".
  • Arrow objects are always timezone-aware and immutable — methods like .shift() and .to() return a new object.
  • You may not need Arrow at all: stdlib datetime + zoneinfo (Python 3.9+) covers timezones with zero dependencies, and the humanize library covers relative phrases.
  • pendulum is the closest alternative; pick based on whether you want a drop-in datetime subclass (pendulum) or an independent wrapper (Arrow).
  • Install with pip install arrow; the old pytz workflow and the legacy Asia/Calcutta name are no longer needed.

What is Arrow and why use it for human-friendly dates?

Arrow is a library and lightweight command-line tool that offers a sensible, human-oriented approach to creating, manipulating, formatting and converting dates, times and timestamps. The standard datetime module is powerful but verbose, and historically timezone handling required the external pytz package. Arrow fixes the ergonomics: a single Arrow type that is tz-aware out of the box, reads like plain English, and chains cleanly.

Install it with pip into your virtual environment:

python -m pip install arrow

How do you create the current date and time in Arrow?

Use arrow.now() for the current local time (or pass a timezone) and arrow.utcnow() for UTC. Unlike a naive datetime.now(), both return a fully timezone-aware object, so you never accidentally compare an aware time with a naive one. You can reach into the usual attributes (.year, .hour) or extract a plain date/time/datetime when you need to hand off to other code.

import arrow

utc = arrow.utcnow()
print(utc)                 # 2026-06-10T12:00:00+00:00  (tz-aware)

local = arrow.now()        # current local time, also tz-aware
kolkata = arrow.now('Asia/Kolkata')

print(utc.year, utc.hour)  # 2026 12
print(utc.date())          # datetime.date(2026, 6, 10)
print(utc.time())          # datetime.time(12, 0)
print(utc.datetime)        # underlying tz-aware datetime.datetime

How do you parse and format dates with Arrow?

arrow.get() is the flexible parser. Give it an ISO 8601 string and Arrow figures it out; give it a value plus a format token string and it parses to spec; give it a Unix timestamp and it converts. To go the other way, .format() turns an Arrow object into a string using readable tokens (YYYY, MM, DD, HH, mm, ss, ZZ).

import arrow

# Parse an ISO 8601 string (offset preserved)
a = arrow.get('2026-06-10T12:00:00+00:00')
print(a)                                   # 2026-06-10T12:00:00+00:00

# Parse with an explicit format
b = arrow.get('2026-06-10 18:30:45', 'YYYY-MM-DD HH:mm:ss')
print(b)                                   # 2026-06-10T18:30:45+00:00

# Parse a Unix timestamp
c = arrow.get(1781438400)
print(c)                                   # 2026-06-10T...+00:00

# Format back to a human-readable string
print(a.format('YYYY-MM-DD HH:mm:ss ZZ'))  # 2026-06-10 12:00:00 +00:00
print(a.format('ddd, MMM D, YYYY'))        # Wed, Jun 10, 2026

How do you show relative, human-friendly time ("x minutes ago")?

This is Arrow's signature trick. .humanize() converts the difference between an Arrow object and now into a natural-language phrase. You can control precision with granularity= (a single unit or a list of units), compare against another Arrow instead of now by passing it as the first argument, and drop the "ago"/"in" framing with only_distance=True. Arrow even supports localized output via the locale= argument.

import arrow

past = arrow.utcnow().shift(hours=-1, minutes=-5)
future = arrow.utcnow().shift(days=+2)

print(past.humanize())                       # an hour ago
print(future.humanize())                     # in 2 days

# Control precision
print(past.humanize(granularity='minute'))   # 65 minutes ago
print(past.humanize(granularity=['hour', 'minute']))  # an hour and 5 minutes ago

# Relative to another Arrow object (not 'now')
start = arrow.get('2026-06-10T12:00:00+00:00')
end = arrow.get('2026-06-10T15:30:00+00:00')
print(end.humanize(start))                   # in 3 hours
print(end.humanize(start, only_distance=True))  # 3 hours

# Localized output
print(future.humanize(locale='fr_fr'))       # dans 2 jours

How do you shift dates with Arrow, and why are Arrow objects immutable?

Use .shift() for relative arithmetic — pass keyword units like days, weeks, hours, months or years with a +/- sign. Use .replace() to set an absolute field ("make the hour 9"). The crucial detail: Arrow objects are immutable, so .shift() and .replace() return a new Arrow object and never mutate the original. That immutability is what makes Arrow safe to pass around — much like preferring immutable structures when working with Python collections.

(Note: very old Arrow used .replace(weeks=+2) for relative arithmetic. That plural-shift behaviour is gone — use .shift() for deltas and reserve .replace() for absolute values.)

import arrow

now = arrow.get('2026-06-10T12:00:00+00:00')

# Relative arithmetic with .shift() -> returns a NEW object
tomorrow = now.shift(days=+1)
next_week = now.shift(weeks=+1, hours=-2)
last_month = now.shift(months=-1)

print(now)        # 2026-06-10T12:00:00+00:00  (unchanged - immutable)
print(tomorrow)   # 2026-06-11T12:00:00+00:00
print(next_week)  # 2026-06-17T10:00:00+00:00

# Absolute set with .replace()
nine_am = now.replace(hour=9, minute=0, second=0)
print(nine_am)    # 2026-06-10T09:00:00+00:00

How does Arrow handle timezones?

Every Arrow object carries a timezone, and .to() converts to any IANA zone by name. Because the object starts tz-aware, conversion is unambiguous: arrow.utcnow().to('Asia/Kolkata') gives you the same instant expressed in Indian Standard Time. Note the canonical IANA name is Asia/Kolkata; the older Asia/Calcutta still resolves as an alias but should not be used in new code.

import arrow

utc = arrow.utcnow()

ist = utc.to('Asia/Kolkata')
ny = utc.to('America/New_York')

print(utc.format('YYYY-MM-DD HH:mm:ss ZZ'))  # ... +00:00
print(ist.format('YYYY-MM-DD HH:mm:ss ZZ'))  # ... +05:30
print(ny.format('YYYY-MM-DD HH:mm:ss ZZ'))   # ... -04:00

# Same instant, different wall-clock representations
print(utc == ist)   # True

Arrow vs datetime + zoneinfo, pendulum and humanize

Here is the honest part. You do not need Arrow for any of this. Since Python 3.9, the standard library includes zoneinfo (PEP 615), which gives you real IANA timezones with no third-party dependency — this finally retired the long-standing pytz requirement. For relative "x ago" phrasing, the small humanize library does exactly that job. So the stdlib stack — datetime + timedelta + zoneinfo + humanize — reproduces almost everything Arrow offers.

The equivalent of the Arrow examples above, using only the standard library plus humanize (pip install humanize):

from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo          # stdlib since Python 3.9 (PEP 615)
import humanize                        # pip install humanize

# Current UTC time, tz-aware (no pytz needed)
now = datetime.now(timezone.utc)

# Convert to a real IANA timezone
ist = now.astimezone(ZoneInfo('Asia/Kolkata'))
print(ist.strftime('%Y-%m-%d %H:%M:%S %z'))   # ... +0530

# Relative arithmetic (datetime is also immutable -> new object)
tomorrow = now + timedelta(days=1)

# Parse ISO 8601
parsed = datetime.fromisoformat('2026-06-10T12:00:00+00:00')

# Human-friendly 'x ago' via the humanize library
five_min_ago = datetime.now() - timedelta(minutes=5)
print(humanize.naturaltime(five_min_ago))     # '5 minutes ago'

Both datetime and Arrow objects are immutable, so the "returns a new object" guarantee is not unique to Arrow. What Arrow buys you is one cohesive API instead of stitching four tools together. pendulum (now on 3.x) takes a different tack: its datetime type subclasses the stdlib datetime, so it is closer to a drop-in replacement, and it has its own .diff_for_humans() for relative phrasing.

Use the table below to choose:

Capability Arrow datetime + zoneinfo (stdlib) Pendulum humanize
Human "x ago" Built in (.humanize()) No — pair with humanize Built in (.diff_for_humans()) Its entire job (naturaltime)
Timezone handling IANA, always tz-aware IANA via ZoneInfo (3.9+) IANA, always tz-aware N/A
Parsing Flexible (arrow.get) fromisoformat / strptime Flexible (pendulum.parse) N/A
Immutability Immutable Immutable Immutable N/A
Dependency Third-party None (standard library) Third-party Third-party (tiny)
datetime compat Wrapper (use .datetime) Native Subclass of datetime N/A
Best for One API for everything Zero-dependency projects Drop-in datetime + rich diffs You only need human strings

When should you not use Arrow?

Arrow is excellent, but it is not always the right call:

  • You want zero dependencies. If timezones and ISO parsing are all you need, datetime + zoneinfo already ship with Python — adding Arrow is avoidable weight.
  • You only need "x minutes ago". Reach for the much smaller humanize library rather than pulling in a full date toolkit.
  • You need a drop-in datetime. Code or libraries that expect a real datetime instance may be happier with pendulum (a subclass) than with Arrow's wrapper, where you call .datetime to unwrap.
  • Hot loops. Wrapping every timestamp in an object adds overhead; for tight numeric work, raw datetime/timestamp floats are leaner — a judgement call covered in our notes on Python coding techniques and programming practices.

For streaming or large time-series data, combine whichever date library you pick with lazy iteration; see Python yield and generators for keeping memory flat while processing timestamps one at a time.

Frequently Asked Questions

What is the Arrow library in Python used for?

Arrow is a third-party Python library for creating, parsing, manipulating, formatting and converting dates, times and timestamps with a single human-friendly API. Its standout feature is .humanize(), which turns a timestamp into a natural-language phrase like "an hour ago" or "in 3 days". Every Arrow object is timezone-aware and immutable, which prevents two of the most common date bugs: mixing naive and aware times, and accidentally mutating a shared value.

How do I show "x minutes ago" in Python?

With Arrow, call .humanize() on an Arrow object: arrow.utcnow().shift(minutes=-5).humanize() returns "5 minutes ago". Without Arrow, the small humanize library does it directly: humanize.naturaltime(datetime.now() - timedelta(minutes=5)) also returns "5 minutes ago". Pendulum offers .diff_for_humans() for the same result. Choose Arrow or pendulum if you want a full date toolkit, or humanize if relative phrasing is all you need.

Do I still need pytz with Arrow or modern Python?

No. Since Python 3.9 the standard library includes zoneinfo (PEP 615), which provides real IANA timezones from the system database with no external package. Arrow uses tz-aware objects internally and accepts IANA names like Asia/Kolkata directly in .to(), so you never touch pytz. If you are not using Arrow, datetime.now(ZoneInfo('Asia/Kolkata')) is the modern, dependency-free replacement for the old pytz pattern.

Are Arrow objects mutable?

No. Arrow objects are immutable. Methods such as .shift(), .replace() and .to() always return a brand-new Arrow object and leave the original unchanged. This makes Arrow values safe to pass between functions and store as defaults without fear of one caller altering another's data. The standard library's datetime is also immutable, so this guarantee is shared, not unique to Arrow.

What is the difference between Arrow and Pendulum?

Both wrap Python date/time handling in a friendlier, tz-aware, immutable API with relative humanization. The key difference is compatibility: Pendulum's datetime type subclasses the standard datetime, so it can act as a near drop-in replacement, whereas Arrow is a separate wrapper that exposes the underlying datetime via .datetime. Pick Pendulum when code expects a real datetime; pick Arrow when you prefer its single, self-contained API and .humanize() ergonomics.

How do I convert a timezone with Arrow?

Call .to() with an IANA timezone name on any Arrow object: arrow.utcnow().to('Asia/Kolkata') converts the current UTC instant to Indian Standard Time. Because Arrow objects are always timezone-aware, the conversion is unambiguous and the two values compare as equal (same instant, different wall clock). Use the canonical name Asia/Kolkata rather than the deprecated Asia/Calcutta alias in new code.

Need help getting dates, times and timezones right across a real product — APIs, scheduling, audit trails and user-facing "x ago" displays? MicroPyramid has built data-intensive Python systems for startups and enterprises for 12+ years across 50+ delivered projects, using Arrow, the stdlib datetime/zoneinfo stack, and frameworks like Django and FastAPI. Explore our Python development services to see how we ship reliable, timezone-correct features faster with AI-assisted delivery.

Share this article