Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Time often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Time, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 29 | class Time |
||
| 30 | { |
||
| 31 | /** |
||
| 32 | * cleanTime |
||
| 33 | * |
||
| 34 | * @param number|\DateTime|string $time An Unix timestamp, DateTime instance or string accepted by strtotime. |
||
| 35 | * |
||
| 36 | * @return \DateTime |
||
| 37 | */ |
||
| 38 | 39 | public static function cleanTime($time = null) |
|
| 39 | { |
||
| 40 | 39 | if (is_a($time, '\DateTime')) { |
|
| 41 | 17 | return $time->setTimezone(Locale::getTimeZone()); |
|
|
|
|||
| 42 | } |
||
| 43 | 23 | if ($time === null || $time === 0 || $time === '') { |
|
| 44 | 22 | return new \DateTime('now', Locale::getTimeZone()); |
|
| 45 | } |
||
| 46 | 21 | return Calendar::toDateTime($time, Locale::getTimeZone()); |
|
| 47 | } |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Describe an relative interval from $dateStart to $dateEnd (eg '2 days ago'). |
||
| 51 | * Only the largest differing unit is described, and the next smaller unit will be used |
||
| 52 | * for rounding. |
||
| 53 | * |
||
| 54 | * @param \DateTime $dateEnd The terminal date |
||
| 55 | * @param \DateTime|null $dateStart The anchor date, defaults to now. (if it has a timezone different than |
||
| 56 | * $dateEnd, we'll use the one of $dateEnd) |
||
| 57 | * @param string $width The format name; it can be '', 'short' or 'narrow' |
||
| 58 | * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data |
||
| 59 | * |
||
| 60 | * @return string |
||
| 61 | * |
||
| 62 | * @throws \InvalidArgumentException |
||
| 63 | */ |
||
| 64 | 14 | public static function describeRelativeInterval($dateEnd, $dateStart = null, $width = '', $locale = '') |
|
| 65 | { |
||
| 66 | 14 | if (!is_a($dateEnd, '\DateTime')) { |
|
| 67 | throw new \InvalidArgumentException('Not a DateTime object'); |
||
| 68 | } |
||
| 69 | 14 | if (empty($dateStart) && ($dateStart !== 0) && ($dateStart !== '0')) { |
|
| 70 | 14 | $dateStart = new \DateTime('now'); |
|
| 71 | } elseif (!is_a($dateStart, '\DateTime')) { |
||
| 72 | throw new \InvalidArgumentException('Not a DateTime object'); |
||
| 73 | } else { |
||
| 74 | $dateStart = clone $dateStart; |
||
| 75 | } |
||
| 76 | 14 | $dateStart->setTimezone($dateEnd->getTimezone()); |
|
| 77 | |||
| 78 | //$utc = new \DateTimeZone('UTC'); |
||
| 79 | //$dateEndUTC = new \DateTime($dateEnd->format('Y-m-d H:i:s'), $utc); |
||
| 80 | //$dateStartUTC = new \DateTime($dateStart->format('Y-m-d H:i:s'), $utc); |
||
| 81 | 14 | $parts = array(); |
|
| 82 | 14 | $data = Data::get('dateFields', $locale); |
|
| 83 | |||
| 84 | 14 | $diff = $dateStart->diff($dateEnd, false); |
|
| 85 | 14 | $past = (boolean) $diff->invert; |
|
| 86 | 14 | $value = 0; |
|
| 87 | 14 | $key = ''; |
|
| 88 | 14 | if ($diff->y != 0) { |
|
| 89 | 2 | $key = 'year'; |
|
| 90 | 2 | $value = $diff->y + (($diff->m > 6) ? 1 : 0); |
|
| 91 | 12 | } elseif ($diff->m != 0) { |
|
| 92 | $key = 'month'; |
||
| 93 | $value = $diff->m + (($diff->d > 15) ? 1 : 0); |
||
| 94 | 12 | } elseif ($diff->d != 0) { |
|
| 95 | 6 | $key = 'day'; |
|
| 96 | 6 | $value = $diff->d + (($diff->h > 12) ? 1 : 0); |
|
| 97 | 6 | } elseif ($diff->h != 0) { |
|
| 98 | $key = 'hour'; |
||
| 99 | $value = $diff->h + (($diff->i > 30) ? 1 : 0); |
||
| 100 | 6 | } elseif ($diff->i != 0) { |
|
| 101 | $key = 'minute'; |
||
| 102 | $value = $diff->i + (($diff->s > 30) ? 1 : 0); |
||
| 103 | 6 | } elseif ($diff->s != 0) { |
|
| 104 | 4 | $key = 'second'; |
|
| 105 | 4 | $value = $diff->s; |
|
| 106 | } |
||
| 107 | 14 | if ($value==0) { |
|
| 108 | 2 | $key = 'second'; |
|
| 109 | 2 | $relKey = 'relative-type-0'; |
|
| 110 | 2 | $relPattern = null; |
|
| 111 | 12 | } elseif ($key === 'day' && $value >1 && $value <7) { |
|
| 112 | $dow = $dateEnd->format('N') - 1; |
||
| 113 | $days = array('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'); |
||
| 114 | $key = $days[$dow]; |
||
| 115 | $relKey = ($past) ? "relative-type--1" : "relative-type-1"; |
||
| 116 | $relPattern = null; |
||
| 117 | } else { |
||
| 118 | 12 | if ($value == 1 && isset($data[$key]['relative-type--1'])) { |
|
| 119 | 4 | $relKey = ($past) ? 'relative-type--1' : 'relative-type-1'; |
|
| 120 | 4 | $relPattern = null; |
|
| 121 | } else { |
||
| 122 | 8 | $relKey = ($past) ? 'relativeTime-type-past' : 'relativeTime-type-future'; |
|
| 123 | 8 | $rule = Plural::getRule($value, $locale); |
|
| 124 | 8 | $relPattern = 'relativeTimePattern-count-' . $rule; |
|
| 125 | } |
||
| 126 | } |
||
| 127 | 14 | if (!empty($width) && array_key_exists($key . '-' . $width, $data)) { |
|
| 128 | $key .= '-' . $width; |
||
| 129 | } |
||
| 130 | 14 | if (empty($relPattern)) { |
|
| 131 | 6 | $relativeString = $data[$key][$relKey]; |
|
| 132 | } else { |
||
| 133 | 8 | $tempString = $data[$key][$relKey][$relPattern]; |
|
| 134 | 8 | $tempString = str_replace('{0}', '%d', $tempString); |
|
| 135 | 8 | $relativeString = sprintf($tempString, $value); |
|
| 136 | } |
||
| 137 | 14 | return $relativeString; |
|
| 138 | } |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Format a date. |
||
| 142 | * |
||
| 143 | * @param number|\DateTime|string $value An Unix timestamp, a `\DateTime` instance or a string accepted |
||
| 144 | * by strtotime(). |
||
| 145 | * @param string $width The format name; it can be |
||
| 146 | * 'full' (eg 'EEEE, MMMM d, y' - 'Wednesday, August 20, 2014'), |
||
| 147 | * 'long' (eg 'MMMM d, y' - 'August 20, 2014'), |
||
| 148 | * 'medium' (eg 'MMM d, y' - 'August 20, 2014') or |
||
| 149 | * 'short' (eg 'M/d/yy' - '8/20/14'). |
||
| 150 | * @param string|\DateTimeZone $toTimezone The timezone to set; leave empty to use the default timezone |
||
| 151 | * (or the timezone associated to $value if it's already a \DateTime) |
||
| 152 | * @param string $locale The locale to use. If empty we'll use the default |
||
| 153 | * |
||
| 154 | * @return string Returns an empty string if $value is empty, the localized textual representation otherwise |
||
| 155 | */ |
||
| 156 | 4 | public static function formatDate($value, $width = 'short', $toTimezone = '', $locale = '') |
|
| 157 | { |
||
| 158 | try { |
||
| 159 | 4 | $formatted = Calendar::formatDateEx($value, $width, $toTimezone, $locale); |
|
| 160 | } catch (\Punic\Exception $e) { |
||
| 161 | \Xoops::getInstance()->events()->triggerEvent('core.exception', $e); |
||
| 162 | $formatted = ''; |
||
| 163 | } |
||
| 164 | 4 | return $formatted; |
|
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Format a date. |
||
| 169 | * |
||
| 170 | * @param number|\DateTime|string $value An Unix timestamp, a `\DateTime` instance or a string accepted |
||
| 171 | * by strtotime(). |
||
| 172 | * @param string $width The format name; it can be |
||
| 173 | * 'full' (eg 'h:mm:ss a zzzz' - '11:42:13 AM GMT+2:00'), |
||
| 174 | * 'long' (eg 'h:mm:ss a z' - '11:42:13 AM GMT+2:00'), |
||
| 175 | * 'medium' (eg 'h:mm:ss a' - '11:42:13 AM') or |
||
| 176 | * 'short' (eg 'h:mm a' - '11:42 AM') |
||
| 177 | * @param string|\DateTimeZone $toTimezone The timezone to set; leave empty to use the default timezone |
||
| 178 | * (or the timezone associated to $value if it's already a \DateTime) |
||
| 179 | * @param string $locale The locale to use. If empty we'll use the default |
||
| 180 | * |
||
| 181 | * @return string Returns an empty string if $value is empty, the localized textual representation otherwise |
||
| 182 | * |
||
| 183 | * @throws \Punic\Exception Throws an exception in case of problems |
||
| 184 | */ |
||
| 185 | 5 | public static function formatTime($value, $width = 'short', $toTimezone = '', $locale = '') |
|
| 186 | { |
||
| 187 | try { |
||
| 188 | 5 | $formatted = Calendar::formatTimeEx($value, $width, $toTimezone, $locale); |
|
| 189 | } catch (\Punic\Exception $e) { |
||
| 190 | \Xoops::getInstance()->events()->triggerEvent('core.exception', $e); |
||
| 191 | $formatted = ''; |
||
| 192 | } |
||
| 193 | 5 | return $formatted; |
|
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Format a date/time. |
||
| 198 | * |
||
| 199 | * @param \DateTime $value The \DateTime instance for which you want the localized textual representation |
||
| 200 | * @param string $width The format name; it can be 'full', 'long', 'medium', 'short' or a combination |
||
| 201 | * for date+time like 'full|short' or a combination for format+date+time like |
||
| 202 | * 'full|full|short' |
||
| 203 | * You can also append an asterisk ('*') to the date part of $width. If so, |
||
| 204 | * special day names may be used (like 'Today', 'Yesterday', 'Tomorrow') instead |
||
| 205 | * of the date part. |
||
| 206 | * @param string $locale The locale to use. If empty we'll use the default locale |
||
| 207 | * |
||
| 208 | * @return string Returns an empty string if $value is empty, the localized textual representation otherwise |
||
| 209 | * |
||
| 210 | * @throws \Punic\Exception Throws an exception in case of problems |
||
| 211 | */ |
||
| 212 | 8 | public static function formatDateTime(\DateTime $value, $width, $locale = '') |
|
| 216 | |||
| 217 | /** |
||
| 218 | * Perform any localization required for date picker used in Form\DateSelect |
||
| 219 | * |
||
| 220 | * @return void |
||
| 221 | */ |
||
| 222 | public static function localizeDatePicker() |
||
| 244 | |||
| 245 | /** |
||
| 246 | * turn a utf8 string into an array of characters |
||
| 247 | * |
||
| 248 | * @param string $input string to convert |
||
| 249 | * |
||
| 250 | * @return array |
||
| 251 | */ |
||
| 252 | protected static function utf8StringToChars($input) |
||
| 261 | |||
| 262 | /** |
||
| 263 | * parse a date input according to a locale and apply it to a DateTime object |
||
| 264 | * |
||
| 265 | * @param \DateTime $datetime datetime to apply date to |
||
| 266 | * @param string $input localized date string |
||
| 267 | * @param string $locale optional locale to use, leave blank to use current |
||
| 268 | * |
||
| 269 | * @return void |
||
| 270 | * |
||
| 271 | * @throws \Punic\Exception\ValueNotInList |
||
| 272 | */ |
||
| 273 | protected static function parseInputDate(\DateTime $datetime, $input, $locale = '') |
||
| 364 | |||
| 365 | /** |
||
| 366 | * parse a time input according to a locale and apply it to a DateTime object |
||
| 367 | * |
||
| 368 | * @param \DateTime $datetime datetime to apply time to |
||
| 369 | * @param string $input localized time string |
||
| 370 | * @param string $locale optional locale to use, leave blank to use current |
||
| 371 | * |
||
| 372 | * @return void |
||
| 373 | * |
||
| 374 | * @throws \Punic\Exception\BadArgumentType |
||
| 375 | * @throws \Punic\Exception\ValueNotInList |
||
| 376 | */ |
||
| 377 | protected static function parseInputTime(\DateTime $datetime, $input, $locale = '') |
||
| 468 | |||
| 469 | /** |
||
| 470 | * Convert a XOOPS DateSelect or DateTime form input into a DateTime object |
||
| 471 | * |
||
| 472 | * @param string|string[] $input date string, or array of date and time strings |
||
| 473 | * @param string $locale optional locale to use, leave blank to use current |
||
| 474 | * |
||
| 475 | * @return \DateTime |
||
| 476 | */ |
||
| 477 | public static function inputToDateTime($input, $locale = '') |
||
| 490 | } |
||
| 491 |
If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe: