├── .gitignore ├── LICENSE ├── README.md ├── StoreHours.class.php ├── StoreHours2.class.php ├── StoreHoursTest.php ├── index.php └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | nbproject 3 | .* 4 | !/.gitignore 5 | *~ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cory Etzkorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ⚠️ Note: I am no longer actively maintaining this repo. 2 | Please reach out if you're interested in becoming the primary maintainer. 3 | 4 | PHP Store Hours 5 | =============== 6 | 7 | PHP Store Hours is a simple PHP class that outputs content based on time-of-day and-day-of-week. Simply include the script in any PHP page, adjust opening and closing hours for each day of the week and the script will output content based on the time ranges you specify. 8 | 9 | ### Easily set open hours for each day of the week 10 | 11 | ~~~ php 12 | // REQUIRED 13 | // Define daily open hours 14 | // Must be in 24-hour format, separated by dash 15 | // If closed for the day, leave blank (ex. sunday) or don't add line 16 | // If open multiple times in one day, enter time ranges separated by a comma 17 | $hours = array( 18 | 'mon' => array('11:00-20:30'), 19 | 'tue' => array('11:00-13:00', '18:00-20:30'), 20 | 'wed' => array('11:00-20:30'), 21 | 'thu' => array('11:00-1:30'), // Open late 22 | 'fri' => array('11:00-20:30'), 23 | 'sat' => array('11:00-20:00'), 24 | 'sun' => array() // Closed all day 25 | ); 26 | ~~~ 27 | 28 | ### Add exceptions for specific dates / holidays 29 | 30 | ~~~ php 31 | // OPTIONAL 32 | // Add exceptions (great for holidays etc.) 33 | // MUST be in format month/day[/year] or year-month-day 34 | // Do not include the year if the exception repeats annually 35 | $exceptions = array( 36 | '2/24' => array('11:00-18:00'), 37 | '10/18' => array('11:00-16:00', '18:00-20:30') 38 | ); 39 | ~~~ 40 | 41 | ### Customize the final output with shortcodes 42 | 43 | Choose what you'd like to output if you're currently open, currently closed, or closed all day. Shortcodes add dynamic times to your open or closed message. 44 | 45 | ~~~ php 46 | // OPTIONAL 47 | // Place HTML for output below. This is what will show in the browser. 48 | // Use {%hours%} shortcode to add dynamic times to your open or closed message. 49 | $template = array( 50 | 'open' => "

Yes, we're open! Today's hours are {%hours%}.

", 51 | 'closed' => "

Sorry, we're closed. Today's hours are {%hours%}.

", 52 | 'closed_all_day' => "

Sorry, we're closed today.

