Overview

Namespaces

  • Peach
    • DF
    • DT
    • Http
      • Body
      • Header
    • Markup
    • Util

Classes

  • Peach\DF\Base64Codec
  • Peach\DF\CodecChain
  • Peach\DF\JsonCodec
  • Peach\DF\SerializationCodec
  • Peach\DF\Utf8Codec
  • Peach\DT\AbstractTime
  • Peach\DT\Clock
  • Peach\DT\Date
  • Peach\DT\Datetime
  • Peach\DT\DefaultClock
  • Peach\DT\FixedClock
  • Peach\DT\FormatWrapper
  • Peach\DT\HttpDateFormat
  • Peach\DT\OffsetClock
  • Peach\DT\ShiftFormat
  • Peach\DT\SimpleFormat
  • Peach\DT\TimeEquator
  • Peach\DT\Timestamp
  • Peach\DT\TimeWrapper
  • Peach\DT\UnixTimeFormat
  • Peach\DT\Util
  • Peach\DT\W3cDatetimeFormat
  • Peach\Http\Body
  • Peach\Http\Body\CodecRenderer
  • Peach\Http\Body\StringRenderer
  • Peach\Http\DefaultEndpoint
  • Peach\Http\Header\CookieItem
  • Peach\Http\Header\CookieOptions
  • Peach\Http\Header\HttpDate
  • Peach\Http\Header\NoField
  • Peach\Http\Header\QualityValues
  • Peach\Http\Header\Raw
  • Peach\Http\Header\SetCookie
  • Peach\Http\Header\Status
  • Peach\Http\Request
  • Peach\Http\Response
  • Peach\Http\Util
  • Peach\Markup\AbstractHelper
  • Peach\Markup\AbstractRenderer
  • Peach\Markup\BaseHelper
  • Peach\Markup\BreakControlWrapper
  • Peach\Markup\Builder
  • Peach\Markup\Code
  • Peach\Markup\Comment
  • Peach\Markup\ContainerElement
  • Peach\Markup\Context
  • Peach\Markup\DebugBuilder
  • Peach\Markup\DebugContext
  • Peach\Markup\DefaultBreakControl
  • Peach\Markup\DefaultBuilder
  • Peach\Markup\DefaultContext
  • Peach\Markup\Element
  • Peach\Markup\EmptyElement
  • Peach\Markup\HelperObject
  • Peach\Markup\HtmlHelper
  • Peach\Markup\Indent
  • Peach\Markup\MinimalBreakControl
  • Peach\Markup\NameBreakControl
  • Peach\Markup\NameValidator
  • Peach\Markup\NodeList
  • Peach\Markup\None
  • Peach\Markup\SgmlRenderer
  • Peach\Markup\Text
  • Peach\Markup\XmlRenderer
  • Peach\Util\AbstractMapEntry
  • Peach\Util\ArrayMap
  • Peach\Util\ArrayMapEntry
  • Peach\Util\Arrays
  • Peach\Util\DefaultComparator
  • Peach\Util\DefaultEquator
  • Peach\Util\HashMap
  • Peach\Util\HashMapEntry
  • Peach\Util\Strings
  • Peach\Util\Values

