/** * Finds full months between two dates and the remaining seconds after the end of the last full month. Takes into account * leap years and variable number of days in months. Uses wcs_add_months * * @param numeric $start_timestamp unix timestamp of a start date * @param numeric $end_timestamp unix timestamp of an end date * @return array with keys 'months' (integer) and 'remainder' (seconds, integer) */ function wcs_find_full_months_between($start_timestamp, $end_timestamp, $interval = 1) { $number_of_months = 0; $remainder = 0; $previous_remainder = 0; $months_in_period = 0; $remainder_in_period = 0; while (0 <= $remainder) { $previous_timestamp = $start_timestamp; $start_timestamp = wcs_add_months($start_timestamp, 1); $previous_remainder = $remainder; $remainder = $end_timestamp - $start_timestamp; $remainder_in_period += $start_timestamp - $previous_timestamp; if ($remainder >= 0) { $number_of_months++; $months_in_period++; } elseif (0 === $previous_remainder) { $previous_remainder = $end_timestamp - $previous_timestamp; } if ($months_in_period >= $interval) { $months_in_period = 0; $remainder_in_period = 0; } } $remainder_in_period += $remainder; $time_difference = array('months' => $number_of_months, 'remainder' => $remainder_in_period); return $time_difference; }
/** * Finds full months between two dates and the remaining seconds after the end of the last full month. Takes into account * leap years and variable number of days in months. Uses wcs_add_months * * @param numeric $start_timestamp unix timestamp of a start date * @param numeric $end_timestamp unix timestamp of an end date * @return array with keys 'months' (integer) and 'remainder' (seconds, integer) */ function wcs_find_full_months_between($start_timestamp, $end_timestamp) { $number_of_months = 0; $remainder = null; $previous_remainder = null; while (0 <= $remainder) { $previous_timestamp = $start_timestamp; $start_timestamp = wcs_add_months($start_timestamp, 1); $previous_remainder = $remainder; $remainder = $end_timestamp - $start_timestamp; if ($remainder >= 0) { $number_of_months++; } elseif (null === $previous_remainder) { $previous_remainder = $end_timestamp - $previous_timestamp; } } $time_difference = array('months' => $number_of_months, 'remainder' => $previous_remainder); return $time_difference; }
/** * Workaround the last day of month quirk in PHP's strtotime function. * * @since 1.2.5 * @deprecated 2.0 */ public static function add_months($from_timestamp, $months_to_add) { _deprecated_function(__METHOD__, '2.0', 'wcs_add_months()'); return wcs_add_months($from_timestamp, $months_to_add); }
/** * Calculate the first payment date for a synced subscription. * * The date is calculated in UTC timezone. * * @param WC_Product $product A subscription product. * @param string $type (optional) The format to return the first payment date in, either 'mysql' or 'timestamp'. Default 'mysql'. * @param string $from_date (optional) The date to calculate the first payment from in GMT/UTC timzeone. If not set, it will use the current date. This should not include any trial period on the product. * @since 1.5 */ public static function calculate_first_payment_date($product, $type = 'mysql', $from_date = '') { if (!is_object($product)) { $product = WC_Subscriptions::get_product($product); } if (!self::is_product_synced($product)) { return 0; } $period = WC_Subscriptions_Product::get_period($product); $trial_period = WC_Subscriptions_Product::get_trial_period($product); $trial_length = WC_Subscriptions_Product::get_trial_length($product); $from_date_param = $from_date; if (empty($from_date)) { $from_date = gmdate('Y-m-d H:i:s'); } // If the subscription has a free trial period, the first payment should be synced to a day after the free trial if ($trial_length > 0) { $from_date = WC_Subscriptions_Product::get_trial_expiration_date($product, $from_date); } $from_timestamp = strtotime($from_date) + get_option('gmt_offset') * 3600; // Site time $payment_day = self::get_products_payment_day($product); if ('week' == $period) { // strtotime() will figure out if the day is in the future or today (see: https://gist.github.com/thenbrent/9698083) $first_payment_timestamp = strtotime(self::$weekdays[$payment_day], $from_timestamp); } elseif ('month' == $period) { // strtotime() needs to know the month, so we need to determine if the specified day has occured this month yet or if we want the last day of the month (see: https://gist.github.com/thenbrent/9698083) if ($payment_day > 27) { // we actually want the last day of the month $payment_day = gmdate('t', $from_timestamp); $month = gmdate('F', $from_timestamp); } elseif (gmdate('j', $from_timestamp) > $payment_day) { // today is later than specified day in the from date, we need the next month $month = date('F', wcs_add_months($from_timestamp, 1)); } else { // specified day is either today or still to come in the month of the from date $month = gmdate('F', $from_timestamp); } $first_payment_timestamp = strtotime("{$payment_day} {$month}", $from_timestamp); } elseif ('year' == $period) { // We can't use $wp_locale here because it is translated switch ($payment_day['month']) { case 1: $month = 'January'; break; case 2: $month = 'February'; break; case 3: $month = 'March'; break; case 4: $month = 'April'; break; case 5: $month = 'May'; break; case 6: $month = 'June'; break; case 7: $month = 'July'; break; case 8: $month = 'August'; break; case 9: $month = 'September'; break; case 10: $month = 'October'; break; case 11: $month = 'November'; break; case 12: $month = 'December'; break; } $first_payment_timestamp = strtotime("{$payment_day['day']} {$month}", $from_timestamp); } // Make sure the next payment is in the future and after the $from_date, as strtotime() will return the date this year for any day in the past when adding months or years (see: https://gist.github.com/thenbrent/9698083) if ('year' == $period || 'month' == $period) { // First make sure the day is in the past so that we don't end up jumping a month or year because of a few hours difference between now and the billing date if (gmdate('Ymd', $first_payment_timestamp) < gmdate('Ymd', $from_timestamp) || gmdate('Ymd', $first_payment_timestamp) < gmdate('Ymd')) { $i = 1; // Then make sure the date and time of the payment is in the future while (($first_payment_timestamp < gmdate('U') || $first_payment_timestamp < $from_timestamp) && $i < 30) { $first_payment_timestamp = strtotime("+ 1 {$period}", $first_payment_timestamp); $i = $i + 1; } } } // We calculated a timestamp for midnight on the specific day in the site's timezone, let's push it to 3am to account for any daylight savings changes $first_payment_timestamp += 3 * HOUR_IN_SECONDS; // And convert it to the UTC equivalent of 3am on that day $first_payment_timestamp -= get_option('gmt_offset') * HOUR_IN_SECONDS; $first_payment = 'mysql' == $type && 0 != $first_payment_timestamp ? date('Y-m-d H:i:s', $first_payment_timestamp) : $first_payment_timestamp; return apply_filters('woocommerce_subscriptions_synced_first_payment_date', $first_payment, $product, $type, $from_date, $from_date_param); }
/** * Takes a subscription product's ID and returns the date on which the subscription trial will expire, * based on the subscription's trial length and calculated from either the $from_date if specified, * or the current date/time. * * @param int $product_id The product/post ID of the subscription * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date (in UTC timezone), or empty (default), which will use today's date/time (in UTC timezone). * @since 1.0 */ public static function get_trial_expiration_date($product_id, $from_date = '') { $trial_period = self::get_trial_period($product_id); $trial_length = self::get_trial_length($product_id); if ($trial_length > 0) { if (empty($from_date)) { $from_date = gmdate('Y-m-d H:i:s'); } if ('month' == $trial_period) { $trial_expiration_date = date('Y-m-d H:i:s', wcs_add_months(strtotime($from_date), $trial_length)); } else { // Safe to just add the billing periods $trial_expiration_date = date('Y-m-d H:i:s', strtotime("+ {$trial_length} {$trial_period}s", strtotime($from_date))); } } else { $trial_expiration_date = 0; } return apply_filters('woocommerce_subscriptions_product_trial_expiration_date', $trial_expiration_date, $product_id, $from_date); }