Sfoglia il codice sorgente

[Locale] refactor date formatting into separate transformer classes, early concepts for date parsing

Igor Wiedler 14 anni fa
parent
commit
0e260c1170

+ 35 - 0
src/Symfony/Component/Locale/Stub/DateFormat/AmPmTransformer.php

@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class AmPmTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        return $dateTime->format('A');
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "AM|PM";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array();
+    }
+}

+ 53 - 0
src/Symfony/Component/Locale/Stub/DateFormat/DayOfWeekTransformer.php

@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class DayOfWeekTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $dayOfWeek = $dateTime->format('l');
+        switch ($length) {
+            case 4:
+                return $dayOfWeek;
+            case 5:
+                return $dayOfWeek[0];
+            default:
+                return substr($dayOfWeek, 0, 3);
+        }
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        $dayOfWeek = $dateTime->format('l');
+        switch ($length) {
+            case 4:
+                return 'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday';
+            case 5:
+                return '[MTWFS]';
+            default:
+                return 'Mon|Tue|Wed|Thu|Fri|Sat|Sun';
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'hour' => (int) $matched,
+        );
+    }
+}

+ 38 - 0
src/Symfony/Component/Locale/Stub/DateFormat/DayOfYearTransformer.php

@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class DayOfYearTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $dayOfYear = $dateTime->format('z') + 1;
+        return $this->padLeft($dayOfYear, $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "\d{$length}";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'hour' => (int) $matched,
+        );
+    }
+}

+ 41 - 0
src/Symfony/Component/Locale/Stub/DateFormat/DayTransformer.php

@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class DayTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        return $this->padLeft($dateTime->format('j'), $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        if (1 == $length) {
+            return '\d{1,2}';
+        } else {
+            return "\d{$length}";
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'day' => (int) $matched,
+        );
+    }
+}

+ 36 - 0
src/Symfony/Component/Locale/Stub/DateFormat/EraTransformer.php

@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class EraTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $year = (int) $dateTime->format('Y');
+        return $year >= 0 ? 'AD' : 'BC';
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "AD|BC";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array();
+    }
+}

+ 135 - 0
src/Symfony/Component/Locale/Stub/DateFormat/FullTransformer.php

@@ -0,0 +1,135 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+use Symfony\Component\Locale\Exception\NotImplementedException;
+use Symfony\Component\Locale\Stub\DateFormat\MonthTransformer;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class FullTransformer
+{
+    private $quoteMatch = "'(?:[^']+|'')*'";
+    private $implementedChars = 'MLydGQqhDEaHkKmsz';
+    private $notImplementedChars = 'YuwWFgecSAZvVW';
+    private $regExp;
+
+    private $transformers;
+    private $pattern;
+
+    public function __construct($pattern)
+    {
+        $this->pattern = $pattern;
+
+        $implementedCharsMatch = $this->buildCharsMatch($this->implementedChars);
+        $notImplementedCharsMatch = $this->buildCharsMatch($this->notImplementedChars);
+        $this->regExp = "/($this->quoteMatch|$implementedCharsMatch|$notImplementedCharsMatch)/";
+
+        $this->transformers = array(
+            'M' => new MonthTransformer(),
+            'L' => new MonthTransformer(),
+            'y' => new YearTransformer(),
+            'd' => new DayTransformer(),
+            'G' => new EraTransformer(),
+            'q' => new QuarterTransformer(),
+            'Q' => new QuarterTransformer(),
+            'h' => new Hour1201Transformer(),
+            'D' => new DayOfYearTransformer(),
+            'E' => new DayOfWeekTransformer(),
+            'a' => new AmPmTransformer(),
+            'H' => new Hour2400Transformer(),
+            'K' => new Hour1200Transformer(),
+            'k' => new Hour2401Transformer(),
+            'm' => new MinuteTransformer(),
+            's' => new SecondTransformer(),
+            'z' => new TimeZoneTransformer(),
+        );
+    }
+
+    public function format(\DateTime $dateTime)
+    {
+        $that = $this;
+
+        $formatted = preg_replace_callback($this->regExp, function($matches) use ($that, $dateTime) {
+            return $that->formatReplace($matches[0], $dateTime);
+        }, $this->pattern);
+
+        return $formatted;
+    }
+
+    public function formatReplace($dateChars, $dateTime)
+    {
+        $length = strlen($dateChars);
+
+        if ("'" === $dateChars[0]) {
+            if (preg_match("/^'+$/", $dateChars)) {
+                return str_replace("''", "'", $dateChars);
+            }
+            return str_replace("''", "'", substr($dateChars, 1, -1));
+        }
+
+        if (isset($this->transformers[$dateChars[0]])) {
+            $transformer = $this->transformers[$dateChars[0]];
+            return $transformer->format($dateTime, $length);
+        } else {
+            // handle unimplemented characters
+            if (false !== strpos($this->notImplementedChars, $dateChars[0])) {
+                throw new NotImplementedException(sprintf("Unimplemented date character '%s' in format '%s'", $dateChars[0], $this->pattern));
+            }
+        }
+    }
+
+    public function getReverseMatchingRegExp($pattern)
+    {
+        $reverseMatchingRegExp = preg_replace_callback($this->regExp, function($matches) {
+            if (isset($this->transformers[$dateChars[0]])) {
+                $transformer = $this->transformers[$dateChars[0]];
+                return $transformer->getReverseMatchingRegExp($length);
+            }
+        }, $this->pattern);
+
+        return $reverseMatchingRegExp;
+    }
+
+    public function parse($pattern, $value)
+    {
+        $reverseMatchingRegExp = $this->getReverseMatchingRegExp($pattern);
+
+        $options = array();
+
+        if (preg_match($reverseMatchingRegExp, $value, $matches)) {
+            foreach ($this->transformers as $char => $transformer) {
+                if (isset($matches[$char])) {
+                    $options = array_merge($options, $transformer->extractDateOptions($char, strlen($matches[$char])));
+                }
+            }
+
+            return $this->calculateUnixTimestamp($options);
+        } else {
+            throw new \InvalidArgumentException(sprintf("Failed to match value '%s' with pattern '%s'", $value, $pattern));
+        }
+    }
+
+    protected function buildCharsMatch($specialChars)
+    {
+        $specialCharsArray = str_split($specialChars);
+
+        $specialCharsMatch = implode('|', array_map(function($char) {
+            return $char . '+';
+        }, $specialCharsArray));
+
+        return $specialCharsMatch;
+    }
+}

