Claude Code for arrow: Human-Friendly Datetime in Python — Claude Skills 360 Blog
Blog / AI / Claude Code for arrow: Human-Friendly Datetime in Python
AI

Claude Code for arrow: Human-Friendly Datetime in Python

Published: May 30, 2028
Read time: 5 min read
By: Claude Skills 360

arrow is a human-friendly Python datetime library. pip install arrow. Now: import arrow; arrow.now() — local time with tzinfo. UTC: arrow.utcnow(). Parse: arrow.get("2024-01-15T09:30:00") — ISO 8601. Epoch: arrow.get(1700000000). String+format: arrow.get("15 Jan 2024", "DD MMM YYYY"). Timezone: arrow.now("America/New_York"). shift: arrow.utcnow().shift(days=+7, hours=-3). replace: a.replace(year=2025). Format: a.format("YYYY-MM-DD HH:mm:ss"). Human: a.humanize() → “2 hours ago”. Humanize from: a.humanize(other, granularity=["hour","minute"]). to: arrow.utcnow().to("Asia/Tokyo"). timestamp: a.timestamp() → float epoch. int: int(a.timestamp()). fromtimestamp: arrow.Arrow.fromtimestamp(ts). fromdatetime: arrow.Arrow.fromdatetime(dt). datetime: a.datetime — naive Python datetime. fold: daylight saving disambiguation. floor: a.floor("hour") — truncate to hour. ceil: a.ceil("day") — end of day. span: a.span("day") → (start, end). span_range: arrow.Arrow.span_range("week", start, end) — iterate weeks. range: arrow.Arrow.range("month", start, end). between: a.is_between(start, end). isoformat: a.isoformat(). date: a.date(). time: a.time(). year/month/day/hour/minute/second: attrs. Claude Code generates arrow date parsers, timezone converters, humanized displays, and calendar pipelines.

CLAUDE.md for arrow

## arrow Stack
- Version: arrow >= 1.3 | pip install arrow
- Now: arrow.utcnow() | arrow.now("America/New_York")
- Parse: arrow.get("2024-01-15") | arrow.get(epoch_float) | arrow.get(dt_obj)
- Shift: a.shift(days=+1, hours=-3, weeks=+1) — immutable
- Format: a.format("YYYY-MM-DD") | a.humanize() | a.isoformat()
- Convert: a.to("UTC") | a.timestamp() | a.datetime | a.date()

arrow Datetime Pipeline

# app/dates.py — arrow parsing, shifting, formatting, timezone conversion, humanize, ranges
from __future__ import annotations

import re
from typing import Generator

import arrow
from arrow import Arrow


# ─────────────────────────────────────────────────────────────────────────────
# 1. Construction helpers
# ─────────────────────────────────────────────────────────────────────────────

def now(tz: str = "UTC") -> Arrow:
    """
    Current time in the given timezone.

    Example:
        now()                       # UTC
        now("America/New_York")     # Eastern
        now("Europe/London")        # London
    """
    return arrow.now(tz)


def utcnow() -> Arrow:
    """Current UTC time."""
    return arrow.utcnow()


def parse(
    value: str | int | float,
    fmt: str | list[str] | None = None,
    tz: str = "UTC",
) -> Arrow:
    """
    Parse a datetime from a string, int, or float.
    fmt: strptime-style format or list of formats to try.
    Defaults to ISO 8601 / epoch detection.

    Example:
        parse("2024-01-15")                          # ISO date
        parse("15/01/2024", fmt="DD/MM/YYYY")        # custom
        parse(1700000000)                            # Unix epoch
        parse("Jan 15, 2024 9:30am", fmt="MMM DD, YYYY h:mma")
    """
    if fmt:
        return arrow.get(value, fmt, tzinfo=tz)
    return arrow.get(value, tzinfo=tz) if isinstance(value, str) and not value.isdigit() else arrow.get(value)


def try_parse(
    value: str | None,
    fmt: str | list[str] | None = None,
    default: Arrow | None = None,
    tz: str = "UTC",
) -> Arrow | None:
    """
    Safe parse — returns default instead of raising on failure.

    Example:
        ts = try_parse(row.get("created_at"), default=arrow.utcnow())
    """
    if not value:
        return default
    try:
        return parse(value, fmt=fmt, tz=tz)
    except Exception:
        return default


def from_epoch(ts: int | float, tz: str = "UTC") -> Arrow:
    """
    Create Arrow from a Unix timestamp.

    Example:
        from_epoch(1700000000)               # second-precision
        from_epoch(1700000000.123)           # millisecond-precision
        from_epoch(1700000000123, tz="UTC")  # ms integer — divide by 1000 first
    """
    if ts > 1e12:  # milliseconds
        ts /= 1000
    return arrow.Arrow.fromtimestamp(ts, tzinfo=tz)


