Projet

Général

Profil

0001-manager-use-independent-date-time-inputs-for-datetim.patch

Frédéric Péters, 25 décembre 2019 08:52

Télécharger (87,7 ko)

Voir les différences:

Subject: [PATCH] manager: use independent date/time inputs for datetime input
 (#27013)

 chrono/manager/forms.py                       |   12 +-
 chrono/manager/static/css/datetimepicker.css  |  372 ----
 chrono/manager/static/css/style.scss          |    4 +
 .../static/js/bootstrap-datetimepicker.js     | 1756 -----------------
 .../js/locales/bootstrap-datetimepicker.fr.js |   18 -
 .../templates/chrono/manager_base.html        |    3 -
 chrono/manager/widgets.py                     |  154 +-
 tests/test_manager.py                         |   82 +-
 8 files changed, 101 insertions(+), 2300 deletions(-)
 delete mode 100644 chrono/manager/static/css/datetimepicker.css
 delete mode 100644 chrono/manager/static/js/bootstrap-datetimepicker.js
 delete mode 100644 chrono/manager/static/js/locales/bootstrap-datetimepicker.fr.js
chrono/manager/forms.py
38 38
)
39 39

  
40 40
from . import widgets
41

  
42

  
43
DATETIME_OPTIONS = {
44
    'weekStart': 1,
45
    'autoclose': True,
46
}
47

  
48

  
49
class DateTimeWidget(widgets.DateTimeWidget):
50
    def __init__(self, *args, **kwargs):
51
        super(DateTimeWidget, self).__init__(*args, options=DATETIME_OPTIONS, **kwargs)
41
from .widgets import DateTimeWidget
52 42

  
53 43

  
54 44
class AgendaAddForm(forms.ModelForm):
chrono/manager/static/css/datetimepicker.css
1
/*!
2
 * Datetimepicker for Bootstrap
3
 *
4
 * Copyright 2012 Stefan Petre
5
 * Improvements by Andrew Rowls
6
 * Licensed under the Apache License v2.0
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 */
10
.datetimepicker {
11
  padding: 4px;
12
  margin-top: 1px;
13
  -webkit-border-radius: 4px;
14
  -moz-border-radius: 4px;
15
  border-radius: 4px;
16
  direction: ltr;
17
  /*.dow {
18
		border-top: 1px solid #ddd !important;
19
	}*/
20

  
21
}
22
.datetimepicker-inline {
23
  width: 220px;
24
}
25
.datetimepicker.datetimepicker-rtl {
26
  direction: rtl;
27
}
28
.datetimepicker.datetimepicker-rtl table tr td span {
29
  float: right;
30
}
31
.datetimepicker-dropdown, .datetimepicker-dropdown-left {
32
  top: 0;
33
  left: 0;
34
}
35
.datetimepicker-dropdown:before {
36
  content: '';
37
  display: inline-block;
38
  border-left: 7px solid transparent;
39
  border-right: 7px solid transparent;
40
  border-bottom: 7px solid #ccc;
41
  border-bottom-color: rgba(0, 0, 0, 0.2);
42
  position: absolute;
43
  top: -7px;
44
  left: 6px;
45
}
46
.datetimepicker-dropdown:after {
47
  content: '';
48
  display: inline-block;
49
  border-left: 6px solid transparent;
50
  border-right: 6px solid transparent;
51
  border-bottom: 6px solid #ffffff;
52
  position: absolute;
53
  top: -6px;
54
  left: 7px;
55
}
56
.datetimepicker-dropdown-left:before {
57
  content: '';
58
  display: inline-block;
59
  border-left: 7px solid transparent;
60
  border-right: 7px solid transparent;
61
  border-bottom: 7px solid #ccc;
62
  border-bottom-color: rgba(0, 0, 0, 0.2);
63
  position: absolute;
64
  top: -7px;
65
  right: 6px;
66
}
67
.datetimepicker-dropdown-left:after {
68
  content: '';
69
  display: inline-block;
70
  border-left: 6px solid transparent;
71
  border-right: 6px solid transparent;
72
  border-bottom: 6px solid #ffffff;
73
  position: absolute;
74
  top: -6px;
75
  right: 7px;
76
}
77
.datetimepicker > div {
78
  display: none;
79
}
80
.datetimepicker.minutes div.datetimepicker-minutes {
81
    display: block;
82
}
83
.datetimepicker.hours div.datetimepicker-hours {
84
    display: block;
85
}
86
.datetimepicker.days div.datetimepicker-days {
87
    display: block;
88
}
89
.datetimepicker.months div.datetimepicker-months {
90
  display: block;
91
}
92
.datetimepicker.years div.datetimepicker-years {
93
  display: block;
94
}
95
.datetimepicker table {
96
  margin: 0;
97
}
98
.datetimepicker  td,
99
.datetimepicker th {
100
  text-align: center;
101
  width: 20px;
102
  height: 20px;
103
  -webkit-border-radius: 4px;
104
  -moz-border-radius: 4px;
105
  border-radius: 4px;
106
  border: none;
107
}
108
.table-striped .datetimepicker table tr td,
109
.table-striped .datetimepicker table tr th {
110
  background-color: transparent;
111
}
112
.datetimepicker table tr td.minute:hover {
113
    background: #eeeeee;
114
    cursor: pointer;
115
}
116
.datetimepicker table tr td.hour:hover {
117
    background: #eeeeee;
118
    cursor: pointer;
119
}
120
.datetimepicker table tr td.day:hover {
121
    background: #eeeeee;
122
    cursor: pointer;
123
}
124
.datetimepicker table tr td.old,
125
.datetimepicker table tr td.new {
126
  color: #999999;
127
}
128
.datetimepicker table tr td.disabled,
129
.datetimepicker table tr td.disabled:hover {
130
  background: none;
131
  color: #999999;
132
  cursor: default;
133
}
134
.datetimepicker table tr td.today,
135
.datetimepicker table tr td.today:hover,
136
.datetimepicker table tr td.today.disabled,
137
.datetimepicker table tr td.today.disabled:hover {
138
  background-color: #fde19a;
139
  background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
140
  background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
141
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
142
  background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
143
  background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
144
  background-image: linear-gradient(top, #fdd49a, #fdf59a);
145
  background-repeat: repeat-x;
146
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
147
  border-color: #fdf59a #fdf59a #fbed50;
148
  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
149
  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
150
}
151
.datetimepicker table tr td.today:hover,
152
.datetimepicker table tr td.today:hover:hover,
153
.datetimepicker table tr td.today.disabled:hover,
154
.datetimepicker table tr td.today.disabled:hover:hover,
155
.datetimepicker table tr td.today:active,
156
.datetimepicker table tr td.today:hover:active,
157
.datetimepicker table tr td.today.disabled:active,
158
.datetimepicker table tr td.today.disabled:hover:active,
159
.datetimepicker table tr td.today.active,
160
.datetimepicker table tr td.today:hover.active,
161
.datetimepicker table tr td.today.disabled.active,
162
.datetimepicker table tr td.today.disabled:hover.active,
163
.datetimepicker table tr td.today.disabled,
164
.datetimepicker table tr td.today:hover.disabled,
165
.datetimepicker table tr td.today.disabled.disabled,
166
.datetimepicker table tr td.today.disabled:hover.disabled,
167
.datetimepicker table tr td.today[disabled],
168
.datetimepicker table tr td.today:hover[disabled],
169
.datetimepicker table tr td.today.disabled[disabled],
170
.datetimepicker table tr td.today.disabled:hover[disabled] {
171
  background-color: #fdf59a;
172
}
173
.datetimepicker table tr td.today:active,
174
.datetimepicker table tr td.today:hover:active,
175
.datetimepicker table tr td.today.disabled:active,
176
.datetimepicker table tr td.today.disabled:hover:active,
177
.datetimepicker table tr td.today.active,
178
.datetimepicker table tr td.today:hover.active,
179
.datetimepicker table tr td.today.disabled.active,
180
.datetimepicker table tr td.today.disabled:hover.active {
181
  background-color: #fbf069 \9;
182
}
183
.datetimepicker table tr td.active,
184
.datetimepicker table tr td.active:hover,
185
.datetimepicker table tr td.active.disabled,
186
.datetimepicker table tr td.active.disabled:hover {
187
  background-color: #006dcc;
188
  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
189
  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
190
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
191
  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
192
  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
193
  background-image: linear-gradient(top, #0088cc, #0044cc);
194
  background-repeat: repeat-x;
195
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
196
  border-color: #0044cc #0044cc #002a80;
197
  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
198
  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
199
  color: #fff;
200
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
201
}
202
.datetimepicker table tr td.active:hover,
203
.datetimepicker table tr td.active:hover:hover,
204
.datetimepicker table tr td.active.disabled:hover,
205
.datetimepicker table tr td.active.disabled:hover:hover,
206
.datetimepicker table tr td.active:active,
207
.datetimepicker table tr td.active:hover:active,
208
.datetimepicker table tr td.active.disabled:active,
209
.datetimepicker table tr td.active.disabled:hover:active,
210
.datetimepicker table tr td.active.active,
211
.datetimepicker table tr td.active:hover.active,
212
.datetimepicker table tr td.active.disabled.active,
213
.datetimepicker table tr td.active.disabled:hover.active,
214
.datetimepicker table tr td.active.disabled,
215
.datetimepicker table tr td.active:hover.disabled,
216
.datetimepicker table tr td.active.disabled.disabled,
217
.datetimepicker table tr td.active.disabled:hover.disabled,
218
.datetimepicker table tr td.active[disabled],
219
.datetimepicker table tr td.active:hover[disabled],
220
.datetimepicker table tr td.active.disabled[disabled],
221
.datetimepicker table tr td.active.disabled:hover[disabled] {
222
  background-color: #0044cc;
223
}
224
.datetimepicker table tr td.active:active,
225
.datetimepicker table tr td.active:hover:active,
226
.datetimepicker table tr td.active.disabled:active,
227
.datetimepicker table tr td.active.disabled:hover:active,
228
.datetimepicker table tr td.active.active,
229
.datetimepicker table tr td.active:hover.active,
230
.datetimepicker table tr td.active.disabled.active,
231
.datetimepicker table tr td.active.disabled:hover.active {
232
  background-color: #003399 \9;
233
}
234
.datetimepicker table tr td span {
235
  display: block;
236
  width: 23%;
237
  height: 54px;
238
  line-height: 54px;
239
  float: left;
240
  margin: 1%;
241
  cursor: pointer;
242
  -webkit-border-radius: 4px;
243
  -moz-border-radius: 4px;
244
  border-radius: 4px;
245
}
246
.datetimepicker .datetimepicker-hours span {
247
  height: 26px;
248
  line-height: 26px;
249
}
250
.datetimepicker .datetimepicker-hours table tr td span.hour_am,
251
.datetimepicker .datetimepicker-hours table tr td span.hour_pm {
252
  width: 14.6%;
253
}
254
.datetimepicker .datetimepicker-hours fieldset legend,
255
.datetimepicker .datetimepicker-minutes fieldset legend {
256
  margin-bottom: inherit;
257
  line-height: 30px;
258
}
259
.datetimepicker .datetimepicker-minutes span {
260
  height: 26px;
261
  line-height: 26px;
262
}
263
.datetimepicker table tr td span:hover {
264
  background: #eeeeee;
265
}
266
.datetimepicker table tr td span.disabled,
267
.datetimepicker table tr td span.disabled:hover {
268
  background: none;
269
  color: #999999;
270
  cursor: default;
271
}
272
.datetimepicker table tr td span.active,
273
.datetimepicker table tr td span.active:hover,
274
.datetimepicker table tr td span.active.disabled,
275
.datetimepicker table tr td span.active.disabled:hover {
276
  background-color: #006dcc;
277
  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
278
  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
279
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
280
  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
281
  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
282
  background-image: linear-gradient(top, #0088cc, #0044cc);
283
  background-repeat: repeat-x;
284
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
285
  border-color: #0044cc #0044cc #002a80;
286
  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
287
  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
288
  color: #fff;
289
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
290
}
291
.datetimepicker table tr td span.active:hover,
292
.datetimepicker table tr td span.active:hover:hover,
293
.datetimepicker table tr td span.active.disabled:hover,
294
.datetimepicker table tr td span.active.disabled:hover:hover,
295
.datetimepicker table tr td span.active:active,
296
.datetimepicker table tr td span.active:hover:active,
297
.datetimepicker table tr td span.active.disabled:active,
298
.datetimepicker table tr td span.active.disabled:hover:active,
299
.datetimepicker table tr td span.active.active,
300
.datetimepicker table tr td span.active:hover.active,
301
.datetimepicker table tr td span.active.disabled.active,
302
.datetimepicker table tr td span.active.disabled:hover.active,
303
.datetimepicker table tr td span.active.disabled,
304
.datetimepicker table tr td span.active:hover.disabled,
305
.datetimepicker table tr td span.active.disabled.disabled,
306
.datetimepicker table tr td span.active.disabled:hover.disabled,
307
.datetimepicker table tr td span.active[disabled],
308
.datetimepicker table tr td span.active:hover[disabled],
309
.datetimepicker table tr td span.active.disabled[disabled],
310
.datetimepicker table tr td span.active.disabled:hover[disabled] {
311
  background-color: #0044cc;
312
}
313
.datetimepicker table tr td span.active:active,
314
.datetimepicker table tr td span.active:hover:active,
315
.datetimepicker table tr td span.active.disabled:active,
316
.datetimepicker table tr td span.active.disabled:hover:active,
317
.datetimepicker table tr td span.active.active,
318
.datetimepicker table tr td span.active:hover.active,
319
.datetimepicker table tr td span.active.disabled.active,
320
.datetimepicker table tr td span.active.disabled:hover.active {
321
  background-color: #003399 \9;
322
}
323
.datetimepicker table tr td span.old {
324
  color: #999999;
325
}
326
.datetimepicker th.switch {
327
  width: 145px;
328
}
329
.datetimepicker thead tr:first-child th,
330
.datetimepicker tfoot tr:first-child th {
331
  cursor: pointer;
332
}
333
.datetimepicker thead tr:first-child th:hover,
334
.datetimepicker tfoot tr:first-child th:hover {
335
  background: #eeeeee;
336
}
337
.input-append.date .add-on i,
338
.input-prepend.date .add-on i {
339
  cursor: pointer;
340
  width: 14px;
341
  height: 14px;
342
}
343

  
344

  
345

  
346
.dropdown-menu {
347
  position: absolute;
348
  top: 100%;
349
  left: 0;
350
  z-index: 1000;
351
  display: none;
352
  float: left;
353
  min-width: 160px;
354
  padding: 5px 0;
355
  margin: 2px 0 0;
356
  list-style: none;
357
  background-color: #ffffff;
358
  border: 1px solid #cccccc;
359
  border: 1px solid rgba(0, 0, 0, 0.15);
360
  border-radius: 4px;
361
  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
362
          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
363
  background-clip: padding-box;
364
}
365

  
366
.icon-arrow-left:before {
367
	content: "←";
368
}
369

  
370
.icon-arrow-right:before {
371
	content: "→";
372
}
chrono/manager/static/css/style.scss
281 281
		content: "\f021"; /* refresh */
282 282
	}
283 283
}
284

  
285
div.ui-dialog form p span.datetime input {
286
	width: auto;
287
}
chrono/manager/static/js/bootstrap-datetimepicker.js
1
/* =========================================================
2
 * bootstrap-datetimepicker.js
3
 * =========================================================
4
 * Copyright 2012 Stefan Petre
5
 * Improvements by Andrew Rowls
6
 * Improvements by Sébastien Malot
7
 * Improvements by Yun Lai
8
 * Project URL : http://www.malot.fr/bootstrap-datetimepicker
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 * http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 * ========================================================= */
22

  
23
/*
24
 * Improvement by CuGBabyBeaR @ 2013-09-12
25
 * 
26
 * Make it work in bootstrap v3
27
 */
28

  
29
!function ($) {
30

  
31
	function UTCDate() {
32
		return new Date(Date.UTC.apply(Date, arguments));
33
	}
34

  
35
	function UTCToday() {
36
		var today = new Date();
37
		return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), today.getUTCHours(), today.getUTCMinutes(), today.getUTCSeconds(), 0);
38
	}