+ 39 - 0
src/Symfony/Component/Locale/Stub/DateFormat/Hour1200Transformer.php

@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class Hour1200Transformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $hourOfDay = $dateTime->format('g');
+        $hourOfDay = ('12' == $hourOfDay) ? '0' : $hourOfDay;
+        return $this->padLeft($hourOfDay, $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "\d{1,$length}";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'hour' => (int) $matched,
+        );
+    }
+}

+ 37 - 0
src/Symfony/Component/Locale/Stub/DateFormat/Hour1201Transformer.php

@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class Hour1201Transformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        return $this->padLeft($dateTime->format('g'), $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "\d{1,$length}";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'hour' => (int) $matched,
+        );
+    }
+}

+ 37 - 0
src/Symfony/Component/Locale/Stub/DateFormat/Hour2400Transformer.php

@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class Hour2400Transformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        return $this->padLeft($dateTime->format('G'), $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "\d{1,$length}";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'hour' => (int) $matched,
+        );
+    }
+}

+ 39 - 0
src/Symfony/Component/Locale/Stub/DateFormat/Hour2401Transformer.php

@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class Hour2401Transformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $hourOfDay = $dateTime->format('G');
+        $hourOfDay = ('0' == $hourOfDay) ? '24' : $hourOfDay;
+        return $this->padLeft($hourOfDay, $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return "\d{1,$length}";
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'hour' => (int) $matched,
+        );
+    }
+}

+ 42 - 0
src/Symfony/Component/Locale/Stub/DateFormat/MinuteTransformer.php

@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class MinuteTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $minuteOfHour = (int) $dateTime->format('i');
+        return $this->padLeft($minuteOfHour, $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        if (1 == $length) {
+            return '\d{1,2}';
+        } else {
+            return "\d{$length}";
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'minute' => (int) $matched,
+        );
+    }
+}

+ 71 - 0
src/Symfony/Component/Locale/Stub/DateFormat/MonthTransformer.php

