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\Map;
30: use Peach\Util\ArrayMap;
31:
32: /**
33: * DATE 型の時間オブジェクトです.
34: * このクラスは年・月・日のフィールドをサポートします.
35: */
36: class Date extends AbstractTime
37: {
38: /**
39: * 年の値です.
40: * @var int
41: * @ignore
42: */
43: protected $year;
44:
45: /**
46: * 月の値です.
47: * @var int
48: * @ignore
49: */
50: protected $month;
51:
52: /**
53: * 日の値です.
54: * @var int
55: * @ignore
56: */
57: protected $date;
58:
59: /**
60: * 現在の日付の Date オブジェクトを返します.
61: *
62: * @param Clock $clock 現在時刻を取得するための Clock オブジェクト
63: * @return Date 現在の日付をあらわす Date オブジェクト
64: */
65: public static function now(Clock $clock = null)
66: {
67: if ($clock === null) {
68: return self::now(DefaultClock::getInstance());
69: }
70:
71: return $clock->getTimestamp()->toDate();
72: }
73:
74: /**
75: * 指定された文字列を解析して Date オブジェクトに変換します.
76: * $format が指定されていない場合は {@link W3cDatetimeFormat::getInstance}
77: * を使って解析を行います.
78: * ("YYYY-MM-DD" 形式の文字列を受理します.)
79: *
80: * @param string $text 変換対象の文字列
81: * @param Format $format 変換に使用するフォーマット
82: * @return Date 変換結果の Date オブジェクト
83: */
84: public static function parse($text, Format $format = null)
85: {
86: if (!isset($format)) {
87: $format = W3cDatetimeFormat::getInstance();
88: }
89: return $format->parseDate($text);
90: }
91:
92: /**
93: * 与えられた日付を表現する Peach_DT_Date オブジェクトを構築します.
94: *
95: * @param int $year 年
96: * @param int $month 月
97: * @param int $date 日
98: */
99: public function __construct($year, $month, $date)
100: {
101: $fields = new ArrayMap();
102: $fields->put(self::$YEAR, intval($year));
103: $fields->put(self::$MONTH, intval($month));
104: $fields->put(self::$DATE, intval($date));
105: $this->init($fields);
106: }
107:
108: /**
109: * このオブジェクトの型 {@link Time::TYPE_DATE} を返します.
110: * @return int Time::TYPE_DATE
111: */
112: public function getType()
113: {
114: return self::TYPE_DATE;
115: }
116:
117: /**
118: * (non-PHPdoc)
119: * @see AbstractTime::init
120: * @ignore
121: */
122: protected function init(Map $fields)
123: {
124: parent::init($fields);
125: $this->year = $fields->get(self::$YEAR);
126: $this->month = $fields->get(self::$MONTH);
127: $this->date = $fields->get(self::$DATE);
128: }
129:
130: /**
131: * 年月日の不整合を調整します.
132: * @ignore
133: */
134: protected function adjust(Map $fields)
135: {
136: // 年と月の不整合を調整します.
137: $this->adjustMonth($fields);
138:
139: $year = $fields->get(self::$YEAR);
140: $month = $fields->get(self::$MONTH);
141: $date = $fields->get(self::$DATE);
142:
143: // 日が1より小さい場合は、月の繰り下げ調整をします.
144: if ($date < 1) {
145: while ($date < 1) {
146: $month --;
147: if ($month === 0) {
148: $month = 12;
149: $year --;
150: }
151: $fields->put(self::$YEAR, $year);
152: $fields->put(self::$MONTH, $month);
153: $date += self::getDateCountOf($year, $month);
154: }
155: $fields->put(self::$DATE, $date);
156: $this->adjust($fields);
157: return;
158: }
159:
160: // 日がこの月の日数より大きい場合は、月の繰上げ調整をします.
161: $dateCount = self::getDateCountOf($year, $month);
162: if ($dateCount < $date) {
163: while ($dateCount < $date) {
164: $date -= $dateCount;
165: $month ++;
166: if ($month === 13) {
167: $month = 1;
168: $year ++;
169: }
170: $fields->put(self::$YEAR, $year);
171: $fields->put(self::$MONTH, $month);
172: $dateCount = self::getDateCountOf($year, $month);
173: }
174: $fields->put(self::$DATE, $date);
175: $this->adjust($fields);
176: return;
177: }
178: }
179:
180: /**
181: * 年と月の不整合を調整します.
182: * @param Map $fields 調整対象のフィールド一覧
183: */
184: private function adjustMonth(Map $fields)
185: {
186: // 年の不整合を調整します.
187: $this->adjustYear($fields);
188: $adjuster = $this->getMonthAdjuster();
189: $month = $fields->get(self::$MONTH);
190: if ($month < 1) {
191: $adjuster->moveDown($fields);
192: } else if (12 < $month) {
193: $adjuster->moveUp($fields);
194: } else {
195: return;
196: }
197: $this->adjustMonth($fields);
198: }
199:
200: /**
201: * 月の調整をするための FieldAdjuster を返します.
202: *
203: * @return FieldAdjuster
204: * @codeCoverageIgnore
205: */
206: private function getMonthAdjuster()
207: {
208: static $adjuster = null;
209: if (!isset($adjuster)) {
210: $adjuster = new FieldAdjuster(self::$MONTH, self::$YEAR, 1, 12);
211: }
212: return $adjuster;
213: }
214:
215: /**
216: * 年の値を4桁に調整します. (10000年問題に対応するまでの暫定的処置です.)
217: * @param Map $fields 調整対象のフィールド一覧
218: */
219: private function adjustYear(Map $fields)
220: {
221: $year = $fields->get(self::$YEAR);
222: $year %= 10000;
223: if ($year < 0) {
224: $year += 10000;
225: }
226: $fields->put(self::$YEAR, $year);
227: }
228:
229: /**
230: * (non-PHPdoc)
231: * @return Date
232: * @see Time::newInstance
233: * @ignore
234: */
235: protected function newInstance(Map $fields)
236: {
237: $year = $fields->get(self::$YEAR);
238: $month = $fields->get(self::$MONTH);
239: $date = $fields->get(self::$DATE);
240: return new self($year, $month, $date);
241: }
242:
243: /**
244: * この時間と指定された時間を比較します.
245: *
246: * この型の時間フィールドと引数の型の時間フィールドのうち,
247: * 共通しているフィールド同士を比較します.
248: *
249: * 引数が Peach_DT_Date を継承したオブジェクトではない場合,
250: * 引数のオブジェクトに対して get("year"), get("month"), get("date") の返り値を比較対象のフィールドとします.
251: *
252: * @param Time 比較対象の時間
253: * @return int この時間のほうが過去の場合は負の値, 未来の場合は正の値, 等しい場合は 0
254: * @ignore
255: */
256: protected function compareFields(Time $time)
257: {
258: $className = __CLASS__;
259: if ($time instanceof $className) {
260: if ($this->year !== $time->year) {
261: return $this->year - $time->year;
262: }
263: if ($this->month !== $time->month) {
264: return $this->month - $time->month;
265: }
266: if ($this->date !== $time->date) {
267: return $this->date - $time->date;
268: }
269: return 0;
270: }
271: else {
272: $y = $time->get("year");
273: $m = $time->get("month");
274: $d = $time->get("date");
275: if ($this->year !== $y) {
276: return (isset($y) ? $this->year - $y : 0);
277: }
278: if ($this->month !== $m) {
279: return (isset($m) ? $this->month - $m : 0);
280: }
281: if ($this->date !== $d) {
282: return (isset($d) ? $this->date - $d : 0);
283: }
284: return 0;
285: }
286: }
287:
288: /**
289: * @return string
290: * @ignore
291: */
292: protected function handleFormat(Format $format)
293: {
294: return $format->formatDate($this);
295: }
296:
297: /**
298: * このオブジェクトの文字列表現です.
299: * "YYYY-MM-DD" 形式の文字列を返します.
300: *
301: * @return string "YYYY-MM-DD" 形式の文字列
302: */
303: public function __toString()
304: {
305: $y = str_pad($this->year, 4, '0', STR_PAD_LEFT);
306: $m = str_pad($this->month, 2, '0', STR_PAD_LEFT);
307: $d = str_pad($this->date, 2, '0', STR_PAD_LEFT);
308: return $y . '-' . $m . '-' . $d;
309: }
310:
311: /**
312: * このオブジェクトを Date 型にキャストします.
313: * 返り値はこのオブジェクトのクローンです.
314: *
315: * @return Date このオブジェクトのクローン
316: */
317: public function toDate()
318: {
319: return new self($this->year, $this->month, $this->date);
320: }
321:
322: /**
323: * このオブジェクトを Datetime 型にキャストします.
324: * この日付の 0 時 0 分を表す Datetime オブジェクトを返します.
325: *
326: * @return Datetime このオブジェクトの時刻表現
327: */
328: public function toDatetime()
329: {
330: return new Datetime($this->year, $this->month, $this->date, 0, 0);
331: }
332:
333: /**
334: * このオブジェクトを Timestamp 型にキャストします.
335: * この日付の 0 時 0 分 0 秒を表す Timestamp オブジェクトを返します.
336: *
337: * @return Timestamp このオブジェクトの時刻表現
338: */
339: public function toTimestamp()
340: {
341: return new Timestamp($this->year, $this->month, $this->date, 0, 0, 0);
342: }
343:
344: /**
345: * この日付の曜日を返します. 返される値は 0 から 6 までの整数で, 0 が日曜, 6 が土曜をあらわします.
346: * それぞれの整数は, このクラスで定義されている各定数に対応しています.
347: *
348: * @return int 曜日 (0 以上 6 以下の整数)
349: *
350: * @see Time::SUNDAY
351: * @see Time::MONDAY
352: * @see Time::TUESDAY
353: * @see Time::WEDNESDAY
354: * @see Time::THURSDAY
355: * @see Time::FRIDAY
356: * @see Time::SATURDAY
357: */
358: public function getDay()
359: {
360: return self::getDayOf($this->year, $this->month, $this->date);
361: }
362:
363: /**
364: * この年がうるう年かどうかを判定します.
365: *
366: * うるう年の判別ルールは以下の通りです.
367: * - 4 で割り切れるはうるう年である
368: * - ただし 100 で割り切れる年はうるう年ではない
369: * - ただし 400 で割り切れる年はうるう年である
370: *
371: * @return bool うるう年である場合に TRUE, それ以外は FALSE
372: */
373: public function isLeapYear()
374: {
375: return self::checkLeapYear($this->year);
376: }
377:
378: /**
379: * 指定された年がうるう年かどうかを判定します.
380: *
381: * @param int $year 判定対象の年
382: * @return bool うるう年である場合に TRUE, それ以外は FALSE
383: */
384: private static function checkLeapYear($year)
385: {
386: if ($year % 4 !== 0) {
387: return false;
388: }
389: if ($year % 100 !== 0) {
390: return true;
391: }
392: return ($year % 400 === 0);
393: }
394:
395: /**
396: * この月の日数を返します.
397: * @return int この月の日数. すなわち, 28 から 31 までの整数.
398: */
399: public function getDateCount()
400: {
401: return self::getDateCountOf($this->year, $this->month);
402: }
403:
404: /**
405: * 指定された月の日数を返します.
406: * @param int $year 年
407: * @param int $month 月
408: * @return int 引数で指定された月の日数. すなわち, 28 から 31 までの整数.
409: */
410: private static function getDateCountOf($year, $month)
411: {
412: switch ($month) {
413: case 1: return 31;
414: case 2: return (self::checkLeapYear($year)) ? 29 : 28;
415: case 3: return 31;
416: case 4: return 30;
417: case 5: return 31;
418: case 6: return 30;
419: case 7: return 31;
420: case 8: return 31;
421: case 9: return 30;
422: case 10: return 31;
423: case 11: return 30;
424: case 12: return 31;
425: }
426: // @codeCoverageIgnoreStart
427: throw new \Exception();
428: // @codeCoverageIgnoreEnd
429: }
430: }
431: