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: