chronos
chronos copied to clipboard
diffForHumans() gives misleading results for future dates
Consider the following example of a future date two weeks ahead of today's date:
echo Chronos::parse(new DateTime('2 weeks'))->diffForHumans();
1 week from now
It seems to me the expected out put should be two weeks from now.
Also consider that as the current time ticks forward, the diff should probably not flip from two weeks to one week, since this is not very granular output. That is, once the difference becomes less than two weeks, it should probably change scale to days, e.g. 11 days, 10 days, etc. If it jumps straight from two weeks to one week, when should this occur? One second prior to the full two weeks? Half way between one week and two weeks? Up to one second after one full week? This presents a boundary condition problem that is difficult to reason about if we do not switch to a more granular time output, e.g. from weeks to days.
As an aside, the behaviour is not the same for past dates:
echo Chronos::parse(new DateTime('-2 weeks'))->diffForHumans();
2 weeks ago
echo Chronos::parse(new DateTime('-2 weeks'))->diffForHumans();2 weeks ago
This is a different interval since now will still be at least 2 weeks away.
I'm not sure what you're saying. -2 weeks is two weeks ago as +2 weeks is two weeks ahead. This is demonstrably the case even just by using this software, given that:
echo Chronos::parse(new DateTime('2 weeks 1 sec'))->diffForHumans();
2 weeks from now
Ergo this is a boundary condition problem on the surface, but as I explained in my opening post, I believe the scale is also inappropriate.
That is, once the difference becomes less than two weeks, it should probably change scale to days, e.g. 11 days, 10 days, etc. If it jumps straight from two weeks to one week, when should this occur? One second prior to the full two weeks? Half way between one week and two weeks? Up to one second after one full week? This presents a boundary condition problem that is difficult to reason about if we do not switch to a more granular time output, e.g. from weeks to days.
When do you think we should switch from days to weeks? I agree that after 1 week is probably too soon. Should we go to 30 days?
I haven't given it too much thought, but I cannot see any issue with your proposal.
A month seems reasonable.
It would be useful if the documentation for this method made it plain exactly where the boundaries are for each unit switch, as that seems to be where the art for this method really comes in. In order to please everyone, it may be necessary to create a method that accepts a custom boundary descriptor that allows the caller to specify exactly where the boundary changes occur. However, I also believe in the fundamental goal of this method which is to define a default set of boundary conditions that will satisfy most use cases, I just think we should do a better job of documenting the ones we have chosen in order to demystify this method a bit.
It's just supposed to be a friendly description, but we can try to make it more useful sequences that reflect how most people view time periods.
In order to please everyone, it may be necessary to create a method that accepts a custom boundary descriptor that allows the caller to specify exactly where the boundary changes occur.
This sounds like a something users that need custom boundaries can build themselves. We can document the boundaries better and tweak the threshold for using weeks but there won't be an implementation that pleases everyone. :smile:
Any progress on this?
If you have a proposal, a PR is welcome.
To the math-mobile! Here's our current distribution, "precision" being (max - min) / max as a way of quantifying "how accurate is our unit before it switches over to the next":
| Unit | Range | Precision |
|---|---|---|
| Year | 365+ | inf |
| Month | 30-365 | 92 |
| Week | 7-30 | 77 |
| Day | 1-7 | 86 |
| Hour | 1-24 | 96 |
| Minute | 1-60 | 98 |
| Second | 1-60 | 98 |
(Those units are a bit of a shorthand, but it's easier to read then a common unit like seconds:)
| Unit | Range | Precision |
|---|---|---|
| Year | 315_360_00+ | inf |
| Month | 2_678_400..31_536_000 | 92 |
| Week | 604_800..2_678_400 | 77 |
| Day | 86_400..604_800 | 86 |
| Hour | 3600..86_400 | 96 |
| Minute | 60..3600 | 98 |
| Second | 1..60 | 98 |
If we want to aim of a precision similar to our time units (95) then our equation goes something like this (where x is our max):
100 * (x - 1) / x = 95
x - 1 = 0.95x
0.05x = 1
x = 20
20 days! Let's say 21 to make the weeks fit nicely:
| Unit | Range | Precision |
|---|---|---|
| Year | 365+ | inf |
| Month | 30-365 | 92 |
| Week | 3-4 | 25 |
| Day | 1-21 | 95 |
| Hour | 1-24 | 96 |
| Minute | 1-60 | 98 |
| Second | 1-60 | 98 |
Notice this squishes our week unit a lot, but if we apply the same goal of 95 precision then we get 60 weeks which then smushes months... basically, I think this shows that "weeks" is not a very helpful unit.
So here's my proposal: Increase days to be 1..21, give weeks a slight bump (like 3..6 or 3..8) and leave the rest alone.
That seems like a better scale than what we have now.
I'll work on a PR soon.