Format.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  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-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @version $Id$
  20. * @license http://framework.zend.com/license/new-bsd New BSD License
  21. */
  22. /**
  23. * include needed classes
  24. */
  25. require_once 'Zend/Locale/Data.php';
  26. /**
  27. * @category Zend
  28. * @package Zend_Locale
  29. * @subpackage Format
  30. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  31. * @license http://framework.zend.com/license/new-bsd New BSD License
  32. */
  33. class Zend_Locale_Format
  34. {
  35. const STANDARD = 'auto';
  36. private static $_options = array('date_format' => null,
  37. 'number_format' => null,
  38. 'format_type' => 'iso',
  39. 'fix_date' => false,
  40. 'locale' => null,
  41. 'cache' => null,
  42. 'precision' => null);
  43. private static $_signs = array(
  44. 'Latn' => array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), // Latn - default latin
  45. 'Arab' => array( '٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'), // 0660 - 0669 arabic
  46. 'Deva' => array( '०', '१', '२', '३', '४', '५', '६', '७', '८', '९'), // 0966 - 096F devanagari
  47. 'Beng' => array( '০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'), // 09E6 - 09EF bengali
  48. 'Guru' => array( '੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯'), // 0A66 - 0A6F gurmukhi
  49. 'Gujr' => array( '૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯'), // 0AE6 - 0AEF gujarati
  50. 'Orya' => array( '୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯'), // 0B66 - 0B6F orija
  51. 'Taml' => array( '௦', '௧', '௨', '௩', '௪', '௫', '௬', '௭', '௮', '௯'), // 0BE6 - 0BEF tamil
  52. 'Telu' => array( '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'), // 0C66 - 0C6F telugu
  53. 'Knda' => array( '೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯'), // 0CE6 - 0CEF kannada
  54. 'Mlym' => array( '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯ '), // 0D66 - 0D6F malayalam
  55. 'Tale' => array( '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙ '), // 0E50 - 0E59 thai
  56. 'Laoo' => array( '໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙'), // 0ED0 - 0ED9 lao
  57. 'Tibt' => array( '༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩ '), // 0F20 - 0F29 tibetan
  58. 'Mymr' => array( '၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉'), // 1040 - 1049 myanmar
  59. 'Khmr' => array( '០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩'), // 17E0 - 17E9 khmer
  60. 'Mong' => array( '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'), // 1810 - 1819 mongolian
  61. 'Limb' => array( '᥆', '᥇', '᥈', '᥉', '᥊', '᥋', '᥌', '᥍', '᥎', '᥏'), // 1946 - 194F limbu
  62. 'Talu' => array( '᧐', '᧑', '᧒', '᧓', '᧔', '᧕', '᧖', '᧗', '᧘', '᧙'), // 19D0 - 19D9 tailue
  63. 'Bali' => array( '᭐', '᭑', '᭒', '᭓', '᭔', '᭕', '᭖', '᭗', '᭘', '᭙'), // 1B50 - 1B59 balinese
  64. 'Nkoo' => array( '߀', '߁', '߂', '߃', '߄', '߅', '߆', '߇', '߈', '߉') // 07C0 - 07C9 nko
  65. );
  66. /**
  67. * Sets class wide options, if no option was given, the actual set options will be returned
  68. * The 'precision' option of a value is used to truncate or stretch extra digits. -1 means not to touch the extra digits.
  69. * The 'locale' option helps when parsing numbers and dates using separators and month names.
  70. * The date format 'format_type' option selects between CLDR/ISO date format specifier tokens and PHP's date() tokens.
  71. * The 'fix_date' option enables or disables heuristics that attempt to correct invalid dates.
  72. * The 'number_format' option can be used to specify a default number format string
  73. * The 'date_format' option can be used to specify a default date format string, but beware of using getDate(),
  74. * checkDateFormat() and getTime() after using setOptions() with a 'format'. To use these four methods
  75. * with the default date format for a locale, use array('date_format' => null, 'locale' => $locale) for their options.
  76. *
  77. * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false,
  78. * locale = Zend_Locale | locale string, precision = whole number between -1 and 30
  79. * @throws Zend_Locale_Exception
  80. * @return Options array if no option was given
  81. */
  82. public static function setOptions(array $options = array())
  83. {
  84. self::$_options = self::_checkOptions($options) + self::$_options;
  85. return self::$_options;
  86. }
  87. /**
  88. * Internal function for checking the options array of proper input values
  89. * See {@link setOptions()} for details.
  90. *
  91. * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false,
  92. * locale = Zend_Locale | locale string, precision = whole number between -1 and 30
  93. * @throws Zend_Locale_Exception
  94. * @return Options array if no option was given
  95. */
  96. private static function _checkOptions(array $options = array())
  97. {
  98. if (count($options) == 0) {
  99. return self::$_options;
  100. }
  101. foreach ($options as $name => $value) {
  102. $name = strtolower($name);
  103. if ($name !== 'locale') {
  104. if (gettype($value) === 'string') {
  105. $value = strtolower($value);
  106. }
  107. }
  108. switch($name) {
  109. case 'number_format' :
  110. if ($value == Zend_Locale_Format::STANDARD) {
  111. $locale = self::$_options['locale'];
  112. if (isset($options['locale'])) {
  113. $locale = $options['locale'];
  114. }
  115. $options['number_format'] = Zend_Locale_Data::getContent($locale, 'decimalnumber');
  116. } else if ((gettype($value) !== 'string') and ($value !== NULL)) {
  117. require_once 'Zend/Locale/Exception.php';
  118. throw new Zend_Locale_Exception("Unknown number format type '" . gettype($value) . "'. "
  119. . "Format '$value' must be a valid number format string.");
  120. }
  121. break;
  122. case 'date_format' :
  123. if ($value == Zend_Locale_Format::STANDARD) {
  124. $locale = self::$_options['locale'];
  125. if (isset($options['locale'])) {
  126. $locale = $options['locale'];
  127. }
  128. $options['date_format'] = Zend_Locale_Format::getDateFormat($locale);
  129. } else if ((gettype($value) !== 'string') and ($value !== NULL)) {
  130. require_once 'Zend/Locale/Exception.php';
  131. throw new Zend_Locale_Exception("Unknown dateformat type '" . gettype($value) . "'. "
  132. . "Format '$value' must be a valid ISO or PHP date format string.");
  133. } else {
  134. if (((isset($options['format_type']) === true) and ($options['format_type'] == 'php')) or
  135. ((isset($options['format_type']) === false) and (self::$_options['format_type'] == 'php'))) {
  136. $options['date_format'] = Zend_Locale_Format::convertPhpToIsoFormat($value);
  137. }
  138. }
  139. break;
  140. case 'format_type' :
  141. if (($value != 'php') && ($value != 'iso')) {
  142. require_once 'Zend/Locale/Exception.php';
  143. throw new Zend_Locale_Exception("Unknown date format type '$value'. Only 'iso' and 'php'"
  144. . " are supported.");
  145. }
  146. break;
  147. case 'fix_date' :
  148. if (($value !== true) && ($value !== false)) {
  149. require_once 'Zend/Locale/Exception.php';
  150. throw new Zend_Locale_Exception("Enabling correction of dates must be either true or false"
  151. . "(fix_date='$value').");
  152. }
  153. break;
  154. case 'locale' :
  155. $options['locale'] = Zend_Locale::findLocale($value);
  156. break;
  157. case 'cache' :
  158. if ($value instanceof Zend_Cache_Core) {
  159. Zend_Locale_Data::setCache($value);
  160. }
  161. break;
  162. case 'precision' :
  163. if ($value === NULL) {
  164. $value = -1;
  165. }
  166. if (($value < -1) || ($value > 30)) {
  167. require_once 'Zend/Locale/Exception.php';
  168. throw new Zend_Locale_Exception("'$value' precision is not a whole number less than 30.");
  169. }
  170. break;
  171. default:
  172. require_once 'Zend/Locale/Exception.php';
  173. throw new Zend_Locale_Exception("Unknown option: '$name' = '$value'");
  174. break;
  175. }
  176. }
  177. return $options;
  178. }
  179. /**
  180. * Changes the numbers/digits within a given string from one script to another
  181. * 'Decimal' representated the stardard numbers 0-9, if a script does not exist
  182. * an exception will be thrown.
  183. *
  184. * Examples for conversion from Arabic to Latin numerals:
  185. * convertNumerals('١١٠ Tests', 'Arab'); -> returns '100 Tests'
  186. * Example for conversion from Latin to Arabic numerals:
  187. * convertNumerals('100 Tests', 'Latn', 'Arab'); -> returns '١١٠ Tests'
  188. *
  189. * @param string $input String to convert
  190. * @param string $from Script to parse, see {@link Zend_Locale::getScriptList()} for details.
  191. * @param string $to OPTIONAL Script to convert to
  192. * @return string Returns the converted input
  193. * @throws Zend_Locale_Exception
  194. */
  195. public static function convertNumerals($input, $from, $to = null)
  196. {
  197. if (is_string($from)) {
  198. $from = ucfirst(strtolower($from));
  199. }
  200. if (isset(self::$_signs[$from]) === false) {
  201. require_once 'Zend/Locale/Exception.php';
  202. throw new Zend_Locale_Exception("Unknown script '$from'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9.");
  203. }
  204. if (is_string($to)) {
  205. $to = ucfirst(strtolower($to));
  206. }
  207. if (($to !== null) and (isset(self::$_signs[$to]) === false)) {
  208. require_once 'Zend/Locale/Exception.php';
  209. throw new Zend_Locale_Exception("Unknown script '$to'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9.");
  210. }
  211. if (isset(self::$_signs[$from])) {
  212. for ($X = 0; $X < 10; ++$X) {
  213. $source[$X + 10] = "/" . self::$_signs[$from][$X] . "/u";
  214. }
  215. }
  216. if (isset(self::$_signs[$to])) {
  217. for ($X = 0; $X < 10; ++$X) {
  218. $dest[$X + 10] = self::$_signs[$to][$X];
  219. }
  220. } else {
  221. for ($X = 0; $X < 10; ++$X) {
  222. $dest[$X + 10] = $X;
  223. }
  224. }
  225. return preg_replace($source, $dest, $input);
  226. }
  227. /**
  228. * Returns the first found number from an string
  229. * Parsing depends on given locale (grouping and decimal)
  230. *
  231. * Examples for input:
  232. * ' 2345.4356,1234' = 23455456.1234
  233. * '+23,3452.123' = 233452.123
  234. * ' 12343 ' = 12343
  235. * '-9456km' = -9456
  236. * '0' = 0
  237. * '(-){0,1}(\d+(\.){0,1})*(\,){0,1})\d+'
  238. * '١١٠ Tests' = 110 call: getNumber($string, 'Arab');
  239. *
  240. * @param string $input Input string to parse for numbers
  241. * @param array $options Options: locale, precision. See {@link setOptions()} for details.
  242. * @return string Returns the extracted number
  243. * @throws Zend_Locale_Exception
  244. */
  245. public static function getNumber($input, array $options = array())
  246. {
  247. $options = self::_checkOptions($options) + self::$_options;
  248. if (!is_string($input)) {
  249. return $input;
  250. }
  251. // Get correct signs for this locale
  252. $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
  253. // Parse input locale aware
  254. $regex = '/([' . $symbols['minus'] . '-]){0,1}(\d+(\\' . $symbols['group'] . '){0,1})*(\\' .
  255. $symbols['decimal'] . '){0,1}\d+/';
  256. preg_match($regex, $input, $found);
  257. if (!isset($found[0])) {
  258. require_once 'Zend/Locale/Exception.php';
  259. throw new Zend_Locale_Exception('No value in ' . $input . ' found');
  260. }
  261. $found = $found[0];
  262. // Change locale input to be default number
  263. if ($symbols['minus'] != "-")
  264. $found = strtr($found,$symbols['minus'],'-');
  265. $found = str_replace($symbols['group'],'', $found);
  266. // Do precision
  267. if (strpos($found, $symbols['decimal']) !== false) {
  268. if ($symbols['decimal'] != '.') {
  269. $found = str_replace($symbols['decimal'], ".", $found);
  270. }
  271. $pre = substr($found, strpos($found, '.') + 1);
  272. if ($options['precision'] === null) {
  273. $options['precision'] = strlen($pre);
  274. }
  275. if (strlen($pre) >= $options['precision']) {
  276. $found = substr($found, 0, strlen($found) - strlen($pre) + $options['precision']);
  277. }
  278. }
  279. return $found;
  280. }
  281. /**
  282. * Returns a locale formatted number depending on the given options.
  283. * The seperation and fraction sign is used from the set locale.
  284. * ##0.# -> 12345.12345 -> 12345.12345
  285. * ##0.00 -> 12345.12345 -> 12345.12
  286. * ##,##0.00 -> 12345.12345 -> 12,345.12
  287. *
  288. * @param string $input Localized number string
  289. * @param array $options Options: number_format, locale, precision. See {@link setOptions()} for details.
  290. * @return string locale formatted number
  291. * @throws Zend_Locale_Exception
  292. */
  293. public static function toNumber($value, array $options = array())
  294. {
  295. // load class within method for speed
  296. require_once 'Zend/Locale/Math.php';
  297. $value = Zend_Locale_Math::normalize($value);
  298. $options = self::_checkOptions($options) + self::$_options;
  299. $options['locale'] = (string) $options['locale'];
  300. // Get correct signs for this locale
  301. $symbols = Zend_Locale_Data::getList($options['locale'], 'symbols');
  302. iconv_set_encoding('internal_encoding', 'UTF-8');
  303. // Get format
  304. $format = $options['number_format'];
  305. if ($format === null) {
  306. $format = Zend_Locale_Data::getContent($options['locale'], 'decimalnumber');
  307. if (iconv_strpos($format, ';') !== false) {
  308. if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $options['precision']) < 0) {
  309. $tmpformat = iconv_substr($format, iconv_strpos($format, ';') + 1);
  310. if ($tmpformat[0] == '(') {
  311. $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
  312. } else {
  313. $format = $tmpformat;
  314. }
  315. } else {
  316. $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
  317. }
  318. }
  319. } else {
  320. // seperate negative format pattern when available
  321. if (iconv_strpos($format, ';') !== false) {
  322. if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $options['precision']) < 0) {
  323. $tmpformat = iconv_substr($format, iconv_strpos($format, ';') + 1);
  324. if ($tmpformat[0] == '(') {
  325. $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
  326. } else {
  327. $format = $tmpformat;
  328. }
  329. } else {
  330. $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
  331. }
  332. }
  333. if (strpos($format, '.')) {
  334. if (is_numeric($options['precision'])) {
  335. $value = Zend_Locale_Math::round($value, $options['precision']);
  336. } else {
  337. if (substr($format, iconv_strpos($format, '.') + 1, 3) == '###') {
  338. $options['precision'] = null;
  339. } else {
  340. $options['precision'] = iconv_strlen(iconv_substr($format, iconv_strpos($format, '.') + 1,
  341. iconv_strrpos($format, '0') - iconv_strpos($format, '.')));
  342. $format = iconv_substr($format, 0, iconv_strpos($format, '.') + 1) . '###'
  343. . iconv_substr($format, iconv_strrpos($format, '0') + 1);
  344. }
  345. }
  346. } else {
  347. $value = Zend_Locale_Math::round($value, 0);
  348. $options['precision'] = 0;
  349. }
  350. $value = Zend_Locale_Math::normalize($value);
  351. }
  352. if (iconv_strpos($format, '0') === false) {
  353. require_once 'Zend/Locale/Exception.php';
  354. throw new Zend_Locale_Exception('Wrong format... missing 0');
  355. }
  356. // get number parts
  357. $pos = iconv_strpos($value, '.');
  358. if ($pos !== false) {
  359. if ($options['precision'] === null) {
  360. $precstr = iconv_substr($value, $pos + 1);
  361. } else {
  362. $precstr = iconv_substr($value, $pos + 1, $options['precision']);
  363. if (iconv_strlen($precstr) < $options['precision']) {
  364. $precstr = $precstr . str_pad("0", ($options['precision'] - iconv_strlen($precstr)), "0");
  365. }
  366. }
  367. } else {
  368. if ($options['precision'] > 0) {
  369. $precstr = str_pad("0", ($options['precision']), "0");
  370. }
  371. }
  372. if ($options['precision'] === null) {
  373. if (isset($precstr)) {
  374. $options['precision'] = iconv_strlen($precstr);
  375. } else {
  376. $options['precision'] = 0;
  377. }
  378. }
  379. // get fraction and format lengths
  380. if (strpos($value, '.') !== false) {
  381. $number = substr((string) $value, 0, strpos($value, '.'));
  382. } else {
  383. $number = $value;
  384. }
  385. $prec = call_user_func(Zend_Locale_Math::$sub, $value, $number, $options['precision']);
  386. $prec = Zend_Locale_Math::normalize($prec);
  387. if (iconv_strpos($prec, '-') !== false) {
  388. $prec = iconv_substr($prec, 1);
  389. }
  390. if (($prec == 0) and ($options['precision'] > 0)) {
  391. $prec = "0.0";
  392. }
  393. if (($options['precision'] + 2) > iconv_strlen($prec)) {
  394. $prec = str_pad((string) $prec, $options['precision'] + 2, "0", STR_PAD_RIGHT);
  395. }
  396. if (iconv_strpos($number, '-') !== false) {
  397. $number = iconv_substr($number, 1);
  398. }
  399. $group = iconv_strrpos($format, ',');
  400. $group2 = iconv_strpos ($format, ',');
  401. $point = iconv_strpos ($format, '0');
  402. // Add fraction
  403. $rest = "";
  404. if (iconv_strpos($format, '.')) {
  405. $rest = iconv_substr($format, iconv_strpos($format, '.') + 1);
  406. $length = iconv_strlen($rest);
  407. for($x = 0; $x < $length; ++$x) {
  408. if (($rest[0] == '0') || ($rest[0] == '#')) {
  409. $rest = iconv_substr($rest, 1);
  410. }
  411. }
  412. $format = iconv_substr($format, 0, iconv_strlen($format) - iconv_strlen($rest));
  413. }
  414. if ($options['precision'] == '0') {
  415. if (iconv_strrpos($format, '-') != 0) {
  416. $format = iconv_substr($format, 0, $point)
  417. . iconv_substr($format, iconv_strrpos($format, '#') + 2);
  418. } else {
  419. $format = iconv_substr($format, 0, $point);
  420. }
  421. } else {
  422. $format = iconv_substr($format, 0, $point) . $symbols['decimal']
  423. . iconv_substr($prec, 2);
  424. }
  425. $format .= $rest;
  426. // Add seperation
  427. if ($group == 0) {
  428. // no seperation
  429. $format = $number . iconv_substr($format, $point);
  430. } else if ($group == $group2) {
  431. // only 1 seperation
  432. $seperation = ($point - $group);
  433. for ($x = iconv_strlen($number); $x > $seperation; $x -= $seperation) {
  434. if (iconv_substr($number, 0, $x - $seperation) !== "") {
  435. $number = iconv_substr($number, 0, $x - $seperation) . $symbols['group']
  436. . iconv_substr($number, $x - $seperation);
  437. }
  438. }
  439. $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point);
  440. } else {
  441. // 2 seperations
  442. if (iconv_strlen($number) > ($point - $group)) {
  443. $seperation = ($point - $group);
  444. $number = iconv_substr($number, 0, iconv_strlen($number) - $seperation) . $symbols['group']
  445. . iconv_substr($number, iconv_strlen($number) - $seperation);
  446. if ((iconv_strlen($number) - 1) > ($point - $group + 1)) {
  447. $seperation2 = ($group - $group2 - 1);
  448. for ($x = iconv_strlen($number) - $seperation2 - 2; $x > $seperation2; $x -= $seperation2) {
  449. $number = iconv_substr($number, 0, $x - $seperation2) . $symbols['group']
  450. . iconv_substr($number, $x - $seperation2);
  451. }
  452. }
  453. }
  454. $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point);
  455. }
  456. // set negative sign
  457. if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $options['precision']) < 0) {
  458. if (iconv_strpos($format, '-') === false) {
  459. $format = $symbols['minus'] . $format;
  460. } else {
  461. $format = str_replace('-', $symbols['minus'], $format);
  462. }
  463. }
  464. return (string) $format;
  465. }
  466. /**
  467. * Checks if the input contains a normalized or localized number
  468. *
  469. * @param string $input Localized number string
  470. * @param array $options Options: locale. See {@link setOptions()} for details.
  471. * @return boolean Returns true if a number was found
  472. */
  473. public static function isNumber($input, array $options = array())
  474. {
  475. $options = self::_checkOptions($options) + self::$_options;
  476. // Get correct signs for this locale
  477. $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
  478. // Parse input locale aware
  479. $regex = '/^[' . $symbols['minus'] . $symbols['plus'] . '-+]?'
  480. . '\d(\d*(\\' . $symbols['group'] . ')?\d+)*'
  481. . '((\\' . $symbols['decimal'] . ')\d*)?([' . $symbols['exponent'] . 'eE]'
  482. . '([' . $symbols['minus'] . $symbols['plus'] . '+-])?\d+)?$/';
  483. preg_match($regex, $input, $found);
  484. if (!isset($found[0]))
  485. return false;
  486. return true;
  487. }
  488. /**
  489. * Alias for getNumber
  490. *
  491. * @param string $value Number to localize
  492. * @param array $options Options: locale, precision. See {@link setOptions()} for details.
  493. * @return float
  494. */
  495. public static function getFloat($input, array $options = array())
  496. {
  497. return floatval(self::getNumber($input, $options));
  498. }
  499. /**
  500. * Returns a locale formatted integer number
  501. * Alias for toNumber()
  502. *
  503. * @param string $value Number to normalize
  504. * @param array $options Options: locale, precision. See {@link setOptions()} for details.
  505. * @return string Locale formatted number
  506. */
  507. public static function toFloat($value, array $options = array())
  508. {
  509. $options['number_format'] = Zend_Locale_Format::STANDARD;
  510. return self::toNumber($value, $options);
  511. }
  512. /**
  513. * Returns if a float was found
  514. * Alias for isNumber()
  515. *
  516. * @param string $input Localized number string
  517. * @param array $options Options: locale. See {@link setOptions()} for details.
  518. * @return boolean Returns true if a number was found
  519. */
  520. public static function isFloat($value, array $options = array())
  521. {
  522. return self::isNumber($value, $options);
  523. }
  524. /**
  525. * Returns the first found integer from an string
  526. * Parsing depends on given locale (grouping and decimal)
  527. *
  528. * Examples for input:
  529. * ' 2345.4356,1234' = 23455456
  530. * '+23,3452.123' = 233452
  531. * ' 12343 ' = 12343
  532. * '-9456km' = -9456
  533. * '0' = 0
  534. * '(-){0,1}(\d+(\.){0,1})*(\,){0,1})\d+'
  535. *
  536. * @param string $input Input string to parse for numbers
  537. * @param array $options Options: locale. See {@link setOptions()} for details.
  538. * @return integer Returns the extracted number
  539. */
  540. public static function getInteger($input, array $options = array())
  541. {
  542. $options['precision'] = 0;
  543. return intval(self::getFloat($input, $options));
  544. }
  545. /**
  546. * Returns a localized number
  547. *
  548. * @param string $value Number to normalize
  549. * @param array $options Options: locale. See {@link setOptions()} for details.
  550. * @return string Locale formatted number
  551. */
  552. public static function toInteger($value, array $options = array())
  553. {
  554. $options['precision'] = 0;
  555. $options['number_format'] = Zend_Locale_Format::STANDARD;
  556. return self::toNumber($value, $options);
  557. }
  558. /**
  559. * Returns if a integer was found
  560. *
  561. * @param string $input Localized number string
  562. * @param array $options Options: locale. See {@link setOptions()} for details.
  563. * @return boolean Returns true if a integer was found
  564. */
  565. public static function isInteger($value, array $options = array())
  566. {
  567. if (!self::isNumber($value, $options)) {
  568. return false;
  569. }
  570. if (self::getInteger($value, $options) == self::getFloat($value, $options)) {
  571. return true;
  572. }
  573. return false;
  574. }
  575. /**
  576. * Converts a format string from PHP's date format to ISO format
  577. * Remember that Zend Date always returns localized string, so a month name which returns the english
  578. * month in php's date() will return the translated month name with this function... use 'en' as locale
  579. * if you are in need of the original english names
  580. *
  581. * The conversion has the following restrictions:
  582. * 'a', 'A' - Meridiem is not explicit upper/lowercase, you have to upper/lowercase the translated value yourself
  583. *
  584. * @param string $format Format string in PHP's date format
  585. * @return string Format string in ISO format
  586. */
  587. public static function convertPhpToIsoFormat($format)
  588. {
  589. $convert = array('d' => 'dd' , 'D' => 'EE' , 'j' => 'd' , 'l' => 'EEEE', 'N' => 'e' , 'S' => 'SS' ,
  590. 'w' => 'eee' , 'z' => 'D' , 'W' => 'ww' , 'F' => 'MMMM', 'm' => 'MM' , 'M' => 'MMM' ,
  591. 'n' => 'M' , 't' => 'ddd' , 'L' => 'l' , 'o' => 'YYYY', 'Y' => 'yyyy', 'y' => 'yy' ,
  592. 'a' => 'a' , 'A' => 'a' , 'B' => 'B' , 'g' => 'h' , 'G' => 'H' , 'h' => 'hh' ,
  593. 'H' => 'HH' , 'i' => 'mm' , 's' => 'ss' , 'e' => 'zzzz', 'I' => 'I' , 'O' => 'Z' ,
  594. 'P' => 'ZZZZ', 'T' => 'z' , 'Z' => 'X' , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ',
  595. 'r' => 'r' , 'U' => 'U');
  596. $values = str_split($format);
  597. foreach ($values as $key => $value) {
  598. if (isset($convert[$value]) === true) {
  599. $values[$key] = $convert[$value];
  600. }
  601. }
  602. return join($values);
  603. }
  604. /**
  605. * Parse date and split in named array fields
  606. *
  607. * @param string $date Date string to parse
  608. * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
  609. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
  610. */
  611. private static function _parseDate($date, $options)
  612. {
  613. $options = self::_checkOptions($options) + self::$_options;
  614. $test = array('h', 'H', 'm', 's', 'y', 'Y', 'M', 'd', 'D', 'E', 'S', 'l', 'B', 'I',
  615. 'X', 'r', 'U', 'G', 'w', 'e', 'a', 'A', 'Z', 'z', 'v');
  616. $format = $options['date_format'];
  617. $number = $date; // working copy
  618. $result['date_format'] = $format; // save the format used to normalize $number (convenience)
  619. $result['locale'] = $options['locale']; // save the locale used to normalize $number (convenience)
  620. $day = iconv_strpos($format, 'd');
  621. $month = iconv_strpos($format, 'M');
  622. $year = iconv_strpos($format, 'y');
  623. $hour = iconv_strpos($format, 'H');
  624. $min = iconv_strpos($format, 'm');
  625. $sec = iconv_strpos($format, 's');
  626. $am = null;
  627. if ($hour === false) {
  628. $hour = iconv_strpos($format, 'h');
  629. }
  630. if ($year === false) {
  631. $year = iconv_strpos($format, 'Y');
  632. }
  633. if ($day === false) {
  634. $day = iconv_strpos($format, 'E');
  635. if ($day === false) {
  636. $day = iconv_strpos($format, 'D');
  637. }
  638. }
  639. if ($day !== false) {
  640. $parse[$day] = 'd';
  641. if (!empty($options['locale']) && ($options['locale'] !== 'root') &&
  642. (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) {
  643. // erase day string
  644. $daylist = Zend_Locale_Data::getList($options['locale'], 'day');
  645. foreach($daylist as $key => $name) {
  646. if (iconv_strpos($number, $name) !== false) {
  647. $number = str_replace($name, "EEEE", $number);
  648. break;
  649. }
  650. }
  651. }
  652. }
  653. $position = false;
  654. if ($month !== false) {
  655. $parse[$month] = 'M';
  656. if (!empty($options['locale']) && ($options['locale'] !== 'root') &&
  657. (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) {
  658. // prepare to convert month name to their numeric equivalents, if requested,
  659. // and we have a $options['locale']
  660. $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'],
  661. 'month'));
  662. if ($position === false) {
  663. $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'],
  664. 'month', array('gregorian', 'format', 'abbreviated')));
  665. }
  666. }
  667. }
  668. if ($year !== false) {
  669. $parse[$year] = 'y';
  670. }
  671. if ($hour !== false) {
  672. $parse[$hour] = 'H';
  673. }
  674. if ($min !== false) {
  675. $parse[$min] = 'm';
  676. }
  677. if ($sec !== false) {
  678. $parse[$sec] = 's';
  679. }
  680. if (empty($parse)) {
  681. require_once 'Zend/Locale/Exception.php';
  682. throw new Zend_Locale_Exception("Unknown date format, neither date nor time in '" . $format . "' found");
  683. }
  684. ksort($parse);
  685. // get daytime
  686. if (iconv_strpos($format, 'a') !== false) {
  687. if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'am'))) !== false) {
  688. $am = true;
  689. } else if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'pm'))) !== false) {
  690. $am = false;
  691. }
  692. }
  693. // split number parts
  694. $split = false;
  695. preg_match_all('/\d+/u', $number, $splitted);
  696. if (count($splitted[0]) == 0) {
  697. require_once 'Zend/Locale/Exception.php';
  698. throw new Zend_Locale_Exception("No date part in '$date' found.");
  699. }
  700. if (count($splitted[0]) == 1) {
  701. $split = 0;
  702. }
  703. $cnt = 0;
  704. foreach($parse as $key => $value) {
  705. switch($value) {
  706. case 'd':
  707. if ($split === false) {
  708. if (count($splitted[0]) > $cnt) {
  709. $result['day'] = $splitted[0][$cnt];
  710. }
  711. } else {
  712. $result['day'] = iconv_substr($splitted[0][0], $split, 2);
  713. $split += 2;
  714. }
  715. ++$cnt;
  716. break;
  717. case 'M':
  718. if ($split === false) {
  719. if (count($splitted[0]) > $cnt) {
  720. $result['month'] = $splitted[0][$cnt];
  721. }
  722. } else {
  723. $result['month'] = iconv_substr($splitted[0][0], $split, 2);
  724. $split += 2;
  725. }
  726. ++$cnt;
  727. break;
  728. case 'y':
  729. $length = 2;
  730. if ((iconv_substr($format, $year, 4) == 'yyyy')
  731. || (iconv_substr($format, $year, 4) == 'YYYY')) {
  732. $length = 4;
  733. }
  734. if ($split === false) {
  735. if (count($splitted[0]) > $cnt) {
  736. $result['year'] = $splitted[0][$cnt];
  737. }
  738. } else {
  739. $result['year'] = iconv_substr($splitted[0][0], $split, $length);
  740. $split += $length;
  741. }
  742. ++$cnt;
  743. break;
  744. case 'H':
  745. if ($split === false) {
  746. if (count($splitted[0]) > $cnt) {
  747. $result['hour'] = $splitted[0][$cnt];
  748. }
  749. } else {
  750. $result['hour'] = iconv_substr($splitted[0][0], $split, 2);
  751. $split += 2;
  752. }
  753. ++$cnt;
  754. break;
  755. case 'm':
  756. if ($split === false) {
  757. if (count($splitted[0]) > $cnt) {
  758. $result['minute'] = $splitted[0][$cnt];
  759. }
  760. } else {
  761. $result['minute'] = iconv_substr($splitted[0][0], $split, 2);
  762. $split += 2;
  763. }
  764. ++$cnt;
  765. break;
  766. case 's':
  767. if ($split === false) {
  768. if (count($splitted[0]) > $cnt) {
  769. $result['second'] = $splitted[0][$cnt];
  770. }
  771. } else {
  772. $result['second'] = iconv_substr($splitted[0][0], $split, 2);
  773. $split += 2;
  774. }
  775. ++$cnt;
  776. break;
  777. }
  778. }
  779. // AM/PM correction
  780. if ($hour !== false) {
  781. if (($am === true) and ($result['hour'] == 12)){
  782. $result['hour'] = 0;
  783. } else if (($am === false) and ($result['hour'] != 12)) {
  784. $result['hour'] += 12;
  785. }
  786. }
  787. if ($options['fix_date'] === true) {
  788. $result['fixed'] = 0; // nothing has been "fixed" by swapping date parts around (yet)
  789. }
  790. if ($day !== false) {
  791. // fix false month
  792. if (isset($result['day']) and isset($result['month'])) {
  793. if (($position !== false) and ((iconv_strpos($date, $result['day']) === false) or
  794. (isset($result['year']) and (iconv_strpos($date, $result['year']) === false)))) {
  795. if ($options['fix_date'] !== true) {
  796. require_once 'Zend/Locale/Exception.php';
  797. throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format
  798. . "' (false month, $position, $month)");
  799. }
  800. $temp = $result['day'];
  801. $result['day'] = $result['month'];
  802. $result['month'] = $temp;
  803. $result['fixed'] = 1;
  804. }
  805. }
  806. // fix switched values d <> y
  807. if (isset($result['day']) and isset($result['year'])) {
  808. if ($result['day'] > 31) {
  809. if ($options['fix_date'] !== true) {
  810. require_once 'Zend/Locale/Exception.php';
  811. throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
  812. . $format . "' (d <> y)");
  813. }
  814. $temp = $result['year'];
  815. $result['year'] = $result['day'];
  816. $result['day'] = $temp;
  817. $result['fixed'] = 2;
  818. }
  819. }
  820. // fix switched values M <> y
  821. if (isset($result['month']) and isset($result['year'])) {
  822. if ($result['month'] > 31) {
  823. if ($options['fix_date'] !== true) {
  824. require_once 'Zend/Locale/Exception.php';
  825. throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
  826. . $format . "' (M <> y)");
  827. }
  828. $temp = $result['year'];
  829. $result['year'] = $result['month'];
  830. $result['month'] = $temp;
  831. $result['fixed'] = 3;
  832. }
  833. }
  834. // fix switched values M <> d
  835. if (isset($result['month']) and isset($result['day'])) {
  836. if ($result['month'] > 12) {
  837. if ($options['fix_date'] !== true || $result['month'] > 31) {
  838. require_once 'Zend/Locale/Exception.php';
  839. throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
  840. . $format . "' (M <> d)");
  841. }
  842. $temp = $result['day'];
  843. $result['day'] = $result['month'];
  844. $result['month'] = $temp;
  845. $result['fixed'] = 4;
  846. }
  847. }
  848. }
  849. if (isset($result['year'])) {
  850. if (((iconv_strlen($result['year']) == 2) && ($result['year'] < 10)) ||
  851. (((iconv_strpos($format, 'yy') !== false) && (iconv_strpos($format, 'yyyy') === false)) ||
  852. ((iconv_strpos($format, 'YY') !== false) && (iconv_strpos($format, 'YYYY') === false)))) {
  853. if (($result['year'] >= 0) && ($result['year'] < 100)) {
  854. if ($result['year'] < 70) {
  855. $result['year'] = (int) $result['year'] + 100;
  856. }
  857. $result['year'] = (int) $result['year'] + 1900;
  858. }
  859. }
  860. }
  861. return $result;
  862. }
  863. /**
  864. * Search $number for a month name found in $monthlist, and replace if found.
  865. *
  866. * @param string $number Date string (modified)
  867. * @param array $monthlist List of month names
  868. *
  869. * @return int|false Position of replaced string (false if nothing replaced)
  870. */
  871. protected static function _replaceMonth(&$number, $monthlist)
  872. {
  873. // If $locale was invalid, $monthlist will default to a "root" identity
  874. // mapping for each month number from 1 to 12.
  875. // If no $locale was given, or $locale was invalid, do not use this identity mapping to normalize.
  876. // Otherwise, translate locale aware month names in $number to their numeric equivalents.
  877. $position = false;
  878. if ($monthlist && $monthlist[1] != 1) {
  879. foreach($monthlist as $key => $name) {
  880. if (($position = iconv_strpos($number, $name)) !== false) {
  881. $number = str_ireplace($name, $key, $number);
  882. return $position;
  883. }
  884. }
  885. }
  886. return false;
  887. }
  888. /**
  889. * Returns the default date format for $locale.
  890. *
  891. * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT')
  892. * @return string format
  893. * @throws Zend_Locale_Exception throws an exception when locale data is broken
  894. */
  895. public static function getDateFormat($locale = null)
  896. {
  897. $format = Zend_Locale_Data::getContent($locale, 'date');
  898. if (empty($format)) {
  899. require_once 'Zend/Locale/Exception.php';
  900. throw new Zend_Locale_Exception("failed to receive data from locale $locale");
  901. }
  902. return $format;
  903. }
  904. /**
  905. * Returns an array with the normalized date from an locale date
  906. * a input of 10.01.2006 without a $locale would return:
  907. * array ('day' => 10, 'month' => 1, 'year' => 2006)
  908. * The 'locale' option is only used to convert human readable day
  909. * and month names to their numeric equivalents.
  910. * The 'format' option allows specification of self-defined date formats,
  911. * when not using the default format for the 'locale'.
  912. *
  913. * @param string $date Date string
  914. * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
  915. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
  916. */
  917. public static function getDate($date, array $options = array())
  918. {
  919. $options = self::_checkOptions($options) + self::$_options;
  920. if (empty($options['date_format'])) {
  921. $options['format_type'] = 'iso';
  922. $options['date_format'] = self::getDateFormat($options['locale']);
  923. }
  924. return self::_parseDate($date, $options);
  925. }
  926. /**
  927. * Returns if the given datestring contains all date parts from the given format.
  928. * If no format is given, the default date format from the locale is used
  929. * If you want to check if the date is a proper date you should use Zend_Date::isDate()
  930. *
  931. * @param string $date Date string
  932. * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
  933. * @return boolean
  934. */
  935. public static function checkDateFormat($date, array $options = array())
  936. {
  937. try {
  938. $date = self::getDate($date, $options);
  939. } catch (Exception $e) {
  940. return false;
  941. }
  942. if (empty($options['date_format'])) {
  943. $options['format_type'] = 'iso';
  944. $options['date_format'] = self::getDateFormat($options['locale']);
  945. }
  946. $options = self::_checkOptions($options) + self::$_options;
  947. // day expected but not parsed
  948. if ((iconv_strpos($options['date_format'], 'd') !== false) and (!isset($date['day']) or ($date['day'] == ""))) {
  949. return false;
  950. }
  951. // month expected but not parsed
  952. if ((iconv_strpos($options['date_format'], 'M') !== false) and (!isset($date['month']) or ($date['month'] == ""))) {
  953. return false;
  954. }
  955. // year expected but not parsed
  956. if (((iconv_strpos($options['date_format'], 'Y') !== false) or
  957. (iconv_strpos($options['date_format'], 'y') !== false)) and (!isset($date['year']) or ($date['year'] == ""))) {
  958. return false;
  959. }
  960. // second expected but not parsed
  961. if ((iconv_strpos($options['date_format'], 's') !== false) and (!isset($date['second']) or ($date['second'] == ""))) {
  962. return false;
  963. }
  964. // minute expected but not parsed
  965. if ((iconv_strpos($options['date_format'], 'm') !== false) and (!isset($date['minute']) or ($date['minute'] == ""))) {
  966. return false;
  967. }
  968. // hour expected but not parsed
  969. if (((iconv_strpos($options['date_format'], 'H') !== false) or
  970. (iconv_strpos($options['date_format'], 'h') !== false)) and (!isset($date['hour']) or ($date['hour'] == ""))) {
  971. return false;
  972. }
  973. return true;
  974. }
  975. /**
  976. * Returns the default time format for $locale.
  977. *
  978. * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT')
  979. * @return string format
  980. */
  981. public static function getTimeFormat($locale = null)
  982. {
  983. $format = Zend_Locale_Data::getContent($locale, 'time');
  984. if (empty($format)) {
  985. require_once 'Zend/Locale/Exception.php';
  986. throw new Zend_Locale_Exception("failed to receive data from locale $locale");
  987. }
  988. return $format;
  989. }
  990. /**
  991. * Returns an array with 'hour', 'minute', and 'second' elements extracted from $time
  992. * according to the order described in $format. For a format of 'H:m:s', and
  993. * an input of 11:20:55, getTime() would return:
  994. * array ('hour' => 11, 'minute' => 20, 'second' => 55)
  995. * The optional $locale parameter may be used to help extract times from strings
  996. * containing both a time and a day or month name.
  997. *
  998. * @param string $time Time string
  999. * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
  1000. * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
  1001. */
  1002. public static function getTime($time, array $options = array())
  1003. {
  1004. $options = self::_checkOptions($options) + self::$_options;
  1005. if (empty($options['date_format'])) {
  1006. $options['format_type'] = 'iso';
  1007. $options['date_format'] = self::getTimeFormat($options['locale']);
  1008. }
  1009. return self::_parseDate($time, $options);
  1010. }
  1011. }