cakephp / chronos

A standalone DateTime library originally based off of Carbon
http://book.cakephp.org/chronos
MIT License
1.36k stars 63 forks source link

Reasoning around no longer extending native DateTimeImmutable #410

Closed acelaya closed 1 year ago

acelaya commented 1 year ago

I was looking at chronos 3 changelog, and saw it no longer extends native date types.

I actually knew this was coming, as I have been using this project for quite some time, but I wanted to ask if it would be possible to share the reasoning of this architectural change.

Knowing this will help understand how maintainers envision this library to be used, and help migrating projects from v2 to v3. I can think on some good reasons, but I don't want to make any assumption.

Also, apologies if this is documented somewhere. I have been looking for issues, discussions and announcements and was not able to find this piece of information.

acelaya commented 1 year ago

Actually, just found this, which explains it to some extent https://github.com/cakephp/chronos/blob/3cbb9e2d8af1065c7189e1dc3f4fa641e797719f/docs/en/2-4-upgrade-guide.rst#no-longer-extending-datetime

Antarian commented 1 year ago

I understand it will make Chronos library cleaner and that is already decided... but:

Will stick to 2.* for now.

ADmad commented 1 year ago

DateTimeImmutable did not required special treatment for Doctrine entity fields. I need to test this, but expect that Doctrine will now require custom converter for Chronos to DateTimeImmutable and back.

Wouldn't this be solvable with a custom mapping type class? CakePHP ORM uses similar type mapping classes and they are simple enough.

ADmad commented 1 year ago

In light of the issues users are having upgrading to v3, we have tried reverting Chronos to extending DatetimeImmutable https://github.com/cakephp/chronos/pull/417. You can try out the extend-datetimeimmutable branch and share your feedback.

ChronosDate will remain unchanged and won't extend DatetimeImmutable.

othercorey commented 1 year ago

I'll try to explain the reasons for the changes. First, thank you for opening this ticket. It's a very large change and very unexpected for most so you could have posted something much worse :).

I apologize for this not being communicated clearly (or at all other than some release notes). We should have opened a public issue to explain our goals and concerns and see if anyone was interested in feedback. We don't have to make these changes often, but that's no excuse. Many of the issues we mention below were created/discussed up to a couple years ago, and with Cake 5 releasing we decided to make the change now without updating the discussion.

Our general goals were:

Extending DateTimeImmutable

The first goal to avoid PHP changes comes from two areas: adding and enforcing return types to internal PHP classes and bugs/lack of support for returning static types from overridden DateTimeImmutable methods.

As of today, the static return type is working. This was originally an issue in createFromFormat(). Our solution was to wrap the call to createFromFormat() and convert the result. We still do this, but only to throw an exception on error.

Also as of today, we only need add the ReturnTypeWillChange attribute to 1 method.

Because of this, we realized we can still extend Chronos from DateTimeImmutable now that we've wrapped everything and updated types. If we need to change it again in the future, the class is ready.

Splitting Chronos and ChronosDate

The ChronosDate object was meant to be a separate concept, but it was originally simpler to just copy Chronos and add several hacks to overwrite the time and time zone. This exposed several methods that did not makes sense for a date object and also required the shared implementation to support both types in many places that didn't need it.

The ChronosInterface was part of this shared implementation along with ChronosDate extending DateTimeImmutable.

Adding ChronosTime

Adding a time-only object is self-explanatory, and we use it in the main cakephp project for database types.

The reason this goes along with the Chronos/ChronosDate split is ChronosTime was never going to extend DateTimeImmutable nor implement ChronosInterface.

othercorey commented 1 year ago

@Antarian We can add a ChronosDateTime alias for Chronos if that will help code readability.

acelaya commented 1 year ago

Thanks for the detailed explanation 🙂

Antarian commented 1 year ago

@ADmad thank you for advice, that's exactly what I have done for Chronos class. Or more specifically child class I've created.

@othercorey Thank you for detailed explanation. I will not be testing that reverted implementation as I have already created mapping type for Doctrine. Alias will not be needed. I have just extended Chronos class, seems just OK for my use case. I even like it better than my old solution.

If revert is just because of me and @acelaya it may not be worth it. As you mentioned it is better to separate it from PHP, as that way it will be cleaner and easier to maintain.

I was not happy about the change at first, but that change helped me to decouple my domain code from this library.

acelaya commented 1 year ago

I was not happy about the change at first, but that change helped me to decouple my domain code from this library.

This is precisely why I posted the question in the first place. These kinds of changes usually have a reason, and I just wanted to understand it to make the best from this library.

If revert is just because of me and @acelaya it may ot be worth it. As you mentioned it is better to separate it from PHP, as that way it will be cleaner and easier to maintain.

Agree on this. In fact, I haven't even started any migration yet, so I don't know if I will be having problems or not.

Of course, I haven't checked if other people are reporting issues that would justify the revert.

othercorey commented 1 year ago

Alias will not be needed. I have just extended Chronos class, seems just OK for my use case. I even like it better than my old solution.

We had a similar naming issue with CakePHP which extends Chronos classes. Originally we had Time which extended MutableDateTime and FrozenTime which extended Chronos. None of these choices were great. After dropping the mutable classes in Chronos, we decided to rename FrozenTime to just DateTime - no prefix to match no prefix in Chronos.

Now, in Chronos, there is just Chronos instead of MutableDateTime.

It's annoying trying to straight up these kinds of names.

pm-michael commented 1 year ago

I was not looking forward to updating to V3, but if this change is reverted that would be great! FYI our use case involves using https://github.com/spatie/period which expects DateTime or DateTimeImmutable arguments. Currently with Chronos V2 I can just give it 2 Chronos dates and it just works. With the V3.0 release I would have needed to rewrite significant amounts of code and there would be unnecessary overhead in converting objects back and forth between different DateTime implementations.

I'm sure there are other libraries that would use dates and expect the type DateTime or at least DateTimeInterface.

othercorey commented 1 year ago

This wasn't very obvious, but we have a toNative() helper or non-DateTimeImmutable classes. I thought it was shorter, but maybe toDateTimeImmutable() is more obvious.

Because we have a DateTime class in cakephp, we call the php classes "native" classes.

othercorey commented 1 year ago

I'm sure there are other libraries that would use dates and expect the type DateTime or at least DateTimeInterface.

FYI, we tried to implement just DateTimeInterface, but PHP doesn't allow it. Only DateTime or DateTimeImmutable are allowed to implement it.

othercorey commented 1 year ago

Chronos 3.0.2 is released where Chronos class extends DateTimeImmutable.