$day, 'end' => $day, 'formatted' => $formatted, ); } else { $current['end'] = $day; } } // @phpstan-ignore notIdentical.alwaysTrue (belt-and-braces for empty input) if ( null !== $current ) { $groups[] = $current; } return $groups; } /** * Format a day range label. * * @param int $start Start day index (0–6). * @param int $end End day index (0–6). * @param string $locale Receipt locale. * @return string */ private static function format_day_range( int $start, int $end, string $locale ): string { if ( $start === $end ) { return self::get_day_name( $start, $locale ); } return self::get_day_name( $start, $locale ) . "\u{2013}" . self::get_day_name( $end, $locale ); } /** * Format time slots for a single day. * * @param array $slots Flat array of time pairs. * @param string $locale Receipt locale. * @param string $closed Closed-day label, already resolved for the receipt locale. * @return string */ private static function format_slots( array $slots, string $locale, string $closed ): string { if ( empty( $slots ) ) { return $closed; } // Drop trailing unpaired element to ensure open/close pairs. if ( count( $slots ) % 2 !== 0 ) { array_pop( $slots ); } if ( empty( $slots ) ) { return $closed; } $ranges = array(); $slot_count = count( $slots ); for ( $i = 0; $i < $slot_count - 1; $i += 2 ) { $open = self::format_time( $slots[ $i ], $locale ); $close = self::format_time( $slots[ $i + 1 ], $locale ); $ranges[] = $open . " \u{2013} " . $close; } return implode( ', ', $ranges ); } /** * Resolve the closed-day label in the receipt locale. * * The day names and times follow the receipt locale, so this label has to * as well — otherwise a store whose locale differs from the site's renders * a mixed-language line ("zo Closed"). Mirrors the locale guard in * Receipt_I18n_Labels::get_labels(); resolve once per public call, because * switch_to_locale() reloads the text domain. * * @param string $locale Receipt locale, or empty string for the site locale. * @return string */ private static function get_closed_label( string $locale ): string { if ( '' !== $locale && get_locale() !== $locale && function_exists( 'switch_to_locale' ) && switch_to_locale( $locale ) ) { try { new i18n(); return self::get_closed_label( '' ); } finally { restore_previous_locale(); } } return /* translators: Short WCPOS UI label; keep concise. */ __( 'Closed', 'woocommerce-pos' ); } /** * Format a wall-clock time through the shared receipt time renderer. * * The stored value is a bare wall clock with no date or zone, so it is * anchored to a fixed UTC instant and rendered in UTC — the reference day * exists only to give the formatter a timestamp. * * @param string $time Time in H:i format (e.g. "09:00"). * @param string $locale Receipt locale. * @return string Formatted time (e.g. "9:00 AM" or "09:00"). */ private static function format_time( string $time, string $locale ): string { $timestamp = strtotime( '2000-01-01 ' . $time . ' UTC' ); if ( false === $timestamp ) { return $time; } return Receipt_Date_Formatter::time( $timestamp, new DateTimeZone( 'UTC' ), '' !== $locale ? $locale : null ); } /** * Get the localized short day name through the shared receipt renderer. * * @param int $day Day index (0=Monday, 6=Sunday). * @param string $locale Receipt locale. * @return string Short day name (e.g. "Mon", "Tue"). */ private static function get_day_name( int $day, string $locale ): string { // 2024-01-01 is a Monday. Offset by $day to get the right weekday. $utc = new DateTimeZone( 'UTC' ); $timestamp = ( new DateTimeImmutable( '2024-01-01 00:00:00', $utc ) )->modify( '+' . $day . ' days' )->getTimestamp(); return Receipt_Date_Formatter::weekday_short( $timestamp, $utc, '' !== $locale ? $locale : null ); } }