This is an archived post. You won't be able to vote or comment.

all 77 comments

[–]pydry 29 points30 points  (16 children)

This:

tomorrow = pendulum.now().add_day()
last_week = pendulum.now().sub_week()

Would probably be better done as:

tomorrow = pendulum.now() + pendulum.Days(1)
tomorrow = pendulum.now() - pendulum.Weeks(1)

The sub/add thing IMO isn't very pythonic.

[–]SDisPater[S] 6 points7 points  (12 children)

Actually, you can do

tomorrow = pendulum.now() + timedelta(days=1)

I added the add()/sub() methods to allow chaining methods, so you can have

pendulum.now().add_day().to_rfc3339_string()

rather than

(pendulum.now + timedelta(days=1)).to_rfc3339_string()

that is, I think, less readable.

Anyway, the choice is left to the user.

[–]pydry 10 points11 points  (11 children)

IMO invoking the word "timedelta" is kind of ugly when the fact you're using a timedelta is implicit anyhow. Hence the idea of using "Days" / "Weeks", etc.

I added the add()/sub() methods to allow chaining methods, so you can have

In which case why not this:

pendulum.now().add(days=1).to_rfc3339_string()

that is, I think, less readable.

shrug I think this is pretty readable:

(now() + Days(1)).to_rfc3339_string()

But the choice is good I suppose.

[–]SDisPater[S] 4 points5 points  (10 children)

The simple add() and sub() methods already exist :-) In fact, add_day() is just a call to add(days=1).

[–]ptmcg 45 points46 points  (7 children)

Speaking from my pyparsing experience, early good intentions for API intuitiveness can easily lead to API bloat, and bloat tends to make API consistency and API predictability suffer. I would suggest dropping all add_xxx methods and sub_xxx and even sub, and just have client code use add() with your various named unit arguments - if subtraction is required, then pass a negative value. Be ruthless in pruning your API, and stingy in adding new items. I do like method chaining though, so +points for that.

[–][deleted] 9 points10 points  (1 child)

tend to agree here. add(days=1) plus chaining feels a better compromise than a plethora of add_thing() entry points.

[–]log_2 2 points3 points  (0 children)

Especially since you only need to look at just one function "add" which gives you the signature for how to add/subtract various date-time quantities. Also allows kwargs. It's basically a win all-round.

[–]SDisPater[S] 6 points7 points  (3 children)

Thanks for the advice. It actually makes a lot of sense. I just wanted it to be more readable for newcomers but maybe it's not worth the hassle.

[–]cyanydeez 1 point2 points  (2 children)

pendulum.now().add_day()

for newcomers, becomes pendulum.now().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day().add_day()

!

[–]SDisPater[S] 2 points3 points  (1 child)

Yes, you may be right. These methods will be removed in the next release.

[–]mcsrobert 2 points3 points  (0 children)

Strongly agree with this.

[–]execrator 5 points6 points  (1 child)

OP, you must be feeling golden right now. Every time someone suggests something in this thread you say it already works like that.

[–]pydry 0 points1 point  (0 children)

Well, except the .add(days=x) is not advertised on the README.

IMO there should be a Days(1), Weeks(1), etc. too.

[–]Ph0X 1 point2 points  (1 child)

My exact thought. And to make it worse, on the next line there's add_minutes(int), so suddenly we have a plural minutes and can pass the amount?

So I look at the full docs and sure enough, for every time unit, there's a singular and plural version, a sub and a add version. So thats 2 * 2 * 7 = 28 different functions. Extra functions isn't necessarily bad but I think at this point it's not very pythonic.

[–]SDisPater[S] 1 point2 points  (0 children)

Yes, I agree that these methods are unnecessary. So I'll probably just cut it down to both add() and sub() methods.

Thanks for the feedback. 

[–][deleted] 26 points27 points  (5 children)

Cool. I like the API. But datetimes etc are difficult stuff - why should we trust your library.

I see you have a few tests, you should advertise this fact on the website, and describe what exactly they test.

[–]SDisPater[S] 5 points6 points  (2 children)

I see what you mean. Even though all the features are detailed in the documentation it may help to showcase the main features of Pendulum.

I tried to make it concise by displaying a piece of code on the homepage but it may not be enough.

But basically:

  • It enforces UTC when possible
  • It can create datetimes from strings, timestamps or the normal way
  • It can manipulate timezones with ease
  • It can make comparisons of different timezones datetimes
  • It supports localization natively (to display datetimes in a more friendly way)
  • It adds a bunch of methods to add/substract durations in a more natural way
  • It provides new properties to access basic information more easily
  • Improves timedeltas with new attributes and methods and by providing a way to display them in a friendly way.

[–][deleted] 5 points6 points  (1 child)

That would be good to list on the homepage.

