');
+ $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:
+
Y
year (4 digits, e.g. 2016)
+
y
year (2 digits, e.g. 16 for 2016)
+
m
month (2 digits, e.g. 01..12)
+
n
month (1 or 2 digits no leading zero, e.g. 1..12)
+
d
day (2 digits, e.g. 01..31)
+
j
day (1 or 2 digits no leading zero, e.g. 1..31)
+
H
hour (24 hour, 2 digits, e.g. 00..23)
+
h
hour (12 hour, 2 digits, e.g. 01..12)
+
G
hour (24 hour, 1 or 2 digits no leading zero, e.g. 0..23)
+
g
hour (12 hour, 1 or 2 digits no leading zero, e.g. 1..12)
+
a
hour, am or pm (lowercase)
+
A
hour, AM or PM (uppercase)
+
i
minutes (2 digits, e.g. 00..59)
+
s
seconds (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:
+
Y
année (sur 4 chiffres, ex. 2016)
+
y
année (sur 2 chiffres, ex. 16 pour 2016)
+
m
mois (sur 2 chiffres: 01..12)
+
n
month (sur 1 ou 2 chiffres sans le zero au début: 1..12)
+
d
jour (sur 2 chiffres: 01..31)
+
j
jour (sur 1 ou 2 chiffres sans le zero au début: 1..31)
+
H
heure (24 heures sur 2 chiffres: 00..23)
+
h
heure (12 heures sur 2 chiffres: 01..12)
+
G
heure (24 heures sur 1 ou 2 chiffres: 0..23)
+
g
heure (12 heures sur 1 ou 2 chiffres: 1..12)
+
a
am ou pm (en minuscules)
+
A
AM ou PM (en majuscules)
+
i
minutes (sur 2 chiffres: 00..59)
+
s
secondes (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 += '
' + tmph + '
';
+ }
+ }
+ else {
+ for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) {
+ gridSize[litem]++;
+ html += '
');
+ $oPage->add_ready_script(
+<<Get('csv_file_default_charset');
@@ -1335,6 +1361,8 @@ EOF
''.
''.
''.
+ ''.
+ ''.
''.
''.
''.
diff --git a/setup/licenses/community-licences.xml b/setup/licenses/community-licences.xml
index 2f0e4f4d2..c788a5fc4 100644
--- a/setup/licenses/community-licences.xml
+++ b/setup/licenses/community-licences.xml
@@ -1798,4 +1798,91 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.]]>
+
+ jQuery Timepicker addon
+ Trent Richardson
+ MIT
+
+Copyright (c) 2009 Trent Richardson, http://trentrichardson.com/Impromptu/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]>
+
+
+ D3 js
+ Mike Bostock
+ BSD
+
+Copyright (c) 2010-2016, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+]]>
+
+
+ C3 js
+ Masayuki Tanaka
+ MIT
+
+The MIT License (MIT)
+
+Copyright (c) 2013 Masayuki Tanaka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]>
+
\ No newline at end of file
diff --git a/synchro/synchro_import.php b/synchro/synchro_import.php
index 88817aaba..356f903b1 100644
--- a/synchro/synchro_import.php
+++ b/synchro/synchro_import.php
@@ -97,7 +97,7 @@ $aPageParams = array
'mandatory' => false,
'modes' => 'http,cli',
'default' => '',
- 'description' => 'Input date format (used both for dates and datetimes) - Examples: %Y-%m-%d, %d/%m/%Y (Europe) - no transformation is applied if the argument is omitted',
+ 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d, d/m/Y (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)',
),
'separator' => array
(
@@ -217,10 +217,10 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter)
function ChangeDateFormat($sProposedDate, $sDateFormat)
{
// Make sure this is a valid MySQL datetime
- $iTime = utils::StringToTime($sProposedDate, $sDateFormat);
- if ($iTime !== false)
+ $oDate = DateTime::createFromFormat($sDateFormat, $sProposedDate);
+ if ($oDate !== false)
{
- $sDate = date('Y-m-d H:i:s', $iTime);
+ $sDate = $oDate->format(AttributeDateTime::GetInternalFormat());
return $sDate;
}
else
@@ -311,6 +311,10 @@ try
$sQualifier = ReadParam($oP, 'qualifier', 'raw_data');
$sCharSet = ReadParam($oP, 'charset', 'raw_data');
$sDateFormat = ReadParam($oP, 'date_format', 'raw_data');
+ if (strpos($sDateFormat, '%') !== false)
+ {
+ $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat);
+ }
$sOutput = ReadParam($oP, 'output');
// $sReportLevel = ReadParam($oP, 'reportlevel');
$sSimulate = ReadParam($oP, 'simulate');
diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php
index 33ce88624..7b4ea0ae3 100644
--- a/synchro/synchrodatasource.class.inc.php
+++ b/synchro/synchrodatasource.class.inc.php
@@ -1862,7 +1862,7 @@ class SynchroReplica extends DBObject implements iDisplay
{
$oStatLog->Inc($sStatsCode.'_updated');
}
- $this->Set('info_last_modified', date('Y-m-d H:i:s'));
+ $this->Set('info_last_modified', date(AttributeDateTime::GetSQLFormat()));
}
else
{
@@ -1912,7 +1912,7 @@ class SynchroReplica extends DBObject implements iDisplay
$this->Set('status_dest_creator', true);
$this->Set('status_last_error', '');
$this->Set('status', 'synchronized');
- $this->Set('info_creation_date', date('Y-m-d H:i:s'));
+ $this->Set('info_creation_date', date(AttributeDateTime::GetSQLFormat()));
$bCreated = true;
$oStatLog->AddTrace("Created (".implode(', ', $aValueTrace).")", $this);
@@ -1950,7 +1950,7 @@ class SynchroReplica extends DBObject implements iDisplay
}
$oDestObj->Set($sAttCode, $value);
}
- $this->Set('info_last_modified', date('Y-m-d H:i:s'));
+ $this->Set('info_last_modified', date(AttributeDateTime::GetSQLFormat()));
$oDestObj->DBUpdateTracked($oChange);
$oStatLog->AddTrace("Replica marked as obsolete", $this);
$oStatLog->Inc('stats_nb_obj_obsoleted');
diff --git a/test/testlist.inc.php b/test/testlist.inc.php
index edd068dd1..985f1453d 100644
--- a/test/testlist.inc.php
+++ b/test/testlist.inc.php
@@ -4896,6 +4896,82 @@ class TestLinkSetRecording_1NAdd_Remove extends TestBizModel
}
}
+class TestDateTimeFormats extends TestBizModel
+{
+ static public function GetName() {return 'Check Date & Time formating and parsing';}
+ static public function GetDescription() {return 'Check the formating and parsing of dates for various formats';}
+ public function DoExecute()
+ {
+ $bRet = true;
+ $aTestFormats = array(
+ 'French (short)' => 'd/m/Y H:i:s',
+ 'French (short - no seconds)' => 'd/m/Y H:i',
+ 'French (long)' => 'd/m/Y H\\h i\\m\\i\\n s\\s',
+ 'English US' => 'm/d/Y H:i:s',
+ 'English US (12 hours)' => 'm/d/Y h:i:s a',
+ 'English US (12 hours, short)' => 'n/j/Y g:i:s a',
+ 'English UK' => 'd/m/Y H:i:s',
+ 'German' => 'd.m.Y H:i:s',
+ 'SQL' => 'Y-m-d H:i:s',
+ );
+ // Valid date and times, all tests should pass
+ $aTestDates = array('2015-01-01 00:00:00', '2015-12-31 23:59:00', '2016-01-01 08:21:00', '2016-02-28 12:30:00', '2016-02-29 16:47:00', /*'2016-02-29 14:30:17'*/);
+ foreach($aTestFormats as $sDesc => $sFormat)
+ {
+ $this->ReportSuccess("Test of the '$sDesc' format: '$sFormat':");
+ AttributeDateTime::SetFormat($sFormat);
+ foreach($aTestDates as $sTestDate)
+ {
+ $oDate = new DateTime($sTestDate);
+ $sFormattedDate = AttributeDateTime::Format($oDate, AttributeDateTime::GetFormat());
+ $sParsedDate = AttributeDateTime::Parse($sFormattedDate, AttributeDateTime::GetFormat());
+ $sPattern = AttributeDateTime::GetRegExpr();
+ $bParseOk = ($sParsedDate == $sTestDate);
+ if (!$bParseOk)
+ {
+ $this->ReportError('Parsed ('.$sFormattedDate.') date different from initial date (difference of '.((int)$oParsedDate->format('U')- (int)$oDate->format('U')).'s)');
+ $bRet = false;
+ }
+ $bValidateOk = preg_match('/'.$sPattern.'/', $sFormattedDate);
+ if (!$bValidateOk)
+ {
+ $this->ReportError('Formatted date ('.$sFormattedDate.') does not match the validation pattern ('.$sPattern.')');
+ $bRet = false;
+ }
+
+ $this->ReportSuccess("Formatted date: $sFormattedDate - Parsing: ".($bParseOk ? 'Ok' : 'KO')." - Validation: ".($bValidateOk ? 'Ok' : 'KO'));
+ }
+ echo "\n";
+ }
+
+ // Invalid date & time strings, all regexpr validation should fail
+ $aInvalidTestDates = array(
+ 'SQL' => array('2015-13-01 00:00:00', '2015-12-51 23:59:00', '2016-01-01 +08:21:00', '2016-02-28 24:30:00', '2016-02-29 16:67:88'),
+ 'French (short)' => array('01/01/20150 00:00:00', '01/01/20150 00:00:00', '01/13/2015 00:00:00', '01/01/2015 40:00:00', '01/01/2015 00:99:00'),
+ 'English US (12 hours)' => array('13/01/2015 12:00:00 am', '12/33/2015 12:00:00 am', '12/23/215 12:00:00 am', '05/04/2016 16:00:00 am', '05/04/2016 10:00:00 ap'),
+ );
+
+ foreach($aInvalidTestDates as $sFormatName => $aDatesToParse)
+ {
+ $sFormat = $aTestFormats[$sFormatName];
+ AttributeDateTime::SetFormat($sFormat);
+ $this->ReportSuccess("Test of the '$sFormatName' format: '$sFormat':");
+ foreach($aDatesToParse as $sDate)
+ {
+ $sPattern = AttributeDateTime::GetRegExpr();
+ $bValidateOk = preg_match('/'.$sPattern.'/', $sDate);
+ if ($bValidateOk)
+ {
+ $this->ReportError('Formatted date ('.$sFormattedDate.') matches the validation pattern ('.$sPattern.') whereas it should not!');
+ $bRet = false;
+ }
+ $this->ReportSuccess("Formatted date: $sDate - Validation: ".($bValidateOk ? 'KO' : 'rejected, Ok.'));
+ }
+ }
+ return $bRet;
+ }
+}
+
class TestExecActions extends TestBizModel
{
static public function GetName()
diff --git a/webservices/import.php b/webservices/import.php
index 5fb0c6195..6c8824d9b 100644
--- a/webservices/import.php
+++ b/webservices/import.php
@@ -96,7 +96,7 @@ $aPageParams = array
'mandatory' => false,
'modes' => 'http,cli',
'default' => '',
- 'description' => 'Input date format (used both for dates and datetimes) - Examples: %Y-%m-%d, %d/%m/%Y (Europe) - no transformation is applied if the argument is omitted',
+ 'description' => 'Input date format (used both for dates and datetimes) - Examples: Y-m-d H:i:s, d/m/Y H:i:s (Europe) - no transformation is applied if the argument is omitted. (note: old format specification using %Y %m %d is also supported for backward compatibility)',
),
'separator' => array
(
@@ -287,6 +287,10 @@ try
$sQualifier = ReadParam($oP, 'qualifier', 'raw_data');
$sCharSet = ReadParam($oP, 'charset', 'raw_data');
$sDateFormat = ReadParam($oP, 'date_format', 'raw_data');
+ if (strpos($sDateFormat, '%') !== false)
+ {
+ $sDateFormat = utils::DateTimeFormatToPHP($sDateFormat);
+ }
$sOutput = ReadParam($oP, 'output', 'string');
$sReconcKeys = ReadParam($oP, 'reconciliationkeys', 'raw_data');
$sSimulate = ReadParam($oP, 'simulate');