@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class MonthTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $matchLengthMap = array(
+            1   => 'n',
+            2   => 'm',
+            3   => 'M',
+            4   => 'F',
+        );
+
+        if (isset($matchLengthMap[$length])) {
+           return $dateTime->format($matchLengthMap[$length]);
+        } else if (5 == $length) {
+            return substr($dateTime->format('M'), 0, 1);
+        } else {
+            return $this->padLeft($dateTime->format('m'), $length);
+        }
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        $months = array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
+
+        switch ($length) {
+            case 1:
+                return '\d{1,2}';
+                break;
+            case 3:
+                $shortMonths = array_map(function($month) {
+                    return substr($month, 0, 2);
+                }, $months);
+                return implode('|', $shortMonths);
+                break;
+            case 4:
+                return implode('|', $months);
+                break;
+            case 5:
+                return '[JFMASOND]';
+                break;
+            default:
+                return "\d{$length}";
+                break;
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'month' => (int) $matched,
+        );
+    }
+}

+ 54 - 0
src/Symfony/Component/Locale/Stub/DateFormat/QuarterTransformer.php

@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class QuarterTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $month = (int) $dateTime->format('n');
+        $quarter = (int) floor(($month - 1) / 3) + 1;
+        switch ($length) {
+            case 1:
+            case 2:
+                return $this->padLeft($quarter, $length);
+            case 3:
+                return 'Q' . $quarter;
+            default:
+                $map = array(1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter');
+                return $map[$quarter];
+        }
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        switch ($length) {
+            case 1:
+            case 2:
+                return "\d{$length}";
+            case 3:
+                return 'Q\d';
+            default:
+                return '(?:1st|2nd|3rd|4th) quarter';
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array();
+    }
+}

+ 42 - 0
src/Symfony/Component/Locale/Stub/DateFormat/SecondTransformer.php

@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class SecondTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        $secondOfMinute = (int) $dateTime->format('s');
+        return $this->padLeft($secondOfMinute, $length);
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        if (1 == $length) {
+            return '\d{1,2}';
+        } else {
+            return "\d{$length}";
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'second' => (int) $matched,
+        );
+    }
+}

+ 37 - 0
src/Symfony/Component/Locale/Stub/DateFormat/TimeZoneTransformer.php

@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class TimeZoneTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        return $dateTime->format('\G\M\TP');
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        return 'GMT[+-]\d{2}:\d{2}';
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'timezone' => (int) $matched,
+        );
+    }
+}

+ 29 - 0
src/Symfony/Component/Locale/Stub/DateFormat/Transformer.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+abstract class Transformer
+{
+    abstract public function format(\DateTime $dateTime, $length);
+    abstract public function getReverseMatchingRegExp($length);
+    abstract public function extractDateOptions($matched, $length);
+
+    protected function padLeft($value, $length)
+    {
+        return str_pad($value, $length, '0', STR_PAD_LEFT);
+    }
+}

+ 45 - 0
src/Symfony/Component/Locale/Stub/DateFormat/YearTransformer.php