def from_parts(
    year: int,
    month: int,
    day: int,
    hour: int = 0,
    minute: int = 0,
    second: int = 0,
    tz: str = "UTC",
) -> Arrow:
    """
    Build an Arrow from explicit date/time parts.

    Example:
        from_parts(2024, 1, 15, 9, 30, tz="America/New_York")
    """
    return arrow.Arrow(year, month, day, hour, minute, second, tzinfo=tz)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Arithmetic / manipulation
# ─────────────────────────────────────────────────────────────────────────────

def shift(
    dt: Arrow,
    years: int = 0,
    months: int = 0,
    weeks: int = 0,
    days: int = 0,
    hours: int = 0,
    minutes: int = 0,
    seconds: int = 0,
) -> Arrow:
    """
    Shift an Arrow by the given offsets. All args can be negative.

    Example:
        tomorrow     = shift(now(), days=+1)
        last_quarter = shift(now(), months=-3)
        next_friday  = shift(start_of_week(), weeks=+1, days=-1)
    """
    return dt.shift(
        years=years, months=months, weeks=weeks,
        days=days, hours=hours, minutes=minutes, seconds=seconds,
    )


def floor_dt(dt: Arrow, frame: str) -> Arrow:
    """
    Truncate to the start of the given time frame.
    frame: "year" | "month" | "week" | "day" | "hour" | "minute" | "second"

    Example:
        floor_dt(now(), "day")    # midnight today
        floor_dt(now(), "month")  # 1st of current month at 00:00
        floor_dt(now(), "hour")   # current hour at :00
    """
    return dt.floor(frame)


def ceil_dt(dt: Arrow, frame: str) -> Arrow:
    """
    Round up to end of the given time frame.

    Example:
        ceil_dt(now(), "day")   # 23:59:59.999999 today
        ceil_dt(now(), "week")  # last second of Sunday
    """
    return dt.ceil(frame)


def clamp(dt: Arrow, start: Arrow, end: Arrow) -> Arrow:
    """Clamp dt to within [start, end]."""
    if dt < start:
        return start
    if dt > end:
        return end
    return dt


# ─────────────────────────────────────────────────────────────────────────────
# 3. Formatting
# ─────────────────────────────────────────────────────────────────────────────

def fmt(dt: Arrow, pattern: str = "YYYY-MM-DD HH:mm:ss ZZ") -> str:
    """
    Format an Arrow to a string using arrow's token format.
    Tokens: YYYY MM DD HH mm ss ZZ (offset) z (tz name).

    Example:
        fmt(now())                           # "2024-01-15 09:30:00 +00:00"
        fmt(dt, "MMM D, YYYY")               # "Jan 15, 2024"
        fmt(dt, "ddd, DD MMM YYYY HH:mm")   # "Mon, 15 Jan 2024 09:30"
    """
    return dt.format(pattern)


def iso(dt: Arrow) -> str:
    """Return ISO 8601 string (includes timezone offset)."""
    return dt.isoformat()


def humanize(
    dt: Arrow,
    other: Arrow | None = None,
    granularity: list[str] | None = None,
    locale: str = "en",
) -> str:
    """
    Return a human-readable time difference.
    other: compare to this Arrow (default: now).
    granularity: e.g. ["hour", "minute"] for "2 hours 30 minutes ago"

    Example:
        humanize(parse("2024-01-14"))          # "a day ago"
        humanize(shift(now(), hours=-3))       # "3 hours ago"
        humanize(shift(now(), days=+5))        # "in 5 days"
        humanize(dt, granularity=["day","hour"])  # "2 days 3 hours ago"
    """
    kwargs: dict = {"locale": locale}
    if other:
        kwargs["other"] = other
    if granularity:
        kwargs["granularity"] = granularity
    return dt.humanize(**kwargs)


def relative_label(dt: Arrow, now_dt: Arrow | None = None) -> str:
    """
    Return a display label: "Today", "Yesterday", "Tomorrow", or humanized.

    Example:
        relative_label(parse("2024-01-15"))  # "Today" (if today is the 15th)
    """
    ref   = (now_dt or arrow.utcnow()).to(dt.tzinfo)
    today = ref.floor("day")
    d     = dt.floor("day")

    if d == today:
        return "Today"
    if d == today.shift(days=-1):
        return "Yesterday"
    if d == today.shift(days=+1):
        return "Tomorrow"
    diff = (d - today).days
    if abs(diff) < 7:
        return dt.format("dddd")  # "Monday", "Tuesday", ...
    return humanize(dt, other=ref)


# ─────────────────────────────────────────────────────────────────────────────
# 4. Timezone conversion
# ─────────────────────────────────────────────────────────────────────────────

