1
|
|
|
<?php |
2
|
|
|
namespace common\components; |
3
|
|
|
|
4
|
|
|
use yii; |
5
|
|
|
use \DateTime; |
6
|
|
|
use \DateTimeZone; |
7
|
|
|
|
8
|
|
|
class Time extends \yii\base\BaseObject implements \common\interfaces\TimeInterface { |
9
|
|
|
|
10
|
|
|
public const EARLIEST_DATE = '2014-01-01'; |
11
|
|
|
|
12
|
|
|
public $timezone; |
13
|
|
|
|
14
|
|
|
public function __construct(String $timezone, $config = []) { |
15
|
|
|
$this->timezone = $timezone; |
16
|
|
|
parent::__construct($config); |
17
|
|
|
} |
18
|
|
|
|
19
|
|
|
/* |
20
|
|
|
* Returns a \DateTime object of the current time in $this->timezone |
21
|
|
|
* |
22
|
|
|
* @return \DateTime the current time in $this->timezone |
23
|
|
|
*/ |
24
|
|
|
public function now() { |
25
|
|
|
return new DateTime("now", new DateTimeZone($this->timezone)); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
/* |
29
|
|
|
* Parses the supplied string into a `\DateTime` object of the |
30
|
|
|
* given `$format`. It assumes the supplied string is in the |
31
|
|
|
* timezone specified in $this->timezone. |
32
|
|
|
* |
33
|
|
|
* @param string $time the questionable time to parse |
34
|
|
|
* @param string $format the format `$time` is expected to be in |
35
|
|
|
* @param bool | \DateTime $format the format `$time` is expected to be in |
36
|
|
|
* @return \DateTime the parsed time or the default value |
37
|
|
|
*/ |
38
|
|
|
public function parse($time, $default = false, string $format = 'Y-m-d') { |
39
|
|
|
if(is_string($time)) { |
|
|
|
|
40
|
|
|
$dt = DateTime::createFromFormat($format, $time, new DateTimeZone($this->timezone)); |
41
|
|
|
if($dt) { |
42
|
|
|
// for some reason, using createFromFromat adds in the time. The regular DateTime constructor _does not_ do this. We manually zero out the time here to make the DateTime objects match. |
43
|
|
|
$dt->setTime(0, 0, 0); |
44
|
|
|
$formatted = $dt->format($format); |
45
|
|
|
if($formatted === $time && $this->inBounds($dt)) { |
46
|
|
|
return $dt; |
47
|
|
|
} |
48
|
|
|
} |
49
|
|
|
} |
50
|
|
|
return $default; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/* |
54
|
|
|
* Checks if the given `\DateTime` is within "acceptable" date bounds. |
55
|
|
|
* It does no good to have the date be far in the past or in the future. |
56
|
|
|
* |
57
|
|
|
* @param \DateTime $dt |
58
|
|
|
* @return boolean |
59
|
|
|
*/ |
60
|
|
|
public function inBounds(DateTime $dt) { |
61
|
|
|
$first = strtotime((new DateTime(self::EARLIEST_DATE))->format('Y-m-d')); |
62
|
|
|
$test = strtotime($dt->format('Y-m-d')); |
63
|
|
|
$now = strtotime($this->getLocalDate()); |
64
|
|
|
|
65
|
|
|
if($first <= $test && $test <= $now) { |
66
|
|
|
return true; |
67
|
|
|
} else { |
68
|
|
|
return false; |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
public function convertLocalToUTC($local, $inc_time = true) { |
73
|
|
|
$fmt = $inc_time ? "Y-m-d H:i:s" : "Y-m-d"; |
74
|
|
|
|
75
|
|
|
$timestamp = new DateTime($local, new DateTimeZone($this->timezone)); |
76
|
|
|
$timestamp->setTimeZone(new DateTimeZone("UTC")); |
77
|
|
|
return $timestamp->format($fmt); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
public function convertUTCToLocal($utc, $iso = true) { |
81
|
|
|
$fmt = $iso ? Datetime::ATOM : "Y-m-d H:i:s"; |
82
|
|
|
|
83
|
|
|
$timestamp = new DateTime($utc, new DateTimeZone("UTC")); |
84
|
|
|
$timestamp->setTimeZone(new DateTimeZone($this->timezone)); |
85
|
|
|
return $timestamp->format($fmt); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
public function getLocalTime($timezone = null) { |
89
|
|
|
if($timezone === null) |
90
|
|
|
$timezone = $this->timezone; |
91
|
|
|
|
92
|
|
|
$timestamp = new DateTime("now", new DateTimeZone($timezone)); |
93
|
|
|
return $timestamp->format("Y-m-d H:i:s"); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
public function getLocalDate($timezone = null) { |
97
|
|
|
if($timezone === null) |
98
|
|
|
$timezone = $this->timezone; |
99
|
|
|
|
100
|
|
|
return (new DateTime("now", new DateTimeZone($timezone))) |
101
|
|
|
->format("Y-m-d"); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
public function alterLocalDate($date, $modifier) { |
105
|
|
|
return (new DateTime("$date $modifier", new DateTimeZone($this->timezone))) |
106
|
|
|
->format("Y-m-d"); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
public function getUTCBookends($local) { |
110
|
|
|
$local = trim($local); |
111
|
|
|
if(strpos($local, " ")) { |
112
|
|
|
return false; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
$start = $local . " 00:00:00"; |
116
|
|
|
$end = $local . "23:59:59"; |
117
|
|
|
|
118
|
|
|
$front = self::convertLocalToUTC($start); |
|
|
|
|
119
|
|
|
$back = self::convertLocalToUTC($end); |
120
|
|
|
|
121
|
|
|
return [$front, $back]; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/* |
125
|
|
|
* Verifies that what it is given is a parseable date string |
126
|
|
|
* If it is not a parseable string, it defaults to the current |
127
|
|
|
* date. |
128
|
|
|
* |
129
|
|
|
* @param $date a date string or null |
130
|
|
|
* @return string a date string |
131
|
|
|
*/ |
132
|
|
|
public function validate($date = null) { |
133
|
|
|
if(is_null($date)) { |
134
|
|
|
return $this->getLocalDate(); |
135
|
|
|
} else if($dt = $this->parse($date)) { |
136
|
|
|
return $dt->format('Y-m-d'); |
137
|
|
|
} else { |
138
|
|
|
return $this->getLocalDate(); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/* |
143
|
|
|
* Returns a \DatePeriod iterable containing a DateTime for each day of the |
144
|
|
|
* last $period days. The \DatePeriod is in the $this->timezone timezone. The |
145
|
|
|
* current day (end of the period) is included in the \returned \DatePeriod. |
146
|
|
|
* |
147
|
|
|
* @param $period int |
148
|
|
|
* @return \DatePeriod of length $period, from $period days ago to today |
149
|
|
|
*/ |
150
|
|
|
public function getDateTimesInPeriod(int $period = 30) { |
151
|
|
|
$dt = new DateTime("now", new DateTimeZone($this->timezone)); |
152
|
|
|
$dt2 = new DateTime("now", new DateTimeZone($this->timezone)); |
153
|
|
|
$end = $dt ->add(new \DateInterval('P1D')) // add a day, so the end date gets included in the intervals |
154
|
|
|
->add(new \DateInterval('PT2M')); // add two minutes, to be sure we have everything |
155
|
|
|
$start = $dt2->add(new \DateInterval('PT2M')) // add two minutes, to be sure we have everything |
156
|
|
|
->sub(new \DateInterval("P${period}D")); // subtract `$period` number of days |
157
|
|
|
|
158
|
|
|
$periods = new \DatePeriod($start, new \DateInterval('P1D'), $end, \DatePeriod::EXCLUDE_START_DATE); |
159
|
|
|
$local_tz = new \DateTimeZone($this->timezone); |
160
|
|
|
foreach($periods as $period) { |
161
|
|
|
$period->setTimezone($local_tz); |
162
|
|
|
} |
163
|
|
|
return $periods; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
|