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.
/ 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.
/ 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.
>>> 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 mannernow()
the is created in the same timezone as the instance.
>>> 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 durationDateTime
between two 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,None
or 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 dateTrue
is less than the current instance.
This will default to , return the absolute value.
>>> 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.
>>> import pendulumThe 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')diff_for_humans()
before the call or specifically for the call by passing the locale keyword
argument. See the Localization section for more detail.
>>> 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.
>>> 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:
>>> import pendulum>>> it = pendulum.duration(days=1177, seconds=7284, microseconds=1234)
!!!note
Unlike the native timedelta class, durations support specifying
years and months.
>>> import pendulum >>> it = pendulum.duration(years=2, months=3)
However, to maintain compatibility, native methods and properties will
make approximations:
>>> it.days
820 >>> it.total_seconds()
70848000.0
Properties and Duration Methods
The Duration class brings more properties than the default days, seconds andmicroseconds.
>>> 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
168Days, 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
1997If 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
7284If 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.
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.
>>> 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.
>>> 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.
>>> 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
>>> 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.
>>> 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.
>>> 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:
$ pip install pendulumor, if you are using poetry:
$ poetry add pendulumOptional 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
$ pip install pendulum[test]or, if you are using poetry:
$ 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.
>>> 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,tz
and the timezone (the keyword argument) to UTC.Timezone
Otherwise it can be a instance or simply a string timezone value.
>>> 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.
>>> 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.
>>> 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.today()
The only thing to really notice here is that , tomorrow() and yesterday(),00:00:00
besides behaving as expected, all accept a timezone parameter
and each has their time value set to .
>>> 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.
>>> import pendulum>>> naive = pendulum.naive(2015, 2, 5)
>>> naive.timezone
None
The next helper, from_format(), is similar to the native datetime.strptime() functionDateTime
but uses custom tokens to create a instance.
>>> 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:
>>> 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 timestampUTC
and will set the timezone as well or default it to .
>>> 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,DateTime
you can create a instance via the instance() function.
>>> 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:
>>> 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 durationHowever the days property will still remain the same
to keep the compatibility with the timedelta class
>>> interval.days
5829Be aware that an interval, just like an duration, is compatible with the timedelta class regardingremaining_days
its attributes. However, its custom attributes (like ) will be aware of any DST
transitions that might have occurred and adjust accordingly. Let's take an example:
>>> 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
82800interval 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.
>>> 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:
>>> 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:
>>> interval = pendulum.interval(end, start)
>>> interval.remaining_days
-2If you have inverted dates but want to make sure that the interval is positive,
you should set the absolute keyword argument to True:
>>> interval = pendulum.interval(end, start, absolute=True)
>>> interval.remaining_days
2Range
If you want to iterate over a interval, you can use the range() method:
>>> 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:
>>> 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,days
the unit will be in this case:
>>> for dt in interval:
>>> print(dt)You can check if a DateTime instance is inside a interval using the in keyword:
>>> 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.UTC
For example, all comparisons are done in or in the timezone of the datetime being used.
>>> 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:
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:
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:
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.
>>> 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 localependulum.set_locale()
by using .
>>> 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()locale
method accepts a keyword argument to use a locale for a specific call.
>>> 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() methods00:00:00
set the time to and the end_of() methods set the time to 23:59:59.999999.
The only one slightly different is the average() method.DateTime
It returns the middle date between itself and the provided argument.
/ 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.
>>> 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.
>>> 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:
>>> 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.
>>> 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:
>>> 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.
>>> 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.
>>> 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
>>> 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:
>>> 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.
>>> 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 preferfreeze
stopping the time, use the parameter:
>>> 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.
>>> 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:
>>> 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.
>>> 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:
>>> 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 uponpendulum
a transition time?
Well , provided with the context of the previous instance, will
adopt the proper behavior and apply the transition accordingly.
>>> 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 instancein_timezone()
with the method.
!!!note
You can also use the more concise in_tz()
>>> 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.
>>> 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.
>>> 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.
>>> 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.convert()
It is actually easy to retrieve the proper datetime by using
again.
>>> dt = tz.convert(dt)
>>> dt.isoformat()
'2013-03-31T03:00:00+02:00'You can also get a normalized datetime objectTimezone
from a by using the datetime() method:
>>> 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 #931Changed
- 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() #826Locales
- Added HI (Hindi) locale #902Removed
- Removed dependency on pytz #911[3.1.0] - 2025-04-19
Added
- Added support for Python 3.13 #871Changed
- 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 #843Locales
- 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. #778Fixed
- 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). #732Changed
- 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(). #626Locales
- 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
---