", 53 | 'separator' => " - ", 54 | 'join' => " and ", 55 | 'format' => "g:ia", // options listed here: http://php.net/manual/en/function.date.php 56 | 'hours' => "{%open%}{%separator%}{%closed%}" 57 | ); 58 | ~~~ 59 | 60 | ### Available Methods 61 | 62 | #### render([timestamp = time()]) 63 | 64 | This is the default method that outputs the templated content. You'll most likely want to use this. 65 | 66 | ~~~ php 67 | $store_hours = new StoreHours($hours, $exceptions, $template); 68 | $store_hours->render(); 69 | ~~~ 70 | 71 | #### hours_overview([groupSameDays = false]) 72 | 73 | This returns an array with a full list of open hours (for a week without exceptions). Days with same hours will be grouped. 74 | 75 | ~~~ php 76 | $store_hours = new StoreHours($hours, $exceptions, $template); 77 | 78 | echo ''; 79 | foreach ($store_hours->hours_overview() as $days => $hours) { 80 | echo ''; 81 | echo ''; 82 | echo ''; 83 | echo ''; 84 | } 85 | echo '
' . $days . '' . $hours . '
'; 86 | ~~~ 87 | 88 | #### hours_today([timestamp = time()]) 89 | 90 | This returns an array of the current day's hours. 91 | 92 | ~~~ php 93 | $store_hours = new StoreHours($hours, $exceptions, $template); 94 | $store_hours->hours_today(); 95 | ~~~ 96 | 97 | #### is_open([timestamp = time()]) 98 | 99 | This returns true/false depending on if the store is currently open. 100 | 101 | ~~~ php 102 | $store_hours = new StoreHours($hours, $exceptions, $template); 103 | $store_hours->is_open(); 104 | ~~~ 105 | 106 | ### Use Cases 107 | 108 | #### Multiple stores / sets of hours 109 | 110 | If you'd like to show multiple sets of hours on the same page, simply invoke two separate instances of `StoreHours`. Remember to set the timezone before each new instance. 111 | 112 | ~~~ php 113 | // New York Hours 114 | date_default_timezone_set('America/New_York'); 115 | $nyc_store_hours = new StoreHours($nyc_hours, $nyc_exceptions, $nyc_template); 116 | $nyc_store_hours->render(); 117 | 118 | // Los Angeles Hours 119 | date_default_timezone_set('America/Los_Angeles'); 120 | $la_store_hours = new StoreHours($la_hours, $la_exceptions, $la_template); 121 | $la_store_hours->render(); 122 | ~~~ 123 | 124 | ### Testing 125 | 126 | ~~~ bash 127 | $ phpunit 128 | ~~~ 129 | 130 | ### Troubleshooting 131 | 132 | If you're getting errors or if times are not rendering as expected, please double check these items before filing an issue on GitHub: 133 | 134 | - Make sure your timezone is configured 135 | - Ensure all exceptions use the month/day format 136 | - Verify that StoreHours.class.php is properly included on the page 137 | 138 | Please report any bugs or issues here on GitHub. I'd love to hear your ideas for improving this script or see how you've used it in your latest project. 139 | 140 | ## Sites using PHP Store Hours 141 | 142 | - [Des Plaines Public Library](http://dppl.org/) 143 | - [The Nevada Discovery Museum](http://www.nvdm.org/) 144 | - [Minne's Diner](http://www.minnesdiner.com/) 145 | - Want to showcase your site? Tweet [@coryetzkorn](http://twitter.com/coryetzkorn) 146 | -------------------------------------------------------------------------------- /StoreHours.class.php: -------------------------------------------------------------------------------- 1 | exceptions = $exceptions; 49 | $this->config = $config; 50 | $this->yesterdayFlag = false; 51 | 52 | $weekdayToIndex = array( 53 | 'mon' => 1, 54 | 'tue' => 2, 55 | 'wed' => 3, 56 | 'thu' => 4, 57 | 'fri' => 5, 58 | 'sat' => 6, 59 | 'sun' => 7 60 | ); 61 | 62 | $this->hours = array(); 63 | 64 | foreach ($hours as $key => $value) { 65 | $this->hours[$weekdayToIndex[$key]] = $value; 66 | } 67 | 68 | // Remove empty elements from values (backwards compatibility) 69 | foreach ($this->hours as $key => $value) { 70 | $this->hours[$key] = array_filter($value, function($element) 71 | { 72 | return (trim($element) !== ''); 73 | }); 74 | } 75 | 76 | // Remove empty elements from values (backwards compatibility) 77 | foreach ($this->exceptions as $key => $value) { 78 | $this->exceptions[$key] = array_filter($value, function($element) 79 | { 80 | return (trim($element) !== ''); 81 | }); 82 | } 83 | 84 | $defaultConfig = array( 85 | 'separator' => ' - ', 86 | 'join' => ' and ', 87 | 'format' => 'g:ia', 88 | 'overview_weekdays' => array( 89 | 'Mon', 90 | 'Tue', 91 | 'Wed', 92 | 'Thu', 93 | 'Fri', 94 | 'Sat', 95 | 'Sun' 96 | ) 97 | ); 98 | 99 | $this->config += $defaultConfig; 100 | 101 | } 102 | 103 | /** 104 | * 105 | * @param string $timestamp 106 | * @return boolean 107 | */ 108 | private function is_open_at($timestamp = null) 109 | { 110 | 111 | $timestamp = (null !== $timestamp) ? $timestamp : time(); 112 | $is_open = false; 113 | 114 | $this->yesterdayFlag = false; 115 | 116 | // Check whether shop's still open from day before 117 | $ts_yesterday = strtotime(date('Y-m-d H:i:s', $timestamp) . ' -1 day'); 118 | $yesterday = date('Y-m-d', $ts_yesterday); 119 | $hours_yesterday = $this->hours_today_array($ts_yesterday); 120 | 121 | foreach ($hours_yesterday as $range) { 122 | $range = explode('-', $range); 123 | $start = strtotime($yesterday . ' ' . $range[0]); 124 | $end = strtotime($yesterday . ' ' . $range[1]); 125 | if ($end <= $start) { 126 | $end = strtotime($yesterday . ' ' . $range[1] . ' +1 day'); 127 | } 128 | if ($start <= $timestamp && $timestamp <= $end) { 129 | $is_open = true; 130 | $this->yesterdayFlag = true; 131 | break; 132 | } 133 | } 134 | 135 | // Check today's hours 136 | if (!$is_open) { 137 | 138 | $day = date('Y-m-d', $timestamp); 139 | $hours_today_array = $this->hours_today_array($timestamp); 140 | 141 | foreach ($hours_today_array as $range) { 142 | $range = explode('-', $range); 143 | $start = strtotime($day . ' ' . $range[0]); 144 | $end = strtotime($day . ' ' . $range[1]); 145 | if ($end <= $start) { 146 | $end = strtotime($day . ' ' . $range[1] . ' +1 day'); 147 | } 148 | if ($start <= $timestamp && $timestamp <= $end) { 149 | $is_open = true; 150 | break; 151 | } 152 | } 153 | 154 | } 155 | 156 | return $is_open; 157 | 158 | } 159 | 160 | /** 161 | * 162 | * @param array $ranges 163 | * @return string 164 | */ 165 | private function format_hours(array $ranges) 166 | { 167 | 168 | $hoursparts = array(); 169 | 170 | foreach ($ranges as $range) { 171 | $day = '2016-01-01'; 172 | 173 | $range = explode('-', $range); 174 | $start = strtotime($day . ' ' . $range[0]); 175 | $end = strtotime($day . ' ' . $range[1]); 176 | 177 | $hoursparts[] = date($this->config['format'], $start) . $this->config['separator'] . date($this->config['format'], $end); 178 | } 179 | 180 | return implode($this->config['join'], $hoursparts); 181 | 182 | } 183 | 184 | /** 185 | * 186 | * @param string $timestamp 187 | * @return array today's hours as array 188 | */ 189 | private function hours_today_array($timestamp = null) 190 | { 191 | 192 | $timestamp = (null !== $timestamp) ? $timestamp : time(); 193 | $today = strtotime(date('Y-m-d', $timestamp) . ' midnight'); 194 | $weekday_short = date('N', $timestamp); 195 | $hours_today_array = array(); 196 | 197 | if (isset($this->hours[$weekday_short])) { 198 | $hours_today_array = $this->hours[$weekday_short]; 199 | } 200 | 201 | foreach ($this->exceptions as $ex_day => $ex_hours) { 202 | if (strtotime($ex_day) === $today) { 203 | // Today is an exception, use alternate hours instead 204 | $hours_today_array = $ex_hours; 205 | } 206 | } 207 | 208 | return $hours_today_array; 209 | 210 | } 211 | 212 | /** 213 | * 214 | * @return array 215 | */ 216 | private function hours_this_week_simple() 217 | { 218 | 219 | $lookup = array_combine(range(1, 7), $this->config['overview_weekdays']); 220 | $ret = array(); 221 | 222 | for ($i = 1; $i <= 7; $i++) { 223 | $hours_str = (isset($this->hours[$i]) && count($this->hours[$i]) > 0) ? $this->format_hours($this->hours[$i]) : '-'; 224 | 225 | $ret[$lookup[$i]] = $hours_str; 226 | } 227 | 228 | return $ret; 229 | 230 | } 231 | 232 | /** 233 | * 234 | * @return array 235 | */ 236 | private function hours_this_week_grouped() 237 | { 238 | $lookup = array_combine(range(1, 7), $this->config['overview_weekdays']); 239 | $blocks = array(); 240 | 241 | // Remove empty elements ("closed all day") 242 | $hours = array_filter($this->hours, function($element) 243 | { 244 | return (count($element) > 0); 245 | }); 246 | 247 | foreach ($hours as $weekday => $hours2) { 248 | foreach ($blocks as &$block) { 249 | if ($block['hours'] === $hours2) { 250 | $block['days'][] = $weekday; 251 | continue 2; 252 | } 253 | } 254 | unset($block); 255 | $blocks[] = array( 256 | 'days' => array( 257 | $weekday 258 | ), 259 | 'hours' => $hours2 260 | ); 261 | } 262 | 263 | // Flatten 264 | $ret = array(); 265 | foreach ($blocks as $block) { 266 | // Format days 267 | $keyparts = array(); 268 | $keys = $block['days']; 269 | $buffer = array(); 270 | $lastIndex = null; 271 | $minGroupSize = 3; 272 | 273 | foreach ($keys as $index) { 274 | if ($lastIndex !== null && $index - 1 !== $lastIndex) { 275 | if (count($buffer) >= $minGroupSize) { 276 | $keyparts[] = $lookup[$buffer[0]] . '-' . $lookup[$buffer[count($buffer) - 1]]; 277 | } else { 278 | foreach ($buffer as $b) { 279 | $keyparts[] = $lookup[$b]; 280 | } 281 | } 282 | $buffer = array(); 283 | } 284 | $buffer[] = $index; 285 | $lastIndex = $index; 286 | } 287 | if (count($buffer) >= $minGroupSize) { 288 | $keyparts[] = $lookup[$buffer[0]] . '-' . $lookup[$buffer[count($buffer) - 1]]; 289 | } else { 290 | foreach ($buffer as $b) { 291 | $keyparts[] = $lookup[$b]; 292 | } 293 | } 294 | // Combine 295 | $ret[implode(', ', $keyparts)] = $this->format_hours($block['hours']); 296 | } 297 | 298 | return $ret; 299 | 300 | } 301 | 302 | /** 303 | * 304 | * @return string 305 | */ 306 | public function is_open() 307 | { 308 | 309 | return $this->is_open_at(); 310 | 311 | } 312 | 313 | /** 314 | * 315 | * @return string 316 | */ 317 | public function hours_today() 318 | { 319 | 320 | $hours_today = $this->hours_today_array(); 321 | return $this->format_hours($hours_today); 322 | 323 | } 324 | 325 | /** 326 | * 327 | * @return array 328 | */ 329 | public function hours_this_week($groupSameDays = false) 330 | { 331 | 332 | return (true === $groupSameDays) ? $this->hours_this_week_grouped() : $this->hours_this_week_simple(); 333 | 334 | } 335 | 336 | } 337 | -------------------------------------------------------------------------------- /StoreHours2.class.php: -------------------------------------------------------------------------------- 1 | exceptions = $exceptions; 48 | $this->templates = $templates; 49 | $this->yesterdayFlag = false; 50 | 51 | $weekdayToIndex = array( 52 | 'mon' => 1, 53 | 'tue' => 2, 54 | 'wed' => 3, 55 | 'thu' => 4, 56 | 'fri' => 5, 57 | 'sat' => 6, 58 | 'sun' => 7 59 | ); 60 | 61 | $this->hours = array(); 62 | 63 | foreach ($hours as $key => $value) { 64 | $this->hours[$weekdayToIndex[$key]] = $value; 65 | } 66 | 67 | // Remove empty elements from values (backwards compatibility) 68 | foreach ($this->hours as $key => $value) { 69 | $this->hours[$key] = array_filter($value, function ($element) { 70 | return (trim($element) !== ''); 71 | }); 72 | } 73 | 74 | // Remove empty elements from values (backwards compatibility) 75 | foreach ($this->exceptions as $key => $value) { 76 | $this->exceptions[$key] = array_filter($value, function ($element) { 77 | return (trim($element) !== ''); 78 | }); 79 | } 80 | 81 | $defaultTemplates = array( 82 | 'open' => '

