diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index ad6f6f85c..2572b9a2b 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -630,7 +630,6 @@ EOF { if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)) { - // Check if the attribute is not read-only because of a synchro... $aReasons = array(); $sSynchroIcon = ''; @@ -1312,8 +1311,8 @@ EOF else { $iDate = AttributeDateTime::GetAsUnixSeconds($sDate); - $aRow[] = ''.date('Y-m-d', $iDate).''; - $aRow[] = ''.date('H:i:s', $iDate).''; + $aRow[] = ''.date('Y-m-d', $iDate).''; // Format kept as-is for 100% backward compatibility of the exports + $aRow[] = ''.date('H:i:s', $iDate).''; // Format kept as-is for 100% backward compatibility of the exports } } else if($oAttDef instanceof AttributeCaseLog) @@ -1711,10 +1710,7 @@ EOF $aEventsList[] ='validate'; $aEventsList[] ='keyup'; $aEventsList[] ='change'; - if (($iFlags & OPT_ATT_MANDATORY) && (empty($sDisplayValue))) - { - $sDisplayValue = date($oAttDef->GetDateFormat()); - } + $sHTMLValue = " {$sValidationSpan}{$sReloadSpan}"; break; @@ -1722,11 +1718,8 @@ EOF $aEventsList[] ='validate'; $aEventsList[] ='keyup'; $aEventsList[] ='change'; - if (($iFlags & OPT_ATT_MANDATORY) && (empty($sDisplayValue))) - { - $sDisplayValue = date($oAttDef->GetDateFormat()); - } - $sHTMLValue = " {$sValidationSpan}{$sReloadSpan}"; + + $sHTMLValue = " {$sValidationSpan}{$sReloadSpan}"; break; case 'Duration': @@ -3161,6 +3154,14 @@ EOF 'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'raw_data'), true), 'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'raw_data'), true) ); } + else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime + { + $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); + if ($value != null) + { + $value = AttributeDateTime::Parse($value, $oAttDef->GetFormat()); + } + } else { $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index c9d23d4e0..d045a620d 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -63,6 +63,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_header("Cache-control: no-cache"); $this->add_linked_stylesheet("../css/jquery.treeview.css"); $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); + $this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css"); $this->add_linked_stylesheet("../css/fg.menu.css"); $this->add_linked_stylesheet("../css/jquery.multiselect.css"); $this->add_linked_stylesheet("../css/magnific-popup.css"); @@ -73,6 +74,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_linked_script("../js/jquery.treeview.js"); $this->add_linked_script("../js/jquery.autocomplete.js"); $this->add_linked_script("../js/date.js"); + $this->add_linked_script("../js/jquery-ui-timepicker-addon.js"); + $this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js"); $this->add_linked_script("../js/jquery.blockUI.js"); $this->add_linked_script("../js/utils.js"); $this->add_linked_script("../js/swfobject.js"); @@ -83,8 +86,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_linked_script('../js/fg.menu.js'); $this->add_linked_script('../js/icon_select.js'); $this->add_linked_script('../js/raphael-min.js'); - $this->add_linked_script('../js/d3.min.js'); - $this->add_linked_script('../js/c3.min.js'); + $this->add_linked_script('../js/d3.js'); + $this->add_linked_script('../js/c3.js'); $this->add_linked_script('../js/jquery.multiselect.js'); $this->add_linked_script('../js/ajaxfileupload.js'); $this->add_linked_script('../js/jquery.mousewheel.js'); @@ -152,6 +155,11 @@ EOF; $sJSMonthsShort = json_encode(array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'), Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'))); $iFirstDayOfWeek = (int) Dict::S('Calendar-FirstDayOfWeek'); + $sDateFormat = AttributeDate::GetDatePickerFormat(); + $sJSDateFormat = json_encode($sDateFormat); + $sJSTimeFormat = json_encode(trim(str_replace($sDateFormat, '', AttributeDateTime::GetDatePickerFormat()))); + $sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2))); + $sJSOk = json_encode(Dict::S('UI:Button:Ok')); $this->m_sInitScript = <<< EOF @@ -393,7 +401,7 @@ EOF showOn: 'button', buttonImage: '../images/calendar.png', buttonImageOnly: true, - dateFormat: 'yy-mm-dd', + dateFormat: $sJSDateFormat, constrainInput: false, changeMonth: true, changeYear: true, @@ -401,18 +409,28 @@ EOF monthNamesShort: $sJSMonthsShort, firstDay: $iFirstDayOfWeek }); - $(".datetime-pick").datepicker({ + $(".datetime-pick").datetimepicker({ showOn: 'button', buttonImage: '../images/calendar.png', buttonImageOnly: true, - dateFormat: 'yy-mm-dd 00:00:00', + dateFormat: $sJSDateFormat, constrainInput: false, changeMonth: true, changeYear: true, dayNamesMin: $sJSDaysMin, monthNamesShort: $sJSMonthsShort, - firstDay: $iFirstDayOfWeek - }); + firstDay: $iFirstDayOfWeek, + // time picker options + timeFormat: $sJSTimeFormat, + controlType: 'select', + timeText: $.timepicker.regional[$sJSLangShort].timeText, + hourText: $.timepicker.regional[$sJSLangShort].hourText, + minuteText: $.timepicker.regional[$sJSLangShort].minuteText, + secondText: $.timepicker.regional[$sJSLangShort].secondText, + currentText: $.timepicker.regional[$sJSLangShort].currentText, + closeText: $sJSOk + }); + // Make sortable, everything that claims to be sortable $('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function() @@ -454,6 +472,11 @@ EOF EOF ); $this->add_ready_script(InlineImage::FixImagesWidth()); + /* + * Not used since the sorting of the tables is always performed server-side + AttributeDateTime::InitTableSorter($this, 'custom_date_time'); + AttributeDate::InitTableSorter($this, 'custom_date'); + */ $sUserPrefs = appUserPreferences::GetAsJSON(); $this->add_script( diff --git a/application/utils.inc.php b/application/utils.inc.php index b81dcdb5a..0c387c129 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -482,6 +482,19 @@ class utils } // http://www.spaweditor.com/scripts/regex/index.php } + + /** + * Convert an old date/time format specifciation (using % placeholders) + * to a format compatible with DateTime::createFromFormat + * @param string $sOldDateTimeFormat + * @return string + */ + static public function DateTimeFormatToPHP($sOldDateTimeFormat) + { + $aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s'); + $aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's'); + return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat); + } static public function GetConfig() { diff --git a/application/xlsxwriter.class.php b/application/xlsxwriter.class.php index bfb1c7e9b..0538b5cad 100644 --- a/application/xlsxwriter.class.php +++ b/application/xlsxwriter.class.php @@ -13,6 +13,8 @@ Class XLSXWriter protected $shared_strings = array();//unique set protected $shared_string_count = 0;//count of non-unique references to the unique set protected $temp_files = array(); + protected $date_format = 'YYYY-MM-DD'; + protected $date_time_format = 'YYYY-MM-DD\ HH:MM:SS'; public function __construct(){} public function setAuthor($author='') { $this->author=$author; } @@ -26,6 +28,16 @@ Class XLSXWriter } } + public function setDateFormat($date_format) + { + $this->date_format = $date_format; + } + + public function setDateTimeFormat($date_time_format) + { + $this->date_time_format = $date_time_format; + } + protected function tempFilename() { $filename = tempnam("/tmp", "xlsx_writer_"); @@ -183,8 +195,8 @@ Class XLSXWriter fwrite($fd, ''); fwrite($fd, ''); fwrite($fd, ''); - fwrite($fd, ''); - fwrite($fd, ''); + fwrite($fd, ''); + fwrite($fd, ''); fwrite($fd, ''); fwrite($fd, ''); fwrite($fd, ''); diff --git a/core/asynctask.class.inc.php b/core/asynctask.class.inc.php index 25f54c76e..4e392cf79 100644 --- a/core/asynctask.class.inc.php +++ b/core/asynctask.class.inc.php @@ -34,7 +34,7 @@ class ExecAsyncTask implements iBackgroundProcess public function Process($iTimeLimit) { - $sNow = date('Y-m-d H:i:s'); + $sNow = date(AttributeDateTime::GetSQLFormat()); // Criteria: planned, and expected to occur... ASAP or in the past $sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))"; $iProcessed = 0; diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index b7114693e..b75817ad7 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -3537,9 +3537,353 @@ class AttributeMetaEnum extends AttributeEnum */ class AttributeDateTime extends AttributeDBField { - static public function GetDateFormat() + static $sDateTimeFormat = null; + static $sTimeFormat = null; + + static public function GetFormat() { - return "Y-m-d H:i:s"; + if (self::$sDateTimeFormat == null) + { + static::LoadFormatFromConfig(); + } + return self::$sDateTimeFormat; + } + + /** + * Load the 3 settings: date format, time format and data_time format from the configuration + */ + protected static function LoadFormatFromConfig() + { + $aFormats = MetaModel::GetConfig()->Get('date_and_time_format'); + $sLang = Dict::GetUserLanguage(); + $sDateFormat = isset($aFormats[$sLang]['date']) ? $aFormats[$sLang]['date'] : (isset($aFormats['default']['date']) ? $aFormats['default']['date'] : 'Y-m-d'); + $sTimeFormat = isset($aFormats[$sLang]['time']) ? $aFormats[$sLang]['time'] : (isset($aFormats['default']['time']) ? $aFormats['default']['time'] : 'H:i:s'); + $sDateAndTimeFormat = isset($aFormats[$sLang]['date_time']) ? $aFormats[$sLang]['date_time'] : (isset($aFormats['default']['date_time']) ? $aFormats['default']['date_time'] : '$date $time'); + + $sFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat); + + self::SetFormat($sFormat); + self::SetTimeFormat($sTimeFormat); + AttributeDate::SetFormat($sDateFormat); + } + + /** + * Returns the format string used for the date & time stored in memory + * @return string + */ + static public function GetInternalFormat() + { + return 'Y-m-d H:i:s'; + } + + /** + * Returns the format string used for the date & time written to MySQL + * @return string + */ + static public function GetSQLFormat() + { + return 'Y-m-d H:i:s'; + } + + static public function SetFormat($sDateTimeFormat) + { + self::$sDateTimeFormat = $sDateTimeFormat; + } + + static public function GetTimeFormat() + { + if (self::$sTimeFormat == null) + { + static::LoadFormatFromConfig(); + } + return self::$sTimeFormat; + } + + static public function GetSQLTimeFormat() + { + return 'H:i:s'; + } + + static public function SetTimeFormat($sTimeFormat) + { + self::$sTimeFormat = $sTimeFormat; + } + + /** + * Return the mapping table for converting between various convention for data formats + */ + public static function GetFormatMapping() + { + return array( + // Days + 'd' => array('regexpr' => '(0[1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'dd', 'usage' => 'day', 'excel' => 'dd'), // Day of the month: 2 digits (with leading zero) + 'j' => array('regexpr' => '([1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'd', 'usage' => 'day', 'excel' => '%d'), // Day of the month: 1 or 2 digits (without leading zero) + // Months + 'm' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'mm', 'usage' => 'month', 'excel' => 'MM'), // Month on 2 digits i.e. 01-12 + 'n' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'm', 'usage' => 'month', 'excel' => '%M'), // Month on 1 or 2 digits 1-12 + // Years + 'Y' => array('regexpr' => '([0-9]{4})', 'datepicker' => 'yy', 'usage' => 'year', 'excel' => 'YYYY'), // Year on 4 digits + 'y' => array('regexpr' => '([0-9]{2})', 'datepicker' => 'y', 'usage' => 'year', 'excel' => 'YY'), // Year on 2 digits + // Hours + 'H' => array('regexpr' => '([0-1][0-9]|2[0-3])', 'datepicker' => 'HH', 'usage' => 'hour', 'excel' => 'HH'), // Hour 00..23 + 'h' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'hh', 'usage' => 'hour', 'excel' => 'hh'), // Hour 01..12 + 'G' => array('regexpr' => '([1-9]|[1[0-9]|2[0-3])', 'datepicker' => 'H', 'usage' => 'hour', 'excel' => '%H'), // Hour 0..23 + 'g' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'h', 'usage' => 'hour', 'excel' => '%h'), // Hour 1..12 + 'a' => array('regexpr' => '(am|pm)', 'datepicker' => 'tt', 'usage' => 'am/pm', 'excel' => 'am/pm'), + 'A' => array('regexpr' => '(AM|PM)', 'datepicker' => 'TT', 'usage' => 'am/pm', 'excel' => 'AM/PM'), + // Minutes + 'i' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'mm', 'usage' => 'minutes', 'excel' => 'mm'), + // Seconds + 's' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'ss', 'usage' => 'seconds', 'excel' => 'ss'), + ); + } + + /** + * Format a date into the supplied format string + * @param mixed $date An int, string, DateTime object or null !! + * @param string $sFormat The format using PHP createFromFormat convention + * @throws Exception + * @return string The formatted date + */ + public static function Format($date, $sFormat = null) + { + if ($sFormat === null) + { + $sFormat = static::GetFormat(); + } + if ($date == null) + { + $sDate = ''; + } + else if (($date === '0000-00-00') || ($date === '0000-00-00 00:00:00')) + { + $sDate = ''; + } + else if ($date instanceof DateTime) + { + // Parameter is a DateTime + $sDate = $date->format($sFormat); + } + else if (is_int($date)) + { + // Parameter is a Unix timestamp + $oDate = new DateTime(); + $oDate->setTimestamp($date); + $sDate = $oDate->format($sFormat); + } + else if (is_string($date)) + { + $oDate = new DateTime($date); + $sDate = $oDate->format($sFormat); + } + else + { + throw new Exception("AttributeDateTime::Format: Unexpected date value: ".print_r($date, true)); + } + return $sDate; + } + + /** + * Parse a date in the supplied format and return the date as a string in the internal format + * @param string $sDate The string to parse + * @param string $sFormat The format, in PHP createFromFormat convention + * @throws Exception + * @return string + */ + public static function Parse($sDate, $sFormat) + { + if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00')) + { + return null; + } + else + { + $sFormat = preg_replace('/\\?/', '', $sFormat); // replace escaped characters by a wildcard for parsing + $oDate = DateTime::createFromFormat($sFormat, $sDate); + if ($oDate === false) + { + throw new Exception("Unable to parse the date: '$sDate' using the format: '$sFormat'"); + } + return $oDate->format(static::GetInternalFormat()); + } + } + + /** + * Get a date or datetime format string in the jQuery UI date picker format + * @param string $sFormat + * @return string The format string using the date picker convention + */ + static public function GetDatePickerFormat() + { + $sFormat = static::GetFormat(); + $aMappings = static::GetFormatMapping(); + $sResult = ''; + + $bEscaping = false; + for($i=0; $i < strlen($sFormat); $i++) + { + if (($sFormat[$i] == '\\')) + { + $bEscaping = true; + continue; + } + + if ($bEscaping) + { + $sResult .= "'{$sFormat[$i]}'"; + $bEscaping = false; + } + else if(array_key_exists($sFormat[$i], $aMappings)) + { + // Not a litteral value, must be replaced by its regular expression pattern + $sResult .= $aMappings[$sFormat[$i]]['datepicker']; + } + else + { + + // Normal char with no special meaning + $sResult .= $sFormat[$i]; + } + } + + return $sResult; + } + + /** + * Get a date or datetime format string in the Excel format + * @param string $sFormat + * @return string The format string using the Excel convention + */ + static public function GetExcelFormat($sFormat = null) + { + $sFormat = ($sFormat == null) ? static::GetFormat() : $sFormat; + $aMappings = static::GetFormatMapping(); + $sResult = ''; + + $bEscaping = false; + for($i=0; $i < strlen($sFormat); $i++) + { + if (($sFormat[$i] == '\\')) + { + $bEscaping = true; + continue; + } + + if ($bEscaping) + { + $sResult .= $sFormat[$i]; // What's the way to escape characters in Excel format ?? + $bEscaping = false; + } + else if(array_key_exists($sFormat[$i], $aMappings)) + { + // Not a litteral value, must be replaced by its regular expression pattern + $sResult .= $aMappings[$sFormat[$i]]['excel']; + } + else + { + + // Normal char with no special meaning + $sResult .= $sFormat[$i]; + } + } + + return $sResult; + } +/* + * Unused since the sorting of the tables is always performed server-side + * + public static function GetTableSorterRule() + { + $aOrder = array(); + $aPos = array(); + $sRegExpr = static::GetRegExpr($aOrder); + foreach(array('year', 'month', 'day', 'hour', 'minutes', 'seconds') as $sUsage) + { + $pos = array_search($sUsage, $aOrder); + if ($pos !== false) + { + $aPos[$sUsage] = '$'.(1+$pos); + } + } + $sIsoDate = "{$aPos['year']}/{$aPos['month']}/{$aPos['day']}"; + if (array_key_exists('hour', $aPos)) + { + $sIsoDate .= " {$aPos['hour']}:{$aPos['minutes']}:{$aPos['seconds']}"; + } + return array('regexpr' => $sRegExpr, 'replacement' => $sIsoDate); + } + + public static function InitTableSorter($oPage, $sRuleName) + { + $aDef = static::GetTableSorterRule(); + + $oPage->add_ready_script( +<<GetDateFormat()); - } - } - - return $default; + // null value will be replaced by the current date, if not already set, in DoComputeValues + return $this->GetNullValue(); } - // END OF THE WORKAROUND - /////////////////////////////////////////////////////////////// public function GetValidationPattern() { - return "^(([0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30))))( (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])(:([0-5][0-9])){0,1}){0,1}|0000-00-00 00:00:00|0000-00-00)$"; + return static::GetRegExpr(); } public function GetBasicFilterOperators() @@ -3654,7 +3990,7 @@ class AttributeDateTime extends AttributeDBField return $proposedValue; } - return date(self::GetDateFormat(), $proposedValue); + return date(self::GetInternalFormat(), $proposedValue); } public function ScalarToSQL($value) @@ -3673,7 +4009,7 @@ class AttributeDateTime extends AttributeDBField public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { - return Str::pure2html($value); + return Str::pure2html(static::Format($value, static::GetFormat())); } public function GetAsXML($value, $oHostObject = null, $bLocalize = true) @@ -3683,6 +4019,19 @@ class AttributeDateTime extends AttributeDBField public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) { + if (empty($sValue) || ($sValue === '0000-00-00 00:00:00') || ($sValue === '0000-00-00')) + { + return ''; + } + else if (self::GetFormat() !== self::GetInternalFormat()) + { + // Format conversion + $oDate = new DateTime($sValue); + if ($oDate !== false) + { + $sValue = $oDate->format(self::GetFormat()); + } + } $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); @@ -3839,13 +4188,40 @@ class AttributeDuration extends AttributeInteger */ class AttributeDate extends AttributeDateTime { - const MYDATEFORMAT = "Y-m-d"; - - static public function GetDateFormat() + static $sDateFormat = null; + + static public function GetFormat() { - return "Y-m-d"; + if (self::$sDateFormat == null) + { + AttributeDateTime::LoadFormatFromConfig(); + } + return self::$sDateFormat; } + static public function SetFormat($sDateFormat) + { + self::$sDateFormat = $sDateFormat; + } + + /** + * Returns the format string used for the date & time stored in memory + * @return string + */ + static public function GetInternalFormat() + { + return 'Y-m-d'; + } + + /** + * Returns the format string used for the date & time written to MySQL + * @return string + */ + static public function GetSQLFormat() + { + return 'Y-m-d'; + } + static public function ListExpectedParams() { return parent::ListExpectedParams(); @@ -3854,11 +4230,6 @@ class AttributeDate extends AttributeDateTime public function GetEditClass() {return "Date";} protected function GetSQLCol($bFullSpec = false) {return "DATE";} - - public function GetValidationPattern() - { - return "^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))$"; - } } /** diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index fc7c24051..c920e17ef 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -258,7 +258,7 @@ class BulkChange protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey) protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined) - protected $m_sDateFormat; // Date format specification, see utils::StringToTime() + protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum) protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias @@ -800,16 +800,16 @@ class BulkChange foreach ($this->m_aAttList as $sAttCode => $iCol) { if ($sAttCode == 'id') continue; - + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); if ($oAttDef instanceof AttributeDateTime) { foreach($this->m_aData as $iRow => $aRowData) { - $sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat); - if ($sNewDate !== false) + $oDate = DateTime::createFromFormat($this->m_sDateFormat, $this->m_aData[$iRow][$iCol]); + if ($oDate !== false) { - // Todo - improve the reporting + $sNewDate = $oDate->format($oAttDef->GetInternalFormat()); $this->m_aData[$iRow][$iCol] = $sNewDate; } else diff --git a/core/bulkexport.class.inc.php b/core/bulkexport.class.inc.php index 8558acca4..d6909fbb4 100644 --- a/core/bulkexport.class.inc.php +++ b/core/bulkexport.class.inc.php @@ -95,7 +95,7 @@ class BulkExportResultGC implements iBackgroundProcess public function Process($iTimeLimit) { - $sDateLimit = date('Y-m-d H:i:s', time() - 24*3600); // Every BulkExportResult older than one day will be deleted + $sDateLimit = date(AttributeDateTime::GetSQLFormat(), time() - 24*3600); // Every BulkExportResult older than one day will be deleted $sOQL = "SELECT BulkExportResult WHERE created < '$sDateLimit'"; $iProcessed = 0; diff --git a/core/config.class.inc.php b/core/config.class.inc.php index da53dde75..409560aa4 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -898,6 +898,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => true, ), + 'date_and_time_format' => array( + 'type' => 'array', + 'description' => 'Format for date and time display (per language)', + 'default' => array('default' => array('date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time')), + 'value' => false, + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), ); public function IsProperty($sPropCode) diff --git a/core/csvbulkexport.class.inc.php b/core/csvbulkexport.class.inc.php index c0461a50e..333636121 100644 --- a/core/csvbulkexport.class.inc.php +++ b/core/csvbulkexport.class.inc.php @@ -34,6 +34,7 @@ class CSVBulkExport extends TabularBulkExport $oP->p(" *\ttext-qualifier: (optional) character to be used around text strings (default is '\"')."); $oP->p(" *\tno_localize: set to 1 to retrieve non-localized values (for instance for ENUM values). Default is 0 (= localized values)"); $oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)"); + $oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the format used in the user interface). Example: 'm/d/Y H:i:s'"); } public function ReadParameters() @@ -57,6 +58,16 @@ class CSVBulkExport extends TabularBulkExport $this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data')); $this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true); + + $sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom'); + if ($sDateFormatRadio == 'default') + { + $this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat(); + } + else + { + $this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data'); + } } @@ -97,6 +108,7 @@ class CSVBulkExport extends TabularBulkExport $oP->add('
'); $oP->add('

'.Dict::S('UI:CSVImport:SeparatorCharacter').'

'); $sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data'); + $sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data'); $aSep = array( ';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'), ',' => Dict::S('UI:CSVImport:SeparatorComma+'), @@ -162,10 +174,28 @@ class CSVBulkExport extends TabularBulkExport $sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : ''; $oP->add('

