Back to Blog
Python

Python Arrow Humanize and Shift

python arrow humanize and shift: Learn how to use Arrow's humanize and shift methods to format relative time and adjust dates in Python, with practical examples and ed...

arrowdatetimehumanizeshiftrelative-timetimezone
An illustration of a calendar with an arrow pointing forward and a speech bubble showing relative time text, representing Arrow's shift and humanize methods.

When working with dates in Python, Arrow provides two methods that cover common needs: humanize for relative time strings and shift for calendar arithmetic. This article explains how to use python arrow humanize and shift effectively, including syntax, edge cases, and practical workflows.

What Arrow's humanize and shift Do

Arrow is a library that offers a more intuitive API for date and time manipulation than the standard library. Two of its most frequently used methods are humanize and shift.

humanize converts a datetime into a human-readable relative string such as "2 hours ago" or "in 3 days". It is useful for activity feeds, notification timestamps, and any UI that needs to show elapsed time without absolute dates.

shift moves a datetime by a specified amount of time. Unlike simple timedelta arithmetic, shift handles calendar-aware units like months and years, which vary in length. This makes it suitable for scheduling, billing cycles, and recurring events.

Both methods work on Arrow objects, which are timezone-aware by default. That awareness matters for correct relative time calculation and for shifting across daylight saving time boundaries.

Using humanize for Relative Time

The humanize method returns a string describing the difference between the Arrow object and another moment, defaulting to the current time. The basic syntax is:

import arrow past = arrow.get('2024-01-01T12:00:00') past.humanize() # e.g., "2 days ago"

You can pass a reference time as the first argument to compare against a specific moment instead of now:

future = arrow.get('2025-06-01T00:00:00') now = arrow.get('2024-12-01T00:00:00') future.humanize(now) # "in 6 months"

The default granularity is automatic: Arrow picks the largest unit that fits the difference (years, months, weeks, days, hours, minutes, seconds). You can control the granularity with the granularity parameter, which accepts a string or a list of strings:

delta = arrow.get('2024-03-15T10:30:00') - arrow.get('2024-03-14T09:00:00') delta.humanize(granularity='day') # "a day ago" delta.humanize(granularity=['hour', 'minute']) # "25 hours and 30 minutes ago"

When you pass a list, Arrow includes all specified units down to the smallest one, even if the difference is zero for a larger unit. This is useful when you need a precise breakdown.

The locale parameter lets you localize the output. For example, locale='fr' produces French relative time. Arrow uses the locale package for translations, so you need to have that installed for non-English locales.

Using shift to Adjust Dates and Times

The shift method returns a new Arrow object moved by the given keyword arguments. Supported units are years, months, weeks, days, hours, minutes, seconds, and microseconds. You can combine multiple units in a single call:

import arrow start = arrow.get('2024-01-31T12:00:00') shifted = start.shift(months=1, days=2) # Result: 2024-03-02T12:00:00 (because Feb has 29 days in 2024)

Negative values move backward:

start.shift(days=-1) # previous day start.shift(years=-2) # two years earlier

shift performs calendar arithmetic, not fixed-duration arithmetic. For months, it clamps the day to the last valid day of the target month. For example, shifting January 31 by one month gives February 29 in a leap year, or February 28 otherwise. This behavior prevents invalid dates.

Here is a summary of the shift units and their behavior:

UnitDescriptionExample
yearsAdds or subtracts calendar yearsshift(years=1)
monthsAdds or subtracts calendar monthsshift(months=-2)
weeksAdds or subtracts weeks (7 days)shift(weeks=2)
daysAdds or subtracts daysshift(days=10)
hoursAdds or subtracts hoursshift(hours=-5)
minutesAdds or subtracts minutesshift(minutes=30)
secondsAdds or subtracts secondsshift(seconds=45)
microsecondsAdds or subtracts microsecondsshift(microseconds=-1)

The result of shift is always a new Arrow object; the original remains unchanged. This immutability avoids accidental side effects when you pass Arrow objects around.