39

  
40
	// Picker object
41

  
42
	var Datetimepicker = function (element, options) {
43
		var that = this;
44

  
45
		this.element = $(element);
46

  
47
		// add container for single page application
48
		// when page switch the datetimepicker div will be removed also.
49
		this.container = options.container || 'body';
50

  
51
		this.language = options.language || this.element.data('date-language') || "en";
52
		this.language = this.language in dates ? this.language : "en";
53
		this.isRTL = dates[this.language].rtl || false;
54
		this.formatType = options.formatType || this.element.data('format-type') || 'standard';
55
		this.format = DPGlobal.parseFormat(options.format || this.element.data('date-format') || dates[this.language].format || DPGlobal.getDefaultFormat(this.formatType, 'input'), this.formatType);
56
		this.isInline = false;
57
		this.isVisible = false;
58
		this.isInput = this.element.is('input');
59

  
60

  
61
		this.bootcssVer = this.isInput ? (this.element.is('.form-control') ? 3 : 2) : ( this.bootcssVer = this.element.is('.input-group') ? 3 : 2 );
62

  
63
		this.component = this.element.is('.date') ? ( this.bootcssVer == 3 ? this.element.find('.input-group-addon .glyphicon-th, .input-group-addon .glyphicon-time, .input-group-addon .glyphicon-calendar').parent() : this.element.find('.add-on .icon-th, .add-on .icon-time, .add-on .icon-calendar').parent()) : false;
64
		this.componentReset = this.element.is('.date') ? ( this.bootcssVer == 3 ? this.element.find('.input-group-addon .glyphicon-remove').parent() : this.element.find('.add-on .icon-remove').parent()) : false;
65
		this.hasInput = this.component && this.element.find('input').length;
66
		if (this.component && this.component.length === 0) {
67
			this.component = false;
68
		}
69
		this.linkField = options.linkField || this.element.data('link-field') || false;
70
		this.linkFormat = DPGlobal.parseFormat(options.linkFormat || this.element.data('link-format') || DPGlobal.getDefaultFormat(this.formatType, 'link'), this.formatType);
71
		this.minuteStep = options.minuteStep || this.element.data('minute-step') || 5;
72
		this.pickerPosition = options.pickerPosition || this.element.data('picker-position') || 'bottom-right';
73
		this.showMeridian = options.showMeridian || this.element.data('show-meridian') || false;
74
		this.initialDate = options.initialDate || new Date();
75

  
76
		this._attachEvents();
77

  
78
		this.formatViewType = "datetime";
79
		if ('formatViewType' in options) {
80
			this.formatViewType = options.formatViewType;
81
		} else if ('formatViewType' in this.element.data()) {
82
			this.formatViewType = this.element.data('formatViewType');
83
		}
84

  
85
		this.minView = 0;
86
		if ('minView' in options) {
87
			this.minView = options.minView;
88
		} else if ('minView' in this.element.data()) {
89
			this.minView = this.element.data('min-view');
90
		}
91
		this.minView = DPGlobal.convertViewMode(this.minView);
92

  
93
		this.maxView = DPGlobal.modes.length - 1;
94
		if ('maxView' in options) {
95
			this.maxView = options.maxView;
96
		} else if ('maxView' in this.element.data()) {
97
			this.maxView = this.element.data('max-view');
98
		}
99
		this.maxView = DPGlobal.convertViewMode(this.maxView);
100

  
101
		this.wheelViewModeNavigation = false;
102
		if ('wheelViewModeNavigation' in options) {
103
			this.wheelViewModeNavigation = options.wheelViewModeNavigation;
104
		} else if ('wheelViewModeNavigation' in this.element.data()) {
105
			this.wheelViewModeNavigation = this.element.data('view-mode-wheel-navigation');
106
		}
107

  
108
		this.wheelViewModeNavigationInverseDirection = false;
109

  
110
		if ('wheelViewModeNavigationInverseDirection' in options) {
111
			this.wheelViewModeNavigationInverseDirection = options.wheelViewModeNavigationInverseDirection;
112
		} else if ('wheelViewModeNavigationInverseDirection' in this.element.data()) {
113
			this.wheelViewModeNavigationInverseDirection = this.element.data('view-mode-wheel-navigation-inverse-dir');
114
		}
115

  
116
		this.wheelViewModeNavigationDelay = 100;
117
		if ('wheelViewModeNavigationDelay' in options) {
118
			this.wheelViewModeNavigationDelay = options.wheelViewModeNavigationDelay;
119
		} else if ('wheelViewModeNavigationDelay' in this.element.data()) {
120
			this.wheelViewModeNavigationDelay = this.element.data('view-mode-wheel-navigation-delay');
121
		}
122

  
123
		this.startViewMode = 2;
124
		if ('startView' in options) {
125
			this.startViewMode = options.startView;
126
		} else if ('startView' in this.element.data()) {
127
			this.startViewMode = this.element.data('start-view');
128
		}
129
		this.startViewMode = DPGlobal.convertViewMode(this.startViewMode);
130
		this.viewMode = this.startViewMode;
131

  
132
		this.viewSelect = this.minView;
133
		if ('viewSelect' in options) {
134
			this.viewSelect = options.viewSelect;
135
		} else if ('viewSelect' in this.element.data()) {
136
			this.viewSelect = this.element.data('view-select');
137
		}
138
		this.viewSelect = DPGlobal.convertViewMode(this.viewSelect);
139

  
140
		this.forceParse = true;
141
		if ('forceParse' in options) {
142
			this.forceParse = options.forceParse;
143
		} else if ('dateForceParse' in this.element.data()) {
144
			this.forceParse = this.element.data('date-force-parse');
145
		}
146

  
147
		this.picker = $((this.bootcssVer == 3) ? DPGlobal.templateV3 : DPGlobal.template)
148
			.appendTo(this.isInline ? this.element : this.container) // 'body')
149
			.on({
150
				click:     $.proxy(this.click, this),
151
				mousedown: $.proxy(this.mousedown, this)
152
			});
153

  
154
		if (this.maxView == 1) {
155
			this.picker.addClass('time-only-picker');
156
		}
157

  
158
		if (this.wheelViewModeNavigation) {
159
			if ($.fn.mousewheel) {
160
				this.picker.on({mousewheel: $.proxy(this.mousewheel, this)});
161
			} else {
162
				console.log("Mouse Wheel event is not supported. Please include the jQuery Mouse Wheel plugin before enabling this option");
163
			}
164
		}
165

  
166
		if (this.isInline) {
167
			this.picker.addClass('datetimepicker-inline');
168
		} else {
169
			this.picker.addClass('datetimepicker-dropdown-' + this.pickerPosition + ' dropdown-menu');
170
		}
171
		if (this.isRTL) {
172
			this.picker.addClass('datetimepicker-rtl');
173
			if (this.bootcssVer == 3) {
174
				this.picker.find('.prev span, .next span')
175
					.toggleClass('glyphicon-arrow-left glyphicon-arrow-right');
176
			} else {
177
				this.picker.find('.prev i, .next i')
178
					.toggleClass('icon-arrow-left icon-arrow-right');
179
			}
180
			;
181

  
182
		}
183
		$(document).on('mousedown', function (e) {
184
			// Clicked outside the datetimepicker, hide it
185
			if ($(e.target).closest('.datetimepicker').length === 0) {
186
				that.hide();
187
			}
188
		});
189

  
190
		this.autoclose = false;
191
		if ('autoclose' in options) {
192
			this.autoclose = options.autoclose;
193
		} else if ('dateAutoclose' in this.element.data()) {
194
			this.autoclose = this.element.data('date-autoclose');
195
		}
196

  
197
		this.keyboardNavigation = true;
198
		if ('keyboardNavigation' in options) {
199
			this.keyboardNavigation = options.keyboardNavigation;
200
		} else if ('dateKeyboardNavigation' in this.element.data()) {
201
			this.keyboardNavigation = this.element.data('date-keyboard-navigation');
202
		}
203

  
204
		this.todayBtn = (options.todayBtn || this.element.data('date-today-btn') || false);
205
		this.todayHighlight = (options.todayHighlight || this.element.data('date-today-highlight') || false);
206

  
207
		this.weekStart = ((options.weekStart || this.element.data('date-weekstart') || dates[this.language].weekStart || 0) % 7);
208
		this.weekEnd = ((this.weekStart + 6) % 7);
209
		this.startDate = -Infinity;
210
		this.endDate = Infinity;
211
		this.daysOfWeekDisabled = [];
212
		this.setStartDate(options.startDate || this.element.data('date-startdate'));
213
		this.setEndDate(options.endDate || this.element.data('date-enddate'));
214
		this.setDaysOfWeekDisabled(options.daysOfWeekDisabled || this.element.data('date-days-of-week-disabled'));
215
		this.fillDow();
216
		this.fillMonths();
217
		this.update();
218
		this.showMode();
219

  
220
		if (this.isInline) {
221
			this.show();
222
		}
223
	};
