Forráskód Böngészése

Support of date and time custom formats... continuing towards the beta !

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4019 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 9 éve
szülő
commit
ccef399cfa
32 módosított fájl, 515 hozzáadás és 457 törlés
  1. 13 3
      application/cmdbabstract.class.inc.php
  2. 13 6
      application/displayblock.class.inc.php
  3. 11 4
      application/itopwebpage.class.inc.php
  4. 90 295
      core/attributedef.class.inc.php
  5. 35 9
      core/bulkchange.class.inc.php
  6. 35 16
      core/csvbulkexport.class.inc.php
  7. 38 13
      core/datetimeformat.class.inc.php
  8. 8 2
      core/dbobjectsearch.class.php
  9. 23 14
      core/excelbulkexport.class.inc.php
  10. 6 6
      core/ormcaselog.class.inc.php
  11. 24 16
      core/pdfbulkexport.class.inc.php
  12. 67 6
      core/spreadsheetbulkexport.class.inc.php
  13. 4 4
      dictionaries/cs.dictionary.itop.core.php
  14. 4 4
      dictionaries/da.dictionary.itop.core.php
  15. 4 4
      dictionaries/de.dictionary.itop.core.php
  16. 4 4
      dictionaries/dictionary.itop.core.php
  17. 4 4
      dictionaries/es_cr.dictionary.itop.core.php
  18. 4 4
      dictionaries/fr.dictionary.itop.core.php
  19. 4 4
      dictionaries/hu.dictionary.itop.core.php
  20. 4 4
      dictionaries/it.dictionary.itop.core.php
  21. 4 4
      dictionaries/ja.dictionary.itop.core.php
  22. 4 4
      dictionaries/nl.dictionary.itop.core.php
  23. 4 4
      dictionaries/pt_br.dictionary.itop.core.php
  24. 4 4
      dictionaries/ru.dictionary.itop.core.php
  25. 4 4
      dictionaries/tr.dictionary.itop.core.php
  26. 4 4
      dictionaries/zh.dictionary.itop.core.php
  27. 8 7
      pages/csvimport.php
  28. 0 1
      sources/autoload.php
  29. 47 1
      sources/form/field/datetimefield.class.inc.php
  30. 1 0
      sources/renderer/bootstrap/bsformrenderer.class.inc.php
  31. 35 0
      sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php
  32. 5 2
      webservices/import.php

+ 13 - 3
application/cmdbabstract.class.inc.php

@@ -1710,8 +1710,9 @@ EOF
 				$aEventsList[] ='validate';
 				$aEventsList[] ='keyup';
 				$aEventsList[] ='change';
+				$sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"';
 
-				$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
+				$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
 				break;
 
 				case 'DateTime':
@@ -1719,7 +1720,8 @@ EOF
 				$aEventsList[] ='keyup';
 				$aEventsList[] ='change';
 
-				$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"15\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
+				$sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"';
+				$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"15\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationSpan}{$sReloadSpan}";
 				break;
 
 				case 'Duration':
@@ -3159,7 +3161,15 @@ EOF
 				$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
 				if ($value != null)
 				{
-					$value = AttributeDateTime::Parse($value, $oAttDef->GetFormat());
+					$oDate = $oAttDef->GetFormat()->Parse($value);
+					if ($oDate instanceof DateTime)
+					{
+						$value = $oDate->format($oAttDef->GetInternalFormat());
+					}
+					else
+					{
+						$value = null;
+					}
 				}
 			}
 			else

+ 13 - 6
application/displayblock.class.inc.php

@@ -319,13 +319,16 @@ class DisplayBlock
 				{
 					$externalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data');
 					$condition = null;
+					$bParseSearchString = true;
 					if (isset($aExtraParams[$sFilterCode]))
 					{
+						$bParseSearchString = false;
 						$condition = $aExtraParams[$sFilterCode];
 					}
 					if ($bDoSearch && $externalFilterValue != "")
 					{
 						// Search takes precedence over context params...
+						$bParseSearchString = true;
 						unset($aExtraParams[$sFilterCode]);
 						if (!is_array($externalFilterValue))
 						{
@@ -350,7 +353,7 @@ class DisplayBlock
 							$sOpCode = 'IN';
 						}
 
-						$this->AddCondition($sFilterCode, $condition, $sOpCode);
+						$this->AddCondition($sFilterCode, $condition, $sOpCode, $bParseSearchString);
 					}
 				}
 				if ($bDoSearch)
@@ -882,14 +885,16 @@ EOF
 			$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
 			$sGroupByExpr = isset($aExtraParams['group_by_expr']) ? '&params[group_by_expr]='.$aExtraParams['group_by_expr'] : '';
 			$sFilter = $this->m_oFilter->serialize();
+			$oContext = new ApplicationContext();
+			$sContextParam = $oContext->GetForLink();
 
 			if (isset($aExtraParams['group_by_label']))
 			{
-				$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[group_by_label]={$aExtraParams['group_by_label']}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter));
+				$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[group_by_label]={$aExtraParams['group_by_label']}&params[chart_type]=$sChartType&params[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
 			}
 			else
 			{
-				$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter));
+				$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[currentId]=$sId{$iChartCounter}&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
 			}
 			
 			$sType = ($sChartType == 'pie') ? 'pie' : 'bar';
@@ -927,6 +932,8 @@ EOF
 				$aGroupBy['grouped_by_1'] = $oGroupByExp;
 				$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true);
 				$aRes = CMDBSource::QueryToArray($sSql);
+				$oContext = new ApplicationContext();
+				$sContextParam = $oContext->GetForLink();
 
 				$aGroupBy = array();
 				$aLabels = array();
@@ -946,7 +953,7 @@ EOF
 					$oSubsetSearch = $this->m_oFilter->DeepClone();
 					$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
 					$oSubsetSearch->AddConditionExpression($oCondition);
-					$aURLs[] = utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html&filter=".urlencode($oSubsetSearch->serialize());
+					$aURLs[] = utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html&filter=".urlencode($oSubsetSearch->serialize()).'&'.$sContextParam;
 				}
 				$sJSURLs = json_encode($aURLs);
 			}
@@ -1059,7 +1066,7 @@ EOF
 	 * Add a condition (restriction) to the current DBSearch on which the display block is based
 	 * taking into account the hierarchical keys for which the condition is based on the 'below' operator
 	 */
-	protected function AddCondition($sFilterCode, $condition, $sOpCode = null)
+	protected function AddCondition($sFilterCode, $condition, $sOpCode = null, $bParseSearchString = false)
 	{
 		// Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations)
 		// Moreover, it keeps the query as simple as possible
@@ -1114,7 +1121,7 @@ EOF
 		// In all other cases, just add the condition directly
 		if (!$bConditionAdded)
 		{
-			$this->m_oFilter->AddCondition($sFilterCode, $condition); // Use the default 'loose' operator
+			$this->m_oFilter->AddCondition($sFilterCode, $condition, null, $bParseSearchString); // Use the default 'loose' operator
 		}
 	}
 	