@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Locale\Stub\DateFormat;
+
+/**
+ * Parser and formatter for date formats
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class YearTransformer extends Transformer
+{
+    public function format(\DateTime $dateTime, $length)
+    {
+        if (2 == $length) {
+            return $dateTime->format('y');
+        } else {
+            return $this->padLeft($dateTime->format('Y'), $length);
+        }
+    }
+
+    public function getReverseMatchingRegExp($length)
+    {
+        if (2 == $length) {
+            return '\d{2}';
+        } else {
+            return "\d{1,$length}";
+        }
+    }
+
+    public function extractDateOptions($matched, $length)
+    {
+        return array(
+            'year' => (int) $matched,
+        );
+    }
+}

+ 2 - 153
src/Symfony/Component/Locale/Stub/StubIntlDateFormatter.php

@@ -120,149 +120,9 @@ class StubIntlDateFormatter
         $dateTime->setTimestamp($timestamp);
         $dateTime->setTimezone($this->dateTimeZone);
 
-        $quoteMatch = "'(?:[^']+|'')*'";
-        $implementedCharsMatch = $this->buildCharsMatch('MLydGQqhDEaHkKmsz');
-
-        $notImplementedChars = 'YuwWFgecSAZvVW';
-        $notImplementedCharsMatch = $this->buildCharsMatch($notImplementedChars);
-
-        $regExp = "/($quoteMatch|$implementedCharsMatch|$notImplementedCharsMatch)/";
-
         $pattern = $this->getPattern();
-
-        $callback = function($matches) use ($dateTime, $notImplementedChars, $pattern) {
-            $dateChars = $matches[0];
-            $length = strlen($dateChars);
-
-            if ("'" === $dateChars[0]) {
-                if (preg_match("/^'+$/", $dateChars)) {
-                    return str_replace("''", "'", $dateChars);
-                }
-                return str_replace("''", "'", substr($dateChars, 1, -1));
-            }
-
-            switch ($dateChars[0]) {
-                case 'M':
-                case 'L':
-                    $matchLengthMap = array(
-                        1   => 'n',
-                        2   => 'm',
-                        3   => 'M',
-                        4   => 'F',
-                    );
-
-                    if (isset($matchLengthMap[$length])) {
-                       return $dateTime->format($matchLengthMap[$length]);
-                    } else if (5 == $length) {
-                        return substr($dateTime->format('M'), 0, 1);
-                    } else {
-                        return str_pad($dateTime->format('m'), $length, '0', STR_PAD_LEFT);
-                    }
-                    break;
-
-                case 'y':
-                    if (2 == $length) {
-                       return $dateTime->format('y');
-                    } else {
-                        return str_pad($dateTime->format('Y'), $length, '0', STR_PAD_LEFT);
-                    }
-                    break;
-
-                case 'd':
-                    return str_pad($dateTime->format('j'), $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'G':
-                    $year = (int) $dateTime->format('Y');
-                    return $year >= 0 ? 'AD' : 'BC';
-                    break;
-
-                case 'q':
-                case 'Q':
-                    $month = (int) $dateTime->format('n');
-                    $quarter = (int) floor(($month - 1) / 3) + 1;
-                    switch ($length) {
-                        case 1:
-                        case 2:
-                            return str_pad($quarter, $length, '0', STR_PAD_LEFT);
-                            break;
-                        case 3:
-                            return 'Q' . $quarter;
-                            break;
-                        default:
-                            $map = array(1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter');
-                            return $map[$quarter];
-                            break;
-                    }
-                    break;
-
-                case 'h':
-                    return str_pad($dateTime->format('g'), $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'D':
-                    $dayOfYear = $dateTime->format('z') + 1;
-                    return str_pad($dayOfYear, $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'E':
-                    $dayOfWeek = $dateTime->format('l');
-                    switch ($length) {
-                        case 4:
-                            return $dayOfWeek;
-                            break;
-                        case 5:
-                            return $dayOfWeek[0];
-                            break;
-                        default:
-                            return substr($dayOfWeek, 0, 3);
-                    }
-                    break;
-
-                case 'a':
-                    return $dateTime->format('A');
-                    break;
-
-                case 'H':
-                    return str_pad($dateTime->format('G'), $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'k':
-                    $hourOfDay = $dateTime->format('G');
-                    $hourOfDay = ('0' == $hourOfDay) ? '24' : $hourOfDay;
-                    return str_pad($hourOfDay, $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'K':
-                    $hourOfDay = $dateTime->format('g');
-                    $hourOfDay = ('12' == $hourOfDay) ? '0' : $hourOfDay;
-                    return str_pad($hourOfDay, $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'm':
-                    $minuteOfHour = (int) $dateTime->format('i');
-                    return str_pad($minuteOfHour, $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 's':
-                    $secondOfMinute = (int) $dateTime->format('s');
-                    return str_pad($secondOfMinute, $length, '0', STR_PAD_LEFT);
-                    break;
-
-                case 'z':
-                    return $dateTime->format('\G\M\TP');
-                    break;
-
-                default:
-                    // handle unimplemented characters
-                    if (false !== strpos($notImplementedChars, $dateChars[0])) {
-                        throw new NotImplementedException(sprintf("Unimplemented date character '%s' in format '%s'", $dateChars[0], $pattern));
-                    }
-                    break;
-            }
-        };
-
-        $formatted = preg_replace_callback($regExp, $callback, $pattern);
+        $transformer = new DateFormat\FullTransformer($pattern);
+        $formatted = $transformer->format($dateTime);
 
         return $formatted;
     }
@@ -462,17 +322,6 @@ class StubIntlDateFormatter
         return $dateTime;
     }
 
-    protected function buildCharsMatch($specialChars)
-    {
-        $specialCharsArray = str_split($specialChars);
-
-        $specialCharsMatch = implode('|', array_map(function($char) {
-            return $char . '+';
-        }, $specialCharsArray));
-
-        return $specialCharsMatch;
-    }
-
     protected function getDefaultPattern()
     {
         $patternParts = array();