def to_tz(dt: Arrow, tz: str) -> Arrow:
    """
    Convert an Arrow to a different timezone.

    Example:
        utc_dt = parse("2024-01-15T14:00:00+00:00")
        ny     = to_tz(utc_dt, "America/New_York")   # 09:00
        tokyo  = to_tz(utc_dt, "Asia/Tokyo")         # 23:00
    """
    return dt.to(tz)


def to_utc(dt: Arrow) -> Arrow:
    """Convert any Arrow to UTC."""
    return dt.to("UTC")


def localize(epoch: int | float, tz: str) -> Arrow:
    """Convert a Unix timestamp to a localized Arrow."""
    return from_epoch(epoch, tz=tz)


def world_times(dt: Arrow, zones: list[str] | None = None) -> dict[str, Arrow]:
    """
    Return dt expressed in multiple timezones.

    Example:
        times = world_times(arrow.utcnow())
        for tz_name, local in times.items():
            print(f"{tz_name}: {fmt(local, 'HH:mm')}")
    """
    default_zones = [
        "UTC", "America/New_York", "America/Los_Angeles",
        "Europe/London", "Europe/Paris", "Asia/Tokyo", "Australia/Sydney"
    ]
    zones = zones or default_zones
    return {z: dt.to(z) for z in zones}


# ─────────────────────────────────────────────────────────────────────────────
# 5. Ranges and spans
# ─────────────────────────────────────────────────────────────────────────────

def date_range(
    start: Arrow,
    end: Arrow,
    frame: str = "day",
) -> Generator[Arrow, None, None]:
    """
    Yield Arrow instances between start and end (inclusive) by frame.
    frame: "day" | "week" | "month" | "hour" | "minute"

    Example:
        for day in date_range(parse("2024-01-01"), parse("2024-01-07")):
            print(fmt(day, "YYYY-MM-DD"))
    """
    yield from arrow.Arrow.range(frame, start, end)


def span_range(
    start: Arrow,
    end: Arrow,
    frame: str = "month",
) -> list[tuple[Arrow, Arrow]]:
    """
    Return list of (start, end) tuples for each period in the range.

    Example:
        for month_start, month_end in span_range(parse("2024-01-01"), parse("2024-06-30"), "month"):
            print(fmt(month_start, "MMMM YYYY"))
    """
    return list(arrow.Arrow.span_range(frame, start, end))


def working_days(start: Arrow, end: Arrow) -> list[Arrow]:
    """
    Return a list of weekdays (Monday–Friday) between start and end.

    Example:
        days = working_days(parse("2024-01-01"), parse("2024-01-31"))
    """
    return [
        d for d in date_range(start, end, "day")
        if d.weekday() < 5  # 0=Mon, 4=Fri
    ]


def is_between(dt: Arrow, start: Arrow, end: Arrow, inclusive: str = "both") -> bool:
    """
    Return True if dt is between start and end.
    inclusive: "both" | "neither" | "left" | "right"

    Example:
        is_between(now(), parse("2024-01-01"), parse("2024-12-31"))
    """
    return dt.is_between(start, end, bounds=inclusive.replace("both","[]").replace("neither","()").replace("left","[)").replace("right","(]") if inclusive in {"both","neither","left","right"} else inclusive)


# ─────────────────────────────────────────────────────────────────────────────
# 6. Common utilities
# ─────────────────────────────────────────────────────────────────────────────

def age_in_days(dt: Arrow, ref: Arrow | None = None) -> int:
    """Days elapsed since dt (positive = past, negative = future)."""
    ref = ref or arrow.utcnow()
    return (ref - dt).days


def business_deadline(
    start: Arrow,
    days: int,
    skip_weekends: bool = True,
) -> Arrow:
    """
    Compute a deadline N business days from start.

    Example:
        deadline = business_deadline(arrow.utcnow(), 5)
    """
    current = start
    added   = 0
    while added < days:
        current = current.shift(days=1)
        if not skip_weekends or current.weekday() < 5:
            added += 1
    return current


def parse_duration(text: str) -> int:
    """
    Parse a human duration string into seconds.
    Supports: "2h", "30m", "1d 4h 30m", "90s"

    Example:
        parse_duration("1d 2h 30m")  # 95400
        parse_duration("90s")        # 90
    """
    units = {"d": 86400, "h": 3600, "m": 60, "s": 1}
    total = 0
    for num, unit in re.findall(r"(\d+)\s*([dhms])", text.lower()):
        total += int(num) * units[unit]
    return total


