briannesbitt / Carbon

A simple PHP API extension for DateTime.
https://carbon.nesbot.com/
MIT License
16.57k stars 1.28k forks source link

diffForHumans only weekday #1389

Closed baptistebisson closed 6 years ago

baptistebisson commented 6 years ago

Hi.

I'm trying to get difference between two dates in human format but only with working days. Here is my actual code:

$start = '2018-09-13 09:30:00';
$end = '2018-10-16 16:30:00';

$from = Carbon::parse($start);
$to = Carbon::parse($end);

$weekDay = $from->diffInWeekdays($to);
$human = $to->diffForHumans($from, true, false, 6);

var_dump($weekDay); //24
var_dump($human); // 1 month 3 days 7 hours

diffForHumans is perfect for my needs but I can't find any way of filtering like diffInDaysFiltered

What I would like to achieve is to get this result 24 days 7 hours

I tried a preg_replace first to replace 3 days by $weekDay result but if we have a month before it's incorrect and I have: 1 month 24 days 7 hours

Is there any solution for my problem ?

kylekatarnls commented 6 years ago

The correct result should be 24 days 7 hours

Sorry but no, diffForHumans does not care about week days and should not no matter you previously calculate something with diffInWeekdays, that's 2 calls that does not interfere.

So, it's a business specific need and solution depends on your specifications. What is your granularity (smallest unit to display), and biggest unit since month/year would not have too much sense with 5-days weeks counting. So I guess, it should rather use week count as maximal unit then, day/hour remaining.

Can you precise your expectations for that?

baptistebisson commented 6 years ago

My mistake sorry. 24 days 7 hours is what I would like to get at the end.

For my needs, the smallest unit should be hours, and the biggest: month. Here is some example of time difference that would be perfect:

Or if this is too complex, the result of diffForHumans is already great ! I only need to get the result with only working days.

kylekatarnls commented 6 years ago

Do you consider a month is 5 weeks, 20 work days? I don't understand your way to cascade it?

And if you use workday, I presume days may not be 24 hours long. What would be expected diff from 2018-09-13 16:30:00 to 2018-09-14 09:30:00?

See what I mean, you need rules to cascade from one unit to the other. That's what allow to split into multiple parts the difference.

If you're not sure, tell me what you are trying to do. You have work task start and end and try to figure out the time spent?

baptistebisson commented 6 years ago

A month is about 20 work days yeah.

Well if you want details here are they:

I'm building an app where you can take your holidays in it. A week is 5 days from 08h30 to 18h00. So yes, a day is not 24 hours longs.

Someone can take holidays the same day. From 2017-07-12 11:00 to 2017-07-12 18:00 so the duration will look like this:

If it's 2 days like from 2017-07-12 to 2017-07-13

If the duration is 10 days

And is you take your holidays from 2017-07-13 11:00 to 2017-07-17

diffForHumans do that really well already. I only want to add a filter to it so I can check if this is a work day or not.

  1. The first goal is to be able to show the duration of one holiday like my first post here
  2. The second goal is to display all your holidays for the month and at the and the total of your holidays duration

-> For this example it will be 7h + 2 days + 2 weeks = 2 weeks 2 days 7h

imbrish commented 6 years ago

I don't think it is possible to use custom factors (hour/day/week ratios) in diffForHumans of plain Carbon.

You could instead count the number of work hours with diffInHoursFiltered. Check if each passed date is on weekday within the hour range you mentioned. Then create a new interval from counted hours and cascade it using custom factors defined by setCascadeFactors:

CarbonInterval::setCascadeFactors(array(
    'dayz' => array(10, 'hours'),
    'weeks' => array(5, 'dayz'),
    'months' => array(20, 'dayz'),
));

$resolution = CarbonInterval::hour();

$start = Carbon::parse('2017-07-13 11:00');
$end = Carbon::parse('2017-07-17 18:00');

$hours = $start->diffFiltered($resolution, function ($date) {
    return $date->isWeekday() && $date->hour >= 8 && $date->hour < 18;
}, $end);

$interval = CarbonInterval::hours($hours)->cascade();

echo $interval->forHumans(); // 2 days 7 hours

Unfortunately it looks like CarbonInterval::forHumans doesn't produce as nice output as Carbon::diffForHumans. It would be nice if we could use the same method internally, to keep output of both in line, for example by calling CarbonInterval::forHumans from Carbon::diffForHumans.

Additionally I noticed that if we define months factor using weeks 'months' => array(4, 'weeks'), it will zero-out the days part completely when cascading.

kylekatarnls commented 6 years ago

diffForHumans do that really well already. I only want to add a filter to it so I can check if this is a work day or not.

I would not say that, because if you leave at 16pm and come back next day 11am, diffForHumans will not allow you to handle the off-business time. And I don't even talk about a possible lunch break.

We are far away from diffForHumans abilities and purpose I think.

Else I think @imbrish is right, if hour is your smallest unit, then count total work hours then cascade it to each next unit using setCascadeFactors and cascade with your own factors.

baptistebisson commented 6 years ago

@imbrish Thank you so much, it's perfect ! I will update my post to share what I've done. Didn't know about the cascade factors.

Last question. Any idea how I could sum up all these results ? For example, here is a list of holidays for 1 month:

And I want to get the total of holidays for the month.

I don't think that Carbon have any method for that. I was looking at Addition and Subtraction But it can only take a date as input and not duration.

Maybe I have to manually sum those results ?

Like this: 2 days 7 hours + 4 days = 6 days 7 hours So --> 1 week 1 day 7 hours

Then 1 week 1 day 7 hours + 9 hours = 1 week 2 days 7 hours

I will try that first, but if you have any idea it will be perfect !

kylekatarnls commented 6 years ago

You can sum intervals:

CarbonInterval::setCascadeFactors(array(
    'days' => array(9, 'hours'),
    'weeks' => array(5, 'days'),
));

$di = CarbonInterval::fromString('2 days 7 hours')->add(CarbonInterval::fromString('4 days'));

echo $di->cascade()->forHumans();

Output:

1 week 1 day 7 hours
baptistebisson commented 6 years ago

Well... I did it in pure PHP but if there is a solution in Carbon :+1: The only things I need to change is 'days' => array(9, 'hours') with 'days' => array(24, 'hours') since it's already formatted.

Thanks a lot for your quick response !

kylekatarnls commented 6 years ago

'days' => array(24, 'hours') mean that if you add 8 hours + 8 hours, you will get 16 hours.