<?php
/**
 * Date Utilities file
 *
 * @category Utilities
 * @package  My Calendar
 * @author   Joe Dolson
 * @license  GPLv3
 * @link     https://www.joedolson.com/my-calendar/
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Generate classes for a given date
 *
 * @param int $current timestamp.
 *
 * @return string classes
 */
function mc_dateclass( $current ) {
	$dayclass = sanitize_html_class( strtolower( date_i18n( 'l', $current ) ) ) . ' ' . sanitize_html_class( strtolower( date_i18n( 'D', $current ) ) );
	if ( current_time( 'Ymd' ) === mc_date( 'Ymd', $current, false ) ) {
		$dateclass = 'current-day';
	} elseif ( my_calendar_date_comp( current_time( 'Y-m-d' ), mc_date( 'Y-m-d', $current, false ) ) ) {
		$dateclass = 'future-day';
	} else {
		$dateclass = 'past-day past-date'; // stupid legacy classes.
	}

	return esc_attr( $dayclass . ' ' . $dateclass );
}

/**
 * Given a date and a quantity of time to add, produce new date
 *
 * @param string $givendate A time string.
 * @param int    $day Number of days to add.
 * @param int    $mth Number of months to add.
 * @param int    $yr number of years to add.
 *
 * @return int
 */
function my_calendar_add_date( $givendate, $day = 0, $mth = 0, $yr = 0 ) {
	$cd      = strtotime( $givendate );
	$newdate = mktime( mc_date( 'H', $cd, false ), mc_date( 'i', $cd, false ), mc_date( 's', $cd, false ), (int) mc_date( 'm', $cd, false ) + $mth, (int) mc_date( 'd', $cd, false ) + $day, (int) mc_date( 'Y', $cd, false ) + $yr );

	return $newdate;
}

/**
 * Test if the date is before or equal to second date with time precision
 *
 * @param string $early date string.
 * @param string $late date string.
 *
 * @return boolean true if first date earlier or equal
 */
function my_calendar_date_comp( $early, $late ) {
	$firstdate = strtotime( $early );
	$lastdate  = strtotime( $late );
	if ( $firstdate <= $lastdate ) {

		return true;
	} else {

		return false;
	}
}

/**
 * Test if first date before second date with time precision
 *
 * @param string $early date string.
 * @param string $late date string.
 *
 * @return boolean true if first date earlier
 */
function my_calendar_date_xcomp( $early, $late ) {
	$firstdate = strtotime( $early );
	$lastdate  = strtotime( $late );
	if ( $firstdate < $lastdate ) {

		return true;
	} else {

		return false;
	}
}

/**
 *  Test if dates are the same with day precision
 *
 * @param string $early date string in current time zone.
 * @param string $late date string in current time zone.
 *
 * @return boolean true if first date equal to second
 */
function my_calendar_date_equal( $early, $late ) {
	// convert full datetime to date only.
	$firstdate = strtotime( mc_date( 'Y-m-d', strtotime( $early ), false ) );
	$lastdate  = strtotime( mc_date( 'Y-m-d', strtotime( $late ), false ) );
	if ( $firstdate === $lastdate ) {

		return true;
	} else {

		return false;
	}
}

/**
 * Function to compare time in event objects for sorting
 *
 * @param object $a event object.
 * @param object $b event object.
 *
 * @return int (ternary value)
 */
function mc_time_cmp( $a, $b ) {
	if ( $a->occur_begin === $b->occur_begin ) {

		return 0;
	}

	return ( $a->occur_begin < $b->occur_begin ) ? - 1 : 1;
}

/**
 * Function to compare datetime in event objects & sort by string
 *
 * @param object $a event object.
 * @param object $b event object.
 *
 * @return integer (ternary value)
 */
function mc_datetime_cmp( $a, $b ) {
	$event_dt_a = strtotime( $a->occur_begin );
	$event_dt_b = strtotime( $b->occur_begin );
	if ( $event_dt_a === $event_dt_b ) {
		// this should sub-sort by title if date is the same. But it doesn't seem to.
		$ta = $a->event_title;
		$tb = $b->event_title;

		return strcmp( $ta, $tb );
	}

	return ( $event_dt_a < $event_dt_b ) ? - 1 : 1;
}

/**
 * Compare two event dates with time precision
 *
 * @param object $a event object.
 * @param object $b event object.
 * @param int    $compare_date Alternate date to compare against.
 *
 * @return integer (ternary value)
 */
