Installing pendulum
is quite simple:
$ pip install pendulum
or, if you are using poetry:
$ poetry add pendulum
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.
>>> 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
.
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,
and the timezone (the tz
keyword argument) to UTC
.
Otherwise it can be a Timezone
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.
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
.
>>> 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()
function
but uses custom tokens to create a DateTime
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 timestamp
and will set the timezone as well or default it to UTC
.
>>> 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.
>>> dt = datetime(2008, 1, 1)
>>> p = pendulum.instance(dt)
>>> print(p)
'2008-01-01T00:00:00+00:00'
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'
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 |
String | Output |
---|---|
20161001T143028+0530 | 2016-10-01T14:30:28+05:30 |
20161001T14 | 2016-10-01T14:00:00+00:00 |
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 |
String | Output |
---|---|
2012-007 | 2012-01-07T00:00:00+00:00 |
2012007 | 2012-01-07T00:00:00+00:00 |
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 |
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 |
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)
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 locale
by using pendulum.set_locale()
.
>>> 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.
>>> pendulum.set_locale('de')
>>> dt = pendulum.now().add(years=1)
>>> dt.diff_for_humans(locale='fr')
'dans 1 an'
Pendulum gives access to more attributes and properties than the default datetime
class.
>>> import pendulum
>>> dt = pendulum.parse('2012-09-05T23:26:11.123789')
# These properties specifically return integers
>>> dt.year
2012
>>> dt.month
9
>>> dt.day
5
>>> dt.hour
23
>>> dt.minute
26
>>> dt.second
11
>>> dt.microsecond
123789
>>> dt.day_of_week
3
>>> dt.day_of_year
248
>>> dt.week_of_month
1
>>> dt.week_of_year
36
>>> dt.days_in_month
30
>>> dt.timestamp()
1346887571.123789
>>> dt.float_timestamp
1346887571.123789
>>> dt.int_timestamp
1346887571
>>> pendulum.datetime(1975, 5, 21).age
41 # calculated vs now in the same tz
>>> dt.quarter
3
# Returns an int of seconds difference from UTC (+/- sign included)
>>> pendulum.from_timestamp(0).offset
0
>>> pendulum.from_timestamp(0, 'America/Toronto').offset
-18000
# Returns a float of hours difference from UTC (+/- sign included)
>>> pendulum.from_timestamp(0, 'America/Toronto').offset_hours
-5.0
>>> pendulum.from_timestamp(0, 'Australia/Adelaide').offset_hours
9.5
# Gets the timezone instance
>>> pendulum.now().timezone
>>> pendulum.now().tz
# Gets the timezone name
>>> pendulum.now().timezone_name
# Indicates if daylight savings time is on
>>> dt = pendulum.datetime(2012, 1, 1, tz='America/Toronto')
>>> dt.is_dst()
False
>>> dt = pendulum.datetime(2012, 9, 1, tz='America/Toronto')
>>> dt.is_dst()
True
# Indicates if the instance is in the same timezone as the local timezone
>>> pendulum.now().is_local()
True
>>> pendulum.now('Europe/London').is_local()
False
# Indicates if the instance is in the UTC timezone
>>> pendulum.now().is_utc()
False
>>> pendulum.now('Europe/London').is_local()
False
>>> pendulum.now('UTC').is_utc()
True
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'
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.
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'
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'
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, 24 |
H | 0, 1, 2 ... 23, 24 | |
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 |
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 |
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'
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 manner
the now()
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, 2, 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
To easily add and subtract time, you can use the add()
and subtract()
methods.
Each method returns a new DateTime
instance.
>>> import pendulum
>>> dt = pendulum.datetime(2012, 1, 31)
>>> dt.to_datetime_string()
'2012-01-31 00:00:00'
>>> dt = dt.add(years=5)
'2017-01-31 00:00:00'
>>> dt = dt.add(years=1)
'2018-01-31 00:00:00'
>>> dt = dt.subtract(years=1)
'2017-01-31 00:00:00'
>>> dt = dt.subtract(years=5)
'2012-01-31 00:00:00'
>>> dt = dt.add(months=60)
'2017-01-31 00:00:00'
>>> dt = dt.add(months=1)
'2017-02-28 00:00:00'
>>> dt = dt.subtract(months=1)
'2017-01-28 00:00:00'
>>> dt = dt.subtract(months=60)
'2012-01-28 00:00:00'
>>> dt = dt.add(days=29)
'2012-02-26 00:00:00'
>>> dt = dt.add(days=1)
'2012-02-27 00:00:00'
>>> dt = dt.subtract(days=1)
'2012-02-26 00:00:00'
>>> dt = dt.subtract(days=29)
'2012-01-28 00:00:00'
>>> dt = dt.add(weeks=3)
'2012-02-18 00:00:00'
>>> dt = dt.add(weeks=1)
'2012-02-25 00:00:00'
>>> dt = dt.subtract(weeks=1)
'2012-02-18 00:00:00'
>>> dt = dt.subtract(weeks=3)
'2012-01-28 00:00:00'
>>> dt = dt.add(hours=24)
'2012-01-29 00:00:00'
>>> dt = dt.add(hours=1)
'2012-02-25 01:00:00'
>>> dt = dt.subtract(hours=1)
'2012-02-29 00:00:00'
>>> dt = dt.subtract(hours=24)
'2012-01-28 00:00:00'
>>> dt = dt.add(minutes=61)
'2012-01-28 01:01:00'
>>> dt = dt.add(minutes=1)
'2012-01-28 01:02:00'
>>> dt = dt.subtract(minutes=1)
'2012-01-28 01:01:00'
>>> dt = dt.subtract(minutes=24)
'2012-01-28 00:00:00'
>>> dt = dt.add(seconds=61)
'2012-01-28 00:01:01'
>>> dt = dt.add(seconds=1)
'2012-01-28 00:01:02'
>>> dt = dt.subtract(seconds=1)
'2012-01-28 00:01:01'
>>> dt = dt.subtract(seconds=61)
'2012-01-28 00:00:00'
>>> dt = dt.add(years=3, months=2, days=6, hours=12, minutes=31, seconds=43)
'2015-04-03 12:31:43'
>>> dt = dt.subtract(years=3, months=2, days=6, hours=12, minutes=31, seconds=43)
'2012-01-28 00:00:00'
Note
Passing negative values to add()
is also possible and will act exactly
like subtract()
The diff()
method returns a Period 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.
>>> 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
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:
When comparing a value in the future to default now:
When comparing a value in the past to another value:
When comparing a value in the future to another value:
You may also pass True
as a 2nd parameter to remove the modifiers ago
, from now
, etc.
>>> 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.
>>> 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'
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.
>>> import pendulum
>>> dt = pendulum.datetime(2012, 1, 31, 12, 0, 0)
>>> dt.start_of('day')
'2012-01-31 00:00:00'
>>> dt.end_of('day')
'2012-01-31 23:59:59'
>>> dt.start_of('month')
'2012-01-01 00:00:00'
>>> dt.end_of('month')
'2012-01-31 23:59:59'
>>> dt.start_of('year')
'2012-01-01 00:00:00'
>>> dt.end_of('year')
'2012-12-31 23:59:59'
>>> dt.start_of('decade')
'2010-01-01 00:00:00'
>>> dt.end_of('decade')
'2019-12-31 23:59:59'
>>> dt.start_of('century')
'2000-01-01 00:00:00'
>>> dt.end_of('century')
'2099-12-31 23:59:59'
>>> dt.start_of('week')
'2012-01-30 00:00:00'
>>> dt.day_of_week == pendulum.MONDAY
True # ISO8601 week starts on Monday
>>> dt.end_of('week')
'2012-02-05 23:59:59'
>>> dt.day_of_week == pendulum.SUNDAY
True # ISO8601 week ends on SUNDAY
>>> dt.next(pendulum.WEDNESDAY)
'2012-02-01 00:00:00'
>>> dt.day_of_week == pendulum.WEDNESDAY
True
>>> dt = pendulum.datetime(2012, 1, 1, 12, 0, 0)
dt.next()
'2012-01-08 00:00:00'
>>> dt.next(keep_time=True)
'2012-01-08T12:00:00+00:00'
>>> dt = pendulum.datetime(2012, 1, 31, 12, 0, 0)
>>> dt.previous(pendulum.WEDNESDAY)
'2012-01-25 00:00:00'
>>> dt.day_of_week == pendulum.WEDNESDAY
True
>>> dt = pendulum.datetime(2012, 1, 1, 12, 0, 0)
>>> dt.previous()
'2011-12-25 00:00:00'
>>> dt.previous(keep_time=True)
'2011-12-25 12:00:00'
>>> start = pendulum.datetime(2014, 1, 1)
>>> end = pendulum.datetime(2014, 1, 30)
>>> start.average(end)
'2014-01-15 12:00:00'
# others that are defined that are similar
# and tha accept month, quarter and year units
# first_of(), last_of(), nth_of()
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.
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.
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.
>>> 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.999998+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'
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()
>>> 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'
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
.
It is actually easy to retrieve the proper datetime by using convert()
again.
>>> 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:
>>> import pendulum
>>> tz = pendulum.timezone('Europe/Paris')
>>> dt = tz.datetime(2013, 3, 31, 2, 30)
>>> dt.isoformat()
'2013-03-31T03:30:00+02:00'
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
>>> from datetime import datetime
>>> d1 = datetime(2012, 1, 1, 1, 2, 3, tzinfo=pytz.UTC)
>>> d2 = datetime(2011, 12, 31, 22, 2, 3, tzinfo=pytz.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
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
The Duration
class brings more properties than the default days
, seconds
and
microseconds
.
>>> 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.
# 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'
When you subtract a DateTime
instance from another, or use the diff()
method, it will return a Period
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)
>>> period = end - start
>>> period.years
15
>>> period.months
11
>>> period.in_years()
15
>>> period.in_months()
191
# Note that the weeks property
# will change compared to the Duration class
>>> period.weeks
2 # 832 for the duration
# However the days property will still remain the same
# to keep the compatiblity with the timedelta class
>>> period.days
5829
Be aware that a period, just like an interval, 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:
>>> import pendulum
>>> start = pendulum.datetime(2017, 3, 7, tz='America/Toronto')
>>> end = start.add(days=6)
>>> period = end - start
# timedelta properties
>>> period.days
5
>>> period.seconds
82800
# period properties
>>> period.remaining_days
6
>>> period.hours
0
>>> period.remaining_seconds
0
Warning
Due to their nature (fixed duration between two datetimes), most arithmetic operations will
return a Duration
instead of a Period
.
>>> import pendulum
>>> dt1 = pendulum.datetime(2016, 8, 7, 12, 34, 56)
>>> dt2 = dt1.add(days=6, seconds=34)
>>> period = pendulum.period(dt1, dt2)
>>> period * 2
Duration(weeks=1, days=5, minutes=1, seconds=8)
You can create an instance by using the period()
helper:
>>> import pendulum
>>> start = pendulum.datetime(2000, 1, 1)
>>> end = pendulum.datetime(2000, 1, 31)
>>> period = pendulum.period(start, end)
You can also make an inverted period:
>>> period = pendulum.period(end, start)
>>> period.remaining_days
-2
If you have inverted dates but want to make sure that the period is positive,
you should set the absolute
keyword argument to True
:
>>> period = pendulum.period(end, start, absolute=True)
>>> period.remaining_days
2
If you want to iterate over a period, you can use the range()
method:
>>> import pendulum
>>> start = pendulum.datetime(2000, 1, 1)
>>> end = pendulum.datetime(2000, 1, 10)
>>> period = pendulum.period(start, end)
>>> for dt in period.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
and seconds
You can pass an amount for the passed unit to control the length of the gap:
>>> for dt in period.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 Period
instance,
the unit will be days
in this case:
>>> for dt in period:
>>> print(dt)
You can check if a DateTime
instance is inside a period using the in
keyword:
>>> dt = pendulum.datetime(2000, 1, 4)
>>> dt in period
True
The testing methods allow you to set a DateTime
instance (real or mock) to be returned
when a "now" instance is created.
The provided instance will be returned specifically under the following conditions:
now()
method, ex. pendulum.now()
.parse()
method, ex. pendulum.parse('now')
>>> import pendulum
# Create testing datetime
>>> known = pendulum.datetime(2001, 5, 21, 12)
# Set the mock
>>> pendulum.set_test_now(known)
>>> print(pendulum.now())
'2001-05-21T12:00:00+00:00'
>>> print(pendulum.parse('now'))
'2001-05-21T12:00:00+00:00'
# Clear the mock
>>> pendulum.set_test_now()
>>> print(pendulum.now())
'2016-07-10T22:10:33.954851-05:00'
Related methods will also return values mocked according to the **now** instance.
```python
>>> print(pendulum.today())
'2001-05-21T00:00:00+00:00'
>>> print(pendulum.tomorrow())
'2001-05-22T00:00:00+00:00'
>>> print(pendulum.yesterday())
'2001-05-20T00:00:00+00:00'
If you don't want to manually clear the mock (or you are afraid of forgetting),
you can use the provided test()
contextmanager.
>>> import pendulum
>>> known = pendulum.datetime(2001, 5, 21, 12)
>>> with pendulum.test(known):
>>> print(pendulum.now())
'2001-05-21T12:00:00+00:00'
>>> print(pendulum.now())
'2016-07-10T22:10:33.954851-05:00'
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 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 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()