Skip to content

Instantly share code, notes, and snippets.

@kylekatarnls
Last active March 8, 2019 13:13
Show Gist options
  • Save kylekatarnls/ecfa3267a02165e758894d324cce8344 to your computer and use it in GitHub Desktop.
Save kylekatarnls/ecfa3267a02165e758894d324cce8344 to your computer and use it in GitHub Desktop.
Carbon 2 proposal

Carbon 2 (Draft)

Complete set of PHP Date classes extensions:

class extends comment
Carbon DateTime
CarbonImmutable DateTimeImmutable new
CarbonInterface DateTimeInterface new
CarbonTimeZone DateTimeZone new
CarbonInterval DateInterval Read-only days property
CarbonPeriod DatePeriod Blocked until php/php-src#3121 get merged

Setter methods of Carbon become cloners in CarbonImmutable:

$m = new Carbon;
$c1 = $m->setDay(1);
// $m->day === $c1->day as $c1 === $m
$i = new CarbonImmutable;
$c2 = $i->setDay(1);
// $m->day !== $c2->day as $c2 is a new object
$c1->day = 9; // $m->day === 9
$c2->day = 9; // error, immutable

Generic methods

To reduce code amount, we could rather have a magic __call method parsing methods matching actionUnit with action in a given list (isSame, isCurrent, add, sub, get, set, floor, ceil, round, etc.) and unit as valid unit (second, minute, hour, day, etc.) to route to generic method (action) with a unit argument.

This will enforce some methods consistency (easy way to handle singular/plural, same arguments for a given action). And a easier way to add actions. This would encourage to drop boolean arguments (which give multiple responsability to a single method) in favor of adding methods.

A automated script could update the PHPDoc comment to add @method annotations so even if the methods does not really exist, IDE autocompletion will propose magic methods and we would not have to add them all manually.

Having a methods set compatible with https://momentjs.com could help to create a full-stack standard.

Stricter extends

Critical methods should be final (macro methods, magic methods) so it would help us to prevent breaking changes in sub-classes.

All methods in traits

Methods should be grouped in traits and each trait should remain stand-alone (could be taken alone).

Instead of extends the Carbon classes, users would be able to compose their own date classes picking traits.

Documentation