224

  
225
	Datetimepicker.prototype = {
226
		constructor: Datetimepicker,
227

  
228
		_events:       [],
229
		_attachEvents: function () {
230
			this._detachEvents();
231
			if (this.isInput) { // single input
232
				this._events = [
233
					[this.element, {
234
						focus:   $.proxy(this.show, this),
235
						keyup:   $.proxy(this.update, this),
236
						keydown: $.proxy(this.keydown, this)
237
					}]
238
				];
239
			}
240
			else if (this.component && this.hasInput) { // component: input + button
241
				this._events = [
242
					// For components that are not readonly, allow keyboard nav
243
					[this.element.find('input'), {
244
						focus:   $.proxy(this.show, this),
245
						keyup:   $.proxy(this.update, this),
246
						keydown: $.proxy(this.keydown, this)
247
					}],
248
					[this.component, {
249
						click: $.proxy(this.show, this)
250
					}]
251
				];
252
				if (this.componentReset) {
253
					this._events.push([
254
						this.componentReset,
255
						{click: $.proxy(this.reset, this)}
256
					]);
257
				}
258
			}
259
			else if (this.element.is('div')) {  // inline datetimepicker
260
				this.isInline = true;
261
			}
262
			else {
263
				this._events = [
264
					[this.element, {
265
						click: $.proxy(this.show, this)
266
					}]
267
				];
268
			}
269
			for (var i = 0, el, ev; i < this._events.length; i++) {
270
				el = this._events[i][0];
271
				ev = this._events[i][1];
272
				el.on(ev);
273
			}
274
		},
275

  
276
		_detachEvents: function () {
277
			for (var i = 0, el, ev; i < this._events.length; i++) {
278
				el = this._events[i][0];
279
				ev = this._events[i][1];
280
				el.off(ev);
281
			}
282
			this._events = [];
283
		},
284

  
285
		show: function (e) {
286
			this.picker.show();
287
			this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
288
			if (this.forceParse && !(this.isVisible && e.type == 'focus')) {
289
				this.update();
290
			}
291
			this.place();
292
			$(window).on('resize', $.proxy(this.place, this));
293
			if (e) {
294
				e.stopPropagation();
295
				e.preventDefault();
296
			}
297
			this.isVisible = true;
298
			this.element.trigger({
299
				type: 'show',
300
				date: this.date
301
			});
302
		},
303

  
304
		hide: function (e) {
305
			if (!this.isVisible) return;
306
			if (this.isInline) return;
307
			this.picker.hide();
308
			$(window).off('resize', this.place);
309
			this.viewMode = this.startViewMode;
310
			this.showMode();
311
			if (!this.isInput) {
312
				$(document).off('mousedown', this.hide);
313
			}
314

  
315
			if (
316
				this.forceParse &&
317
					(
318
						this.isInput && this.element.val() ||
319
							this.hasInput && this.element.find('input').val()
320
						)
321
				)
322
				this.setValue();
323
			this.isVisible = false;
324
			this.element.trigger({
325
				type: 'hide',
326
				date: this.date
327
			});
328
		},
329

  
330
		remove: function () {
331
			this._detachEvents();
332
			this.picker.remove();
333
			delete this.picker;
334
			delete this.element.data().datetimepicker;
335
		},
336

  
337
		getDate: function () {
338
			var d = this.getUTCDate();
339
			return new Date(d.getTime() + (d.getTimezoneOffset() * 60000));
340
		},
341

  
342
		getUTCDate: function () {
343
			return this.date;
344
		},
345

  
346
		setDate: function (d) {
347
			this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset() * 60000)));
348
		},
349

  
350
		setUTCDate: function (d) {
351
			if (d >= this.startDate && d <= this.endDate) {
352
				this.date = d;
353
				this.setValue();
354
				this.viewDate = this.date;
355
				this.fill();
356
			} else {
357
				this.element.trigger({
358
					type:      'outOfRange',
359
					date:      d,
360
					startDate: this.startDate,
361
					endDate:   this.endDate
362
				});
363
			}
364
		},
365

  
366
		setFormat: function (format) {
367
			this.format = DPGlobal.parseFormat(format, this.formatType);
368
			var element;
369
			if (this.isInput) {
370
				element = this.element;
371
			} else if (this.component) {
372
				element = this.element.find('input');
373
			}
374
			if (element && element.val()) {
375
				this.setValue();
376
			}
377
		},
378

  
379
		setValue: function () {
380
			var formatted = this.getFormattedDate();
381
			if (!this.isInput) {
382
				if (this.component) {
383
					this.element.find('input').val(formatted);
384
				}
385
				this.element.data('date', formatted);
386
			} else {
387
				this.element.val(formatted);
388
			}
389
			if (this.linkField) {
390
				$('#' + this.linkField).val(this.getFormattedDate(this.linkFormat));
391
			}
392
		},
393

  
394
		getFormattedDate: function (format) {
395
			if (format == undefined) format = this.format;
396
			return DPGlobal.formatDate(this.date, format, this.language, this.formatType);
397
		},
398

  
399
		setStartDate: function (startDate) {
400
			this.startDate = startDate || -Infinity;
401
			if (this.startDate !== -Infinity) {
402
				this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language, this.formatType);
403
			}
404
			this.update();
405
			this.updateNavArrows();
406
		},
407

  
408
		setEndDate: function (endDate) {
409
			this.endDate = endDate || Infinity;
410
			if (this.endDate !== Infinity) {
411
				this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language, this.formatType);
412
			}
413
			this.update();
414
			this.updateNavArrows();
415
		},
416

  
417
		setDaysOfWeekDisabled: function (daysOfWeekDisabled) {
418
			this.daysOfWeekDisabled = daysOfWeekDisabled || [];
419
			if (!$.isArray(this.daysOfWeekDisabled)) {
420
				this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
421
			}
422
			this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
423
				return parseInt(d, 10);
424
			});
425
			this.update();
426
			this.updateNavArrows();
427
		},
428

  
429
		place: function () {
430
			if (this.isInline) return;
431

  
432
			var index_highest = 0;
433
			$('div').each(function () {
434
				var index_current = parseInt($(this).css("zIndex"), 10);
435
				if (index_current > index_highest) {
436
					index_highest = index_current;
437
				}
438
			});
439
			var zIndex = index_highest + 10;
440

  
441
			var offset, top, left, containerOffset;
442
			if (this.container instanceof $) {
443
				containerOffset = this.container.offset();
444
			} else {
445
				containerOffset = $(this.container).offset();
446
			}
447

  
448
			if (this.component) {
449
				offset = this.component.offset();
450
				left = offset.left;
451
				if (this.pickerPosition == 'bottom-left' || this.pickerPosition == 'top-left') {
452
					left += this.component.outerWidth() - this.picker.outerWidth();
453
				}
454
			} else {
455
				offset = this.element.offset();
456
				left = offset.left;
457
			}
458
			
459
			if(left+220 > document.body.clientWidth){
460
            			left = document.body.clientWidth-220;
461
          		}
462
			
463
			if (this.pickerPosition == 'top-left' || this.pickerPosition == 'top-right') {
464
				top = offset.top - this.picker.outerHeight();
465
			} else {
466
				top = offset.top + this.height;
467
			}
468

  
469
			top = top - containerOffset.top;
470
			left = left - containerOffset.left;
471

  
472
			this.picker.css({
473
				top:    top,
474
				left:   left,
475
				zIndex: zIndex
476
			});
477
		},
478

  
479
		update: function () {
480
			var date, fromArgs = false;
481
			if (arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
482
				date = arguments[0];
483
				fromArgs = true;
484
			} else {
485
				date = (this.isInput ? this.element.val() : this.element.find('input').val()) || this.element.data('date') || this.initialDate;
486
				if (typeof date == 'string' || date instanceof String) {
487
				  date = date.replace(/^\s+|\s+$/g,'');
488
				}
489
			}
490

  
491
			if (!date) {
492
				date = new Date();
493
				fromArgs = false;
494
			}
495

  
496
			this.date = DPGlobal.parseDate(date, this.format, this.language, this.formatType);
497

  
498
			if (fromArgs) this.setValue();
499

  
500
			if (this.date < this.startDate) {
501
				this.viewDate = new Date(this.startDate);
502
			} else if (this.date > this.endDate) {
503
				this.viewDate = new Date(this.endDate);
504
			} else {
505
				this.viewDate = new Date(this.date);
506
			}
507
			this.fill();
508
		},
509

  
510
		fillDow: function () {
511
			var dowCnt = this.weekStart,
512
				html = '<tr>';
513
			while (dowCnt < this.weekStart + 7) {
514
				html += '<th class="dow">' + dates[this.language].daysMin[(dowCnt++) % 7] + '</th>';
515
			}
516
			html += '</tr>';
517
			this.picker.find('.datetimepicker-days thead').append(html);
518
		},
519

  
520
		fillMonths: function () {
521
			var html = '',
522
				i = 0;
523
			while (i < 12) {
524
				html += '<span class="month">' + dates[this.language].monthsShort[i++] + '</span>';
525
			}
526
			this.picker.find('.datetimepicker-months td').html(html);
527
		},
528

  
529
		fill: function () {
530
			if (this.date == null || this.viewDate == null) {
531
				return;
532
			}
533
			var d = new Date(this.viewDate),
534
				year = d.getUTCFullYear(),
535
				month = d.getUTCMonth(),
536
				dayMonth = d.getUTCDate(),
537
				hours = d.getUTCHours(),
538
				minutes = d.getUTCMinutes(),
539
				startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
540
				startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
541
				endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
542
				endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
543
				currentDate = (new UTCDate(this.date.getUTCFullYear(), this.date.getUTCMonth(), this.date.getUTCDate())).valueOf(),
544
				today = new Date();
545
			this.picker.find('.datetimepicker-days thead th:eq(1)')
546
				.text(dates[this.language].months[month] + ' ' + year);
547
			if (this.formatViewType == "time") {
548
				var hourConverted = hours % 12 ? hours % 12 : 12;
549
				var hoursDisplay = (hourConverted < 10 ? '0' : '') + hourConverted;
550
				var minutesDisplay = (minutes < 10 ? '0' : '') + minutes;
551
				var meridianDisplay = dates[this.language].meridiem[hours < 12 ? 0 : 1];
552
				this.picker.find('.datetimepicker-hours thead th:eq(1)')
553
					.text(hoursDisplay + ':' + minutesDisplay + ' ' + (meridianDisplay ? meridianDisplay.toUpperCase() : ''));
554
				this.picker.find('.datetimepicker-minutes thead th:eq(1)')
555
					.text(hoursDisplay + ':' + minutesDisplay + ' ' + (meridianDisplay ? meridianDisplay.toUpperCase() : ''));
556
			} else {
557
				this.picker.find('.datetimepicker-hours thead th:eq(1)')
558
					.text(dayMonth + ' ' + dates[this.language].months[month] + ' ' + year);
559
				this.picker.find('.datetimepicker-minutes thead th:eq(1)')
560
					.text(dayMonth + ' ' + dates[this.language].months[month] + ' ' + year);
561
			}
562
			this.picker.find('tfoot th.today')
563
				.text(dates[this.language].today)
564
				.toggle(this.todayBtn !== false);
565
			this.updateNavArrows();
566
			this.fillMonths();
567
			/*var prevMonth = UTCDate(year, month, 0,0,0,0,0);
568
			 prevMonth.setUTCDate(prevMonth.getDate() - (prevMonth.getUTCDay() - this.weekStart + 7)%7);*/
569
			var prevMonth = UTCDate(year, month - 1, 28, 0, 0, 0, 0),
570
				day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
571
			prevMonth.setUTCDate(day);
572
			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7) % 7);
