pendulum

GitHub

Python datetimes made easy

RAW Doc

Docs/Addition Subtraction

Addition and Subtraction

To easily add and subtract time, you can use the add() and subtract()
methods.
Each method returns a new DateTime instance.

text
/ Detailed source-code truncated for AI context efficiency. /

!!!note

Passing negative values to add() is also possible and will act exactly
like subtract()

---

Docs/Attributes Properties

#Β Attributes and Properties

Pendulum gives access to more attributes and properties than the default `datetime class.

text
/ Detailed source-code truncated for AI context efficiency. /

---

Docs/Comparison

Comparison

Simple comparison is offered up via the basic operators.
Remember that the comparison is done in the UTC timezone
so things aren't always as they seem.

python
>>> import pendulum

>>> first = pendulum.datetime(2012, 9, 5, 23, 26, 11, 0, tz='America/Toronto')
>>> second = pendulum.datetime(2012, 9, 5, 20, 26, 11, 0, tz='America/Vancouver')

>>> first.to_datetime_string()
'2012-09-05 23:26:11'
>>> first.timezone_name
'America/Toronto'
>>> second.to_datetime_string()
'2012-09-05 20:26:11'
>>> second.timezone_name
'America/Vancouver'

>>> first == second
True
>>> first != second
False
>>> first > second
False
>>> first >= second
True
>>> first < second
False
>>> first <= second
True

>>> first = first.on(2012, 1, 1).at(0, 0, 0)
>>> second = second.on(2012, 1, 1).at(0, 0, 0)

tz is still America/Vancouver for second

>>> first == second
False
>>> first != second
True
>>> first > second
False
>>> first >= second
False
>>> first < second
True
>>> first <= second
True

To handle the most used cases there are some simple helper functions.
For the methods that compare to
now() (ex. is_today()) in some manner
the
now() is created in the same timezone as the instance.

python
>>> import pendulum

>>> dt = pendulum.now()

>>> dt.is_past()
>>> dt.is_leap_year()

>>> born = pendulum.datetime(1987, 4, 23)
>>> not_birthday = pendulum.datetime(2014, 9, 26)
>>> birthday = pendulum.datetime(2014, 4, 23)
>>> past_birthday = pendulum.now().subtract(years=50)

>>> born.is_birthday(not_birthday)
False
>>> born.is_birthday(birthday)
True
>>> past_birthday.is_birthday()

Compares to now by default


True

---

Docs/Difference

Difference

The diff() method returns an Interval instance that represents the total duration
between two
DateTime instances. This interval can be then expressed in various units.
These interval methods always return the total difference expressed in the specified time requested.
All values are truncated and not rounded.

The diff() method has a default first parameter which is the DateTime instance to compare to,
or
None if you want to use now().
The 2nd parameter is optional and indicates if you want the return value to be the absolute value
or a relative value that might have a
- (negative) sign if the passed in date
is less than the current instance.
This will default to
True, return the absolute value.

python
>>> import pendulum

>>> dt_ottawa = pendulum.datetime(2000, 1, 1, tz='America/Toronto')
>>> dt_vancouver = pendulum.datetime(2000, 1, 1, tz='America/Vancouver')

>>> dt_ottawa.diff(dt_vancouver).in_hours()
3
>>> dt_ottawa.diff(dt_vancouver, False).in_hours()
3
>>> dt_vancouver.diff(dt_ottawa, False).in_hours()
-3

>>> dt = pendulum.datetime(2012, 1, 31, 0)
>>> dt.diff(dt.add(months=1)).in_days()
29
>>> dt.diff(dt.subtract(months=1), False).in_days()
-31

>>> dt = pendulum.datetime(2012, 4, 30, 0)
>>> dt.diff(dt.add(months=1)).in_days()
30
>>> dt.diff(dt.add(weeks=1)).in_days()
7

>>> dt = pendulum.datetime(2012, 1, 1, 0)
>>> dt.diff(dt.add(seconds=59)).in_minutes()
0
>>> dt.diff(dt.add(seconds=60)).in_minutes()
1
>>> dt.diff(dt.add(seconds=119)).in_minutes()
1
>>> dt.diff(dt.add(seconds=120)).in_minutes()
2

Difference for Humans
---------------------

The diff_for_humans() method will add a phrase after the difference value relative
to the instance and the passed in instance. There are 4 possibilities:

* When comparing a value in the past to default now:
* 1 hour ago
* 5 months ago

* When comparing a value in the future to default now:
* 1 hour from now
* 5 months from now

* When comparing a value in the past to another value:
* 1 hour before
* 5 months before

* When comparing a value in the future to another value:
* 1 hour after
* 5 months after

You may also pass True as a 2nd parameter to remove the modifiers ago, from now, etc.

python
>>> import pendulum

The most typical usage is for comments


The instance is the date the comment was created


and its being compared to default now()


>>> pendulum.now().subtract(days=1).diff_for_humans()
'1 day ago'

>>> pendulum.now().diff_for_humans(pendulum.now().subtract(years=1))
'1 year after'

>>> dt = pendulum.datetime(2011, 8, 1)
>>> dt.diff_for_humans(dt.add(months=1))
'1 month before'
>>> dt.diff_for_humans(dt.subtract(months=1))
'1 month after'

>>> pendulum.now().add(seconds=5).diff_for_humans()
'5 seconds from now'

>>> pendulum.now().subtract(days=24).diff_for_humans()
'3 weeks ago'

>>> pendulum.now().subtract(days=24).diff_for_humans(absolute=True)
'3 weeks'

You can also change the locale of the string either globally by using pendulum.set_locale('fr')
before the
diff_for_humans() call or specifically for the call by passing the locale keyword
argument. See the Localization section for more detail.

python
>>> import pendulum

>>> pendulum.set_locale('de')
>>> pendulum.now().add(years=1).diff_for_humans()
'in 1 Jahr'
>>> pendulum.now().add(years=1).diff_for_humans(locale='fr')
'dans 1 an'

---

Docs/Duration

Duration

The Duration class is inherited from the native timedelta class.
It has many improvements over the base class.

!!!note

Even though, it inherits from the timedelta class, its behavior is slightly different.
The more important to notice is that the native normalization does not happen, this is so that
it feels more intuitive.

python
>>> import pendulum
>>> import datetime

>>> d1 = datetime.datetime(2012, 1, 1, 1, 2, 3, tzinfo=datetime.UTC)
>>> d2 = datetime.datetime(2011, 12, 31, 22, 2, 3, tzinfo=datetime.UTC)
>>> delta = d2 - d1
>>> delta.days
-1
>>> delta.seconds
75600

>>> d1 = pendulum.datetime(2012, 1, 1, 1, 2, 3)
>>> d2 = pendulum.datetime(2011, 12, 31, 22, 2, 3)
>>> delta = d2 - d1
>>> delta.days
0
>>> delta.hours
-3

Instantiation

To create a Duration instance, you can use the duration() helper:

python
>>> import pendulum

>>> it = pendulum.duration(days=1177, seconds=7284, microseconds=1234)

!!!note

Unlike the native timedelta class, durations support specifying
years and months.

python
>>> import pendulum

>>> it = pendulum.duration(years=2, months=3)

However, to maintain compatibility, native methods and properties will
make approximations:

python
>>> it.days
820

>>> it.total_seconds()
70848000.0

Properties and Duration Methods

The Duration class brings more properties than the default days, seconds and
microseconds.

python
>>> import pendulum

>>> it = pendulum.duration(
... years=2, months=3,
... days=1177, seconds=7284, microseconds=1234
... )

>>> it.years
2
>>> it.months
3

Weeks are based on the total of days


It does not take into account years and months


>>> it.weeks
168

Days, just like in timedelta, represents the total of days


in the duration. If years and/or months are specified


it will use an approximation


>>> it.days
1997

If you want the remaining days not included in full weeks


>>> it.remaining_days
1

>>> # The remaining number in each unit
>>> it.hours
2
>>> it.minutes
1

Seconds are, like days, a special case and the default


property will return the whole value of remaining


seconds just like the timedelta class for compatibility


>>> it.seconds
7284

If you want the number of seconds not included


in hours and minutes


>>> it.remaining_seconds
24

>>> it.microseconds
1234

If you want to get the duration in each supported unit
you can use the appropriate methods.

python

Each method returns a float like the native


total_seconds() method


>>> it.total_weeks()
168.15490079569113

>>> it.total_days()
1177.0843055698379

>>> it.total_hours()
28250.02333367611

>>> it.total_minutes()
1695001.4000205665

>>> it.total_seconds()
101700084.001234

Similarly, the in_xxx() methods return the total duration in each
supported unit as a truncated integer.

python
>>> it.in_weeks()
168

>>> it.in_days()
1997

>>> it.in_hours()
28250

>>> it.in_minutes()
1695001

>>> it.in_seconds()
101700084

It also has a handy in_words() method, which determines the duration representation when printed.

python
>>> import pendulum

>>> pendulum.set_locale('fr')

>>> it = pendulum.duration(days=1177, seconds=7284, microseconds=1234)
>>> it.in_words()
'168 semaines 1 jour 2 heures 1 minute 24 secondes'

>>> print(it)
'168 semaines 1 jour 2 heures 1 minute 24 secondes'

>>> it.in_words(locale='de')
'168 Wochen 1 Tag 2 Stunden 1 Minute 24 Sekunden'

---

Docs/Fluent Helpers

Fluent helpers

Pendulum provides helpers that return a new instance with some attributes
modified compared to the original instance.
However, none of these helpers, with the exception of explicitly setting the
timezone, will change the timezone of the instance. Specifically,
setting the timestamp will not set the corresponding timezone to UTC.

python
>>> import pendulum

>>> dt = pendulum.now()

>>> dt.set(year=1975, month=5, day=21).to_datetime_string()
'1975-05-21 13:45:18'

>>> dt.set(hour=22, minute=32, second=5).to_datetime_string()
'2016-11-16 22:32:05'

You can also use the on() and at() methods to change the date and the time
respectively

python
>>> dt.on(1975, 5, 21).at(22, 32, 5).to_datetime_string()
'1975-05-21 22:32:05'

>>> dt.at(10).to_datetime_string()
'2016-11-16 10:00:00'

>>> dt.at(10, 30).to_datetime_string()
'2016-11-16 10:30:00'

You can also modify the timezone.

python
>>> dt.set(tz='Europe/London')

Setting the timezone just modifies the timezone information without
making any conversion, while
in_timezone() (or in_tz())
converts the time in the appropriate timezone.

python
>>> import pendulum

>>> dt = pendulum.datetime(2013, 3, 31, 2, 30)
>>> print(dt)
'2013-03-31T02:30:00+00:00'

>>> dt = dt.set(tz='Europe/Paris')
>>> print(dt)
'2013-03-31T03:30:00+02:00'

>>> dt = dt.in_tz('Europe/Paris')
>>> print(dt)
'2013-03-31T04:30:00+02:00'

>>> dt = dt.set(tz='Europe/Paris').set(tz='UTC')
>>> print(dt)
'2013-03-31T03:30:00+00:00'

>>> dt = dt.in_tz('Europe/Paris').in_tz('UTC')
>>> print(dt)
'2013-03-31T02:30:00+00:00'

---

Docs/Index

{!docs/installation.md!}
{!docs/introduction.md!}
{!docs/instantiation.md!}
{!docs/parsing.md!}
{!docs/localization.md!}
{!docs/attributes_properties.md!}
{!docs/fluent_helpers.md!}
{!docs/string_formatting.md!}
{!docs/comparison.md!}
{!docs/addition_subtraction.md!}
{!docs/difference.md!}
{!docs/modifiers.md!}
{!docs/timezones.md!}
{!docs/duration.md!}
{!docs/interval.md!}
{!docs/testing.md!}
{!docs/limitations.md!}

---

Docs/Installation

Installation

Installing pendulum is quite simple:

bash
$ pip install pendulum

or, if you are using poetry:

bash
$ poetry add pendulum

Optional features

Pendulum provides optional features that you must explicitly require in order to use them.

These optional features are:

- test: Provides a set of helpers to make testing easier by allowing you to control the flow of time.

You can install them by specifying them when installing Pendulum

bash
$ pip install pendulum[test]

or, if you are using poetry:

bash
$ poetry add pendulum[test]

---

Docs/Instantiation

Instantiation

There are several different methods available to create a new DateTime instance.

First there is the main datetime() helper.

python
>>> import pendulum

>>> dt = pendulum.datetime(2015, 2, 5)
>>> isinstance(dt, datetime)
True
>>> dt.timezone.name
'UTC'

datetime() sets the time to 00:00:00 if it's not specified,
and the timezone (the
tz keyword argument) to UTC.
Otherwise it can be a
Timezone instance or simply a string timezone value.

python
>>> import pendulum

>>> pendulum.datetime(2015, 2, 5, tz='Europe/Paris')
>>> tz = pendulum.timezone('Europe/Paris')
>>> pendulum.datetime(2015, 2, 5, tz=tz)

!!!note

Supported strings for timezones are the one provided
by the IANA time zone database.

The special local string is also supported and will return your current timezone.

!!!warning

The tz argument is keyword-only, unlike in version 1.x

The local() helper is similar to datetime() but automatically sets the
timezone to the local timezone.

python
>>> import pendulum

>>> dt = pendulum.local(2015, 2, 5)
>>> print(dt.timezone.name)
'America/Toronto'

!!!note

local() is just an alias for datetime(..., tz='local').

There is also the now() method.

python
>>> import pendulum

>>> now = pendulum.now()

>>> now_in_london_tz = pendulum.now('Europe/London')
>>> now_in_london_tz.timezone_name
'Europe/London'

To accompany now(), a few other static instantiation helpers exist to create known instances.
The only thing to really notice here is that
today(), tomorrow() and yesterday(),
besides behaving as expected, all accept a timezone parameter
and each has their time value set to
00:00:00.

python
>>> now = pendulum.now()
>>> print(now)
'2016-06-28T16:51:45.978473-05:00'

>>> today = pendulum.today()
>>> print(today)
'2016-06-28T00:00:00-05:00'

>>> tomorrow = pendulum.tomorrow('Europe/London')
>>> print(tomorrow)
'2016-06-29T00:00:00+01:00'

>>> yesterday = pendulum.yesterday()
>>> print(yesterday)
'2016-06-27T00:00:00-05:00'

Pendulum enforces timezone aware datetimes, and using them is the preferred and recommended way
of using the library. However, if you really need a naive
DateTime object, the naive() helper
is there for you.

python
>>> import pendulum

>>> naive = pendulum.naive(2015, 2, 5)
>>> naive.timezone
None

The next helper, from_format(), is similar to the native datetime.strptime() function
but uses custom tokens to create a
DateTime instance.

python
>>> dt = pendulum.from_format('1975-05-21 22', 'YYYY-MM-DD HH')
>>> print(dt)
'1975-05-21T22:00:00+00:00'

!!!note

To see all the available tokens, you can check the Formatter section.

It also accepts a tz keyword argument to specify the timezone:

python
>>> dt = pendulum.from_format('1975-05-21 22', 'YYYY-MM-DD HH', tz='Europe/London')
'1975-05-21T22:00:00+01:00'

The final helper is for working with unix timestamps.
from_timestamp() will create a DateTime instance equal to the given timestamp
and will set the timezone as well or default it to
UTC.

python
>>> dt = pendulum.from_timestamp(-1)
>>> print(dt)
'1969-12-31T23:59:59+00:00'

>>> dt = pendulum.from_timestamp(-1, tz='Europe/London')
>>> print(dt)
'1970-01-01T00:59:59+01:00'

Finally, if you find yourself inheriting a datetime.datetime instance,
you can create a
DateTime instance via the instance() function.

python
>>> dt = datetime(2008, 1, 1)
>>> p = pendulum.instance(dt)
>>> print(p)
'2008-01-01T00:00:00+00:00'

---

Docs/Interval

Interval

When you subtract a DateTime instance from another, or use the diff() method, it will return an Interval instance.
It inherits from the Duration class with the added benefit that it is aware of the
instances that generated it, so that it can give access to more methods and properties:

python
>>> import pendulum

>>> start = pendulum.datetime(2000, 11, 20)
>>> end = pendulum.datetime(2016, 11, 5)

>>> interval = end - start

>>> interval.years
15
>>> interval.months
11
>>> interval.in_years()
15
>>> interval.in_months()
191

Note that the weeks property


will change compared to the Duration class


>>> interval.weeks
2 # 832 for the duration

However the days property will still remain the same


to keep the compatibility with the timedelta class


>>> interval.days
5829

Be aware that an interval, just like an duration, is compatible with the timedelta class regarding
its attributes. However, its custom attributes (like
remaining_days) will be aware of any DST
transitions that might have occurred and adjust accordingly. Let's take an example:

python
>>> import pendulum

>>> start = pendulum.datetime(2017, 3, 7, tz='America/Toronto')
>>> end = start.add(days=6)

>>> interval = end - start

timedelta properties


>>> interval.days
5
>>> interval.seconds
82800

interval properties


>>> interval.remaining_days
6
>>> interval.hours
0
>>> interval.remaining_seconds
0

!!!warning

Due to their nature (fixed duration between two datetimes), most arithmetic operations will
return a
Duration instead of an Interval.

python
>>> import pendulum

>>> dt1 = pendulum.datetime(2016, 8, 7, 12, 34, 56)
>>> dt2 = dt1.add(days=6, seconds=34)
>>> interval = pendulum.interval(dt1, dt2)
>>> interval * 2
Duration(weeks=1, days=5, minutes=1, seconds=8)


Instantiation

You can create an instance by using the interval() helper:

python
>>> import pendulum

>>> start = pendulum.datetime(2000, 1, 1)
>>> end = pendulum.datetime(2000, 1, 31)

>>> interval = pendulum.interval(start, end)

You can also make an inverted interval:

python
>>> interval = pendulum.interval(end, start)
>>> interval.remaining_days
-2

If you have inverted dates but want to make sure that the interval is positive,
you should set the
absolute keyword argument to True:

python
>>> interval = pendulum.interval(end, start, absolute=True)
>>> interval.remaining_days
2

Range

If you want to iterate over a interval, you can use the range() method:

python
>>> import pendulum

>>> start = pendulum.datetime(2000, 1, 1)
>>> end = pendulum.datetime(2000, 1, 10)

>>> interval = pendulum.interval(start, end)

>>> for dt in interval.range('days'):
>>> print(dt)

'2000-01-01T00:00:00+00:00'
'2000-01-02T00:00:00+00:00'
'2000-01-03T00:00:00+00:00'
'2000-01-04T00:00:00+00:00'
'2000-01-05T00:00:00+00:00'
'2000-01-06T00:00:00+00:00'
'2000-01-07T00:00:00+00:00'
'2000-01-08T00:00:00+00:00'
'2000-01-09T00:00:00+00:00'
'2000-01-10T00:00:00+00:00'

!!!note

Supported units for range() are: years, months, weeks,
days, hours, minutes, seconds and microseconds

You can pass an amount for the passed unit to control the length of the gap:

python
>>> for dt in interval.range('days', 2):
>>> print(dt)

'2000-01-01T00:00:00+00:00'
'2000-01-03T00:00:00+00:00'
'2000-01-05T00:00:00+00:00'
'2000-01-07T00:00:00+00:00'
'2000-01-09T00:00:00+00:00'

You can also directly iterate over the Interval instance,
the unit will be
days in this case:

python
>>> for dt in interval:
>>> print(dt)

You can check if a DateTime instance is inside a interval using the in keyword:

python
>>> dt = pendulum.datetime(2000, 1, 4)
>>> dt in interval
True

---

Docs/Introduction

Introduction

Pendulum is a Python package to ease datetimes manipulation.

It provides classes that are drop-in replacements for the native ones (they inherit from them).

Special care has been taken to ensure timezones are handled correctly,
and are based on the underlying
tzinfo implementation.
For example, all comparisons are done in
UTC or in the timezone of the datetime being used.

python
>>> import pendulum

>>> dt_toronto = pendulum.datetime(2012, 1, 1, tz='America/Toronto')
>>> dt_vancouver = pendulum.datetime(2012, 1, 1, tz='America/Vancouver')

>>> print(dt_vancouver.diff(dt_toronto).in_hours())
3

The default timezone, except when using the now() method, will always be UTC.

---

Docs/Limitations

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:

python
import pendulum
from sqlite3 import register_adapter

register_adapter(pendulum.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:

python
import pendulum
import MySQLdb.converters
import pymysql.converters

MySQLdb.converters.conversions[pendulum.DateTime] = MySQLdb.converters.DateTime2literal
pymysql.converters.conversions[pendulum.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:

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


class DateTimeField(BaseDateTimeField):

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

if isinstance(value, pendulum.DateTime):
return value.format('YYYY-MM-DD HH:mm:ss')

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

---

Docs/Localization

Localization

Localization occurs when using the format() method which accepts a locale keyword.

python
>>> import pendulum

>>> dt = pendulum.datetime(1975, 5, 21)
>>> dt.format('dddd DD MMMM YYYY', locale='de')
'Mittwoch 21 Mai 1975'

>>> dt.format('dddd DD MMMM YYYY')
'Wednesday 21 May 1975'

diff_for_humans() is also localized, you can set the locale
by using
pendulum.set_locale().

python
>>> import pendulum

>>> pendulum.set_locale('de')
>>> pendulum.now().add(years=1).diff_for_humans()
'in 1 Jahr'
>>> pendulum.set_locale('en')

However, you might not want to set the locale globally. The diff_for_humans()
method accepts a
locale keyword argument to use a locale for a specific call.

python
>>> pendulum.set_locale('de')
>>> dt = pendulum.now().add(years=1)
>>> dt.diff_for_humans(locale='fr')
'dans 1 an'

---

Docs/Modifiers

Modifiers

This group of methods performs helpful modifications to a copy of the current instance.
You'll notice that the
start_of(), next() and previous() methods
set the time to
00:00:00 and the end_of() methods set the time to 23:59:59.999999.

The only one slightly different is the average() method.
It returns the middle date between itself and the provided
DateTime argument.

text
/ Detailed source-code truncated for AI context efficiency. /

---

Docs/Parsing

Parsing

The library natively supports the RFC 3339 format, most ISO 8601 formats and some other common formats.

python
>>> import pendulum

>>> dt = pendulum.parse('1975-05-21T22:00:00')
>>> print(dt)
'1975-05-21T22:00:00+00:00

You can pass a tz keyword to specify the timezone


>>> dt = pendulum.parse('1975-05-21T22:00:00', tz='Europe/Paris')
>>> print(dt)
'1975-05-21T22:00:00+01:00'

Not ISO 8601 compliant but common


>>> dt = pendulum.parse('1975-05-21 22:00:00')

If you pass a non-standard or more complicated string, it will raise an exception, so it is advised to
use the
from_format() helper instead.

However, if you want the library to fall back on the dateutil parser,
you have to pass
strict=False.

python
>>> import pendulum

>>> dt = pendulum.parse('31-01-01')
Traceback (most recent call last):
...
ParserError: Unable to parse string [31-01-01]

>>> dt = pendulum.parse('31-01-01', strict=False)
>>> print(dt)
'2031-01-01T00:00:00+00:00'


RFC 3339

| String | Output |
| --------------------------------- | ------------------------------------------|
| 1996-12-19T16:39:57-08:00 | 1996-12-19T16:39:57-08:00 |
| 1990-12-31T23:59:59Z | 1990-12-31T23:59:59+00:00 |

ISO 8601

Datetime

| String | Output |
| --------------------------------- | ----------------------------------------- |
| 20161001T143028+0530 | 2016-10-01T14:30:28+05:30 |
| 20161001T14 | 2016-10-01T14:00:00+00:00 |

Date

| String | Output |
| --------------------------------- | ----------------------------------------- |
| 2012 | 2012-01-01T00:00:00+00:00 |
| 2012-05-03 | 2012-05-03T00:00:00+00:00 |
| 20120503 | 2012-05-03T00:00:00+00:00 |
| 2012-05 | 2012-05-01T00:00:00+00:00 |

Ordinal day

| String | Output |
| ---------------------------------- | ----------------------------------------- |
| 2012-007 | 2012-01-07T00:00:00+00:00 |
| 2012007 | 2012-01-07T00:00:00+00:00 |

Week number

| String | Output |
| --------------------------------- | ----------------------------------------- |
| 2012-W05 | 2012-01-30T00:00:00+00:00 |
| 2012W05 | 2012-01-30T00:00:00+00:00 |
| 2012-W05-5 | 2012-02-03T00:00:00+00:00 |
| 2012W055 | 2012-02-03T00:00:00+00:00 |

Time

When passing only time information the date will default to today.

| String | Output |
| --------------------------------- | ------------------------------------------ |
| 00:00 | 2016-12-17T00:00:00+00:00 |
| 12:04:23 | 2016-12-17T12:04:23+00:00 |
| 120423 | 2016-12-17T12:04:23+00:00 |
| 12:04:23.45 | 2016-12-17T12:04:23.450000+00:00 |

Intervals

| String | Output |
| ----------------------------------------- | ------------------------------------------------------ |
| 2007-03-01T13:00:00Z/2008-05-11T15:30:00Z | 2007-03-01T13:00:00+00:00 -> 2008-05-11T15:30:00+00:00 |
| 2008-05-11T15:30:00Z/P1Y2M10DT2H30M | 2008-05-11T15:30:00+00:00 -> 2009-07-21T18:00:00+00:00 |
| P1Y2M10DT2H30M/2008-05-11T15:30:00Z | 2007-03-01T13:00:00+00:00 -> 2008-05-11T15:30:00+00:00 |

!!!note

You can pass the exact keyword argument to parse() to get the exact type
that the string represents:

python
>>> import pendulum

>>> pendulum.parse('2012-05-03', exact=True)
Date(2012, 05, 03)

>>> pendulum.parse('12:04:23', exact=True)
Time(12, 04, 23)

---

Docs/String Formatting

String formatting

The __str__ magic method is defined to allow DateTime instances to be printed
as a pretty date string when used in a string context.

The default string representation is the same as the one returned by the isoformat() method.

python
>>> import pendulum

>>> dt = pendulum.datetime(1975, 12, 25, 14, 15, 16)
>>> print(dt)
'1975-12-25T14:15:16+00:00'

>>> dt.to_date_string()
'1975-12-25'

>>> dt.to_formatted_date_string()
'Dec 25, 1975'

>>> dt.to_time_string()
'14:15:16'

>>> dt.to_datetime_string()
'1975-12-25 14:15:16'

>>> dt.to_day_datetime_string()
'Thu, Dec 25, 1975 2:15 PM'

You can also use the format() method


>>> dt.format('dddd Do [of] MMMM YYYY HH:mm:ss A')
'Thursday 25th of December 1975 02:15:16 PM'

Of course, the strftime method is still available


>>> dt.strftime('%A %-d%t of %B %Y %I:%M:%S %p')
'Thursday 25th of December 1975 02:15:16 PM'

!!!note

For localization support see the Localization section.

Common Formats


The following are methods to display a
DateTime instance as a common format:

python
>>> import pendulum

>>> dt = pendulum.now()

>>> dt.to_atom_string()
'1975-12-25T14:15:16-05:00'

>>> dt.to_cookie_string()
'Thursday, 25-Dec-1975 14:15:16 EST'

>>> dt.to_iso8601_string()
'1975-12-25T14:15:16-0500'

>>> dt.to_rfc822_string()
'Thu, 25 Dec 75 14:15:16 -0500'

>>> dt.to_rfc850_string()
'Thursday, 25-Dec-75 14:15:16 EST'

>>> dt.to_rfc1036_string()
'Thu, 25 Dec 75 14:15:16 -0500'

>>> dt.to_rfc1123_string()
'Thu, 25 Dec 1975 14:15:16 -0500'

>>> dt.to_rfc2822_string()
'Thu, 25 Dec 1975 14:15:16 -0500'

>>> dt.to_rfc3339_string()
'1975-12-25T14:15:16-05:00'

>>> dt.to_rss_string()
'Thu, 25 Dec 1975 14:15:16 -0500'

>>> dt.to_w3c_string()
'1975-12-25T14:15:16-05:00'

Formatter

Pendulum uses its own formatter when using the format() method.

This format is more intuitive to use than the one used with strftime()
and supports more directives.

python
>>> import pendulum

>>> dt = pendulum.datetime(1975, 12, 25, 14, 15, 16)
>>> dt.format('YYYY-MM-DD HH:mm:ss')
'1975-12-25 14:15:16'

Tokens

The following tokens are currently supported:


| | Token | Output |
| ------------------------------ | ------------- | ------------------------------------------ |
| Year | YYYY | 2000, 2001, 2002 ... 2012, 2013 |
| | YY | 00, 01, 02 ... 12, 13 |
| | Y | 2000, 2001, 2002 ... 2012, 2013 |
| Quarter | Q | 1 2 3 4 |
| | Qo | 1st 2nd 3rd 4th |
| Month | MMMM | January, February, March ... |
| | MMM | Jan, Feb, Mar ... |
| | MM | 01, 02, 03 ... 11, 12 |
| | M | 1, 2, 3 ... 11, 12 |
| | Mo | 1st 2nd ... 11th 12th |
| Day of Year | DDDD | 001, 002, 003 ... 364, 365 |
| | DDD | 1, 2, 3 ... 4, 5 |
| Day of Month | DD | 01, 02, 03 ... 30, 31 |
| | D | 1, 2, 3 ... 30, 31 |
| | Do | 1st, 2nd, 3rd ... 30th, 31st |
| Day of Week | dddd | Monday, Tuesday, Wednesday ... |
| | ddd | Mon, Tue, Wed ... |
| | dd | Mo, Tu, We ... |
| | d | 0, 1, 2 ... 6 |
| Days of ISO Week | E | 1, 2, 3 ... 7 |
| Hour | HH | 00, 01, 02 ... 23 |
| | H | 0, 1, 2 ... 23 |
| | hh | 01, 02, 03 ... 11, 12 |
| | h | 1, 2, 3 ... 11, 12 |
| Minute | mm | 00, 01, 02 ... 58, 59 |
| | m | 0, 1, 2 ... 58, 59 |
| Second | ss | 00, 01, 02 ... 58, 59 |
| | s | 0, 1, 2 ... 58, 59 |
| Fractional Second | S | 0 1 ... 8 9 |
| | SS | 00, 01, 02 ... 98, 99 |
| | SSS | 000 001 ... 998 999 |
| | SSSS ... | 000[0..] 001[0..] ... 998[0..] 999[0..] |
| | SSSSSS | |
| AM / PM | A | AM, PM |
| Timezone | Z | -07:00, -06:00 ... +06:00, +07:00 |
| | ZZ | -0700, -0600 ... +0600, +0700 |
| | z | Asia/Baku, Europe/Warsaw, GMT ... |
| | zz | EST CST ... MST PST |
| Seconds timestamp | X | 1381685817, 1234567890.123 |
| Milliseconds timestamp | x | 1234567890123 |


Localized Formats

Because preferred formatting differs based on locale,
there are a few tokens that can be used to format an instance based on its locale.

| | | |
| ------------------------------------------------------ | ------------- | ------------------------------------------ |
| Time | LT | 8:30 PM |
| Time with seconds | LTS | 8:30:25 PM |
| Month numeral, day of month, year | L | 09/04/1986 |
| Month name, day of month, year | LL | September 4 1986 |
| Month name, day of month, year, time | LLL | September 4 1986 8:30 PM |
| Month name, day of month, day of week, year, time | LLLL | Thursday, September 4 1986 8:30 PM |

Escaping Characters

To escape characters in format strings, you can wrap the characters in square brackets.

python
>>> import pendulum

>>> dt = pendulum.now()
>>> dt.format('[today] dddd')
'today Sunday'

---

Docs/Testing

Testing

Pendulum provides a few helpers to help you control the flow of time in your tests. Note that
these helpers are only available if you opted in the
test extra during installation.

!!!warning
If you are migrating from Pendulum 2, note that the
set_test_now() and test()
helpers have been removed.


Relative time travel

You can travel in time relatively to the current time

python
>>> import pendulum

>>> now = pendulum.now()
>>> pendulum.travel(minutes=5)
>>> pendulum.now().diff_for_humans(now)
"5 minutes after"

Note that once you've travelled in time the clock keeps ticking. If you prefer to stop the time completely
you can use the
freeze parameter:

python
>>> import pendulum

>>> now = pendulum.now()
>>> pendulum.travel(minutes=5, freeze=True)
>>> pendulum.now().diff_for_humans(now)
"5 minutes after" # This will stay like this indefinitely


Absolute time travel

Sometimes, you may want to place yourself at a specific point in time.
This is possible by using the
travel_to() helper. This helper accepts a DateTime instance
that represents the point in time where you want to travel to.

python
>>> import pendulum

>>> pendulum.travel_to(pendulum.yesterday())

Similarly to travel, it's important to remember that, by default, the time keeps ticking so, if you prefer
stopping the time, use the
freeze parameter:

python
>>> import pendulum

>>> pendulum.travel_to(pendulum.yesterday(), freeze=True)

Travelling back to the present

Using any of the travel helpers will keep you in the past, or future, until you decide
to travel back to the present time. To do so, you may use the
travel_back() helper.

python
>>> import pendulum

>>> now = pendulum.now()
>>> pendulum.travel(minutes=5, freeze=True)
>>> pendulum.now().diff_for_humans(now)
"5 minutes after"
>>> pendulum.travel_back()
>>> pendulum.now().diff_for_humans(now)
"a few seconds after"

However, it might be cumbersome to remember to travel back so, instead, you can use any of the helpers as a context
manager:

python
>>> import pendulum

>>> now = pendulum.now()
>>> with pendulum.travel(minutes=5, freeze=True):
>>> pendulum.now().diff_for_humans(now)
"5 minutes after"
>>> pendulum.now().diff_for_humans(now)
"a few seconds after"

---

Docs/Timezones

Timezones

Timezones are an important part of every datetime library, and pendulum
tries to provide an easy and accurate system to handle them properly.

!!!note

The timezone system works best inside the pendulum ecosystem but
can also be used with the standard
datetime library with a few limitations.
See Using the timezone library directly.

Normalization

When you create a DateTime instance, the library will normalize it for the
given timezone to properly handle any transition that might have occurred.

python
>>> import pendulum

>>> pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris')

2:30 for the 31th of March 2013 does not exist


so pendulum will return the actual time which is 3:30+02:00


'2013-03-31T03:30:00+02:00'

>>> pendulum.datetime(2013, 10, 27, 2, 30, tz='Europe/Paris')

Here, 2:30 exists twice in the day so pendulum will


assume that the transition already occurred


'2013-10-27T02:30:00+01:00'

You can, however, control the normalization behavior:

python
>>> import pendulum

>>> pendulum.datetime(2013, 3, 31, 2, 30, 0, 0, tz='Europe/Paris',
dst_rule=pendulum.PRE_TRANSITION)
'2013-03-31T01:30:00+01:00'
>>> pendulum.datetime(2013, 10, 27, 2, 30, 0, 0, tz='Europe/Paris',
dst_rule=pendulum.PRE_TRANSITION)
'2013-10-27T02:30:00+02:00'

>>> pendulum.datetime(2013, 3, 31, 2, 30, 0, 0, tz='Europe/Paris',
dst_rule=pendulum.TRANSITION_ERROR)

NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist


>>> pendulum.datetime(2013, 10, 27, 2, 30, 0, 0, tz='Europe/Paris',
dst_rule=pendulum.TRANSITION_ERROR)

AmbiguousTime: The datetime 2013-10-27 02:30:00 is ambiguous.

Note that it only affects instances at creation time. Shifting time around
transition times still behaves the same.

Shifting time to transition

So, what happens when you add time to a DateTime instance and stumble upon
a transition time?
Well
pendulum, provided with the context of the previous instance, will
adopt the proper behavior and apply the transition accordingly.

python
>>> import pendulum

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

>>> dt = pendulum.datetime(2013, 10, 27, 2, 59, 59, 999999,
tz='Europe/Paris',
dst_rule=pendulum.PRE_TRANSITION)
'2013-10-27T02:59:59.999999+02:00'
>>> dt = dt.add(microseconds=1)
'2013-10-27T02:00:00+01:00'
>>> dt = dt.subtract(microseconds=1)
'2013-10-27T02:59:59.999999+02:00'

Switching timezones

You can easily change the timezone of a DateTime instance
with the
in_timezone() method.

!!!note

You can also use the more concise in_tz()

python
>>> in_paris = pendulum.datetime(2016, 8, 7, 22, 24, 30, tz='Europe/Paris')
'2016-08-07T22:24:30+02:00'
>>> in_paris.in_timezone('America/New_York')
'2016-08-07T16:24:30-04:00'
>>> in_paris.in_tz('Asia/Tokyo')
'2016-08-08T05:24:30+09:00'

Using the timezone library directly

!!!warning

You should avoid using the timezone library in Python < 3.6.

This is due to the fact that Pendulum relies heavily on the presence
of the
fold attribute which was introduced in Python 3.6.

The reason it works inside the Pendulum ecosystem is that it
backports the
fold attribute in the DateTime class.

Like said in the introduction, you can use the timezone library
directly with standard
datetime objects but with limitations, especially
when adding and subtracting time around transition times.

The value of the fold attribute will be used
by default to determine the transition rule.

python
>>> from datetime import datetime
>>> from pendulum import timezone

>>> paris = timezone('Europe/Paris')
>>> dt = datetime(2013, 3, 31, 2, 30)

By default, fold is set to 0


>>> dt = paris.convert(dt)
>>> dt.isoformat()
'2013-03-31T01:30:00+01:00'

>>> dt = datetime(2013, 3, 31, 2, 30, fold=1)
>>> dt = paris.convert(dt)
>>> dt.isoformat()
'2013-03-31T03:30:00+02:00'

Instead of relying on the fold attribute, you can use the dst_rule
keyword argument. This is especially useful if you want to raise errors
on non-existing and ambiguous times.

python
>>> import pendulum

>>> dt = datetime(2013, 3, 31, 2, 30)

By default, fold is set to 0


>>> dt = paris.convert(dt, dst_rule=pendulum.PRE_TRANSITION)
>>> dt.isoformat()
'2013-03-31T01:30:00+01:00'

>>> dt = paris.convert(dt, dst_rule=pendulum.POST_TRANSITION)
>>> dt.isoformat()
'2013-03-31T03:30:00+02:00'

>>> paris.convert(dt, dst_rule=pendulum.TRANSITION_ERROR)

NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist

This works as expected. However, whenever we add or subtract a timedelta
object, things get tricky.

python
>>> from datetime import datetime, timedelta
>>> from pendulum import timezone

>>> dt = datetime(2013, 3, 31, 1, 59, 59, 999999)
>>> dt = paris.convert(dt)
>>> dt.isoformat()
'2013-03-31T01:59:59.999999+01:00'
>>> dt = dt + timedelta(microseconds=1)
>>> dt.isoformat()
'2013-03-31T02:00:00+01:00'

This is not what we expect. It should be 2013-03-31T03:00:00+02:00.
It is actually easy to retrieve the proper datetime by using
convert()
again.

python
>>> dt = tz.convert(dt)
>>> dt.isoformat()
'2013-03-31T03:00:00+02:00'

You can also get a normalized datetime object
from a
Timezone by using the datetime() method:

python
>>> import pendulum

>>> tz = pendulum.timezone('Europe/Paris')
>>> dt = tz.datetime(2013, 3, 31, 2, 30)
>>> dt.isoformat()
'2013-03-31T03:30:00+02:00'

---

CHANGELOG

Change Log

[3.2.0] - 2026-01-30

Added


- Added support for Python 3.14 #923
- Added upper limit to
time-machine dependency #931

Changed


- Dropped support for PyPy below PyPy3.11 #937
- Optimize usage of
re. methods #741
- Fixed
pendulum.parse not being marked as exported #693
- Fixed
pendulum.parse('now', tz='...') ignoring the timezone #701
- Use
pathlib to read Unix TZ data #742
- Fixed
Interval deepcopying #850
- Fixed typo in
end_of('century') docs #910
- Bumped PyO3 to 0.27 #922
- Fixed incorrect date offset calculation in Rust extensions #918
- Changed locales and
pytest to be lazy loaded #926
- Fixed error of
Duration deepcopy not including weeks #933
- Fixed empty
Durations not being an error in Python ISO8601 parser implementation #903
- Fixed parsing invalid interval string #860
- Fixed pluralization bug in
Duration.in_words() #826

Locales


- Added HI (Hindi) locale #902

Removed


- Removed dependency on
pytz #911

[3.1.0] - 2025-04-19

Added


- Added support for Python 3.13 #871

Changed


- Removed support for Python 3.8 #863
- Fixed pure Python wheels support #889
- Fixed
pendulum.tz.timezones() to use system tzdata #801
- Fixed spelling of Kyiv #885
- Fixed
DeprecationWarning from utcfromtimestamp #887
- Fixed parsing of invalid intervals #843

Locales


- Added UA (Ukraine) locale #793
- Added BG (Bulgarian) locale #812
- Fixed KO (Korean) translations for
before and after #858


[3.0.0] - 2023-12-16

Changed

- Relaxed dependency constraints. #760
- The testing helpers are now optional and must be opted-in via the
test extra. #778

Fixed

- Removed remaining mentions of periods instead of intervals. #757
- Fixed the behavior of the
week_of_month property for edge cases in January and December. #774
- Fixed the handling of the
fold attribute when deep-copying a DateTime instance. #776
- Fixed errors where hours and days were not handled properly when adding durations. #775
- Fixed errors where hours and days were not handled properly when adding durations. #775


[3.0.0b1] - 2023-10-01

Added

- Made instance() support all native types (date, time, datetime). #732

Changed

- Dropped support for Python 3.7. #734
- Rewrote extensions in Rust. #721
- Made day of week convention more consistent across the codebase. #731

Fixed

- Fixed datetime string representation to match the native library. #733
- Fixed issues on some system when retrieving the local timezone. #733
- Fixed DST handling in
start_of()/end_of() methods. #713


[3.0.0a1] - 2022-11-23

Added

- Added new testing helpers to time travel. #626

Changed

- Dropped support for Python 2.7, 3.5 and 3.6. #569
- The
Timezone class now relies on the native zoneinfo.ZoneInfo class. #569
- Renamed the
Period class to Interval. #676
- Renamed the
period helper to interval. #676
- Removed existing testing helpers:
test() and set_test_now(). #626

Locales

- Added the sk locale. #575
- Added the
ja locale. #610
- Added the
he locale. #585
- Added the
sv locale. #562


[2.1.1] - 2020-07-13

Fixed

- Fixed errors where invalid timezones were matched in from_format() (#374).
- Fixed errors when subtracting negative timedeltas (#419).
- Fixed errors in total units computation for durations with years and months (#482).
- Fixed an error where the
fold attribute was overridden when using replace() (#414).
- Fixed an error where
now() was not returning the correct result on DST transitions (#483).
- Fixed inconsistent typing annotation for the
parse() function (#452).

Locales

- Added the pl locale (#459).


[2.1.0] - 2020-03-07

Added

- Added better typing and PEP-561 compliance (#320).
- Added the
is_anniversary() method as an alias of is_birthday() (#298).

Changed

- Dropped support for Python 3.4.
-
is_utc() will now return True for any datetime with an offset of 0, similar to the behavior in the 1.* versions (#295)
-
Duration.in_words() will now return 0 milliseconds for empty durations.

Fixed

- Fixed various issues with timezone transitions for some edge cases (#321, (#350)).
- Fixed out of bound detection for
nth_of("month") (#357).
- Fixed an error where extra text was accepted in
from_format() (#372).
- Fixed a recursion error when adding time to a
DateTime with a fixed timezone (#431).
- Fixed errors where
Period instances were not properly compared to other classes, especially timedelta instances (#427).
- Fixed deprecation warnings due to internal regexps (#427).
- Fixed an error where the
test() helper would not unset the test instance when an exception was raised (#445).
- Fixed an error where the
week_of_month attribute was not returning the correct value (#446).
- Fixed an error in the way the
Z ISO-8601 UTC designator was not parsed as UTC (#448).

Locales

- Added the nl locale.
- Added the
it locale.
- Added the
id locale.
- Added the
nb locale.
- Added the
nn locale.


[2.0.5] - 2019-07-03

Fixed

- Fixed ISO week dates not being parsed properly in from_format().
- Fixed loading of some timezones with empty posix spec.
- Fixed deprecation warnings.

Locales

- Added RU locale.


[2.0.4] - 2018-10-30

Fixed

- Fixed from_format() not recognizing input strings when the specified pattern had escaped elements.
- Fixed missing
x token for string formatting.
- Fixed reading timezone files.
- Added support for parsing padded 2-digit days of the month with
from_format()
- Fixed
from_format() trying to parse escaped tokens.
- Fixed the
z token timezone parsing in from_format() to allow underscores.
- Fixed C extensions build errors.
- Fixed
age calculation for future dates.


[2.0.3] - 2018-07-30

Fixed

- Fixed handling of pytz timezones.
- Fixed some formatter's tokens handling.
- Fixed errors on some systems when retrieving timezone from localtime files.
- Fixed
diff methods.
- Fixed
closest()/farthest() methods.


[2.0.2] - 2018-05-29

Fixed

- Fixed the weeks property for negative Period instances.
- Fixed
start_of() methods not setting microseconds to 0.
- Fixed errors on some systems when retrieving timezone from clock files.
- Fixed parsing of partial time.
- Fixed parsing not raising an error for week 53 for ordinary years.
- Fixed string formatting not supporting
strftime format.


[2.0.1] - 2018-05-10

Fixed

- Fixed behavior of the YY token in from_format().
- Fixed errors on some systems when retrieving timezone from clock files.


[2.0.0] - 2018-05-08

Added

- Added years and months support to durations.
- Added the
test_local_timezone() and set_local_timezone() helpers to ease testing.
- Added support of ISO 8601 duration parsing.
- Added support of ISO 8601 interval parsing.
- Added a
local() helper.
- Added a
naive() helper and a naive() method.
- Added support for POSIX specification to extend timezones DST transitions.

Changed

- Pendulum class has been renamed to DateTime.
-
Interval class has been renamed to Duration.
- Changed and improved the timezone system.
- Removed the
create() helper.
- Removed the
utcnow() helper.
-
strict keyword argument for parse has been renamed to exact.
-
at() now supports setting partial time.
-
local, utc and is_dst are now methods rather than properties (is_local(), is_utc(), is_dst()).
- Changed the
repr of most common objects.
- Made the
strict keyword argument for parse false by default, which means it will not fallback on the dateutil parser.
- Improved performances of the
precise_diff() helper.
- The
alternative formatter is now the default one.
-
set_to_string_format()/reset_to_string_format() methods have been removed.
-
from_format() now uses the alternative formatter tokens.
- Removed
xrange() method of the Period class and made range() a generator.
- New locale system which uses CLDR data for most of the translations.
-
diff_for_humans() now returns a few seconds where appropriate.
- Removed
Period.intersect()`.

[Unreleased]: https://github.com/python-pendulum/pendulum/compare/3.1.0...master
[3.2.0]: https://github.com/python-pendulum/pendulum/releases/tag/3.2.0
[3.1.0]: https://github.com/python-pendulum/pendulum/releases/tag/3.1.0
[3.1.0]: https://github.com/python-pendulum/pendulum/releases/tag/3.1.0
[3.0.0]: https://github.com/python-pendulum/pendulum/releases/tag/3.0.0
[3.0.0b1]: https://github.com/python-pendulum/pendulum/releases/tag/3.0.0b1
[3.0.0a1]: https://github.com/python-pendulum/pendulum/releases/tag/3.0.0a1
[2.1.1]: https://github.com/python-pendulum/pendulum/releases/tag/2.1.1
[2.1.0]: https://github.com/python-pendulum/pendulum/releases/tag/2.1.0
[2.0.5]: https://github.com/python-pendulum/pendulum/releases/tag/2.0.5
[2.0.4]: https://github.com/python-pendulum/pendulum/releases/tag/2.0.4
[2.0.3]: https://github.com/python-pendulum/pendulum/releases/tag/2.0.3
[2.0.2]: https://github.com/python-pendulum/pendulum/releases/tag/2.0.2
[2.0.1]: https://github.com/python-pendulum/pendulum/releases/tag/2.0.1
[2.0.0]: https://github.com/python-pendulum/pendulum/releases/tag/2.0.0

---