Back to Blog
Python

Python Arrow: Datetime Parsing, Formatting, and Timezone

Learn how to parse, format, and convert timezones with Python Arrow. Includes code examples, timezone edge cases, and dependency considerations.

Arrowdatetimetimezoneparsingformatting
Illustration of a clock with timezone arrows pointing to different world cities, representing Arrow's timezone conversion.

Python's standard library can parse an ISO 8601 string such as 2024-03-15T10:30:00+02:00 with datetime.fromisoformat() in one call. Other tasks—parsing non-standard formats, converting timezones, and producing readable timestamps—often require extra steps. Arrow provides a consistent API around datetime for parsing, formatting, and timezone conversion. This article shows how to use it with code examples you can apply immediately.

Why Arrow for Datetime Handling in Python

The standard library's datetime module is powerful but often verbose. For ISO 8601 input, datetime.fromisoformat() is available in Python 3.7+, but for other common formats you need strptime with the exact format string. Formatting output for different timezones means repeated calls to astimezone() and strftime(). Arrow provides a wrapper around datetime that offers a more concise and readable API for common operations. It also includes utilities like humanized relative times and easy timezone shifting, which are not available in the standard library without extra code.

Parsing Datetime Strings with Arrow

Arrow's get() method is the primary entry point for parsing. It accepts strings, timestamps, and other datetime-like objects. When given a string, it automatically detects the format, including ISO 8601 and many common patterns.

import arrow # Parse an ISO 8601 string parsed = arrow.get('2024-03-15T10:30:00+02:00') print(parsed) # 2024-03-15T10:30:00+02:00

For non-standard formats, you can pass a format string as the second argument, similar to strptime:

parsed = arrow.get('15/03/2024 10:30', 'DD/MM/YYYY HH:mm') print(parsed) # 2024-03-15T10:30:00+00:00

Note that Arrow uses its own format tokens (e.g., YYYY for year, DD for day) rather than the %Y style used by strptime. This is a common source of confusion when migrating from the standard library.

If the string lacks timezone information, Arrow assumes UTC by default. You can change this by passing a tzinfo parameter or using the replace() method later.

Formatting Datetimes for Output

Formatting with Arrow uses the format() method, which accepts the same token set as parsing. This makes it easy to convert a datetime to a human-readable string or a specific API format.

import arrow now = arrow.now() print(now.format('YYYY-MM-DD HH:mm:ss')) # 2024-03-15 10:30:45 print(now.format('dddd, DD MMMM YYYY')) # Friday, 15 March 2024

Arrow also provides humanize() for relative time strings like "2 hours ago" or "in 3 days". This is useful for user-facing timestamps without writing custom logic.

past = arrow.now().shift(hours=-5) print(past.humanize()) # 5 hours ago

For timezone-aware output, you can combine to() and format() to display the same moment in different timezones.

Timezone Conversion and Localization

Arrow's to() method converts a datetime to a specified timezone. It accepts IANA timezone names, such as 'US/Pacific', or fixed offsets like '+05:30'. This is particularly useful when dealing with users in different regions.

import arrow utc_time = arrow.get('2024-03-15T10:30:00+00:00') print(utc_time.to('US/Pacific')) # 2024-03-15T03:30:00-07:00 print(utc_time.to('Asia/Kolkata')) # 2024-03-15T16:00:00+05:30

Arrow handles daylight saving time transitions by relying on the dateutil library for timezone handling. This avoids the common bug of manually applying a fixed offset that does not account for DST.

When you create a datetime with arrow.now(), it uses the local timezone of the system. To work with UTC explicitly, use arrow.utcnow() or arrow.now('UTC').

Arrow vs. Standard Library datetime

The table below compares common operations using the standard library and Arrow.

OperationStandard Library (datetime)Arrow
Parse ISO 8601datetime.fromisoformat(s)arrow.get(s)
Parse custom formatdatetime.strptime(s, fmt)arrow.get(s, fmt)
Format with custom patterndt.strftime(fmt)arrow_obj.format(fmt)
Convert timezonedt.astimezone(tz)arrow_obj.to(tz)
Current time in UTCdatetime.now(timezone.utc)arrow.utcnow()
Humanized relative timeNot built-in (requires third-party library)arrow_obj.humanize()

Arrow's API is more consistent: parsing and formatting use the same token set, and timezone conversion is a single method call. The standard library is more verbose but has zero dependencies. For projects that already require Arrow, the convenience often outweighs the extra dependency.

Performance and Compatibility Considerations

Arrow adds a layer of abstraction over the standard library, and in high-volume parsing or formatting code that overhead can matter. If profiling shows that Arrow is a bottleneck, consider using the standard library's fromisoformat() and strftime() for the hot path and keep Arrow where readability matters more.

Arrow depends on python-dateutil for timezone support, so you need to manage that dependency in your environment. Check the project's documentation for the Python versions it supports in your installation.

One operational note: Arrow objects are immutable. Every operation like shift() or to() returns a new Arrow object. This design prevents accidental mutation, but chaining many operations creates intermediate objects, so avoid unnecessary chains in tight loops.

Common Pitfalls and Edge Cases

When parsing strings with Arrow, be aware that the format tokens are case-sensitive. For example, YYYY is the four-digit year, while YY is the two-digit year. Using the wrong case can produce incorrect results or raise an error.

Arrow's get() can raise arrow.parser.ParserError if the input string does not match the expected format. Always handle this exception in production code, especially when parsing user input.

Another edge case is timezone-aware vs. naive datetimes. If you parse a string without timezone information, Arrow assumes UTC. This is usually the desired behavior, but if you need the local timezone, you must explicitly pass it:

import arrow local_parsed = arrow.get('2024-03-15 10:30', tzinfo='local')

Finally, remember that humanize() supports localization via the locale parameter; the default locale is English.

Arrow is a practical choice for applications that need concise datetime handling without sacrificing timezone correctness. Its API reduces boilerplate and makes the intent of the code clearer, especially when dealing with multiple timezones.

Python Arrow: Parsing, Formatting, and Timezone Conversion | RYUSLOG DEV