{"id":345,"date":"2019-12-11T16:47:02","date_gmt":"2019-12-12T00:47:02","guid":{"rendered":"http:\/\/shanelabs.com\/blog\/?p=345"},"modified":"2019-12-11T16:49:45","modified_gmt":"2019-12-12T00:49:45","slug":"calculating-business-time-logic-in-php","status":"publish","type":"post","link":"https:\/\/shanelabs.com\/blog\/2019\/12\/11\/calculating-business-time-logic-in-php\/","title":{"rendered":"Calculating &#8220;Business time&#8221; logic in PHP"},"content":{"rendered":"\n<p>Struggling trying to come up with a solution to calculate the time difference between two datetimes, with respect to when a business is open? I was too.<\/p>\n\n\n\n<p>First approach I looked into was a package that does just this:  <a href=\"https:\/\/packagist.org\/packages\/hughgrigg\/php-business-time\">https:\/\/packagist.org\/packages\/hughgrigg\/php-business-time<\/a> . Unfortunately it doesn&#8217;t allow much configuration for constraints. Ie, if you have different hours on a weekday than you do a weekend then you&#8217;re out of luck.<\/p>\n\n\n\n<p>Second approach was to use the diffFiltered function that&#8217;s part of Carbon. My solution looked like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$minutes = $fromTime-&gt;diffFiltered(CarbonInterval::minute(), function(Carbon $date) {\n            return $this-&gt;isOpen($date);\n        }, $toTime, false);<\/pre>\n\n\n\n<p>There are two problems with this approach. First, it&#8217;s slow. It works by iterating through your time interval one by one. That means if you have a huge span between your fromTime and toTime, you&#8217;re going to be wasting a lot of cycles.<\/p>\n\n\n\n<p>Second, there&#8217;s a constant in the <a href=\"https:\/\/github.com\/briannesbitt\/Carbon\/blob\/master\/src\/Carbon\/CarbonPeriod.php\">Carbon<\/a> class:  NEXT_MAX_ATTEMPTS = 1000; This means we can&#8217;t go more than 1000 iterations without returning a valid date otherwise we&#8217;ll get<strong> &#8220;RuntimeException: Could not find next valid date&#8221;<\/strong>. This effectively prevents us to measuring anything more than a day, which doesn&#8217;t work for us considering businesses can be closed on weekends, or over holidays.<\/p>\n\n\n\n<p>We can overcome both of those concerns by changing the CarbonInterval to a bigger value, such as CarbonInterval::hour(), or even CarbonInterval::minute(5). The downside of this is we lose precision. Everything is now chunked into that interval length, so (in the case of minute(5)) we wouldn&#8217;t get anything more accurate than 0, 5, 10, 15, etc.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Best Approach<\/h2>\n\n\n\n<p>What first seemed like a hard problem didn&#8217;t turn out to be too bad if I took the time to think through it. If you simply iterate through your time span a day at a time, and consider the open and close time of your business, the logic isn&#8217;t that tricky. Here&#8217;s the code I used<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><em>\/**\n * Returns the number of minutes between two datetimes, excluding time the facility was closed\n * This method works by iterating through each day and comparing open\/close times of facility\n *\n * <\/em><strong><em>@param <\/em><\/strong><em>Carbon|null $fromTime\n * <\/em><strong><em>@param <\/em><\/strong><em>Carbon|null $toTime\n * <\/em><strong><em>@return <\/em><\/strong><em>int $minutes\n *\/\n<\/em><strong>public function <\/strong>minutesDiffWithinOpenHours(Carbon $fromTime, Carbon $toTime)\n{\n    <em>\/\/prevent time travel\n    <\/em><strong>if<\/strong>($fromTime &gt;= $toTime) {\n        <strong>return <\/strong>0;\n    }\n\n    $minutes = 0;\n    $loopTime = $fromTime;\n\n    <strong>while<\/strong>($loopTime &lt; $toTime) {\n        $openTime = $this-&gt;openTime($loopTime);\n        $closeTime = $this-&gt;closeTime($loopTime);\n\n        <strong>if <\/strong>($openTime &amp;&amp; $closeTime) {\n            $calcFromTime = <em>max<\/em>($loopTime, $openTime);\n            $calcToTime = <em>min<\/em>($toTime, $closeTime);\n\n            <strong>if <\/strong>($calcFromTime &lt; $calcToTime) {\n                $minutes += $calcFromTime-&gt;diffInMinutes($calcToTime);\n            }\n            <em>\/\/else from is after hours, don't count\n        <\/em>}\n        <em>\/\/else facility is closed all day, don't count\n\n        <\/em>$loopTime = Carbon::<em>parse<\/em>($loopTime-&gt;<strong>addDay<\/strong>()-&gt;toDateString().<strong>' 00:00:01'<\/strong>, $this-&gt;<strong>timezone<\/strong>);\n    }\n\n    <strong>return <\/strong>$minutes;\n}<\/pre>\n\n\n\n<p>Note that it&#8217;s dependent on you coming up with your own implementations of openTime() and closeTime(), which returns a Carbon datetime of the open\/close time on that date.<\/p>\n\n\n\n<p>What&#8217;s great about this is it&#8217;s fast, and it&#8217;s precise. Win-win!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Struggling trying to come up with a solution to calculate the time difference between two datetimes, with respect to when a business is open? I was too. First approach I looked into was a package that does just this: https:\/\/packagist.org\/packages\/hughgrigg\/php-business-time . Unfortunately it doesn&#8217;t allow much configuration for constraints. Ie, if you have different hours [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-345","post","type-post","status-publish","format-standard","hentry","category-software"],"_links":{"self":[{"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/posts\/345","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/comments?post=345"}],"version-history":[{"count":4,"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/posts\/345\/revisions"}],"predecessor-version":[{"id":349,"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/posts\/345\/revisions\/349"}],"wp:attachment":[{"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/media?parent=345"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/categories?post=345"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shanelabs.com\/blog\/wp-json\/wp\/v2\/tags?post=345"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}