Documentation could be interactive (see https://pug-demo.herokuapp.com and https://phug-lang.com/, it provides code examples the user can edit in live to see the PHP result).

Carbon become big, the single-page and one-level links menu will become a bit too limited, some search/expand/collapse tools would help to browse it.

Each method must be documented with minimal informations:

  • history, example: added in 2.1.0, argument $foo added in 2.2.0, $foo can be an array since 2.2.1
  • trait, example: set() belongs to Setter trait
  • arguments, return and exceptions thrown
  • code examples

Requirement

To provide reliable solid library, Carbon should rely on a full microseconds support, that means at least PHP 7.1.8.

About version 1.x

Carbon is widely used. We should not abandon v1 users.

  • V1 documentation must remain available
  • An issue templating and 1.x/2.x tagging will help to handle issues
  • A migration guide could help user to upgrade
  • Dropped methods could remain in the documentation (tagged as obsolete with some gray-styling and collapsed description)
@imbrish
Copy link

imbrish commented May 23, 2018

Compatibility and bugs

After some diving in the depths of PHP I bring a good news regarding CarbonPeriod. Work has been done in php/php-src#3121 to allow modifying DatePeriod's properties. If it gets merged it'll be possible to rewrite CarbonPeriod so that it extends the DatePeriod!

I was thinking about fixing the daylight saving time transitions and other bugs directly in the source. Interestingly RFC regarding DST stuff was proposed and accepted (!) already in 2011. However it looks like nothing was done about it ever after. Discussions in internals here and here. On top of that, it looks like tests for the transitions have been added, but as of now they're marked as "expected to fail" - see http://gcov.php.net/viewer.php?version=PHP_7_2&func=expected_tests.

I bet it wouldn't be difficult to push the fixes through. Unfortunately the fixing itself seems to be beyond my possibilities, both in terms of skills and free time. However best-case scenario here could still be 7.3 release. I suppose we may settle on patching what we can in Carbon, at least we can act faster. Is there anything we can do about DST transitions? Would some variant of the hack used in CarbonPeriod work in Carbon?

Generic methods

I agree that standardization of methods should be done. However I'm afraid that proxying everything through __call will quickly get complicated, particularly as new methods are added. We can automate generating class docblocks to provide auto-completion in IDEs, but we still make Carbon less transparent for someone lurking the code. Also code coverage would become less informative.

As an alternative we could generate a trait with all the repetitive methods based on a template. Drawback here is that contributors could feel tempted to modify or add methods to that auto-generated file.

Last option would be to leave the definitions as they currently are, just do some manual standardization. Would that be so bad after all? These methods don't change very often. Once written will probably stay just the same ever after. Cluttering the code shouldn't be a problem as now the methods can be moved into a dedicated trait.

Traits

In my opinion splitting into traits just for better organization of the codebase. I don't think composing your own Carbon is any useful for normal users, so it's not something I'd encourage or mention in the docs.

Stricter extends

I don't like the idea of making methods final/private. Too many times I was coming up with some weird workarounds to make a simple change, because making it directly wasn't possible. Is it because of backwards compatibility? I think we can adhere to the convention of guarantying only the public methods

On the other hand I don't have much experience with maintaining such projects so I may be missing something.

Immutability

Browsing https://github.com/cakephp/chronos made me realize that immutability comes with it's own set of PHP quirks. Splitting into two variants (Carbon and CarbonImmutable) will also increase number of issues. Just something we should be aware of.

Translations

Translated formats like in https://github.com/jenssegers/date would be a great addition to the toString and diff translations. Not sure how useful is the reverse translation for createFromFormat method, but that's probably something that could also be implemented.

I think it'd be nice if by example of Chronos we could get rid of dependencies. I realize that it would require writing and maintaining our own Translator. I didn't investigate how much effort that would require, thus I'll just leave it as that.

Moment.js compatibility

It'd be great if we could achieve compliance with moment not only in terms of API, but also formatting - so that Carbon's diffForHumans and moment's fromNow would produce the same strings for the same ranges and both could be used interchangeably.

@kylekatarnls
Copy link
Author

Translator seems always simple at first sight, sounds like just a big mapping. But check the size of https://github.com/symfony/translation, it's not filled with air, it's really how tricky is the translation rules. Just have a look at pluralization rules to start: https://github.com/symfony/translation/blob/master/PluralizationRules.php

Nearly all the languages are already configured and ready to translate, ready to be customized by end-users and already documented. And Symfony is very well maintained so if a language is missing (it happened only once until now) they are very reactive, last time, they merged in less than 24 hours: symfony/symfony#27286

The symfony/translation dependency also means consistency between the app and Carbon for each one using Symfony/Laravel.

I prefer to focus on date features and rely on a dedicated package for translation.

About mutable/immutable, there are common ways. modify() exists on both for example but returns new object with Immutable. So we can create common interfaces like Modifyable and trait with common methods using modify() no matter it's mutable or not, then on unit tests, we can have a standard way to check it:

protected function assertCarbonModification($expected, $source, $modification)
{
    $actual = $modification($source);
    self::assertEquals($expected, $actual);
    $method = 'assert'.($source instanceof DateTimeImmutable ? 'Not' : '').'Same';
    self::$method($expected, $actual);
}

@kylekatarnls
Copy link
Author

kylekatarnls commented Jun 17, 2018

floor/ceil/round is nearly finished in my local env.

Quick overview about momentjs compatibility (including briannesbitt/Carbon#1360):

Method exists is compatible
add x x
calendar x x
clone x x
creationData
date (4) (4)
dates (3) (3)
day x
dayOfYear x x
days x
daysInMonth (4) (4)
diff x
endOf x x
format x x(2)
from x x
fromNow x x
get x x
hasAlignedHourOffset
hour x x
hours x x
inspect
invalidAt
isAfter x x
isBefore x x
isBetween x x
isDST x x
isDSTShifted (3) (3)
isLeapYear x x
isLocal x x
isSame
isSameOrAfter
isSameOrBefore
isUTC x x
isUtc x x
isUtcOffset
isValid x x
isoWeek x x
isoWeekYear x x
isoWeekday x x
isoWeeks x x
isoWeeksInYear x x
lang (3) (3)
local
locale x x
localeData
max x (1)
millisecond x x
milliseconds x x
min x (1)
minute x x
minutes x x
month x x
months x x
parseZone
parsingFlags
quarter x x
quarters x x
second x x
seconds x x
set x x
startOf x x
subtract x x
to x x
toArray x
toDate x x
toISOString x x
toJSON x x
toNow x x
toObject x x
toString x x
tz x
unix x x
utc x x
utcOffset x x
valueOf x x
week x x
weekYear x x
weekday x x
weeks x x
weeksInYear x x
year x x
years x x
zone (3) (3)
zoneAbbr (3) (3)
zoneName (3) (3)

(1) min and max are deprecated because of an inverted behavior, our behavior match the non-inverted behavior. Moment also have a static method which could be nice to have for min/max.
(2) format in momentjs is isoFormat in Carbon to keep original format method from DateTime class.
(3) deprecated method not relevant.
(4) provide getter instead.

@kylekatarnls
Copy link
Author

Added from, fromNow, to, toNow via briannesbitt/Carbon#1385

@kylekatarnls
Copy link
Author

Lot of new methods added in briannesbitt/Carbon#1385

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment