<?php

/**
 * Class ActionScheduler_TimezoneHelper
 */
abstract class ActionScheduler_TimezoneHelper {

	/** @var null|DateTimeZone */
	private static $local_timezone = NULL;

	/**
	 * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
	 * if no timezone string is available.
	 *
	 * @since  2.1.0
	 *
	 * @param DateTime $date Timestamp.
	 * @return ActionScheduler_DateTime
	 */
	public static function set_local_timezone( DateTime $date ) {

		// Accept a DateTime for easier backward compatibility, even though we require methods on ActionScheduler_DateTime.
		if ( ! is_a( $date, 'ActionScheduler_DateTime' ) ) {
			$date = as_get_datetime_object( $date->format( 'U' ) );
		}

		if ( get_option( 'timezone_string' ) ) {
			$date->setTimezone( new DateTimeZone( self::get_local_timezone_string() ) );
		} else {
			$date->setUtcOffset( self::get_local_timezone_offset() );
		}

		return $date;
	}

	/**
	 * Helper to retrieve the timezone string for a site until a WP core method exists
	 * (see https://core.trac.wordpress.org/ticket/24730).
	 *
	 * Adapted from wc_timezone_string() and https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155.
	 *
	 * If no timezone string is set, and its not possible to match the UTC offset set for the site to a timezone
	 * string, then an empty string will be returned, and the UTC offset should be used to set a DateTime's
	 * timezone.
	 *
	 * @since 2.1.0
	 * @param bool $reset Unused.
	 * @return string PHP timezone string for the site or empty if no timezone string is available.
	 */
	protected static function get_local_timezone_string( $reset = false ) {
		// If site timezone string exists, return it.
		$timezone = get_option( 'timezone_string' );
		if ( $timezone ) {
			return $timezone;
		}

		// Get UTC offset, if it isn't set then return UTC.
		$utc_offset = intval( get_option( 'gmt_offset', 0 ) );
		if ( 0 === $utc_offset ) {
			return 'UTC';
		}

		// Adjust UTC offset from hours to seconds.
		$utc_offset *= 3600;

		// Attempt to guess the timezone string from the UTC offset.
		$timezone = timezone_name_from_abbr( '', $utc_offset );
		if ( $timezone ) {
			return $timezone;
		}

		// Last try, guess timezone string manually.
		foreach ( timezone_abbreviations_list() as $abbr ) {
			foreach ( $abbr as $city ) {
				if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date	 -- we are actually interested in the runtime timezone.
					return $city['timezone_id'];
				}
			}
		}

		// No timezone string.
		return '';
	}

	/**
	 * Get timezone offset in seconds.
	 *
	 * @since  2.1.0
	 * @return float
	 */
	protected static function get_local_timezone_offset() {
		$timezone = get_option( 'timezone_string' );

		if ( $timezone ) {
			$timezone_object = new DateTimeZone( $timezone );
			return $timezone_object->getOffset( new DateTime( 'now' ) );
		} else {
			return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
		}
	}

	/**
	 * Get local timezone.
	 *
	 * @param bool $reset Toggle to discard stored value.
	 * @deprecated 2.1.0
	 */
	public static function get_local_timezone( $reset = FALSE ) {
		_deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
		if ( $reset ) {
			self::$local_timezone = NULL;
		}
		if ( !isset(self::$local_timezone) ) {
			$tzstring = get_option('timezone_string');

			if ( empty($tzstring) ) {
				$gmt_offset = get_option('gmt_offset');
				if ( $gmt_offset == 0 ) {
					$tzstring = 'UTC';
				} else {
					$gmt_offset *= HOUR_IN_SECONDS;
					$tzstring   = timezone_name_from_abbr( '', $gmt_offset, 1 );

					// If there's no timezone string, try again with no DST.
					if ( false === $tzstring ) {
						$tzstring = timezone_name_from_abbr( '', $gmt_offset, 0 );
					}

					// Try mapping to the first abbreviation we can find.
					if ( false === $tzstring ) {
						$is_dst = date( 'I' ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date	 -- we are actually interested in the runtime timezone.
						foreach ( timezone_abbreviations_list() as $abbr ) {
							foreach ( $abbr as $city ) {
								if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) {
									// If there's no valid timezone ID, keep looking.
									if ( null === $city['timezone_id'] ) {
										continue;
									}

									$tzstring = $city['timezone_id'];
									break 2;
								}
							}
						}
					}

					// If we still have no valid string, then fall back to UTC.
					if ( false === $tzstring ) {
						$tzstring = 'UTC';
					}
				}
			}

			self::$local_timezone = new DateTimeZone($tzstring);
		}
		return self::$local_timezone;
	}
}