+ 11 - 4
application/itopwebpage.class.inc.php

@@ -155,9 +155,10 @@ 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())));
+		$sJSDateFormat = json_encode(AttributeDate::GetFormat()->ToDatePicker());
+		$sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat();
+		$oTimeFormat = new DateTimeFormat($sTimeFormat);
+		$sJSTimeFormat = json_encode($oTimeFormat->ToDatePicker());
 		$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
 		$sJSOk = json_encode(Dict::S('UI:Button:Ok'));
 		
@@ -423,14 +424,20 @@ EOF
 			// time picker options	
 			timeFormat: $sJSTimeFormat,
 			controlType: 'select',
+			closeText: $sJSOk
+	});
+	
+	if ($sJSLangShort != 'en')
+	{
+		$(".datetime-pick").datetimepicker('option', {
 			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()

+ 90 - 295
core/attributedef.class.inc.php

@@ -34,6 +34,7 @@ require_once('htmlsanitizer.class.inc.php');
 require_once(APPROOT.'sources/autoload.php');
 require_once('customfieldshandler.class.inc.php');
 require_once('ormcustomfieldsvalue.class.inc.php');
+require_once('datetimeformat.class.inc.php');
 // This should be changed to a use when we go full-namespace
 require_once(APPROOT . 'sources/form/validator/validator.class.inc.php');
 require_once(APPROOT . 'sources/form/validator/notemptyextkeyvalidator.class.inc.php');
@@ -298,6 +299,15 @@ abstract class AttributeDefinition
 		return $this->MakeRealValue($sProposedValue, null);
 	}
 
+	/**
+	 * Parses a search string coming from user input
+	 * @param string $sSearchString
+	 * @return string
+	 */
+	public function ParseSearchString($sSearchString)
+	{
+		return $sSearchString;
+	}
 	public function GetLabel_Obsolete()
 	{
 		// Written for compatibility with a data model written prior to version 0.9.1
@@ -3537,17 +3547,16 @@ class AttributeMetaEnum extends AttributeEnum
  */
 class AttributeDateTime extends AttributeDBField
 {
-	static $sDateTimeFormat = null;
-	static $sTimeFormat = null;
+	static $oFormat = null;
 	
 	static public function GetFormat()
 	{
-		if (self::$sDateTimeFormat == null)
+		if (self::$oFormat == null)
 		{
 			static::LoadFormatFromConfig();		
 		}
-		return self::$sDateTimeFormat;
-	}
+		return self::$oFormat;
+	}	
 	
 	/**
 	 * Load the 3 settings: date format, time format and data_time format from the configuration
@@ -3560,11 +3569,10 @@ class AttributeDateTime extends AttributeDBField
 		$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);
+		$sFullFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat);
 		
-		self::SetFormat($sFormat);
-		self::SetTimeFormat($sTimeFormat);
-		AttributeDate::SetFormat($sDateFormat);		
+		self::SetFormat(new DateTimeFormat($sFullFormat));
+		AttributeDate::SetFormat(new DateTimeFormat($sDateFormat));		
 	}
 	
 	/**
@@ -3585,18 +3593,9 @@ class AttributeDateTime extends AttributeDBField
 		return 'Y-m-d H:i:s';
 	}
 	
-	static public function SetFormat($sDateTimeFormat)
-	{
-		self::$sDateTimeFormat = $sDateTimeFormat;
-	}
-
-	static public function GetTimeFormat()
+	static public function SetFormat(DateTimeFormat $oDateTimeFormat)
 	{
-		if (self::$sTimeFormat == null)
-		{
-			static::LoadFormatFromConfig();		
-		}
-		return self::$sTimeFormat;
+		self::$oFormat = $oDateTimeFormat;
 	}
 	
 	static public function GetSQLTimeFormat()
@@ -3604,286 +3603,47 @@ class AttributeDateTime extends AttributeDBField
 		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
+	 * Parses a search string coming from user input
+	 * @param string $sSearchString
 	 * @return string
 	 */
-	public static function Parse($sDate, $sFormat)
+	public function ParseSearchString($sSearchString)
 	{
-		if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00'))
+		try
 		{
-			return null;	
+			$oDateTime = $this->GetFormat()->Parse($sSearchString);
+			$sSearchString = $oDateTime->format($this->GetInternalFormat());
 		}
-		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++)
+		catch(Exception $e)
 		{
-			if (($sFormat[$i] == '\\'))
-			{
-				$bEscaping = true;
-				continue;
-			}
-			
-			if ($bEscaping)
-			{
-				$sResult .= "'{$sFormat[$i]}'";
-				$bEscaping = false;
-			}
-			else if(array_key_exists($sFormat[$i], $aMappings))
+			$sFormatString = '!'.(string)AttributeDate::GetFormat(); // BEWARE: ! is needed to set non-parsed fields to zero !!!
+			$oDateTime = DateTime::createFromFormat($sFormatString, $sSearchString); 
+			if ($oDateTime !== false)
 			{
-				// 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];
+				$sSearchString = $oDateTime->format($this->GetInternalFormat());
 			}
 		}
-		
-		return $sResult;
+		return $sSearchString;
 	}
 	
-	/**
-	 * 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()
+	static public function GetFormFieldClass()
 	{
-		$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);
+		return '\\Combodo\\iTop\\Form\\Field\\DateTimeField';
 	}
 	
-	public static function InitTableSorter($oPage, $sRuleName)
-	{
-		$aDef = static::GetTableSorterRule();
-		
-		$oPage->add_ready_script(
-<<<EOF
-    $.tablesorter.addParser({
-        id: "$sRuleName",
-        is: function (s) {
-            return /^({$aDef['regexpr']})$/.test(s);
-        }, format: function (s) {
-            s = s.replace(/{$aDef['regexpr']}/, "{$aDef['replacement']}");
-            return $.tablesorter.formatFloat(new Date(s).getTime());
-        }, type: "numeric"
-    });
-EOF
-		);
-	}
-*/	
 	/**
-	 * Get the regular expression to (approximately) validate a date/time for the current format
-	 * @param array $aOrder
-	 * @return string The regular expression in PCRE syntax
+	 * Override to specify Field class
+	 *
+	 * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare.
 	 */
-	static public function GetRegExpr(&$aOrder = null)
+	public function MakeFormField(DBObject $oObject, $oFormField = null)
 	{
-		$sFormat = static::GetFormat();
-		$aMappings = static::GetFormatMapping();
-		$sSpecialChars = '.?*$^()[]/'; // Characters having a special meaning in a regular expression, must be escaped by prepending a backslash
-		$sResult = '^';
-
-		$bEscaping = false;
-		for($i=0; $i < strlen($sFormat); $i++)
-		{
-			if (($sFormat[$i] == '\\') && !$bEscaping)
-			{
-				$bEscaping = true;
-				continue;
-			}
-			
-			if (!$bEscaping && array_key_exists($sFormat[$i], $aMappings))
-			{
-				// Not a litteral value, must be replaced by its regular expression pattern
-				$sResult .= $aMappings[$sFormat[$i]]['regexpr'];
-				if ($aOrder !== null)
-				{
-					$aOrder[] = $aMappings[$sFormat[$i]]['usage'];
-				}
-			}
-			else
-			{
-				// Litteral value, take care of special characters in a RegExpr
-				if (strpos($sSpecialChars, $sFormat[$i]) !== false)
-				{
-					$sResult .= '\\'.$sFormat[$i];
-				}
-				else
-				{
-					// Normal char with no special meaning
-					$sResult .= $sFormat[$i];
-				}
-			}
-
-			if ($bEscaping)
-			{
-				$bEscaping = false;
-			}
-		}
-		$sResult .= '$';
+		$oFormField = parent::MakeFormField($oObject, $oFormField);
+		$oFormField->SetPHPDateTimeFormat($this->GetFormat());
+		$oFormField->SetJSDateTimeFormat($this->GetMomentJSFormat());
 		
-		return $sResult;
+		return $oFormField;
 	}
 
 	static public function ListExpectedParams()
@@ -3897,7 +3657,11 @@ EOF
 
 	public function GetEditValue($sValue, $oHostObj = null)
 	{
-		return (string)static::Format($sValue, static::GetFormat());
+		return (string)static::GetFormat()->format($sValue);
+	}	
+	public function GetValueLabel($sValue, $oHostObj = null)
+	{
+		return (string)static::GetFormat()->format($sValue);
 	}	
 	
 	protected function GetSQLCol($bFullSpec = false) {return "DATETIME";}
@@ -3916,7 +3680,7 @@ EOF
 
 	public function GetValidationPattern()
 	{
-		return static::GetRegExpr();
+		return static::GetFormat()->ToRegExpr();
 	}
 
 	public function GetBasicFilterOperators()
@@ -3990,7 +3754,7 @@ EOF
 			return $proposedValue;
 		}
 
-		return date(self::GetInternalFormat(), $proposedValue);
+		return date(static::GetInternalFormat(), $proposedValue);
 	}
 
 	public function ScalarToSQL($value)
@@ -4009,7 +3773,7 @@ EOF
 
 	public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
 	{
-		return Str::pure2html(static::Format($value, static::GetFormat()));
+		return Str::pure2html(static::GetFormat()->format($value));
 	}
 
 	public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
@@ -4023,13 +3787,13 @@ EOF
 		{
 			return '';
 		}
-		else if (self::GetFormat() !== self::GetInternalFormat())
+		else if ((string)static::GetFormat() !== static::GetInternalFormat())
 		{
 			// Format conversion
 			$oDate = new DateTime($sValue);
 			if ($oDate !== false)
 			{
-				$sValue = $oDate->format(self::GetFormat());
+				$sValue = static::GetFormat()->format($oDate);
 			}
 		}
 		$sFrom = array("\r\n", $sTextQualifier);
@@ -4047,7 +3811,7 @@ EOF
 	 * @param Hash $aParams Values of the query parameters
 	 * @return Expression The search condition to be added (AND) to the current search
 	 */
-	public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams)
+	public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false)
 	{
 		// Possible smart patterns
 		$aPatterns = array(
@@ -4075,13 +3839,27 @@ EOF
 			
 			$sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1';
 			$oRightExpr = new VariableExpression($sParamName1);
+			if ($bParseSearchString)
+			{
+				$aParams[$sParamName1] = $this->ParseSearchString($aMatches[1]);
+			}
+			else
+			{
 			$aParams[$sParamName1] = $aMatches[1];
+			}
 			$oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr);
 
 			$sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2';
 			$oRightExpr = new VariableExpression($sParamName2);
 			$sOperator = $this->GetBasicFilterLooseOperator();
+			if ($bParseSearchString)
+			{
+				$aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]);
+			}
+			else
+			{
 			$aParams[$sParamName2] = $aMatches[2];
+			}
 			$oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr);
 			
 			$oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2);
@@ -4094,18 +3872,35 @@ EOF
 			$sSQLOperator = $aPatterns[$sPatternFound]['operator'];
 			$sParamName = $oField->GetParent().'_'.$oField->GetName();
 			$oRightExpr = new VariableExpression($sParamName);
+			if ($bParseSearchString)
+			{
+				$aParams[$sParamName] = $this->ParseSearchString($aMatches[1]);
+			}
+			else
+			{
 			$aParams[$sParamName] = $aMatches[1];
+			}
 			$oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr);
 			
 			break;
 						
 			default:
-			$oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams);
+			$oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams, $bParseSearchString);
 
 		}
 
 		return $oNewCondition;
 	}
+
+
+	public function GetHelpOnSmartSearch()
+	{
+		$sDict = parent::GetHelpOnSmartSearch();
+		
+		$oFormat = static::GetFormat();
+		$sExample = $oFormat->Format(new DateTime('2015-07-19 18:40:00'));
+		return vsprintf($sDict, array($oFormat->ToPlaceholder(), $sExample));
+	}	
 }
 
 /**
@@ -4188,20 +3983,20 @@ class AttributeDuration extends AttributeInteger
  */
 class AttributeDate extends AttributeDateTime
 {
-	static $sDateFormat = null;
+	static $oDateFormat = null;
 	
 	static public function GetFormat()
 	{
-		if (self::$sDateFormat == null)
+		if (self::$oDateFormat == null)
 		{			
 			AttributeDateTime::LoadFormatFromConfig();		
 		}
-		return self::$sDateFormat;
+		return self::$oDateFormat;
 	}
 
-	static public function SetFormat($sDateFormat)
+	static public function SetFormat(DateTimeFormat $oDateFormat)
 	{
-		self::$sDateFormat = $sDateFormat;
+		self::$oDateFormat = $oDateFormat;
 	}
 
 	/**
@@ -4251,7 +4046,7 @@ class AttributeDeadline extends AttributeDateTime
 		if ($value !== null)
 		{
 			$iValue = AttributeDateTime::GetAsUnixSeconds($value);
-			$sDate = $value;
+			$sDate = AttributeDateTime::Format($value);
 			$difference = $iValue - time();
 	
 			if ($difference >= 0)

+ 35 - 9
core/bulkchange.class.inc.php

@@ -554,7 +554,15 @@ class BulkChange
 				else
 				{
 					// By default... nothing happens
-					$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
+					$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
+					if ($oAttDef instanceof AttributeDateTime)
+					{
+						$aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol]));
+					}
+					else
+					{
+						$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
+					}
 				}
 			}
 		}
@@ -795,6 +803,11 @@ class BulkChange
 
 		if (!is_null($this->m_sDateFormat) && (strlen($this->m_sDateFormat) > 0))
 		{
+			$sDateTimeFormat = $this->m_sDateFormat; // the specified format is actually the date AND time format
+			$oDateTimeFormat = new DateTimeFormat($sDateTimeFormat);
+			$sDateFormat = $oDateTimeFormat->ToDateFormat();
+			AttributeDateTime::SetFormat($oDateTimeFormat);
+			AttributeDate::SetFormat(new DateTimeFormat($sDateFormat));
 			// Translate dates from the source data
 			//
 			foreach ($this->m_aAttList as $sAttCode => $iCol)
@@ -802,21 +815,34 @@ class BulkChange
 				if ($sAttCode == 'id') continue;
 				
 				$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
-				if ($oAttDef instanceof AttributeDateTime)
+				if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
 				{
 					foreach($this->m_aData as $iRow => $aRowData)
 					{
-						$oDate = DateTime::createFromFormat($this->m_sDateFormat, $this->m_aData[$iRow][$iCol]);
-						if ($oDate !== false)
+						$sFormat = $sDateTimeFormat;
+						$sValue = $this->m_aData[$iRow][$iCol];
+						if (!empty($sValue))
 						{
-							$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
-							$this->m_aData[$iRow][$iCol] = $sNewDate;
+							if ($oAttDef instanceof AttributeDate)
+							{
+								$sFormat = $sDateFormat;
+							}
+							$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
+							if ($oDate !== false)
+							{
+								$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
+								$this->m_aData[$iRow][$iCol] = $sNewDate;
+							}
+							else
+							{
+								// Leave the cell unchanged
+								$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
+								$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
+							}
 						}
 						else
 						{
-							// Leave the cell unchanged
-							$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
-							$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, $this->m_aData[$iRow][$iCol], Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
+							$this->m_aData[$iRow][$iCol] = '';
 						}
 					}
 				}

+ 35 - 16
core/csvbulkexport.class.inc.php

@@ -34,7 +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'");
+		$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format used in the user interface). e.g. 'Y-m-d H:i:s'");
 	}
 
 	public function ReadParameters()
@@ -59,14 +59,22 @@ 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')
+		$sDateFormatRadio = utils::ReadParam('date_format_radio', '');
+		switch($sDateFormatRadio)
 		{
-			$this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat();
-		}
-		else
-		{
-			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
+			case 'default':
+			// Export from the UI => format = same as is the UI
+			$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
+			break;
+			
+			case 'custom':
+			// Custom format specified from the UI
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+			break;
+			
+			default:
+			// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
 		}
 	}
 
@@ -176,12 +184,12 @@ class CSVBulkExport extends TabularBulkExport
 				$oP->add('<input type="checkbox" id="csv_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="csv_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
 				$oP->add('</td><td style="vertical-align:top">');
 				
-				$sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
-				$sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : '';
-				$sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+				$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
 				$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
-				$sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
-				$sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
+				$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
+				$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
 				$oP->add('<input type="radio" id="csv_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="csv_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
 				$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
 				$oP->add('<input type="radio" id="csv_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="csv_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
@@ -267,6 +275,17 @@ EOF
 		$sData = '';
 		$iPreviousTimeLimit = ini_get('max_execution_time');
 		$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
+		$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
+		$oPrevDateTimeFormat = AttributeDateTime::GetFormat();
+		$oPrevDateFormat = AttributeDate::GetFormat();
+		if ($sExportDateTimeFormat !== (string)$oPrevDateTimeFormat)
+		{
+			// Change date & time formats
+			$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
+			$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
+			AttributeDateTime::SetFormat($oDateTimeFormat);
+			AttributeDate::SetFormat($oDateFormat);
+		}
 		while($aRow = $oSet->FetchAssoc())
 		{
 			set_time_limit($iLoopTimeLimit);
@@ -287,10 +306,7 @@ EOF
 							break;
 								
 						default:
-							$sPrevFormat = AttributeDateTime::GetFormat();
-							AttributeDateTime::SetFormat($this->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')
@@ -307,6 +323,9 @@ EOF
 			$sData .= implode($this->aStatusInfo['separator'], $aData)."\n";
 			$iCount++;
 		}
+		// Restore original date & time formats
+		AttributeDateTime::SetFormat($oPrevDateTimeFormat);
+		AttributeDate::SetFormat($oPrevDateFormat);
 		set_time_limit($iPreviousTimeLimit);
 		$this->aStatusInfo['position'] += $this->iChunkSize;
 		if ($this->aStatusInfo['total'] == 0)

+ 38 - 13
core/datetimeformat.class.inc.php

@@ -40,7 +40,15 @@ class DateTimeFormat
 	 */
 	public function __construct($sPHPFormat)
 	{
-		$this->sPHPFormat = $sPHPFormat;
+		$this->sPHPFormat = (string)$sPHPFormat;
+	}
+	
+	/**
+	 * @return string
+	 */
+	public function __toString()
+	{
+		return $this->sPHPFormat;
 	}
 	
 	/**
@@ -231,14 +239,13 @@ class DateTimeFormat
 	 */
 	public function ToPlaceholder($sFormat = null)
 	{
-		$sFormat = ($sFormat == null) ? static::GetFormat() : $sFormat;
 		$aMappings = static::GetFormatMapping();
 		$sResult = '';
 		
 		$bEscaping = false;
-		for($i=0; $i < strlen($sFormat); $i++)
+		for($i=0; $i < strlen($this->sPHPFormat); $i++)
 		{
-			if (($sFormat[$i] == '\\'))
+			if (($this->sPHPFormat[$i] == '\\'))
 			{
 				$bEscaping = true;
 				continue;
@@ -246,41 +253,41 @@ class DateTimeFormat
 			
 			if ($bEscaping)
 			{
-				$sResult .= $sFormat[$i]; // No need to escape characters in the placeholder
+				$sResult .= $this->sPHPFormat[$i]; // No need to escape characters in the placeholder
 				$bEscaping = false;
 			}
-			else if(array_key_exists($sFormat[$i], $aMappings))
+			else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
 			{
 				// Not a litteral value, must be replaced by Dict equivalent
-				$sResult .= Dict::S('Core:DateTime:Placeholder_'.$sFormat[$i]);
+				$sResult .= Dict::S('Core:DateTime:Placeholder_'.$this->sPHPFormat[$i]);
 			}
 			else
 			{
 
 				// Normal char with no special meaning
-				$sResult .= $sFormat[$i];
+				$sResult .= $this->sPHPFormat[$i];
 			}
 		}
 		
 		return $sResult;
 	}
-	
+
 	/**
-	 * Produces the Date format string by extracting only the date part of the date and time format string
+	 * Produces a subformat (Date or Time) by extracting the part of the whole DateTime format containing only the given placeholders
 	 * @return string
 	 */
-	public function ToDateFormat()
+	protected function ToSubFormat($aPlaceholders)
 	{
 		$aDatePlaceholders = array('Y', 'y', 'd', 'j', 'm', 'n');
 		$iStart = 999;
 		$iEnd = 0;
 		
-		foreach($aDatePlaceholders as $sChar)
+		foreach($aPlaceholders as $sChar)
 		{
 			$iPos = strpos($this->sPHPFormat, $sChar);
 			if ($iPos !== false)
 			{
-				if (($iPos > 0) && ($aDatePlaceholders[$iPos-1] == '\\'))
+				if (($iPos > 0) && ($this->sPHPFormat[$iPos-1] == '\\'))
 				{
 					// The placeholder is actually escaped, it's a litteral character, ignore it
 					continue;
@@ -294,6 +301,24 @@ class DateTimeFormat
 	}
 	
 	/**
+	 * Produces the Date format string by extracting only the date part of the date and time format string
+	 * @return string
+	 */
+	public function ToDateFormat()
+	{
+		return $this->ToSubFormat(array('Y', 'y', 'd', 'j', 'm', 'n'));
+	}
+	
+	/**
+	 * Produces the Time format string by extracting only the time part of the date and time format string
+	 * @return string
+	 */
+	public function ToTimeFormat()
+	{
+		return $this->ToSubFormat(array('H', 'h', 'G', 'g', 'i', 's'));
+	}
+	
+	/**
 	 * Get the regular expression to (approximately) validate a date/time for the current format
 	 * The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
 	 * @return string The regular expression in PCRE syntax

+ 8 - 2
core/dbobjectsearch.class.php

@@ -268,7 +268,7 @@ class DBObjectSearch extends DBSearch
 		$this->AddConditionExpression($oNewCondition);
 	}
 
-	public function AddCondition($sFilterCode, $value, $sOpCode = null)
+	public function AddCondition($sFilterCode, $value, $sOpCode = null, $bParseSeachString = false)
 	{
 		MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
 		$oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode);
@@ -283,12 +283,18 @@ class DBObjectSearch extends DBSearch
 			else
 			{
 				$oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode);
-				$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams);
+				$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams, $bParseSeachString);
 				$this->AddConditionExpression($oNewCondition);
 				return;
 			}
 		}
 		MyHelpers::CheckKeyInArray('operator', $sOpCode, $oFilterDef->GetOperators());
+		// Parse search strings if needed and if the filter code corresponds to a valid attcode
+		if($bParseSeachString && MetaModel::IsValidAttCode($this->GetClass(), $sFilterCode))
+		{
+			$oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode);
+			$value = $oAttDef->ParseSearchString($value);
+		}
 
 		// Preserve backward compatibility - quick n'dirty way to change that API semantic
 		//

+ 23 - 14
core/excelbulkexport.class.inc.php

@@ -47,7 +47,7 @@ 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'");
+		$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
 	}
 
 	public function ReadParameters()
@@ -55,14 +55,22 @@ class ExcelBulkExport extends TabularBulkExport
 		parent::ReadParameters();
 		$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
 			
-		$sDateFormatRadio = utils::ReadParam('date_format_radio', 'custom');
-		if ($sDateFormatRadio == 'default')
+		$sDateFormatRadio = utils::ReadParam('date_format_radio', '');
+		switch($sDateFormatRadio)
 		{
-			$this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat();
-		}
-		else
-		{
-			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
+			case 'default':
+			// Export from the UI => format = same as is the UI
+			$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
+			break;
+			
+			case 'custom':
+			// Custom format specified from the UI
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+			break;
+			
+			default:
+			// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
 		}
 	}
 	
@@ -89,12 +97,12 @@ class ExcelBulkExport extends TabularBulkExport
 				
 				$oP->add('</td><td style="vertical-align:top">');
 				
-				$sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
-				$sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : '';
-				$sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+				$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
 				$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
-				$sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
-				$sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
+				$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
+				$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
 				$oP->add('<input type="radio" id="excel_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="excel_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
 				$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
 				$oP->add('<input type="radio" id="excel_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="excel_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
@@ -304,7 +312,8 @@ EOF
 			
 		$fStartExcel = microtime(true);
 		$writer = new XLSXWriter();
-		$writer->setDateTimeFormat(AttributeDateTime::GetExcelFormat($this->aStatusInfo['date_format']));
+		$oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']);
+		$writer->setDateTimeFormat($oDateTimeFormat->ToExcel());
 		$writer->setAuthor(UserRights::GetUserFriendlyName());
 		$aHeaderTypes = array();
 		$aHeaderNames = array();

+ 6 - 6
core/ormcaselog.class.inc.php

@@ -234,14 +234,14 @@ class ormCaseLog {
 			if (is_int($aIndex[$index]['date']))
 			{
 				// Unix timestamp
-				$sDate = date(AttributeDateTime::GetFormat(), $aIndex[$index]['date']);
+				$sDate = date((string)AttributeDateTime::GetFormat(), $aIndex[$index]['date']);
 			}
 			elseif (is_object($aIndex[$index]['date']))
 			{
 				if (version_compare(phpversion(), '5.3.0', '>='))
 				{
 					// DateTime
-					$sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat());
+					$sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
 				}
 				else
 				{
@@ -322,14 +322,14 @@ class ormCaseLog {
 			if (is_int($aIndex[$index]['date']))
 			{
 				// Unix timestamp
-				$sDate = date(AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
+				$sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
 			}
 			elseif (is_object($aIndex[$index]['date']))
 			{
 				if (version_compare(phpversion(), '5.3.0', '>='))
 				{
 					// DateTime
-					$sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat());
+					$sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
 				}
 				else
 				{
@@ -425,14 +425,14 @@ class ormCaseLog {
 			if (is_int($aIndex[$index]['date']))
 			{
 				// Unix timestamp
-				$sDate = date(AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
+				$sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
 			}
 			elseif (is_object($aIndex[$index]['date']))
 			{
 				if (version_compare(phpversion(), '5.3.0', '>='))
 				{
 					// DateTime
-					$sDate = $aIndex[$index]['date']->format(AttributeDateTime::GetFormat());
+					$sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
 				}
 				else
 				{

+ 24 - 16
core/pdfbulkexport.class.inc.php

@@ -31,7 +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'");
+		$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
 	}
 
 	public function EnumFormParts()
@@ -59,12 +59,12 @@ class PDFBulkExport extends HTMLBulkExport
 				
 				$oP->add('</td><td style="vertical-align:top">');
 				
-				$sDateTimeFormat = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
-				$sDefaultChecked = ($sDateTimeFormat == AttributeDateTime::GetFormat()) ? ' checked' : '';
-				$sCustomChecked = ($sDateTimeFormat !== AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+				$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
 				$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
-				$sDefaultFormat = htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
-				$sExample = htmlentities(date(AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
+				$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
+				$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
 				$oP->add('<input type="radio" id="pdf_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="pdf_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
 				$sFormatInput = '<input type="text" size="15" name="date_format" id="pdf_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
 				$oP->add('<input type="radio" id="pdf_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="pdf_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
@@ -114,14 +114,22 @@ EOF
 		$this->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')
+		$sDateFormatRadio = utils::ReadParam('date_format_radio', '');
+		switch($sDateFormatRadio)
 		{
-			$this->aStatusInfo['date_format'] = AttributeDateTime::GetFormat();
-		}
-		else
-		{
-			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', AttributeDateTime::GetFormat(), true, 'raw_data');
+			case 'default':
+			// Export from the UI => format = same as is the UI
+			$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
+			break;
+			
+			case 'custom':
+			// Custom format specified from the UI
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+			break;
+			
+			default:
+			// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
 		}
 	}
 
@@ -141,10 +149,10 @@ EOF
 
 	public function GetNextChunk(&$aStatus)
 	{
-		$sPrevFormat = AttributeDateTime::GetFormat();
-		AttributeDateTime::SetFormat($this->aStatusInfo['date_format']);
+		$oPrevFormat = AttributeDateTime::GetFormat();
+		AttributeDateTime::SetFormat(new DateTimeFormat($this->aStatusInfo['date_format']));
 		$sData = parent::GetNextChunk($aStatus);
-		AttributeDateTime::SetFormat($sPrevFormat);
+		AttributeDateTime::SetFormat($oPrevFormat);
 		$hFile = @fopen($this->aStatusInfo['tmp_file'], 'ab');
 		if ($hFile === false)
 		{

+ 67 - 6
core/spreadsheetbulkexport.class.inc.php

@@ -30,6 +30,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
 		$oP->p(" * spreadsheet format options:");
 		$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
 		$oP->p(" *\tno_localize: (optional) pass 1 to retrieve the raw (untranslated) values for enumerated fields. Default: 0.");
+		$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
 	}
 
 	public function EnumFormParts()
@@ -51,15 +52,59 @@ class SpreadsheetBulkExport extends TabularBulkExport
 				$oP->add('<table>');
 				$oP->add('<tr>');
 				$oP->add('<td><input type="checkbox" id="spreadsheet_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="spreadsheet_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label></td>');
+				
+				$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+				$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
+				$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
+				
+				$oP->add('<td>');
+				$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
+				$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
+				$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
+				$oP->add('<input type="radio" id="spreadsheet_date_time_format_default" name="date_format_radio" value="default"'.$sDefaultChecked.'><label for="spreadsheet_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
+				$sFormatInput = '<input type="text" size="15" name="date_format" id="spreadsheet_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
+				$oP->add('<input type="radio" id="spreadsheet_date_time_format_custom" name="date_format_radio" value="custom"'.$sCustomChecked.'><label for="spreadsheet_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
+				$oP->add('</td>');
+				
 				$oP->add('</tr>');
 				$oP->add('</table>');
 				$oP->add('</fieldset>');
+				$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
+				$oP->add_ready_script(
+<<<EOF
+$('#spreadsheet_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
+$('#spreadsheet_custom_date_time_format').on('click', function() { $('#spreadsheet_date_time_format_custom').prop('checked', true); });
+EOF
+				);
 				break;
 				
 			default:
 				return parent:: DisplayFormPart($oP, $sPartId);
 		}
 	}
+
+	public function ReadParameters()
+	{
+		parent::ReadParameters();
+
+		$sDateFormatRadio = utils::ReadParam('date_format_radio', '');
+		switch($sDateFormatRadio)
+		{
+			case 'default':
+			// Export from the UI => format = same as is the UI
+			$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
+			break;
+			
+			case 'custom':
+			// Custom format specified from the UI
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
+			break;
+			
+			default:
+			// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
+			$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
+		}
+	}
 	
 	protected function GetSampleData($oObj, $sAttCode)
 	{
@@ -89,6 +134,10 @@ class SpreadsheetBulkExport extends TabularBulkExport
 				{
 					$sRet = $oObj->GetAsHTML($sAttCode);
 				}
+				elseif ($oAttDef instanceof AttributeCustomFields)
+				{
+					$sRet = $oObj->GetAsHTML($sAttCode);
+				}
 				else
 				{
 					if ($this->bLocalizeOutput)
@@ -97,7 +146,7 @@ class SpreadsheetBulkExport extends TabularBulkExport
 					}
 					else
 					{
-						$sRet = htmlentities($value, ENT_QUOTES, 'UTF-8');
+						$sRet = htmlentities((string)$value, ENT_QUOTES, 'UTF-8');
 					}
 				}
 		}
@@ -164,7 +213,13 @@ class SpreadsheetBulkExport extends TabularBulkExport
 		$oSet = new DBObjectSet($this->oSearch);
 		$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
 		$this->OptimizeColumnLoad($oSet);
-
+		
+		$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
+		// Date & time formats
+		$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
+		$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
+		$oTimeFormat = new DateTimeFormat($oDateTimeFormat->ToTimeFormat());
+		
 		$iCount = 0;
 		$sData = '';
 		$iPreviousTimeLimit = ini_get('max_execution_time');
@@ -199,10 +254,16 @@ class SpreadsheetBulkExport extends TabularBulkExport
 					$oFinalAttDef = $oAttDef->GetFinalAttDef();
 					if (get_class($oFinalAttDef) == 'AttributeDateTime')
 					{
-						$iDate = AttributeDateTime::GetAsUnixSeconds($oObj->Get($sAttCode));
-						$sData .= '<td>'.date('Y-m-d', $iDate).'</td>'; // Add the first column directly
-						$sField = date('H:i:s', $iDate); // Will add the second column below
-						$sData .= "<td>$sField</td>";
+						// Split the date and time in two columns
+						$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
+						$sTime = $oTimeFormat->Format($oObj->Get($sAttCode));
+						$sData .= "<td>$sDate</td>";
+						$sData .= "<td>$sTime</td>";
+					}
+					else if (get_class($oFinalAttDef) == 'AttributeDate')
+					{
+						$sDate = $oDateFormat->Format($oObj->Get($sAttCode));
+						$sData .= "<td>$sDate</td>";
 					}
 					else if($oAttDef instanceof AttributeCaseLog)
 					{

+ 4 - 4
dictionaries/cs.dictionary.itop.core.php

@@ -98,8 +98,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
     'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Formát data:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Například: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Například: %2$s
 </p>
 <p>
 Operátory:<br/>
@@ -116,8 +116,8 @@ Je-li čas vynechán, bude nastaveno 00:00:00
     'Core:AttributeDate?SmartSearch' => '
 <p>
 	Formát data:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Například: 2011-07-19
+	<b>%1$s</b><br/>
+	Například: %2$s
 </p>
 <p>
 Operátory:<br/>

+ 4 - 4
dictionaries/da.dictionary.itop.core.php

@@ -1432,8 +1432,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Example: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -1449,8 +1449,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 4 - 4
dictionaries/de.dictionary.itop.core.php

@@ -358,8 +358,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Datumsformat:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Beispiel: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Beispiel: %2$s
 </p>
 <p>
 Operatoren:<br/>
@@ -375,8 +375,8 @@ Falls der Zeit-Wert weggelassenw ird, ist der Default 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Datumsformat:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Beispiel: 2011-07-19
+	<b>%1$s</b><br/>
+	Beispiel: %2$s
 </p>
 <p>
 Operatoren:<br/>

+ 4 - 4
dictionaries/dictionary.itop.core.php

@@ -96,8 +96,8 @@ Dict::Add('EN US', 'English', 'English', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Example: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -114,8 +114,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 4 - 4
dictionaries/es_cr.dictionary.itop.core.php

@@ -96,8 +96,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Formato de Fecha:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Ejemplo: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Ejemplo: %2$s
 </p>
 <p>
 Operadores:<br/>
@@ -114,8 +114,8 @@ Si se omite el tiempo, por omisión es 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Formato de Fecha:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Ejemplo: 2011-07-19
+	<b>%1$s</b><br/>
+	Ejemplo: %2$s
 </p>
 <p>
 Operadores:<br/>

+ 4 - 4
dictionaries/fr.dictionary.itop.core.php

@@ -467,8 +467,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Format de date :<br/>
-	<b>aaaa-mm-jj hh:mm:ss</b><br/>
-	Exemple : 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Exemple : %2$s
 </p>
 <p>
 Opérateurs :<br/>
@@ -484,8 +484,8 @@ Si l\'heure n\'est pas spécifiée, cela revient à 00:00:00 (minuit)
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Format de date :<br/>
-	<b>aaaa-mm-jj</b><br/>
-	Exemple : 2011-07-19
+	<b>%1$s</b><br/>
+	Exemple : %2$s
 </p>
 <p>
 Opérateurs :<br/>

+ 4 - 4
dictionaries/hu.dictionary.itop.core.php

@@ -501,8 +501,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Example: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -516,8 +516,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 4 - 4
dictionaries/it.dictionary.itop.core.php

@@ -90,8 +90,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Formato data:<br/>
-	<b>aaaa-mm-gg hh:mm:ss</b><br/>
-	Esempio: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Esempio: %2$s
 </p>
 <p>
 Operatori:<br/>
@@ -108,8 +108,8 @@ Se \'oraè omessa, di default è 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Formato data:<br/>
-	<b>aaaa-mm-gg</b><br/>
-	Esempio: 2011-07-19
+	<b>%1$s</b><br/>
+	Esempio: %2$s
 </p>
 <p>
 Operatori:<br/>

+ 4 - 4
dictionaries/ja.dictionary.itop.core.php

@@ -360,8 +360,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	例: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	例: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -377,8 +377,8 @@ Operators:<br/>
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	日付フォーマット:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	例: 2011-07-19
+	<b>%1$s</b><br/>
+	例: %2$s
 </p>
 <p>
 演算子:<br/>

+ 4 - 4
dictionaries/nl.dictionary.itop.core.php

@@ -101,8 +101,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Example: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -119,8 +119,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 4 - 4
dictionaries/pt_br.dictionary.itop.core.php

@@ -96,8 +96,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Data formato:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Exemplo: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Exemplo: %2$s
 </p>
 <p>
 Operadores:<br/>
@@ -114,8 +114,8 @@ Se o tempo for omitido, o padrão é 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Data formato:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Exemplo: 2011-07-19
+	<b>%1$s</b><br/>
+	Exemplo: %2$s
 </p>
 <p>
 Operadores:<br/>

+ 4 - 4
dictionaries/ru.dictionary.itop.core.php

@@ -91,8 +91,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>гггг-мм-дд чч:мм:сс</b><br/>
-	Пример: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Пример: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -109,8 +109,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>гггг-мм-дд</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 4 - 4
dictionaries/tr.dictionary.itop.core.php

@@ -448,8 +448,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Example: 2011-07-19 18:40:00
+	<b>%1$ss</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -465,8 +465,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 4 - 4
dictionaries/zh.dictionary.itop.core.php

@@ -447,8 +447,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
 	'Core:AttributeDateTime?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd hh:mm:ss</b><br/>
-	Example: 2011-07-19 18:40:00
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>
@@ -464,8 +464,8 @@ If the time is omitted, it defaults to 00:00:00
 	'Core:AttributeDate?SmartSearch' => '
 <p>
 	Date format:<br/>
-	<b>yyyy-mm-dd</b><br/>
-	Example: 2011-07-19
+	<b>%1$s</b><br/>
+	Example: %2$s
 </p>
 <p>
 Operators:<br/>

+ 8 - 7
pages/csvimport.php

@@ -210,9 +210,9 @@ try
 		$sEncoding = utils::ReadParam('encoding', 'UTF-8');
 		$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
 		$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
-		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
+		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
 		
-		$sChosenDateFormat = ($sDateTimeFormat == 'default') ? AttributeDateTime::GetFormat() : $sCustomDateTimeFormat;
+		$sChosenDateFormat = ($sDateTimeFormat == 'default') ? (string)AttributeDateTime::GetFormat() : $sCustomDateTimeFormat;
 		
 		if (!empty($sSynchroScope))
 		{
@@ -762,7 +762,7 @@ EOF
 		$bAdvanced = utils::ReadParam('advanced', 0);
 		$sEncoding = utils::ReadParam('encoding', 'UTF-8');
 		$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
-		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
+		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
 	
 		$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
 		if (!empty($sSynchroScope))
@@ -1104,7 +1104,7 @@ EOF
 		$aFieldsMapping = utils::ReadParam('field', array(), false, 'raw_data');
 		$aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name');
 		$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
-		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
+		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
 		
 		// Create a truncated version of the data used for the fast preview
 		// Take about 20 lines of data... knowing that some lines may contain carriage returns
@@ -1167,8 +1167,8 @@ EOF
 		$oPage->add('<p><input type="checkbox" name="box_skiplines" value="1" id="box_skiplines" onClick="DoPreview()"'.IsChecked($bBoxSkipLines, 1).'/> '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '<input type="text" size=2 name="nb_skipped_lines" id="nb_skipped_lines" onChange="DoPreview()" value="'.$iSkippedLines.'">').'<p>');
 		$oPage->add('</td><td style="vertical-align:top;">');
 		$oPage->add('<h3>'.Dict::S('UI:CSVImport:DateAndTimeFormats').'</h3>');
-		$oPage->add('<p><input type="radio" name="date_time_format" id="radio_date_time_std" value="default"'.IsChecked($sDateTimeFormat, 'default').'/> '.Dict::Format('UI:CSVImport:DefaultDateTimeFormat_Format_Example', htmlentities(AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'), date(AttributeDateTime::GetFormat())).'<p>');
-		$oPage->add('<p><input type="radio" name="date_time_format" id="radio_date_time_custom" value="custom"'.IsChecked($sDateTimeFormat, 'custom').'/> '.Dict::Format('UI:CSVImport:CustomDateTimeFormat', '<input type="text" size="15" name="custom_date_time_format" id="custom_date_time_format" title="" value="'.htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8').'">').'<p>');
+		$oPage->add('<p><input type="radio" name="date_time_format" id="radio_date_time_std" value="default"'.IsChecked($sDateTimeFormat, 'default').'/><label for="radio_date_time_std"> '.Dict::Format('UI:CSVImport:DefaultDateTimeFormat_Format_Example', htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'), date((string)AttributeDateTime::GetFormat())).'</label><p>');
+		$oPage->add('<p><input type="radio" name="date_time_format" id="radio_date_time_custom" value="custom"'.IsChecked($sDateTimeFormat, 'custom').'/><label for="radio_date_time_custom"> '.Dict::Format('UI:CSVImport:CustomDateTimeFormat', '<input type="text" size="15" name="custom_date_time_format" id="custom_date_time_format" title="" value="'.htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8').'">').'</label><p>');
 		$oPage->add('</td></tr></table>');
 		$oPage->add('<input type="hidden" name="csvdata_truncated" id="csvdata_truncated" value="'.htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8').'"/>');
 		$oPage->add('<input type="hidden" name="csvdata" id="csvdata" value="'.htmlentities($sUTF8Data, ENT_QUOTES, 'UTF-8').'"/>');
@@ -1262,6 +1262,7 @@ EOF
 <<<EOF
 DoPreview();
 $('#custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
+$('#custom_date_time_format').on('click', function() { $('#radio_date_time_custom').prop('checked', true); });
 EOF
 		);
 	}
@@ -1298,7 +1299,7 @@ EOF
 		$bAdvanced = utils::ReadParam('advanced', 0);
 		$sEncoding = utils::ReadParam('encoding', '');
 		$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
-		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', AttributeDateTime::GetFormat(), false, 'raw_data');
+		$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
 		
 		if ($sEncoding == '')
 		{

+ 0 - 1
sources/autoload.php

@@ -29,7 +29,6 @@ require_once APPROOT . 'sources/form/field/textfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/hiddenfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/labelfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/stringfield.class.inc.php';
-require_once APPROOT . 'sources/form/field/datefield.class.inc.php';
 require_once APPROOT . 'sources/form/field/datetimefield.class.inc.php';
 require_once APPROOT . 'sources/form/field/durationfield.class.inc.php';
 require_once APPROOT . 'sources/form/field/textareafield.class.inc.php';

+ 47 - 1
sources/form/field/datetimefield.class.inc.php

@@ -22,9 +22,55 @@ namespace Combodo\iTop\Form\Field;
 use \Combodo\iTop\Form\Field\StringField;
 
 /**
- * Description of StringField
+ * A field for Dates and Date & Times, supporting custom formats
  */
 class DateTimeField extends StringField
 {
+	protected $sJSDateTimeFormat;
+	protected $sPHPDateTimeFormat;
 
+	/**
+	 *
+	 * @return string
+	 */
+	public function GetPHPDateTimeFormat()
+	{
+		return $this->sPHPDateTimeFormat;
+	}
+
+	/**
+	 *
+	 * @param string $sFormat
+	 * @return \Combodo\iTop\Form\Field\DateTimeField
+	 */
+	public function SetPHPDateTimeFormat($sDateTimeFormat)
+	{
+		$this->sPHPDateTimeFormat = $sDateTimeFormat;
+		return $this;
+	}
+	
+	/**
+	 *
+	 * @return string
+	 */
+	public function GetJSDateTimeFormat()
+	{
+		return $this->sDateTimeFormat;
+	}
+
+	/**
+	 *
+	 * @param string $sFormat
+	 * @return \Combodo\iTop\Form\Field\DateTimeField
+	 */
+	public function SetJSDateTimeFormat($sDateTimeFormat)
+	{
+		$this->sDateTimeFormat = $sDateTimeFormat;
+		return $this;
+	}
+	
+	public function GetDisplayValue()
+	{
+		return \AttributeDatetime::Format($this->currentValue, $this->GetPHPDateTimeFormat());
+	}
 }

+ 1 - 0
sources/renderer/bootstrap/bsformrenderer.class.inc.php

@@ -52,6 +52,7 @@ class BsFormRenderer extends FormRenderer
 		$this->AddSupportedField('SubFormField', 'BsSubFormFieldRenderer');
 		$this->AddSupportedField('SelectObjectField', 'BsSelectObjectFieldRenderer');
 		$this->AddSupportedField('LinkedSetField', 'BsLinkedSetFieldRenderer');
+		$this->AddSupportedField('DateTimeField', 'BsSimpleFieldRenderer');
 		$this->AddSupportedField('FileUploadField', 'BsFileUploadFieldRenderer');
 	}
 

+ 35 - 0
sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php

@@ -51,6 +51,25 @@ class BsSimpleFieldRenderer extends FieldRenderer
 		{
 			switch ($sFieldClass)
 			{
+				case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
+					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
+					if ($this->oField->GetLabel() !== '')
+					{
+						$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
+					}
+					$oOutput->AddHtml('<div class="help-block"></div>');
+					$oOutput->AddHtml('<div class="input-group date" id="datepicker_' . $this->oField->GetGlobalId() . '">');
+					$oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
+					$oOutput->AddHtml('<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>');
+					$oOutput->AddHtml('</div>');
+					$oOutput->AddHtml('</div>');
+					$sJSFormat = json_encode($this->oField->GetJSDateTimeFormat());
+					$oOutput->AddJs(
+<<<EOF
+					$('#datepicker_{$this->oField->GetGlobalId()}').datetimepicker({format: $sJSFormat});
+EOF
+					);
+					break;
 				case 'Combodo\\iTop\\Form\\Field\\StringField':
 					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
 					if ($this->oField->GetLabel() !== '')
@@ -230,6 +249,21 @@ EOF
 						$oOutput->AddHtml('</div>');
 						break;
 
+					case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
+						$oOutput->AddHtml('<div class="form-group">');
+						// Showing label / value only if read-only but not hidden
+						if (!$this->oField->GetHidden())
+						{
+							if ($this->oField->GetLabel() !== '')
+							{
+								$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
+							}
+							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('</div>');
+						}
+						$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" />');
+						$oOutput->AddHtml('</div>');
+						break;
+
 					case 'Combodo\\iTop\\Form\\Field\\RadioField':
 					case 'Combodo\\iTop\\Form\\Field\\SelectField':
 					case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
@@ -317,6 +351,7 @@ EOF
 			case 'Combodo\\iTop\\Form\\Field\\HiddenField':
 			case 'Combodo\\iTop\\Form\\Field\\RadioField':
 			case 'Combodo\\iTop\\Form\\Field\\CheckboxField':
+			case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
 				$oOutput->AddJs(
 					<<<EOF
 					$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field($sFormFieldOptions);

+ 5 - 2
webservices/import.php

@@ -357,9 +357,12 @@ try
 		$oP->add_comment("Separator: ".$sSep);
 		$oP->add_comment("Qualifier: ".$sQualifier);
 		$oP->add_comment("Charset Encoding:".$sCharSet);
-		if (strlen($sDateFormat) > 0)
+		if (($sDateFormat !== null) && (strlen($sDateFormat) > 0))
 		{
-			$oP->add_comment("Date format: '$sDateFormat'");
+			$oP->add_comment("Date and time format: '$sDateFormat'");
+			$oDateTimeFormat = new DateTimeFormat($sDateFormat);
+			$sDateOnlyFormat = $oDateTimeFormat->ToDateFormat();
+			$oP->add_comment("Date format: '$sDateOnlyFormat'");
 		}
 		else
 		{