From 8fdc531345d96127f66cc5064dbb209e01cbdd87 Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Fri, 24 Feb 2012 16:18:36 +1300 Subject: [PATCH 1/4] BUGFIX Ensure Date and Datetime field types actually set NULL, false, empty string values correctly instead of "1970-01-01" which gets saved to the database instead of NULL. BUGFIX Datetime::Nice() and casting methods return NULL when there is no value, to be consistent with Date::Nice() and so on --- model/fieldtypes/Date.php | 13 +++++++++++-- model/fieldtypes/Datetime.php | 22 ++++++++++++++++------ tests/model/DateTest.php | 22 +++++++++++++++++++++- tests/model/DatetimeTest.php | 20 +++++++++++++++++++- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index 736179911..3defd4579 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -21,17 +21,26 @@ class Date extends DBField { function setValue($value) { + if($value === false || $value === null || (is_string($value) && !strlen($value))) { + // don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be saved as NULL in database + $this->value = null; + return; + } + // @todo This needs tidy up (what if you only specify a month and a year, for example?) if(is_array($value)) { if(!empty($value['Day']) && !empty($value['Month']) && !empty($value['Year'])) { $this->value = $value['Year'] . '-' . $value['Month'] . '-' . $value['Day']; return; + } else { + // return nothing (so ereg() doesn't fail on an empty array below) + return null; } } // Default to NZ date format - strtotime expects a US date if(ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $value, $parts)) { - $value = "$parts[2]/$parts[1]/$parts[3]"; + $value = "$parts[2]/$parts[1]/$parts[3]"; } if(is_numeric($value)) { @@ -357,4 +366,4 @@ class Date extends DBField { public function scaffoldFormField($title = null, $params = null) { return new DateField($this->name, $title); } -} \ No newline at end of file +} diff --git a/model/fieldtypes/Datetime.php b/model/fieldtypes/Datetime.php index 77a56688a..83cb184e2 100644 --- a/model/fieldtypes/Datetime.php +++ b/model/fieldtypes/Datetime.php @@ -26,6 +26,12 @@ class SS_Datetime extends Date { function setValue($value) { + if($value === false || $value === null || (is_string($value) && !strlen($value))) { + // don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be saved as NULL in database + $this->value = null; + return; + } + // Default to NZ date format - strtotime expects a US date if(ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $value, $parts)) { $value = "$parts[2]/$parts[1]/$parts[3]"; @@ -42,19 +48,23 @@ class SS_Datetime extends Date { * Returns the date in the raw SQL-format, e.g. “2006-01-18 16:32:04” */ function Nice() { - return date('d/m/Y g:ia', strtotime($this->value)); + if($this->value) return date('d/m/Y g:ia', strtotime($this->value)); } + function Nice24() { - return date('d/m/Y H:i', strtotime($this->value)); + if($this->value) return date('d/m/Y H:i', strtotime($this->value)); } + function Date() { - return date('d/m/Y', strtotime($this->value)); + if($this->value) return date('d/m/Y', strtotime($this->value)); } + function Time() { - return date('g:ia', strtotime($this->value)); + if($this->value) return date('g:ia', strtotime($this->value)); } + function Time24() { - return date('H:i', strtotime($this->value)); + if($this->value) return date('H:i', strtotime($this->value)); } function requireField() { @@ -64,7 +74,7 @@ class SS_Datetime extends Date { } function URLDatetime() { - return date('Y-m-d%20H:i:s', strtotime($this->value)); + if($this->value) return date('Y-m-d%20H:i:s', strtotime($this->value)); } public function scaffoldFormField($title = null, $params = null) { diff --git a/tests/model/DateTest.php b/tests/model/DateTest.php index 44b1b498d..f14f09dfc 100644 --- a/tests/model/DateTest.php +++ b/tests/model/DateTest.php @@ -93,5 +93,25 @@ class DateTest extends SapphireTest { "Date->Long() works with D/M/YYYY" ); } - + + function testSetNullAndZeroValues() { + $date = DBField::create('Date', ''); + $this->assertNull($date->getValue(), 'Empty string evaluates to NULL'); + + $date = DBField::create('Date', null); + $this->assertNull($date->getValue(), 'NULL is set as NULL'); + + $date = DBField::create('Date', false); + $this->assertNull($date->getValue(), 'Boolean FALSE evaluates to NULL'); + + $date = DBField::create('Date', array()); + $this->assertNull($date->getValue(), 'Empty array evaluates to NULL'); + + $date = DBField::create('Date', '0'); + $this->assertEquals('1970-01-01', $date->getValue(), 'Zero is UNIX epoch date'); + + $date = DBField::create('Date', 0); + $this->assertEquals('1970-01-01', $date->getValue(), 'Zero is UNIX epoch date'); + } + } diff --git a/tests/model/DatetimeTest.php b/tests/model/DatetimeTest.php index b18c6fb01..f3636ef6a 100644 --- a/tests/model/DatetimeTest.php +++ b/tests/model/DatetimeTest.php @@ -33,4 +33,22 @@ class SS_DatetimeTest extends SapphireTest { $nowDatetime = SS_Datetime::now(); $this->assertEquals($systemDatetime->Date(), $nowDatetime->Date()); } -} \ No newline at end of file + + function testSetNullAndZeroValues() { + $date = DBField::create('SS_Datetime', ''); + $this->assertNull($date->getValue(), 'Empty string evaluates to NULL'); + + $date = DBField::create('SS_Datetime', null); + $this->assertNull($date->getValue(), 'NULL is set as NULL'); + + $date = DBField::create('SS_Datetime', false); + $this->assertNull($date->getValue(), 'Boolean FALSE evaluates to NULL'); + + $date = DBField::create('SS_Datetime', '0'); + $this->assertEquals('1970-01-01 12:00:00', $date->getValue(), 'Zero is UNIX epoch time'); + + $date = DBField::create('SS_Datetime', 0); + $this->assertEquals('1970-01-01 12:00:00', $date->getValue(), 'Zero is UNIX epoch time'); + } + +} From 58d48583a9953d0aa3eee8757927d5a884cdfff9 Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Fri, 24 Feb 2012 16:49:41 +1300 Subject: [PATCH 2/4] ENHANCEMENT Date::DayOfMonth() now supports ordinal argument, so you can get somehing like "10th" or "2nd". Also supported in Date::RangeString --- model/fieldtypes/Date.php | 12 +++++++----- tests/model/DateTest.php | 10 ++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index 3defd4579..f8b9ec279 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -95,8 +95,10 @@ class Date extends DBField { /** * Returns the date of the month */ - function DayOfMonth() { - if($this->value) return date('j', strtotime($this->value)); + function DayOfMonth($includeOrdinal = false) { + $format = 'j'; + if ($includeOrdinal) $format .= 'S'; + if($this->value) return date($format, strtotime($this->value)); } @@ -140,9 +142,9 @@ class Date extends DBField { /* * Return a string in the form "12 - 16 Sept" or "12 Aug - 16 Sept" */ - function RangeString($otherDateObj) { - $d1 = $this->DayOfMonth(); - $d2 = $otherDateObj->DayOfMonth(); + function RangeString($otherDateObj, $includeOrdinals = false) { + $d1 = $this->DayOfMonth($includeOrdinals); + $d2 = $otherDateObj->DayOfMonth($includeOrdinals); $m1 = $this->ShortMonth(); $m2 = $otherDateObj->ShortMonth(); $y1 = $this->Year(); diff --git a/tests/model/DateTest.php b/tests/model/DateTest.php index f14f09dfc..f09da2c86 100644 --- a/tests/model/DateTest.php +++ b/tests/model/DateTest.php @@ -114,4 +114,14 @@ class DateTest extends SapphireTest { $this->assertEquals('1970-01-01', $date->getValue(), 'Zero is UNIX epoch date'); } + function testDayOfMonth() { + $date = DBField::create('Date', '2000-10-10'); + $this->assertEquals('10', $date->DayOfMonth()); + $this->assertEquals('10th', $date->DayOfMonth(true)); + + $range = $date->RangeString(DBField::create('Date', '2000-10-20')); + $this->assertEquals('10 - 20 Oct 2000', $range); + $range = $date->RangeString(DBField::create('Date', '2000-10-20'), true); + $this->assertEquals('10th - 20th Oct 2000', $range); + } } From f4a758e500f3244e8b0c2d2c51f5e3124bc4a73c Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Fri, 24 Feb 2012 16:13:43 +1300 Subject: [PATCH 3/4] BUGFIX DatetimeField allows non-US style dates with an empty time component to be saved correctly --- forms/DatetimeField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/DatetimeField.php b/forms/DatetimeField.php index f44108cd7..6d4342b0c 100644 --- a/forms/DatetimeField.php +++ b/forms/DatetimeField.php @@ -148,7 +148,7 @@ class DatetimeField extends FormField { unset($userValueObj); } else { // Validation happens later, so set the raw string in case Zend_Date doesn't accept it - $this->value = sprintf($this->getConfig('datetimeorder'), $val['date'], $val['time']); + $this->value = trim(sprintf($this->getConfig('datetimeorder'), $val['date'], $val['time'])); } if($userTz) date_default_timezone_set($dataTz); From 8109a5e54d5be92e6c1b4fa776841a414fee3e05 Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Thu, 1 Mar 2012 09:56:05 +1300 Subject: [PATCH 4/4] MINOR phpDoc to methods in Date, small tweaks to DayOfMonth method to be consistent with value checks --- model/fieldtypes/Date.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index f8b9ec279..bd7048f87 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -33,7 +33,7 @@ class Date extends DBField { $this->value = $value['Year'] . '-' . $value['Month'] . '-' . $value['Day']; return; } else { - // return nothing (so ereg() doesn't fail on an empty array below) + // return nothing (so checks below don't fail on an empty array) return null; } } @@ -93,15 +93,18 @@ class Date extends DBField { } /** - * Returns the date of the month + * Returns the day of the month. + * @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd" + * @return string */ function DayOfMonth($includeOrdinal = false) { - $format = 'j'; - if ($includeOrdinal) $format .= 'S'; - if($this->value) return date($format, strtotime($this->value)); + if($this->value) { + $format = 'j'; + if ($includeOrdinal) $format .= 'S'; + return date($format, strtotime($this->value)); + } } - /** * Returns the date in the format 24 December 2006 */ @@ -132,7 +135,7 @@ class Date extends DBField { * strftime obeys the current LC_TIME/LC_ALL when printing lexical values * like day- and month-names */ - function FormatI18N($formattingString) { + function FormatI18N($formattingString) { if($this->value) { $fecfrm = strftime($formattingString, strtotime($this->value)); return utf8_encode($fecfrm); @@ -141,6 +144,9 @@ class Date extends DBField { /* * Return a string in the form "12 - 16 Sept" or "12 Aug - 16 Sept" + * @param Date $otherDateObj Another date object specifying the end of the range + * @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd" + * @return string */ function RangeString($otherDateObj, $includeOrdinals = false) { $d1 = $this->DayOfMonth($includeOrdinals);