/library/Zend/Locale/Format.php
PHP | 1265 lines | 924 code | 108 blank | 233 comment | 249 complexity | 11910485c316af352ce0484bfa136b09 MD5 | raw file
Possible License(s): AGPL-1.0
Large files files are truncated, but you can click here to view the full file
1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category Zend
16 * @package Zend_Locale
17 * @subpackage Format
18 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
19 * @version $Id: Format.php 24594 2012-01-05 21:27:01Z matthew $
20 * @license http://framework.zend.com/license/new-bsd New BSD License
21 */
22
23/**
24 * include needed classes
25 */
26require_once 'Zend/Locale/Data.php';
27
28/**
29 * @category Zend
30 * @package Zend_Locale
31 * @subpackage Format
32 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
33 * @license http://framework.zend.com/license/new-bsd New BSD License
34 */
35class Zend_Locale_Format
36{
37 const STANDARD = 'auto';
38
39 private static $_options = array('date_format' => null,
40 'number_format' => null,
41 'format_type' => 'iso',
42 'fix_date' => false,
43 'locale' => null,
44 'cache' => null,
45 'disableCache' => false,
46 'precision' => null);
47
48 /**
49 * Sets class wide options, if no option was given, the actual set options will be returned
50 * The 'precision' option of a value is used to truncate or stretch extra digits. -1 means not to touch the extra digits.
51 * The 'locale' option helps when parsing numbers and dates using separators and month names.
52 * The date format 'format_type' option selects between CLDR/ISO date format specifier tokens and PHP's date() tokens.
53 * The 'fix_date' option enables or disables heuristics that attempt to correct invalid dates.
54 * The 'number_format' option can be used to specify a default number format string
55 * The 'date_format' option can be used to specify a default date format string, but beware of using getDate(),
56 * checkDateFormat() and getTime() after using setOptions() with a 'format'. To use these four methods
57 * with the default date format for a locale, use array('date_format' => null, 'locale' => $locale) for their options.
58 *
59 * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false,
60 * locale = Zend_Locale | locale string, precision = whole number between -1 and 30
61 * @throws Zend_Locale_Exception
62 * @return Options array if no option was given
63 */
64 public static function setOptions(array $options = array())
65 {
66 self::$_options = self::_checkOptions($options) + self::$_options;
67 return self::$_options;
68 }
69
70 /**
71 * Internal function for checking the options array of proper input values
72 * See {@link setOptions()} for details.
73 *
74 * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false,
75 * locale = Zend_Locale | locale string, precision = whole number between -1 and 30
76 * @throws Zend_Locale_Exception
77 * @return Options array if no option was given
78 */
79 private static function _checkOptions(array $options = array())
80 {
81 if (count($options) == 0) {
82 return self::$_options;
83 }
84 foreach ($options as $name => $value) {
85 $name = strtolower($name);
86 if ($name !== 'locale') {
87 if (gettype($value) === 'string') {
88 $value = strtolower($value);
89 }
90 }
91
92 switch($name) {
93 case 'number_format' :
94 if ($value == Zend_Locale_Format::STANDARD) {
95 $locale = self::$_options['locale'];
96 if (isset($options['locale'])) {
97 $locale = $options['locale'];
98 }
99 $options['number_format'] = Zend_Locale_Data::getContent($locale, 'decimalnumber');
100 } else if ((gettype($value) !== 'string') and ($value !== NULL)) {
101 require_once 'Zend/Locale/Exception.php';
102 throw new Zend_Locale_Exception("Unknown number format type '" . gettype($value) . "'. "
103 . "Format '$value' must be a valid number format string.");
104 }
105 break;
106
107 case 'date_format' :
108 if ($value == Zend_Locale_Format::STANDARD) {
109 $locale = self::$_options['locale'];
110 if (isset($options['locale'])) {
111 $locale = $options['locale'];
112 }
113 $options['date_format'] = Zend_Locale_Format::getDateFormat($locale);
114 } else if ((gettype($value) !== 'string') and ($value !== NULL)) {
115 require_once 'Zend/Locale/Exception.php';
116 throw new Zend_Locale_Exception("Unknown dateformat type '" . gettype($value) . "'. "
117 . "Format '$value' must be a valid ISO or PHP date format string.");
118 } else {
119 if (((isset($options['format_type']) === true) and ($options['format_type'] == 'php')) or
120 ((isset($options['format_type']) === false) and (self::$_options['format_type'] == 'php'))) {
121 $options['date_format'] = Zend_Locale_Format::convertPhpToIsoFormat($value);
122 }
123 }
124 break;
125
126 case 'format_type' :
127 if (($value != 'php') && ($value != 'iso')) {
128 require_once 'Zend/Locale/Exception.php';
129 throw new Zend_Locale_Exception("Unknown date format type '$value'. Only 'iso' and 'php'"
130 . " are supported.");
131 }
132 break;
133
134 case 'fix_date' :
135 if (($value !== true) && ($value !== false)) {
136 require_once 'Zend/Locale/Exception.php';
137 throw new Zend_Locale_Exception("Enabling correction of dates must be either true or false"
138 . "(fix_date='$value').");
139 }
140 break;
141
142 case 'locale' :
143 $options['locale'] = Zend_Locale::findLocale($value);
144 break;
145
146 case 'cache' :
147 if ($value instanceof Zend_Cache_Core) {
148 Zend_Locale_Data::setCache($value);
149 }
150 break;
151
152 case 'disablecache' :
153 Zend_Locale_Data::disableCache($value);
154 break;
155
156 case 'precision' :
157 if ($value === NULL) {
158 $value = -1;
159 }
160
161 if (($value < -1) || ($value > 30)) {
162 require_once 'Zend/Locale/Exception.php';
163 throw new Zend_Locale_Exception("'$value' precision is not a whole number less than 30.");
164 }
165 break;
166
167 default:
168 require_once 'Zend/Locale/Exception.php';
169 throw new Zend_Locale_Exception("Unknown option: '$name' = '$value'");
170 break;
171
172 }
173 }
174
175 return $options;
176 }
177
178 /**
179 * Changes the numbers/digits within a given string from one script to another
180 * 'Decimal' representated the stardard numbers 0-9, if a script does not exist
181 * an exception will be thrown.
182 *
183 * Examples for conversion from Arabic to Latin numerals:
184 * convertNumerals('١١٠ Tests', 'Arab'); -> returns '100 Tests'
185 * Example for conversion from Latin to Arabic numerals:
186 * convertNumerals('100 Tests', 'Latn', 'Arab'); -> returns '١١٠ Tests'
187 *
188 * @param string $input String to convert
189 * @param string $from Script to parse, see {@link Zend_Locale::getScriptList()} for details.
190 * @param string $to OPTIONAL Script to convert to
191 * @return string Returns the converted input
192 * @throws Zend_Locale_Exception
193 */
194 public static function convertNumerals($input, $from, $to = null)
195 {
196 if (!self::_getUniCodeSupport()) {
197 trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE);
198 }
199
200 $from = strtolower($from);
201 $source = Zend_Locale_Data::getContent('en', 'numberingsystem', $from);
202 if (empty($source)) {
203 require_once 'Zend/Locale/Exception.php';
204 throw new Zend_Locale_Exception("Unknown script '$from'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9.");
205 }
206
207 if ($to !== null) {
208 $to = strtolower($to);
209 $target = Zend_Locale_Data::getContent('en', 'numberingsystem', $to);
210 if (empty($target)) {
211 require_once 'Zend/Locale/Exception.php';
212 throw new Zend_Locale_Exception("Unknown script '$to'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9.");
213 }
214 } else {
215 $target = '0123456789';
216 }
217
218 for ($x = 0; $x < 10; ++$x) {
219 $asource[$x] = "/" . iconv_substr($source, $x, 1, 'UTF-8') . "/u";
220 $atarget[$x] = iconv_substr($target, $x, 1, 'UTF-8');
221 }
222
223 return preg_replace($asource, $atarget, $input);
224 }
225
226 /**
227 * Returns the normalized number from a localized one
228 * Parsing depends on given locale (grouping and decimal)
229 *
230 * Examples for input:
231 * '2345.4356,1234' = 23455456.1234
232 * '+23,3452.123' = 233452.123
233 * '12343 ' = 12343
234 * '-9456' = -9456
235 * '0' = 0
236 *
237 * @param string $input Input string to parse for numbers
238 * @param array $options Options: locale, precision. See {@link setOptions()} for details.
239 * @return string Returns the extracted number
240 * @throws Zend_Locale_Exception
241 */
242 public static function getNumber($input, array $options = array())
243 {
244 $options = self::_checkOptions($options) + self::$_options;
245 if (!is_string($input)) {
246 return $input;
247 }
248
249 if (!self::isNumber($input, $options)) {
250 require_once 'Zend/Locale/Exception.php';
251 throw new Zend_Locale_Exception('No localized value in ' . $input . ' found, or the given number does not match the localized format');
252 }
253
254 // Get correct signs for this locale
255 $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
256 // Change locale input to be default number
257 if ((strpos($input, $symbols['minus']) !== false) ||
258 (strpos($input, '-') !== false)) {
259 $input = strtr($input, array($symbols['minus'] => '', '-' => ''));
260 $input = '-' . $input;
261 }
262
263 $input = str_replace($symbols['group'],'', $input);
264 if (strpos($input, $symbols['decimal']) !== false) {
265 if ($symbols['decimal'] != '.') {
266 $input = str_replace($symbols['decimal'], ".", $input);
267 }
268
269 $pre = substr($input, strpos($input, '.') + 1);
270 if ($options['precision'] === null) {
271 $options['precision'] = strlen($pre);
272 }
273
274 if (strlen($pre) >= $options['precision']) {
275 $input = substr($input, 0, strlen($input) - strlen($pre) + $options['precision']);
276 }
277
278 if (($options['precision'] == 0) && ($input[strlen($input) - 1] == '.')) {
279 $input = substr($input, 0, -1);
280 }
281 }
282
283 return $input;
284 }
285
286 /**
287 * Returns a locale formatted number depending on the given options.
288 * The seperation and fraction sign is used from the set locale.
289 * ##0.# -> 12345.12345 -> 12345.12345
290 * ##0.00 -> 12345.12345 -> 12345.12
291 * ##,##0.00 -> 12345.12345 -> 12,345.12
292 *
293 * @param string $input Localized number string
294 * @param array $options Options: number_format, locale, precision. See {@link setOptions()} for details.
295 * @return string locale formatted number
296 * @throws Zend_Locale_Exception
297 */
298 public static function toNumber($value, array $options = array())
299 {
300 // load class within method for speed
301 require_once 'Zend/Locale/Math.php';
302
303 $value = Zend_Locale_Math::normalize($value);
304 $value = Zend_Locale_Math::floatalize($value);
305 $options = self::_checkOptions($options) + self::$_options;
306 $options['locale'] = (string) $options['locale'];
307
308 // Get correct signs for this locale
309 $symbols = Zend_Locale_Data::getList($options['locale'], 'symbols');
310 $oenc = iconv_get_encoding('internal_encoding');
311 iconv_set_encoding('internal_encoding', 'UTF-8');
312
313 // Get format
314 $format = $options['number_format'];
315 if ($format === null) {
316 $format = Zend_Locale_Data::getContent($options['locale'], 'decimalnumber');
317 $format = self::_seperateFormat($format, $value, $options['precision']);
318
319 if ($options['precision'] !== null) {
320 $value = Zend_Locale_Math::normalize(Zend_Locale_Math::round($value, $options['precision']));
321 }
322 } else {
323 // seperate negative format pattern when available
324 $format = self::_seperateFormat($format, $value, $options['precision']);
325 if (strpos($format, '.')) {
326 if (is_numeric($options['precision'])) {
327 $value = Zend_Locale_Math::round($value, $options['precision']);
328 } else {
329 if (substr($format, iconv_strpos($format, '.') + 1, 3) == '###') {
330 $options['precision'] = null;
331 } else {
332 $options['precision'] = iconv_strlen(iconv_substr($format, iconv_strpos($format, '.') + 1,
333 iconv_strrpos($format, '0') - iconv_strpos($format, '.')));
334 $format = iconv_substr($format, 0, iconv_strpos($format, '.') + 1) . '###'
335 . iconv_substr($format, iconv_strrpos($format, '0') + 1);
336 }
337 }
338 } else {
339 $value = Zend_Locale_Math::round($value, 0);
340 $options['precision'] = 0;
341 }
342 $value = Zend_Locale_Math::normalize($value);
343 }
344
345 if (iconv_strpos($format, '0') === false) {
346 iconv_set_encoding('internal_encoding', $oenc);
347 require_once 'Zend/Locale/Exception.php';
348 throw new Zend_Locale_Exception('Wrong format... missing 0');
349 }
350
351 // get number parts
352 $pos = iconv_strpos($value, '.');
353 if ($pos !== false) {
354 if ($options['precision'] === null) {
355 $precstr = iconv_substr($value, $pos + 1);
356 } else {
357 $precstr = iconv_substr($value, $pos + 1, $options['precision']);
358 if (iconv_strlen($precstr) < $options['precision']) {
359 $precstr = $precstr . str_pad("0", ($options['precision'] - iconv_strlen($precstr)), "0");
360 }
361 }
362 } else {
363 if ($options['precision'] > 0) {
364 $precstr = str_pad("0", ($options['precision']), "0");
365 }
366 }
367
368 if ($options['precision'] === null) {
369 if (isset($precstr)) {
370 $options['precision'] = iconv_strlen($precstr);
371 } else {
372 $options['precision'] = 0;
373 }
374 }
375
376 // get fraction and format lengths
377 if (strpos($value, '.') !== false) {
378 $number = substr((string) $value, 0, strpos($value, '.'));
379 } else {
380 $number = $value;
381 }
382
383 $prec = call_user_func(Zend_Locale_Math::$sub, $value, $number, $options['precision']);
384 $prec = Zend_Locale_Math::floatalize($prec);
385 $prec = Zend_Locale_Math::normalize($prec);
386 if (iconv_strpos($prec, '-') !== false) {
387 $prec = iconv_substr($prec, 1);
388 }
389
390 if (($prec == 0) and ($options['precision'] > 0)) {
391 $prec = "0.0";
392 }
393
394 if (($options['precision'] + 2) > iconv_strlen($prec)) {
395 $prec = str_pad((string) $prec, $options['precision'] + 2, "0", STR_PAD_RIGHT);
396 }
397
398 if (iconv_strpos($number, '-') !== false) {
399 $number = iconv_substr($number, 1);
400 }
401 $group = iconv_strrpos($format, ',');
402 $group2 = iconv_strpos ($format, ',');
403 $point = iconv_strpos ($format, '0');
404 // Add fraction
405 $rest = "";
406 if (iconv_strpos($format, '.')) {
407 $rest = iconv_substr($format, iconv_strpos($format, '.') + 1);
408 $length = iconv_strlen($rest);
409 for($x = 0; $x < $length; ++$x) {
410 if (($rest[0] == '0') || ($rest[0] == '#')) {
411 $rest = iconv_substr($rest, 1);
412 }
413 }
414 $format = iconv_substr($format, 0, iconv_strlen($format) - iconv_strlen($rest));
415 }
416
417 if ($options['precision'] == '0') {
418 if (iconv_strrpos($format, '-') != 0) {
419 $format = iconv_substr($format, 0, $point)
420 . iconv_substr($format, iconv_strrpos($format, '#') + 2);
421 } else {
422 $format = iconv_substr($format, 0, $point);
423 }
424 } else {
425 $format = iconv_substr($format, 0, $point) . $symbols['decimal']
426 . iconv_substr($prec, 2);
427 }
428
429 $format .= $rest;
430 // Add seperation
431 if ($group == 0) {
432 // no seperation
433 $format = $number . iconv_substr($format, $point);
434 } else if ($group == $group2) {
435 // only 1 seperation
436 $seperation = ($point - $group);
437 for ($x = iconv_strlen($number); $x > $seperation; $x -= $seperation) {
438 if (iconv_substr($number, 0, $x - $seperation) !== "") {
439 $number = iconv_substr($number, 0, $x - $seperation) . $symbols['group']
440 . iconv_substr($number, $x - $seperation);
441 }
442 }
443 $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point);
444 } else {
445
446 // 2 seperations
447 if (iconv_strlen($number) > ($point - $group)) {
448 $seperation = ($point - $group);
449 $number = iconv_substr($number, 0, iconv_strlen($number) - $seperation) . $symbols['group']
450 . iconv_substr($number, iconv_strlen($number) - $seperation);
451
452 if ((iconv_strlen($number) - 1) > ($point - $group + 1)) {
453 $seperation2 = ($group - $group2 - 1);
454 for ($x = iconv_strlen($number) - $seperation2 - 2; $x > $seperation2; $x -= $seperation2) {
455 $number = iconv_substr($number, 0, $x - $seperation2) . $symbols['group']
456 . iconv_substr($number, $x - $seperation2);
457 }
458 }
459
460 }
461 $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point);
462 }
463 // set negative sign
464 if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $options['precision']) < 0) {
465 if (iconv_strpos($format, '-') === false) {
466 $format = $symbols['minus'] . $format;
467 } else {
468 $format = str_replace('-', $symbols['minus'], $format);
469 }
470 }
471
472 iconv_set_encoding('internal_encoding', $oenc);
473 return (string) $format;
474 }
475
476 private static function _seperateFormat($format, $value, $precision)
477 {
478 if (iconv_strpos($format, ';') !== false) {
479 if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $precision) < 0) {
480 $tmpformat = iconv_substr($format, iconv_strpos($format, ';') + 1);
481 if ($tmpformat[0] == '(') {
482 $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
483 } else {
484 $format = $tmpformat;
485 }
486 } else {
487 $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
488 }
489 }
490
491 return $format;
492 }
493
494
495 /**
496 * Checks if the input contains a normalized or localized number
497 *
498 * @param string $input Localized number string
499 * @param array $options Options: locale. See {@link setOptions()} for details.
500 * @return boolean Returns true if a number was found
501 */
502 public static function isNumber($input, array $options = array())
503 {
504 if (!self::_getUniCodeSupport()) {
505 trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE);
506 }
507
508 $options = self::_checkOptions($options) + self::$_options;
509
510 // Get correct signs for this locale
511 $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
512
513 $regexs = Zend_Locale_Format::_getRegexForType('decimalnumber', $options);
514 $regexs = array_merge($regexs, Zend_Locale_Format::_getRegexForType('scientificnumber', $options));
515 if (!empty($input) && ($input[0] == $symbols['decimal'])) {
516 $input = 0 . $input;
517 }
518 foreach ($regexs as $regex) {
519 preg_match($regex, $input, $found);
520 if (isset($found[0])) {
521 return true;
522 }
523 }
524
525 return false;
526 }
527
528 /**
529 * Internal method to convert cldr number syntax into regex
530 *
531 * @param string $type
532 * @return string
533 */
534 private static function _getRegexForType($type, $options)
535 {
536 $decimal = Zend_Locale_Data::getContent($options['locale'], $type);
537 $decimal = preg_replace('/[^#0,;\.\-Ee]/u', '',$decimal);
538 $patterns = explode(';', $decimal);
539
540 if (count($patterns) == 1) {
541 $patterns[1] = '-' . $patterns[0];
542 }
543
544 $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
545
546 foreach($patterns as $pkey => $pattern) {
547 $regex[$pkey] = '/^';
548 $rest = 0;
549 $end = null;
550 if (strpos($pattern, '.') !== false) {
551 $end = substr($pattern, strpos($pattern, '.') + 1);
552 $pattern = substr($pattern, 0, -strlen($end) - 1);
553 }
554
555 if (strpos($pattern, ',') !== false) {
556 $parts = explode(',', $pattern);
557 $count = count($parts);
558 foreach($parts as $key => $part) {
559 switch ($part) {
560 case '#':
561 case '-#':
562 if ($part[0] == '-') {
563 $regex[$pkey] .= '[' . $symbols['minus'] . '-]{0,1}';
564 } else {
565 $regex[$pkey] .= '[' . $symbols['plus'] . '+]{0,1}';
566 }
567
568 if (($parts[$key + 1]) == '##0') {
569 $regex[$pkey] .= '[0-9]{1,3}';
570 } else if (($parts[$key + 1]) == '##') {
571 $regex[$pkey] .= '[0-9]{1,2}';
572 } else {
573 throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 1):"' . $pattern . '"');
574 }
575 break;
576 case '##':
577 if ($parts[$key + 1] == '##0') {
578 $regex[$pkey] .= '(\\' . $symbols['group'] . '{0,1}[0-9]{2})*';
579 } else {
580 throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 2):"' . $pattern . '"');
581 }
582 break;
583 case '##0':
584 if ($parts[$key - 1] == '##') {
585 $regex[$pkey] .= '[0-9]';
586 } else if (($parts[$key - 1] == '#') || ($parts[$key - 1] == '-#')) {
587 $regex[$pkey] .= '(\\' . $symbols['group'] . '{0,1}[0-9]{3})*';
588 } else {
589 throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 3):"' . $pattern . '"');
590 }
591 break;
592 case '#0':
593 if ($key == 0) {
594 $regex[$pkey] .= '[0-9]*';
595 } else {
596 throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 4):"' . $pattern . '"');
597 }
598 break;
599 }
600 }
601 }
602
603 if (strpos($pattern, 'E') !== false) {
604 if (($pattern == '#E0') || ($pattern == '#E00')) {
605 $regex[$pkey] .= '[' . $symbols['plus']. '+]{0,1}[0-9]{1,}(\\' . $symbols['decimal'] . '[0-9]{1,})*[eE][' . $symbols['plus']. '+]{0,1}[0-9]{1,}';
606 } else if (($pattern == '-#E0') || ($pattern == '-#E00')) {
607 $regex[$pkey] .= '[' . $symbols['minus']. '-]{0,1}[0-9]{1,}(\\' . $symbols['decimal'] . '[0-9]{1,})*[eE][' . $symbols['minus']. '-]{0,1}[0-9]{1,}';
608 } else {
609 throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 5):"' . $pattern . '"');
610 }
611 }
612
613 if (!empty($end)) {
614 if ($end == '###') {
615 $regex[$pkey] .= '(\\' . $symbols['decimal'] . '{1}[0-9]{1,}){0,1}';
616 } else if ($end == '###-') {
617 $regex[$pkey] .= '(\\' . $symbols['decimal'] . '{1}[0-9]{1,}){0,1}[' . $symbols['minus']. '-]';
618 } else {
619 throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 6):"' . $pattern . '"');
620 }
621 }
622
623 $regex[$pkey] .= '$/u';
624 }
625
626 return $regex;
627 }
628
629 /**
630 * Alias for getNumber
631 *
632 * @param string $value Number to localize
633 * @param array $options Options: locale, precision. See {@link setOptions()} for details.
634 * @return float
635 */
636 public static function getFloat($input, array $options = array())
637 {
638 return floatval(self::getNumber($input, $options));
639 }
640
641 /**
642 * Returns a locale formatted integer number
643 * Alias for toNumber()
644 *
645 * @param string $value Number to normalize
646 * @param array $options Options: locale, precision. See {@link setOptions()} for details.
647 * @return string Locale formatted number
648 */
649 public static function toFloat($value, array $options = array())
650 {
651 $options['number_format'] = Zend_Locale_Format::STANDARD;
652 return self::toNumber($value, $options);
653 }
654
655 /**
656 * Returns if a float was found
657 * Alias for isNumber()
658 *
659 * @param string $input Localized number string
660 * @param array $options Options: locale. See {@link setOptions()} for details.
661 * @return boolean Returns true if a number was found
662 */
663 public static function isFloat($value, array $options = array())
664 {
665 return self::isNumber($value, $options);
666 }
667
668 /**
669 * Returns the first found integer from an string
670 * Parsing depends on given locale (grouping and decimal)
671 *
672 * Examples for input:
673 * ' 2345.4356,1234' = 23455456
674 * '+23,3452.123' = 233452
675 * ' 12343 ' = 12343
676 * '-9456km' = -9456
677 * '0' = 0
678 * '(-){0,1}(\d+(\.){0,1})*(\,){0,1})\d+'
679 *
680 * @param string $input Input string to parse for numbers
681 * @param array $options Options: locale. See {@link setOptions()} for details.
682 * @return integer Returns the extracted number
683 */
684 public static function getInteger($input, array $options = array())
685 {
686 $options['precision'] = 0;
687 return intval(self::getFloat($input, $options));
688 }
689
690 /**
691 * Returns a localized number
692 *
693 * @param string $value Number to normalize
694 * @param array $options Options: locale. See {@link setOptions()} for details.
695 * @return string Locale formatted number
696 */
697 public static function toInteger($value, array $options = array())
698 {
699 $options['precision'] = 0;
700 $options['number_format'] = Zend_Locale_Format::STANDARD;
701 return self::toNumber($value, $options);
702 }
703
704 /**
705 * Returns if a integer was found
706 *
707 * @param string $input Localized number string
708 * @param array $options Options: locale. See {@link setOptions()} for details.
709 * @return boolean Returns true if a integer was found
710 */
711 public static function isInteger($value, array $options = array())
712 {
713 if (!self::isNumber($value, $options)) {
714 return false;
715 }
716
717 if (self::getInteger($value, $options) == self::getFloat($value, $options)) {
718 return true;
719 }
720
721 return false;
722 }
723
724 /**
725 * Converts a format string from PHP's date format to ISO format
726 * Remember that Zend Date always returns localized string, so a month name which returns the english
727 * month in php's date() will return the translated month name with this function... use 'en' as locale
728 * if you are in need of the original english names
729 *
730 * The conversion has the following restrictions:
731 * 'a', 'A' - Meridiem is not explicit upper/lowercase, you have to upper/lowercase the translated value yourself
732 *
733 * @param string $format Format string in PHP's date format
734 * @return string Format string in ISO format
735 */
736 public static function convertPhpToIsoFormat($format)
737 {
738 if ($format === null) {
739 return null;
740 }
741
742 $convert = array('d' => 'dd' , 'D' => 'EE' , 'j' => 'd' , 'l' => 'EEEE', 'N' => 'eee' , 'S' => 'SS' ,
743 'w' => 'e' , 'z' => 'D' , 'W' => 'ww' , 'F' => 'MMMM', 'm' => 'MM' , 'M' => 'MMM' ,
744 'n' => 'M' , 't' => 'ddd' , 'L' => 'l' , 'o' => 'YYYY', 'Y' => 'yyyy', 'y' => 'yy' ,
745 'a' => 'a' , 'A' => 'a' , 'B' => 'B' , 'g' => 'h' , 'G' => 'H' , 'h' => 'hh' ,
746 'H' => 'HH' , 'i' => 'mm' , 's' => 'ss' , 'e' => 'zzzz', 'I' => 'I' , 'O' => 'Z' ,
747 'P' => 'ZZZZ', 'T' => 'z' , 'Z' => 'X' , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ',
748 'r' => 'r' , 'U' => 'U');
749 $values = str_split($format);
750 foreach ($values as $key => $value) {
751 if (isset($convert[$value]) === true) {
752 $values[$key] = $convert[$value];
753 }
754 }
755
756 return join($values);
757 }
758
759 /**
760 * Parse date and split in named array fields
761 *
762 * @param string $date Date string to parse
763 * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
764 * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
765 */
766 private static function _parseDate($date, $options)
767 {
768 if (!self::_getUniCodeSupport()) {
769 trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE);
770 }
771
772 $options = self::_checkOptions($options) + self::$_options;
773 $test = array('h', 'H', 'm', 's', 'y', 'Y', 'M', 'd', 'D', 'E', 'S', 'l', 'B', 'I',
774 'X', 'r', 'U', 'G', 'w', 'e', 'a', 'A', 'Z', 'z', 'v');
775
776 $format = $options['date_format'];
777 $number = $date; // working copy
778 $result['date_format'] = $format; // save the format used to normalize $number (convenience)
779 $result['locale'] = $options['locale']; // save the locale used to normalize $number (convenience)
780
781 $oenc = iconv_get_encoding('internal_encoding');
782 iconv_set_encoding('internal_encoding', 'UTF-8');
783 $day = iconv_strpos($format, 'd');
784 $month = iconv_strpos($format, 'M');
785 $year = iconv_strpos($format, 'y');
786 $hour = iconv_strpos($format, 'H');
787 $min = iconv_strpos($format, 'm');
788 $sec = iconv_strpos($format, 's');
789 $am = null;
790 if ($hour === false) {
791 $hour = iconv_strpos($format, 'h');
792 }
793 if ($year === false) {
794 $year = iconv_strpos($format, 'Y');
795 }
796 if ($day === false) {
797 $day = iconv_strpos($format, 'E');
798 if ($day === false) {
799 $day = iconv_strpos($format, 'D');
800 }
801 }
802
803 if ($day !== false) {
804 $parse[$day] = 'd';
805 if (!empty($options['locale']) && ($options['locale'] !== 'root') &&
806 (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) {
807 // erase day string
808 $daylist = Zend_Locale_Data::getList($options['locale'], 'day');
809 foreach($daylist as $key => $name) {
810 if (iconv_strpos($number, $name) !== false) {
811 $number = str_replace($name, "EEEE", $number);
812 break;
813 }
814 }
815 }
816 }
817 $position = false;
818
819 if ($month !== false) {
820 $parse[$month] = 'M';
821 if (!empty($options['locale']) && ($options['locale'] !== 'root') &&
822 (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) {
823 // prepare to convert month name to their numeric equivalents, if requested,
824 // and we have a $options['locale']
825 $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'],
826 'month'));
827 if ($position === false) {
828 $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'],
829 'month', array('gregorian', 'format', 'abbreviated')));
830 }
831 }
832 }
833 if ($year !== false) {
834 $parse[$year] = 'y';
835 }
836 if ($hour !== false) {
837 $parse[$hour] = 'H';
838 }
839 if ($min !== false) {
840 $parse[$min] = 'm';
841 }
842 if ($sec !== false) {
843 $parse[$sec] = 's';
844 }
845
846 if (empty($parse)) {
847 iconv_set_encoding('internal_encoding', $oenc);
848 require_once 'Zend/Locale/Exception.php';
849 throw new Zend_Locale_Exception("Unknown date format, neither date nor time in '" . $format . "' found");
850 }
851 ksort($parse);
852
853 // get daytime
854 if (iconv_strpos($format, 'a') !== false) {
855 if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'am'))) !== false) {
856 $am = true;
857 } else if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'pm'))) !== false) {
858 $am = false;
859 }
860 }
861
862 // split number parts
863 $split = false;
864 preg_match_all('/\d+/u', $number, $splitted);
865
866 if (count($splitted[0]) == 0) {
867 iconv_set_encoding('internal_encoding', $oenc);
868 require_once 'Zend/Locale/Exception.php';
869 throw new Zend_Locale_Exception("No date part in '$date' found.");
870 }
871 if (count($splitted[0]) == 1) {
872 $split = 0;
873 }
874 $cnt = 0;
875 foreach($parse as $key => $value) {
876
877 switch($value) {
878 case 'd':
879 if ($split === false) {
880 if (count($splitted[0]) > $cnt) {
881 $result['day'] = $splitted[0][$cnt];
882 }
883 } else {
884 $result['day'] = iconv_substr($splitted[0][0], $split, 2);
885 $split += 2;
886 }
887 ++$cnt;
888 break;
889 case 'M':
890 if ($split === false) {
891 if (count($splitted[0]) > $cnt) {
892 $result['month'] = $splitted[0][$cnt];
893 }
894 } else {
895 $result['month'] = iconv_substr($splitted[0][0], $split, 2);
896 $split += 2;
897 }
898 ++$cnt;
899 break;
900 case 'y':
901 $length = 2;
902 if ((iconv_substr($format, $year, 4) == 'yyyy')
903 || (iconv_substr($format, $year, 4) == 'YYYY')) {
904 $length = 4;
905 }
906
907 if ($split === false) {
908 if (count($splitted[0]) > $cnt) {
909 $result['year'] = $splitted[0][$cnt];
910 }
911 } else {
912 $result['year'] = iconv_substr($splitted[0][0], $split, $length);
913 $split += $length;
914 }
915
916 ++$cnt;
917 break;
918 case 'H':
919 if ($split === false) {
920 if (count($splitted[0]) > $cnt) {
921 $result['hour'] = $splitted[0][$cnt];
922 }
923 } else {
924 $result['hour'] = iconv_substr($splitted[0][0], $split, 2);
925 $split += 2;
926 }
927 ++$cnt;
928 break;
929 case 'm':
930 if ($split === false) {
931 if (count($splitted[0]) > $cnt) {
932 $result['minute'] = $splitted[0][$cnt];
933 }
934 } else {
935 $result['minute'] = iconv_substr($splitted[0][0], $split, 2);
936 $split += 2;
937 }
938 ++$cnt;
939 break;
940 case 's':
941 if ($split === false) {
942 if (count($splitted[0]) > $cnt) {
943 $result['second'] = $splitted[0][$cnt];
944 }
945 } else {
946 $result['second'] = iconv_substr($splitted[0][0], $split, 2);
947 $split += 2;
948 }
949 ++$cnt;
950 break;
951 }
952 }
953
954 // AM/PM correction
955 if ($hour !== false) {
956 if (($am === true) and ($result['hour'] == 12)){
957 $result['hour'] = 0;
958 } else if (($am === false) and ($result['hour'] != 12)) {
959 $result['hour'] += 12;
960 }
961 }
962
963 if ($options['fix_date'] === true) {
964 $result['fixed'] = 0; // nothing has been "fixed" by swapping date parts around (yet)
965 }
966
967 if ($day !== false) {
968 // fix false month
969 if (isset($result['day']) and isset($result['month'])) {
970 if (($position !== false) and ((iconv_strpos($date, $result['day']) === false) or
971 (isset($result['year']) and (iconv_strpos($date, $result['year']) === false)))) {
972 if ($options['fix_date'] !== true) {
973 iconv_set_encoding('internal_encoding', $oenc);
974 require_once 'Zend/Locale/Exception.php';
975 throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format
976 . "' (false month, $position, $month)");
977 }
978 $temp = $result['day'];
979 $result['day'] = $result['month'];
980 $result['month'] = $temp;
981 $result['fixed'] = 1;
982 }
983 }
984
985 // fix switched values d <> y
986 if (isset($result['day']) and isset($result['year'])) {
987 if ($result['day'] > 31) {
988 if ($options['fix_date'] !== true) {
989 iconv_set_encoding('internal_encoding', $oenc);
990 require_once 'Zend/Locale/Exception.php';
991 throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
992 . $format . "' (d <> y)");
993 }
994 $temp = $result['year'];
995 $result['year'] = $result['day'];
996 $result['day'] = $temp;
997 $result['fixed'] = 2;
998 }
999 }
1000
1001 // fix switched values M <> y
1002 if (isset($result['month']) and isset($result['year'])) {
1003 if ($result['month'] > 31) {
1004 if ($options['fix_date'] !== true) {
1005 iconv_set_encoding('internal_encoding', $oenc);
1006 require_once 'Zend/Locale/Exception.php';
1007 throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
1008 . $format . "' (M <> y)");
1009 }
1010 $temp = $result['year'];
1011 $result['year'] = $result['month'];
1012 $result['month'] = $temp;
1013 $result['fixed'] = 3;
1014 }
1015 }
1016
1017 // fix switched values M <> d
1018 if (isset($result['month']) and isset($result['day'])) {
1019 if ($result['month'] > 12) {
1020 if ($options['fix_date'] !== true || $result['month'] > 31) {
1021 iconv_set_encoding('internal_encoding', $oenc);
1022 require_once 'Zend/Locale/Exception.php';
1023 throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
1024 . $format . "' (M <> d)");
1025 }
1026 $temp = $result['day'];
1027 $result['day'] = $result['month'];
1028 $result['month'] = $temp;
1029 $result['fixed'] = 4;
1030 }
1031 }
1032 }
1033
1034 if (isset($result['year'])) {
1035 if (((iconv_strlen($result['year']) == 2) && ($result['year'] < 10)) ||
1036 (((iconv_strpos($format, 'yy') !== false) && (iconv_strpos($format, 'yyyy') === false)) ||
1037 ((iconv_strpos($format, 'YY') !== false) && (iconv_strpos($format, 'YYYY') === false)))) {
1038 if (($result['year'] >= 0) && ($result['year'] < 100)) {
1039 if ($result['year'] < 70) {
1040 $result['year'] = (int) $result['year'] + 100;
1041 }
1042
1043 $result['year'] = (int) $result['year'] + 1900;
1044 }
1045 }
1046 }
1047
1048 iconv_set_encoding('internal_encoding', $oenc);
1049 return $result;
1050 }
1051
1052 /**
1053 * Search $number for a month name found in $monthlist, and replace if found.
1054 *
1055 * @param string $number Date string (modified)
1056 * @param array $monthlist List of month names
1057 *
1058 * @return int|false Position of replaced string (false if nothing replaced)
1059 */
1060 protected static function _replaceMonth(&$number, $monthlist)
1061 {
1062 // If $locale was invalid, $monthlist will default to a "root" identity
1063 // mapping for each month number from 1 to 12.
1064 // If no $locale was given, or $locale was invalid, do not use this identity mapping to normalize.
1065 // Otherwise, translate locale aware month names in $number to their numeric equivalents.
1066 $position = false;
1067 if ($monthlist && $monthlist[1] != 1) {
1068 foreach($monthlist as $key => $name) {
1069 if (($position = iconv_strpos($number, $name, 0, 'UTF-8')) !== false) {
1070 $number = str_ireplace($name, $key, $number);
1071 return $position;
1072 }
1073 }
1074 }
1075
1076 return false;
1077 }
1078
1079 /**
1080 * Returns the default date format for $locale.
1081 *
1082 * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT')
1083 * @return string format
1084 * @throws Zend_Locale_Exception throws an exception when locale data is broken
1085 */
1086 public static function getDateFormat($locale = null)
1087 {
1088 $format = Zend_Locale_Data::getContent($locale, 'date');
1089 if (empty($format)) {
1090 require_once 'Zend/Locale/Exception.php';
1091 throw new Zend_Locale_Exception("failed to receive data from locale $locale");
1092 }
1093
1094 return $format;
1095 }
1096
1097 /**
1098 * Returns an array with the normalized date from an locale date
1099 * a input of 10.01.2006 without a $locale would return:
1100 * array ('day' => 10, 'month' => 1, 'year' => 2006)
1101 * The 'locale' option is only used to convert human readable day
1102 * and month names to their numeric equivalents.
1103 * The 'format' option allows specification of self-defined date formats,
1104 * when not using the default format for the 'locale'.
1105 *
1106 * @param string $date Date string
1107 * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
1108 * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
1109 */
1110 public static function getDate($date, array $options = array())
1111 {
1112 $options = self::_checkOptions($options) + self::$_options;
1113 if (empty($options['date_format'])) {
1114 $options['format_type'] = 'iso';
1115 $options['date_format'] = self::getDateFormat($options['locale']);
1116 }
1117
1118 return self::_parseDate($date, $options);
1119 }
1120
1121 /**
1122 * Returns if the given datestring contains all date parts from the given format.
1123 * If no format is given, the default date format from the locale is used
1124 * If you want to check if the date is a proper date you should use Zend_Date::isDate()
1125 *
1126 * @param string $date Date string
1127 * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
1128 * @return boolean
1129 */
1130 public static function checkDateFormat($date, array $options = array())
1131 {
1132 try {
1133 $date = self::getDate($date, $options);
1134 } catch (Exception $e) {
1135 return false;
1136 }
1137
1138 if (empty($options['date_format'])) {
1139 $options['format_type'] = 'iso';
1140 $options['date_format'] = self::getDateFormat(isset($options['locale']) ? $options['locale'] : null);
1141 }
1142 $options = self::_checkOptions($options) + self::$_options;
1143
1144 // day expected but not parsed
1145 if ((iconv_strpos($options['date_format'], 'd', 0, 'UTF-8') !== false) and (!isset($date['day']) or ($date['day'] === ""))) {
1146 return false;
1147 }
1148
1149 // month expected but not parsed
1150 if ((iconv_strpos($options['date_format'], 'M', 0, 'UTF-8') !== false) and (!isset($date['month']) or ($date['month'] === ""))) {
1151 return false;
1152 }
1153
1154 // year expected but not parsed
1155 if (((iconv_strpos($options['date_format'], 'Y', 0, 'UTF-8') !== false) or
1156 (iconv_strpos($options['date_format'], 'y', 0, 'UTF-8') !== false)) and (!isset($date['year']) or ($date['year'] === ""))) {
1157 return false;
1158 }
1159
1160 // second expected but not parsed
1161 if ((iconv_strpos($options['date_format'], 's', 0, 'UTF-8') !== false) and (!isset($date['second']) or ($date['second'] === ""))) {
1162 return false;
1163 }
1164
1165 // minute expected but not parsed
1166 if ((iconv_strpos($options['date_format'], 'm', 0, 'UTF-8') !== false) and (!isset($date['minute']) or ($date['minute'] === ""))) {
1167 return false;
1168 }
1169
1170 // hour expected but not parsed
1171 if (((iconv_strpos($options['date_format'], 'H', 0, 'UTF-8') !== false) or
1172 (iconv_strpos($options['date_format'], 'h', 0, 'UTF-8') !== false)) and (!isset($date['hour']) or ($date['hour'] === ""))) {
1173 return false;
1174 }
1175
1176 return true;
1177 }
1178
1179 /**
1180 * Returns the default time format for $locale…
Large files files are truncated, but you can click here to view the full file