Yes, we\'re open! Today\'s hours are {%hours%}.

', 83 | 'closed' => '

Sorry, we\'re closed. Today\'s hours are {%hours%}.

', 84 | 'closed_all_day' => '

Sorry, we\'re closed.

', 85 | 'separator' => ' - ', 86 | 'join' => ' and ', 87 | 'format' => 'g:ia', 88 | 'hours' => '{%open%}{%separator%}{%closed%}', 89 | 90 | 'overview_separator' => '-', 91 | 'overview_join' => ', ', 92 | 'overview_format' => 'g:ia', 93 | 'overview_weekdays' => array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') 94 | ); 95 | 96 | $this->templates += $defaultTemplates; 97 | } 98 | 99 | /** 100 | * 101 | * @param string $timestamp 102 | * @return array Today's hours 103 | */ 104 | public function hours_today($timestamp = null) 105 | { 106 | $timestamp = (null !== $timestamp) ? $timestamp : time(); 107 | $today = strtotime(date('Y-m-d', $timestamp) . ' midnight'); 108 | $weekday_short = date('N', $timestamp); 109 | 110 | $hours_today = array(); 111 | 112 | if (isset($this->hours[$weekday_short])) { 113 | $hours_today = $this->hours[$weekday_short]; 114 | } 115 | 116 | foreach ($this->exceptions as $ex_day => $ex_hours) { 117 | if (strtotime($ex_day) === $today) { 118 | // Today is an exception, use alternate hours instead 119 | $hours_today = $ex_hours; 120 | } 121 | } 122 | 123 | return $hours_today; 124 | } 125 | 126 | /** 127 | * 128 | * @param string $timestamp 129 | * @return boolean 130 | */ 131 | public function is_open($timestamp = null) 132 | { 133 | $timestamp = (null !== $timestamp) ? $timestamp : time(); 134 | 135 | $is_open = false; 136 | $this->yesterdayFlag = false; 137 | 138 | // Check whether shop's still open from day before 139 | 140 | $ts_yesterday = strtotime(date('Y-m-d H:i:s', $timestamp) . ' -1 day'); 141 | $yesterday = date('Y-m-d', $ts_yesterday); 142 | $hours_yesterday = $this->hours_today($ts_yesterday); 143 | 144 | foreach ($hours_yesterday as $range) { 145 | $range = explode('-', $range); 146 | $start = strtotime($yesterday . ' ' . $range[0]); 147 | $end = strtotime($yesterday . ' ' . $range[1]); 148 | 149 | if ($end <= $start) { 150 | $end = strtotime($yesterday . ' ' . $range[1] . ' +1 day'); 151 | } 152 | 153 | if ($start <= $timestamp && $timestamp <= $end) { 154 | $is_open = true; 155 | $this->yesterdayFlag = true; 156 | break; 157 | } 158 | } 159 | 160 | // Check today's hours 161 | 162 | if (!$is_open) { 163 | $day = date('Y-m-d', $timestamp); 164 | $hours_today = $this->hours_today($timestamp); 165 | 166 | foreach ($hours_today as $range) { 167 | $range = explode('-', $range); 168 | $start = strtotime($day . ' ' . $range[0]); 169 | $end = strtotime($day . ' ' . $range[1]); 170 | 171 | if ($end <= $start) { 172 | $end = strtotime($day . ' ' . $range[1] . ' +1 day'); 173 | } 174 | 175 | if ($start <= $timestamp && $timestamp <= $end) { 176 | $is_open = true; 177 | break; 178 | } 179 | } 180 | } 181 | 182 | return $is_open; 183 | } 184 | 185 | /** 186 | * Prep HTML 187 | * 188 | * @param string $template_name 189 | * @param int $timestamp 190 | */ 191 | private function render_html($template_name, $timestamp) 192 | { 193 | $template = $this->templates; 194 | $hours_today = $this->hours_today($timestamp); 195 | $day = date('Y-m-d', $timestamp); 196 | $output = ''; 197 | 198 | if (count($hours_today) > 0) { 199 | $hours_template = ''; 200 | $first = true; 201 | 202 | foreach ($hours_today as $range) { 203 | $range = explode('-', $range); 204 | $start = strtotime($day . ' ' . $range[0]); 205 | $end = strtotime($day . ' ' . $range[1]); 206 | 207 | if (false === $first) { 208 | $hours_template .= $template['join']; 209 | } 210 | 211 | $hours_template .= $template['hours']; 212 | 213 | $hours_template = str_replace('{%open%}', date($template['format'], $start), $hours_template); 214 | $hours_template = str_replace('{%closed%}', date($template['format'], $end), $hours_template); 215 | $hours_template = str_replace('{%separator%}', $template['separator'], $hours_template); 216 | 217 | $first = false; 218 | } 219 | 220 | $output .= str_replace('{%hours%}', $hours_template, $template[$template_name]); 221 | } else { 222 | $output .= $template['closed_all_day']; 223 | } 224 | 225 | echo $output; 226 | } 227 | 228 | /** 229 | * Output HTML 230 | * 231 | * @param string $timestamp 232 | */ 233 | public function render($timestamp = null) 234 | { 235 | $timestamp = (null !== $timestamp) ? $timestamp : time(); 236 | 237 | if ($this->is_open($timestamp)) { 238 | // Print yesterday's hours if shop's still open from day before 239 | if ($this->yesterdayFlag) { 240 | $timestamp = strtotime(date('Y-m-d H:i:s', $timestamp) . ' -1 day'); 241 | } 242 | 243 | $this->render_html('open', $timestamp); 244 | } else { 245 | $this->render_html('closed', $timestamp); 246 | } 247 | } 248 | 249 | /** 250 | * 251 | * @param array $ranges 252 | * @return string 253 | */ 254 | private function hours_overview_format_hours(array $ranges) 255 | { 256 | $hoursparts = array(); 257 | 258 | foreach ($ranges as $range) { 259 | $day = '2016-01-01'; 260 | 261 | $range = explode('-', $range); 262 | $start = strtotime($day . ' ' . $range[0]); 263 | $end = strtotime($day . ' ' . $range[1]); 264 | 265 | $hoursparts[] = date($this->templates['overview_format'], $start) 266 | . $this->templates['overview_separator'] 267 | . date($this->templates['overview_format'], $end); 268 | } 269 | 270 | return implode($this->templates['overview_join'], $hoursparts); 271 | } 272 | 273 | /** 274 | * 275 | */ 276 | private function hours_this_week_simple() 277 | { 278 | $lookup = array_combine(range(1, 7), $this->templates['overview_weekdays']); 279 | 280 | $ret = array(); 281 | 282 | for ($i = 1; $i <= 7; $i++) { 283 | $hours_str = (isset($this->hours[$i]) && count($this->hours[$i]) > 0) 284 | ? $this->hours_overview_format_hours($this->hours[$i]) 285 | : '-'; 286 | 287 | $ret[$lookup[$i]] = $hours_str; 288 | } 289 | 290 | return $ret; 291 | } 292 | 293 | /** 294 | * 295 | * @return array 296 | */ 297 | private function hours_this_week_grouped() 298 | { 299 | $lookup = array_combine(range(1, 7), $this->templates['overview_weekdays']); 300 | 301 | $blocks = array(); 302 | 303 | // Remove empty elements ("closed all day") 304 | 305 | $hours = array_filter($this->hours, function ($element) { 306 | return (count($element) > 0); 307 | }); 308 | 309 | foreach ($hours as $weekday => $hours2) { 310 | foreach ($blocks as &$block) { 311 | if ($block['hours'] === $hours2) { 312 | $block['days'][] = $weekday; 313 | continue 2; 314 | } 315 | } 316 | unset($block); 317 | 318 | $blocks[] = array('days' => array($weekday), 'hours' => $hours2); 319 | } 320 | 321 | // Flatten 322 | 323 | $ret = array(); 324 | 325 | foreach ($blocks as $block) { 326 | // Format days 327 | 328 | $keyparts = array(); 329 | $keys = $block['days']; 330 | $buffer = array(); 331 | $lastIndex = null; 332 | $minGroupSize = 3; 333 | 334 | foreach ($keys as $index) { 335 | if ($lastIndex !== null && $index - 1 !== $lastIndex) { 336 | if (count($buffer) >= $minGroupSize) { 337 | $keyparts[] = $lookup[$buffer[0]] . '-' . $lookup[$buffer[count($buffer) - 1]]; 338 | } else { 339 | foreach ($buffer as $b) { 340 | $keyparts[] = $lookup[$b]; 341 | } 342 | } 343 | $buffer = array(); 344 | } 345 | 346 | $buffer[] = $index; 347 | 348 | $lastIndex = $index; 349 | } 350 | if (count($buffer) >= $minGroupSize) { 351 | $keyparts[] = $lookup[$buffer[0]] . '-' . $lookup[$buffer[count($buffer) - 1]]; 352 | } else { 353 | foreach ($buffer as $b) { 354 | $keyparts[] = $lookup[$b]; 355 | } 356 | } 357 | 358 | // Combine 359 | 360 | $ret[implode(', ', $keyparts)] = $this->hours_overview_format_hours($block['hours']); 361 | } 362 | 363 | return $ret; 364 | } 365 | 366 | /** 367 | * 368 | * @return array 369 | */ 370 | public function hours_this_week($groupSameDays = false) 371 | { 372 | return (true === $groupSameDays) 373 | ? $this->hours_this_week_grouped() 374 | : $this->hours_this_week_simple(); 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /StoreHoursTest.php: -------------------------------------------------------------------------------- 1 | array('11:00-20:30'), 26 | 'tue' => array('11:00-13:00', '18:00-20:30'), 27 | 'wed' => array('11:00-20:30'), 28 | 'thu' => array('11:00-1:30'), // Open late 29 | 'fri' => array('11:00-20:30'), 30 | 'sat' => array('11:00-20:00'), 31 | 'sun' => array('') // Closed all day 32 | ); 33 | 34 | $exceptions = array( 35 | '2/24' => array('11:00-18:00'), 36 | '10/18' => array('11:00-16:00', '18:00-20:30'), 37 | 38 | '2016-02-01' => array('09:00-11:00') 39 | ); 40 | 41 | return new StoreHours($hours, $exceptions); 42 | } 43 | 44 | /** 45 | * 46 | */ 47 | public function testHoursTodayMethod() 48 | { 49 | $sh = $this->instantiateWithDefaultData(); 50 | 51 | $this->assertEquals(array('11:00-20:30'), $sh->hours_today(strtotime('2016-02-08'))); // mon 52 | $this->assertEquals(array('11:00-13:00', '18:00-20:30'), $sh->hours_today(strtotime('2016-02-09'))); // tue 53 | $this->assertEquals(array('11:00-20:30'), $sh->hours_today(strtotime('2016-02-10'))); // wed 54 | $this->assertEquals(array('11:00-1:30'), $sh->hours_today(strtotime('2016-02-11'))); // thu 55 | $this->assertEquals(array('11:00-20:30'), $sh->hours_today(strtotime('2016-02-12'))); // fri 56 | $this->assertEquals(array('11:00-20:00'), $sh->hours_today(strtotime('2016-02-13'))); // sat 57 | $this->assertEquals(array(), $sh->hours_today(strtotime('2016-02-14'))); // sun 58 | 59 | // Exceptions (dates, not the PHP kind) 60 | 61 | $this->assertEquals(array('11:00-18:00'), $sh->hours_today(strtotime('2016-02-24'))); 62 | $this->assertEquals(array('11:00-16:00', '18:00-20:30'), $sh->hours_today(strtotime('2016-10-18'))); 63 | 64 | $this->assertEquals(array('09:00-11:00'), $sh->hours_today(strtotime('2016-02-01'))); 65 | $this->assertEquals(array('11:00-20:30'), $sh->hours_today(strtotime('2017-02-01'))); // wed 66 | } 67 | 68 | /** 69 | * 70 | */ 71 | public function testIsOpenMethod() 72 | { 73 | $sh = $this->instantiateWithDefaultData(); 74 | 75 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-08 10:59:59'))); // mon 76 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-08 11:00:00'))); 77 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-08 20:30:00'))); 78 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-08 20:30:01'))); 79 | 80 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-09 10:59:59'))); // tue 81 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-09 11:00:00'))); 82 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-09 13:00:00'))); 83 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-09 13:00:01'))); 84 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-09 17:59:59'))); 85 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-09 18:00:00'))); 86 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-09 20:30:00'))); 87 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-09 20:30:01'))); 88 | 89 | // "open late" 90 | 91 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-11 00:30:00'))); // thu 92 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-11 23:59:59'))); 93 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-12 00:00:00'))); // fri 94 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-12 01:30:00'))); 95 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-12 01:30:01'))); 96 | 97 | // Exceptions (dates, not the PHP kind) 98 | 99 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-24 10:59:59'))); 100 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-24 11:00:00'))); 101 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-24 18:00:00'))); 102 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-24 18:00:01'))); 103 | 104 | $this->assertEquals(false, $sh->is_open(strtotime('2016-10-18 10:59:59'))); 105 | $this->assertEquals(true, $sh->is_open(strtotime('2016-10-18 11:00:00'))); 106 | $this->assertEquals(true, $sh->is_open(strtotime('2016-10-18 16:00:00'))); 107 | $this->assertEquals(false, $sh->is_open(strtotime('2016-10-18 16:00:01'))); 108 | $this->assertEquals(false, $sh->is_open(strtotime('2016-10-18 17:59:59'))); 109 | $this->assertEquals(true, $sh->is_open(strtotime('2016-10-18 18:00:00'))); 110 | $this->assertEquals(true, $sh->is_open(strtotime('2016-10-18 20:30:00'))); 111 | $this->assertEquals(false, $sh->is_open(strtotime('2016-10-18 20:30:01'))); 112 | 113 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-01 08:59:59'))); 114 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-01 09:00:00'))); 115 | $this->assertEquals(true, $sh->is_open(strtotime('2016-02-01 11:00:00'))); 116 | $this->assertEquals(false, $sh->is_open(strtotime('2016-02-01 11:00:01'))); 117 | 118 | $this->assertEquals(false, $sh->is_open(strtotime('2017-02-01 10:59:59'))); // wed 119 | $this->assertEquals(true, $sh->is_open(strtotime('2017-02-01 11:00:00'))); 120 | $this->assertEquals(true, $sh->is_open(strtotime('2017-02-01 20:30:00'))); 121 | $this->assertEquals(false, $sh->is_open(strtotime('2017-02-01 20:30:01'))); 122 | } 123 | 124 | /** 125 | * 126 | */ 127 | public function testRenderMethod() 128 | { 129 | $sh = $this->instantiateWithDefaultData(); 130 | 131 | ob_start(); 132 | $sh->render(strtotime('2016-02-13 14:30:00')); // sat 133 | $this->assertEquals('

Yes, we\'re open! Today\'s hours are 11:00am - 8:00pm.

', ob_get_clean()); 134 | 135 | ob_start(); 136 | $sh->render(strtotime('2016-02-09 14:30:00')); // tue 137 | $this->assertEquals('

Sorry, we\'re closed. Today\'s hours are 11:00am - 1:00pm and 6:00pm - 8:30pm.

', ob_get_clean()); 138 | 139 | ob_start(); 140 | $sh->render(strtotime('2016-02-14 12:00:00')); // sun 141 | $this->assertEquals('

Sorry, we\'re closed.

', ob_get_clean()); 142 | 143 | // "open late" (if still open, display hours from yesterday) 144 | 145 | ob_start(); 146 | $sh->render(strtotime('2016-02-11 23:59:59')); // night from thu->fri, thursday's hours 147 | $this->assertEquals('

Yes, we\'re open! Today\'s hours are 11:00am - 1:30am.

', ob_get_clean()); 148 | 149 | ob_start(); 150 | $sh->render(strtotime('2016-02-12 00:30:00')); // night from thu->fri, thursday's hours 151 | $this->assertEquals('

Yes, we\'re open! Today\'s hours are 11:00am - 1:30am.

', ob_get_clean()); 152 | 153 | ob_start(); 154 | $sh->render(strtotime('2016-02-12 01:30:01')); // closed on friday morning, friday's hours 155 | $this->assertEquals('

Sorry, we\'re closed. Today\'s hours are 11:00am - 8:30pm.

', ob_get_clean()); 156 | 157 | // Exceptions (dates, not the PHP kind) 158 | 159 | ob_start(); 160 | $sh->render(strtotime('2016-02-24 19:00:00')); 161 | $this->assertEquals('

Sorry, we\'re closed. Today\'s hours are 11:00am - 6:00pm.

', ob_get_clean()); 162 | 163 | ob_start(); 164 | $sh->render(strtotime('2016-10-18 19:00:00')); 165 | $this->assertEquals('

Yes, we\'re open! Today\'s hours are 11:00am - 4:00pm and 6:00pm - 8:30pm.

', ob_get_clean()); 166 | 167 | ob_start(); 168 | $sh->render(strtotime('2016-02-01 09:00:00')); 169 | $this->assertEquals('

Yes, we\'re open! Today\'s hours are 9:00am - 11:00am.

', ob_get_clean()); 170 | 171 | ob_start(); 172 | $sh->render(strtotime('2016-02-01 12:00:00')); 173 | $this->assertEquals('

Sorry, we\'re closed. Today\'s hours are 9:00am - 11:00am.

', ob_get_clean()); 174 | 175 | ob_start(); 176 | $sh->render(strtotime('2017-02-01 12:00:00')); // wed 177 | $this->assertEquals('

Yes, we\'re open! Today\'s hours are 11:00am - 8:30pm.

', ob_get_clean()); 178 | } 179 | 180 | /** 181 | * 182 | */ 183 | public function testWithCustomTemplates() 184 | { 185 | $hours = array( 186 | 'mon' => array('09:00-17:00', '17:30-18:00', '19:00-02:30'), 187 | 'thu' => array('17:45-18:00') 188 | ); 189 | 190 | $exceptions = array( 191 | '2016-02-15' => array() 192 | ); 193 | 194 | $templates = array( 195 | 'open' => 'Open. Hours {%hours%}.', 196 | 'separator' => '-', 197 | 'format' => 'G.i' 198 | ); 199 | 200 | $sh = new StoreHours($hours, $exceptions, $templates); 201 | 202 | ob_start(); 203 | $sh->render(strtotime('2016-02-08 14:30:00')); // mon 204 | $this->assertEquals('Open. Hours 9.00-17.00 and 17.30-18.00 and 19.00-2.30.', ob_get_clean()); 205 | 206 | ob_start(); 207 | $sh->render(strtotime('2016-02-11 14:30:00')); // thu 208 | $this->assertEquals('

Sorry, we\'re closed. Today\'s hours are 17.45-18.00.

', ob_get_clean()); 209 | 210 | ob_start(); 211 | $sh->render(strtotime('2016-02-15 14:30:00')); // mon 212 | $this->assertEquals('

Sorry, we\'re closed.

', ob_get_clean()); 213 | } 214 | 215 | /** 216 | * 217 | */ 218 | public function testHoursOverviewSimple() 219 | { 220 | $sh = new StoreHours(array()); 221 | $this->assertEquals(array( 222 | 'Mon' => '-', 223 | 'Tue' => '-', 224 | 'Wed' => '-', 225 | 'Thu' => '-', 226 | 'Fri' => '-', 227 | 'Sat' => '-', 228 | 'Sun' => '-' 229 | ), $sh->hours_this_week()); 230 | 231 | $sh = new StoreHours(array('fri' => array(''))); 232 | $this->assertEquals(array( 233 | 'Mon' => '-', 234 | 'Tue' => '-', 235 | 'Wed' => '-', 236 | 'Thu' => '-', 237 | 'Fri' => '-', 238 | 'Sat' => '-', 239 | 'Sun' => '-' 240 | ), $sh->hours_this_week()); 241 | 242 | 243 | $sh = new StoreHours(array( 244 | 'mon' => array('08:00-12:00', '13:00-1:30'), 245 | 'tue' => array('08:00-12:00', '13:00-1:30'), 246 | 'thu' => array('08:00-12:00', '13:00-1:30'), 247 | 'fri' => array('08:00-12:00', '13:00-1:30'), 248 | 'sun' => array('08:00-1:30') 249 | )); 250 | $this->assertEquals(array( 251 | 'Mon' => '8:00am-12:00pm, 1:00pm-1:30am', 252 | 'Tue' => '8:00am-12:00pm, 1:00pm-1:30am', 253 | 'Wed' => '-', 254 | 'Thu' => '8:00am-12:00pm, 1:00pm-1:30am', 255 | 'Fri' => '8:00am-12:00pm, 1:00pm-1:30am', 256 | 'Sat' => '-', 257 | 'Sun' => '8:00am-1:30am' 258 | ), $sh->hours_this_week()); 259 | } 260 | 261 | /** 262 | * 263 | */ 264 | public function testHoursOverviewGrouped() 265 | { 266 | $sh = new StoreHours(array()); 267 | $this->assertEquals(array(), $sh->hours_this_week(true)); 268 | 269 | $sh = new StoreHours(array('fri' => array(''))); 270 | $this->assertEquals(array(), $sh->hours_this_week(true)); 271 | 272 | 273 | $sh = new StoreHours(array( 274 | 'sun' => array('08:00-12:00') 275 | )); 276 | $this->assertEquals(array( 277 | 'Sun' => '8:00am-12:00pm' 278 | ), $sh->hours_this_week(true)); 279 | 280 | 281 | $sh = new StoreHours(array( 282 | 'mon' => array('08:00-12:00', '13:00-1:30'), 283 | 'tue' => array('08:00-12:00', '13:00-1:30') 284 | )); 285 | $this->assertEquals(array( 286 | 'Mon, Tue' => '8:00am-12:00pm, 1:00pm-1:30am' 287 | ), $sh->hours_this_week(true)); 288 | 289 | 290 | $sh = new StoreHours(array( 291 | 'mon' => array('08:00-12:00', '13:00-1:30'), 292 | 'tue' => array('08:00-12:00', '13:00-1:30'), 293 | 'wed' => array('08:00-12:00', '13:00-1:30') 294 | )); 295 | $this->assertEquals(array( 296 | 'Mon-Wed' => '8:00am-12:00pm, 1:00pm-1:30am' 297 | ), $sh->hours_this_week(true)); 298 | 299 | 300 | $sh = new StoreHours(array( 301 | 'mon' => array('08:00-12:00', '13:00-1:30'), 302 | 'tue' => array('08:00-12:00', '13:00-1:30'), 303 | 'thu' => array('08:00-12:00', '13:00-1:30'), 304 | 'fri' => array('08:00-12:00', '13:00-1:30'), 305 | 'sun' => array('08:00-1:30') 306 | )); 307 | $this->assertEquals(array( 308 | 'Mon, Tue, Thu, Fri' => '8:00am-12:00pm, 1:00pm-1:30am', 309 | 'Sun' => '8:00am-1:30am' 310 | ), $sh->hours_this_week(true)); 311 | 312 | 313 | $sh = new StoreHours(array( 314 | 'mon' => array('08:00-12:00', '13:00-1:30'), 315 | 'tue' => array('08:00-12:00', '13:00-1:30'), 316 | 'wed' => array('08:00-12:00', '13:00-1:30'), 317 | 'fri' => array('08:00-12:00', '13:00-1:30'), 318 | 'sat' => array('08:00-12:00', '13:00-1:30'), 319 | 'sun' => array('08:00-12:00', '13:00-1:30') 320 | )); 321 | $this->assertEquals(array( 322 | 'Mon-Wed, Fri-Sun' => '8:00am-12:00pm, 1:00pm-1:30am' 323 | ), $sh->hours_this_week(true)); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PHP Store Hours 7 | 8 | 24 | 25 | 26 | 27 | 28 |

Gadgets Inc.

29 |

Store Hours

30 | 31 | array('11:00-20:30'), 46 | 'tue' => array('11:00-13:00', '18:00-20:30'), 47 | 'wed' => array('11:00-13:00', '18:00-20:30'), 48 | 'thu' => array('11:00-1:30'), // Open late 49 | 'fri' => array('11:00-20:30'), 50 | 'sat' => array('11:00-20:00'), 51 | 'sun' => array('11:00-20:00') 52 | ); 53 | 54 | // OPTIONAL 55 | // Add exceptions (great for holidays etc.) 56 | // MUST be in a format month/day[/year] or [year-]month-day 57 | // Do not include the year if the exception repeats annually 58 | $exceptions = array( 59 | '2/24' => array('11:00-18:00'), 60 | '10/18' => array('11:00-16:00', '18:00-20:30') 61 | ); 62 | 63 | $config = array( 64 | 'separator' => ' - ', 65 | 'join' => ' and ', 66 | 'format' => 'g:ia', 67 | 'overview_weekdays' => array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') 68 | ); 69 | 70 | // Instantiate class 71 | $store_hours = new StoreHours($hours, $exceptions, $config); 72 | 73 | // Display open / closed message 74 | if($store_hours->is_open()) { 75 | echo "Yes, we're open! Today's hours are " . $store_hours->hours_today() . "."; 76 | } else { 77 | echo "Sorry, we're closed. Today's hours are " . $store_hours->hours_today() . "."; 78 | } 79 | 80 | // Display full list of open hours (for a week without exceptions) 81 | echo ''; 82 | foreach ($store_hours->hours_this_week() as $days => $hours) { 83 | echo ''; 84 | echo ''; 85 | echo ''; 86 | echo ''; 87 | } 88 | echo '
' . $days . '' . $hours . '
'; 89 | 90 | // Same list, but group days with identical hours 91 | echo ''; 92 | foreach ($store_hours->hours_this_week(true) as $days => $hours) { 93 | echo ''; 94 | echo ''; 95 | echo ''; 96 | echo ''; 97 | } 98 | echo '
' . $days . '' . $hours . '
'; 99 | 100 | ?> 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StoreHoursTest.php 6 | 7 | 8 | 9 | 10 | StoreHours.class.php 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------