arrow-py/arrow: a datetime wrapper for Python that stays timezone-aware by default
🏹 Better dates & times for Python
At a glance
- What is it?
- Arrow wraps the standard library's date, time and calendar modules behind one UTC-first type. It removes the import juggling, but it does not replace datetime, and the project's own README says where the boundary sits.
- Who is it for?
- Adopt Arrow if your codebase keeps stitching together datetime, time, calendar and dateutil, and you want one UTC-aware object with shift, to, format and humanize on it. Do not adopt it if you are writing a library that must expose plain datetime objects to callers, or if you need calendar arithmetic that dateutil's relativedelta already gives you.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 100 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The import problem Arrow is trying to remove
Python's date and time support is spread across datetime, time, calendar, dateutil and pytz, and each one exposes a different type: date, time, datetime, tzinfo, timedelta, relativedelta. The README states the case plainly, listing "too many modules" and "too many types" as the first two reasons to use Arrow over the built-in modules. It also names the two consequences that bite in practice: timezone and timestamp conversions are verbose, and timezone naivety is the norm.
That last point is the real one. A naive datetime in Python is not an error, it is a default, and it will happily compare against an aware one until it raises. Arrow's answer is to make UTC the default at construction time. The README describes the library as "Timezone-aware and UTC by default", and the quick start shows arrow.utcnow() returning an object with an explicit +00:00 offset rather than a bare wall-clock reading.
The audience is application code, not library internals. If you are writing a scheduler, a log formatter, an API client or anything that parses timestamps from the outside world, you are the target. If you are writing a library that other people will call, the calculus is different, and the last section covers why.
One Arrow object over dateutil, pytz and ZoneInfo
Arrow is not a reimplementation of the calendar. pyproject.toml declares python-dateutil>=2.7.0 as a runtime dependency, plus backports.zoneinfo==0.2.1 on Python below 3.9 and tzdata on Python 3.9 and above. So the timezone database and the calendar arithmetic underneath are the ones you already have, and Arrow is the surface over them.
The README describes it as a "Fully-implemented, drop-in replacement for datetime", which is a strong phrase, and the mechanism behind it is that Arrow subclasses the standard datetime type. That is what makes it usable in code that expects a datetime, while adding methods the base type does not have. The README also says Arrow supports dateutil, pytz and ZoneInfo tzinfo objects, so the conversion boundary is not one-way.
The methods that carry most of the weight are shift, to, format, parse and humanize. shift takes relative offsets including weeks. to converts between timezones. format and parse handle the ISO 8601 strings the README calls out as a gap in the standard library. humanize turns a timestamp into "an hour ago" and takes a locale argument, with a growing list of contributed locales.
Two more pieces are worth naming because they are less visible: timespans and ranges, and floors and ceilings. The README lists generation of "time spans, ranges, floors and ceilings for time frames ranging from microsecond to year". That is the part that replaces hand-written start-of-day and end-of-month arithmetic, which is where off-by-one errors usually live.
Installing Arrow and parsing a timestamp
Installation is a single pip command. The README gives it as pip install -U arrow, and the package name on PyPI is arrow, matching the import name.
pip install -U arrowAfter that, the fastest useful thing to do is parse an ISO 8601 string, because that is the case where the standard library makes you choose between strptime format strings and a third-party parser. The README's quick start uses this exact input.
>>> import arrow
>>> arrow.get('2013-05-11T21:23:58.970460+07:00')
<Arrow [2013-05-11T21:23:58.970460+07:00]>The offset in the input is preserved in the repr, so you can see immediately whether the parser kept the timezone or dropped it. From there, the README's example chain shifts by an hour, converts to a named zone, and reads the POSIX timestamp back out.
>>> utc = arrow.utcnow()
>>> utc = utc.shift(hours=-1)
>>> local = utc.to('US/Pacific')
>>> local.timestamp()
1368303838.970460Note that the shift is applied while the value is still in UTC, and the conversion to US/Pacific happens afterwards. That ordering matters: shifting a wall-clock time across a daylight saving boundary is not the same operation as shifting an instant, and Arrow's default of holding UTC until you ask for a zone is what makes the difference explicit. The same example then formats and humanizes the result.
>>> local.format('YYYY-MM-DD HH:mm:ss ZZ')
'2013-05-11 13:23:58 -07:00'
>>> local.humanize()
'an hour ago'
>>> local.humanize(locale='ko-kr')
'한시간 ě „'The format tokens are moment.js style, not strftime style, which is worth flagging before you port a format string across. YYYY, MM, DD, HH, mm, ss and ZZ are not the same letters Python's strftime uses, and mixing the two by habit is a common first mistake.
What Arrow does not do for you
The drop-in claim has a limit, and the README does not hide it but does not dwell on it either. Arrow is a datetime subclass, so an Arrow instance is a datetime, but a datetime is not an Arrow. Any function that constructs a datetime internally and returns it hands you back a plain object with none of the shift, humanize or timespan methods. You will be converting at the boundary, and the conversions add up in code that passes values through several layers.
The second limitation is arithmetic semantics. shift handles relative offsets including weeks, but the README does not present Arrow as a replacement for dateutil's relativedelta. Month and year arithmetic, where adding one month to January 31 is genuinely ambiguous, is not something the README claims to solve. If your code does a lot of that, you will still be reaching for relativedelta, and you will be holding two mental models at once.
Third, the dependency set is not zero. Arrow brings python-dateutil, and on Python 3.9 and later it also brings tzdata. That is a deliberate choice, since a bundled timezone database is what makes named-zone conversion work without the system tz database, but it is an extra wheel in your image and it needs to stay current. The README does not document an upgrade or rollback procedure, and the CHANGELOG.rst file is the place to look for what changed between releases rather than the README.
Finally, humanization is locale-dependent and the README describes the locale list as growing and contributed. That means coverage is uneven across languages, and a locale you need may be partial. There is no claim in the README about which locales are complete.
Arrow against plain datetime and dateutil
The honest alternative is not another library, it is the standard library plus dateutil, used carefully. That combination is already a dependency of Arrow, so you are not choosing between a small thing and a big thing, you are choosing whether to put a convenience layer on top.
The difference in approach is where timezone awareness lives. With plain datetime you decide per call site: datetime.now() gives you a naive value, datetime.now(timezone.utc) gives you an aware one, and it is on you to be consistent. With Arrow the default is aware, and the README's quick start reflects that in arrow.utcnow(). If your team has ever shipped a bug because one code path used the naive constructor and another did not, that default is the whole argument.
The second difference is the format and parse surface. dateutil.parser.parse is good at guessing, and Arrow uses dateutil underneath, but Arrow adds the moment.js-style format tokens and the humanize layer on top of the same parsing. If you already have dateutil in your dependency tree and you are comfortable with strftime, Arrow buys you the humanize and timespan methods and not much else.
The third difference is type identity. Plain datetime is the lingua franca of the Python ecosystem. Arrow is a subclass, which usually works, but any code doing an exact type check rather than an isinstance check will treat it differently. That is the cost of the convenience layer, and it is the reason library authors tend to stay with datetime for public interfaces.
Release cadence, licence and what you take on
The version history is not uniform. Release 1.2.3 and 1.3.0 both landed on 2023-09-30, and 1.4.0 arrived on 2025-10-24. The repository's last push was on 2026-06-22, and it is not archived. That pattern, a long gap followed by a release and then continued activity, is worth reading as a signal about how much churn to expect. It also means the upgrade cost is low in practice: a library that ships a minor release roughly every year or two is not one that will break your build on a Tuesday.
The Python version floor is 3.8, and pyproject.toml lists classifiers through 3.14. If you are still on 3.8 you are supported, but the backports.zoneinfo==0.2.1 pin on that range is an exact pin, not a range, which is the kind of constraint that can conflict with another package asking for a different version of the same backport. On 3.9 and later you get tzdata instead and the pin disappears.
The licence is Apache-2.0, stated in the repository metadata and in the LICENSE file at the top level, with a matching classifier in pyproject.toml. Apache-2.0 is permissive and includes an explicit patent grant, which is the practical difference from MIT for some legal teams. It is not a copyleft licence, so it does not reach into your own source. Whether that fits your organisation's policy is a question for your legal team, not for this article.
Maintenance is the open question. The repository shows a test workflow, a pre-commit configuration, a tox.ini and a Makefile with build targets for Python 3.8 through 3.14, so the tooling to keep it current exists. What the README does not provide is a support commitment or a deprecation policy. If you depend on Arrow, the CHANGELOG.rst file is the document you should actually be reading before each upgrade, not the README.
Editorial conclusion
Adopt Arrow if your codebase keeps stitching together datetime, time, calendar and dateutil, and you want one UTC-aware object with shift, to, format and humanize on it. Do not adopt it if you are writing a library that must expose plain datetime objects to callers, or if you need calendar arithmetic that dateutil's relativedelta already gives you. Before you commit, check the Python version floor of 3.8 in pyproject.toml against your own support matrix, and confirm that the tzdata dependency is acceptable in your deployment image, since Arrow pulls it in on Python 3.9 and later.
Frequently asked questions
How do I install arrow-py/arrow?
The README gives the command as pip install -U arrow. The PyPI package name is arrow, which is also the import name, so no aliasing is needed.
Is Arrow a drop-in replacement for Python's datetime?
The README describes it as a fully-implemented, drop-in replacement for datetime, and Arrow subclasses the standard datetime type. That means an Arrow instance can be used where a datetime is expected, but a plain datetime returned by other code will not have Arrow's methods.
Which Python versions does arrow-py/arrow support?
pyproject.toml sets requires-python to >=3.8 and lists classifiers for 3.8 through 3.14. On Python below 3.9 it depends on backports.zoneinfo==0.2.1; on 3.9 and later it depends on tzdata instead.
What timezone does Arrow use by default?
The README states that Arrow is timezone-aware and UTC by default, and the quick start shows arrow.utcnow() producing a value with a +00:00 offset. Conversion to a named zone is a separate step using the to method.
How do I humanize a date in another language with Arrow?
The humanize method accepts a locale argument, and the README's example passes locale='ko-kr' to get a Korean string. The README describes the locale list as growing and contributed, so coverage varies by language.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/arrow-py-arrow)