Combining humanize and shift in Real Workflows

A common pattern is to shift a date to a future or past point and then humanize it relative to the current time. For example, a subscription service might calculate the next renewal date and display "in 3 days" to the user.

import arrow now = arrow.now() next_renewal = now.shift(months=1) print(next_renewal.humanize(now)) # "in a month"

Another scenario is showing a post's age after adjusting for timezone. If you store UTC timestamps, you can convert to the user's timezone, shift to the local calendar day, and then humanize:

utc_time = arrow.get('2024-11-20T23:30:00', tzinfo='UTC') local = utc_time.to('America/New_York') local.shift(days=1).humanize() # "tomorrow" or "in a day" depending on now

When combining both methods, remember that humanize compares instants, not calendar dates. If you shift by months, the result may land on a different day of the month, which can affect the relative string. Always test with concrete dates to confirm the output matches your expectation.

Handling Timezones and Locale

Arrow objects are timezone-aware by default. This is critical for humanize because the difference between two moments depends on the timezone offset. For example, comparing a UTC time to a local time without conversion can produce an off-by-one-hour error.

utc = arrow.get('2024-12-01T12:00:00', tzinfo='UTC') ny = arrow.get('2024-12-01T08:00:00', tzinfo='America/New_York') # These represent the same instant, but naive subtraction would be wrong.

Always convert to a common timezone before calling humanize or shift if you are comparing across zones. Arrow's to() method handles conversion.

For humanize, the locale parameter changes the language of the output. Arrow relies on the locale package, so you must install it (pip install locale) and import it for non-English locales. Without that, passing a locale other than en raises an error.

Edge Cases and Common Pitfalls

Several edge cases can trip up developers new to humanize and shift.

Month-end clamping – Shifting from January 31 to February produces February 28 or 29, not March 2 or 3. If you need the last day of the next month, you must handle that explicitly, for example by checking the day of the month after shifting.

Negative shifts across month boundariesshift(days=-1) from March 1 gives February 29 in a leap year, which is correct. But shift(months=-1) from March 31 gives February 28 (or 29), not February 31. This is intentional, but you should be aware of it when calculating due dates.

humanize with future dates – The output uses "in X" for future dates and "X ago" for past dates. The exact wording depends on the locale and granularity. For example, humanize(granularity='hour') on a future time returns "in 2 hours" even if the difference is less than 2 hours but more than 1.5 hours.

Leap years and DSTshift(years=1) from February 29, 2024 gives February 28, 2025 because 2025 is not a leap year. Similarly, shifting across a daylight saving time transition can change the wall-clock time if you shift by days. Arrow preserves the timezone, so the result may have a different UTC offset.

Immutability – Both humanize and shift do not modify the original Arrow object. humanize returns a string, and shift returns a new Arrow. If you forget to assign the result of shift, you will still be working with the original date.

Performance and Alternatives

Arrow is a convenience library, not a performance-critical one. Creating an Arrow object involves more overhead than using datetime directly. For high-throughput code that processes millions of timestamps, the standard library's datetime and zoneinfo may be more appropriate.

However, for typical application code where readability and maintainability matter, the overhead is negligible. humanize and shift themselves are implemented in pure Python and do not introduce significant latency for occasional calls.

If you need only relative time strings and want to avoid the Arrow dependency, you can implement a simple version with datetime and timedelta, but you will lose calendar-aware month shifting and localization. Arrow's shift is particularly valuable when you need to handle months and years correctly without writing your own logic.

For timezone handling, Python 3.9+ includes zoneinfo, which makes the standard library more capable. Still, Arrow's ergonomic API often reduces boilerplate, especially when you need to chain operations like shift followed by humanize.

When deciding whether to use Arrow, consider the complexity of your date logic. If you only need timedelta-style arithmetic, the standard library suffices. If you need calendar-aware shifts, relative time strings, and timezone conversion in a concise way, Arrow is a solid choice.

python arrow humanize and shift: Practical Usage and Code Ex | RYUSLOG DEV