'.Dict::S('Core:BulkExport:TextFormat').'

'); $oP->add(''); + $oP->add('
'); + $sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data'); + $sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : ''; + $sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : ''; + $oP->add('

'.Dict::S('Core:BulkExport:DateTimeFormat').'

'); + $sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'); + $sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8'); + $oP->add('
'); + $sFormatInput = ''; + $oP->add(''); $oP->add('
'); $oP->add(''); + $sJSTooltip = json_encode('
'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'
'); + $oP->add_ready_script( +<<aStatusInfo['date_format']); $sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']); + AttributeDateTime::SetFormat($sPrevFormat); } } if ($this->aStatusInfo['charset'] != 'UTF-8') diff --git a/core/dbobject.class.php b/core/dbobject.class.php index e7516165b..94e95068d 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -705,6 +705,17 @@ abstract class DBObject implements iDisplay if ($aCallInfo["function"] != "ComputeValues") continue; return; //skip! } + + // Set the "null-not-allowed" datetimes (and dates) whose value is not initialized + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) + { + // AttributeDate is derived from AttributeDateTime + if (($oAttDef instanceof AttributeDateTime) && (!$oAttDef->IsNullAllowed()) && ($this->Get($sAttCode) == $oAttDef->GetNullValue())) + { + $this->Set($sAttCode, date($oAttDef->GetFormat())); + } + } + $this->ComputeValues(); } @@ -3300,8 +3311,8 @@ abstract class DBObject implements iDisplay } $aContext['current_contact_id'] = UserRights::GetContactId(); $aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName(); - $aContext['current_date'] = date('Y-m-d'); - $aContext['current_time'] = date('H:i:s'); + $aContext['current_date'] = date(AttributeDate::GetSQLFormat()); + $aContext['current_time'] = date(AttributeDateTime::GetSQLTimeFormat()); $sValue = MetaModel::ApplyParams($sRawValue, $aContext); $this->Set($sAttCode, $sValue); break; @@ -3328,8 +3339,8 @@ abstract class DBObject implements iDisplay } $aContext['current_contact_id'] = UserRights::GetContactId(); $aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName(); - $aContext['current_date'] = date('Y-m-d'); - $aContext['current_time'] = date('H:i:s'); + $aContext['current_date'] = date(AttributeDate::GetSQLFormat()); + $aContext['current_time'] = date(AttributeDateTime::GetSQLTimeFormat()); $sAddendum = MetaModel::ApplyParams($sRawAddendum, $aContext); $this->Set($sAttCode, $this->Get($sAttCode).$sAddendum); break; diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index 0c852cd15..63c84f9b0 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -47,12 +47,23 @@ class ExcelBulkExport extends TabularBulkExport $oP->p(" * xlsx format options:"); $oP->p(" *\tfields: the comma separated list of field codes to export (e.g: name,org_id,service_name...)."); $oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)"); + $oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the format used in the user interface). Example: 'm/d/Y H:i:s'"); } public function ReadParameters() { parent::ReadParameters(); $this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true); + + $sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom'); + if ($sDateFormatRadio == 'default') + { + $this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat(); + } + else + { + $this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data'); + } } public function EnumFormParts() @@ -76,9 +87,28 @@ class ExcelBulkExport extends TabularBulkExport $oP->add('