def time_until(dt: Arrow, ref: Arrow | None = None) -> dict:
    """
    Return dict of time components until (or since) dt.

    Example:
        countdown = time_until(parse("2025-01-01"))
        print(f"{countdown['days']}d {countdown['hours']}h until New Year")
    """
    ref   = ref or arrow.utcnow()
    delta = dt - ref
    total_seconds = int(delta.total_seconds())
    sign  = 1 if total_seconds >= 0 else -1
    total_seconds = abs(total_seconds)

    days, rem   = divmod(total_seconds, 86400)
    hours, rem  = divmod(rem, 3600)
    minutes, s  = divmod(rem, 60)

    return {
        "past":    sign < 0,
        "days":    days * sign,
        "hours":   hours,
        "minutes": minutes,
        "seconds": s,
        "total_seconds": int(delta.total_seconds()),
    }


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== Construction ===")
    print(f"  utcnow():        {utcnow()}")
    print(f"  now(NY):         {now('America/New_York')}")
    print(f"  parse ISO:       {parse('2024-01-15T09:30:00+00:00')}")
    print(f"  parse custom:    {parse('15 Jan 2024', fmt='DD MMM YYYY')}")
    print(f"  from_epoch:      {from_epoch(1700000000)}")
    print(f"  from_parts:      {from_parts(2024, 6, 15, 12, 0, tz='Europe/London')}")

    print("\n=== Arithmetic ===")
    base = parse("2024-01-15")
    print(f"  +7 days:         {fmt(shift(base, days=7), 'YYYY-MM-DD')}")
    print(f"  -3 months:       {fmt(shift(base, months=-3), 'YYYY-MM-DD')}")
    print(f"  floor month:     {fmt(floor_dt(base, 'month'), 'YYYY-MM-DD')}")
    print(f"  ceil month:      {fmt(ceil_dt(base, 'month'), 'YYYY-MM-DD')}")

    print("\n=== Formatting ===")
    dt = parse("2024-01-15T14:30:00+00:00")
    print(f"  default fmt:     {fmt(dt)}")
    print(f"  long:            {fmt(dt, 'dddd, MMMM D, YYYY [at] h:mm A')}")
    print(f"  iso:             {iso(dt)}")
    print(f"  humanize:        {humanize(dt)}")
    print(f"  relative_label:  {relative_label(shift(utcnow(), days=-1))}")

    print("\n=== Timezone conversion ===")
    utc = parse("2024-01-15T14:00:00+00:00")
    for tz_name, local in world_times(utc, ["UTC","America/New_York","Asia/Tokyo"]).items():
        print(f"  {tz_name:20s}: {fmt(local, 'YYYY-MM-DD HH:mm ZZ')}")

    print("\n=== Ranges ===")
    start = parse("2024-01-01")
    end   = parse("2024-01-07")
    days  = list(date_range(start, end))
    print(f"  Week days: {[fmt(d,'ddd DD') for d in days]}")
    wdays = working_days(start, end)
    print(f"  Working days: {[fmt(d,'ddd DD') for d in wdays]}")

    print("\n=== Utilities ===")
    print(f"  age_in_days(2024-01-01): {age_in_days(parse('2024-01-01'))} days ago")
    deadline = business_deadline(parse("2024-01-15"), 5)
    print(f"  5 biz days from Jan 15: {fmt(deadline, 'YYYY-MM-DD (ddd)')}")
    print(f"  parse_duration('1d 2h 30m'): {parse_duration('1d 2h 30m')} seconds")
    countdown = time_until(parse("2025-01-01"))
    print(f"  time_until(2025-01-01): {countdown['days']}d {countdown['hours']}h {'ago' if countdown['past'] else 'away'}")

For the python-dateutil alternative — python-dateutil’s relativedelta handles month arithmetic and DST transitions, and parser.parse() covers a wide range of ambiguous date strings; arrow wraps relativedelta internally and adds a chainable fluent API, human-readable humanize(), and CLDR-locale support out of the box — use arrow when you want a single consistent API for parsing, shifting, formatting, and humanizing, python-dateutil when you need its low-level rrule recurrence engine or strict ambiguity control. For the pendulum alternative — pendulum has a stricter immutable API, first-class period objects, and ISO 8601 interval support; arrow has a larger install base, more lenient parsing (multi-format lists), and the humanize(locale=...) localization feature — use pendulum for calendar-math heavy applications where strict DST handling is critical, arrow for developer-friendly general datetime work including APIs, reporting, and web apps. The Claude Skills 360 bundle includes arrow skill sets covering now()/utcnow()/parse()/try_parse()/from_epoch()/from_parts() construction, shift() arithmetic, floor_dt()/ceil_dt() truncation, clamp(), fmt()/iso()/humanize()/relative_label() formatting, to_tz()/to_utc()/localize()/world_times() conversion, date_range()/span_range()/working_days() iteration, is_between(), age_in_days()/business_deadline()/parse_duration()/time_until() utilities. Start with the free tier to try human-friendly datetime parsing and conversion code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free