Interfaces

  • Peach\DF\Codec
  • Peach\DT\Format
  • Peach\DT\Time
  • Peach\Http\BodyRenderer
  • Peach\Http\Endpoint
  • Peach\Http\HeaderField
  • Peach\Http\MultiHeaderField
  • Peach\Http\SingleHeaderField
  • Peach\Markup\BreakControl
  • Peach\Markup\Component
  • Peach\Markup\Container
  • Peach\Markup\Helper
  • Peach\Markup\Node
  • Peach\Markup\Renderer
  • Peach\Util\Comparable
  • Peach\Util\Comparator
  • Peach\Util\Equator
  • Peach\Util\Map
  • Peach\Util\MapEntry
  • Overview
  • Namespace
  • Class
  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: 
 30: use Peach\Util\ArrayMap;
 31: use Peach\DT\SimpleFormat\Pattern;
 32: use Peach\DT\SimpleFormat\Numbers;
 33: use Peach\DT\SimpleFormat\Raw;
 34: 
 35: /**
 36:  * Java の
 37:  * {@link http://docs.oracle.com/javase/jp/7/api/java/text/SimpleDateFormat.html SimpleDateFormat}
 38:  * と同じような使い勝手で, ユーザー定義の書式を扱うことができるクラスです.
 39:  * 日付・時刻のパターンは {@link www.php.net/manual/function.date.php date()} の一部を採用しています.
 40:  * 
 41:  * - Y: 年 (4桁固定)
 42:  * - m: 月 (2桁固定)
 43:  * - n: 月 (1~2桁)
 44:  * - d: 日 (2桁固定)
 45:  * - j: 日 (1~2桁)
 46:  * - H: 時 (2桁固定)
 47:  * - G: 時 (1~2桁)
 48:  * - i: 分 (2桁固定)
 49:  * - s: 秒 (2桁固定)
 50:  * 
 51:  * 以下は, このクラス独自の拡張パターンです.
 52:  * 
 53:  * - f: 分 (1~2桁)
 54:  * - b: 秒 (1~2桁)
 55:  * - E: 曜日 (後述)
 56:  * 
 57:  * "2012年5月21日(月)" のように曜日を含む書式の入出力を行う場合は,
 58:  * コンストラクタの第 2 引数に曜日文字列の一覧を指定してください.
 59:  * 以下に例を示します.
 60:  * <code>
 61:  * $format = new SimpleFormat("Y年n月j日(E)", array("日", "月", "火", "水", "木", "金", "土"));
 62:  * </code>
 63:  * 第 2 引数を省略した場合はデフォルト値として array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
 64:  * が適用されます.
 65:  * 
 66:  * パターンを繋げて記述する場合は (例: "Ymd_His" など)
 67:  * 必ず固定長のパターンを使用してください.
 68:  * 可変長 (n, j, G など) のパターンは常に最長一致でマッチングするため,
 69:  * 繋げて記述した際にパースに失敗する可能性があります.
 70:  * 
 71:  * このクラスは, 月の文字列表記 (例えば "Jan", "Feb" など) のためのパターンをサポートしません.
 72:  * そのようなフォーマットが必要となった場合は, 独自の Format クラスを定義する必要があります.
 73:  * 
 74:  * パースまたは書式化を行う際, 情報が足りない場合はデフォルト値が指定されます.
 75:  * 年・月・日については現在の日付, 時・分・秒については 0 が適用されます.
 76:  * 具体的には, 以下のような状況が相当します.
 77:  * 
 78:  * 1. パースする際, オブジェクトを構成するために必要な情報をパターン文字列が網羅していなかった.
 79:  *    (例: "m/d" というパターンをパースした場合,
 80:  *   「年」の情報がパターン文字列に含まれていないため, 現在の年が適用されます)
 81:  * 2. 時間オブジェクトを書式化する際に, パターン文字列に含まれるフィールドを
 82:  *    そのオブジェクトが持っていなかった.
 83:  *    (例: "Y/m/d H:i:s" というパターンで Date オブジェクトを書式化した場合,
 84:  *    Date は時刻の情報を持たないため, 時刻部分は 00:00:00 となります.)
 85:  * 
 86:  * PHP の date() 関数の実装と同様に,
 87:  * バックスラッシュをつけることでパターン文字列が展開されるのを抑制することができます.
 88:  * 
 89:  * このクラスはイミュータブルです. 一つのオブジェクトを複数の箇所で使いまわすことが出来ます.
 90:  */
 91: class SimpleFormat implements Format
 92: {
 93:     /**
 94:      * parse または format に使うパターン文字列です.
 95:      * @var string
 96:      */
 97:     private $format;
 98:     
 99:     /**
100:      * 曜日文字列の一覧を表す, 長さ 7 の配列です.
101:      * @var array
102:      */
103:     private $dayList;
104:     
105:     /**
106:      * Pattern オブジェクトの配列です.
107:      * キーが "Y", "n" などのパターン文字, 値がその文字に該当する
108:      * Pattern オブジェクトとなります.
109:      * 
110:      * @var array
111:      */
112:     private $patternList;
113:     
114:     /**
115:      * パターン文字列を分解した結果をあらわします.
116:      * 
117:      * @var array
118:      */
119:     private $context;
120:     
121:     /**
122:      * 指定されたパターン文字列で SimpleFormat を初期化します.
123:      * 
124:      * @param string $pattern パターン文字列
125:      * @param array  $dayList 曜日文字列の配列. デフォルトは array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
126:      */
127:     public function __construct($pattern, array $dayList = array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"))
128:     {
129:         $format            = strval($pattern);
130:         $this->format      = $format;
131:         $this->dayList     = $this->initDayList($dayList);
132:         $this->patternList = $this->initPatternList($this->dayList);
133:         $this->context     = $this->createContext($format);
134:     }
135:     
136:     /**
137:      * 曜日文字列を初期化します.
138:      * もしも配列の長さが 7 より大きかった場合, 8 個目以降の要素は無視されます.
139:      * 
140:      * @param  array $dayList 引数
141:      * @return array          曜日文字列の配列
142:      * @throws \InvalidArgumentException 配列の長さが 7 未満であるか, または空文字列が含まれている場合
143:      */
144:     private function initDayList(array $dayList)
145:     {
146:         $values = array_slice(array_values($dayList), 0, 7);
147:         $count  = count($values);
148:         if ($count !== 7) {
149:             throw new \InvalidArgumentException("Invalid array count({$count}). Expected: 7");
150:         }
151:         for ($i = 0; $i < 7; $i++) {
152:             $value = $values[$i];
153:             if (!strlen($value)) {
154:                 throw new \InvalidArgumentException("Daystring is empty at index {$i}");
155:             }
156:         }
157:         return $values;
158:     }
159:     
160:     /**
161:      * パターン文字の一覧を作成します.
162:      * @param  array $dayList 曜日文字列の配列
163:      * @return array          Pattern オブジェクトの配列
164:      */
165:     private function initPatternList(array $dayList)
166:     {
167:         $patternList      = $this->getDefaultPatternList();
168:         $patternList["E"] = new Raw($dayList);
169:         return $patternList;
170:     }
171:     
172:     /**
173:      * このオブジェクトのパターン文字列を返します.
174:      * @return string パターン文字列
175:      */
176:     public function getFormat()
177:     {
178:         return $this->format;
179:     }
180:     
181:     /**
182:      * 指定された文字列を解析し, Date に変換します.
183:      * @param  string $format 解析対象の文字列
184:      * @return Date           解析結果
185:      */
186:     public function parseDate($format)
187:     {
188:         $d = Date::now();
189:         return $d->setAll($this->interpret($format));
190:     }
191:     
192:     /**
193:      * 指定された文字列を解析し, Datetime に変換します.
194:      * @param  string $format 解析対象の文字列
195:      * @return Datetime       解析結果
196:      */
197:     public function parseDatetime($format)
198:     {
199:         $d = Date::now();
200:         return $d->toDatetime()->setAll($this->interpret($format));
201:     }
202:     
203:     /**
204:      * 指定された文字列を解析し, Timestamp に変換します.
205:      * @param  string $format 解析対象の文字列
206:      * @return Date           解析結果
207:      */
208:     public function parseTimestamp($format)
209:     {
210:         $d = Date::now();
211:         return $d->toTimestamp()->setAll($this->interpret($format));
212:     }
213:     
214:     /**
215:      * 指定された Date オブジェクトを書式化します.
216:      * @param  Date $d 書式化対象の時間オブジェクト
217:      * @return string  このフォーマットによる文字列表現
218:      */
219:     public function formatDate(Date $d)
220:     {
221:         return $this->formatTimestamp($d->toTimestamp());
222:     }
223:     
224:     /**
225:      * 指定された Datetime オブジェクトを書式化します.
226:      * @param  Datetime $d 書式化対象の時間オブジェクト
227:      * @return string      このフォーマットによる文字列表現
228:      */
229:     public function formatDatetime(Datetime $d)
230:     {
231:         return $this->formatTimestamp($d->toTimestamp());
232:     }
233:     
234:     /**
235:      * 指定された Timestamp オブジェクトを書式化します.
236:      * @param  Timestamp $d 書式化対象の時間オブジェクト
237:      * @return string       このフォーマットによる文字列表現
238:      */
239:     public function formatTimestamp(Timestamp $d)
240:     {
241:         $patternList = $this->patternList;
242:         $result      = "";
243:         foreach ($this->context as $part) {
244:             $buf = array_key_exists($part, $patternList) ? $this->formatKey($d, $part) : stripslashes($part);
245:             $result .= $buf;
246:         }
247:         return $result;
248:     }
249:     
250:     /**
251:      * パターン一覧を返します.
252:      * キーが変換文字, 値がその文字に対応する Pattern オブジェクトとなります.
253:      * 
254:      * @return array
255:      * @codeCoverageIgnore
256:      */
257:     private function getDefaultPatternList()
258:     {
259:         static $patterns = null;
260:         if (!isset($patterns)) {
261:             $fixed4   = "\\d{4}";
262:             $fixed2   = "\\d{2}";
263:             $var2     = "[1-5][0-9]|[0-9]";
264:             $varM     = "1[0-2]|[1-9]";
265:             $varD     = "3[0-1]|[1-2][0-9]|[0-9]";
266:             $varH     = "2[0-4]|1[0-9]|[0-9]";
267:             $patterns = array(
268:                 "Y" => new Numbers("year",   $fixed4),
269:                 "m" => new Numbers("month",  $fixed2),
270:                 "n" => new Numbers("month",  $varM),
271:                 "d" => new Numbers("date",   $fixed2),
272:                 "j" => new Numbers("date",   $varD),
273:                 "H" => new Numbers("hour",   $fixed2),
274:                 "G" => new Numbers("hour",   $varH),
275:                 "i" => new Numbers("minute", $fixed2),
276:                 "f" => new Numbers("minute", $var2),
277:                 "s" => new Numbers("second", $fixed2),
278:                 "b" => new Numbers("second", $var2),
279:             );
280:         }
281:         return $patterns;
282:     }
283:     
284:     /**
285:      * 指定された文字列に相当する Pattern オブジェクトを返します.
286:      * 
287:      * @param  string $part
288:      * @return Pattern
289:      * @codeCoverageIgnore
290:      */
291:     private function getPatternByPart($part)
292:     {
293:         $patterns = $this->patternList;
294:         return array_key_exists($part, $patterns) ? $patterns[$part] : new Raw(array(stripslashes($part)));
295:     }
296:     
297:     /**
298:      * 指定されたパターン文字を, 対応するフィールドの値に変換します.
299:      * 
300:      * @param  Time   $d   変換対象の時間オブジェクト
301:      * @param  string $key パターン文字 ("Y", "m", "d" など)
302:      * @return int         変換結果
303:      * @throws \Exception  不正なパターン文字が指定された場合
304:      */
305:     private function formatKey(Time $d, $key)
306:     {
307:         $year  = $d->get("year");
308:         $month = $d->get("month");
309:         $date  = $d->get("date");
310:         $hour  = $d->get("hour");
311:         $min   = $d->get("minute");
312:         $sec   = $d->get("second");
313:         
314:         switch ($key) {
315:             case "Y":
316:                 return str_pad($year,  4, "0", STR_PAD_LEFT);
317:             case "m":
318:                 return str_pad($month, 2, "0", STR_PAD_LEFT);
319:             case "n":
320:                 return $month;
321:             case "d":
322:                 return str_pad($date,  2, "0", STR_PAD_LEFT);
323:             case "j":
324:                 return $date;
325:             case "H":
326:                 return str_pad($hour,  2, "0", STR_PAD_LEFT);
327:             case "G":
328:                 return $hour;
329:             case "i":
330:                 return str_pad($min,   2, "0", STR_PAD_LEFT);
331:             case "f":
332:                 return $min;
333:             case "s":
334:                 return str_pad($sec,   2, "0", STR_PAD_LEFT);
335:             case "b":
336:                 return $sec;
337:             case "E":
338:                 return $this->dayList[$d->getDay()];
339:         }
340:         
341:         // @codeCoverageIgnoreStart
342:         throw new \Exception("Illegal pattern: " . $key);
343:         // @codeCoverageIgnoreEnd
344:     }
345:     
346:     /**
347:      * 指定されたパターン文字列 (例えば "Y/m/d" など) を構文解析します.
348:      * 
349:      * @param  string $format パターン文字列
350:      * @return array          解析結果
351:      */
352:     private function createContext($format)
353:     {
354:         $patternList = $this->patternList;
355:         $result      = array();
356:         $current     = "";
357:         $escaped     = false;
358:         for ($i = 0, $length = strlen($format); $i < $length; $i ++) {
359:             $chr = substr($format, $i, 1);
360:             if ($escaped) {
361:                 $current .= $chr;
362:                 $escaped = false;
363:             } else if ($chr === "\\") {
364:                 $current .= $chr;
365:                 $escaped = true;
366:             } else if (array_key_exists($chr, $patternList)) {
367:                 if (strlen($current)) {
368:                     $result[] = $current;
369:                     $current = "";
370:                 }
371:                 $result[] = $chr;
372:             } else {
373:                 $current .= $chr;
374:             }
375:         }
376:         if (strlen($current)) {
377:             $result[] = $current;
378:         }
379:         return $result;
380:     }
381:     
382:     /**
383:      * 指定されたテキストを構文解析します.
384:      * 
385:      * @param  string $text 解析対象の文字列
386:      * @return ArrayMap     構文解析した結果
387:      */
388:     private function interpret($text)
389:     {
390:         $input       = $text;
391:         $result      = new ArrayMap();
392:         $matched     = null;
393:         foreach ($this->context as $part) {
394:             $pattern = $this->getPatternByPart($part);
395:             $matched = $pattern->match($input);
396:             if ($matched === null) {
397:                 throw $this->createFormatException($input, $this->format);
398:             }
399:             $pattern->apply($result, $matched);
400:             $input = substr($input, strlen($matched));
401:         }
402:         return $result;
403:     }
404:     
405:     /**
406:      * 文字列を時間オブジェクトに変換する際に, 不正な文字列が指定されたことをあらわす例外を生成します.
407:      * 
408:      * @param  string $format   指定された文字列
409:      * @param  string $expected 想定されるパターン文字列
410:      * @return \InvalidArgumentException
411:      */
412:     private function createFormatException($format, $expected)
413:     {
414:         return new \InvalidArgumentException("Illegal format({$format}). Expected: {$expected}");
415:     }
416: }
417: 
PEACH2 API documentation generated by ApiGen