573
			var nextMonth = new Date(prevMonth);
574
			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
575
			nextMonth = nextMonth.valueOf();
576
			var html = [];
577
			var clsName;
578
			while (prevMonth.valueOf() < nextMonth) {
579
				if (prevMonth.getUTCDay() == this.weekStart) {
580
					html.push('<tr>');
581
				}
582
				clsName = '';
583
				if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
584
					clsName += ' old';
585
				} else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
586
					clsName += ' new';
587
				}
588
				// Compare internal UTC date with local today, not UTC today
589
				if (this.todayHighlight &&
590
					prevMonth.getUTCFullYear() == today.getFullYear() &&
591
					prevMonth.getUTCMonth() == today.getMonth() &&
592
					prevMonth.getUTCDate() == today.getDate()) {
593
					clsName += ' today';
594
				}
595
				if (prevMonth.valueOf() == currentDate) {
596
					clsName += ' active';
597
				}
598
				if ((prevMonth.valueOf() + 86400000) <= this.startDate || prevMonth.valueOf() > this.endDate ||
599
					$.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
600
					clsName += ' disabled';
601
				}
602
				html.push('<td class="day' + clsName + '">' + prevMonth.getUTCDate() + '</td>');
603
				if (prevMonth.getUTCDay() == this.weekEnd) {
604
					html.push('</tr>');
605
				}
606
				prevMonth.setUTCDate(prevMonth.getUTCDate() + 1);
607
			}
608
			this.picker.find('.datetimepicker-days tbody').empty().append(html.join(''));
609

  
610
			html = [];
611
			var txt = '', meridian = '', meridianOld = '';
612
			for (var i = 0; i < 24; i++) {
613
				var actual = UTCDate(year, month, dayMonth, i);
614
				clsName = '';
615
				// We want the previous hour for the startDate
616
				if ((actual.valueOf() + 3600000) <= this.startDate || actual.valueOf() > this.endDate) {
617
					clsName += ' disabled';
618
				} else if (hours == i) {
619
					clsName += ' active';
620
				}
621
				if (this.showMeridian && dates[this.language].meridiem.length == 2) {
622
					meridian = (i < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]);
623
					if (meridian != meridianOld) {
624
						if (meridianOld != '') {
625
							html.push('</fieldset>');
626
						}
627
						html.push('<fieldset class="hour"><legend>' + meridian.toUpperCase() + '</legend>');
628
					}
629
					meridianOld = meridian;
630
					txt = (i % 12 ? i % 12 : 12);
631
					html.push('<span class="hour' + clsName + ' hour_' + (i < 12 ? 'am' : 'pm') + '">' + txt + '</span>');
632
					if (i == 23) {
633
						html.push('</fieldset>');
634
					}
635
				} else {
636
					txt = i + ':00';
637
					html.push('<span class="hour' + clsName + '">' + txt + '</span>');
638
				}
639
			}
640
			this.picker.find('.datetimepicker-hours td').html(html.join(''));
641

  
642
			html = [];
643
			txt = '', meridian = '', meridianOld = '';
644
			for (var i = 0; i < 60; i += this.minuteStep) {
645
				var actual = UTCDate(year, month, dayMonth, hours, i, 0);
646
				clsName = '';
647
				if (actual.valueOf() < this.startDate || actual.valueOf() > this.endDate) {
648
					clsName += ' disabled';
649
				} else if (Math.floor(minutes / this.minuteStep) == Math.floor(i / this.minuteStep)) {
650
					clsName += ' active';
651
				}
652
				if (this.showMeridian && dates[this.language].meridiem.length == 2) {
653
					meridian = (hours < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]);
654
					if (meridian != meridianOld) {
655
						if (meridianOld != '') {
656
							html.push('</fieldset>');
657
						}
658
						html.push('<fieldset class="minute"><legend>' + meridian.toUpperCase() + '</legend>');
659
					}
660
					meridianOld = meridian;
661
					txt = (hours % 12 ? hours % 12 : 12);
662
					//html.push('<span class="minute'+clsName+' minute_'+(hours<12?'am':'pm')+'">'+txt+'</span>');
663
					html.push('<span class="minute' + clsName + '">' + txt + ':' + (i < 10 ? '0' + i : i) + '</span>');
664
					if (i == 59) {
665
						html.push('</fieldset>');
666
					}
667
				} else {
668
					txt = i + ':00';
669
					//html.push('<span class="hour'+clsName+'">'+txt+'</span>');
670
					html.push('<span class="minute' + clsName + '">' + hours + ':' + (i < 10 ? '0' + i : i) + '</span>');
671
				}
672
			}
673
			this.picker.find('.datetimepicker-minutes td').html(html.join(''));
674

  
675
			var currentYear = this.date.getUTCFullYear();
676
			var months = this.picker.find('.datetimepicker-months')
677
				.find('th:eq(1)')
678
				.text(year)
679
				.end()
680
				.find('span').removeClass('active');
681
			if (currentYear == year) {
682
				months.eq(this.date.getUTCMonth()).addClass('active');
683
			}
684
			if (year < startYear || year > endYear) {
685
				months.addClass('disabled');
686
			}
687
			if (year == startYear) {
688
				months.slice(0, startMonth).addClass('disabled');
689
			}
690
			if (year == endYear) {
691
				months.slice(endMonth + 1).addClass('disabled');
692
			}
693

  
694
			html = '';
695
			year = parseInt(year / 10, 10) * 10;
696
			var yearCont = this.picker.find('.datetimepicker-years')
697
				.find('th:eq(1)')
698
				.text(year + '-' + (year + 9))
699
				.end()
700
				.find('td');
701
			year -= 1;
702
			for (var i = -1; i < 11; i++) {
703
				html += '<span class="year' + (i == -1 || i == 10 ? ' old' : '') + (currentYear == year ? ' active' : '') + (year < startYear || year > endYear ? ' disabled' : '') + '">' + year + '</span>';
704
				year += 1;
705
			}
706
			yearCont.html(html);
707
			this.place();
708
		},
709

  
710
		updateNavArrows: function () {
711
			var d = new Date(this.viewDate),
712
				year = d.getUTCFullYear(),
713
				month = d.getUTCMonth(),
714
				day = d.getUTCDate(),
715
				hour = d.getUTCHours();
716
			switch (this.viewMode) {
717
				case 0:
718
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()
719
						&& month <= this.startDate.getUTCMonth()
720
						&& day <= this.startDate.getUTCDate()
721
						&& hour <= this.startDate.getUTCHours()) {
722
						this.picker.find('.prev').css({visibility: 'hidden'});
723
					} else {
724
						this.picker.find('.prev').css({visibility: 'visible'});
725
					}
726
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()
727
						&& month >= this.endDate.getUTCMonth()
728
						&& day >= this.endDate.getUTCDate()
729
						&& hour >= this.endDate.getUTCHours()) {
730
						this.picker.find('.next').css({visibility: 'hidden'});
731
					} else {
732
						this.picker.find('.next').css({visibility: 'visible'});
733
					}
734
					break;
735
				case 1:
736
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()
737
						&& month <= this.startDate.getUTCMonth()
738
						&& day <= this.startDate.getUTCDate()) {
739
						this.picker.find('.prev').css({visibility: 'hidden'});
740
					} else {
741
						this.picker.find('.prev').css({visibility: 'visible'});
742
					}
743
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()
744
						&& month >= this.endDate.getUTCMonth()
745
						&& day >= this.endDate.getUTCDate()) {
746
						this.picker.find('.next').css({visibility: 'hidden'});
747
					} else {
748
						this.picker.find('.next').css({visibility: 'visible'});
749
					}
750
					break;
751
				case 2:
752
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()
753
						&& month <= this.startDate.getUTCMonth()) {
754
						this.picker.find('.prev').css({visibility: 'hidden'});
755
					} else {
756
						this.picker.find('.prev').css({visibility: 'visible'});
757
					}
758
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()
759
						&& month >= this.endDate.getUTCMonth()) {
760
						this.picker.find('.next').css({visibility: 'hidden'});
761
					} else {
762
						this.picker.find('.next').css({visibility: 'visible'});
763
					}
764
					break;
765
				case 3:
766
				case 4:
767
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
768
						this.picker.find('.prev').css({visibility: 'hidden'});
769
					} else {
770
						this.picker.find('.prev').css({visibility: 'visible'});
771
					}
772
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
773
						this.picker.find('.next').css({visibility: 'hidden'});
774
					} else {
775
						this.picker.find('.next').css({visibility: 'visible'});
776
					}
777
					break;
778
			}
779
		},
780

  
781
		mousewheel: function (e) {
782

  
783
			e.preventDefault();
784
			e.stopPropagation();
785

  
786
			if (this.wheelPause) {
787
				return;
788
			}
789

  
790
			this.wheelPause = true;
791

  
792
			var originalEvent = e.originalEvent;
793

  
794
			var delta = originalEvent.wheelDelta;
795

  
796
			var mode = delta > 0 ? 1 : (delta === 0) ? 0 : -1;
797

  
798
			if (this.wheelViewModeNavigationInverseDirection) {
799
				mode = -mode;
800
			}
801

  
802
			this.showMode(mode);
803

  
804
			setTimeout($.proxy(function () {
805

  
806
				this.wheelPause = false
807

  
808
			}, this), this.wheelViewModeNavigationDelay);
809

  
810
		},
