diff --git a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php --- a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php @@ -19,6 +19,7 @@ $username = $user->getUsername(); $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; + $pref_date = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; $preferences = $user->loadPreferences(); @@ -31,12 +32,16 @@ $errors[] = pht('The selected timezone is not a valid timezone.'); } - $preferences->setPreference( - $pref_time, - $request->getStr($pref_time)); - $preferences->setPreference( - $pref_week_start, - $request->getStr($pref_week_start)); + $preferences + ->setPreference( + $pref_time, + $request->getStr($pref_time)) + ->setPreference( + $pref_date, + $request->getStr($pref_date)) + ->setPreference( + $pref_week_start, + $request->getStr($pref_week_start)); if (!$errors) { $preferences->save(); @@ -71,6 +76,18 @@ ->setValue($preferences->getPreference($pref_time))) ->appendChild( id(new AphrontFormSelectControl()) + ->setLabel(pht('Date Format')) + ->setName($pref_date) + ->setOptions(array( + 'Y-m-d' => pht('ISO 8601 (2000-02-28)'), + 'n/j/Y' => pht('US (2/28/2000)'), + 'd-m-Y' => pht('European (28-02-2000)'), + )) + ->setCaption( + pht('Format used when rendering a date.')) + ->setValue($preferences->getPreference($pref_date))) + ->appendChild( + id(new AphrontFormSelectControl()) ->setLabel(pht('Week Starts On')) ->setOptions($this->getWeekDays()) ->setName($pref_week_start) diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -8,6 +8,7 @@ const PREFERENCE_MULTIEDIT = 'multiedit'; const PREFERENCE_TITLES = 'titles'; const PREFERENCE_MONOSPACED_TEXTAREAS = 'monospaced-textareas'; + const PREFERENCE_DATE_FORMAT = 'date-format'; const PREFERENCE_TIME_FORMAT = 'time-format'; const PREFERENCE_WEEK_START_DAY = 'week-start-day'; diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -127,7 +127,21 @@ } private function getDateInputValue() { - return $this->valueDate; + $date_format = $this->getDateFormat(); + $timezone = $this->getTimezone(); + + $datetime = new DateTime($this->valueDate, $timezone); + $date = $datetime->format($date_format); + + return $date; + } + + private function getDateFormat() { + $viewer = $this->getUser(); + $preferences = $viewer->loadPreferences(); + $pref_date_format = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; + + return $preferences->getPreference($pref_date_format, 'Y-m-d'); } private function getTimeInputValue() { @@ -242,7 +256,9 @@ ), $time_sel); - Javelin::initBehavior('fancy-datepicker', array()); + Javelin::initBehavior('fancy-datepicker', array( + 'format' => $this->getDateFormat(), + )); $classes = array(); $classes[] = 'aphront-form-date-container'; diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -10,6 +10,7 @@ private $zone; private $optional; + public function getValueDate() { return $this->valueDate; } @@ -65,7 +66,11 @@ $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - $value->valueDate = $month.'/'.$day.'/'.$year; + $value->valueDate = $value->getFormattedDateFromParts( + $year, + $month, + $day, + $value); $value->valueTime = coalesce($time, '12:00 AM'); $value->valueEnabled = $enabled; @@ -75,10 +80,13 @@ public static function newFromRequest(AphrontRequest $request, $key) { $value = new AphrontFormDateControlValue(); $value->viewer = $request->getViewer(); - $value->valueDate = $request->getStr($key.'_d'); + + $value->valueDate = $value->getFormattedDateFromDate( + $request->getStr($key.'_d'), + $value); + $value->valueTime = $request->getStr($key.'_t'); $value->valueEnabled = $request->getStr($key.'_e'); - return $value; } @@ -92,7 +100,11 @@ $month = $readable[1]; $day = $readable[2]; - $value->valueDate = $month.'/'.$day.'/'.$year; + $value->valueDate = $value->getFormattedDateFromParts( + $year, + $month, + $day, + $value); $value->valueTime = $readable[3]; @@ -105,7 +117,10 @@ $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - $value->valueDate = idx($dictionary, 'd'); + $value->valueDate = $value->getFormattedDateFromDate( + idx($dictionary, 'd'), + $value); + $value->valueTime = idx($dictionary, 't'); $value->valueEnabled = idx($dictionary, 'e'); @@ -185,6 +200,45 @@ return $value; } + private function getDateFormat() { + $preferences = $this->viewer->loadPreferences(); + $pref_date_format = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; + + return $preferences->getPreference($pref_date_format, 'Y-m-d'); + } + + private function getFormattedDateFromDate($date, $value) { + $original_input = $date; + $zone = $value->getTimezone(); + $separator = $value->getFormatSeparator(); + $parts = preg_split('@[,./:-]@', $date); + $date = implode($separator, $parts); + $date = id(new DateTime($date, $zone)); + + if ($date) { + return $date->format($value->getDateFormat()); + } else { + return $original_input; + } + } + + private function getFormattedDateFromParts($year, $month, $day, $value) { + $zone = $value->getTimezone(); + + return id(new DateTime("{$year}-{$month}-{$day}", $zone)) + ->format($value->getDateFormat()); + } + + private function getFormatSeparator() { + $format = $this->getDateFormat(); + switch ($format) { + case 'n/j/Y': + return '/'; + default: + return '-'; + } + } + public function getDateTime() { $epoch = $this->getEpoch(); $date = null; diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js --- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js +++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js @@ -7,7 +7,11 @@ * javelin-vector */ -JX.behavior('fancy-datepicker', function() { +JX.behavior('fancy-datepicker', function(config, statics) { + if (statics.initialized) { + return; + } + statics.initialized = true; var picker; var root; @@ -16,6 +20,32 @@ var value_m; var value_d; + var get_format_separator = function() { + var format = get_format(); + switch (format.toLowerCase()) { + case 'n/j/y': + return '/'; + default: + return '-'; + } + }; + + var get_key_maps = function() { + var format = get_format(); + var regex = new RegExp('[./ -]'); + return format.split(regex); + }; + + var get_format = function() { + var format = config.format; + + if (format === null) { + format = 'Y-m-d'; + } + + return format; + }; + var onopen = function(e) { e.kill(); @@ -85,18 +115,76 @@ }; }; - var read_date = function() { - var i = get_inputs(); - var date = i.d.value; - var parts = date.split('/'); - value_y = +parts[2]; - value_m = +parts[0]; - value_d = +parts[1]; + var read_date = function(){ + var inputs = get_inputs(); + var date = inputs.d.value; + var regex = new RegExp('[./ -]'); + var date_parts = date.split(regex); + var map = get_key_maps(); + + for (var i=0; i < date_parts.length; i++) { + var key = map[i].toLowerCase(); + + switch (key) { + case 'y': + value_y = date_parts[i]; + break; + case 'm': + value_m = date_parts[i]; + break; + case 'd': + value_d = date_parts[i]; + break; + } + } }; var write_date = function() { - var i = get_inputs(); - i.d.value = value_m + '/' + value_d + '/' + value_y; + var inputs = get_inputs(); + var map = get_key_maps(); + var arr_values = []; + + for(var i=0; i < map.length; i++) { + switch (map[i].toLowerCase()) { + case 'y': + arr_values[i] = value_y; + break; + case 'm': + arr_values[i] = value_m; + break; + case 'n': + arr_values[i] = value_m; + break; + case 'd': + arr_values[i] = value_d; + break; + case 'j': + arr_values[i] = value_d; + break; + } + } + + var text_value = ''; + var separator = get_format_separator(); + + for(var j=0; j < arr_values.length; j++) { + var element = arr_values[j]; + var format = get_format(); + + if ((format.toLowerCase() === 'd-m-y' || + format.toLowerCase() === 'y-m-d') && + element < 10) { + element = '0' + element; + } + + if (text_value.length === 0) { + text_value += element; + } else { + text_value = text_value + separator + element; + } + } + + inputs.d.value = text_value; }; var render = function() {