'.Dict::S('Core:BulkExport:TextFormat').'

'); $oP->add(''); + $oP->add(''); + + $sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data'); + $sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : ''; + $sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : ''; + $oP->add('

'.Dict::S('Core:BulkExport:DateTimeFormat').'

'); + $sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'); + $sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8'); + $oP->add('
'); + $sFormatInput = ''; + $oP->add(''); + $oP->add(''); $oP->add(''); + $sJSTooltip = json_encode('
'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'
'); + $oP->add_ready_script( +<<aStatusInfo) && $this->aStatusInfo['formatted_text']) - { + if ($oAttDef instanceof AttributeDateTime) + { + // Date and times are formatted using the ISO encoding, not the localized format + $sRet = $value; + } + else if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text']) + { $sRet = $oAttDef->GetEditValue($value, $oObj); - } - else - { - $sRet = $oAttDef->GetAsPlainText($value, $oObj); - } + } + else + { + $sRet = $oAttDef->GetAsPlainText($value, $oObj); + } } } return $sRet; @@ -269,6 +304,7 @@ class ExcelBulkExport extends TabularBulkExport $fStartExcel = microtime(true); $writer = new XLSXWriter(); + $writer->setDateTimeFormat(AttributeDateTime::GetExcelFormat($this->aStatusInfo['date_format'])); $writer->setAuthor(UserRights::GetUserFriendlyName()); $aHeaderTypes = array(); $aHeaderNames = array(); diff --git a/core/inlineimage.class.inc.php b/core/inlineimage.class.inc.php index 5ad2d9fd3..91f00e30e 100644 --- a/core/inlineimage.class.inc.php +++ b/core/inlineimage.class.inc.php @@ -488,7 +488,7 @@ class InlineImageGC implements iBackgroundProcess public function Process($iTimeLimit) { - $sDateLimit = date('Y-m-d H:i:s', time()); // Every temporary InlineImage/Attachment expired will be deleted + $sDateLimit = date(AttributeDateTime::GetSQLFormat(), time()); // Every temporary InlineImage/Attachment expired will be deleted $iProcessed = 0; $sOQL = "SELECT InlineImage WHERE (item_id = 0) AND (expire < '$sDateLimit')"; diff --git a/core/ormcaselog.class.inc.php b/core/ormcaselog.class.inc.php index 6ee7426b9..c0cea3eab 100644 --- a/core/ormcaselog.class.inc.php +++ b/core/ormcaselog.class.inc.php @@ -115,14 +115,14 @@ class ormCaseLog { if (is_int($this->m_aIndex[$index]['date'])) { // Unix timestamp - $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$this->m_aIndex[$index]['date']); + $sDate = date(AttributeDateTime::GetInternalFormat(),$this->m_aIndex[$index]['date']); } elseif (is_object($this->m_aIndex[$index]['date'])) { if (version_compare(phpversion(), '5.3.0', '>=')) { // DateTime - $sDate = $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat')); + $sDate = $this->m_aIndex[$index]['date']->format(AttributeDateTime::GetInternalFormat()); } else { @@ -234,14 +234,14 @@ class ormCaseLog { if (is_int($aIndex[$index]['date'])) { // Unix timestamp - $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']); + $sDate = date(AttributeDateTime::GetFormat(), $aIndex[$index]['date']); } elseif (is_object($aIndex[$index]['date'])) { if (version_compare(phpversion(), '5.3.0', '>=')) { // DateTime - $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat')); + $sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat()); } else { @@ -322,14 +322,14 @@ class ormCaseLog { if (is_int($aIndex[$index]['date'])) { // Unix timestamp - $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']); + $sDate = date(AttributeDateTime::GetFormat(),$aIndex[$index]['date']); } elseif (is_object($aIndex[$index]['date'])) { if (version_compare(phpversion(), '5.3.0', '>=')) { // DateTime - $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat')); + $sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat()); } else { @@ -425,14 +425,14 @@ class ormCaseLog { if (is_int($aIndex[$index]['date'])) { // Unix timestamp - $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']); + $sDate = date(AttributeDateTime::GetFormat(),$aIndex[$index]['date']); } elseif (is_object($aIndex[$index]['date'])) { if (version_compare(phpversion(), '5.3.0', '>=')) { // DateTime - $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat')); + $sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat()); } else { @@ -498,7 +498,7 @@ class ormCaseLog { { $sText = HTMLSanitizer::Sanitize($sText); $bMergeEntries = false; - $sDate = date(Dict::S('UI:CaseLog:DateFormat')); + $sDate = date(AttributeDateTime::GetInternalFormat()); if ($sOnBehalfOf == '') { $sOnBehalfOf = UserRights::GetUserFriendlyName(); @@ -612,7 +612,7 @@ class ormCaseLog { $sFormat = 'html'; } - $sDate = date(Dict::S('UI:CaseLog:DateFormat'), $iDate); + $sDate = date(AttributeDateTime::GetInternalFormat(), $iDate); $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId); $iSepLength = strlen($sSeparator); diff --git a/core/ormstopwatch.class.inc.php b/core/ormstopwatch.class.inc.php index e144d93d6..ef5010011 100644 --- a/core/ormstopwatch.class.inc.php +++ b/core/ormstopwatch.class.inc.php @@ -490,7 +490,7 @@ class CheckStopWatchThresholds implements iBackgroundProcess { $iPercent = $aThresholdData['percent']; // could be different than the index ! - $sNow = date('Y-m-d H:i:s'); + $sNow = date(AttributeDateTime::GetSQLFormat()); $sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < '$sNow'"; $oFilter = DBObjectSearch::FromOQL($sExpression); $oSet = new DBObjectSet($oFilter); diff --git a/core/ownershiplock.class.inc.php b/core/ownershiplock.class.inc.php index 852c02731..aa5e39d16 100644 --- a/core/ownershiplock.class.inc.php +++ b/core/ownershiplock.class.inc.php @@ -239,7 +239,7 @@ class iTopOwnershipLock { if ($sToken === $this->oToken->Get('token')) { - $this->oToken->Set('last_seen', date('Y-m-d H:i:s')); + $this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat())); $this->oToken->DBUpdate(); $aResult['acquired'] = $this->oToken->Get('acquired'); } @@ -327,9 +327,9 @@ class iTopOwnershipLock $this->oToken->Set('obj_class', $this->sObjClass); $this->oToken->Set('obj_key', $this->iObjKey); } - $this->oToken->Set('acquired', date('Y-m-d H:i:s')); + $this->oToken->Set('acquired', date(AttributeDateTime::GetSQLFormat())); $this->oToken->Set('user_id', UserRights::GetUserId()); - $this->oToken->Set('last_seen', date('Y-m-d H:i:s')); + $this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat())); if ($sToken === null) { $sToken = sprintf('%X', microtime(true)); @@ -342,7 +342,7 @@ class iTopOwnershipLock protected static function DeleteExpiredLocks() { $sOQL = "SELECT iTopOwnershipToken WHERE last_seen < :last_seen_limit"; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, array('last_seen_limit' => date('Y-m-d H:i:s', time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay'))))); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, array('last_seen_limit' => date(AttributeDateTime::GetSQLFormat(), time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay'))))); while($oToken = $oSet->Fetch()) { $oToken->DBDelete(); diff --git a/core/pdfbulkexport.class.inc.php b/core/pdfbulkexport.class.inc.php index ba079e656..851b59522 100644 --- a/core/pdfbulkexport.class.inc.php +++ b/core/pdfbulkexport.class.inc.php @@ -31,6 +31,7 @@ class PDFBulkExport extends HTMLBulkExport $oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...)."); $oP->p(" *\tpage_size: (optional) size of the page. One of A4, A3, Letter (default is 'A4')."); $oP->p(" *\tpage_orientation: (optional) the orientation of the page. Either Portrait or Landscape (default is 'Portrait')."); + $oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the format used in the user interface). Example: 'm/d/Y H:i:s'"); } public function EnumFormParts() @@ -44,6 +45,8 @@ class PDFBulkExport extends HTMLBulkExport { case 'pdf_options': $oP->add('
'.Dict::S('Core:BulkExport:PDFOptions').''); + $oP->add('
'); + $oP->add('

'.Dict::S('Core:PDFBulkExport:PageFormat').'

'); $oP->add(''); $oP->add(''); $oP->add(''); @@ -53,8 +56,30 @@ class PDFBulkExport extends HTMLBulkExport $oP->add(''); $oP->add(''); $oP->add('
'.Dict::S('Core:BulkExport:PDFPageSize').''.$this->GetSelectCtrl('page_orientation', array('P', 'L'), 'Core:BulkExport:PageOrientation-', 'L').'
'); - + + $oP->add('
'); + + $sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data'); + $sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : ''; + $sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : ''; + $oP->add('

'.Dict::S('Core:BulkExport:DateTimeFormat').'

'); + $sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'); + $sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8'); + $oP->add('
'); + $sFormatInput = ''; + $oP->add(''); + + $oP->add('
'); + + $oP->add('
'); + $sJSTooltip = json_encode('
'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'
'); + $oP->add_ready_script( +<<aStatusInfo['page_size'] = utils::ReadParam('page_size', 'A4', true, 'raw_data'); $this->aStatusInfo['page_orientation'] = utils::ReadParam('page_orientation', 'L', true); + + $sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom'); + if ($sDateFormatRadio == 'default') + { + $this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat(); + } + else + { + $this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data'); + } } public function GetHeader() @@ -106,7 +141,10 @@ class PDFBulkExport extends HTMLBulkExport public function GetNextChunk(&$aStatus) { + $sPrevFormat = AttributeDateTime::GetFormat(); + AttributeDateTime::SetFormat($this->aStatusInfo['date_format']); $sData = parent::GetNextChunk($aStatus); + AttributeDateTime::SetFormat($sPrevFormat); $hFile = @fopen($this->aStatusInfo['tmp_file'], 'ab'); if ($hFile === false) { diff --git a/css/jquery-ui-timepicker-addon.css b/css/jquery-ui-timepicker-addon.css new file mode 100644 index 000000000..95a22420c --- /dev/null +++ b/css/jquery-ui-timepicker-addon.css @@ -0,0 +1,30 @@ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: left; } +.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; } + +.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input { background: none; color: inherit; border: none; outline: none; border-bottom: solid 1px #555; width: 95%; } +.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus { border-bottom-color: #aaa; } + +.ui-timepicker-rtl{ direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; } +.ui-timepicker-rtl dl dt{ float: right; clear: right; } +.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; } + +/* Shortened version style */ +.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; } +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time, +.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; } +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; } +.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; } +.ui-timepicker-div.ui-timepicker-oneLine dl dd, +.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; } +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before, +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; } +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before, +.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; } +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide, +.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; } \ No newline at end of file diff --git a/css/light-grey.css b/css/light-grey.css index 6c9f870fc..aaa12fbc0 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -2268,3 +2268,14 @@ span.refresh-button { } +.ui-datepicker-buttonpane, .ui-timepicker-div { + font-size: 9pt; + font-family: Tahoma, Verdana, Arial, Helvetica; +} + + +.date_format_tooltip td { + padding: 0.25em; +} + + diff --git a/css/light-grey.scss b/css/light-grey.scss index 7843a027b..07be67338 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -1695,3 +1695,10 @@ span.refresh-button { } } } +.ui-datepicker-buttonpane, .ui-timepicker-div { + font-size: 9pt; + font-family: Tahoma, Verdana, Arial, Helvetica; +} +.date_format_tooltip td { + padding: 0.25em; +} diff --git a/dictionaries/cs.dictionary.itop.ui.php b/dictionaries/cs.dictionary.itop.ui.php index 46fd20aa0..0321746a4 100755 --- a/dictionaries/cs.dictionary.itop.ui.php +++ b/dictionaries/cs.dictionary.itop.ui.php @@ -1048,7 +1048,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'UI:FailedToApplyStimuli' => 'Akce se nezdařila.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Upravuji %2$d objekt(ů) třídy %3$s', 'UI:CaseLogTypeYourTextHere' => 'Zadejte text zde:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Počáteční hodnota:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'Pole %1$s není zapisovatelné, protože je spravováno synchronizací dat.', diff --git a/dictionaries/da.dictionary.itop.ui.php b/dictionaries/da.dictionary.itop.ui.php index ba144ce99..a5b31d068 100644 --- a/dictionaries/da.dictionary.itop.ui.php +++ b/dictionaries/da.dictionary.itop.ui.php @@ -840,7 +840,6 @@ Ved tilknytningen til en trigger, bliver hver handling tildelt et "rækkefølge" 'UI:FailedToApplyStimuli' => 'Handlingen fejlede.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Ændrer %2$d objekter af klasse %3$s', 'UI:CaseLogTypeYourTextHere' => 'Skriv din tekst her:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Begyndelses værdi:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'Feltet %1$s er skrivebeskyttet, fordi det administreres af data synchronization. Værdien er ikke sat.', diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 901909ebb..e941d5a0a 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -861,7 +861,6 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm 'UI:FailedToApplyStimuli' => 'Der Vorgang ist fehlgeschlagen.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifiziere %2$d Objekte der Klasse %3$s', 'UI:CaseLogTypeYourTextHere' => 'Geben Sie Ihren Text hier ein:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Anfangswert:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'Das Feld %1$s ist nicht schreibbar, weil es durch die Datensynchronisation geführt wird. Wert nicht gesetzt.', diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 4793a1aa1..1966b8080 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -853,4 +853,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:BulkExportLegacyExport' => 'Click here to access the legacy export.', 'Core:BulkExport:XLSXOptions' => 'Excel Options', 'Core:BulkExport:TextFormat' => 'Text fields containing some HTML markup', + 'Core:BulkExport:DateTimeFormat' => 'Date and Time format', + 'Core:BulkExport:DateTimeFormatDefault_Example' => 'Default format (%1$s), e.g. %2$s', + 'Core:BulkExport:DateTimeFormatCustom_Format' => 'Custom format: %1$s', )); diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index df45ca591..1291af404 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -1053,7 +1053,6 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:FailedToApplyStimuli' => 'The action has failed.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s', 'UI:CaseLogTypeYourTextHere' => 'Type your text here:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Initial value:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.', @@ -1255,7 +1254,26 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:CSVImportCreated_items' => 'Created: %1$d', 'UI:CSVImportModified_items' => 'Modified: %1$d', 'UI:CSVImportUnchanged_items' => 'Unchanged: %1$d', - + 'UI:CSVImport:DateAndTimeFormats' => 'Date and time format', + 'UI:CSVImport:DefaultDateTimeFormat_Format_Example' => 'Default format: %1$s (e.g. %2$s)', + 'UI:CSVImport:CustomDateTimeFormat' => 'Custom format: %1$s', + 'UI:CSVImport:CustomDateTimeFormatTooltip' => 'Available placeholders: + + + + + + + + + + + + + + +
Yyear (4 digits, e.g. 2016)
yyear (2 digits, e.g. 16 for 2016)
mmonth (2 digits, e.g. 01..12)
nmonth (1 or 2 digits no leading zero, e.g. 1..12)
dday (2 digits, e.g. 01..31)
jday (1 or 2 digits no leading zero, e.g. 1..31)
Hhour (24 hour, 2 digits, e.g. 00..23)
hhour (12 hour, 2 digits, e.g. 01..12)
Ghour (24 hour, 1 or 2 digits no leading zero, e.g. 0..23)
ghour (12 hour, 1 or 2 digits no leading zero, e.g. 1..12)
ahour, am or pm (lowercase)
Ahour, AM or PM (uppercase)
iminutes (2 digits, e.g. 00..59)
sseconds (2 digits, e.g. 00..59)
', + 'UI:Button:Remove' => 'Remove', 'UI:AddAnExisting_Class' => 'Add objects of type %1$s...', 'UI:SelectionOf_Class' => 'Selection of objects of type %1$s', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 446c1d05f..4bad7fab5 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -1017,7 +1017,6 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:FailedToApplyStimuli' => 'La acción ha fallado.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modificando %2$d objetos de la clase %3$s', 'UI:CaseLogTypeYourTextHere' => 'Escriba su texto aquí:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Valor inicial:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'El campo %1$s no es escribible porque es manejado por el sincronizador de datos. Valor no cambiado.', diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index d8adb8f1a..8daefa744 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -711,6 +711,9 @@ Opérateurs :
'Core:BulkExportLegacyExport' => 'Cliquez ici pour exécuter l\'ancienne version de l\'export.', 'Core:BulkExport:XLSXOptions' => 'Options du format Excel', 'Core:BulkExport:TextFormat' => 'Champs texte contenant des balises HTML', + 'Core:BulkExport:DateTimeFormat' => 'Format de date et heure', + 'Core:BulkExport:DateTimeFormatDefault_Example' => 'Format par défaut (%1$s), ex. %2$s', + 'Core:BulkExport:DateTimeFormatCustom_Format' => 'Format spécial: %1$s', 'Core:DeletedObjectLabel' => '%1s (effacé)', 'Core:SyncSplitModeCLIOnly' => 'The synchronization can be executed in chunks only if run in mode CLI~~', 'Core:ExecProcess:Code1' => 'Wrong command or command finished with errors (e.g. wrong script name)~~', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 8261c6f2f..476be45d6 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -456,6 +456,25 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:CSVImport:AlertMultipleMapping' => 'Veuillez vous assurer que chaque champ cible est sélectionné une seule fois.', 'UI:CSVImport:AlertNoSearchCriteria' => 'Veuillez choisir au moins une clef de recherche.', 'UI:CSVImport:Encoding' => 'Encodage des caractères', + 'UI:CSVImport:DateAndTimeFormats' => 'Format de date et heure', + 'UI:CSVImport:DefaultDateTimeFormat_Format_Example' => 'Format par défaut: %1$s (ex. %2$s)', + 'UI:CSVImport:CustomDateTimeFormat' => 'Format spécial: %1$s', + 'UI:CSVImport:CustomDateTimeFormatTooltip' => 'Codes de format: + + + + + + + + + + + + + + +
Yannée (sur 4 chiffres, ex. 2016)
yannée (sur 2 chiffres, ex. 16 pour 2016)
mmois (sur 2 chiffres: 01..12)
nmonth (sur 1 ou 2 chiffres sans le zero au début: 1..12)
djour (sur 2 chiffres: 01..31)
jjour (sur 1 ou 2 chiffres sans le zero au début: 1..31)
Hheure (24 heures sur 2 chiffres: 00..23)
hheure (12 heures sur 2 chiffres: 01..12)
Gheure (24 heures sur 1 ou 2 chiffres: 0..23)
gheure (12 heures sur 1 ou 2 chiffres: 1..12)
aam ou pm (en minuscules)
AAM ou PM (en majuscules)
iminutes (sur 2 chiffres: 00..59)
ssecondes (sur 2 chiffres: 00..59)
', 'UI:CSVReport-Value-Modified' => 'Modifié', 'UI:CSVReport-Value-SetIssue' => 'Modification impossible - cause : %1$s', @@ -897,7 +916,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:FailedToApplyStimuli' => 'L\'action a échoué', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modification de %2$d objet(s) de type %3$s', 'UI:CaseLogTypeYourTextHere' => 'Nouvelle entrée ci-dessous:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Valeur initiale:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'Le champ %1$s ne peut pas être modifié car il est géré par une synchronisation avec une source de données. Valeur ignorée.', diff --git a/dictionaries/hu.dictionary.itop.ui.php b/dictionaries/hu.dictionary.itop.ui.php index c632fe388..f8d57783b 100755 --- a/dictionaries/hu.dictionary.itop.ui.php +++ b/dictionaries/hu.dictionary.itop.ui.php @@ -760,7 +760,6 @@ Akció kiváltó okhoz rendelésekor kap egy sorszámot , amely meghatározza az 'UI:FailedToApplyStimuli' => 'A művelet sikertelen', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: %3$s osztály %2$d objketumainak módosítása', 'UI:CaseLogTypeYourTextHere' => 'Írjon ide:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Kezdeti érték:', 'UI:AttemptingToSetASlaveAttribute_Name' => '%1$s mező nem írható, mert a szinkronizációnál használt kulcs. Érték nem lett beállítva.', diff --git a/dictionaries/it.dictionary.itop.ui.php b/dictionaries/it.dictionary.itop.ui.php index bf37657f0..54b3cde4a 100644 --- a/dictionaries/it.dictionary.itop.ui.php +++ b/dictionaries/it.dictionary.itop.ui.php @@ -894,7 +894,6 @@ Quando è associata a un trigger, ad ogni azione è assegnato un numero "ordine" 'UI:FailedToApplyStimuli' => 'L\'azione non è riuscita.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifica %2$d oggetti della classe %3$s~~', 'UI:CaseLogTypeYourTextHere' => 'Digitare il tuo testo qui:', - 'UI:CaseLog:DateFormat' => 'A-m-g H:m:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~', 'UI:CaseLog:InitialValue' => 'Valore iniziale:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'Il campo %1$s on è scrivibile, perché è comandato dalla sincronizzazione dei dati. Valore non impostato.', diff --git a/dictionaries/ja.dictionary.itop.ui.php b/dictionaries/ja.dictionary.itop.ui.php index 12ae3dbb7..e1b74e783 100644 --- a/dictionaries/ja.dictionary.itop.ui.php +++ b/dictionaries/ja.dictionary.itop.ui.php @@ -839,7 +839,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:FailedToApplyStimuli' => 'アクションは失敗しました。', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正', 'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => '初期値:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'フィールド %1$s は、データの同期によってマスターリングされているため書き込み可能ではありません。値は設定されません。', diff --git a/dictionaries/nl.dictionary.itop.ui.php b/dictionaries/nl.dictionary.itop.ui.php index 5c33ef367..6bc20bbd4 100644 --- a/dictionaries/nl.dictionary.itop.ui.php +++ b/dictionaries/nl.dictionary.itop.ui.php @@ -1025,7 +1025,6 @@ Indien gekoppeld aan een Trigger, wordt aan elke actie een "orde" nummer gegeven 'UI:FailedToApplyStimuli' => 'De actie is mislukt.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Bezig met het bewerken van %2$d objecten van klasse %3$s', 'UI:CaseLogTypeYourTextHere' => 'Typ uw text hier:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Initiële waarde:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'Het veld %1$s is niet beschrijfbaar omdat het onderdeel is van de data synchronisatie. Waarde niet opgegeven', diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index 7aad6aaf9..98d6a8d61 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -1016,7 +1016,6 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:FailedToApplyStimuli' => 'A ação falhou.', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: modificando objetos %2$d da classe %3$s', 'UI:CaseLogTypeYourTextHere' => 'Digite seu texto aqui:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Valor inicial:', 'UI:AttemptingToSetASlaveAttribute_Name' => 'O campo %1$s não é editável, porque é originado pela sincronização de dados. Valor não definido.', diff --git a/dictionaries/ru.dictionary.itop.ui.php b/dictionaries/ru.dictionary.itop.ui.php index 77452f8c1..5298bfac5 100644 --- a/dictionaries/ru.dictionary.itop.ui.php +++ b/dictionaries/ru.dictionary.itop.ui.php @@ -1010,7 +1010,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:FailedToApplyStimuli' => 'The action has failed.~~', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s~~', 'UI:CaseLogTypeYourTextHere' => 'Введите свой текст:', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s~~', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~', 'UI:CaseLog:InitialValue' => 'Initial value:~~', 'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.~~', diff --git a/dictionaries/tr.dictionary.itop.ui.php b/dictionaries/tr.dictionary.itop.ui.php index 876ca1bce..71b80641c 100644 --- a/dictionaries/tr.dictionary.itop.ui.php +++ b/dictionaries/tr.dictionary.itop.ui.php @@ -1041,7 +1041,6 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe 'UI:FailedToApplyStimuli' => 'The action has failed.~~', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s~~', 'UI:CaseLogTypeYourTextHere' => 'Type your text here:~~', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s~~', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~', 'UI:CaseLog:InitialValue' => 'Initial value:~~', 'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.~~', diff --git a/dictionaries/zh.dictionary.itop.ui.php b/dictionaries/zh.dictionary.itop.ui.php index 836caffc8..f318abebc 100644 --- a/dictionaries/zh.dictionary.itop.ui.php +++ b/dictionaries/zh.dictionary.itop.ui.php @@ -1040,7 +1040,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:FailedToApplyStimuli' => 'The action has failed.~~', 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s~~', 'UI:CaseLogTypeYourTextHere' => 'Type your text here:~~', - 'UI:CaseLog:DateFormat' => 'Y-m-d H:i:s~~', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:~~', 'UI:CaseLog:InitialValue' => 'Initial value:~~', 'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.~~', diff --git a/js/jquery-ui-timepicker-addon-i18n.min.js b/js/jquery-ui-timepicker-addon-i18n.min.js new file mode 100644 index 000000000..9a60ee0a1 --- /dev/null +++ b/js/jquery-ui-timepicker-addon-i18n.min.js @@ -0,0 +1,4 @@ +/*! jQuery Timepicker Addon - v1.6.1 - 2015-11-14 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2015 Trent Richardson; Licensed MIT */ +!function(a){a.timepicker.regional.af={timeOnlyTitle:"Kies Tyd",timeText:"Tyd ",hourText:"Ure ",minuteText:"Minute",secondText:"Sekondes",millisecText:"Millisekondes",microsecText:"Mikrosekondes",timezoneText:"Tydsone",currentText:"Huidige Tyd",closeText:"Klaar",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.am={timeOnlyTitle:"Ընտրեք ժամանակը",timeText:"Ժամանակը",hourText:"Ժամ",minuteText:"Րոպե",secondText:"Վարկյան",millisecText:"Միլիվարկյան",microsecText:"Միկրովարկյան",timezoneText:"Ժամային գոտին",currentText:"Այժմ",closeText:"Փակել",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.bg={timeOnlyTitle:"Изберете време",timeText:"Време",hourText:"Час",minuteText:"Минути",secondText:"Секунди",millisecText:"Милисекунди",microsecText:"Микросекунди",timezoneText:"Часови пояс",currentText:"Сега",closeText:"Затвори",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.ca={timeOnlyTitle:"Escollir una hora",timeText:"Hora",hourText:"Hores",minuteText:"Minuts",secondText:"Segons",millisecText:"Milisegons",microsecText:"Microsegons",timezoneText:"Fus horari",currentText:"Ara",closeText:"Tancar",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.cs={timeOnlyTitle:"Vyberte čas",timeText:"Čas",hourText:"Hodiny",minuteText:"Minuty",secondText:"Vteřiny",millisecText:"Milisekundy",microsecText:"Mikrosekundy",timezoneText:"Časové pásmo",currentText:"Nyní",closeText:"Zavřít",timeFormat:"HH:mm",timeSuffix:"",amNames:["dop.","AM","A"],pmNames:["odp.","PM","P"],isRTL:!1},a.timepicker.regional.da={timeOnlyTitle:"Vælg tid",timeText:"Tid",hourText:"Time",minuteText:"Minut",secondText:"Sekund",millisecText:"Millisekund",microsecText:"Mikrosekund",timezoneText:"Tidszone",currentText:"Nu",closeText:"Luk",timeFormat:"HH:mm",timeSuffix:"",amNames:["am","AM","A"],pmNames:["pm","PM","P"],isRTL:!1},a.timepicker.regional.de={timeOnlyTitle:"Zeit wählen",timeText:"Zeit",hourText:"Stunde",minuteText:"Minute",secondText:"Sekunde",millisecText:"Millisekunde",microsecText:"Mikrosekunde",timezoneText:"Zeitzone",currentText:"Jetzt",closeText:"Fertig",timeFormat:"HH:mm",timeSuffix:"",amNames:["vorm.","AM","A"],pmNames:["nachm.","PM","P"],isRTL:!1},a.timepicker.regional.el={timeOnlyTitle:"Επιλογή ώρας",timeText:"Ώρα",hourText:"Ώρες",minuteText:"Λεπτά",secondText:"Δευτερόλεπτα",millisecText:"μιλιδευτερόλεπτο",microsecText:"Microseconds",timezoneText:"Ζώνη ώρας",currentText:"Τώρα",closeText:"Κλείσιμο",timeFormat:"HH:mm",timeSuffix:"",amNames:["π.μ.","AM","A"],pmNames:["μ.μ.","PM","P"],isRTL:!1},a.timepicker.regional.es={timeOnlyTitle:"Elegir una hora",timeText:"Hora",hourText:"Horas",minuteText:"Minutos",secondText:"Segundos",millisecText:"Milisegundos",microsecText:"Microsegundos",timezoneText:"Uso horario",currentText:"Hoy",closeText:"Cerrar",timeFormat:"HH:mm",timeSuffix:"",amNames:["a.m.","AM","A"],pmNames:["p.m.","PM","P"],isRTL:!1},a.timepicker.regional.et={timeOnlyTitle:"Vali aeg",timeText:"Aeg",hourText:"Tund",minuteText:"Minut",secondText:"Sekund",millisecText:"Millisekundis",microsecText:"Mikrosekundis",timezoneText:"Ajavöönd",currentText:"Praegu",closeText:"Valmis",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.eu={timeOnlyTitle:"Aukeratu ordua",timeText:"Ordua",hourText:"Orduak",minuteText:"Minutuak",secondText:"Segundoak",millisecText:"Milisegundoak",microsecText:"Mikrosegundoak",timezoneText:"Ordu-eremua",currentText:"Orain",closeText:"Itxi",timeFormat:"HH:mm",timeSuffix:"",amNames:["a.m.","AM","A"],pmNames:["p.m.","PM","P"],isRTL:!1},a.timepicker.regional.fa={timeOnlyTitle:"انتخاب زمان",timeText:"زمان",hourText:"ساعت",minuteText:"دقیقه",secondText:"ثانیه",millisecText:"میلی ثانیه",microsecText:"میکرو ثانیه",timezoneText:"منطقه زمانی",currentText:"الان",closeText:"انتخاب",timeFormat:"HH:mm",timeSuffix:"",amNames:["قبل ظهر","AM","A"],pmNames:["بعد ظهر","PM","P"],isRTL:!0},a.timepicker.regional.fi={timeOnlyTitle:"Valitse aika",timeText:"Aika",hourText:"Tunti",minuteText:"Minuutti",secondText:"Sekunti",millisecText:"Millisekunnin",microsecText:"Mikrosekuntia",timezoneText:"Aikavyöhyke",currentText:"Nyt",closeText:"Sulje",timeFormat:"HH:mm",timeSuffix:"",amNames:["ap.","AM","A"],pmNames:["ip.","PM","P"],isRTL:!1},a.timepicker.regional.fr={timeOnlyTitle:"Choisir une heure",timeText:"Heure",hourText:"Heures",minuteText:"Minutes",secondText:"Secondes",millisecText:"Millisecondes",microsecText:"Microsecondes",timezoneText:"Fuseau horaire",currentText:"Maintenant",closeText:"Terminé",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.gl={timeOnlyTitle:"Elixir unha hora",timeText:"Hora",hourText:"Horas",minuteText:"Minutos",secondText:"Segundos",millisecText:"Milisegundos",microsecText:"Microssegundos",timezoneText:"Fuso horario",currentText:"Agora",closeText:"Pechar",timeFormat:"HH:mm",timeSuffix:"",amNames:["a.m.","AM","A"],pmNames:["p.m.","PM","P"],isRTL:!1},a.timepicker.regional.he={timeOnlyTitle:"בחירת זמן",timeText:"שעה",hourText:"שעות",minuteText:"דקות",secondText:"שניות",millisecText:"אלפית השנייה",microsecText:"מיקרו",timezoneText:"אזור זמן",currentText:"עכשיו",closeText:"סגור",timeFormat:"HH:mm",timeSuffix:"",amNames:['לפנה"צ',"AM","A"],pmNames:['אחה"צ',"PM","P"],isRTL:!0},a.timepicker.regional.hr={timeOnlyTitle:"Odaberi vrijeme",timeText:"Vrijeme",hourText:"Sati",minuteText:"Minute",secondText:"Sekunde",millisecText:"Milisekunde",microsecText:"Mikrosekunde",timezoneText:"Vremenska zona",currentText:"Sada",closeText:"Gotovo",timeFormat:"HH:mm",timeSuffix:"",amNames:["a.m.","AM","A"],pmNames:["p.m.","PM","P"],isRTL:!1},a.timepicker.regional.hu={timeOnlyTitle:"Válasszon időpontot",timeText:"Idő",hourText:"Óra",minuteText:"Perc",secondText:"Másodperc",millisecText:"Milliszekundumos",microsecText:"Ezredmásodperc",timezoneText:"Időzóna",currentText:"Most",closeText:"Kész",timeFormat:"HH:mm",timeSuffix:"",amNames:["de.","AM","A"],pmNames:["du.","PM","P"],isRTL:!1},a.timepicker.regional.id={timeOnlyTitle:"Pilih Waktu",timeText:"Waktu",hourText:"Pukul",minuteText:"Menit",secondText:"Detik",millisecText:"Milidetik",microsecText:"Mikrodetik",timezoneText:"Zona Waktu",currentText:"Sekarang",closeText:"OK",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.it={timeOnlyTitle:"Scegli orario",timeText:"Orario",hourText:"Ora",minuteText:"Minuti",secondText:"Secondi",millisecText:"Millisecondi",microsecText:"Microsecondi",timezoneText:"Fuso orario",currentText:"Adesso",closeText:"Chiudi",timeFormat:"HH:mm",timeSuffix:"",amNames:["m.","AM","A"],pmNames:["p.","PM","P"],isRTL:!1},a.timepicker.regional.ja={timeOnlyTitle:"時間を選択",timeText:"時間",hourText:"時",minuteText:"分",secondText:"秒",millisecText:"ミリ秒",microsecText:"マイクロ秒",timezoneText:"タイムゾーン",currentText:"現時刻",closeText:"閉じる",timeFormat:"HH:mm",timeSuffix:"",amNames:["午前","AM","A"],pmNames:["午後","PM","P"],isRTL:!1},a.timepicker.regional.ko={timeOnlyTitle:"시간 선택",timeText:"시간",hourText:"시",minuteText:"분",secondText:"초",millisecText:"밀리초",microsecText:"마이크로",timezoneText:"표준 시간대",currentText:"현재 시각",closeText:"닫기",timeFormat:"tt h:mm",timeSuffix:"",amNames:["오전","AM","A"],pmNames:["오후","PM","P"],isRTL:!1},a.timepicker.regional.lt={timeOnlyTitle:"Pasirinkite laiką",timeText:"Laikas",hourText:"Valandos",minuteText:"Minutės",secondText:"Sekundės",millisecText:"Milisekundės",microsecText:"Mikrosekundės",timezoneText:"Laiko zona",currentText:"Dabar",closeText:"Uždaryti",timeFormat:"HH:mm",timeSuffix:"",amNames:["priešpiet","AM","A"],pmNames:["popiet","PM","P"],isRTL:!1},a.timepicker.regional.lv={timeOnlyTitle:"Ievadiet laiku",timeText:"Laiks",hourText:"Stundas",minuteText:"Minūtes",secondText:"Sekundes",millisecText:"Milisekundes",microsecText:"Mikrosekundes",timezoneText:"Laika josla",currentText:"Tagad",closeText:"Aizvērt",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","AM","A"],pmNames:["PM","PM","P"],isRTL:!1},a.timepicker.regional.mk={timeOnlyTitle:"Одберете време",timeText:"Време",hourText:"Час",minuteText:"Минути",secondText:"Секунди",millisecText:"Милисекунди",microsecText:"Микросекунди",timezoneText:"Временска зона",currentText:"Сега",closeText:"Затвори",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.nl={timeOnlyTitle:"Tijdstip",timeText:"Tijd",hourText:"Uur",minuteText:"Minuut",secondText:"Seconde",millisecText:"Milliseconde",microsecText:"Microseconde",timezoneText:"Tijdzone",currentText:"Vandaag",closeText:"Sluiten",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.no={timeOnlyTitle:"Velg tid",timeText:"Tid",hourText:"Time",minuteText:"Minutt",secondText:"Sekund",millisecText:"Millisekund",microsecText:"mikrosekund",timezoneText:"Tidssone",currentText:"Nå",closeText:"Lukk",timeFormat:"HH:mm",timeSuffix:"",amNames:["am","AM","A"],pmNames:["pm","PM","P"],isRTL:!1},a.timepicker.regional.pl={timeOnlyTitle:"Wybierz godzinę",timeText:"Czas",hourText:"Godzina",minuteText:"Minuta",secondText:"Sekunda",millisecText:"Milisekunda",microsecText:"Mikrosekunda",timezoneText:"Strefa czasowa",currentText:"Teraz",closeText:"Gotowe",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional["pt-BR"]={timeOnlyTitle:"Escolha o horário",timeText:"Horário",hourText:"Hora",minuteText:"Minutos",secondText:"Segundos",millisecText:"Milissegundos",microsecText:"Microssegundos",timezoneText:"Fuso horário",currentText:"Agora",closeText:"Fechar",timeFormat:"HH:mm",timeSuffix:"",amNames:["a.m.","AM","A"],pmNames:["p.m.","PM","P"],isRTL:!1},a.timepicker.regional.pt={timeOnlyTitle:"Escolha uma hora",timeText:"Hora",hourText:"Horas",minuteText:"Minutos",secondText:"Segundos",millisecText:"Milissegundos",microsecText:"Microssegundos",timezoneText:"Fuso horário",currentText:"Agora",closeText:"Fechar",timeFormat:"HH:mm",timeSuffix:"",amNames:["a.m.","AM","A"],pmNames:["p.m.","PM","P"],isRTL:!1},a.timepicker.regional.ro={timeOnlyTitle:"Alegeţi o oră",timeText:"Timp",hourText:"Ore",minuteText:"Minute",secondText:"Secunde",millisecText:"Milisecunde",microsecText:"Microsecunde",timezoneText:"Fus orar",currentText:"Acum",closeText:"Închide",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.ru={timeOnlyTitle:"Выберите время",timeText:"Время",hourText:"Часы",minuteText:"Минуты",secondText:"Секунды",millisecText:"Миллисекунды",microsecText:"Микросекунды",timezoneText:"Часовой пояс",currentText:"Сейчас",closeText:"Закрыть",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.sk={timeOnlyTitle:"Zvoľte čas",timeText:"Čas",hourText:"Hodiny",minuteText:"Minúty",secondText:"Sekundy",millisecText:"Milisekundy",microsecText:"Mikrosekundy",timezoneText:"Časové pásmo",currentText:"Teraz",closeText:"Zavrieť",timeFormat:"H:m",timeSuffix:"",amNames:["dop.","AM","A"],pmNames:["pop.","PM","P"],isRTL:!1},a.timepicker.regional.sl={timeOnlyTitle:"Izberite čas",timeText:"Čas",hourText:"Ura",minuteText:"Minute",secondText:"Sekunde",millisecText:"Milisekunde",microsecText:"Mikrosekunde",timezoneText:"Časovni pas",currentText:"Sedaj",closeText:"Zapri",timeFormat:"HH:mm",timeSuffix:"",amNames:["dop.","AM","A"],pmNames:["pop.","PM","P"],isRTL:!1},a.timepicker.regional["sr-RS"]={timeOnlyTitle:"Одаберите време",timeText:"Време",hourText:"Сати",minuteText:"Минути",secondText:"Секунде",millisecText:"Милисекунде",microsecText:"Микросекунде",timezoneText:"Временска зона",currentText:"Сада",closeText:"Затвори",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional["sr-YU"]={timeOnlyTitle:"Odaberite vreme",timeText:"Vreme",hourText:"Sati",minuteText:"Minuti",secondText:"Sekunde",millisecText:"Milisekunde",microsecText:"Mikrosekunde",timezoneText:"Vremenska zona",currentText:"Sada",closeText:"Zatvori",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.sv={timeOnlyTitle:"Välj en tid",timeText:"Tid",hourText:"Timme",minuteText:"Minut",secondText:"Sekund",millisecText:"Millisekund",microsecText:"Mikrosekund",timezoneText:"Tidszon",currentText:"Nu",closeText:"Stäng",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.th={timeOnlyTitle:"เลือกเวลา",timeText:"เวลา ",hourText:"ชั่วโมง ",minuteText:"นาที",secondText:"วินาที",millisecText:"มิลลิวินาที",microsecText:"ไมโคริวินาที",timezoneText:"เขตเวลา",currentText:"เวลาปัจจุบัน",closeText:"ปิด",timeFormat:"hh:mm tt",timeSuffix:""},a.timepicker.regional.tr={timeOnlyTitle:"Zaman Seçiniz",timeText:"Zaman",hourText:"Saat",minuteText:"Dakika",secondText:"Saniye",millisecText:"Milisaniye",microsecText:"Mikrosaniye",timezoneText:"Zaman Dilimi",currentText:"Şu an",closeText:"Tamam",timeFormat:"HH:mm",timeSuffix:"",amNames:["ÖÖ","Ö"],pmNames:["ÖS","S"],isRTL:!1},a.timepicker.regional.uk={timeOnlyTitle:"Виберіть час",timeText:"Час",hourText:"Години",minuteText:"Хвилини",secondText:"Секунди",millisecText:"Мілісекунди",microsecText:"Мікросекунди",timezoneText:"Часовий пояс",currentText:"Зараз",closeText:"Закрити",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional.vi={timeOnlyTitle:"Chọn giờ",timeText:"Thời gian",hourText:"Giờ",minuteText:"Phút",secondText:"Giây",millisecText:"Mili giây",microsecText:"Micrô giây",timezoneText:"Múi giờ",currentText:"Hiện thời",closeText:"Đóng",timeFormat:"HH:mm",timeSuffix:"",amNames:["SA","S"],pmNames:["CH","C"],isRTL:!1},a.timepicker.regional["zh-CN"]={timeOnlyTitle:"选择时间",timeText:"时间",hourText:"小时",minuteText:"分钟",secondText:"秒钟",millisecText:"毫秒",microsecText:"微秒",timezoneText:"时区",currentText:"现在时间",closeText:"关闭",timeFormat:"HH:mm",timeSuffix:"",amNames:["AM","A"],pmNames:["PM","P"],isRTL:!1},a.timepicker.regional["zh-TW"]={timeOnlyTitle:"選擇時分秒",timeText:"時間",hourText:"時",minuteText:"分",secondText:"秒",millisecText:"毫秒",microsecText:"微秒",timezoneText:"時區",currentText:"現在時間",closeText:"確定",timeFormat:"HH:mm",timeSuffix:"",amNames:["上午","AM","A"],pmNames:["下午","PM","P"],isRTL:!1}}(jQuery); \ No newline at end of file diff --git a/js/jquery-ui-timepicker-addon.js b/js/jquery-ui-timepicker-addon.js new file mode 100644 index 000000000..6e0e8f41c --- /dev/null +++ b/js/jquery-ui-timepicker-addon.js @@ -0,0 +1,2269 @@ +/*! jQuery Timepicker Addon - v1.6.1 - 2015-11-14 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2015 Trent Richardson; Licensed MIT */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', 'jquery-ui'], factory); + } else { + factory(jQuery); + } +}(function ($) { + + /* + * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded" + */ + $.ui.timepicker = $.ui.timepicker || {}; + if ($.ui.timepicker.version) { + return; + } + + /* + * Extend jQueryUI, get it started with our version number + */ + $.extend($.ui, { + timepicker: { + version: "1.6.1" + } + }); + + /* + * Timepicker manager. + * Use the singleton instance of this class, $.timepicker, to interact with the time picker. + * Settings for (groups of) time pickers are maintained in an instance object, + * allowing multiple different settings on the same page. + */ + var Timepicker = function () { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'HH:mm', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + microsecText: 'Microsecond', + timezoneText: 'Time Zone', + isRTL: false + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + timeOnlyShowDate: false, + showHour: null, + showMinute: null, + showSecond: null, + showMillisec: null, + showMicrosec: null, + showTimezone: null, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + stepMicrosec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + microsecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + microsecMax: 999, + minDateTime: null, + maxDateTime: null, + maxTime: null, + minTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + microsecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + altTimeFormat: null, + altSeparator: null, + altTimeSuffix: null, + altRedirectFocus: true, + pickerTimeFormat: null, + pickerTimeSuffix: null, + showTimepicker: true, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null, + controlType: 'slider', + oneLine: false, + defaultValue: null, + parse: 'strict', + afterInject: null + }; + $.extend(this._defaults, this.regional['']); + }; + + $.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + microsec_slider: null, + timezone_select: null, + maxTime: null, + minTime: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + microsec: 0, + timezone: null, + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + microsecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + microsecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + units: ['hour', 'minute', 'second', 'millisec', 'microsec'], + support: {}, + control: null, + + /* + * Override the default settings for all instances of the time picker. + * @param {Object} settings object - the new settings to use as defaults (anonymous object) + * @return {Object} the manager object + */ + setDefaults: function (settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + /* + * Create a new Timepicker instance + */ + _newInst: function ($input, opts) { + var tp_inst = new Timepicker(), + inlineSettings = {}, + fns = {}, + overrides, i; + + for (var attrName in this._defaults) { + if (this._defaults.hasOwnProperty(attrName)) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + } + + overrides = { + beforeShow: function (input, dp_inst) { + if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) { + return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst); + } + }, + onChangeMonthYear: function (year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + // tp_inst._updateDateTime(dp_inst); + if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) { + tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + } + }, + onClose: function (dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() !== '') { + tp_inst._updateDateTime(dp_inst); + } + if ($.isFunction(tp_inst._defaults.evnts.onClose)) { + tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst); + } + } + }; + for (i in overrides) { + if (overrides.hasOwnProperty(i)) { + fns[i] = opts[i] || this._defaults[i] || null; + } + } + + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, { + evnts: fns, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) { + return val.toUpperCase(); + }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) { + return val.toUpperCase(); + }); + + // detect which units are supported + tp_inst.support = detectSupport( + tp_inst._defaults.timeFormat + + (tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') + + (tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : '')); + + // controlType is string - key to our this._controls + if (typeof(tp_inst._defaults.controlType) === 'string') { + if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') { + tp_inst._defaults.controlType = 'select'; + } + tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType]; + } + // controlType is an object and must implement create, options, value methods + else { + tp_inst.control = tp_inst._defaults.controlType; + } + + // prep the timezone options + var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60, + 0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840]; + if (tp_inst._defaults.timezoneList !== null) { + timezoneList = tp_inst._defaults.timezoneList; + } + var tzl = timezoneList.length, tzi = 0, tzv = null; + if (tzl > 0 && typeof timezoneList[0] !== 'object') { + for (; tzi < tzl; tzi++) { + tzv = timezoneList[tzi]; + timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) }; + } + } + tp_inst._defaults.timezoneList = timezoneList; + + // set the default units + tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) : + ((new Date()).getTimezoneOffset() * -1); + tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin : + tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin : + tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin : + tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin : + tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec; + tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin : + tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (tp_inst._defaults.altField) { + tp_inst.$altInput = $(tp_inst._defaults.altField); + if (tp_inst._defaults.altRedirectFocus === true) { + tp_inst.$altInput.css({ + cursor: 'pointer' + }).focus(function () { + $input.trigger("focus"); + }); + } + } + + if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) { + tp_inst._defaults.minDate = new Date(); + } + if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) { + tp_inst._defaults.maxDate = new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) { + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + } + if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) { + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + } + if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) { + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + } + if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) { + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + } + tp_inst.$input.bind('focus', function () { + tp_inst._onFocus(); + }); + + return tp_inst; + }, + + /* + * add our sliders to the calendar + */ + _addTimePicker: function (dp_inst) { + var currDT = $.trim((this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val()); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + this._afterInject(); + }, + + /* + * parse the time string from input value or _setTime + */ + _parseTime: function (timeString, withDate) { + if (!this.inst) { + this.inst = $.datepicker._getInst(this.$input[0]); + } + + if (withDate || !this._defaults.timeOnly) { + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + try { + var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); + if (!parseRes.timeObj) { + return false; + } + $.extend(this, parseRes.timeObj); + } catch (err) { + $.timepicker.log("Error parsing the date/time string: " + err + + "\ndate/time string = " + timeString + + "\ntimeFormat = " + this._defaults.timeFormat + + "\ndateFormat = " + dp_dateFormat); + return false; + } + return true; + } else { + var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); + if (!timeObj) { + return false; + } + $.extend(this, timeObj); + return true; + } + }, + + /* + * Handle callback option after injecting timepicker + */ + _afterInject: function() { + var o = this.inst.settings; + if ($.isFunction(o.afterInject)) { + o.afterInject.call(this); + } + }, + + /* + * generate and inject html for timepicker into ui datepicker + */ + _injectTimePicker: function () { + var $dp = this.inst.dpDiv, + o = this.inst.settings, + tp_inst = this, + litem = '', + uitem = '', + show = null, + max = {}, + gridSize = {}, + size = null, + i = 0, + l = 0; + + // Prevent displaying twice + if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) { + var noDisplay = ' ui_tpicker_unit_hide', + html = '
' + '
' + o.timeText + '
' + + '
'; + + // Create the markup + for (i = 0, l = this.units.length; i < l; i++) { + litem = this.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); + gridSize[litem] = 0; + + html += '
' + o[litem + 'Text'] + '
' + + '
'; + + if (show && o[litem + 'Grid'] > 0) { + html += '
'; + + if (litem === 'hour') { + for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); + html += ''; + } + } + else { + for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + html += ''; + } + } + + html += '
' + tmph + '' + ((m < 10) ? '0' : '') + m + '
'; + } + html += '
'; + } + + // Timezone + var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; + html += '
' + o.timezoneText + '
'; + html += '
'; + + // Create the elements from string + html += '
'; + var $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend('
' + '
' + o.timeOnlyTitle + '
' + '
'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + // add sliders, adjust grids, add events + for (i = 0, l = tp_inst.units.length; i < l; i++) { + litem = tp_inst.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // add the slider + tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]); + + // adjust the grid and add click event + if (show && o[litem + 'Grid'] > 0) { + size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']); + $tp.find('.ui_tpicker_' + litem + ' table').css({ + width: size + "%", + marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"), + marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0', + borderCollapse: 'collapse' + }).find("td").click(function (e) { + var $t = $(this), + h = $t.html(), + n = parseInt(h.replace(/[^0-9]/g), 10), + ap = h.replace(/[^apm]/ig), + f = $t.data('for'); // loses scope, so we use data-for + + if (f === 'hour') { + if (ap.indexOf('p') !== -1 && n < 12) { + n += 12; + } + else { + if (ap.indexOf('a') !== -1 && n === 12) { + n = 0; + } + } + } + + tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n); + + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / gridSize[litem]) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + } // end if grid > 0 + } // end for loop + + // Add timezone options + this.timezone_select = $tp.find('.ui_tpicker_timezone').append('').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function (val, idx) { + return $("