function mc_timediff_cmp( $a, $b, $compare_date = 0 ) {
	$compare    = ( $compare_date ) ? strtotime( $compare_date ) : 'NOW';
	$a          = $a . current_time( ' H:i:s' );
	$b          = $b . current_time( ' H:i:s' );
	$event_dt_a = strtotime( $a );
	$event_dt_b = strtotime( $b );
	$diff_a     = mc_date_diff_precise( $event_dt_a, $compare );
	$diff_b     = mc_date_diff_precise( $event_dt_b, $compare );

	if ( $diff_a === $diff_b ) {
		return 0;
	}

	return ( $diff_a < $diff_b ) ? - 1 : 1;
}

/**
 * Compare two dates for diff with high precision
 *
 * @param int        $start timestamp.
 * @param int|string $end timestamp or 'now'.
 *
 * @return int absolute value of time diff
 */
function mc_date_diff_precise( $start, $end = 'NOW' ) {
	if ( 'NOW' === $end ) {
		$end = strtotime( 'NOW' );
	}
	$sdate = $start;
	$edate = $end;

	$time = $edate - $sdate;

	return abs( $time );
}

/**
 * Get the week of the month a given date falls on.
 *
 * @param integer $date_of_event  A numbered day of the month. E.g., 9 or 24.
 *
 * @return integer $week_of_event The week of the month this date falls in, 0 - 4.
 */
function mc_week_of_month( $date_of_event ) {
	$week_of_event = 0;
	switch ( $date_of_event ) {
		case ( $date_of_event >= 1 && $date_of_event < 8 ):
			$week_of_event = 0;
			break;
		case ( $date_of_event >= 8 && $date_of_event < 15 ):
			$week_of_event = 1;
			break;
		case ( $date_of_event >= 15 && $date_of_event < 22 ):
			$week_of_event = 2;
			break;
		case ( $date_of_event >= 22 && $date_of_event < 29 ):
			$week_of_event = 3;
			break;
		case ( $date_of_event >= 29 ):
			$week_of_event = 4;
			break;
	}

	return $week_of_event;
}

/**
 * Validate that a string is a valid date. Returns a Y-m-d date string with no timezone offset applied.
 *
 * @param string $date date string.
 *
 * @return boolean|string date string if verified date, false if not.
 */
function mc_checkdate( $date ) {
	$time = strtotime( $date );
	$m    = mc_date( 'n', $time );
	$d    = mc_date( 'j', $time );
	$y    = mc_date( 'Y', $time );

	$check = checkdate( $m, $d, $y );
	if ( $check ) {
		return mc_date( 'Y-m-d', $time, false );
	}

	return false;
}

/**
 * Determine where a date lies in a month in terms of week/day.
 *
 * @param string $ts Timestamp.
 *
 * @return array
 */
function mc_recur_date( $ts ) {
	$ts      = is_numeric( $ts ) ? $ts : strtotime( $ts );
	$weekday = mc_date( 'l', $ts, false );
	$month   = mc_date( 'M', $ts, false );
	$ord     = 1;

	while ( mc_date( 'M', ( $ts = strtotime( '-1 week', $ts ) ), false ) === $month ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
		++$ord;
	}

	return array(
		'num' => $ord,
		'day' => $weekday,
	);
}

/**
 * Get the first day value of the current week.
 *
 * @param int|boolean $timestamp timestamp + offset or false if now.
 *
 * @return array day and integer representing the month offset. -1 for previous month, 0 for current month.
 */
