Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/daemon/workers/clock/PhabricatorSubscriptionTriggerClock.php
12242 views
1
<?php
2
3
/**
4
* Triggers an event every month on the same day of the month, like the 12th
5
* of the month.
6
*
7
* If a given month does not have such a day (for instance, the clock triggers
8
* on the 30th of each month and the month in question is February, which never
9
* has a 30th day), it will trigger on the last day of the month instead.
10
*
11
* Choosing this strategy for subscriptions is predictable (it's easy to
12
* anticipate when a subscription period will end) and fair (billing
13
* periods always have nearly equal length). It also spreads subscriptions
14
* out evenly. If there are issues with billing, this provides an opportunity
15
* for them to be corrected after only a few customers are affected, instead of
16
* (for example) having every subscription fail all at once on the 1st of the
17
* month.
18
*/
19
final class PhabricatorSubscriptionTriggerClock
20
extends PhabricatorTriggerClock {
21
22
public function validateProperties(array $properties) {
23
PhutilTypeSpec::checkMap(
24
$properties,
25
array(
26
'start' => 'int',
27
));
28
}
29
30
public function getNextEventEpoch($last_epoch, $is_reschedule) {
31
$start_epoch = $this->getProperty('start');
32
if (!$last_epoch) {
33
$last_epoch = $start_epoch;
34
}
35
36
// Constructing DateTime objects like this implies UTC, so we don't need
37
// to set that explicitly.
38
$start = new DateTime('@'.$start_epoch);
39
$last = new DateTime('@'.$last_epoch);
40
41
$year = (int)$last->format('Y');
42
$month = (int)$last->format('n');
43
44
// Note that we're getting the day of the month from the start date, not
45
// from the last event date. This lets us schedule on March 31 after moving
46
// the date back to Feb 28.
47
$day = (int)$start->format('j');
48
49
// We trigger at the same time of day as the original event. Generally,
50
// this means that you should get invoiced at a reasonable local time in
51
// most cases, unless you subscribed at 1AM or something.
52
$hms = $start->format('G:i:s');
53
54
// Increment the month by 1.
55
$month = $month + 1;
56
57
// If we ran off the end of the calendar, set the month back to January
58
// and increment the year by 1.
59
if ($month > 12) {
60
$month = 1;
61
$year = $year + 1;
62
}
63
64
// Now, move the day backward until it falls in the correct month. If we
65
// pass an invalid date like "2014-2-31", it will internally be parsed
66
// as though we had passed "2014-3-3".
67
while (true) {
68
$next = new DateTime("{$year}-{$month}-{$day} {$hms} UTC");
69
if ($next->format('n') == $month) {
70
// The month didn't get corrected forward, so we're all set.
71
break;
72
} else {
73
// The month did get corrected forward, so back off a day.
74
$day--;
75
}
76
}
77
78
return (int)$next->format('U');
79
}
80
81
}
82
83