Override the DrupalBase getUserObject function with a Drupal8/9 compatible version
[civicrm-core.git] / CRM / Utils / Time.php
index dd4b4e2cca6100321cfc29eeb81d8eda11617a31..dd56d6b9e422125c1c3832cbbf6b521e6b3c87b5 100644 (file)
 class CRM_Utils_Time {
 
   /**
-   * @var int
-   *   the seconds offset from the real world time
+   * A function which determines the current time.
+   * Only used during testing (with mocked time).
+   *
+   * The normal value, NULL, indicates the use of real time.
+   *
+   * @var callable|null
+   */
+  static private $callback = NULL;
+
+  /**
+   * Evaluate a time expression (relative to current time).
+   *
+   * @param string $str
+   *   Ex: '2001-02-03 04:05:06' or '+2 days'
+   * @param string|int $now
+   *   For relative time strings, $now determines the base time.
+   * @return false|int
+   *   The indicated time (seconds since epoch)
+   * @see strtotime()
+   */
+  public static function strtotime($str, $now = 'time()') {
+    if ($now === NULL || $now === 'time()') {
+      $now = self::time();
+    }
+    return strtotime($str, $now);
+  }
+
+  /**
+   * Format a date/time expression.
+   *
+   * @param string $format
+   *   Ex: 'Y-m-d H:i:s'
+   * @param null|int $timestamp
+   *   The time (seconds since epoch). NULL will use current time.
+   * @return string
+   *   Ex: '2001-02-03 04:05:06'
+   * @see date()
+   */
+  public static function date($format, $timestamp = NULL) {
+    return date($format, $timestamp ?: self::time());
+  }
+
+  /**
+   * Get the time.
+   *
+   * @return int
+   *   seconds since epoch
+   * @see time()
    */
-  static private $_delta = 0;
+  public static function time() {
+    return self::$callback === NULL ? time() : call_user_func(self::$callback);
+  }
 
   /**
    * Get the time.
@@ -33,9 +81,11 @@ class CRM_Utils_Time {
    *   Format in which date is to be retrieved.
    *
    * @return string
+   * @deprecated
+   *   Prefer CRM_Utils_Time::date(), whose name looks similar to the stdlib work-a-like.
    */
   public static function getTime($returnFormat = 'YmdHis') {
-    return date($returnFormat, self::getTimeRaw());
+    return date($returnFormat, self::time());
   }
 
   /**
@@ -43,9 +93,11 @@ class CRM_Utils_Time {
    *
    * @return int
    *   seconds since epoch
+   * @deprecated
+   *   Prefer CRM_Utils_Time::time(), whose name looks similar to the stdlib work-a-like.
    */
   public static function getTimeRaw() {
-    return time() + self::$_delta;
+    return self::time();
   }
 
   /**
@@ -56,10 +108,61 @@ class CRM_Utils_Time {
    * @param string $returnFormat
    *   Format in which date is to be retrieved.
    *
+   * Note: The progression of time will be influenced by TIME_FUNC, which may be:
+   *   - 'frozen' (time does not move)
+   *   - 'natural' (time moves naturally)
+   *   - 'linear:XXX' (time moves in increments of XXX milliseconds - with every lookup)
+   *   - 'prng:XXX' (time moves by random increments, between 0 and XXX milliseconds)
    * @return string
    */
   public static function setTime($newDateTime, $returnFormat = 'YmdHis') {
-    self::$_delta = strtotime($newDateTime) - time();
+    $mode = getenv('TIME_FUNC') ? getenv('TIME_FUNC') : 'natural';
+
+    list ($modeName, $modeNum) = explode(":", "$mode:");
+
+    switch ($modeName) {
+      case 'frozen':
+        // Every getTime() will produce the same value (ie $newDateTime).
+        $now = strtotime($newDateTime);
+        self::$callback = function () use ($now) {
+          return $now;
+        };
+        break;
+
+      case 'natural':
+        // Time changes to $newDateTime and then proceeds naturally.
+        $delta = strtotime($newDateTime) - time();
+        self::$callback = function () use ($delta) {
+          return time() + $delta;
+        };
+        break;
+
+      case 'linear':
+        // Time changes to $newDateTime and then proceeds in fixed increments ($modeNum milliseconds).
+        $incr = ($modeNum / 1000.0);
+        $now = (float) strtotime($newDateTime) - $incr;
+        self::$callback = function () use (&$now, $incr) {
+          $now += $incr;
+          return floor($now);
+        };
+        break;
+
+      case 'prng':
+        // Time changes to $newDateTime and then proceeds using deterministic pseudorandom increments (of up to $modeNum milliseconds).
+        $seed = md5($newDateTime . chr(0) . $mode, TRUE);
+        $now = (float) strtotime($newDateTime);
+        self::$callback = function () use (&$seed, &$now, $modeNum) {
+          $mod = gmp_strval(gmp_mod(gmp_import($seed), "$modeNum"));
+          $seed = md5($seed . $now, TRUE);
+          $now = $now + ($mod / 1000.0);
+          return floor($now);
+        };
+        break;
+
+      default:
+        throw new \RuntimeException("Unrecognized TIME_FUNC ($mode)");
+    }
+
     return self::getTime($returnFormat);
   }
 
@@ -67,7 +170,7 @@ class CRM_Utils_Time {
    * Remove any time overrides.
    */
   public static function resetTime() {
-    self::$_delta = 0;
+    self::$callback = NULL;
   }
 
   /**