/**
  * Takes a subscription key and calculates the date on which the subscription is scheduled to expire
  * or 0 if it will never expire.
  *
  * @param $subscription_key string A subscription key of the form created by @see self::get_subscription_key()
  * @param $user_id int The ID of the user who owns the subscriptions. Although this parameter is optional, if you have the User ID you should pass it to improve performance.
  * @param $type string (optional) The format for the Either 'mysql' or 'timestamp'.
  * @since 1.1
  */
 public static function calculate_trial_expiration_date($subscription_key, $user_id = '', $type = 'mysql')
 {
     $subscription = self::get_subscription($subscription_key, $user_id);
     if (empty($subscription)) {
         $expiration_date = 0;
     } else {
         $order = new WC_Order($subscription['order_id']);
         $subscription_trial_length = WC_Subscriptions_Order::get_subscription_trial_length($order, $subscription['product_id']);
         $subscription_trial_period = WC_Subscriptions_Order::get_subscription_trial_period($order, $subscription['product_id']);
         if ($subscription_trial_length > 0) {
             if (isset($subscription['start_date']) && !empty($subscription['start_date'])) {
                 $start_date = $subscription['start_date'];
             } elseif (!empty($order->order_date)) {
                 $start_date = get_gmt_from_date($order->order_date);
             } else {
                 $start_date = gmdate('Y-m-d H:i:s');
             }
             if ('month' == $subscription_trial_period) {
                 $expiration_date = WC_Subscriptions::add_months(strtotime($start_date), $subscription_trial_length);
             } else {
                 // Safe to just add the billing periods
                 $expiration_date = strtotime("+ {$subscription_trial_length} {$subscription_trial_period}", strtotime($start_date));
             }
         } else {
             $expiration_date = 0;
         }
     }
     $expiration_date = 'mysql' == $type && 0 != $expiration_date ? date('Y-m-d H:i:s', $expiration_date) : $expiration_date;
     return apply_filters('woocommerce_subscription_calculated_trial_expiration_date', $expiration_date, $subscription_key, $user_id);
 }
 /**
  * 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() only handles English, so can't use $wp_locale->weekday here
         $weekdays = array(1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday');
         // strtotime() will figure out if the day is in the future or today (see: https://gist.github.com/thenbrent/9698083)
         $first_payment_timestamp = strtotime($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', WC_Subscriptions::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('j', $first_payment_timestamp) < gmdate('j') && gmdate('n', $first_payment_timestamp) <= gmdate('n') && gmdate('Y', $first_payment_timestamp) <= gmdate('Y')) {
             $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', WC_Subscriptions::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);
 }
 /**
  * Takes a subscription product's ID and calculates the date on which the next payment is due.
  *
  * Calculation is based on $from_date if specified, otherwise it will fall back to the last
  * completed payment, the subscription's start time, or the current date/time, in that order.
  *
  * The next payment date will occur after any free trial period and up to any expiration date.
  *
  * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in.
  * @param int $product_id The product/post ID of the subscription
  * @param string $type (optional) The format for the Either 'mysql' or 'timestamp'.
  * @param mixed $from_date A MySQL formatted date/time string from which to calculate the next payment date, or empty (default), which will use the last payment on the subscription, or today's date/time if no previous payments have been made.
  * @return mixed If there is no future payment set, returns 0, otherwise it will return a date of the next payment in the form specified by $type
  * @since 1.0
  */
 public static function calculate_next_payment_date($order, $product_id, $type = 'mysql', $from_date = '')
 {
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     $from_date_arg = $from_date;
     $subscription = WC_Subscriptions_Manager::get_subscription(WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id));
     $subscription_period = self::get_subscription_period($order, $product_id);
     $subscription_interval = self::get_subscription_interval($order, $product_id);
     $subscription_trial_length = self::get_subscription_trial_length($order, $product_id);
     $subscription_trial_period = self::get_subscription_trial_period($order, $product_id);
     $subscription_length = self::get_subscription_length($order, $product_id);
     $trial_end_time = !empty($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, get_gmt_from_date($order->order_date));
     $trial_end_time = strtotime($trial_end_time);
     // If the subscription is not active, there is no next payment date
     if ($subscription['status'] != 'active' || $subscription_interval == $subscription_length) {
         $next_payment_timestamp = 0;
         // If the subscription has a free trial period, and we're still in the free trial period, the next payment is due at the end of the free trial
     } elseif ($subscription_trial_length > 0 && $trial_end_time > gmdate('U') + 60 * 60 * 23 + 120) {
         // Make sure trial expiry is more than 23+ hours in the future to account for trial expiration dates incorrectly stored in non-UTC/GMT timezone and also for any potential changes to the site's timezone
         $next_payment_timestamp = $trial_end_time;
         // The next payment date is {interval} billing periods from the from date
     } else {
         // We have a timestamp
         if (!empty($from_date) && is_numeric($from_date)) {
             $from_date = date('Y-m-d H:i:s', $from_date);
         }
         if (empty($from_date)) {
             if (!empty($subscription['completed_payments'])) {
                 $from_date = array_pop($subscription['completed_payments']);
                 $add_failed_payments = true;
             } else {
                 if (!empty($subscription['start_date'])) {
                     $from_date = $subscription['start_date'];
                     $add_failed_payments = true;
                 } else {
                     $from_date = gmdate('Y-m-d H:i:s');
                     $add_failed_payments = false;
                 }
             }
             $failed_payment_count = self::get_failed_payment_count($order, $product_id);
             // Maybe take into account any failed payments
             if (true === $add_failed_payments && $failed_payment_count > 0) {
                 $failed_payment_periods = $failed_payment_count * $subscription_interval;
                 $from_timestamp = strtotime($from_date);
                 if ('month' == $subscription_period) {
                     $from_date = date('Y-m-d H:i:s', WC_Subscriptions::add_months($from_timestamp, $failed_payment_periods));
                 } else {
                     // Safe to just add the billing periods
                     $from_date = date('Y-m-d H:i:s', strtotime("+ {$failed_payment_periods} {$subscription_period}", $from_timestamp));
                 }
             }
         }
         $from_timestamp = strtotime($from_date);
         if ('month' == $subscription_period) {
             // Workaround potential PHP issue
             $next_payment_timestamp = WC_Subscriptions::add_months($from_timestamp, $subscription_interval);
         } else {
             $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $from_timestamp);
         }
         // Make sure the next payment is in the future
         $i = 1;
         while ($next_payment_timestamp < gmdate('U') && $i < 30) {
             if ('month' == $subscription_period) {
                 $next_payment_timestamp = WC_Subscriptions::add_months($next_payment_timestamp, $subscription_interval);
             } else {
                 // Safe to just add the billing periods
                 $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $next_payment_timestamp);
             }
             $i = $i + 1;
         }
     }
     // If the subscription has an expiry date and the next billing period comes after the expiration, return 0
     if (isset($subscription['expiry_date']) && 0 != $subscription['expiry_date'] && $next_payment_timestamp + 120 > strtotime($subscription['expiry_date'])) {
         $next_payment_timestamp = 0;
     }
     $next_payment = 'mysql' == $type && 0 != $next_payment_timestamp ? date('Y-m-d H:i:s', $next_payment_timestamp) : $next_payment_timestamp;
     return apply_filters('woocommerce_subscriptions_calculated_next_payment_date', $next_payment, $order, $product_id, $type, $from_date, $from_date_arg);
 }