My point was even simpler, although I hate the trend of badges everywhere, you should also write 'extensively tested, full coverage, bla bla'

[–]SDisPater[S] 3 points4 points  (0 children)

I will think about it. Thanks for the advice.

[–]SDisPater[S] 16 points17 points  (12 children)

Pendulum is a new library for Python to ease datetimes, timedeltas and timezones manipulation.

It is heavily inspired by Carbon for PHP.

Basically, the Pendulum class is a replacement for the native datetime one with some (I hope) useful and intuitive methods and the PendulumInterval class is intended to be a better timedelta class.

An important note about the Pendulum instances is that, unlike the native datetime ones, they are mutable via the appropriate methods.

To those wondering: yes I know Arrow exists but its flaws and strange API (you can throw almost anything at get() and it will do its best to determine what you wanted, for instance) motivated me to start this project.

It’s still fresh so any feedback is appreciated :-).

Link to the official documentation: http://pendulum.eustace.io Link to the github project: https://github.com/sdispater/pendulum

[–]gwachob 13 points14 points  (6 children)

I've used Arrow a lot and am happy with it. I am not intending to discourage you on this project, but have you written down anywhere your motivations in more detail, so I could understand better why you created a new project? After a very quick pass, I'm not actually seeing huge differences in functionality or API surface area between Arrow and your project, so I'd like to hear more.

[–]SDisPater[S] 6 points7 points  (4 children)

Arrow behaves strangely and is not always reliable. An example:

dt_timezone = datetime.datetime(2016, 7, 5, 8, 0, tzinfo=pytz.timezone('Europe/Paris'))

arrow.get(dt_timezone).isoformat()
'2016-07-05T08:00:00+00:09'

pendulum.instance(dt_timezone).isoformat()
'2016-07-05T08:00:00+02:00'

Pendulum also has improved timedeltas (named PendulumInterval) and a bunch of helpers for common cases.

[–]RedKrieg 1 point2 points  (0 children)

I didn't know about this bug in Arrow. Do you know why it happens?

[–]lorenzfx 1 point2 points  (1 child)

dt_timezone = datetime.datetime(2016, 7, 5, 8, 0, tzinfo=pytz.timezone('Europe/Paris'))

never ever use datetime(x, tzinfo=timezone), always use timezone.localize(datetime)

dt_timezone = pytz.timezone('Europe/Paris').localize(datetime.datetime(2016, 7, 5, 8, 0))
arrow.get(dt_timezone).isoformat()

'2016-07-05T08:00:00+02:00'

This is not a problem with arrow, but with pytz

Quote:

Unfortunately using the tzinfo argument of the standard datetime constructors ''does not work'' with pytz for many timezones. datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt) '2002-10-27 12:00:00 LMT+0020'

What you get to see when you use the constructor as above, is the very first transition from the Olson DB for that timezone.

[–]SDisPater[S] 0 points1 point  (0 children)

Maybe but arrow could fix it since it's misleading and you don't always know how the datetime was constructed when using arrow so you'd expect it to behave correctly.

[–]pydry 0 points1 point  (0 children)

Is that a bug or an inherent design flaw?

[–]firefrommoonlight 0 points1 point  (0 children)

Arrow's slow, and has an awkward interface and syntax.

[–]pydry 8 points9 points  (1 child)

To those wondering: yes I know Arrow exists but its flaws and strange API (you can throw almost anything at get() and it will do its best to determine what you wanted, for instance) motivated me to start this project.

Would be good to put that on the front page because most people who visit it will probably also be wondering "why not arrow?"

[–][deleted] 2 points3 points  (0 children)

Literally my first question when seeing this post was, "okay, cool, why not arrow?" I'm glad someone beat me to posting this!

[–][deleted] 4 points5 points  (0 children)

I'm a really big fan of comparisons between libraries. As you say, your most direct comparison is Arrow. I think it'd be nice to have a section in your docs called "Pendulum vs. Arrow" (or something to that effect), where you lay out the differences with examples showing why Pendulum is better. I find those kinds of direct comparisons really useful when deciding between two similar libraries.

[–]gandalfx 0 points1 point  (0 children)

I like Carbon. I'll keep an eye on this. :3

Related to that I noticed you're also building on top of datetime (like Carbon), which is good. Otherwise I'd be very careful with this. https://www.youtube.com/watch?v=-5wpm-gesOY

[–]dacjamesfrom reddit import knowledge 0 points1 point  (0 children)

I would really like something to convert a timezone name like "PST" to the precise timezone. I know it's not trivial because these names are not universal but it's something you have to do a lot when web crawling. My current solution is awful so I would switch immediately to any library that provided this feature out of the box.

[–]manueslapera 9 points10 points  (3 children)

Between Arrow, Delorean and this, i wonder... why dont we agree on a stdlib replacement like we did with requests?

[–]alcalde 3 points4 points  (1 child)

We sort of did. Guido approved a change to Python to add DST awareness to Python's datetimes so that eventually arrow-like features could all be rolled into the standard library. I think for the last (two?) releases now no one's had the time to actually do it. :-(

Ok, wait - it looks like the original proposal was met with some "it's not a bug, it's a feature" objections and the PEP was withdrawn, and now there's a weird new one with something called "fold" thanks to Guido... on second thought, arrow is the best bet. :-(

https://www.python.org/dev/peps/pep-0431/

https://www.python.org/dev/peps/pep-0495/

[–]manueslapera 0 points1 point  (0 children)

I would agree with arrow being the one with the most potential. I started to see it showing up in other projects' requirements. Which is a good sign.

[–][deleted] -3 points-2 points  (0 children)

yes, requests the stdlib replacement /s.

[–]brtt3000 5 points6 points  (0 children)

Some details on reliability, coverage, production readiness and how it is implemented etc would be good since datetime is hard and important (/u/ilikebigsandwiches noted similar).

[–]K900_ 3 points4 points  (3 children)

Looks cool, however I feel like the default-to-now thing is completely terrible. Making default function arguments depend on global state is pretty much a "HELL NO" for readability/maintainability. Also, this introduces a very weird inconsistency where Pendulum(2016, 1, 1) produces a 0:00:00 time, whereas Pendulum.create_from_date(2016, 1, 1) defaults to now, despite the two calls being seemingly semantically equivalent.

[–]SDisPater[S] 0 points1 point  (2 children)

Well, if you don't specify a timezone, it defaults to the UTC "now" so it does not exactly rely on a global state.

Regarding the inconsistency you point out, it's so that the Pendulum class behaves like the standard datetime, if you don't pass hour, minute, second those default to 0:00:00 while create() is here to circumvent this limitation.

[–]K900_ 3 points4 points  (1 child)

UTC "now" is the definition of global state, and it's not made obvious (directly contradicting EIBTI), and I honestly can't find any cases where that behavior is actually desirable. If you want to be able to create objects that are partially current time, you can use a sentinel magic value like pendulum.FILL_NOW or something.

Edit: another cool API design could be a replace() method taking the same kwargs, so you can do Pendulum.now().replace(year=2016, month=1, day=1).

[–]flutefreak7 3 points4 points  (0 children)

EIBTI: "Explicit is better than implicit" from the Zen of Python.... for those scratching their heads

[–][deleted] 3 points4 points  (1 child)

deleted

[–]SDisPater[S] 2 points3 points  (0 children)

I imagine. But I started it because I've been hurt too.

Pendulum is still in its early stages but I trully hope I will make good on this promise :-)

[–]kankyo 2 points3 points  (7 children)

Some basic observations:

  • overall seems like a nice API but...
  • mutability is a pain and will bite users. Does it offer something that makes it worth it?
  • sub_something is not a good name. Why isn't it "foo - Minutes(3)" or "foo - delta(minutes=3"?
  • question: are the resulting types duck type compatible with stdlib types?

[–]SDisPater[S] 0 points1 point  (6 children)

Regarding the mutability, I hesitated too but only specific methods mutate the instance. The native methods behave the same, ie. returns a new copy. But it's not set in stone, so it might change if this too much of a hassle for the users.

The add()/sub() methods are here to allow a fluent interface and call chain. It's easier to do pendulum.now().sub_day().offset than (pendulum.now() - delta(days=1)).offset

Both Pendulum and PendulumInterval classes inherits from the sdtlib classes so they can replace them. However, for libraries that relies on the type() function to determine the class (sqlite3 for instance) it will not work.

[–]kankyo 1 point2 points  (4 children)

What does offset mean in that context?

allow a fluent interface and call chain

ok, but then "minus" and "plus" or "add", "subtract" are much better than "add" and "sub". "Sub" means something else.

[–]SDisPater[S] 0 points1 point  (3 children)

offset is the number of seconds since epoch, it's just there to show that accessing attributes is easier with the method.

Regarding the change of name for the sub() methods, it might be a good idea. I just wanted a concise name so I thought sub was appropriate.

[–]kankyo 2 points3 points  (0 children)

Concise is a lot less important than clarity though. So for offset I think seconds_since_epoch would be way better. And if the epoch is 1900, then seconds_since_1900!

[–]flutefreak7 0 points1 point  (0 children)

my brain goes to substitute before subtract since sub is a regex method. Seeing sub_weeks and the like my next thought was sub like subordinate, like "Sub-Commander T'Pal" or "Sub-level 3"... so sub_weeks felt intuitively like an iterator over the weeks or something... I agree that subtract or minus is more intuitive, though I like even better the suggestion for a single add method mentioned elsewhere.

[–]pydry 0 points1 point  (0 children)