function mc_first_day_of_week( $timestamp = false ) {
	$start_of_week = ( get_option( 'start_of_week' ) === '1' || get_option( 'start_of_week' ) === '0' ) ? absint( get_option( 'start_of_week' ) ) : 0;
	if ( $timestamp ) {
		$today = mc_date( 'w', $timestamp, false );
		$now   = mc_date( 'Y-m-d', $timestamp, false );
	} else {
		$today = current_time( 'w' );
		$now   = current_time( 'Y-m-d' );
	}
	$month = 0;
	$sub   = 0; // don't change month.
	switch ( $today ) {
		case 1:
			$sub = ( 1 === $start_of_week ) ? 0 : 1;
			break; // mon.
		case 2:
			$sub = ( 1 === $start_of_week ) ? 1 : 2;
			break; // tues.
		case 3:
			$sub = ( 1 === $start_of_week ) ? 2 : 3;
			break; // wed.
		case 4:
			$sub = ( 1 === $start_of_week ) ? 3 : 4;
			break; // thu.
		case 5:
			$sub = ( 1 === $start_of_week ) ? 4 : 5;
			break; // fri.
		case 6:
			$sub = ( 1 === $start_of_week ) ? 5 : 6;
			break; // sat.
		case 0:
			$sub = ( 1 === $start_of_week ) ? 6 : 0;
			break; // sun.
	}
	$day = mc_date( 'j', strtotime( $now . ' -' . $sub . ' day' ), false );
	if ( 0 !== $sub ) {
		if ( mc_date( 'n', strtotime( $now . ' -' . $sub . ' day' ), false ) !== mc_date( 'n', strtotime( $now ), false ) ) {
			$month = - 1;
		} else {
			$month = 0;
		}
	}

	return array( $day, $month );
}

/**
 * Generate an ordinal string in English for numeric values
 *
 * @param int $number Any integer value.
 *
 * @return string number plus ordinal value
 */
function mc_ordinal( $number ) {
	// when fed a number, adds the English ordinal suffix. Works for any number.
	if ( $number % 100 > 10 && $number % 100 < 14 ) {
		$suffix = 'th';
	} else {
		switch ( $number % 10 ) {
			case 0:
				$suffix = 'th';
				break;
			case 1:
				$suffix = 'st';
				break;
			case 2:
				$suffix = 'nd';
				break;
			case 3:
				$suffix = 'rd';
				break;
			default:
				$suffix = 'th';
				break;
		}
	}

	return apply_filters( 'mc_ordinal', $number . $suffix, $number, $suffix );
}

/**
 * Handles all cases for exiting processing early: private events, drafts, etc.
 *
 * @param object $event Event object.
 * @param string $process_date Current date being articulated.
 *
 * @return boolean true if early exit is qualified.
 */
function mc_exit_early( $event, $process_date ) {
	// if event is in trash, return without processing.
	if ( 2 === (int) $event->event_approved && ! mc_is_preview() ) {
		return true;
	}

	$hide_days_default = false;
	if ( 'true' === get_post_meta( $event->event_post, '_event_same_day', true ) ) {
		$hide_days_default = true;
	}
	/**
	 * Hide subsequent days of events crossing multiple days.
	 *
	 * @hook mc_hide_additional_days
	 *
	 * @param {bool}   $hide_days_default False if 'same day event' not checked.
	 * @param {object} $event Event object.
	 *
	 * @return {bool}
	 */
	$hide_days = apply_filters( 'mc_hide_additional_days', $hide_days_default, $event );
	$today     = mc_date( 'Y-m-d', strtotime( $event->occur_begin ), false );
	$current   = mc_date( 'Y-m-d', strtotime( $process_date ), false );
	$end       = mc_date( 'Y-m-d', strtotime( $event->occur_end ), false );
	// if event ends at midnight today (e.g., very first thing of the day), exit without re-drawing.
	// or if event started yesterday & has event_hide_end checked.
	$ends_at_midnight = ( '00:00:00' === $event->event_endtime && $end === $process_date && $current !== $today ) ? true : false;

	// hides events if hiding end time & not first day.
	$hide_day_two = ( $hide_days && ( $today !== $current ) ) ? true : false;

	if ( $ends_at_midnight || $hide_day_two ) {
		return true;
	}

	if ( mc_private_event( $event ) ) {
		return true;
	}

	return false;
}

/**
 * Check whether an object with a category_private property is private &or hidden.
 *
 * @param object $event [can be a category object].
 * @param bool   $type true to check whether an object is hidden; false to check the object configuration.
 *
 * @return boolean
 */
function mc_private_event( $event, $type = true ) {
	// If this is an invalid event, consider it private.
	if ( ! is_object( $event ) || ! property_exists( $event, 'category_private' ) ) {
		return true;
	}
	// This is a personal event, only visible to the author.
	if ( property_exists( $event, 'event_approved' ) && 5 === (int) $event->event_approved ) {
		$status = ( is_user_logged_in() && wp_get_current_user()->ID === $event->event_author ) ? false : true;
	}
	// If this event has the private state.
	if ( property_exists( $event, 'event_approved' ) && 4 === (int) $event->event_approved ) {
		$status = ( is_user_logged_in() ) ? false : true;
	} else {
		if ( $type ) {
			// Checking whether this should currently be hidden.
			$status = ( 1 === absint( $event->category_private ) && ! is_user_logged_in() ) ? true : false;
		} else {
			// Checking whether this is supposed to be private.
			$status = ( 1 === absint( $event->category_private ) ) ? true : false;
		}
	}

	/**
	 * Filter the privacy status of an event or category.
	 *
	 * @hook mc_private_event
	 *
	 * @param {bool}   $status true if an event is private, false if it is public.
	 * @param {object} $event A category or event object to test.
	 *
	 * @return {bool}
	 */
	$status = apply_filters( 'mc_private_event', $status, $event );

	return $status;
}

/**
 * Parse a string and replace internationalized months with English so strtotime() will parse correctly
 *
 * @param string $date Date information.
 *
 * @return int de-internationalized change
 */
function mc_strtotime( $date ) {
	$months  = array(
		date_i18n( 'F', strtotime( 'January 1' ) ),
		date_i18n( 'F', strtotime( 'February 1' ) ),
		date_i18n( 'F', strtotime( 'March 1' ) ),
		date_i18n( 'F', strtotime( 'April 1' ) ),
		date_i18n( 'F', strtotime( 'May 1' ) ),
		date_i18n( 'F', strtotime( 'June 1' ) ),
		date_i18n( 'F', strtotime( 'July 1' ) ),
		date_i18n( 'F', strtotime( 'August 1' ) ),
		date_i18n( 'F', strtotime( 'September 1' ) ),
		date_i18n( 'F', strtotime( 'October 1' ) ),
		date_i18n( 'F', strtotime( 'November 1' ) ),
		date_i18n( 'F', strtotime( 'December 1' ) ),
		date_i18n( 'M', strtotime( 'January 1' ) ),
		date_i18n( 'M', strtotime( 'February 1' ) ),
		date_i18n( 'M', strtotime( 'March 1' ) ),
		date_i18n( 'M', strtotime( 'April 1' ) ),
		date_i18n( 'M', strtotime( 'May 1' ) ),
		date_i18n( 'M', strtotime( 'June 1' ) ),
		date_i18n( 'M', strtotime( 'July 1' ) ),
		date_i18n( 'M', strtotime( 'August 1' ) ),
		date_i18n( 'M', strtotime( 'September 1' ) ),
		date_i18n( 'M', strtotime( 'October 1' ) ),
		date_i18n( 'M', strtotime( 'November 1' ) ),
		date_i18n( 'M', strtotime( 'December 1' ) ),
	);
	$english = array(
		'January',
		'February',
		'March',
		'April',
		'May',
		'June',
		'July',
		'August',
		'September',
		'October',
		'November',
		'December',
		'Jan',
		'Feb',
		'Mar',
		'Apr',
		'May',
		'Jun',
		'Jul',
		'Aug',
		'Sep',
		'Oct',
		'Nov',
		'Dec',
	);

	return strtotime( str_replace( $months, $english, $date ) );
}

/**
 * Wrapper for date(). Used for date comparisons and non-translatable dates.
 *
 * @param string    $format Default ''. Format to use. Empty string for timestamp.
 * @param int|false $timestamp Default false. Timestamp or false if current time..
 * @param bool      $offset Default true. False to not add offset; if already a timestamp.
 *
 * @return string|int Formatted date or timestamp if no format provided.
 */
function mc_date( $format = '', $timestamp = false, $offset = true ) {
	if ( ! $timestamp ) {
		// Timestamp is UTC.
		$timestamp = time();
	}
	if ( $offset ) {
		$offset = intval( get_option( 'gmt_offset', 0 ) ) * 60 * 60;
	} else {
		$offset = 0;
	}
	$timestamp = $timestamp + $offset;

	return ( '' === $format ) ? $timestamp : gmdate( $format, $timestamp );
}

/**
 * Get the days of the week for calendar layout.
 *
 * @param array $params Calendar parameters.
 *
 * @return array
 */
