pendulum

Python datetimes made easy

Github stars Tracking Chart

Pendulum
########

.. image:: https://img.shields.io/pypi/v/pendulum.svg
:target: https://pypi.python.org/pypi/pendulum

.. image:: https://img.shields.io/pypi/l/pendulum.svg
:target: https://pypi.python.org/pypi/pendulum

.. image:: https://img.shields.io/codecov/c/github/sdispater/pendulum/master.svg
:target: https://codecov.io/gh/sdispater/pendulum/branch/master

.. image:: https://travis-ci.org/sdispater/pendulum.svg
:alt: Pendulum Build status
:target: https://travis-ci.org/sdispater/pendulum

Python datetimes made easy.

Supports Python 2.7 and 3.4+.

.. code-block:: python

import pendulum

now_in_paris = pendulum.now('Europe/Paris')
now_in_paris
'2016-07-04T00:49:58.502116+02:00'

Seamless timezone switching

now_in_paris.in_timezone('UTC')
'2016-07-03T22:49:58.502116+00:00'

tomorrow = pendulum.now().add(days=1)
last_week = pendulum.now().subtract(weeks=1)

past = pendulum.now().subtract(minutes=2)
past.diff_for_humans()
'2 minutes ago'

delta = past - last_week
delta.hours
23
delta.in_words(locale='en')
'6 days 23 hours 58 minutes'

Proper handling of datetime normalization

pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris')
'2013-03-31T03:30:00+02:00' # 2:30 does not exist (Skipped time)

Proper handling of dst transitions

just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, tz='Europe/Paris')
'2013-03-31T01:59:59.999999+01:00'
just_before.add(microseconds=1)
'2013-03-31T03:00:00+02:00'

Why Pendulum?

Native datetime instances are enough for basic cases but when you face more complex use-cases
they often show limitations and are not so intuitive to work with.
Pendulum provides a cleaner and more easy to use API while still relying on the standard library.
So it's still datetime but better.

Unlike other datetime libraries for Python, Pendulum is a drop-in replacement
for the standard datetime class (it inherits from it), so, basically, you can replace all your datetime
instances by DateTime instances in you code (exceptions exist for libraries that check
the type of the objects by using the type function like sqlite3 or PyMySQL for instance).

It also removes the notion of naive datetimes: each Pendulum instance is timezone-aware
and by default in UTC for ease of use.

Pendulum also improves the standard timedelta class by providing more intuitive methods and properties.

Why not Arrow?

Arrow is the most popular datetime library for Python right now, however its behavior
and API can be erratic and unpredictable. The get() method can receive pretty much anything
and it will try its best to return something while silently failing to handle some cases:

.. code-block:: python

arrow.get('2016-1-17')
# <Arrow [2016-01-01T00:00:00+00:00]>

pendulum.parse('2016-1-17')
# <Pendulum [2016-01-17T00:00:00+00:00]>

arrow.get('20160413')
# <Arrow [1970-08-22T08:06:53+00:00]>

pendulum.parse('20160413')
# <Pendulum [2016-04-13T00:00:00+00:00]>

arrow.get('2016-W07-5')
# <Arrow [2016-01-01T00:00:00+00:00]>

pendulum.parse('2016-W07-5')
# <Pendulum [2016-02-19T00:00:00+00:00]>

# Working with DST
just_before = arrow.Arrow(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris')
just_after = just_before.replace(microseconds=1)
'2013-03-31T02:00:00+02:00'
# Should be 2013-03-31T03:00:00+02:00

(just_after.to('utc') - just_before.to('utc')).total_seconds()
-3599.999999
# Should be 1e-06

just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris')
just_after = just_before.add(microseconds=1)
'2013-03-31T03:00:00+02:00'

(just_after.in_timezone('utc') - just_before.in_timezone('utc')).total_seconds()
1e-06

Those are a few examples showing that Arrow cannot always be trusted to have a consistent
behavior with the data you are passing to it.

Limitations

Even though the DateTime class is a subclass of datetime there are some rare cases where
it can't replace the native class directly. Here is a list (non-exhaustive) of the reported cases with
a possible solution, if any:

  • sqlite3 will use the type() function to determine the type of the object by default. To work around it you can register a new adapter:

.. code-block:: python

from pendulum import DateTime
from sqlite3 import register_adapter

register_adapter(DateTime, lambda val: val.isoformat(' '))
  • mysqlclient (former MySQLdb) and PyMySQL will use the type() function to determine the type of the object by default. To work around it you can register a new adapter:

.. code-block:: python

import MySQLdb.converters
import pymysql.converters

from pendulum import DateTime

MySQLdb.converters.conversions[DateTime] = MySQLdb.converters.DateTime2literal
pymysql.converters.conversions[DateTime] = pymysql.converters.escape_datetime
  • django will use the isoformat() method to store datetimes in the database. However since pendulum is always timezone aware the offset information will always be returned by isoformat() raising an error, at least for MySQL databases. To work around it you can either create your own DateTimeField or use the previous workaround for MySQLdb:

.. code-block:: python

from django.db.models import DateTimeField as BaseDateTimeField
from pendulum import DateTime


class DateTimeField(BaseDateTimeField):

    def value_to_string(self, obj):
        val = self.value_from_object(obj)

        if isinstance(value, DateTime):
            return value.to_datetime_string()

        return '' if val is None else val.isoformat()

Resources

  • Official Website <https://pendulum.eustace.io>_
  • Documentation <https://pendulum.eustace.io/docs/>_
  • Issue Tracker <https://github.com/sdispater/pendulum/issues>_

Contributing

Contributions are welcome, especially with localization.

Getting started

To work on the Pendulum codebase, you'll want to clone the project locally
and install the required depedendencies via poetry <https://poetry.eustace.io>_.

.. code-block:: bash

$ git clone git@github.com:sdispater/pendulum.git
$ poetry install

Localization

If you want to help with localization, there are two different cases: the locale already exists
or not.

If the locale does not exist you will need to create it by using the clock utility:

.. code-block:: bash

./clock locale create <your-locale>

It will generate a directory in pendulum/locales named after your locale, with the following
structure:

.. code-block:: text

<your-locale>/
    - custom.py
    - locale.py

The locale.py file must not be modified. It contains the translations provided by
the CLDR database.

The custom.py file is the one you want to modify. It contains the data needed
by Pendulum that are not provided by the CLDR database. You can take the en <https://github.com/sdispater/pendulum/tree/master/pendulum/locales/en/custom.py>_
data as a reference to see which data is needed.

You should also add tests for the created or modified locale.

Main metrics

Overview
Name With Ownerpython-pendulum/pendulum
Primary LanguagePython
Program languageMakefile (Language Count: 3)
Platform
License:MIT License
所有者活动
Created At2016-06-27 23:37:53
Pushed At2025-06-22 12:20:18
Last Commit At2025-06-22 17:50:18
Release Count55
Last Release Name3.1.0 (Posted on 2025-04-19 16:16:28)
First Release Name0.1 (Posted on )
用户参与
Stargazers Count6.5k
Watchers Count69
Fork Count400
Commits Count877
Has Issues Enabled
Issues Count558
Issue Open Count202
Pull Requests Count218
Pull Requests Open Count31
Pull Requests Close Count79
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private