811

  
812
		click: function (e) {
813
			e.stopPropagation();
814
			e.preventDefault();
815
			var target = $(e.target).closest('span, td, th, legend');
816
			if (target.is('.glyphicon')) {
817
				target = $(target).parent().closest('span, td, th, legend');
818
			}
819
			if (target.length == 1) {
820
				if (target.is('.disabled')) {
821
					this.element.trigger({
822
						type:      'outOfRange',
823
						date:      this.viewDate,
824
						startDate: this.startDate,
825
						endDate:   this.endDate
826
					});
827
					return;
828
				}
829
				switch (target[0].nodeName.toLowerCase()) {
830
					case 'th':
831
						switch (target[0].className) {
832
							case 'switch':
833
								this.showMode(1);
834
								break;
835
							case 'prev':
836
							case 'next':
837
								var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
838
								switch (this.viewMode) {
839
									case 0:
840
										this.viewDate = this.moveHour(this.viewDate, dir);
841
										break;
842
									case 1:
843
										this.viewDate = this.moveDate(this.viewDate, dir);
844
										break;
845
									case 2:
846
										this.viewDate = this.moveMonth(this.viewDate, dir);
847
										break;
848
									case 3:
849
									case 4:
850
										this.viewDate = this.moveYear(this.viewDate, dir);
851
										break;
852
								}
853
								this.fill();
854
								this.element.trigger({
855
									type:      target[0].className + ':' + this.convertViewModeText(this.viewMode),
856
									date:      this.viewDate,
857
									startDate: this.startDate,
858
									endDate:   this.endDate
859
								});
860
								break;
861
							case 'today':
862
								var date = new Date();
863
								date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0);
864

  
865
								// Respect startDate and endDate.
866
								if (date < this.startDate) date = this.startDate;
867
								else if (date > this.endDate) date = this.endDate;
868

  
869
								this.viewMode = this.startViewMode;
870
								this.showMode(0);
871
								this._setDate(date);
872
								this.fill();
873
								if (this.autoclose) {
874
									this.hide();
875
								}
876
								break;
877
						}
878
						break;
879
					case 'span':
880
						if (!target.is('.disabled')) {
881
							var year = this.viewDate.getUTCFullYear(),
882
								month = this.viewDate.getUTCMonth(),
883
								day = this.viewDate.getUTCDate(),
884
								hours = this.viewDate.getUTCHours(),
885
								minutes = this.viewDate.getUTCMinutes(),
886
								seconds = this.viewDate.getUTCSeconds();
887

  
888
							if (target.is('.month')) {
889
								this.viewDate.setUTCDate(1);
890
								month = target.parent().find('span').index(target);
891
								day = this.viewDate.getUTCDate();
892
								this.viewDate.setUTCMonth(month);
893
								this.element.trigger({
894
									type: 'changeMonth',
895
									date: this.viewDate
896
								});
897
								if (this.viewSelect >= 3) {
898
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
899
								}
900
							} else if (target.is('.year')) {
901
								this.viewDate.setUTCDate(1);
902
								year = parseInt(target.text(), 10) || 0;
903
								this.viewDate.setUTCFullYear(year);
904
								this.element.trigger({
905
									type: 'changeYear',
906
									date: this.viewDate
907
								});
908
								if (this.viewSelect >= 4) {
909
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
910
								}
911
							} else if (target.is('.hour')) {
912
								hours = parseInt(target.text(), 10) || 0;
913
								if (target.hasClass('hour_am') || target.hasClass('hour_pm')) {
914
									if (hours == 12 && target.hasClass('hour_am')) {
915
										hours = 0;
916
									} else if (hours != 12 && target.hasClass('hour_pm')) {
917
										hours += 12;
918
									}
919
								}
920
								this.viewDate.setUTCHours(hours);
921
								this.element.trigger({
922
									type: 'changeHour',
923
									date: this.viewDate
924
								});
925
								if (this.viewSelect >= 1) {
926
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
927
								}
928
							} else if (target.is('.minute')) {
929
								minutes = parseInt(target.text().substr(target.text().indexOf(':') + 1), 10) || 0;
930
								this.viewDate.setUTCMinutes(minutes);
931
								this.element.trigger({
932
									type: 'changeMinute',
933
									date: this.viewDate
934
								});
935
								if (this.viewSelect >= 0) {
936
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
937
								}
938
							}
939
							if (this.viewMode != 0) {
940
								var oldViewMode = this.viewMode;
941
								this.showMode(-1);
942
								this.fill();
943
								if (oldViewMode == this.viewMode && this.autoclose) {
944
									this.hide();
945
								}
946
							} else {
947
								this.fill();
948
								if (this.autoclose) {
949
									this.hide();
950
								}
951
							}
952
						}
953
						break;
954
					case 'td':
955
						if (target.is('.day') && !target.is('.disabled')) {
956
							var day = parseInt(target.text(), 10) || 1;
957
							var year = this.viewDate.getUTCFullYear(),
958
								month = this.viewDate.getUTCMonth(),
959
								hours = this.viewDate.getUTCHours(),
960
								minutes = this.viewDate.getUTCMinutes(),
961
								seconds = this.viewDate.getUTCSeconds();
962
							if (target.is('.old')) {
963
								if (month === 0) {
964
									month = 11;
965
									year -= 1;
966
								} else {
967
									month -= 1;
968
								}
969
							} else if (target.is('.new')) {
970
								if (month == 11) {
971
									month = 0;
972
									year += 1;
973
								} else {
974
									month += 1;
975
								}
976
							}
977
							this.viewDate.setUTCFullYear(year);
978
							this.viewDate.setUTCMonth(month, day);
979
							this.element.trigger({
980
								type: 'changeDay',
981
								date: this.viewDate
982
							});
983
							if (this.viewSelect >= 2) {
984
								this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
985
							}
986
						}
987
						var oldViewMode = this.viewMode;
988
						this.showMode(-1);
989
						this.fill();
990
						if (oldViewMode == this.viewMode && this.autoclose) {
991
							this.hide();
992
						}
993
						break;
994
				}
995
			}
996
		},
997

  
998
		_setDate: function (date, which) {
999
			if (!which || which == 'date')
1000
				this.date = date;
1001
			if (!which || which == 'view')
1002
				this.viewDate = date;
1003
			this.fill();
1004
			this.setValue();
1005
			var element;
1006
			if (this.isInput) {
1007
				element = this.element;
1008
			} else if (this.component) {
1009
				element = this.element.find('input');
1010
			}
1011
			if (element) {
1012
				element.change();
1013
				if (this.autoclose && (!which || which == 'date')) {
1014
					//this.hide();
1015
				}
1016
			}
1017
			this.element.trigger({
1018
				type: 'changeDate',
1019
				date: this.date
1020
			});
1021
		},
1022

  
1023
		moveMinute: function (date, dir) {
1024
			if (!dir) return date;
1025
			var new_date = new Date(date.valueOf());
1026
			//dir = dir > 0 ? 1 : -1;
1027
			new_date.setUTCMinutes(new_date.getUTCMinutes() + (dir * this.minuteStep));
1028
			return new_date;
1029
		},
1030

  
1031
		moveHour: function (date, dir) {
1032
			if (!dir) return date;
1033
			var new_date = new Date(date.valueOf());
1034
			//dir = dir > 0 ? 1 : -1;
1035
			new_date.setUTCHours(new_date.getUTCHours() + dir);
1036
			return new_date;
1037
		},
1038

  
1039
		moveDate: function (date, dir) {
1040
			if (!dir) return date;
1041
			var new_date = new Date(date.valueOf());
1042
			//dir = dir > 0 ? 1 : -1;
1043
			new_date.setUTCDate(new_date.getUTCDate() + dir);
1044
			return new_date;
1045
		},
1046

  
1047
		moveMonth: function (date, dir) {
1048
			if (!dir) return date;
1049
			var new_date = new Date(date.valueOf()),
1050
				day = new_date.getUTCDate(),
1051
				month = new_date.getUTCMonth(),
1052
				mag = Math.abs(dir),
1053
				new_month, test;
1054
			dir = dir > 0 ? 1 : -1;
1055
			if (mag == 1) {
1056
				test = dir == -1
1057
					// If going back one month, make sure month is not current month
1058
					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
1059
					? function () {
1060
					return new_date.getUTCMonth() == month;
1061
				}
1062
					// If going forward one month, make sure month is as expected
1063
					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
1064
					: function () {
1065
					return new_date.getUTCMonth() != new_month;
1066
				};
1067
				new_month = month + dir;
1068
				new_date.setUTCMonth(new_month);
1069
				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
1070
				if (new_month < 0 || new_month > 11)
1071
					new_month = (new_month + 12) % 12;
1072
			} else {
1073
				// For magnitudes >1, move one month at a time...
1074
				for (var i = 0; i < mag; i++)
1075
					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
1076
					new_date = this.moveMonth(new_date, dir);
1077
				// ...then reset the day, keeping it in the new month
1078
				new_month = new_date.getUTCMonth();
1079
				new_date.setUTCDate(day);
1080
				test = function () {
1081
					return new_month != new_date.getUTCMonth();
1082
				};
1083
			}
1084
			// Common date-resetting loop -- if date is beyond end of month, make it
1085
			// end of month
1086
			while (test()) {
1087
				new_date.setUTCDate(--day);
1088
				new_date.setUTCMonth(new_month);
1089
			}
1090
			return new_date;
1091
		},
1092

  
1093
		moveYear: function (date, dir) {
1094
			return this.moveMonth(date, dir * 12);
1095
		},
1096

  
1097
		dateWithinRange: function (date) {
1098
			return date >= this.startDate && date <= this.endDate;
1099
		},
1100

  
1101
		keydown: function (e) {
1102
			if (this.picker.is(':not(:visible)')) {
1103
				if (e.keyCode == 27) // allow escape to hide and re-show picker
1104
					this.show();
1105
				return;
1106
			}
1107
			var dateChanged = false,
1108
				dir, day, month,
1109
				newDate, newViewDate;
1110
			switch (e.keyCode) {
1111
				case 27: // escape
1112
					this.hide();
1113
					e.preventDefault();
1114
					break;
1115
				case 37: // left
1116
				case 39: // right
1117
					if (!this.keyboardNavigation) break;
1118
					dir = e.keyCode == 37 ? -1 : 1;
1119
					viewMode = this.viewMode;
1120
					if (e.ctrlKey) {
1121
						viewMode += 2;
1122
					} else if (e.shiftKey) {
1123
						viewMode += 1;
1124
					}
1125
					if (viewMode == 4) {
1126
						newDate = this.moveYear(this.date, dir);
1127
						newViewDate = this.moveYear(this.viewDate, dir);
1128
					} else if (viewMode == 3) {
1129
						newDate = this.moveMonth(this.date, dir);
1130
						newViewDate = this.moveMonth(this.viewDate, dir);
1131
					} else if (viewMode == 2) {
1132
						newDate = this.moveDate(this.date, dir);
1133
						newViewDate = this.moveDate(this.viewDate, dir);
1134
					} else if (viewMode == 1) {
1135
						newDate = this.moveHour(this.date, dir);
1136
						newViewDate = this.moveHour(this.viewDate, dir);
1137
					} else if (viewMode == 0) {
1138
						newDate = this.moveMinute(this.date, dir);
1139
						newViewDate = this.moveMinute(this.viewDate, dir);
1140
					}
1141
					if (this.dateWithinRange(newDate)) {
1142
						this.date = newDate;
1143
						this.viewDate = newViewDate;
1144
						this.setValue();
1145
						this.update();
1146
						e.preventDefault();
1147
						dateChanged = true;
1148
					}
1149
					break;
1150
				case 38: // up
1151
				case 40: // down
1152
					if (!this.keyboardNavigation) break;
1153
					dir = e.keyCode == 38 ? -1 : 1;
1154
					viewMode = this.viewMode;
1155
					if (e.ctrlKey) {
1156
						viewMode += 2;
1157
					} else if (e.shiftKey) {
1158
						viewMode += 1;
1159
					}
1160
					if (viewMode == 4) {
1161
						newDate = this.moveYear(this.date, dir);
1162
						newViewDate = this.moveYear(this.viewDate, dir);
1163
					} else if (viewMode == 3) {
1164
						newDate = this.moveMonth(this.date, dir);
1165
						newViewDate = this.moveMonth(this.viewDate, dir);
1166
					} else if (viewMode == 2) {
1167
						newDate = this.moveDate(this.date, dir * 7);
1168
						newViewDate = this.moveDate(this.viewDate, dir * 7);
1169
					} else if (viewMode == 1) {
1170
						if (this.showMeridian) {
1171
							newDate = this.moveHour(this.date, dir * 6);
1172
							newViewDate = this.moveHour(this.viewDate, dir * 6);
1173
						} else {
1174
							newDate = this.moveHour(this.date, dir * 4);
1175
							newViewDate = this.moveHour(this.viewDate, dir * 4);
1176
						}
1177
					} else if (viewMode == 0) {
1178
						newDate = this.moveMinute(this.date, dir * 4);
1179
						newViewDate = this.moveMinute(this.viewDate, dir * 4);
1180
					}
1181
					if (this.dateWithinRange(newDate)) {
1182
						this.date = newDate;
1183
						this.viewDate = newViewDate;
1184
						this.setValue();
1185
						this.update();
1186
						e.preventDefault();
1187
						dateChanged = true;
1188
					}
1189
					break;
1190
				case 13: // enter
1191
					if (this.viewMode != 0) {
1192
						var oldViewMode = this.viewMode;
1193
						this.showMode(-1);
1194
						this.fill();
1195
						if (oldViewMode == this.viewMode && this.autoclose) {
1196
							this.hide();
1197
						}
1198
					} else {
1199
						this.fill();
1200
						if (this.autoclose) {
1201
							this.hide();
1202
						}
1203
					}
1204
					e.preventDefault();
1205
					break;
1206
				case 9: // tab
1207
					this.hide();
1208
					break;
1209
			}
1210
			if (dateChanged) {
1211
				var element;
1212
				if (this.isInput) {
1213
					element = this.element;
1214
				} else if (this.component) {
1215
					element = this.element.find('input');
1216
				}
1217
				if (element) {
1218
					element.change();
1219
				}
1220
				this.element.trigger({
1221
					type: 'changeDate',
1222
					date: this.date
1223
				});
1224
			}
1225
		},
1226

  
1227
		showMode: function (dir) {
1228
			if (dir) {
1229
				var newViewMode = Math.max(0, Math.min(DPGlobal.modes.length - 1, this.viewMode + dir));
1230
				if (newViewMode >= this.minView && newViewMode <= this.maxView) {
1231
					this.element.trigger({
1232
						type:        'changeMode',
1233
						date:        this.viewDate,
1234
						oldViewMode: this.viewMode,
1235
						newViewMode: newViewMode
1236
					});
1237

  
1238
					this.viewMode = newViewMode;
1239
				}
1240
			}
1241
			/*
1242
			 vitalets: fixing bug of very special conditions:
1243
			 jquery 1.7.1 + webkit + show inline datetimepicker in bootstrap popover.
1244
			 Method show() does not set display css correctly and datetimepicker is not shown.
1245
			 Changed to .css('display', 'block') solve the problem.
1246
			 See https://github.com/vitalets/x-editable/issues/37
1247

  
1248
			 In jquery 1.7.2+ everything works fine.
1249
			 */
1250
			//this.picker.find('>div').hide().filter('.datetimepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
1251
			this.picker.find('>div').hide().filter('.datetimepicker-' + DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
1252
			this.updateNavArrows();
1253
		},
1254

  
1255
		reset: function (e) {
1256
			this._setDate(null, 'date');
1257
		},
1258

  
1259
		convertViewModeText:  function (viewMode) {
1260
			switch (viewMode) {
1261
				case 4:
1262
					return 'decade';
1263
				case 3:
1264
					return 'year';
1265
				case 2:
1266
					return 'month';
1267
				case 1:
1268
					return 'day';
1269
				case 0:
1270
					return 'hour';
1271
			}
1272
		}
1273
	};
1274

  
1275
	$.fn.datetimepicker = function (option) {
1276
		var args = Array.apply(null, arguments);
1277
		args.shift();
1278
		var internal_return;
1279
		this.each(function () {
1280
			var $this = $(this),
1281
				data = $this.data('datetimepicker'),
1282
				options = typeof option == 'object' && option;
1283
			if (!data) {
1284
				$this.data('datetimepicker', (data = new Datetimepicker(this, $.extend({}, $.fn.datetimepicker.defaults, options))));
1285
			}
1286
			if (typeof option == 'string' && typeof data[option] == 'function') {
1287
				internal_return = data[option].apply(data, args);
1288
				if (internal_return !== undefined) {
1289
					return false;
1290
				}
1291
			}
1292
		});
1293
		if (internal_return !== undefined)
1294
			return internal_return;
1295
		else
1296
			return this;
1297
	};
1298

  
1299
	$.fn.datetimepicker.defaults = {
1300
	};
1301
	$.fn.datetimepicker.Constructor = Datetimepicker;
1302
	var dates = $.fn.datetimepicker.dates = {
1303
		en: {
1304
			days:        ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
1305
			daysShort:   ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1306
			daysMin:     ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
1307
			months:      ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
1308
			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
1309
			meridiem:    ["am", "pm"],
1310
			suffix:      ["st", "nd", "rd", "th"],
1311
			today:       "Today"
1312
		}
1313
	};
