1<?php 2 3namespace Punic; 4 5/** 6 * Units helper stuff. 7 */ 8class Unit 9{ 10 /** 11 * Get the list of all the available units. 12 * 13 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data 14 */ 15 public static function getAvailableUnits($locale = '') 16 { 17 $data = Data::get('units', $locale); 18 $categories = array(); 19 foreach ($data as $width => $units) { 20 if ($width[0] !== '_') { 21 foreach ($units as $category => $units) { 22 if ($category[0] !== '_') { 23 $unitIDs = array_keys($units); 24 if (isset($categories[$category])) { 25 $categories[$category] = array_unique(array_merge($categories[$category], $unitIDs)); 26 } else { 27 $categories[$category] = array_keys($units); 28 } 29 } 30 } 31 } 32 } 33 ksort($categories); 34 foreach (array_keys($categories) as $category) { 35 sort($categories[$category]); 36 } 37 38 return $categories; 39 } 40 41 /** 42 * Get the localized name of a unit. 43 * 44 * @param string $unit The unit identifier (eg 'duration/millisecond' or 'millisecond') 45 * @param string $width The format name; it can be 'long' ('milliseconds'), 'short' (eg 'millisecs') or 'narrow' (eg 'msec') 46 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data 47 * 48 * @throws Exception\ValueNotInList 49 * 50 * @return string 51 */ 52 public static function getName($unit, $width = 'short', $locale = '') 53 { 54 $data = self::getDataForWidth($width, $locale); 55 $unitData = self::getDataForUnit($data, $unit); 56 57 return $unitData['_name']; 58 } 59 60 /** 61 * Get the "per" localized format string of a unit. 62 * 63 * @param string $unit The unit identifier (eg 'duration/minute' or 'minute') 64 * @param string $width The format name; it can be 'long' ('%1$s per minute'), 'short' (eg '%1$s/min') or 'narrow' (eg '%1$s/min') 65 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data 66 * 67 * @throws Exception\ValueNotInList 68 * 69 * @return string 70 */ 71 public static function getPerFormat($unit, $width = 'short', $locale = '') 72 { 73 $data = self::getDataForWidth($width, $locale); 74 $unitData = self::getDataForUnit($data, $unit); 75 76 if (isset($unitData['_per'])) { 77 return $unitData['_per']; 78 } 79 $pluralRule = Plural::getRuleOfType(1, Plural::RULETYPE_CARDINAL, $locale); 80 $name = trim(sprintf($unitData[$pluralRule], '')); 81 82 return sprintf($data['_compoundPattern'], '%1$s', $name); 83 } 84 85 /** 86 * Format a unit string. 87 * 88 * @param int|float|string $number The unit amount 89 * @param string $unit The unit identifier (eg 'duration/millisecond' or 'millisecond') 90 * @param string $width The format name; it can be 'long' (eg '3 milliseconds'), 'short' (eg '3 ms') or 'narrow' (eg '3ms'). You can also add a precision specifier ('long,2' or just '2') 91 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data 92 * 93 * @throws Exception\ValueNotInList 94 * 95 * @return string 96 */ 97 public static function format($number, $unit, $width = 'short', $locale = '') 98 { 99 $precision = null; 100 $m = null; 101 if (is_int($width)) { 102 $precision = $width; 103 $width = 'short'; 104 } elseif (is_string($width) && preg_match('/^(?:(.*),)?([+\\-]?\\d+)$/', $width, $m)) { 105 $precision = (int) $m[2]; 106 $width = (string) $m[1]; 107 if ($width === '') { 108 $width = 'short'; 109 } 110 } 111 $data = self::getDataForWidth($width, $locale); 112 $rules = self::getDataForUnit($data, $unit); 113 $pluralRule = Plural::getRuleOfType($number, Plural::RULETYPE_CARDINAL, $locale); 114 //@codeCoverageIgnoreStart 115 // These checks aren't necessary since $pluralRule should always be in $rules, but they don't hurt ;) 116 if (!isset($rules[$pluralRule])) { 117 if (isset($rules['other'])) { 118 $pluralRule = 'other'; 119 } else { 120 $availableRules = array_keys($rules); 121 $pluralRule = $availableRules[0]; 122 } 123 } 124 //@codeCoverageIgnoreEnd 125 return sprintf($rules[$pluralRule], Number::format($number, $precision, $locale)); 126 } 127 128 /** 129 * Retrieve the measurement systems and their localized names. 130 * 131 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data 132 * 133 * @return array The array keys are the measurement system codes (eg 'metric', 'US', 'UK'), the values are the localized measurement system names (eg 'Metric', 'US', 'UK' for English) 134 */ 135 public static function getMeasurementSystems($locale = '') 136 { 137 return Data::get('measurementSystemNames', $locale); 138 } 139 140 /** 141 * Retrieve the measurement system for a specific territory. 142 * 143 * @param string $territoryCode The territory code (eg. 'US' for 'United States of America'). 144 * 145 * @return string Return the measurement system code (eg: 'metric') for the specified territory. If $territoryCode is not valid we'll return an empty string. 146 */ 147 public static function getMeasurementSystemFor($territoryCode) 148 { 149 $result = ''; 150 if (is_string($territoryCode) && preg_match('/^[a-z0-9]{2,3}$/i', $territoryCode)) { 151 $territoryCode = strtoupper($territoryCode); 152 $data = Data::getGeneric('measurementData'); 153 while ($territoryCode !== '') { 154 if (isset($data['measurementSystem'][$territoryCode])) { 155 $result = $data['measurementSystem'][$territoryCode]; 156 break; 157 } 158 $territoryCode = Territory::getParentTerritoryCode($territoryCode); 159 } 160 } 161 162 return $result; 163 } 164 165 /** 166 * Returns the list of countries that use a specific measurement system. 167 * 168 * @param string $measurementSystem The measurement system identifier ('metric', 'US' or 'UK') 169 * 170 * @return array The list of country IDs that use the specified measurement system (if $measurementSystem is invalid you'll get an empty array) 171 */ 172 public static function getCountriesWithMeasurementSystem($measurementSystem) 173 { 174 $result = array(); 175 if (is_string($measurementSystem) && $measurementSystem !== '') { 176 $someGroup = false; 177 $data = Data::getGeneric('measurementData'); 178 foreach ($data['measurementSystem'] as $territory => $ms) { 179 if (strcasecmp($measurementSystem, $ms) === 0) { 180 $children = Territory::getChildTerritoryCodes($territory, true); 181 if (empty($children)) { 182 $result[] = $territory; 183 } else { 184 $someGroup = true; 185 $result = array_merge($result, $children); 186 } 187 } 188 } 189 if ($someGroup) { 190 $otherCountries = array(); 191 foreach ($data['measurementSystem'] as $territory => $ms) { 192 if (($territory !== '001') && (strcasecmp($measurementSystem, $ms) !== 0)) { 193 $children = Territory::getChildTerritoryCodes($territory, true); 194 if (empty($children)) { 195 $otherCountries[] = $territory; 196 } else { 197 $otherCountries = array_merge($otherCountries, $children); 198 } 199 } 200 } 201 $result = array_values(array_diff($result, $otherCountries)); 202 } 203 } 204 205 return $result; 206 } 207 208 /** 209 * Retrieve the standard paper size for a specific territory. 210 * 211 * @param string $territoryCode The territory code (eg. 'US' for 'United States of America'). 212 * 213 * @return string Return the standard paper size (eg: 'A4' or 'US-Letter') for the specified territory. If $territoryCode is not valid we'll return an empty string. 214 */ 215 public static function getPaperSizeFor($territoryCode) 216 { 217 $result = ''; 218 if (is_string($territoryCode) && preg_match('/^[a-z0-9]{2,3}$/i', $territoryCode)) { 219 $territoryCode = strtoupper($territoryCode); 220 $data = Data::getGeneric('measurementData'); 221 while ($territoryCode !== '') { 222 if (isset($data['paperSize'][$territoryCode])) { 223 $result = $data['paperSize'][$territoryCode]; 224 break; 225 } 226 $territoryCode = Territory::getParentTerritoryCode($territoryCode); 227 } 228 } 229 230 return $result; 231 } 232 233 /** 234 * Returns the list of countries that use a specific paper size by default. 235 * 236 * @param string $paperSize The paper size identifier ('A4' or 'US-Letter') 237 * 238 * @return array The list of country IDs that use the specified paper size (if $paperSize is invalid you'll get an empty array) 239 */ 240 public static function getCountriesWithPaperSize($paperSize) 241 { 242 $result = array(); 243 if (is_string($paperSize) && $paperSize !== '') { 244 $someGroup = false; 245 $data = Data::getGeneric('measurementData'); 246 foreach ($data['paperSize'] as $territory => $ms) { 247 if (strcasecmp($paperSize, $ms) === 0) { 248 $children = Territory::getChildTerritoryCodes($territory, true); 249 if (empty($children)) { 250 $result[] = $territory; 251 } else { 252 $someGroup = true; 253 $result = array_merge($result, $children); 254 } 255 } 256 } 257 if ($someGroup) { 258 $otherCountries = array(); 259 foreach ($data['paperSize'] as $territory => $ms) { 260 if (($territory !== '001') && (strcasecmp($paperSize, $ms) !== 0)) { 261 $children = Territory::getChildTerritoryCodes($territory, true); 262 if (empty($children)) { 263 $otherCountries[] = $territory; 264 } else { 265 $otherCountries = array_merge($otherCountries, $children); 266 } 267 } 268 } 269 $result = array_values(array_diff($result, $otherCountries)); 270 } 271 } 272 273 return $result; 274 } 275 276 /** 277 * Get the width-specific unit data. 278 * 279 * @param string $width the data width 280 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data 281 * 282 * @throws Exception\ValueNotInList 283 * 284 * @return array 285 */ 286 private static function getDataForWidth($width, $locale = '') 287 { 288 $data = Data::get('units', $locale); 289 if ($width[0] === '_' || !isset($data[$width])) { 290 $widths = array(); 291 foreach (array_keys($data) as $w) { 292 if (strpos($w, '_') !== 0) { 293 $widths[] = $w; 294 } 295 } 296 throw new Exception\ValueNotInList($width, $widths); 297 } 298 299 return $data[$width]; 300 } 301 302 /** 303 * Get a unit-specific data. 304 * 305 * @param array $data the width-specific data 306 * @param string $unit The unit identifier (eg 'duration/millisecond' or 'millisecond') 307 * 308 * @throws Exception\ValueNotInList 309 * 310 * @return array 311 */ 312 private static function getDataForUnit(array $data, $unit) 313 { 314 $chunks = explode('/', $unit, 2); 315 if (isset($chunks[1])) { 316 list($unitCategory, $unitID) = $chunks; 317 } else { 318 $unitCategory = null; 319 $unitID = null; 320 foreach (array_keys($data) as $c) { 321 if ($c[0] !== '_') { 322 if (isset($data[$c][$unit])) { 323 if ($unitCategory === null) { 324 $unitCategory = $c; 325 $unitID = $unit; 326 } else { 327 $unitCategory = null; 328 break; 329 } 330 } 331 } 332 } 333 } 334 if ( 335 $unitCategory === null || $unitCategory[0] === '_' 336 || !isset($data[$unitCategory]) 337 || $unitID === null || $unitID[0] === '_' 338 || !isset($data[$unitCategory][$unitID]) 339 ) { 340 $units = array(); 341 foreach ($data as $c => $us) { 342 if (strpos($c, '_') === false) { 343 foreach (array_keys($us) as $u) { 344 if (strpos($c, '_') === false) { 345 $units[] = "{$c}/{$u}"; 346 } 347 } 348 } 349 } 350 throw new \Punic\Exception\ValueNotInList($unit, $units); 351 } 352 353 return $data[$unitCategory][$unitID]; 354 } 355} 356