如何准确地实现一个定时任务?

最近在考虑另外一个有趣的问题。

需要设计一个方案来实现定时命令(比如定时开关空调吧),下面是一些用例描述
(1)不重复触发 08:20-09:10 时间段的定时开关,即是08:20开启,09:10关闭。如果当前进行设置的时间为10:00,超过了结束时间,那么就挪到次日执行
(2)不重复触发 23:50-02:10 该例子是(1)的升级版本,开始时间和结束时间跨天了
(3)重复触发 08:20-09:10 【每周一、周三】在每周一或者周三的开始时间打开空调,结束时间关闭空调
(4)重复触发 23:50-02:10 【每周一、周三】该例子是(3)的升级版本,开始和结束时间跨天了。

最开始思考这个问题的时候觉得比较容易,就是判断加执行的问题。但是随着思考的深入,渐渐地问题多了起来。

  1. 时间段都是相对时间,是否可以变相对时间为绝对时间?如果改成绝对时间那么对于(3)和(4)是不是总是要调整,如果不改成绝对时间那么该怎么办?
  2. 到了开始时间就执行,到了关闭时间就关闭。关闭之后是不是要把命令设置为无效?

不重复触发&不跨天

先看看简单的不重复触发而且不跨天的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$startRange = "08:20";
$endRange = "09:10";
$ts = time();
$startTime = strtotime($startRange);
$endTime = strtotime($endRange);
if ($ts < $startTime) {
// 不能执行
} else if ($ts < $endTime) {
// 开启空调(防止多次开启)
} else {
// 关闭空调(防止多次关闭)
// 关闭命令
}

不重复触发&跨天

跨天的逻辑比较复杂,上述代码就不适用了

当处于t2时刻的时候,需要将开始时间往前推一天
否则就是结束时间往后推一天

1
2
3
4
5
6
7
8
// 如果是次日还未到结束的时候创建的
if ($current_time < $end_time) {
$start_time = strtotime(date('Y-m-d') . " " . $rangeStart) - 24 * 3600;
$end_time = strtotime(date('Y-m-d') . " " . $rangeEnd);
} else {
$start_time = strtotime(date('Y-m-d') . " " . $rangeStart);
$end_time = strtotime(date('Y-m-d') . " " . $rangeEnd) + 24 * 3600;
}

把相对的时间改为绝对的时间,然后判断方式同 【不重复触发&不跨天】

重复触发&不跨天

就是加多一个判断而已

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$current_time = time();
$currentW = date("w");
// 如果不在重复执行的周X列表里面就直接跳出
if (!in_array($currentW, $weekDays)) {
$this->log("{$ruleId} {$vin} 星期不符合, 跳出不执行了");
return [false, 0];
}

if ($current_time < $start_time) {
// 时间段不符合, 还没有到时间, 跳出不执行了
return [false, 1];
} else if ($current_time <= $end_time) {
// 时间段符合, 继续执行
} else {
// 时间段不符合, 已经超过时间了, 跳出不执行了, 看看要执行关闭吗");
}

重复触发&跨天

这个应该是最复杂的情况了,非常地烧脑~
首先是星期的判断。如果今天是周四,那么我需要判断是否有周四或者周三的规则(因为周三的跨天规则可以在周四执行的哦)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$start_time = strtotime($rangeStart);
$end_time = strtotime($rangeEnd);

$current_time = time();
$currentW = date("w");

// 如果结束时间小于开始时间,那么结束时间是在次日,属于跨天的情况,需要特殊处理
if ($end_time < $start_time) {
// 如果今天是周日,那么可以执行的日期只有周六或者周日
if ($currentW == 0) {
$findList = [6, 0];
} else {
$findList = [$currentW - 1, $currentW];
}

$findFlag = false;
foreach ($weekDays as $singleWeek) {
if (in_array($singleWeek, $findList)) {
$findFlag = true;
break;
}
}
if (!$findFlag) {
// 星期不符合, 跳出不执行了
return [false, 0];
}

// 可以执行后需要结合星期来确定开始结束时间
$shotFlag = in_array($currentW, $weekDays); // 是否是当日状态

if ($shotFlag) {
$start_time = strtotime(date('Y-m-d') . " " . $rangeStart);
$end_time = strtotime(date('Y-m-d') . " " . $rangeEnd) + 24 * 3600;
} else {
$start_time = strtotime(date('Y-m-d') . " " . $rangeStart) - 24 * 3600;
$end_time = strtotime(date('Y-m-d') . " " . $rangeEnd);
}
}