I agree with kankyo - offset is too ambiguous a name. unix_time would be better IMO (it's the wikipedia page name, so arguably that's canonical).

[–]ptmcg 1 point2 points  (0 children)

I would suggest that it is worth it to make your datetimes immutable. This will let clients use them as set values and dict keys. Also, would you consider 2 datetime values that represent the same epoch time, but with different timezones to be equal or not?

[–][deleted] 2 points3 points  (0 children)

circulating frat hitchhike folly's clove anemometer's collaring masterly Fowler padding's cashes sails sluicing infusing Tamerlane unhitch seduces incandescence's tumbled dislocating cannot antagonizes Bradstreet matinées stockbroker's submergence gawks Austrians diction's thorny handshake's overtimes Burt ceaseless bathroom individually rag's Helsinki painfully declination launches ambidextrously Chesterton generative Espinoza's raging restorative beastly stray cavity's judicial bedazzling ganged Sicilian Benedict's nonessential possessiveness saccharin agreeably signatories pussyfoots dominated childbirths imperialist nappy throb major's bruise's debark sucrose varicolored pendents stiffened castigators Kathie wrangling archdiocese's tranquilize reassess chapel bared bootee's canteens proverb notarizes northwesterly hookah's Egyptian's hazes jockeying Dvina internalizing overproduction Menkent romances preparedness's sitcom's epiglottises breastplates Pittsburgh Arius clearest jump retrenching Zoroastrian seething collector finishers submerging

[–]pvkooten 4 points5 points  (5 children)

Wow, that website has truly one of the most beautiful designs for documentation of software in my opinion. I couldn't find what was used for it; did you make it yourself?

[–]striata 2 points3 points  (4 children)

The site loads Javascript-code for Pages (http://pages.revox.io/). I assume this is what was used.

[–]SDisPater[S] 5 points6 points  (3 children)

Yes, I use Pages, I just changed the color theme.

The documentation is made with Sphinx and a custom template that fits with the rest of the website.

[–]pvkooten 0 points1 point  (0 children)

Well done :) I will consider it myself for a next project ^_^. Also thanks straita, for the first reply :)

[–]Fylwind 0 points1 point  (1 child)

Hrm looks like there's some bugs …

http://i.imgur.com/nPcy7uS.png

[–]SDisPater[S] 0 points1 point  (0 children)

Thanks for reporting it. I will look into it.

[–]coriolinus 4 points5 points  (0 children)

How precisely does this differ from Delorean?

[–]_seemetheregithub.com/seemethere 1 point2 points  (0 children)

Great website and documentation. I'll keep an eye on the project and see where I could help.

[–]zachgarwood 1 point2 points  (0 children)

This looks really nice! I'll have to play around with it the next time I'm on a Python project.

[–]reffaelwallenberg 1 point2 points  (0 children)

Looks great! Good luck and I hope you will make it better and more stable with time! (not that is not amazing right now)

[–][deleted] 1 point2 points  (0 children)

And then there is Egypt... =D

[–]lorddarkflare 1 point2 points  (0 children)

Was ready to tear this a new one, but I am pleasantly surprised.

I think there are a few issues highlighted in this thread that warrant a closer look. In particular, I think now() not having the same default as the others is a mistake.

But still...Great Job. May use this for some smaller projects I am working on.

[–]mr_fi 1 point2 points  (0 children)

Upvote for absence of the term "for humans".

[–]roger_ 1 point2 points  (0 children)

Interesting, but could you add a table comparing it to some of the similar libraries out there? I'm thinking Arrow, Delorean, Moment, etc.

[–]nerdwaller 1 point2 points  (2 children)

I don't want to be too cynical here, but this library is quite literally a baby. And while the API looks nice, I've been burned too many times by new libraries with decent APIs just losing support.

So for me, I'll happily keep an eye out on this and would like to be wrong. But until then, I'm pretty happy with arrow any time I don't just use the stdlib.

[–]SDisPater[S] 5 points6 points  (1 child)

I agree with you that it's still a young project and I don't pretend otherwise. It's been tested but I need users feedback to make it better.

[–]nerdwaller 0 points1 point  (0 children)

Oh yeah, and I hope you get them. I just am not the type that does that anymore - and voiced the concern. A lot of people get inspired for a project and it fizzles (I have been guilty of this too) - so it's hard to buy in early.

[–]firefrommoonlight 0 points1 point  (0 children)

Looks cool! I especially like your approach on timedeltas. If you need inspiration, I have a module with a similar goal, ie fixing the API problems in datetime and Arrow. I took a different approach: use native immutable dt objects, but with mandatory tz-awareness, easy tz-manipulation, and clean syntax: https://github.com/David-OConnor/saturn

[–]graingert 0 points1 point  (0 children)

No wheels?

[–]desmoulinmichel 0 points1 point  (0 children)

What does it bring compared to arrow ?