Changed DateParser to use only user format and ISO-8601
This commit is contained in:
@@ -26,10 +26,11 @@ Improvements:
|
|||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
|
* Fixed user date format parsing for dates that can be valid in multiple formats
|
||||||
* Do not sync user role if LDAP groups are not configured
|
* Do not sync user role if LDAP groups are not configured
|
||||||
* Fixed issue with unicode handling for letter based avatars and user initials
|
* Fixed issue with unicode handling for letter based avatars and user initials
|
||||||
* Do not send notifications to disabled users
|
* Do not send notifications to disabled users
|
||||||
* Fix wrong redirect when removing a task from the task view page
|
* Fixed wrong redirect when removing a task from the task view page
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
|
|||||||
@@ -91,11 +91,10 @@ class TaskModificationController extends BaseController
|
|||||||
$values = $task;
|
$values = $task;
|
||||||
$values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values));
|
$values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values));
|
||||||
$values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values));
|
$values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values));
|
||||||
|
$values = $this->dateParser->format($values, array('date_due'), $this->dateParser->getUserDateFormat());
|
||||||
|
$values = $this->dateParser->format($values, array('date_started'), $this->dateParser->getUserDateTimeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
$values = $this->dateParser->format($values, array('date_due'), $this->configModel->get('application_date_format', DateParser::DATE_FORMAT));
|
|
||||||
$values = $this->dateParser->format($values, array('date_started'), $this->configModel->get('application_datetime_format', DateParser::DATE_TIME_FORMAT));
|
|
||||||
|
|
||||||
$this->response->html($this->template->render('task_modification/edit_task', array(
|
$this->response->html($this->template->render('task_modification/edit_task', array(
|
||||||
'project' => $project,
|
'project' => $project,
|
||||||
'values' => $values,
|
'values' => $values,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class TaskViewController extends BaseController
|
|||||||
'time_spent' => $task['time_spent'] ?: '',
|
'time_spent' => $task['time_spent'] ?: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
$values = $this->dateParser->format($values, array('date_started'), $this->configModel->get('application_datetime_format', DateParser::DATE_TIME_FORMAT));
|
$values = $this->dateParser->format($values, array('date_started'), $this->dateParser->getUserDateTimeFormat());
|
||||||
|
|
||||||
$this->response->html($this->helper->layout->task('task/show', array(
|
$this->response->html($this->helper->layout->task('task/show', array(
|
||||||
'task' => $task,
|
'task' => $task,
|
||||||
|
|||||||
@@ -15,6 +15,28 @@ class DateParser extends Base
|
|||||||
const DATE_FORMAT = 'm/d/Y';
|
const DATE_FORMAT = 'm/d/Y';
|
||||||
const DATE_TIME_FORMAT = 'm/d/Y H:i';
|
const DATE_TIME_FORMAT = 'm/d/Y H:i';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get date format from settings
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUserDateFormat()
|
||||||
|
{
|
||||||
|
return $this->configModel->get('application_date_format', DateParser::DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get date time format from settings
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUserDateTimeFormat()
|
||||||
|
{
|
||||||
|
return $this->configModel->get('application_datetime_format', DateParser::DATE_TIME_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of time formats
|
* List of time formats
|
||||||
*
|
*
|
||||||
@@ -38,19 +60,29 @@ class DateParser extends Base
|
|||||||
*/
|
*/
|
||||||
public function getDateFormats($iso = false)
|
public function getDateFormats($iso = false)
|
||||||
{
|
{
|
||||||
$iso_formats = array(
|
$formats = array(
|
||||||
|
$this->getUserDateFormat(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$isoFormats = array(
|
||||||
'Y-m-d',
|
'Y-m-d',
|
||||||
'Y_m_d',
|
'Y_m_d',
|
||||||
);
|
);
|
||||||
|
|
||||||
$user_formats = array(
|
$userFormats = array(
|
||||||
'm/d/Y',
|
'm/d/Y',
|
||||||
'd/m/Y',
|
'd/m/Y',
|
||||||
'Y/m/d',
|
'Y/m/d',
|
||||||
'd.m.Y',
|
'd.m.Y',
|
||||||
);
|
);
|
||||||
|
|
||||||
return $iso ? array_merge($iso_formats, $user_formats) : $user_formats;
|
if ($iso) {
|
||||||
|
$formats = array_merge($formats, $isoFormats, $userFormats);
|
||||||
|
} else {
|
||||||
|
$formats = array_merge($formats, $userFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($formats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +94,9 @@ class DateParser extends Base
|
|||||||
*/
|
*/
|
||||||
public function getDateTimeFormats($iso = false)
|
public function getDateTimeFormats($iso = false)
|
||||||
{
|
{
|
||||||
$formats = array();
|
$formats = array(
|
||||||
|
$this->getUserDateTimeFormat(),
|
||||||
|
);
|
||||||
|
|
||||||
foreach ($this->getDateFormats($iso) as $date) {
|
foreach ($this->getDateFormats($iso) as $date) {
|
||||||
foreach ($this->getTimeFormats() as $time) {
|
foreach ($this->getTimeFormats() as $time) {
|
||||||
@@ -70,7 +104,7 @@ class DateParser extends Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $formats;
|
return array_unique($formats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,12 +131,30 @@ class DateParser extends Base
|
|||||||
$values = array();
|
$values = array();
|
||||||
|
|
||||||
foreach ($formats as $format) {
|
foreach ($formats as $format) {
|
||||||
$values[$format] = date($format);
|
$values[$format] = date($format).' ('.$format.')';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $values;
|
return $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get formats for date parsing
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getParserFormats()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
$this->getUserDateFormat(),
|
||||||
|
'Y-m-d',
|
||||||
|
'Y_m_d',
|
||||||
|
$this->getUserDateTimeFormat(),
|
||||||
|
'Y-m-d H:i',
|
||||||
|
'Y_m_d H:i',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a date and return a unix timestamp, try different date formats
|
* Parse a date and return a unix timestamp, try different date formats
|
||||||
*
|
*
|
||||||
@@ -116,7 +168,7 @@ class DateParser extends Base
|
|||||||
return (int) $value;
|
return (int) $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->getAllDateFormats(true) as $format) {
|
foreach ($this->getParserFormats() as $format) {
|
||||||
$timestamp = $this->getValidDate($value, $format);
|
$timestamp = $this->getValidDate($value, $format);
|
||||||
|
|
||||||
if ($timestamp !== 0) {
|
if ($timestamp !== 0) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class TransitionExport extends Base
|
|||||||
$transition['src_column'],
|
$transition['src_column'],
|
||||||
$transition['dst_column'],
|
$transition['dst_column'],
|
||||||
$transition['name'] ?: $transition['username'],
|
$transition['name'] ?: $transition['username'],
|
||||||
date($this->configModel->get('application_datetime_format', DateParser::DATE_TIME_FORMAT), $transition['date']),
|
date($this->dateParser->getUserDateTimeFormat(), $transition['date']),
|
||||||
round($transition['time_spent'] / 3600, 2)
|
round($transition['time_spent'] / 3600, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ class TaskValidator extends BaseValidator
|
|||||||
new Validators\Integer('priority', t('This value must be an integer')),
|
new Validators\Integer('priority', t('This value must be an integer')),
|
||||||
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
|
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
|
||||||
new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50),
|
new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50),
|
||||||
new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats(true)),
|
new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getParserFormats()),
|
||||||
new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getDateTimeFormats(true)),
|
new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getParserFormats()),
|
||||||
new Validators\Numeric('time_spent', t('This value must be numeric')),
|
new Validators\Numeric('time_spent', t('This value must be numeric')),
|
||||||
new Validators\Numeric('time_estimated', t('This value must be numeric')),
|
new Validators\Numeric('time_estimated', t('This value must be numeric')),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,26 @@ class DateParserTest extends Base
|
|||||||
$this->assertContains('Y-m-d', $dateParser->getDateFormats(true));
|
$this->assertContains('Y-m-d', $dateParser->getDateFormats(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testThatUserDateFormatIsReturnedFirst()
|
||||||
|
{
|
||||||
|
$dateParser = new DateParser($this->container);
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateFormats();
|
||||||
|
$this->assertEquals('m/d/Y', $dates[0]);
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateFormats(true);
|
||||||
|
$this->assertEquals('m/d/Y', $dates[0]);
|
||||||
|
|
||||||
|
$this->container['configModel']->save(array('application_date_format' => 'd/m/Y'));
|
||||||
|
$this->container['memoryCache']->flush();
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateFormats();
|
||||||
|
$this->assertEquals('d/m/Y', $dates[0]);
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateFormats(true);
|
||||||
|
$this->assertEquals('d/m/Y', $dates[0]);
|
||||||
|
}
|
||||||
|
|
||||||
public function testGetDateTimeFormats()
|
public function testGetDateTimeFormats()
|
||||||
{
|
{
|
||||||
$dateParser = new DateParser($this->container);
|
$dateParser = new DateParser($this->container);
|
||||||
@@ -34,6 +54,26 @@ class DateParserTest extends Base
|
|||||||
$this->assertContains('Y-m-d g:i a', $dateParser->getDateTimeFormats(true));
|
$this->assertContains('Y-m-d g:i a', $dateParser->getDateTimeFormats(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testThatUserDateTimeFormatIsReturnedFirst()
|
||||||
|
{
|
||||||
|
$dateParser = new DateParser($this->container);
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateTimeFormats();
|
||||||
|
$this->assertEquals('m/d/Y H:i', $dates[0]);
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateTimeFormats(true);
|
||||||
|
$this->assertEquals('m/d/Y H:i', $dates[0]);
|
||||||
|
|
||||||
|
$this->container['configModel']->save(array('application_datetime_format' => 'd/m/Y g:i a'));
|
||||||
|
$this->container['memoryCache']->flush();
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateTimeFormats();
|
||||||
|
$this->assertEquals('d/m/Y g:i a', $dates[0]);
|
||||||
|
|
||||||
|
$dates = $dateParser->getDateTimeFormats(true);
|
||||||
|
$this->assertEquals('d/m/Y g:i a', $dates[0]);
|
||||||
|
}
|
||||||
|
|
||||||
public function testGetAllDateFormats()
|
public function testGetAllDateFormats()
|
||||||
{
|
{
|
||||||
$dateParser = new DateParser($this->container);
|
$dateParser = new DateParser($this->container);
|
||||||
@@ -52,33 +92,63 @@ class DateParserTest extends Base
|
|||||||
|
|
||||||
$formats = $dateParser->getAvailableFormats($dateParser->getDateFormats());
|
$formats = $dateParser->getAvailableFormats($dateParser->getDateFormats());
|
||||||
$this->assertArrayHasKey('d/m/Y', $formats);
|
$this->assertArrayHasKey('d/m/Y', $formats);
|
||||||
$this->assertContains(date('d/m/Y'), $formats);
|
$this->assertContains(date('d/m/Y').' (d/m/Y)', $formats);
|
||||||
|
|
||||||
$formats = $dateParser->getAvailableFormats($dateParser->getDateTimeFormats());
|
$formats = $dateParser->getAvailableFormats($dateParser->getDateTimeFormats());
|
||||||
$this->assertArrayHasKey('d/m/Y H:i', $formats);
|
$this->assertArrayHasKey('d/m/Y H:i', $formats);
|
||||||
$this->assertContains(date('d/m/Y H:i'), $formats);
|
$this->assertContains(date('d/m/Y H:i').' (d/m/Y H:i)', $formats);
|
||||||
|
|
||||||
$formats = $dateParser->getAvailableFormats($dateParser->getAllDateFormats());
|
$formats = $dateParser->getAvailableFormats($dateParser->getAllDateFormats());
|
||||||
$this->assertArrayHasKey('d/m/Y', $formats);
|
$this->assertArrayHasKey('d/m/Y', $formats);
|
||||||
$this->assertContains(date('d/m/Y'), $formats);
|
$this->assertContains(date('d/m/Y').' (d/m/Y)', $formats);
|
||||||
$this->assertArrayHasKey('d/m/Y H:i', $formats);
|
$this->assertArrayHasKey('d/m/Y H:i', $formats);
|
||||||
$this->assertContains(date('d/m/Y H:i'), $formats);
|
$this->assertContains(date('d/m/Y H:i').' (d/m/Y H:i)', $formats);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetTimestamp()
|
public function testGetTimestampFromDefaultFormats()
|
||||||
{
|
{
|
||||||
$dateParser = new DateParser($this->container);
|
$dateParser = new DateParser($this->container);
|
||||||
|
|
||||||
$this->assertEquals(1393995600, $dateParser->getTimestamp(1393995600));
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('06/09/2016')));
|
||||||
$this->assertEquals('2014-03-05', date('Y-m-d', $dateParser->getTimestamp('2014-03-05')));
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('2016-06-09')));
|
||||||
$this->assertEquals('2014-03-05', date('Y-m-d', $dateParser->getTimestamp('2014_03_05')));
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('2016_06_09')));
|
||||||
$this->assertEquals('2014-03-05', date('Y-m-d', $dateParser->getTimestamp('03/05/2014')));
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('2016-06-09 21:15')));
|
||||||
$this->assertEquals('2014-03-25 17:18', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 5:18 pm')));
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('2016_06_09 21:15')));
|
||||||
$this->assertEquals('2014-03-25 05:18', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 5:18 am')));
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('06/09/2016 21:15')));
|
||||||
$this->assertEquals('2014-03-25 17:18', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 5:18pm')));
|
}
|
||||||
$this->assertEquals('2014-03-25 23:14', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 23:14')));
|
|
||||||
$this->assertEquals('2014-03-29 23:14', date('Y-m-d H:i', $dateParser->getTimestamp('2014_03_29 23:14')));
|
public function testGetTimestampFromUserDateFormats()
|
||||||
$this->assertEquals('2014-03-29 23:14', date('Y-m-d H:i', $dateParser->getTimestamp('2014-03-29 23:14')));
|
{
|
||||||
|
$this->container['configModel']->save(array(
|
||||||
|
'application_date_format' => 'd/m/Y',
|
||||||
|
'application_datetime_format' => 'd/m/Y g:i a',
|
||||||
|
));
|
||||||
|
|
||||||
|
$dateParser = new DateParser($this->container);
|
||||||
|
|
||||||
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('09/06/2016')));
|
||||||
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('2016-06-09')));
|
||||||
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('2016_06_09')));
|
||||||
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('2016-06-09 21:15')));
|
||||||
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('2016_06_09 21:15')));
|
||||||
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('09/06/2016 9:15 pm')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTimestampFromAnotherUserDateFormats()
|
||||||
|
{
|
||||||
|
$this->container['configModel']->save(array(
|
||||||
|
'application_date_format' => 'd.m.Y',
|
||||||
|
'application_datetime_format' => 'd.m.Y H:i',
|
||||||
|
));
|
||||||
|
|
||||||
|
$dateParser = new DateParser($this->container);
|
||||||
|
|
||||||
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('09.06.2016')));
|
||||||
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('2016-06-09')));
|
||||||
|
$this->assertEquals('2016-06-09', date('Y-m-d', $dateParser->getTimestamp('2016_06_09')));
|
||||||
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('2016-06-09 21:15')));
|
||||||
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('2016_06_09 21:15')));
|
||||||
|
$this->assertEquals('2016-06-09 21:15', date('Y-m-d H:i', $dateParser->getTimestamp('09.06.2016 21:15')));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDateRange()
|
public function testDateRange()
|
||||||
@@ -117,9 +187,6 @@ class DateParserTest extends Base
|
|||||||
$this->assertEquals('2014-03-05', $dateParser->getIsoDate('2014-03-05'));
|
$this->assertEquals('2014-03-05', $dateParser->getIsoDate('2014-03-05'));
|
||||||
$this->assertEquals('2014-03-05', $dateParser->getIsoDate('2014_03_05'));
|
$this->assertEquals('2014-03-05', $dateParser->getIsoDate('2014_03_05'));
|
||||||
$this->assertEquals('2014-03-05', $dateParser->getIsoDate('03/05/2014'));
|
$this->assertEquals('2014-03-05', $dateParser->getIsoDate('03/05/2014'));
|
||||||
$this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 5:18 pm'));
|
|
||||||
$this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 5:18 am'));
|
|
||||||
$this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 5:18pm'));
|
|
||||||
$this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 23:14'));
|
$this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 23:14'));
|
||||||
$this->assertEquals('2014-03-29', $dateParser->getIsoDate('2014_03_29 23:14'));
|
$this->assertEquals('2014-03-29', $dateParser->getIsoDate('2014_03_29 23:14'));
|
||||||
$this->assertEquals('2014-03-29', $dateParser->getIsoDate('2014-03-29 23:14'));
|
$this->assertEquals('2014-03-29', $dateParser->getIsoDate('2014-03-29 23:14'));
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ class TaskCreationTest extends Base
|
|||||||
$this->assertEquals('2014-11-24 16:25', date('Y-m-d H:i', $task['date_started']));
|
$this->assertEquals('2014-11-24 16:25', date('Y-m-d H:i', $task['date_started']));
|
||||||
|
|
||||||
// Set a datetime
|
// Set a datetime
|
||||||
$this->assertEquals(3, $tc->create(array('project_id' => 1, 'title' => 'test', 'date_started' => '2014-11-24 6:25pm')));
|
$this->assertEquals(3, $tc->create(array('project_id' => 1, 'title' => 'test', 'date_started' => '11/24/2014 18:25')));
|
||||||
|
|
||||||
$task = $tf->getById(3);
|
$task = $tf->getById(3);
|
||||||
$this->assertEquals('2014-11-24 18:25', date('Y-m-d H:i', $task['date_started']));
|
$this->assertEquals('2014-11-24 18:25', date('Y-m-d H:i', $task['date_started']));
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class TaskModificationTest extends Base
|
|||||||
$this->assertEquals('2014-11-24 16:25', date('Y-m-d H:i', $task['date_started']));
|
$this->assertEquals('2014-11-24 16:25', date('Y-m-d H:i', $task['date_started']));
|
||||||
|
|
||||||
// Set a datetime
|
// Set a datetime
|
||||||
$this->assertTrue($tm->update(array('id' => 1, 'date_started' => '2014-11-24 6:25pm')));
|
$this->assertTrue($tm->update(array('id' => 1, 'date_started' => '11/24/2014 18:25')));
|
||||||
|
|
||||||
$task = $tf->getById(1);
|
$task = $tf->getById(1);
|
||||||
$this->assertEquals('2014-11-24 18:25', date('Y-m-d H:i', $task['date_started']));
|
$this->assertEquals('2014-11-24 18:25', date('Y-m-d H:i', $task['date_started']));
|
||||||
|
|||||||
Reference in New Issue
Block a user