Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

SS_Datetime::setValue()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 27
Code Lines 16

Duplication

Lines 20
Ratio 74.07 %
Metric Value
dl 20
loc 27
rs 4.909
cc 9
eloc 16
nc 11
nop 2
1
<?php
2
/**
3
 * Represents a date-time field.
4
 * The field currently supports New Zealand date format (DD/MM/YYYY),
5
 * or an ISO 8601 formatted date and time (Y-m-d H:i:s).
6
 * Alternatively you can set a timestamp that is evaluated through
7
 * PHP's built-in date() and strtotime() function according to your system locale.
8
 *
9
 * For all computations involving the current date and time,
10
 * please use {@link SS_Datetime::now()} instead of PHP's built-in date() and time()
11
 * methods. This ensures that all time-based computations are testable with mock dates
12
 * through {@link SS_Datetime::set_mock_now()}.
13
 *
14
 * Example definition via {@link DataObject::$db}:
15
 * <code>
16
 * static $db = array(
17
 *  "Expires" => "SS_Datetime",
18
 * );
19
 * </code>
20
 *
21
 * @todo Add localization support, see http://open.silverstripe.com/ticket/2931
22
 *
23
 * @package framework
24
 * @subpackage model
25
 */
26
class SS_Datetime extends Date implements TemplateGlobalProvider {
27
28
	public function setValue($value, $record = null) {
29 View Code Duplication
		if($value === false || $value === null || (is_string($value) && !strlen($value))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
30
			// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
31
			// saved as NULL in database
32
			$this->value = null;
33
			return;
34
		}
35
36
		// Default to NZ date format - strtotime expects a US date
37 View Code Duplication
		if(preg_match('#^([0-9]+)/([0-9]+)/([0-9]+)$#', $value, $parts)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
38
			$value = "$parts[2]/$parts[1]/$parts[3]";
39
		}
40
41
		if(is_numeric($value)) {
42
			$this->value = date('Y-m-d H:i:s', $value);
43 View Code Duplication
		} elseif(is_string($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
44
			// $this->value = date('Y-m-d H:i:s', strtotime($value));
45
			try{
46
				$date = new DateTime($value);
47
				$this->value = $date->Format('Y-m-d H:i:s');
48
				return;
49
			}catch(Exception $e){
50
				$this->value = null;
51
				return;
52
			}
53
		}
54
	}
55
56
	/**
57
	 * Returns the date and time (in 12-hour format) using the format string 'd/m/Y g:ia' e.g. '31/01/2014 2:23pm'.
58
	 * @return string Formatted date and time.
59
	 */
60
	public function Nice() {
61
		if($this->value) return $this->Format('d/m/Y g:ia');
62
	}
63
64
	/**
65
	 * Returns the date and time (in 24-hour format) using the format string 'd/m/Y H:i' e.g. '28/02/2014 13:32'.
66
	 * @return string Formatted date and time.
67
	 */
68
	public function Nice24() {
69
		if($this->value) return $this->Format('d/m/Y H:i');
70
	}
71
72
	/**
73
	 * Returns the date using the format string 'd/m/Y' e.g. '28/02/2014'.
74
	 * @return string Formatted date.
75
	 */
76
	public function Date() {
77
		if($this->value) return $this->Format('d/m/Y');
78
	}
79
80
	/**
81
	 * Returns the time in 12-hour format using the format string 'g:ia' e.g. '1:32pm'.
82
	 * @return string Formatted time.
83
	 */
84
	public function Time() {
85
		if($this->value) return $this->Format('g:ia');
86
	}
87
88
	/**
89
	 * Returns the time in 24-hour format using the format string 'H:i' e.g. '13:32'.
90
	 * @return string Formatted time.
91
	 */
92
	public function Time24() {
93
		if($this->value) return $this->Format('H:i');
94
	}
95
96
	/**
97
	 * Return a date and time formatted as per a CMS user's settings.
98
	 *
99
	 * @param Member $member
100
	 * @return boolean | string A time and date pair formatted as per user-defined settings.
101
	 */
102
	public function FormatFromSettings($member = null) {
103
		require_once 'Zend/Date.php';
104
105
		if(!$member) {
106
			if(!Member::currentUserID()) {
107
				return false;
108
			}
109
			$member = Member::currentUser();
110
		}
111
112
		$formatD = $member->getDateFormat();
113
		$formatT = $member->getTimeFormat();
114
115
		$zendDate = new Zend_Date($this->getValue(), 'y-MM-dd HH:mm:ss');
116
		return $zendDate->toString($formatD).' '.$zendDate->toString($formatT);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $zendDate->toStri...te->toString($formatT); (string) is incompatible with the return type of the parent method Date::FormatFromSettings of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
117
	}
118
119
	public function requireField() {
120
		$parts=Array('datatype'=>'datetime', 'arrayValue'=>$this->arrayValue);
121
		$values=Array('type'=>'SS_Datetime', 'parts'=>$parts);
122
		DB::require_field($this->tableName, $this->name, $values);
0 ignored issues
show
Documentation introduced by
$values is of type array<string,string|arra...arrayValue\":\"?\"}>"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
123
	}
124
125
	/**
126
	 * Returns the url encoded date and time in ISO 6801 format using format
127
	 * string 'Y-m-d%20H:i:s' e.g. '2014-02-28%2013:32:22'.
128
	 *
129
	 * @return string Formatted date and time.
130
	 */
131
	public function URLDatetime() {
132
		if($this->value) return $this->Format('Y-m-d%20H:i:s');
133
	}
134
135
	public function scaffoldFormField($title = null, $params = null) {
136
		$field = DatetimeField::create($this->name, $title);
137
138
		// Show formatting hints for better usability
139
		$dateField = $field->getDateField();
140
		$dateField->setDescription(sprintf(
141
			_t('FormField.Example', 'e.g. %s', 'Example format'),
142
			Convert::raw2xml(Zend_Date::now()->toString($dateField->getConfig('dateformat')))
143
		));
144
		$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
145
		$timeField = $field->getTimeField();
146
		$timeField->setDescription(sprintf(
147
			_t('FormField.Example', 'e.g. %s', 'Example format'),
148
			Convert::raw2xml(Zend_Date::now()->toString($timeField->getConfig('timeformat')))
149
		));
150
		$timeField->setAttribute('placeholder', $timeField->getConfig('timeformat'));
151
152
		return $field;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $field; (DatetimeField) is incompatible with the return type of the parent method Date::scaffoldFormField of type DateField.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
153
	}
154
155
	/**
156
	 *
157
	 */
158
	protected static $mock_now = null;
159
160
	/**
161
	 * Returns either the current system date as determined
162
	 * by date(), or a mocked date through {@link set_mock_now()}.
163
	 *
164
	 * @return SS_Datetime
165
	 */
166
	public static function now() {
167
		if(self::$mock_now) {
168
			return self::$mock_now;
169
		} else {
170
			return DBField::create_field('SS_Datetime', date('Y-m-d H:i:s'));
171
		}
172
	}
173
174
	/**
175
	 * Mock the system date temporarily, which is useful for time-based unit testing.
176
	 * Use {@link clear_mock_now()} to revert to the current system date.
177
	 * Caution: This sets a fixed date that doesn't increment with time.
178
	 *
179
	 * @param SS_Datetime|string $datetime Either in object format, or as a SS_Datetime compatible string.
180
	 */
181
	public static function set_mock_now($datetime) {
182
		if($datetime instanceof SS_Datetime) {
183
			self::$mock_now = $datetime;
184
		} elseif(is_string($datetime)) {
185
			self::$mock_now = DBField::create_field('SS_Datetime', $datetime);
186
		} else {
187
			throw new Exception('SS_Datetime::set_mock_now(): Wrong format: ' . $datetime);
188
		}
189
	}
190
191
	/**
192
	 * Clear any mocked date, which causes
193
	 * {@link Now()} to return the current system date.
194
	 */
195
	public static function clear_mock_now() {
196
		self::$mock_now = null;
197
	}
198
199
	public static function get_template_global_variables() {
200
		return array(
201
			'Now' => array('method' => 'now', 'casting' => 'SS_Datetime'),
202
		);
203
	}
204
}
205
206