1314

  
1315
	var DPGlobal = {
1316
		modes:            [
1317
			{
1318
				clsName: 'minutes',
1319
				navFnc:  'Hours',
1320
				navStep: 1
1321
			},
1322
			{
1323
				clsName: 'hours',
1324
				navFnc:  'Date',
1325
				navStep: 1
1326
			},
1327
			{
1328
				clsName: 'days',
1329
				navFnc:  'Month',
1330
				navStep: 1
1331
			},
1332
			{
1333
				clsName: 'months',
1334
				navFnc:  'FullYear',
1335
				navStep: 1
1336
			},
1337
			{
1338
				clsName: 'years',
1339
				navFnc:  'FullYear',
1340
				navStep: 10
1341
			}
1342
		],
1343
		isLeapYear:       function (year) {
1344
			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
1345
		},
1346
		getDaysInMonth:   function (year, month) {
1347
			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
1348
		},
1349
		getDefaultFormat: function (type, field) {
1350
			if (type == "standard") {
1351
				if (field == 'input')
1352
					return 'yyyy-mm-dd hh:ii';
1353
				else
1354
					return 'yyyy-mm-dd hh:ii:ss';
1355
			} else if (type == "php") {
1356
				if (field == 'input')
1357
					return 'Y-m-d H:i';
1358
				else
1359
					return 'Y-m-d H:i:s';
1360
			} else {
1361
				throw new Error("Invalid format type.");
1362
			}
1363
		},
1364
		validParts:       function (type) {
1365
			if (type == "standard") {
1366
				return /hh?|HH?|p|P|ii?|ss?|dd?|DD?|mm?|MM?|yy(?:yy)?/g;
1367
			} else if (type == "php") {
1368
				return /[dDjlNwzFmMnStyYaABgGhHis]/g;
1369
			} else {
1370
				throw new Error("Invalid format type.");
1371
			}
1372
		},
1373
		nonpunctuation:   /[^ -\/:-@\[-`{-~\t\n\rTZ]+/g,
1374
		parseFormat:      function (format, type) {
1375
			// IE treats \0 as a string end in inputs (truncating the value),
1376
			// so it's a bad format delimiter, anyway
1377
			var separators = format.replace(this.validParts(type), '\0').split('\0'),
1378
				parts = format.match(this.validParts(type));
1379
			if (!separators || !separators.length || !parts || parts.length == 0) {
1380
				throw new Error("Invalid date format.");
1381
			}
1382
			return {separators: separators, parts: parts};
1383
		},
1384
		parseDate:        function (date, format, language, type) {
1385
			if (date instanceof Date) {
1386
				var dateUTC = new Date(date.valueOf() - date.getTimezoneOffset() * 60000);
1387
				dateUTC.setMilliseconds(0);
1388
				return dateUTC;
1389
			}
1390
			if (/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(date)) {
1391
				format = this.parseFormat('yyyy-mm-dd', type);
1392
			}
1393
			if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}$/.test(date)) {
1394
				format = this.parseFormat('yyyy-mm-dd hh:ii', type);
1395
			}
1396
			if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}\:\d{1,2}[Z]{0,1}$/.test(date)) {
1397
				format = this.parseFormat('yyyy-mm-dd hh:ii:ss', type);
1398
			}
1399
			if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
1400
				var part_re = /([-+]\d+)([dmwy])/,
1401
					parts = date.match(/([-+]\d+)([dmwy])/g),
1402
					part, dir;
1403
				date = new Date();
1404
				for (var i = 0; i < parts.length; i++) {
1405
					part = part_re.exec(parts[i]);
1406
					dir = parseInt(part[1]);
1407
					switch (part[2]) {
1408
						case 'd':
1409
							date.setUTCDate(date.getUTCDate() + dir);
1410
							break;
1411
						case 'm':
1412
							date = Datetimepicker.prototype.moveMonth.call(Datetimepicker.prototype, date, dir);
1413
							break;
1414
						case 'w':
1415
							date.setUTCDate(date.getUTCDate() + dir * 7);
1416
							break;
1417
						case 'y':
1418
							date = Datetimepicker.prototype.moveYear.call(Datetimepicker.prototype, date, dir);
1419
							break;
1420
					}
1421
				}
1422
				return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), 0);
1423
			}
1424
			var parts = date && date.match(this.nonpunctuation) || [],
1425
				date = new Date(0, 0, 0, 0, 0, 0, 0),
1426
				parsed = {},
1427
				setters_order = ['hh', 'h', 'ii', 'i', 'ss', 's', 'yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'D', 'DD', 'd', 'dd', 'H', 'HH', 'p', 'P'],
1428
				setters_map = {
1429
					hh:   function (d, v) {
1430
						return d.setUTCHours(v);
1431
					},
1432
					h:    function (d, v) {
1433
						return d.setUTCHours(v);
1434
					},
1435
					HH:   function (d, v) {
1436
						return d.setUTCHours(v == 12 ? 0 : v);
1437
					},
1438
					H:    function (d, v) {
1439
						return d.setUTCHours(v == 12 ? 0 : v);
1440
					},
1441
					ii:   function (d, v) {
1442
						return d.setUTCMinutes(v);
1443
					},
1444
					i:    function (d, v) {
1445
						return d.setUTCMinutes(v);
1446
					},
1447
					ss:   function (d, v) {
1448
						return d.setUTCSeconds(v);
1449
					},
1450
					s:    function (d, v) {
1451
						return d.setUTCSeconds(v);
1452
					},
1453
					yyyy: function (d, v) {
1454
						return d.setUTCFullYear(v);
1455
					},
1456
					yy:   function (d, v) {
1457
						return d.setUTCFullYear(2000 + v);
1458
					},
1459
					m:    function (d, v) {
1460
						v -= 1;
1461
						while (v < 0) v += 12;
1462
						v %= 12;
1463
						d.setUTCMonth(v);
1464
						while (d.getUTCMonth() != v)
1465
							if (isNaN(d.getUTCMonth()))
1466
								return d;
1467
							else
1468
								d.setUTCDate(d.getUTCDate() - 1);
1469
						return d;
1470
					},
1471
					d:    function (d, v) {
1472
						return d.setUTCDate(v);
1473
					},
1474
					p:    function (d, v) {
1475
						return d.setUTCHours(v == 1 ? d.getUTCHours() + 12 : d.getUTCHours());
1476
					}
1477
				},
1478
				val, filtered, part;
1479
			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
1480
			setters_map['dd'] = setters_map['d'];
1481
			setters_map['P'] = setters_map['p'];
1482
			date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
1483
			if (parts.length == format.parts.length) {
1484
				for (var i = 0, cnt = format.parts.length; i < cnt; i++) {
1485
					val = parseInt(parts[i], 10);
1486
					part = format.parts[i];
1487
					if (isNaN(val)) {
1488
						switch (part) {
1489
							case 'MM':
1490
								filtered = $(dates[language].months).filter(function () {
1491
									var m = this.slice(0, parts[i].length),
1492
										p = parts[i].slice(0, m.length);
1493
									return m == p;
1494
								});
1495
								val = $.inArray(filtered[0], dates[language].months) + 1;
1496
								break;
1497
							case 'M':
1498
								filtered = $(dates[language].monthsShort).filter(function () {
1499
									var m = this.slice(0, parts[i].length),
1500
										p = parts[i].slice(0, m.length);
1501
									return m.toLowerCase() == p.toLowerCase();
1502
								});
1503
								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
1504
								break;
1505
							case 'p':
1506
							case 'P':
1507
								val = $.inArray(parts[i].toLowerCase(), dates[language].meridiem);
1508
								break;
1509
						}
1510
					}
1511
					parsed[part] = val;
1512
				}
1513
				for (var i = 0, s; i < setters_order.length; i++) {
1514
					s = setters_order[i];
1515
					if (s in parsed && !isNaN(parsed[s]))
1516
						setters_map[s](date, parsed[s])
1517
				}
1518
			}
1519
			return date;
1520
		},
1521
		formatDate:       function (date, format, language, type) {
1522
			if (date == null) {
1523
				return '';
1524
			}
1525
			var val;
1526
			if (type == 'standard') {
1527
				val = {
1528
					// year
1529
					yy:   date.getUTCFullYear().toString().substring(2),
1530
					yyyy: date.getUTCFullYear(),
1531
					// month
1532
					m:    date.getUTCMonth() + 1,
1533
					M:    dates[language].monthsShort[date.getUTCMonth()],
1534
					MM:   dates[language].months[date.getUTCMonth()],
1535
					// day
1536
					d:    date.getUTCDate(),
1537
					D:    dates[language].daysShort[date.getUTCDay()],
1538
					DD:   dates[language].days[date.getUTCDay()],
1539
					p:    (dates[language].meridiem.length == 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''),
1540
					// hour
1541
					h:    date.getUTCHours(),
1542
					// minute
1543
					i:    date.getUTCMinutes(),
1544
					// second
1545
					s:    date.getUTCSeconds()
1546
				};
1547

  
1548
				if (dates[language].meridiem.length == 2) {
1549
					val.H = (val.h % 12 == 0 ? 12 : val.h % 12);
1550
				}
1551
				else {
1552
					val.H = val.h;
1553
				}
1554
				val.HH = (val.H < 10 ? '0' : '') + val.H;
1555
				val.P = val.p.toUpperCase();
1556
				val.hh = (val.h < 10 ? '0' : '') + val.h;
1557
				val.ii = (val.i < 10 ? '0' : '') + val.i;
1558
				val.ss = (val.s < 10 ? '0' : '') + val.s;
1559
				val.dd = (val.d < 10 ? '0' : '') + val.d;
1560
				val.mm = (val.m < 10 ? '0' : '') + val.m;
1561
			} else if (type == 'php') {
1562
				// php format
1563
				val = {
1564
					// year
1565
					y: date.getUTCFullYear().toString().substring(2),
1566
					Y: date.getUTCFullYear(),
1567
					// month
1568
					F: dates[language].months[date.getUTCMonth()],
1569
					M: dates[language].monthsShort[date.getUTCMonth()],
1570
					n: date.getUTCMonth() + 1,
1571
					t: DPGlobal.getDaysInMonth(date.getUTCFullYear(), date.getUTCMonth()),
1572
					// day
1573
					j: date.getUTCDate(),
1574
					l: dates[language].days[date.getUTCDay()],
1575
					D: dates[language].daysShort[date.getUTCDay()],
1576
					w: date.getUTCDay(), // 0 -> 6
1577
					N: (date.getUTCDay() == 0 ? 7 : date.getUTCDay()),       // 1 -> 7
1578
					S: (date.getUTCDate() % 10 <= dates[language].suffix.length ? dates[language].suffix[date.getUTCDate() % 10 - 1] : ''),
1579
					// hour
1580
					a: (dates[language].meridiem.length == 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''),
1581
					g: (date.getUTCHours() % 12 == 0 ? 12 : date.getUTCHours() % 12),
1582
					G: date.getUTCHours(),
1583
					// minute
1584
					i: date.getUTCMinutes(),
1585
					// second
1586
					s: date.getUTCSeconds()
1587
				};
1588
				val.m = (val.n < 10 ? '0' : '') + val.n;
1589
				val.d = (val.j < 10 ? '0' : '') + val.j;
1590
				val.A = val.a.toString().toUpperCase();
1591
				val.h = (val.g < 10 ? '0' : '') + val.g;
1592
				val.H = (val.G < 10 ? '0' : '') + val.G;
1593
				val.i = (val.i < 10 ? '0' : '') + val.i;
1594
				val.s = (val.s < 10 ? '0' : '') + val.s;
1595
			} else {
1596
				throw new Error("Invalid format type.");
1597
			}
1598
			var date = [],
1599
				seps = $.extend([], format.separators);
1600
			for (var i = 0, cnt = format.parts.length; i < cnt; i++) {
1601
				if (seps.length) {
1602
					date.push(seps.shift());
1603
				}
1604
				date.push(val[format.parts[i]]);
1605
			}
1606
			if (seps.length) {
1607
				date.push(seps.shift());
1608
			}
1609
			return date.join('');
1610
		},
1611
		convertViewMode:  function (viewMode) {
1612
			switch (viewMode) {
1613
				case 4:
1614
				case 'decade':
1615
					viewMode = 4;
1616
					break;
1617
				case 3:
1618
				case 'year':
1619
					viewMode = 3;
1620
					break;
1621
				case 2:
1622
				case 'month':
1623
					viewMode = 2;
1624
					break;
1625
				case 1:
1626
				case 'day':
1627
					viewMode = 1;
1628
					break;
1629
				case 0:
1630
				case 'hour':
1631
					viewMode = 0;
1632
					break;
1633
			}
1634

  
1635
			return viewMode;
1636
		},
1637
		headTemplate:     '<thead>' +
1638
							  '<tr>' +
1639
							  '<th class="prev"><i class="icon-arrow-left"/></th>' +
1640
							  '<th colspan="5" class="switch"></th>' +
1641
							  '<th class="next"><i class="icon-arrow-right"/></th>' +
1642
							  '</tr>' +
1643
			'</thead>',
1644
		headTemplateV3:   '<thead>' +
1645
							  '<tr>' +
1646
							  '<th class="prev"><span class="glyphicon glyphicon-arrow-left"></span> </th>' +
1647
							  '<th colspan="5" class="switch"></th>' +
1648
							  '<th class="next"><span class="glyphicon glyphicon-arrow-right"></span> </th>' +
1649
							  '</tr>' +
1650
			'</thead>',
1651
		contTemplate:     '<tbody><tr><td colspan="7"></td></tr></tbody>',
1652
		footTemplate:     '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
1653
	};
1654
	DPGlobal.template = '<div class="datetimepicker">' +
1655
		'<div class="datetimepicker-minutes">' +
1656
		'<table class=" table-condensed">' +
1657
		DPGlobal.headTemplate +
1658
		DPGlobal.contTemplate +
1659
		DPGlobal.footTemplate +
1660
		'</table>' +
1661
		'</div>' +
1662
		'<div class="datetimepicker-hours">' +
1663
		'<table class=" table-condensed">' +
1664
		DPGlobal.headTemplate +
1665
		DPGlobal.contTemplate +
1666
		DPGlobal.footTemplate +
1667
		'</table>' +
1668
		'</div>' +
1669
		'<div class="datetimepicker-days">' +
1670
		'<table class=" table-condensed">' +
1671
		DPGlobal.headTemplate +
1672
		'<tbody></tbody>' +
1673
		DPGlobal.footTemplate +
1674
		'</table>' +
1675
		'</div>' +
1676
		'<div class="datetimepicker-months">' +
1677
		'<table class="table-condensed">' +
1678
		DPGlobal.headTemplate +
1679
		DPGlobal.contTemplate +
1680
		DPGlobal.footTemplate +
1681
		'</table>' +
1682
		'</div>' +
1683
		'<div class="datetimepicker-years">' +
1684
		'<table class="table-condensed">' +
1685
		DPGlobal.headTemplate +
1686
		DPGlobal.contTemplate +
1687
		DPGlobal.footTemplate +
1688
		'</table>' +
1689
		'</div>' +
1690
		'</div>';
1691
	DPGlobal.templateV3 = '<div class="datetimepicker">' +
1692
		'<div class="datetimepicker-minutes">' +
1693
		'<table class=" table-condensed">' +
1694
		DPGlobal.headTemplateV3 +
1695
		DPGlobal.contTemplate +
1696
		DPGlobal.footTemplate +
1697
		'</table>' +
1698
		'</div>' +
1699
		'<div class="datetimepicker-hours">' +
1700
		'<table class=" table-condensed">' +
1701
		DPGlobal.headTemplateV3 +
1702
		DPGlobal.contTemplate +
1703
		DPGlobal.footTemplate +
1704
		'</table>' +
1705
		'</div>' +
1706
		'<div class="datetimepicker-days">' +
1707
		'<table class=" table-condensed">' +
1708
		DPGlobal.headTemplateV3 +
1709
		'<tbody></tbody>' +
1710
		DPGlobal.footTemplate +
1711
		'</table>' +
1712
		'</div>' +
1713
		'<div class="datetimepicker-months">' +
1714
		'<table class="table-condensed">' +
1715
		DPGlobal.headTemplateV3 +
1716
		DPGlobal.contTemplate +
1717
		DPGlobal.footTemplate +
1718
		'</table>' +
1719
		'</div>' +
1720
		'<div class="datetimepicker-years">' +
1721
		'<table class="table-condensed">' +
1722
		DPGlobal.headTemplateV3 +
1723
		DPGlobal.contTemplate +
1724
		DPGlobal.footTemplate +
1725
		'</table>' +
1726
		'</div>' +
1727
		'</div>';
1728
	$.fn.datetimepicker.DPGlobal = DPGlobal;
1729

  
1730
	/* DATETIMEPICKER NO CONFLICT
1731
	 * =================== */
1732

  
1733
	$.fn.datetimepicker.noConflict = function () {
1734
		$.fn.datetimepicker = old;
1735
		return this;
1736
	};
1737

  
1738
	/* DATETIMEPICKER DATA-API
1739
	 * ================== */
1740

  
1741
	$(document).on(
1742
		'focus.datetimepicker.data-api click.datetimepicker.data-api',
1743
		'[data-provide="datetimepicker"]',
1744
		function (e) {
1745
			var $this = $(this);
1746
			if ($this.data('datetimepicker')) return;
1747
			e.preventDefault();
1748
			// component click requires us to explicitly show it
1749
			$this.datetimepicker('show');
1750
		}
1751
	);
1752
	$(function () {
1753
		$('[data-provide="datetimepicker-inline"]').datetimepicker();
1754
	});
1755

  
1756
}(window.jQuery);
chrono/manager/static/js/locales/bootstrap-datetimepicker.fr.js
1
/**
2
 * French translation for bootstrap-datetimepicker
3
 * Nico Mollet <nico.mollet@gmail.com>
4
 */
5
;(function($){
6
	$.fn.datetimepicker.dates['fr'] = {
7
		days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
8
		daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
9
		daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"],
10
		months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
11
		monthsShort: ["Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
12
		today: "Aujourd'hui",
13
		suffix: [],
14
		meridiem: ["am", "pm"],
15
		weekStart: 1,
16
		format: "dd/mm/yyyy"
17
	};
18
}(jQuery));
chrono/manager/templates/chrono/manager_base.html
3 3

  
4 4
{% block extrascripts %}
5 5
{{ block.super }}
6
<link rel="stylesheet" type="text/css" media="all" href="{% static 'css/datetimepicker.css' %}" />
7 6
<script src="{% static 'js/chrono.manager.js' %}"></script>
8
<script src="{% static 'js/bootstrap-datetimepicker.js' %}"></script>
9
<script src="{% static 'js/locales/bootstrap-datetimepicker.fr.js' %}"></script>
10 7
{% endblock %}
11 8

  
12 9
{% block page-title %}
chrono/manager/widgets.py
1
# Bootstrap django-datetime-widget is a simple and clean widget for DateField,
2
# Timefiled and DateTimeField in Django framework. It is based on Bootstrap
3
# datetime picker, supports Bootstrap 2
1
# chrono - agendas system
2
# Copyright (C) 2016-2019  Entr'ouvert
4 3
#
5
# https://github.com/asaglimbeni/django-datetime-widget
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
6 8
#
7
# License: BSD
8
# Initial Author: Alfredo Saglimbeni
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
9 17

  
18
import datetime
10 19
import json
11 20
import re
12 21
import uuid
13 22

  
14 23
from django.forms.widgets import DateTimeInput, TimeInput, SelectMultiple
15
from django.utils.formats import get_language
24
from django.utils import dateparse, formats
16 25
from django.utils.safestring import mark_safe
17 26

  
18
DATE_FORMAT_JS_PY_MAPPING = {
19
    'P': '%p',
20
    'ss': '%S',
21
    'ii': '%M',
22
    'hh': '%H',
23
    'HH': '%I',
24
    'dd': '%d',
25
    'mm': '%m',
26
    'yy': '%y',
27
    'yyyy': '%Y',
28
}
29

  
30
DATE_FORMAT_TO_PYTHON_REGEX = re.compile(r'\b(' + '|'.join(DATE_FORMAT_JS_PY_MAPPING.keys()) + r')\b')
31

  
32

  
33
DATE_FORMAT_PY_JS_MAPPING = {
34
    '%M': 'ii',
35
    '%m': 'mm',
36
    '%I': 'HH',
37
    '%H': 'hh',
38
    '%d': 'dd',
39
    '%Y': 'yyyy',
40
    '%y': 'yy',
41
    '%p': 'P',
42
    '%S': 'ss',
43
}
44

  
45
DATE_FORMAT_TO_JS_REGEX = re.compile(r'(?<!\w)(' + '|'.join(DATE_FORMAT_PY_JS_MAPPING.keys()) + r')\b')
46

  
47

  
48
BOOTSTRAP_INPUT_TEMPLATE = """
49
       <div id="%(id)s"  class="controls input-append date">
50
           %(rendered_widget)s
51
           %(clear_button)s
52
           <span class="add-on"><i class="icon-th"></i></span>
53
       </div>
54
       <script type="text/javascript">
55
           $("#%(id)s").datetimepicker({%(options)s});
56
       </script>
57
       """
58

  
59
CLEAR_BTN_TEMPLATE = """<span class="add-on"><i class="icon-remove"></i></span>"""
60

  
61

  
62
class PickerWidgetMixin(object):
63

  
64
    format_name = None
65
    glyphicon = None
66

  
67
    def __init__(self, attrs=None, options=None, usel10n=None):
68

  
69
        if attrs is None:
70
            attrs = {'readonly': ''}
71

  
72
        self.options = options
73
        if get_language():
74
            self.options['language'] = get_language().split('-')[0]
75

  
76
        # We're not doing localisation, get the Javascript date format provided by the user,
77
        # with a default, and convert it to a Python data format for later string parsing
78
        date_format = self.options['format']
79
        self.format = DATE_FORMAT_TO_PYTHON_REGEX.sub(
80
            lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()], date_format
81
        )
82 27

  
83
        super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
28
class DateTimeWidget(DateTimeInput):
29
    template_name = 'chrono/widget_datetime.html'
84 30

  
85
    def render(self, name, value, attrs=None, renderer=None):
86
        final_attrs = self.build_attrs(attrs)
87
        rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs, renderer=renderer)
31
    def format_value(self, value):
32
        if value:
33
            x = {
34
                'date': value.date().strftime('%Y-%m-%d'),
35
                'time': value.time().strftime('%H:%M'),
36
            }
37
            return x
38
        return None
88 39

  
89
        # if not set, autoclose have to be true.
90
        self.options.setdefault('autoclose', True)
91

  
92
        # Build javascript options out of python dictionary
93
        options_list = []
94
        for key, value in iter(self.options.items()):
95
            options_list.append("%s: %s" % (key, json.dumps(value)))
96

  
97
        js_options = ",\n".join(options_list)
98

  
99
        # Use provided id or generate hex to avoid collisions in document
100
        id = final_attrs.get('id', uuid.uuid4().hex)
101

  
102
        return mark_safe(
103
            BOOTSTRAP_INPUT_TEMPLATE
104
            % dict(
105
                id=id,
106
                rendered_widget=rendered_widget,
107
                clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
108
                glyphicon=self.glyphicon,
109
                options=js_options,
110
            )
111
        )
112

  
113

  
114
class DateTimeWidget(PickerWidgetMixin, DateTimeInput):
115
    """
116
    DateTimeWidget is the corresponding widget for Datetime field, it renders both the date and time
117
    sections of the datetime picker.
118
    """
119

  
120
    format_name = 'DATETIME_INPUT_FORMATS'
121
    glyphicon = 'glyphicon-th'
122

  
123
    def __init__(self, attrs=None, options=None, usel10n=None):
124

  
125
        if options is None:
126
            options = {}
127

  
128
        # Set the default options to show only the datepicker object
129
        options['format'] = options.get('format', 'dd/mm/yyyy hh:ii')
130

  
131
        super(DateTimeWidget, self).__init__(attrs, options, usel10n)
40
    def value_from_datadict(self, data, files, name):
41
        date_string = data.get(name + '$date')
42
        time_string = data.get(name + '$time')
43
        if not date_string or not time_string:
44
            return None
45
        date_value = dateparse.parse_date(date_string)
46
        time_value = dateparse.parse_time(time_string)
47
        if not date_value or not time_value:
48
            return None
49
        return datetime.datetime.combine(date_value, time_value)
132 50

  
133 51

  
134 52
class TimeWidget(TimeInput):
tests/test_manager.py
351 351
    assert "This agenda doesn't have any event yet." in resp.text
352 352
    year = now().year + 1
353 353
    resp = resp.click('New Event')
354
    resp.form['start_datetime'] = '%s-02-15 17:00' % year
354
    resp.form['start_datetime$date'] = '%s-02-15' % year
355
    resp.form['start_datetime$time'] = '17:00'
355 356
    resp.form['places'] = 10
356 357
    resp = resp.form.submit()
357 358
    resp = resp.follow()
......
369 370
    # add with a description
370 371
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
371 372
    resp = resp.click('New Event')
372
    resp.form['start_datetime'] = '%s-02-15 18:00' % year
373
    resp.form['start_datetime$date'] = '%s-02-15' % year
374
    resp.form['start_datetime$time'] = '18:00'
373 375
    resp.form['places'] = 11
374 376
    resp.form['description'] = 'A description'
375 377
    resp = resp.form.submit()
......
377 379
    event = Event.objects.get(places=11)
378 380
    assert event.description == 'A description'
379 381

  
382
    # add with errors in datetime parts
383
    for parts in (
384
        ('', ''),
385
        ('invalid', ''),
386
        ('', 'invalid'),
387
        ('2019-02-24', 'invalid'),
388
        ('invalid', '17:00'),
389
    ):
390
        resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
391
        resp = resp.click('New Event')
392
        resp.form['start_datetime$date'] = parts[0]
393
        resp.form['start_datetime$time'] = parts[1]
394
        resp.form['places'] = 10
395
        resp = resp.form.submit()
396
        assert resp.text.count('This field is required.') == 1
397

  
380 398

  
381 399
def test_add_event_on_missing_agenda(app, admin_user):
382 400
    app = login(app)
......
398 416
    resp = resp.click('Settings')
399 417
    assert '<h2>Settings' in resp.text
400 418
    resp = resp.click('New Event')
401
    resp.form['start_datetime'] = '2016-02-15 17:00'
419
    resp.form['start_datetime$date'] = '2016-02-15'
420
    resp.form['start_datetime$time'] = '17:00'
402 421
    resp.form['places'] = 10
403 422
    resp = resp.form.submit()
404 423
    resp = resp.follow()
......
417 436
    app = login(app)
418 437
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
419 438
    resp = resp.click('Feb. 15, 2016, 5 p.m.')
420
    assert resp.form['start_datetime'].value == '15/02/2016 17:00'
421
    resp.form['start_datetime'] = '2016-02-16 17:00'
439
    assert resp.form['start_datetime$date'].value == '2016-02-15'
440
    assert resp.form['start_datetime$time'].value == '17:00'
441
    resp.form['start_datetime$date'] = '2016-02-16'
442
    resp.form['start_datetime$time'] = '17:00'
422 443
    resp.form['places'] = 20
423 444
    resp = resp.form.submit()
424 445
    resp = resp.follow()
......
445 466
    agenda.save()
446 467
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
447 468
    resp = resp.click('Feb. 15, 2016, 5 p.m.')
448
    assert resp.form['start_datetime'].value == '15/02/2016 17:00'
449
    resp.form['start_datetime'] = '2016-02-16 17:00'
469
    assert resp.form['start_datetime$date'].value == '2016-02-15'
470
    assert resp.form['start_datetime$time'].value == '17:00'
471
    resp.form['start_datetime$date'] = '2016-02-16'
472
    resp.form['start_datetime$time'] = '17:00'
450 473
    resp.form['places'] = 20
451 474
    resp = resp.form.submit()
452 475
    resp = resp.follow()
......
954 977
    tomorrow = make_aware(today + datetime.timedelta(days=1))
955 978
    dt_format = '%Y-%m-%d %H:%M'
956 979
    resp.form['label'] = 'Exception 1'
957
    resp.form['start_datetime'] = tomorrow.replace(hour=8).strftime(dt_format)
958
    resp.form['end_datetime'] = tomorrow.replace(hour=16).strftime(dt_format)
980
    resp.form['start_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
981
    resp.form['start_datetime$time'] = '08:00'
982
    resp.form['end_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
983
    resp.form['end_datetime$time'] = '16:00'
959 984
    resp = resp.form.submit().follow()
960 985
    assert TimePeriodException.objects.count() == 1
961 986
    time_period_exception = TimePeriodException.objects.first()
......
969 994
    resp = resp.click('Add a time period exception', index=1)
970 995
    future = tomorrow + datetime.timedelta(days=15)
971 996
    resp.form['label'] = 'Exception 2'
972
    resp.form['start_datetime'] = future.replace(hour=0, minute=0).strftime(dt_format)
973
    resp.form['end_datetime'] = future.replace(hour=16).strftime(dt_format)
997
    resp.form['start_datetime$date'] = future.strftime('%Y-%m-%d')
998
    resp.form['start_datetime$time'] = '00:00'
999
    resp.form['end_datetime$date'] = future.strftime('%Y-%m-%d')
1000
    resp.form['end_datetime$time'] = '16:00'
974 1001
    resp = resp.form.submit().follow()
975 1002
    assert TimePeriodException.objects.count() == 2
976 1003
    assert 'Exception 1' in resp.text
......
1001 1028
    # fields should be marked with errors
1002 1029
    assert resp.text.count('This field is required.') == 2
1003 1030
    # try again with data in fields
1004
    resp.form['start_datetime'] = '2017-05-22 08:00'
1005
    resp.form['end_datetime'] = '2017-05-26 17:30'
1031
    resp.form['start_datetime$date'] = '2017-05-22'
1032
    resp.form['start_datetime$time'] = '08:00'
1033
    resp.form['end_datetime$date'] = '2017-05-26'
1034
    resp.form['end_datetime$time'] = '17:30'
1006 1035
    resp = resp.form.submit()
1007 1036
    assert 'One or several bookings exists within this time slot.' in resp.text
1008 1037
    assert TimePeriodException.objects.count() == 0
......
1015 1044
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1016 1045
    resp = resp.click('Settings')
1017 1046
    resp = resp.click('Add a time period exception', href='desk/%s/' % desk.id)
1018
    resp.form['start_datetime'] = '2017-05-22 08:00'
1019
    resp.form['end_datetime'] = '2017-05-26 17:30'
1047
    resp.form['start_datetime$date'] = '2017-05-22'
1048
    resp.form['start_datetime$time'] = '08:00'
1049
    resp.form['end_datetime$date'] = '2017-05-26'
1050
    resp.form['end_datetime$time'] = '17:30'
1020 1051
    resp = resp.form.submit()
1021 1052
    assert TimePeriodException.objects.count() == 1
1022 1053

  
......
1038 1069
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1039 1070
    resp = resp.click('Settings')
1040 1071
    resp = resp.click('Add a time period exception')
1041
    resp.form['start_datetime'] = '2017-05-22 08:00'
1042
    resp.form['end_datetime'] = '2017-05-26 17:30'
1072
    resp.form['start_datetime$date'] = '2017-05-22'
1073
    resp.form['start_datetime$time'] = '08:00'
1074
    resp.form['end_datetime$date'] = '2017-05-26'
1075
    resp.form['end_datetime$time'] = '17:30'
1043 1076
    resp = resp.form.submit()
1044 1077
    assert 'One or several bookings exists within this time slot.' not in resp.text
1045 1078
    assert TimePeriodException.objects.count() == 1
......
1056 1089
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1057 1090
    resp = resp.click('Settings')
1058 1091
    resp = resp.click('Add a time period exception')
1059
    resp.form['start_datetime'] = '2017-05-26 17:30'
1060
    resp.form['end_datetime'] = '2017-05-22 08:00'
1092
    resp.form['start_datetime$date'] = '2017-05-26'
1093
    resp.form['start_datetime$time'] = '17:30'
1094
    resp.form['end_datetime$date'] = '2017-05-22'
1095
    resp.form['end_datetime$time'] = '08:00'
1061 1096
    resp = resp.form.submit()
1062 1097
    assert 'End datetime must be greater than start datetime.' in resp.text
1063 1098

  
......
1077 1112
    tomorrow = make_aware(today + datetime.timedelta(days=15))
1078 1113
    dt_format = '%Y-%m-%d %H:%M'
1079 1114
    resp.form['label'] = 'Exception 1'
1080
    resp.form['start_datetime'] = tomorrow.replace(hour=8).strftime(dt_format)
1081
    resp.form['end_datetime'] = tomorrow.replace(hour=16).strftime(dt_format)
1115
    resp.form['start_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
1116
    resp.form['start_datetime$time'] = '08:00'
1117
    resp.form['end_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
1118
    resp.form['end_datetime$time'] = '16:00'
1082 1119
    resp = resp.form.submit().follow()
1083 1120
    assert TimePeriodException.objects.count() == 1
1084 1121
    time_period_exception = TimePeriodException.objects.first()
......
1932 1969
    resp = app.get(event_url)
1933 1970
    assert 'Options' in resp.text
1934 1971
    resp = resp.click('Options')
1935
    resp.form['start_datetime'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d %H:%M')
1972
    resp.form['start_datetime$date'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d')
1973
    resp.form['start_datetime$time'] = agenda.event_set.first().start_datetime.strftime('%H:%M')
1936 1974
    resp = resp.form.submit(status=302).follow()
1937 1975
    assert event_url == resp.request.url
1938 1976

  
1939
-