PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 All 162 releases
woocommerce-pos / includes / Services / Opening_Hours_Formatter.php

Opening_Hours_Formatter.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.16, at includes/Services/Opening_Hours_Formatter.php

239 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Opening hours formatter service.
4 *
5 * Converts the structured opening hours array into human-readable strings
6 * in three formats: compact (grouped), vertical (one day per line), and
7 * inline (single comma-separated line).
8 *
9 * Times and day names are rendered through Receipt_Date_Formatter so a receipt
10 * uses a single convention source: the opening hours and the order timestamps
11 * share the same clock convention, day-period style, and locale.
12 *
13 * @package WCPOS\WooCommercePOS\Services
14 */
15
16 namespace WCPOS\WooCommercePOS\Services;
17
18 use DateTimeImmutable;
19 use DateTimeZone;
20 use WCPOS\WooCommercePOS\i18n;
21
22 /**
23 * Opening_Hours_Formatter class.
24 */
25 class Opening_Hours_Formatter {
26
27 /**
28 * Day keys in order (Monday=0 through Sunday=6).
29 */
30 private const DAY_KEYS = array( 0, 1, 2, 3, 4, 5, 6 );
31
32 /**
33 * Format as vertical list — one day per line, newline-separated.
34 *
35 * @param array $hours Structured hours array (keys 0–6).
36 * @param string $locale Optional receipt locale; defaults to the site locale.
37 * @return string Newline-separated string.
38 */
39 public static function format_vertical( array $hours, string $locale = '' ): string {
40 $closed = self::get_closed_label( $locale );
41 $lines = array();
42 foreach ( self::DAY_KEYS as $day ) {
43 $day_name = self::get_day_name( $day, $locale );
44 $slots = isset( $hours[ (string) $day ] ) ? $hours[ (string) $day ] : array();
45 $formatted = self::format_slots( $slots, $locale, $closed );
46 $lines[] = $day_name . ' ' . $formatted;
47 }
48
49 return implode( "\n", $lines );
50 }
51
52 /**
53 * Format as compact grouped — consecutive days with identical hours are ranged.
54 *
55 * @param array $hours Structured hours array (keys 0–6).
56 * @param string $locale Optional receipt locale; defaults to the site locale.
57 * @return string Newline-separated string.
58 */
59 public static function format_compact( array $hours, string $locale = '' ): string {
60 $groups = self::group_consecutive_days( $hours, $locale, self::get_closed_label( $locale ) );
61 $lines = array();
62
63 foreach ( $groups as $group ) {
64 $day_label = self::format_day_range( $group['start'], $group['end'], $locale );
65 $lines[] = $day_label . ' ' . $group['formatted'];
66 }
67
68 return implode( "\n", $lines );
69 }
70
71 /**
72 * Format as inline — single comma-separated line using compact grouping.
73 *
74 * @param array $hours Structured hours array (keys 0–6).
75 * @param string $locale Optional receipt locale; defaults to the site locale.
76 * @return string Single line string.
77 */
78 public static function format_inline( array $hours, string $locale = '' ): string {
79 $groups = self::group_consecutive_days( $hours, $locale, self::get_closed_label( $locale ) );
80 $parts = array();
81
82 foreach ( $groups as $group ) {
83 $day_label = self::format_day_range( $group['start'], $group['end'], $locale );
84 $parts[] = $day_label . ' ' . $group['formatted'];
85 }
86
87 return implode( ', ', $parts );
88 }
89
90 /**
91 * Group consecutive days that share identical time slots.
92 *
93 * @param array $hours Structured hours array.
94 * @param string $locale Receipt locale.
95 * @param string $closed Closed-day label, already resolved for the receipt locale.
96 * @return array Array of groups, each with 'start', 'end', 'formatted'.
97 */
98 private static function group_consecutive_days( array $hours, string $locale, string $closed ): array {
99 $groups = array();
100 $current = null;
101
102 foreach ( self::DAY_KEYS as $day ) {
103 $slots = isset( $hours[ (string) $day ] ) ? $hours[ (string) $day ] : array();
104 $formatted = self::format_slots( $slots, $locale, $closed );
105
106 if ( null === $current || $current['formatted'] !== $formatted ) {
107 if ( null !== $current ) {
108 $groups[] = $current;
109 }
110 $current = array(
111 'start' => $day,
112 'end' => $day,
113 'formatted' => $formatted,
114 );
115 } else {
116 $current['end'] = $day;
117 }
118 }
119
120 // @phpstan-ignore notIdentical.alwaysTrue (belt-and-braces for empty input)
121 if ( null !== $current ) {
122 $groups[] = $current;
123 }
124
125 return $groups;
126 }
127
128 /**
129 * Format a day range label.
130 *
131 * @param int $start Start day index (0–6).
132 * @param int $end End day index (0–6).
133 * @param string $locale Receipt locale.
134 * @return string
135 */
136 private static function format_day_range( int $start, int $end, string $locale ): string {
137 if ( $start === $end ) {
138 return self::get_day_name( $start, $locale );
139 }
140
141 return self::get_day_name( $start, $locale ) . "\u{2013}" . self::get_day_name( $end, $locale );
142 }
143
144 /**
145 * Format time slots for a single day.
146 *
147 * @param array $slots Flat array of time pairs.
148 * @param string $locale Receipt locale.
149 * @param string $closed Closed-day label, already resolved for the receipt locale.
150 * @return string
151 */
152 private static function format_slots( array $slots, string $locale, string $closed ): string {
153 if ( empty( $slots ) ) {
154 return $closed;
155 }
156
157 // Drop trailing unpaired element to ensure open/close pairs.
158 if ( count( $slots ) % 2 !== 0 ) {
159 array_pop( $slots );
160 }
161
162 if ( empty( $slots ) ) {
163 return $closed;
164 }
165
166 $ranges = array();
167 $slot_count = count( $slots );
168 for ( $i = 0; $i < $slot_count - 1; $i += 2 ) {
169 $open = self::format_time( $slots[ $i ], $locale );
170 $close = self::format_time( $slots[ $i + 1 ], $locale );
171 $ranges[] = $open . " \u{2013} " . $close;
172 }
173
174 return implode( ', ', $ranges );
175 }
176
177 /**
178 * Resolve the closed-day label in the receipt locale.
179 *
180 * The day names and times follow the receipt locale, so this label has to
181 * as well — otherwise a store whose locale differs from the site's renders
182 * a mixed-language line ("zo Closed"). Mirrors the locale guard in
183 * Receipt_I18n_Labels::get_labels(); resolve once per public call, because
184 * switch_to_locale() reloads the text domain.
185 *
186 * @param string $locale Receipt locale, or empty string for the site locale.
187 * @return string
188 */
189 private static function get_closed_label( string $locale ): string {
190 if ( '' !== $locale && get_locale() !== $locale && function_exists( 'switch_to_locale' ) && switch_to_locale( $locale ) ) {
191 try {
192 new i18n();
193
194 return self::get_closed_label( '' );
195 } finally {
196 restore_previous_locale();
197 }
198 }
199
200 return /* translators: Short WCPOS UI label; keep concise. */ __( 'Closed', 'woocommerce-pos' );
201 }
202
203 /**
204 * Format a wall-clock time through the shared receipt time renderer.
205 *
206 * The stored value is a bare wall clock with no date or zone, so it is
207 * anchored to a fixed UTC instant and rendered in UTC — the reference day
208 * exists only to give the formatter a timestamp.
209 *
210 * @param string $time Time in H:i format (e.g. "09:00").
211 * @param string $locale Receipt locale.
212 * @return string Formatted time (e.g. "9:00 AM" or "09:00").
213 */
214 private static function format_time( string $time, string $locale ): string {
215 $timestamp = strtotime( '2000-01-01 ' . $time . ' UTC' );
216
217 if ( false === $timestamp ) {
218 return $time;
219 }
220
221 return Receipt_Date_Formatter::time( $timestamp, new DateTimeZone( 'UTC' ), '' !== $locale ? $locale : null );
222 }
223
224 /**
225 * Get the localized short day name through the shared receipt renderer.
226 *
227 * @param int $day Day index (0=Monday, 6=Sunday).
228 * @param string $locale Receipt locale.
229 * @return string Short day name (e.g. "Mon", "Tue").
230 */
231 private static function get_day_name( int $day, string $locale ): string {
232 // 2024-01-01 is a Monday. Offset by $day to get the right weekday.
233 $utc = new DateTimeZone( 'UTC' );
234 $timestamp = ( new DateTimeImmutable( '2024-01-01 00:00:00', $utc ) )->modify( '+' . $day . ' days' )->getTimestamp();
235
236 return Receipt_Date_Formatter::weekday_short( $timestamp, $utc, '' !== $locale ? $locale : null );
237 }
238 }
239