Page MenuHomePhabricator

D11427.id27454.diff
No OneTemporary

D11427.id27454.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1595,6 +1595,7 @@
'PhabricatorDaemonTasksTableView' => 'applications/daemon/view/PhabricatorDaemonTasksTableView.php',
'PhabricatorDaemonsApplication' => 'applications/daemon/application/PhabricatorDaemonsApplication.php',
'PhabricatorDaemonsSetupCheck' => 'applications/config/check/PhabricatorDaemonsSetupCheck.php',
+ 'PhabricatorDailyRoutineTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php',
'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php',
'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php',
@@ -4797,6 +4798,7 @@
'PhabricatorDaemonTasksTableView' => 'AphrontView',
'PhabricatorDaemonsApplication' => 'PhabricatorApplication',
'PhabricatorDaemonsSetupCheck' => 'PhabricatorSetupCheck',
+ 'PhabricatorDailyRoutineTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorDashboard' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',
diff --git a/src/infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php b/src/infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Triggers a daily routine, like server backups.
+ *
+ * This clock triggers events every 24 hours, using UTC. It does not use a
+ * locale, and is intended for technical processes like backing up a server
+ * every night.
+ *
+ * Because UTC does not have daylight savings, the local hour when this event
+ * occurs will change over the course of the year. For example, from the
+ * perspective of a user in California, it might run backups at 3AM in the
+ * winter and 2AM in the summer. This is desirable for maintenance processes,
+ * but problematic for some human processes. Use a different clock if you're
+ * triggering a human-oriented event.
+ *
+ * The clock uses the time of day of the `start` epoch to calculate the time
+ * of day of the next event, so you can change the time of day when the event
+ * occurs by adjusting the `start` time of day.
+ */
+final class PhabricatorDailyRoutineTriggerClock
+ extends PhabricatorTriggerClock {
+
+ public function validateProperties(array $properties) {
+ PhutilTypeSpec::checkMap(
+ $properties,
+ array(
+ 'start' => 'int',
+ ));
+ }
+
+ public function getNextEventEpoch($last_epoch, $is_reschedule) {
+ $start_epoch = $this->getProperty('start');
+ if (!$last_epoch) {
+ $last_epoch = $start_epoch;
+ }
+
+ $start = new DateTime('@'.$start_epoch);
+ $last = new DateTime('@'.$last_epoch);
+
+ // NOTE: We're choosing the date from the last event, but the time of day
+ // from the start event. This allows callers to change when the event
+ // occurs by updating the trigger's start parameter.
+ $ymd = $last->format('Y-m-d');
+ $hms = $start->format('G:i:s');
+
+ $next = new DateTime("{$ymd} {$hms} UTC");
+
+ // Add a day.
+ // NOTE: DateInterval doesn't exist until PHP 5.3.0, and we currently
+ // target PHP 5.2.3.
+ $next->modify('+1 day');
+
+ return (int)$next->format('U');
+ }
+
+}
diff --git a/src/infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php b/src/infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php
--- a/src/infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php
+++ b/src/infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php
@@ -30,6 +30,90 @@
pht('Should never trigger.'));
}
+ public function testDailyRoutineTriggerClockDaylightSavings() {
+ // These dates are selected to cross daylight savings in PST; they should
+ // be unaffected.
+ $start = strtotime('2015-03-05 16:17:18 UTC');
+
+ $clock = new PhabricatorDailyRoutineTriggerClock(
+ array(
+ 'start' => $start,
+ ));
+
+ $expect_list = array(
+ '2015-03-06 16:17:18',
+ '2015-03-07 16:17:18',
+ '2015-03-08 16:17:18',
+ '2015-03-09 16:17:18',
+ '2015-03-10 16:17:18',
+ );
+
+ $this->expectClock($clock, $expect_list, pht('Daily Routine (PST)'));
+ }
+
+ public function testDailyRoutineTriggerClockLeapSecond() {
+ // These dates cross the leap second on June 30, 2012. There has never
+ // been a negative leap second, so we can't test that yet.
+ $start = strtotime('2012-06-28 23:59:59 UTC');
+
+ $clock = new PhabricatorDailyRoutineTriggerClock(
+ array(
+ 'start' => $start,
+ ));
+
+ $expect_list = array(
+ '2012-06-29 23:59:59',
+ '2012-06-30 23:59:59',
+ '2012-07-01 23:59:59',
+ '2012-07-02 23:59:59',
+ );
+
+ $this->expectClock($clock, $expect_list, pht('Daily Routine (Leap)'));
+ }
+
+
+ public function testCDailyRoutineTriggerClockAdjustTimeOfDay() {
+ // In this case, we're going to update the time of day on the clock and
+ // make sure it keeps track of the date but adjusts the time.
+ $start = strtotime('2015-01-15 6:07:08 UTC');
+
+ $clock = new PhabricatorDailyRoutineTriggerClock(
+ array(
+ 'start' => $start,
+ ));
+
+ $expect_list = array(
+ '2015-01-16 6:07:08',
+ '2015-01-17 6:07:08',
+ '2015-01-18 6:07:08',
+ );
+
+ $last_epoch = $this->expectClock(
+ $clock,
+ $expect_list,
+ pht('Daily Routine (Pre-Adjust)'));
+
+ // Now, change the time of day.
+ $new_start = strtotime('2015-01-08 1:23:45 UTC');
+
+ $clock = new PhabricatorDailyRoutineTriggerClock(
+ array(
+ 'start' => $new_start,
+ ));
+
+ $expect_list = array(
+ '2015-01-19 1:23:45',
+ '2015-01-20 1:23:45',
+ '2015-01-21 1:23:45',
+ );
+
+ $this->expectClock(
+ $clock,
+ $expect_list,
+ pht('Daily Routine (Post-Adjust)'),
+ $last_epoch);
+ }
+
public function testSubscriptionTriggerClock() {
$start = strtotime('2014-01-31 2:34:56 UTC');
@@ -76,7 +160,15 @@
'2016-03-31 2:34:56',
);
- $last_epoch = null;
+ $this->expectClock($clock, $expect_list, pht('Billing Cycle'));
+ }
+
+ private function expectClock(
+ PhabricatorTriggerClock $clock,
+ array $expect_list,
+ $clock_name,
+ $last_epoch = null) {
+
foreach ($expect_list as $cycle => $expect) {
$next_epoch = $clock->getNextEventEpoch(
$last_epoch,
@@ -84,11 +176,13 @@
$this->assertEqual(
$expect,
- id(new DateTime('@'.$next_epoch))->format('Y-m-d g:i:s'),
- pht('Billing cycle %s.', $cycle));
+ id(new DateTime('@'.$next_epoch))->format('Y-m-d G:i:s'),
+ pht('%s (%s)', $clock_name, $cycle));
$last_epoch = $next_epoch;
}
+
+ return $last_epoch;
}
}

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 11, 9:00 AM (2 d, 20 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6714070
Default Alt Text
D11427.id27454.diff (7 KB)

Event Timeline