1: <?php
2: /*
3: * Copyright (c) 2014 @trashtoy
4: * https://github.com/trashtoy/
5: *
6: * Permission is hereby granted, free of charge, to any person obtaining a copy of
7: * this software and associated documentation files (the "Software"), to deal in
8: * the Software without restriction, including without limitation the rights to use,
9: * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10: * Software, and to permit persons to whom the Software is furnished to do so,
11: * subject to the following conditions:
12: *
13: * The above copyright notice and this permission notice shall be included in all
14: * copies or substantial portions of the Software.
15: *
16: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18: * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19: * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20: * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21: * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22: */
23: /**
24: * PHP class file.
25: * @auhtor trashtoy
26: * @since 2.0.0
27: */
28: namespace Peach\DT;
29: use Peach\Util\Values;
30: use Peach\Util\Strings;
31: use Peach\Util\Map;
32: use Peach\Util\ArrayMap;
33:
34: /**
35: * 時間を表す抽象基底クラスです.
36: * {@link Date}, {@link Datetime}, {@link Timestamp}
37: * の共通部分の実装です.
38: */
39: abstract class AbstractTime implements Time
40: {
41: /**
42: * 年フィールドのキーです
43: * @var int
44: * @ignore
45: */
46: protected static $YEAR = 0;
47:
48: /**
49: * 月フィールドのキーです
50: * @var int
51: * @ignore
52: */
53: protected static $MONTH = 1;
54:
55: /**
56: * 日フィールドのキーです
57: * @var int
58: * @ignore
59: */
60: protected static $DATE = 2;
61:
62: /**
63: * 時フィールドのキーです
64: * @var int
65: * @ignore
66: */
67: protected static $HOUR = 3;
68:
69: /**
70: * 分フィールドのキーです
71: * @var int
72: * @ignore
73: */
74: protected static $MINUTE = 4;
75:
76: /**
77: * 秒フィールドのキーです
78: * @var int
79: * @ignore
80: */
81: protected static $SECOND = 5;
82:
83: /**
84: * 年・月・日などの各種フィールドです.
85: *
86: * @var Map
87: * @ignore
88: */
89: protected $fields;
90:
91: /**
92: * 指定されたフィールドの値を取得します.
93: * @param string $field フィールド名
94: * @return int 対象フィールドの値. ただしフィールド名が不正な場合は NULL
95: */
96: public final function get($field)
97: {
98: $index = $this->getFieldIndex($field);
99: return $this->fields->get($index);
100: }
101:
102: /**
103: * この時間オブジェクトの指定されたフィールドを上書きします.
104: *
105: * @param string $field フィールド名
106: * @param int $value 設定する値
107: * @return Time 設定後の時間オブジェクト
108: */
109: public final function set($field, $value)
110: {
111: $index = $this->getFieldIndex($field);
112: $newFields = new ArrayMap($this->fields);
113: $newFields->put($index, $value);
114: return $this->newInstance($newFields);
115: }
116:
117: /**
118: * この時間オブジェクトの複数のフィールドを一度に上書きします.
119: * 引数には,
120: * <code>
121: * array("year" => 2010, "month" => 8, "date" => 31)
122: * </code>
123: * などの配列か, または同様の Map オブジェクトを指定してください.
124: *
125: * @param Map|array $subject フィールドと値の一覧
126: * @return Time 設定後の時間オブジェクト
127: * @throws \InvalidArgumentException 引数の型が不正な場合
128: */
129: public final function setAll($subject)
130: {
131: if (is_array($subject)) {
132: $subject = new ArrayMap($subject);
133: }
134: if (!($subject instanceof Map)) {
135: throw new \InvalidArgumentException("Argument (" . Values::getType($subject) . ") must be array or \\Peach\\Util\\Map");
136: }
137: $newFields = new ArrayMap($this->fields);
138: $entryList = $subject->entryList();
139: foreach ($entryList as $entry) {
140: $index = $this->getFieldIndex($entry->getKey());
141: $newFields->put($index, $entry->getValue());
142: }
143: return $this->newInstance($newFields);
144: }
145:
146: /**
147: * 引数のフィールドを, $amount だけ増加 (負の場合は減少) させます.
148: * @param string $field 対象のフィールド
149: * @param int $amount 加算する量. マイナスの場合は過去方向に移動する.
150: * @return Time 設定後の時間オブジェクト
151: */
152: public final function add($field, $amount)
153: {
154: $newFields = new ArrayMap($this->fields);
155: $key = $this->getFieldIndex($field);
156: $current = $this->fields->get($key);
157: $newFields->put($key, $current + $amount);
158: return $this->newInstance($newFields);
159: }
160:
161: /**
162: * この時間と指定された時間を比較します.
163: * このメソッドは, 自身と引数の時間オブジェクトが共通で持っているフィールドについて比較を行います.
164: * 比較の結果すべてのフィールドの値が等しかった場合,
165: * より多くの時間フィールドを持つほうが「後」となります.
166: *
167: * 例: 2012-05-21 (Date) < 2012-05-21T00:00 (Datetime) < 2012-05-21T00:00:00 (Timestamp)
168: *
169: * @param mixed $obj 比較対象のオブジェクト
170: * @return int この時間のほうが過去の場合は負の値, 未来の場合は正の値, 等しい場合は 0.
171: * ただし, 引数が時間オブジェクトでない場合は NULL
172: */
173: public final function compareTo($obj)
174: {
175: if ($obj instanceof Time) {
176: $c = $this->compareFields($obj);
177: return ($c !== 0) ? $c : $this->getType() - $obj->getType();
178: } else {
179: return null;
180: }
181: }
182:
183: /**
184: * 指定されたフォーマットを使ってこの時間オブジェクトを書式化します.
185: * フォーマットを指定しない場合はデフォルトの方法 (SQL などで使われる慣用表現) で書式化を行ないます.
186: * @param Format $format
187: * @return string 時間オブジェクトの文字列表現 (例: "YYYY-MM-DD" など)
188: */
189: public final function format(Format $format = null)
190: {
191: return isset($format) ? $this->handleFormat($format) : $this->__toString();
192: }
193:
194: /**
195: * 指定されたオブジェクトとこのオブジェクトを比較します.
196: * compareTo による比較結果が 0 を返し, かつクラスが同じ場合に TRUE を返します.
197: *
198: * @param mixed $obj 比較対象のオブジェクト
199: * @return bool 二つのオブジェクトが等しい場合に TRUE, それ以外は FALSE
200: */
201: public function equals($obj)
202: {
203: if (get_class($this) !== get_class($obj)) {
204: return false;
205: }
206: return $this->compareTo($obj) === 0;
207: }
208:
209: /**
210: * 指定された時間とこの時間を比較します.
211: *
212: * もしもこのオブジェクトが持つ時間フィールドすべてが
213: * 引数のオブジェクトの時間フィールドと一致した場合,
214: * より多くの時間フィールドを持つほうが「後」となります.
215: *
216: * 例: 2012-05-21 (Date) < 2012-05-21T00:00 (Datetime) < 2012-05-21T00:00:00 (Timestamp)
217: *
218: * @param Time $time 比較対象の時間
219: * @return bool この時間のほうが過去である場合は TRUE, それ以外は FALSE
220: */
221: public final function before(Time $time)
222: {
223: $c = $this->compareTo($time);
224: return isset($c) && ($c < 0);
225: }
226:
227: /**
228: * 指定された時間とこの時間を比較します.
229: *
230: * もしもこのオブジェクトが持つ時間フィールドすべてが
231: * 引数のオブジェクトの時間フィールドと一致した場合,
232: * より多くの時間フィールドを持つほうが「後」となります.
233: *
234: * 例: 2012-05-21 (Date) < 2012-05-21T00:00 (Datetime) < 2012-05-21T00:00:00 (Timestamp)
235: *
236: * @param Time $time 比較対象の時間
237: * @return bool この時間のほうが未来である場合は TRUE, それ以外は FALSE
238: */
239: public final function after(Time $time)
240: {
241: $c = $this->compareTo($time);
242: return isset($c) && (0 < $c);
243: }
244:
245: /**
246: * この時間の時刻 (時・分・秒) 部分を書式化します.
247: * 時・分・秒をサポートしていないオブジェクトの場合は空文字列を返します.
248: *
249: * @return string "hh:mm:ss" 形式の文字列. このオブジェクトが時刻をサポートしない場合は空文字列.
250: */
251: public function formatTime()
252: {
253: return "";
254: }
255:
256: /**
257: * このオブジェクトが指す時刻を, SQL などで使われる慣用表現に変換して返します.
258: *
259: * @return string このオブジェクトの文字列表現 ("YYYY-MM-DD", "YYYY-MM-DD hh:mm" など)
260: * @codeCoverageIgnore
261: */
262: public function __toString()
263: {
264: return (string) $this;
265: }
266:
267: /**
268: * 指定されたフィールドを使ってこの時間オブジェクトを初期化します.
269: * サブクラスはこのメソッドを継承して独自の初期化処理を行ないます.
270: *
271: * @param Map $fields
272: * @ignore
273: */
274: protected function init(Map $fields)
275: {
276: $this->adjust($fields);
277: $this->fields = $fields;
278: }
279:
280: /**
281: * 時間の不整合を調整します.
282: * 例えば, 0 から 23 までの値を取るはずの「時」のフィールドが
283: * 24 以上の値を持っていた場合に, 日付の繰上げ処理を行うなどの操作を行います.
284: *
285: * このメソッドはサブクラスのコンストラクタ内で参照されます.
286: *
287: * @param Map フィールド一覧
288: * @ignore
289: */
290: protected abstract function adjust(Map $fields);
291:
292: /**
293: * 指定されたフィールドを持つ新しいインスタンスを構築します.
294: *
295: * @param Map 各種フィールド
296: * @return Time
297: * @ignore
298: */
299: protected abstract function newInstance(Map $fields);
300:
301: /**
302: * このオブジェクトと指定された時間オブジェクトについて,
303: * 共通するフィールド同士を比較します.
304: * このメソッドは compareTo() から参照されます.
305: *
306: * @param Time $time
307: * @return int
308: * @see Time::compareTo
309: * @ignore
310: */
311: protected abstract function compareFields(Time $time);
312:
313: /**
314: * 指定されたフォーマットを使ってこの時間オブジェクトを書式化します.
315: * このメソッドは format() から参照されます.
316: *
317: * @param Format $format
318: * @return string
319: * @see Time::format
320: * @ignore
321: */
322: protected abstract function handleFormat(Format $format);
323:
324: /**
325: * 指定された日付の曜日を返します. 返される値は 0 から 6 までの整数で, 0 が日曜, 6 が土曜をあらわします.
326: * プログラム内で各曜日を表現する場合は, ソースコード内に数値を直接書き込む代わりに
327: * Time::SUNDAY や Time::SATURDAY などの定数を使ってください.
328: *
329: * @param int $y 年
330: * @param int $m 月
331: * @param int $d 日
332: * @return int 曜日 (0 以上 6 以下の整数)
333: *
334: * @see Date::SUNDAY
335: * @see Date::MONDAY
336: * @see Date::TUESDAY
337: * @see Date::WEDNESDAY
338: * @see Date::THURSDAY
339: * @see Date::FRIDAY
340: * @see Date::SATURDAY
341: * @ignore
342: */
343: protected static function getDayOf($y, $m, $d)
344: {
345: static $m_sub = array(0, 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4);
346: if ($m < 3) {
347: $y --;
348: }
349: return ($y + intval($y / 4) - intval($y / 100) + intval($y / 400) + $m_sub[$m] + $d) % 7;
350: }
351:
352: /**
353: * 指定されたフィールド名を $fields のインデックスに変換します.
354: * 不正なフィールド名の場合は -1 を返します.
355: *
356: * @param string $field フィールド名
357: * @return int インデックス
358: *
359: * @see Time::$YEAR
360: * @see Time::$MONTH
361: * @see Time::$DATE
362: * @see Time::$HOUR
363: * @see Time::$MINUTE
364: * @see Time::$SECOND
365: * @codeCoverageIgnore
366: */
367: private function getFieldIndex($field)
368: {
369: static $mapping = null;
370: if (!isset($mapping)) {
371: $mapping = array(
372: "y" => self::$YEAR,
373: "mo" => self::$MONTH,
374: "d" => self::$DATE,
375: "h" => self::$HOUR,
376: "m" => self::$MINUTE,
377: "s" => self::$SECOND
378: );
379: }
380:
381: $field = strtolower($field);
382: foreach ($mapping as $key => $index) {
383: if (Strings::startsWith($field, $key)) {
384: return $index;
385: }
386: }
387: return -1;
388: }
389: }
390: