Humanizing Differences: Making Time Intervals More Readable with Pendulum

Datatypes Mar 19, 2026

Humanizing Differences: Making Time Intervals More Readable with Pendulum

Raw time differences like timedelta(days=45, hours=12) are hard for humans to understand quickly. In data science, showing "3 days ago", "2 weeks from now", or "5 months old" makes reports, dashboards, and logs far more intuitive. Pendulum’s humanizing features turn technical time deltas into natural, readable language with almost zero effort.

TL;DR — Humanizing Time with Pendulum

  • .diff() → calculates difference
  • .in_words() → a duration in words ("1 month 3 days")
  • .diff_for_humans() → relative wording ("3 days ago", "in 2 weeks")
  • .in_words(locale=...) → supports multiple languages
  • Works on both past and future intervals

1. Basic Humanizing

import pendulum

now = pendulum.now("UTC")

# Past
past = pendulum.parse("2026-02-15 10:00:00", tz="UTC")
print(past.diff(now).in_words())          # a duration, e.g. "8 months 2 weeks ..."
print(past.diff_for_humans())             # relative, e.g. "8 months ago"

# Future
future = now.add(days=14)
print(now.diff(future).in_words())        # "2 weeks"
print(future.diff_for_humans())           # "in 2 weeks"

2. Real-World Data Science Examples

import pandas as pd
import pendulum

# Small inline sample so this example runs as-is.
# In a real project you would load it with pd.read_csv("sales_data.csv")
df = pd.DataFrame({
    "customer_id": [101, 102, 103, 104, 105, 106],
    "customer_name": ["Ana", "Ben", "Chen", "Dana", "Eli", "Fay"],
    "order_date": ["2026-01-05", "2026-01-12", "2026-02-03",
                   "2026-02-18", "2026-03-01", "2026-03-09"],
    "amount": [1250.75, 890.50, 2340.00, 1780.25, 2615.40, 450.00],
    "quantity": [3, 2, 5, 4, 6, 1],
    "profit": [312.69, 222.63, 585.00, 445.06, 653.85, 112.50],
    "region": ["North", "South", "East", "North", "West", "South"],
    "category": ["Electronics", "Books", "Electronics", "Home", "Home", "Books"],
    "rating": [5, 4, None, 3, 5, 4],
    "year": [2025, 2025, 2026, 2026, 2026, 2026],
    "description": ["fast laptop", "python book", "noise cancelling headphones",
                    "desk lamp", "standing desk", "data science book"],
})
df["order_date"] = pd.to_datetime(df["order_date"]).dt.tz_localize("UTC")   # make the dates UTC-aware

# Current time
now = pendulum.now("UTC")

# Human-readable freshness
df["time_ago"] = df["order_date"].apply(
    lambda x: pendulum.instance(x).diff_for_humans(now) if pd.notna(x) else "Unknown"
)

df["days_ago"] = df["order_date"].apply(
    lambda x: (now - pendulum.instance(x)).in_words() if pd.notna(x) else "Unknown"
)

print(df[["order_date", "time_ago"]].head())

3. Advanced Humanizing Features

# Custom granularity
diff = pendulum.parse("2025-12-01").diff(now)
print(diff.in_words())                    # duration, e.g. "11 months ..."
print(diff.in_words(locale="fr"))         # same duration in French
print(pendulum.parse("2025-12-01").diff_for_humans(locale="fr"))   # e.g. "il y a 11 mois"

# Relative time in reports
for row in df.itertuples():
    print(f"Order {row.customer_id} was placed {pendulum.instance(row.order_date).diff_for_humans()}")

4. Best Practices in 2026

  • Use .diff_for_humans() for "ago / from now" labels and .in_words() for durations
  • Always parse with explicit timezone before calculating differences
  • Use locale parameter for international applications
  • Combine with pandas .apply() for batch humanization
  • Keep raw datetime columns for calculations and add humanized columns for display

Duration Words vs Relative Words

Pendulum offers two different kinds of human-readable output, and mixing them up is the most common error with this feature. interval.in_words() describes a length of time — "1 month 3 days 14 hours" — with no direction. dt.diff_for_humans(other) describes a position in time relative to another moment and rounds to the largest sensible unit: "8 months ago", "in 2 weeks", or "1 month before" when you pass an explicit reference. Both accept a locale argument.

Worked Example: Deterministic Labels

import pendulum

ref = pendulum.datetime(2026, 3, 19, 12, 0, tz="UTC")     # fixed "now" for reproducible output
events = {
    "signup": ref.subtract(months=1, days=3, hours=14),
    "last_login": ref.subtract(minutes=5),
    "renewal": ref.add(days=14),
}

for name, when in events.items():
    gap = when.diff(ref)                                  # absolute interval
    print(f"{name:10} | {gap.in_words():24} | {when.diff_for_humans(ref)}")
# signup     | 1 month 3 days 14 hours  | 1 month before
# last_login | 5 minutes                | 5 minutes before
# renewal    | 2 weeks                  | 2 weeks after

print(ref.subtract(days=3).diff_for_humans(ref, locale="fr"))   # 3 jours avant

With an explicit reference, the wording is "before/after"; without one, pendulum compares against the current time and says "ago/in". Fixing the reference makes reports and tests reproducible.

Common Mistakes

  • Expecting in_words() to say "ago": it only describes the duration
  • Calling pendulum.parse() on pandas Timestamps: use pendulum.instance()
  • Using humanised strings for sorting or maths: keep the raw datetimes

Quick Summary

  • in_words() = how long; diff_for_humans() = when, relative to now
  • Pass a reference datetime for reproducible labels
  • Use locale= for other languages

Conclusion

Pendulum’s ability to humanize time differences turns technical timedelta objects into natural language that users actually understand. In 2026 data science projects, features like “3 days ago”, “in 2 weeks”, or “5 months old” make dashboards, alerts, and reports far more user-friendly and professional. Add Pendulum’s humanizing methods to your toolkit and watch your time-based insights become instantly more accessible.

Next steps:

  • Add a human-readable “time ago” column to one of your datasets using Pendulum’s .diff_for_humans()

Related Tutorials

Share:
Last updated: March 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices