Commit 566b5805586 for woocommerce
commit 566b58055869f7fe2c8949fb8d0beffcc6eb47e8
Author: Cvetan Cvetanov <cvetan.cvetanov@automattic.com>
Date: Fri Aug 21 18:13:02 2026 +0300
Fix get_timeframe_dates() using UTC instead of the site timezone (#67827)
* Fix get_timeframe_dates() defaulting to server timezone
The default reference date used new \DateTime(), which runs in the PHP
default timezone (UTC under WordPress) rather than the site timezone.
The returned period strings are later parsed as site-local datetimes in
Reports\DataStore::normalize_timezones(), so near a period boundary the
no-argument call could select the wrong week, month, quarter, half year
or year on sites whose timezone differs from UTC. The only production
caller affected is the total_payments_volume remote spec rule processor.
Anchor the default to new \DateTime( 'now', wp_timezone() ) so the
reference date matches the timezone the output is interpreted in, and
add a regression test pinning the contract.
* Add changelog entry for timeframe timezone fix
* Document get_timeframe_dates() reference date contract
The period is selected from the calendar date of $current_date in its
own timezone, and the object is modified in place by the modify() calls.
Neither fact was documented, so external callers passing a UTC-anchored
DateTime could silently reproduce the wrong-period bug this branch
fixes. Also correct the $timeframe param type: it is a string, not int.
* Add deterministic local-calendar test for timeframe dates
The site-timezone default test compares against a site-local-now call,
so with a real clock it only discriminates near period boundaries. Add
an explicit-argument case whose local calendar date differs from its
UTC date, deterministically pinning that the reference date's local
calendar selects the period, and document the boundary-window scope of
the default-path assertion.
* Fix DateTime docblock types resolving to namespace-relative classes
DateTimeProviderInterface::get_now(), CurrentDateTimeProvider::get_now()
and the get_timeframe_dates() $current_date param documented the global
DateTime class without a leading backslash, so PHPStan resolved it as a
nonexistent class in each file's namespace. This hid type errors behind
baseline entries and broke analysis of new call sites. Remove the four
baseline entries the fix resolves and shrink the format() pattern count
accordingly.
* Move timeframe regression tests out of the frozen legacy suite
tests/README.md forbids adding tests under tests/legacy/unit-tests; new
coverage for src classes belongs in tests/php/src. Relocate the two
timeframe tests added on this branch to a new TimeIntervalTest and
restore the legacy file to its pre-branch state.
* Inject a mockable clock into TotalPaymentsVolumeProcessor
The processor relied on get_timeframe_dates()'s implicit default for
the reference date, which cannot be frozen in tests: with a real clock,
a UTC-vs-site-timezone regression is only observable near period
boundaries, so the regression test guarding the timezone fix stayed
green on most days even against broken code.
Accept an optional DateTimeProviderInterface (the pattern the
PublishBefore/After processors already use) and resolve the timeframe
from the provider's now converted to the site timezone. A new test
freezes the clock at a past instant where the UTC and site-local dates
fall in different months and asserts the queried revenue bounds, making
the production path deterministically covered year-round.
* Remove stray blank lines left by baseline entry removal
* Harden TotalPaymentsVolumeProcessor against provider-owned state
The timeframe calculations modify the reference date in place, and the
processor handed them the exact instance the provider returned. The
interface does not promise a fresh object per call: the legacy
MockDateTimeProvider caches one instance, so repeated evaluations would
silently drift one period back per call. Resolve the timeframe on a
fresh DateTime built from the provider's timestamp instead.
Also fall back to CurrentDateTimeProvider inside process() so an
out-of-tree subclass whose constructor skips parent::__construct()
(valid before this class gained a constructor) keeps working instead of
fataling on a null provider. The test provider now caches its instance
like the legacy mock and process() runs twice, pinning both behaviors.
* Collapse the remaining multi-blank run in the PHPStan baseline
* Remove baseline entry fully resolved by the DateTime docblock fix
All four modify() calls the entry matched sit inside
get_timeframe_dates(), whose $current_date param is now typed
\DateTime, so the pattern matches nothing and the full PHPStan run
fails on the unmatched ignore. Verified with the CI-identical
whole-repo analyse run.
* Document the false return of get_timeframe_dates() and guard the caller
The docblock declared array while the method returns false for an
unknown timeframe. Correcting it surfaced that the only production
caller read offsets straight off a potential false, so process() now
fails the rule early instead, with a test covering the path. The
resolved return-type baseline entry is removed, validated with the
CI-identical whole-repo PHPStan run.
* refactor(remote-specs): drop the redundant DateTime provider fallback
process() re-guarded the provider with `?? new CurrentDateTimeProvider()`,
covering the case where a subclass constructor skips parent::__construct()
and leaves the property null.
Nothing in the tree hits that path: there are no subclasses of this
processor, and the legacy mocks all run the real constructor (none call
disableOriginalConstructor()). Both sibling processors,
PublishAfterTimeRuleProcessor and PublishBeforeTimeRuleProcessor, carry the
same theoretical exposure and dereference the provider directly.
Call the provider directly and narrow the property docblock back to
DateTimeProviderInterface, matching what the constructor already guarantees
and how the siblings are written.
* test: restore timezone_string and assert identity in the timezone tests
Both new tests set the timezone_string option and left it set. The
transaction rollback in tear_down() means nothing actually leaks, but every
other test in tests/php/ that touches the option captures and restores it
(class-wc-admin-post-types-test.php, class-wc-rest-report-sales-controller-tests.php,
FulfillmentsDataStoreTest.php, OrdersTableDataStoreTests.php).
Wrap both in try/finally so the restore also runs when an assertion fails,
and switch the four assertEquals calls in TimeIntervalTest to assertSame:
the values compared are date strings, and the processor test already asserts
identity throughout.
diff --git a/plugins/woocommerce/changelog/fix-timeframe-dates-site-timezone b/plugins/woocommerce/changelog/fix-timeframe-dates-site-timezone
new file mode 100644
index 00000000000..61f73335718
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-timeframe-dates-site-timezone
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Use the site timezone instead of the server timezone for the default reference date in TimeInterval::get_timeframe_dates(), so remote spec timeframe rules select the correct period near period boundaries.
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 1425b170337..87e42963f50 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -44256,7 +44256,7 @@ parameters:
-
message: '#^Call to method format\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\DateTime\.$#'
identifier: class.notFound
- count: 41
+ count: 35
path: src/Admin/API/Reports/TimeInterval.php
-
@@ -44265,12 +44265,6 @@ parameters:
count: 1
path: src/Admin/API/Reports/TimeInterval.php
- -
- message: '#^Call to method modify\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\DateTime\.$#'
- identifier: class.notFound
- count: 4
- path: src/Admin/API/Reports/TimeInterval.php
-
-
message: '#^Call to method setTime\(\) on an unknown class Automattic\\WooCommerce\\Admin\\API\\Reports\\DateTime\.$#'
identifier: class.notFound
@@ -44343,12 +44337,6 @@ parameters:
count: 1
path: src/Admin/API/Reports/TimeInterval.php
- -
- message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\TimeInterval\:\:get_timeframe_dates\(\) should return array but returns false\.$#'
- identifier: return.type
- count: 1
- path: src/Admin/API/Reports/TimeInterval.php
-
-
message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\TimeInterval\:\:iterate\(\) has invalid return type Automattic\\WooCommerce\\Admin\\API\\Reports\\DateTime\.$#'
identifier: class.notFound
@@ -44469,12 +44457,6 @@ parameters:
count: 2
path: src/Admin/API/Reports/TimeInterval.php
- -
- message: '#^Parameter \$current_date of method Automattic\\WooCommerce\\Admin\\API\\Reports\\TimeInterval\:\:get_timeframe_dates\(\) has invalid type Automattic\\WooCommerce\\Admin\\API\\Reports\\DateTime\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/API/Reports/TimeInterval.php
-
-
message: '#^Parameter \$datetime of method Automattic\\WooCommerce\\Admin\\API\\Reports\\TimeInterval\:\:iterate\(\) has invalid type Automattic\\WooCommerce\\Admin\\API\\Reports\\DateTime\.$#'
identifier: class.notFound
@@ -45279,24 +45261,6 @@ parameters:
count: 1
path: src/Admin/DataSourcePoller.php
- -
- message: '#^Method Automattic\\WooCommerce\\Admin\\DateTimeProvider\\CurrentDateTimeProvider\:\:get_now\(\) has invalid return type Automattic\\WooCommerce\\Admin\\DateTimeProvider\\DateTime\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/DateTimeProvider/CurrentDateTimeProvider.php
-
- -
- message: '#^Method Automattic\\WooCommerce\\Admin\\DateTimeProvider\\CurrentDateTimeProvider\:\:get_now\(\) should return Automattic\\WooCommerce\\Admin\\DateTimeProvider\\DateTime but returns DateTime\.$#'
- identifier: return.type
- count: 1
- path: src/Admin/DateTimeProvider/CurrentDateTimeProvider.php
-
- -
- message: '#^Method Automattic\\WooCommerce\\Admin\\DateTimeProvider\\DateTimeProviderInterface\:\:get_now\(\) has invalid return type Automattic\\WooCommerce\\Admin\\DateTimeProvider\\DateTime\.$#'
- identifier: class.notFound
- count: 1
- path: src/Admin/DateTimeProvider/DateTimeProviderInterface.php
-
-
message: '#^Method Automattic\\WooCommerce\\Admin\\DeprecatedClassFacade\:\:__call\(\) has no return type specified\.$#'
identifier: missingType.return
diff --git a/plugins/woocommerce/src/Admin/API/Reports/TimeInterval.php b/plugins/woocommerce/src/Admin/API/Reports/TimeInterval.php
index 447e6d3d148..641a2875ad0 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/TimeInterval.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/TimeInterval.php
@@ -637,13 +637,21 @@ class TimeInterval {
/**
* Get dates from a timeframe string.
*
- * @param int $timeframe Timeframe to use. One of: last_week|last_month|last_quarter|last_6_months|last_year.
- * @param DateTime|null $current_date DateTime of current date to compare.
- * @return array
+ * The period is selected from the calendar date of $current_date in its own timezone,
+ * so the argument should be anchored to the site timezone: the returned strings are
+ * naive datetimes that downstream consumers parse as site-local. When omitted, it
+ * defaults to the current time in the site timezone. Note that the passed object is
+ * modified in place by the date calculations.
+ *
+ * @param string $timeframe Timeframe to use. One of: last_week|last_month|last_quarter|last_6_months|last_year.
+ * @param \DateTime|null $current_date DateTime of current date to compare.
+ * @return array|false Array of start and end dates, or false for an unknown timeframe.
*/
public static function get_timeframe_dates( $timeframe, $current_date = null ) {
if ( ! $current_date ) {
- $current_date = new \DateTime();
+ // The returned date strings are interpreted in the site timezone (see DataStore::normalize_timezones()),
+ // so the reference "now" must use the site timezone too, not the PHP default (UTC in WordPress).
+ $current_date = new \DateTime( 'now', wp_timezone() );
}
$current_year = $current_date->format( 'Y' );
$current_month = $current_date->format( 'm' );
diff --git a/plugins/woocommerce/src/Admin/DateTimeProvider/CurrentDateTimeProvider.php b/plugins/woocommerce/src/Admin/DateTimeProvider/CurrentDateTimeProvider.php
index a7f314a5ea3..1a9001132e6 100644
--- a/plugins/woocommerce/src/Admin/DateTimeProvider/CurrentDateTimeProvider.php
+++ b/plugins/woocommerce/src/Admin/DateTimeProvider/CurrentDateTimeProvider.php
@@ -18,7 +18,7 @@ class CurrentDateTimeProvider implements DateTimeProviderInterface {
/**
* Returns the current DateTime.
*
- * @return DateTime
+ * @return \DateTime
*/
public function get_now() {
return new \DateTime();
diff --git a/plugins/woocommerce/src/Admin/DateTimeProvider/DateTimeProviderInterface.php b/plugins/woocommerce/src/Admin/DateTimeProvider/DateTimeProviderInterface.php
index 4bf1db3b231..279ed9152b5 100644
--- a/plugins/woocommerce/src/Admin/DateTimeProvider/DateTimeProviderInterface.php
+++ b/plugins/woocommerce/src/Admin/DateTimeProvider/DateTimeProviderInterface.php
@@ -15,7 +15,7 @@ interface DateTimeProviderInterface {
/**
* Returns the current DateTime.
*
- * @return DateTime
+ * @return \DateTime
*/
public function get_now();
}
diff --git a/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessor.php b/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessor.php
index e2442822f5e..c76747751e4 100644
--- a/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessor.php
+++ b/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessor.php
@@ -11,11 +11,31 @@ defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\Revenue\Query as RevenueQuery;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
+use Automattic\WooCommerce\Admin\DateTimeProvider\CurrentDateTimeProvider;
+use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface;
/**
* Rule processor that passes when a store's payments volume exceeds a provided amount.
*/
class TotalPaymentsVolumeProcessor implements RuleProcessorInterface {
+ /**
+ * The DateTime provider.
+ *
+ * @var DateTimeProviderInterface
+ */
+ protected $date_time_provider;
+
+ /**
+ * Constructor.
+ *
+ * @param DateTimeProviderInterface|null $date_time_provider The DateTime provider.
+ */
+ public function __construct( $date_time_provider = null ) {
+ $this->date_time_provider = null === $date_time_provider
+ ? new CurrentDateTimeProvider()
+ : $date_time_provider;
+ }
+
/**
* Compare against the store's total payments volume.
*
@@ -25,7 +45,17 @@ class TotalPaymentsVolumeProcessor implements RuleProcessorInterface {
* @return bool The result of the operation.
*/
public function process( $rule, $stored_state ) {
- $dates = TimeInterval::get_timeframe_dates( $rule->timeframe );
+ // The timeframe must be resolved against the site's calendar date: the report query
+ // parses the returned boundary strings as site-local datetimes. Work on a fresh
+ // instance: the date calculations modify it in place, and the provider's own object
+ // (which an implementation may cache and return again) must not absorb that.
+ $now = ( new \DateTime( '@' . $this->date_time_provider->get_now()->getTimestamp() ) )->setTimezone( wp_timezone() );
+ $dates = TimeInterval::get_timeframe_dates( $rule->timeframe, $now );
+
+ if ( false === $dates ) {
+ return false;
+ }
+
$reports_revenue = $this->get_reports_query(
array(
'before' => $dates['end'],
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/TimeIntervalTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/TimeIntervalTest.php
new file mode 100644
index 00000000000..d1cc1e27dfb
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/TimeIntervalTest.php
@@ -0,0 +1,73 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Admin\API\Reports;
+
+use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
+use DateTime;
+use DateTimeZone;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the timeframe date calculations in TimeInterval.
+ *
+ * The pre-existing coverage for this class lives in the frozen legacy suite
+ * (tests/legacy/unit-tests/woocommerce-admin/api/reports-interval.php); new
+ * TimeInterval tests belong here.
+ */
+class TimeIntervalTest extends WC_Unit_Test_Case {
+
+ /**
+ * @testdox Should select timeframe periods from the reference date's local calendar date, not its UTC instant.
+ */
+ public function test_timeframes_use_local_calendar_date(): void {
+ // 2022-09-01 05:00 in Kiritimati (UTC+14) is 2022-08-31 15:00 UTC: last month is August, not July.
+ $datetime = new DateTime( '2022-09-01 05:00:00', new DateTimeZone( 'Pacific/Kiritimati' ) );
+ $dates = TimeInterval::get_timeframe_dates( 'last_month', $datetime );
+
+ $this->assertSame( '2022-08-01 00:00:00', $dates['start'] );
+ $this->assertSame( '2022-08-31 23:59:59', $dates['end'] );
+
+ // 2021-12-31 20:00 in Midway (UTC-11) is 2022-01-01 07:00 UTC: last year is 2020, not 2021.
+ $datetime = new DateTime( '2021-12-31 20:00:00', new DateTimeZone( 'Pacific/Midway' ) );
+ $dates = TimeInterval::get_timeframe_dates( 'last_year', $datetime );
+
+ $this->assertSame( '2020-01-01 00:00:00', $dates['start'] );
+ $this->assertSame( '2020-12-31 23:59:59', $dates['end'] );
+ }
+
+ /**
+ * @testdox Should anchor the default reference date to the site timezone, not the PHP default timezone.
+ *
+ * With a real clock this assertion only discriminates a wrong default anchor while the UTC date
+ * and the site-local date fall in different periods (around period-boundary midnights), so an
+ * intermittent failure here means a real timezone regression, not flakiness. The deterministic
+ * production-path guarantee lives in TotalPaymentsVolumeProcessorTest.
+ */
+ public function test_timeframes_default_to_site_timezone_now(): void {
+ $timeframes = array( 'last_week', 'last_month', 'last_quarter', 'last_6_months', 'last_year' );
+ $original_timezone = get_option( 'timezone_string' );
+
+ try {
+ // Extreme offsets on both sides of UTC maximize the window where the site-local date differs from the UTC date.
+ foreach ( array( 'Pacific/Kiritimati', 'Pacific/Midway' ) as $timezone ) {
+ update_option( 'timezone_string', $timezone );
+
+ foreach ( $timeframes as $timeframe ) {
+ // Compute the expectation before and after the call so the assertion also holds if midnight passes mid-test.
+ $expected_before = TimeInterval::get_timeframe_dates( $timeframe, new DateTime( 'now', wp_timezone() ) );
+ $actual = TimeInterval::get_timeframe_dates( $timeframe );
+ $expected_after = TimeInterval::get_timeframe_dates( $timeframe, new DateTime( 'now', wp_timezone() ) );
+
+ $this->assertContains(
+ $actual,
+ array( $expected_before, $expected_after ),
+ "Default \"$timeframe\" dates should be based on the current date in the $timezone site timezone"
+ );
+ }
+ }
+ } finally {
+ update_option( 'timezone_string', $original_timezone );
+ }
+ }
+}
diff --git a/plugins/woocommerce/tests/php/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessorTest.php b/plugins/woocommerce/tests/php/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessorTest.php
new file mode 100644
index 00000000000..27b7e9eec9e
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Admin/RemoteSpecs/RuleProcessors/TotalPaymentsVolumeProcessorTest.php
@@ -0,0 +1,131 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Admin\RemoteSpecs\RuleProcessors;
+
+use Automattic\WooCommerce\Admin\DateTimeProvider\DateTimeProviderInterface;
+use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\TotalPaymentsVolumeProcessor;
+use DateTime;
+use DateTimeZone;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the TotalPaymentsVolumeProcessor class.
+ */
+class TotalPaymentsVolumeProcessorTest extends WC_Unit_Test_Case {
+
+ /**
+ * Builds the processor with a frozen clock and a stubbed report query that captures its args.
+ *
+ * The frozen "now" is 2020-08-31 15:00 UTC, which is 2020-09-01 05:00 in Pacific/Kiritimati
+ * (UTC+14). A past instant is used so a regression that falls back to the real clock can
+ * never reproduce the expected bounds.
+ *
+ * @return TotalPaymentsVolumeProcessor Processor exposing a public $captured_args property.
+ */
+ private function get_processor_with_frozen_clock() {
+ $frozen_provider = new class() implements DateTimeProviderInterface {
+ /**
+ * The cached frozen instance.
+ *
+ * @var DateTime|null
+ */
+ private $now;
+
+ /**
+ * Returns the frozen current DateTime. Deliberately returns the same cached
+ * instance on every call, like the legacy MockDateTimeProvider: the processor
+ * must not leak its date mutations back into the provider's object.
+ *
+ * @return DateTime
+ */
+ public function get_now() {
+ if ( null === $this->now ) {
+ $this->now = new DateTime( '2020-08-31 15:00:00', new DateTimeZone( 'UTC' ) );
+ }
+ return $this->now;
+ }
+ };
+
+ return new class( $frozen_provider ) extends TotalPaymentsVolumeProcessor {
+ /**
+ * Args the report query was built with.
+ *
+ * @var array
+ */
+ public $captured_args;
+
+ /**
+ * Captures the query args and returns a stubbed query.
+ *
+ * @param array $args The query args.
+ * @return object Stub with a get_data() method.
+ */
+ protected function get_reports_query( $args ) {
+ $this->captured_args = $args;
+
+ return new class() {
+ /**
+ * Returns stubbed report data.
+ *
+ * @return object
+ */
+ public function get_data() {
+ return (object) array( 'totals' => (object) array( 'total_sales' => 100 ) );
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * @testdox Should evaluate the timeframe against the site's calendar date, not the UTC date.
+ */
+ public function test_process_resolves_timeframe_in_site_timezone(): void {
+ $original_timezone = get_option( 'timezone_string' );
+ update_option( 'timezone_string', 'Pacific/Kiritimati' );
+
+ try {
+ $processor = $this->get_processor_with_frozen_clock();
+ $rule = (object) array(
+ 'timeframe' => 'last_month',
+ 'value' => 50,
+ 'operation' => '>',
+ );
+
+ $result = $processor->process( $rule, (object) array() );
+
+ $this->assertTrue( $result, 'total_sales of 100 should satisfy "> 50"' );
+ // On the frozen instant the site-local date is 2020-09-01 while the UTC date is still
+ // 2020-08-31, so last_month must resolve to August 2020, not July 2020.
+ $this->assertSame( '2020-08-01 00:00:00', $processor->captured_args['after'] );
+ $this->assertSame( '2020-08-31 23:59:59', $processor->captured_args['before'] );
+
+ // A second evaluation must produce the same window: the date calculations mutate the
+ // reference date in place, and that must not leak into the provider's cached instance.
+ $processor->process( $rule, (object) array() );
+
+ $this->assertSame( '2020-08-01 00:00:00', $processor->captured_args['after'], 'Repeated process() calls should not drift the timeframe' );
+ $this->assertSame( '2020-08-31 23:59:59', $processor->captured_args['before'], 'Repeated process() calls should not drift the timeframe' );
+ } finally {
+ update_option( 'timezone_string', $original_timezone );
+ }
+ }
+
+ /**
+ * @testdox Should fail the rule without querying reports when the timeframe is unknown.
+ */
+ public function test_process_returns_false_for_unknown_timeframe(): void {
+ $processor = $this->get_processor_with_frozen_clock();
+ $rule = (object) array(
+ 'timeframe' => 'last_century',
+ 'value' => 50,
+ 'operation' => '>',
+ );
+
+ $result = $processor->process( $rule, (object) array() );
+
+ $this->assertFalse( $result );
+ $this->assertNull( $processor->captured_args, 'No report query should be built for an unknown timeframe' );
+ }
+}