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 Date 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 Date, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class Date extends DBField { |
||
22 | |||
23 | public function setValue($value, $record = null) { |
||
24 | View Code Duplication | if($value === false || $value === null || (is_string($value) && !strlen($value))) { |
|
|
|||
25 | // don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be |
||
26 | // saved as NULL in database |
||
27 | $this->value = null; |
||
28 | return; |
||
29 | } |
||
30 | |||
31 | // @todo This needs tidy up (what if you only specify a month and a year, for example?) |
||
32 | if(is_array($value)) { |
||
33 | if(!empty($value['Day']) && !empty($value['Month']) && !empty($value['Year'])) { |
||
34 | $this->value = $value['Year'] . '-' . $value['Month'] . '-' . $value['Day']; |
||
35 | return; |
||
36 | } else { |
||
37 | // return nothing (so checks below don't fail on an empty array) |
||
38 | return null; |
||
39 | } |
||
40 | } |
||
41 | |||
42 | // Default to NZ date format - strtotime expects a US date |
||
43 | View Code Duplication | if(preg_match('#^([0-9]+)/([0-9]+)/([0-9]+)$#', $value, $parts)) { |
|
44 | $value = "$parts[2]/$parts[1]/$parts[3]"; |
||
45 | } |
||
46 | |||
47 | if(is_numeric($value)) { |
||
48 | $this->value = date('Y-m-d', $value); |
||
49 | View Code Duplication | } elseif(is_string($value)) { |
|
50 | try{ |
||
51 | $date = new DateTime($value); |
||
52 | $this->value = $date->Format('Y-m-d'); |
||
53 | return; |
||
54 | }catch(Exception $e){ |
||
55 | $this->value = null; |
||
56 | return; |
||
57 | } |
||
58 | } |
||
59 | } |
||
60 | |||
61 | /** |
||
62 | * Returns the date in the format dd/mm/yy |
||
63 | */ |
||
64 | public function Nice() { |
||
67 | |||
68 | /** |
||
69 | * Returns the date in US format: “01/18/2006” |
||
70 | */ |
||
71 | public function NiceUS() { |
||
74 | |||
75 | /** |
||
76 | * Returns the year from the given date |
||
77 | */ |
||
78 | public function Year() { |
||
81 | |||
82 | /** |
||
83 | * Returns the Full day, of the given date. |
||
84 | */ |
||
85 | public function Day(){ |
||
88 | |||
89 | /** |
||
90 | * Returns a full textual representation of a month, such as January. |
||
91 | */ |
||
92 | public function Month() { |
||
95 | |||
96 | /** |
||
97 | * Returns the short version of the month such as Jan |
||
98 | */ |
||
99 | public function ShortMonth() { |
||
102 | |||
103 | /** |
||
104 | * Returns the day of the month. |
||
105 | * @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd" |
||
106 | * @return string |
||
107 | */ |
||
108 | public function DayOfMonth($includeOrdinal = false) { |
||
109 | if($this->value) { |
||
110 | $format = 'j'; |
||
111 | if ($includeOrdinal) $format .= 'S'; |
||
112 | return $this->Format($format); |
||
113 | } |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Returns the date in the format 24 December 2006 |
||
118 | */ |
||
119 | public function Long() { |
||
122 | |||
123 | /** |
||
124 | * Returns the date in the format 24 Dec 2006 |
||
125 | */ |
||
126 | public function Full() { |
||
129 | |||
130 | /** |
||
131 | * Return the date using a particular formatting string. |
||
132 | * |
||
133 | * @param string $format Format code string. e.g. "d M Y" (see http://php.net/date) |
||
134 | * @return string The date in the requested format |
||
135 | */ |
||
136 | public function Format($format) { |
||
137 | if($this->value){ |
||
138 | $date = new DateTime($this->value); |
||
139 | return $date->Format($format); |
||
140 | } |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * Return the date formatted using the given strftime formatting string. |
||
145 | * |
||
146 | * strftime obeys the current LC_TIME/LC_ALL when printing lexical values |
||
147 | * like day- and month-names |
||
148 | */ |
||
149 | public function FormatI18N($formattingString) { |
||
150 | if($this->value) { |
||
151 | return strftime($formattingString, strtotime($this->value)); |
||
152 | } |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * Return a date formatted as per a CMS user's settings. |
||
157 | * |
||
158 | * @param Member $member |
||
159 | * @return boolean | string A date formatted as per user-defined settings. |
||
160 | */ |
||
161 | public function FormatFromSettings($member = null) { |
||
162 | require_once 'Zend/Date.php'; |
||
163 | |||
164 | if(!$member) { |
||
165 | if(!Member::currentUserID()) { |
||
166 | return false; |
||
167 | } |
||
168 | $member = Member::currentUser(); |
||
169 | } |
||
170 | |||
171 | $formatD = $member->getDateFormat(); |
||
172 | $zendDate = new Zend_Date($this->getValue(), 'y-MM-dd'); |
||
173 | |||
174 | return $zendDate->toString($formatD); |
||
175 | } |
||
176 | |||
177 | /* |
||
178 | * Return a string in the form "12 - 16 Sept" or "12 Aug - 16 Sept" |
||
179 | * @param Date $otherDateObj Another date object specifying the end of the range |
||
180 | * @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd" |
||
181 | * @return string |
||
182 | */ |
||
183 | public function RangeString($otherDateObj, $includeOrdinals = false) { |
||
184 | $d1 = $this->DayOfMonth($includeOrdinals); |
||
185 | $d2 = $otherDateObj->DayOfMonth($includeOrdinals); |
||
186 | $m1 = $this->ShortMonth(); |
||
187 | $m2 = $otherDateObj->ShortMonth(); |
||
188 | $y1 = $this->Year(); |
||
189 | $y2 = $otherDateObj->Year(); |
||
190 | |||
191 | if($y1 != $y2) return "$d1 $m1 $y1 - $d2 $m2 $y2"; |
||
192 | else if($m1 != $m2) return "$d1 $m1 - $d2 $m2 $y1"; |
||
193 | else return "$d1 - $d2 $m1 $y1"; |
||
194 | } |
||
195 | |||
196 | public function Rfc822() { |
||
199 | |||
200 | public function Rfc2822() { |
||
203 | |||
204 | public function Rfc3339() { |
||
219 | |||
220 | /** |
||
221 | * Returns the number of seconds/minutes/hours/days or months since the timestamp. |
||
222 | * |
||
223 | * @param boolean $includeSeconds Show seconds, or just round to "less than a minute". |
||
224 | * @param int $significance Minimum significant value of X for "X units ago" to display |
||
225 | * @return String |
||
226 | */ |
||
227 | public function Ago($includeSeconds = true, $significance = 2) { |
||
247 | |||
248 | /** |
||
249 | * @param boolean $includeSeconds Show seconds, or just round to "less than a minute". |
||
250 | * @param int $significance Minimum significant value of X for "X units ago" to display |
||
251 | * @return string |
||
252 | */ |
||
253 | public function TimeDiff($includeSeconds = true, $significance = 2) { |
||
254 | if(!$this->value) return false; |
||
255 | |||
256 | $time = SS_Datetime::now()->Format('U'); |
||
257 | $ago = abs($time - strtotime($this->value)); |
||
258 | if($ago < 60 && !$includeSeconds) { |
||
259 | return _t('Date.LessThanMinuteAgo', 'less than a minute'); |
||
260 | } elseif($ago < $significance * 60 && $includeSeconds) { |
||
261 | return $this->TimeDiffIn('seconds'); |
||
262 | } elseif($ago < $significance * 3600) { |
||
263 | return $this->TimeDiffIn('minutes'); |
||
264 | } elseif($ago < $significance * 86400) { |
||
265 | return $this->TimeDiffIn('hours'); |
||
266 | } elseif($ago < $significance * 86400 * 30) { |
||
267 | return $this->TimeDiffIn('days'); |
||
268 | } elseif($ago < $significance * 86400 * 365) { |
||
269 | return $this->TimeDiffIn('months'); |
||
270 | } else { |
||
271 | return $this->TimeDiffIn('years'); |
||
272 | } |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * Gets the time difference, but always returns it in a certain format |
||
277 | * |
||
278 | * @param string $format The format, could be one of these: |
||
279 | * 'seconds', 'minutes', 'hours', 'days', 'months', 'years'. |
||
280 | * @return string The resulting formatted period |
||
281 | */ |
||
282 | public function TimeDiffIn($format) { |
||
283 | if(!$this->value) return false; |
||
284 | |||
285 | $time = SS_Datetime::now()->Format('U'); |
||
286 | $ago = abs($time - strtotime($this->value)); |
||
287 | |||
288 | switch($format) { |
||
289 | case "seconds": |
||
290 | $span = $ago; |
||
291 | return ($span != 1) ? "{$span} "._t("Date.SECS", "secs") : "{$span} "._t("Date.SEC", "sec"); |
||
292 | |||
293 | case "minutes": |
||
294 | $span = round($ago/60); |
||
295 | return ($span != 1) ? "{$span} "._t("Date.MINS", "mins") : "{$span} "._t("Date.MIN", "min"); |
||
296 | |||
297 | case "hours": |
||
298 | $span = round($ago/3600); |
||
299 | return ($span != 1) ? "{$span} "._t("Date.HOURS", "hours") : "{$span} "._t("Date.HOUR", "hour"); |
||
300 | |||
301 | case "days": |
||
302 | $span = round($ago/86400); |
||
303 | return ($span != 1) ? "{$span} "._t("Date.DAYS", "days") : "{$span} "._t("Date.DAY", "day"); |
||
304 | |||
305 | case "months": |
||
306 | $span = round($ago/86400/30); |
||
307 | return ($span != 1) ? "{$span} "._t("Date.MONTHS", "months") : "{$span} "._t("Date.MONTH", "month"); |
||
308 | |||
309 | case "years": |
||
310 | $span = round($ago/86400/365); |
||
311 | return ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year"); |
||
312 | } |
||
313 | } |
||
314 | |||
315 | public function requireField() { |
||
316 | $parts=Array('datatype'=>'date', 'arrayValue'=>$this->arrayValue); |
||
317 | $values=Array('type'=>'date', 'parts'=>$parts); |
||
318 | DB::require_field($this->tableName, $this->name, $values); |
||
319 | } |
||
320 | |||
321 | /** |
||
322 | * Returns true if date is in the past. |
||
323 | * @return boolean |
||
324 | */ |
||
325 | public function InPast() { |
||
328 | |||
329 | /** |
||
330 | * Returns true if date is in the future. |
||
331 | * @return boolean |
||
332 | */ |
||
333 | public function InFuture() { |
||
336 | |||
337 | /** |
||
338 | * Returns true if date is today. |
||
339 | * @return boolean |
||
340 | */ |
||
341 | public function IsToday() { |
||
344 | |||
345 | /** |
||
346 | * Returns a date suitable for insertion into a URL and use by the system. |
||
347 | */ |
||
348 | public function URLDate() { |
||
351 | |||
352 | |||
353 | public function days_between($fyear, $fmonth, $fday, $tyear, $tmonth, $tday){ |
||
356 | |||
357 | public function day_before($fyear, $fmonth, $fday){ |
||
360 | |||
361 | public function next_day($fyear, $fmonth, $fday){ |
||
364 | |||
365 | public function weekday($fyear, $fmonth, $fday){ // 0 is a Monday |
||
368 | |||
369 | public function prior_monday($fyear, $fmonth, $fday){ |
||
372 | |||
373 | /** |
||
374 | * Return the nearest date in the past, based on day and month. |
||
375 | * Automatically attaches the correct year. |
||
376 | * |
||
377 | * This is useful for determining a financial year start or end date. |
||
378 | * |
||
379 | * @param $fmonth int The number of the month (e.g. 3 is March, 4 is April) |
||
380 | * @param $fday int The day of the month |
||
381 | * @param $fyear int Determine historical value |
||
382 | * @return string Date in YYYY-MM-DD format |
||
383 | */ |
||
384 | public static function past_date($fmonth, $fday = 1, $fyear = null) { |
||
399 | |||
400 | View Code Duplication | public function scaffoldFormField($title = null, $params = null) { |
|
412 | } |
||
413 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.