function mc_get_week_days( $params ) {
	$name_days = mc_name_days( $params['format'] );
	$abbrevs   = array( 'mon', 'tues', 'wed', 'thur', 'fri', 'sat', 'sun' );
	$return    = array(
		'name_days' => $name_days,
		'abbrevs'   => $abbrevs,
	);

	/**
	 * Filter the days of the week as used for column headings in grid format.
	 *
	 * @hook mc_get_week_days
	 * @since 3.4.0
	 *
	 * @param {array} $return Array of full names and abbreviations.
	 * @param {array} $params Array of parameters for this calendar view.
	 *
	 * @return {array}
	 */
	return apply_filters( 'mc_get_week_days', $return, $params );
}

/**
 * Generate abbreviations & code used for HTML output of calendar headings.
 *
 * @param string $format 'mini', 'list', 'grid'.
 *
 * @return array HTML for each day in an array.
 */
function mc_name_days( $format ) {
	$name_days = array(
		'<abbr title="' . date_i18n( 'l', strtotime( 'Monday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Monday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Monday' ) ) . '</span>',
		'<abbr title="' . date_i18n( 'l', strtotime( 'Tuesday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Tuesday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Tuesday' ) ) . '</span>',
		'<abbr title="' . date_i18n( 'l', strtotime( 'Wednesday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Wednesday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Wednesday' ) ) . '</span>',
		'<abbr title="' . date_i18n( 'l', strtotime( 'Thursday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Thursday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Thursday' ) ) . '</span>',
		'<abbr title="' . date_i18n( 'l', strtotime( 'Friday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Friday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Friday' ) ) . '</span>',
		'<abbr title="' . date_i18n( 'l', strtotime( 'Saturday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Saturday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Saturday' ) ) . '</span>',
		'<abbr title="' . date_i18n( 'l', strtotime( 'Sunday' ) ) . '" aria-hidden="true">' . date_i18n( 'D', strtotime( 'Sunday' ) ) . '</abbr><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Sunday' ) ) . '</span>',
	);
	if ( 'mini' === $format ) {
		// PHP doesn't have a single letter abbreviation, so this has to be a translatable.
		$name_days = array(
			'<span aria-hidden="true">' . __( '<abbr title="Monday">M</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Monday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
			'<span aria-hidden="true">' . __( '<abbr title="Tuesday">T</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Tuesday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
			'<span aria-hidden="true">' . __( '<abbr title="Wednesday">W</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Wednesday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
			'<span aria-hidden="true">' . __( '<abbr title="Thursday">T</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Thursday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
			'<span aria-hidden="true">' . __( '<abbr title="Friday">F</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Friday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
			'<span aria-hidden="true">' . __( '<abbr title="Saturday">S</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Saturday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
			'<span aria-hidden="true">' . __( '<abbr title="Sunday">S</abbr>', 'my-calendar' ) . '</span><span class="screen-reader-text">' . date_i18n( 'l', strtotime( 'Sunday' ) ) . '</span>', // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
		);
	}

	return $name_days;
}

/**
 * Calculate dates that should be used to set start and end dates for current view.
 *
 * @param string $timestamp Time stamp for first date of current period.
 * @param string $period base type of date span displayed.
 * @param int    $months Number of months to add to display.
 *
 * @return array from and to dates
 */
function mc_date_array( $timestamp, $period, $months = 0 ) {
	switch ( $period ) {
		case 'month':
		case 'month+1':
			if ( 'month+1' === $period ) {
				$timestamp = strtotime( '+1 month', $timestamp );
			}
			$start_of_week = get_option( 'start_of_week' );
			$first         = mc_date( 'N', $timestamp, false ); // ISO-8601.
			$sub           = mc_date( 'w', $timestamp, false ); // numeric (how WordPress option is stored).
			$n             = ( 1 === (int) $start_of_week ) ? $first - 1 : $first;

			if ( $sub === $start_of_week ) {
				$from = mc_date( 'Y-m-d', $timestamp, false );
			} else {
				$start = strtotime( "-$n days", $timestamp );
				$from  = mc_date( 'Y-m-d', $start, false );
			}
			$endtime = mktime( 0, 0, 0, mc_date( 'm', $timestamp, false ), mc_date( 't', $timestamp, false ), mc_date( 'Y', $timestamp, false ) );
			$endtime = strtotime( "+$months months", $endtime );
			$last    = (int) mc_date( 'N', $endtime, false );
			$n       = ( '1' === get_option( 'start_of_week' ) ) ? 7 - $last : 6 - $last;
			if ( -1 === $n && '7' === mc_date( 'N', $endtime, false ) ) {
				$n = 6;
			}
			$to = mc_date( 'Y-m-d', strtotime( "+$n days", $endtime ), false );

			$calculated_date = mc_date( 'j', strtotime( $to ), false );
			$last_of_month   = mc_date( 't', strtotime( $to ), false );
			$boundary        = (int) $last_of_month - 7;
			/**
			 * This looks magic, but it's logical. The problem here is that
			 * strtotime( "+ 1 month" ) doesn't always yield the next month.
			 * But it does always yield something close to it. If the date is
			 * more than length - 7, this is late in the month, and can be increased.
			 */
			if ( $calculated_date < $last_of_month && $calculated_date > $boundary ) {
				$n  = $n + 7;
				$to = mc_date( 'Y-m-d', strtotime( "+$n days", $endtime ), false );
			}

			$return = array(
				'from' => $from,
				'to'   => $to,
			);
			break;
		case 'week':
			$day_of_week   = (int) mc_date( 'N', $timestamp, false );
			$start_of_week = ( get_option( 'start_of_week' ) === '1' ) ? 1 : 7; // convert start of week to ISO 8601 (Monday/Sunday).
			if ( $day_of_week !== $start_of_week ) {
				if ( $start_of_week > $day_of_week ) {
					$diff = 7 - ( ( $start_of_week - $day_of_week ) );
				} else {
					$diff = ( $day_of_week - $start_of_week );
				}
				$timestamp = ( 7 !== $diff ) ? strtotime( "-$diff days", $timestamp ) : $timestamp;
			}
			$from = mc_date( 'Y-m-d', $timestamp, false );
			$to   = mc_date( 'Y-m-d', strtotime( '+6 days', $timestamp ), false );

			$return = array(
				'from' => $from,
				'to'   => $to,
			);
			break;
		default:
			$return = false;
	}

	return $return;
}

/**
 * Get from and to values for current view
 *
 * @param int   $show_months List view parameter.
 * @param array $params Calendar view parameters.
 * @param array $date Current date viewed.
 *
 * @return array from & to dates
 */
function mc_get_from_to( $show_months, $params, $date ) {
	$format = $params['format'];
	$time   = $params['time'];
	// The value is total months to show; need additional months to show.
	$num     = $show_months - 1;
	$c_month = (int) $date['month'];
	$c_year  = (int) $date['year'];
	// The first day of the current month.
	$month_first = mktime( 0, 0, 0, $c_month, 1, $c_year );

	if ( 'list' === $format && 'week' !== $time ) {
		if ( $num > 0 && 'day' !== $time && 'week' !== $time ) {
			if ( 'month+1' === $time ) {
				$from = mc_date( 'Y-m-d', strtotime( '+1 month', $month_first ), false );
				$next = strtotime( "+$num months", strtotime( '+1 month', $month_first ) );
			} else {
				$from = mc_date( 'Y-m-d', $month_first, false );
				$next = strtotime( "+$num months", $month_first );
			}
			$last = mc_date( 't', $next, false );
			$to   = mc_date( 'Y-m', $next, false ) . '-' . $last;
		} else {
			$from = mc_date( 'Y-m-d', $month_first, false );
			$to   = mc_date( 'Y-m-d', mktime( 0, 0, 0, $c_month, mc_date( 't', $month_first, false ), $c_year ), false );
		}
		$dates = array(
			'from' => $from,
			'to'   => $to,
		);
	} else {
		// Get a view based on current date.
		$dates = mc_date_array( $date['current_date'], $time, $num );
	}
	/**
	 * Filter the calendar start date.
	 *
	 * @hook mc_from_date
	 *
	 * @param {string} $from Start date of events shown in main calendar shortcode in format `yyyy-mm-dd`.
	 * @param {string} $to Ending date of current view in format `yyyy-mm-dd`.
	 * @param {array}  $params Calendar view parameters.
	 *
	 * @return {string}
	 */
	$dates['from'] = apply_filters( 'mc_from_date', $dates['from'], $dates['to'], $params );
	/**
	 * Filter the calendar end date.
	 *
	 * @hook mc_to_date
	 *
	 * @param {string} $to End date of events shown in main calendar shortcode in format `yyyy-mm-dd`.
	 * @param {string} $from Starting date of current view in format `yyyy-mm-dd`.
	 * @param {array}  $params Calendar view parameters.
	 *
	 * @return {string}
	 */
	$dates['to'] = apply_filters( 'mc_to_date', $dates['to'], $dates['from'], $params );

	return $dates;
}

/**
 * Test whether an event is in the past, currently happening, or in the future.
 *
 * @param object $event Event object.
 *
 * @return int
 */
function mc_date_relation( $event ) {
	$ts            = $event->ts_occur_begin;
	$end           = $event->ts_occur_end;
	$date_relation = 2;
	$now           = time();
	if ( $ts < $now && $end > $now ) {
		/**
		 * Execute action while an event is happening.
		 *
		 * @hook mc_event_happening
		 *
		 * @param {object} $object Event object.
		 */
		do_action( 'mc_event_happening', $event );
		$date_relation = 1;
	} elseif ( $now < $ts ) {
		/**
		 * Execute action before an event will occur.
		 *
		 * @hook mc_event_future
		 *
		 * @param {object} $object Event object.
		 */
		do_action( 'mc_event_future', $event );
		$date_relation = 2;
	} elseif ( $now > $end ) {
		/**
		 * Execute action after an event has occurred.
		 *
		 * @hook mc_event_over
		 *
		 * @param {object} $object Event object.
		 */
		do_action( 'mc_event_over', $event );
		$date_relation = 0;
	}

	return $date_relation;
}

/**
 * Get the date format for My Calendar primary views.
 *
 * @return string
 */
function mc_date_format() {
	$date_format = ( '' === mc_get_option( 'date_format' ) ) ? get_option( 'date_format' ) : mc_get_option( 'date_format' );

	return $date_format;
}

/**
 * Produce the human-readable string for recurrence.
 *
 * @param object $event Event object.
 * @param array  $args Recurrence settings.
 *
 * @return string Type of recurrence
 */
function mc_recur_string( $event, $args = array() ) {
	if ( ! $event ) {
		$recur = $args['recur'];
		$every = (int) $args['every'];
		$until = $args['until'];
	} else {
		$recurs = str_split( $event->event_recur, 1 );
		$recur  = $recurs[0];
		$every  = ( isset( $recurs[1] ) ) ? str_replace( $recurs[0], '', $event->event_recur ) : 1;
		$until  = $event->event_repeats;
	}
	$string = '';
	// Interpret the DB values into something human readable.
	switch ( $recur ) {
		case 'D':
			// Translators: number of days between repetitions.
			$string = ( 1 === (int) $every ) ? __( 'Daily', 'my-calendar' ) : sprintf( __( 'Every %s day', 'my-calendar' ), mc_ordinal( $every ) );
			break;
		case 'E':
			// Translators: number of days between repetitions.
			$string = ( 1 === (int) $every ) ? __( 'Weekdays', 'my-calendar' ) : sprintf( __( 'Every %s weekday', 'my-calendar' ), mc_ordinal( $every ) );
			break;
		case 'W':
			// Translators: number of weeks between repetitions.
			$string = ( 1 === (int) $every ) ? __( 'Weekly', 'my-calendar' ) : sprintf( __( 'Every %d weeks', 'my-calendar' ), $every );
			break;
		case 'B':
			$string = __( 'Bi-Weekly', 'my-calendar' );
			break;
		case 'M':
			// Translators: number of months between repetitions.
			$string = ( 1 === (int) $every ) ? __( 'Monthly (by date)', 'my-calendar' ) : sprintf( __( 'Every %d months (by date)', 'my-calendar' ), $every );
			break;
		case 'U':
			$string = __( 'Monthly (by day)', 'my-calendar' );
			break;
		case 'Y':
			// Translators: number of years between repetitions.
			$string = ( 1 === (int) $every ) ? __( 'Yearly', 'my-calendar' ) : sprintf( __( 'Every %d years', 'my-calendar' ), $every );
			break;
	}
	$eternity = _mc_increment_values( $recur );
	if ( $until && 'S' !== $recur ) {
		if ( is_numeric( $until ) ) {
			// Translators: number of repeats.
			$string .= ' ' . sprintf( __( '- %d Times', 'my-calendar' ), $until );
		} else {
			// Translators: date repeating until.
			$string .= ' ' . sprintf( __( ' until %s', 'my-calendar' ), $until );
		}
	} elseif ( $eternity ) {
		// Translators: number of repeats.
		$string .= ' ' . sprintf( __( '- %d Times', 'my-calendar' ), $eternity );
	}

	return $string;
}
