├── README.md
├── __init__.py
├── __manifest__.py
├── security
└── ir.model.access.csv
└── static
└── js
├── daterangepicker_fixed.js
├── fullcalendar_fixed.js
├── moment-jalaali.js
├── mytime.js
└── tempusdominus_fixed.js
/README.md:
--------------------------------------------------------------------------------
1 | # jalaali
2 | This addon module will help you to show odoo 15 date fields in jalaali format.
3 |
4 | Thanks Mr. Mohammadi (https://github.com/parodoo/parOdoo) for his hard work to prepare javascript files.
5 |
6 | This version of jalaali works on odoo 15 community edition. It shows most date fields in jalaali format.
7 |
8 | Note: All dates are store in gregorian format in database. If your users preference language is English, all the dates are in gregorian too.
9 |
10 | Note: To show jalaali dates, you need to change Persian as preference language.
11 |
12 | # Installation:
13 | ## 1- On odoo linux server:
14 | 1.0. Make sure you installed npm and actvated rtlcss. If not, your odoo web will not work correctly while your user's prefrences language is Persian. So you need to run the folowing commands first.
15 |
16 | sudo apt-get install npm
17 |
18 | sudo npm install -g rtlcss
19 |
20 | npm install jalali-moment -S
21 |
22 | pip install jdatetime
23 |
24 | https://www.odoo.com/documentation/15.0/administration/install/install.html#id10
25 |
26 |
27 | 1.1. Go to custom folder of your odoo server
28 |
29 | cd /usr/lib/python3/dist-packages/odoo/custom/addons/
30 |
31 | 1.2. Run git clone to recive a copy of jalaali filels on your server
32 |
33 | git clone https://github.com/gilaneh/odoo_jalaali.git
34 |
35 | 1.3. Edit the odoo.conf file on the /etc/odoo/odoo.conf path and then add your custom file on
36 |
37 | vi /etc/odoo/odoo.conf
38 | (hit i or insert key to start edit)
39 | Original odoo.conf file:
40 | [options]
41 | addons_path = /usr/lib/python3/dist-packages/odoo/addons
42 |
43 | Edited odoo.conf file:
44 | [options]
45 | addons_path = /usr/lib/python3/dist-packages/odoo/addons , /usr/lib/python3/dist-packages/odoo/custom/addons
46 |
47 | (when finished the editing, firest hit ESC, then enter semicolon, ":", and after that enter "wq" and finaly hit Enter key)
48 |
49 | 1.4. After all changes, the odoo service must be restarted
50 |
51 | systemctl restart odoo
52 |
53 | ## 2- On odoo web application
54 |
55 | 2.1. settings > Activate the developer mode (with assets)
56 |
57 | 2.2. apps > update apps list
58 |
59 | 2.3. apps > (search for jalaali) > install
60 |
61 | 2.4. settings > users > (select your user) > Preferences > Languages > (select Persian)
62 |
63 | ## 3- Fix the date on group_by
64 |
65 | edit list_renderer.js:
66 | ```
67 | addons/web/static/src/legacy/js/views/list/list_renderer.js
68 | ```
69 | Line-728: replace const by var for name variable
70 | ```
71 | var name = groupByField.type === "boolean"
72 | ? (group.value === undefined ? _t('Undefined') : group.value)
73 | : (group.value === undefined || group.value === false ? _t('Undefined') : group.value);
74 | ```
75 | line-732: add the following commands
76 |
77 | ``` const regex = /^\d{4}\-\d{2}\-\d{2}$/;
78 | if (name.match(regex) != null){
79 | name = moment(name, 'YYYY-MM-DD').locale('fa').format('jYYYY/jMM/jDD');
80 | }
81 | ```
82 |
83 | ## 4- Fix inbox message group date
84 | edit message.js
85 | ```
86 | addons/mail/static/src/models/message/message.js
87 | ```
88 | line-381: inside _computeDateDay() function, replace
89 | ```
90 | return this.date.format('LL');
91 | ```
92 | to
93 | ```
94 | if (session.user_context.lang=='fa_IR'){
95 | return this.date.format('jYYYY jMMMM jDD');
96 | }else{
97 | return this.date.format('LL');
98 | }
99 |
100 | ```
101 | ## 5- Fix filter based on date issue
102 | ### You need to edit `web/static/lib/owl/owl.js`
103 |
104 | line-3890:
105 | ```
106 | try {
107 | isValid = isValidProp(props[propName], propsDef[propName]);
108 | }
109 | ```
110 | to
111 | ```
112 | try {
113 | isValid = isValidProp(props[propName], propsDef[propName]);
114 | if(Widget.name === 'DateTimePicker' || Widget.name === 'DatePicker' ){
115 | isValid = true;
116 | }
117 | }
118 |
119 | ```
120 |
121 | ### In newer version 15 releases:
122 | line-3888:
123 | ```html
124 | try {
125 | whyInvalid = whyInvalidProp(props[propName], propsDef[propName]);
126 | }
127 | ```
128 | to:
129 | ```html
130 | try {
131 | whyInvalid = whyInvalidProp(props[propName], propsDef[propName]);
132 | if(Widget.name === 'DateTimePicker' || Widget.name === 'DatePicker' ){
133 | whyInvalid = null;
134 | }
135 | }
136 | ```
137 |
138 | #
139 |
140 | Feel free to send me email on homayounfar@msn.com if you have any question.
141 |
142 |
143 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # from . import models
4 |
--------------------------------------------------------------------------------
/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | 'name': "odoo_jalaali",
4 |
5 | 'summary': """
6 | It will show the jalaali date for most of date fields""",
7 |
8 | 'description': """
9 |
10 | """,
11 |
12 | 'author': "Arash Homayounfar",
13 | 'website': "https://karvazendegi.com/odoo",
14 |
15 | # Categories can be used to filter modules in modules listing
16 | # for the full list
17 | 'category': 'Service Desk/Service Desk',
18 | 'application': False,
19 | 'version': '15.2.1',
20 |
21 | # any module necessary for this one to work correctly
22 | 'depends': ['base', 'web'],
23 |
24 | # always loaded
25 | 'data': [
26 | # 'security/ir.model.access.csv',
27 | ],
28 | 'assets': {
29 | 'web._assets_common_scripts': [
30 | 'jalaali/static/js/mytime.js',
31 | 'jalaali/static/js/moment-jalaali.js',
32 | 'jalaali/static/js/daterangepicker_fixed.js',
33 | 'jalaali/static/js/tempusdominus_fixed.js',
34 | # 'jalaali/static/js/fullcalendar_fixed.js',
35 | ],
36 | },
37 | 'license': 'LGPL-3',
38 | }
39 |
--------------------------------------------------------------------------------
/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | access_jalaali_jalaali,jalaali.jalaali,model_jalaali_jalaali,base.group_user,1,1,1,1
--------------------------------------------------------------------------------
/static/js/daterangepicker_fixed.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @version: 3.0.5
3 | * @author: Dan Grossman http://www.dangrossman.info/
4 | * @copyright: Copyright (c) 2012-2019 Dan Grossman. All rights reserved.
5 | * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
6 | * @website: http://www.daterangepicker.com/
7 | */
8 | // Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
9 | (function (root, factory) {
10 | if (typeof define === 'function' && define.amd) {
11 | // AMD. Make globally available as well
12 | define(['moment', 'jquery'], function (moment, jquery) {
13 | if (!jquery.fn) jquery.fn = {}; // webpack server rendering
14 | if (typeof moment !== 'function' && moment.default) moment = moment.default
15 | return factory(moment, jquery);
16 | });
17 | } else if (typeof module === 'object' && module.exports) {
18 | // Node / Browserify
19 | //isomorphic issue
20 | var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined;
21 | if (!jQuery) {
22 | jQuery = require('jquery');
23 | if (!jQuery.fn) jQuery.fn = {};
24 | }
25 | var moment = (typeof window != 'undefined' && typeof window.moment != 'undefined') ? window.moment : require('moment');
26 | module.exports = factory(moment, jQuery);
27 | } else {
28 | // Browser globals
29 | root.daterangepicker = factory(root.moment, root.jQuery);
30 | }
31 | }(this, function(moment, $) {
32 | var DateRangePicker = function(element, options, cb) {
33 |
34 | //default settings for options
35 | this.parentEl = 'body';
36 | this.element = $(element);
37 | this.startDate = moment().startOf('day');
38 | this.endDate = moment().endOf('day');
39 | this.minDate = false;
40 | this.maxDate = false;
41 | this.maxSpan = false;
42 | this.autoApply = false;
43 | this.singleDatePicker = true;
44 | this.showDropdowns = false;
45 | this.minYear = moment().subtract(100, 'year').format('YYYY');
46 | this.maxYear = moment().add(100, 'year').format('YYYY');
47 | this.showWeekNumbers = false;
48 | this.showISOWeekNumbers = false;
49 | this.showCustomRangeLabel = true;
50 | this.timePicker = true;
51 | this.timePicker24Hour = false;
52 | this.timePickerIncrement = 1;
53 | this.timePickerSeconds = false;
54 | this.linkedCalendars = true;
55 | this.autoUpdateInput = true;
56 | this.alwaysShowCalendars = false;
57 | this.ranges = {};
58 |
59 | this.opens = 'right';
60 | if (this.element.hasClass('pull-right'))
61 | this.opens = 'left';
62 |
63 | this.drops = 'down';
64 | if (this.element.hasClass('dropup'))
65 | this.drops = 'up';
66 |
67 | this.buttonClasses = 'btn btn-sm';
68 | this.applyButtonClasses = 'btn-primary';
69 | this.cancelButtonClasses = 'btn-default';
70 |
71 | this.locale = {
72 | direction: 'ltr',
73 | format: moment.localeData().longDateFormat('L'),
74 | separator: ' - ',
75 | applyLabel: 'Apply',
76 | cancelLabel: 'Cancel',
77 | weekLabel: 'W',
78 | customRangeLabel: 'Custom Range',
79 | daysOfWeek: moment.weekdaysMin(),
80 | monthNames: moment.monthsShort(),
81 | firstDay: moment.localeData().firstDayOfWeek()
82 | };
83 |
84 | this.callback = function() { };
85 |
86 | //some state information
87 | this.isShowing = false;
88 | this.leftCalendar = {};
89 | this.rightCalendar = {};
90 |
91 | //custom options from user
92 | if (typeof options !== 'object' || options === null)
93 | options = {};
94 |
95 | //allow setting options with data attributes
96 | //data-api options will be overwritten with custom javascript options
97 | options = $.extend(this.element.data(), options);
98 |
99 | //html template for the picker UI
100 | if (typeof options.template !== 'string' && !(options.template instanceof $))
101 | options.template =
102 | '
' +
103 | '
' +
104 | '
' +
105 | '
' +
106 | '
' +
107 | '
' +
108 | '
' +
109 | '
' +
110 | '
' +
111 | '
' +
112 | '
' +
113 | '' +
114 | '' +
115 | ' ' +
116 | '
' +
117 | '
';
118 |
119 | this.parentEl = (options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
120 | this.container = $(options.template).appendTo(this.parentEl);
121 |
122 | //
123 | // handle all the possible options overriding defaults
124 | //
125 |
126 | if (typeof options.locale === 'object') {
127 |
128 | if (typeof options.locale.direction === 'string')
129 | this.locale.direction = options.locale.direction;
130 |
131 | if (typeof options.locale.format === 'string')
132 | this.locale.format = options.locale.format;
133 |
134 | if (typeof options.locale.separator === 'string')
135 | this.locale.separator = options.locale.separator;
136 |
137 | if (typeof options.locale.daysOfWeek === 'object')
138 | this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
139 |
140 | if (typeof options.locale.monthNames === 'object')
141 | this.locale.monthNames = options.locale.monthNames.slice();
142 |
143 | if (typeof options.locale.firstDay === 'number')
144 | this.locale.firstDay = options.locale.firstDay;
145 |
146 | if (typeof options.locale.applyLabel === 'string')
147 | this.locale.applyLabel = options.locale.applyLabel;
148 |
149 | if (typeof options.locale.cancelLabel === 'string')
150 | this.locale.cancelLabel = options.locale.cancelLabel;
151 |
152 | if (typeof options.locale.weekLabel === 'string')
153 | this.locale.weekLabel = options.locale.weekLabel;
154 |
155 | if (typeof options.locale.customRangeLabel === 'string'){
156 | //Support unicode chars in the custom range name.
157 | var elem = document.createElement('textarea');
158 | elem.innerHTML = options.locale.customRangeLabel;
159 | var rangeHtml = elem.value;
160 | this.locale.customRangeLabel = rangeHtml;
161 | }
162 | }
163 | this.container.addClass(this.locale.direction);
164 |
165 | if (typeof options.startDate === 'string')
166 | this.startDate = moment(options.startDate, this.locale.format);
167 |
168 | if (typeof options.endDate === 'string')
169 | this.endDate = moment(options.endDate, this.locale.format);
170 |
171 | if (typeof options.minDate === 'string')
172 | this.minDate = moment(options.minDate, this.locale.format);
173 |
174 | if (typeof options.maxDate === 'string')
175 | this.maxDate = moment(options.maxDate, this.locale.format);
176 |
177 | if (typeof options.startDate === 'object')
178 | this.startDate = moment(options.startDate);
179 |
180 | if (typeof options.endDate === 'object')
181 | this.endDate = moment(options.endDate);
182 |
183 | if (typeof options.minDate === 'object')
184 | this.minDate = moment(options.minDate);
185 |
186 | if (typeof options.maxDate === 'object')
187 | this.maxDate = moment(options.maxDate);
188 |
189 | // sanity check for bad options
190 | if (this.minDate && this.startDate.isBefore(this.minDate))
191 | this.startDate = this.minDate.clone();
192 |
193 | // sanity check for bad options
194 | if (this.maxDate && this.endDate.isAfter(this.maxDate))
195 | this.endDate = this.maxDate.clone();
196 |
197 | if (typeof options.applyButtonClasses === 'string')
198 | this.applyButtonClasses = options.applyButtonClasses;
199 |
200 | if (typeof options.applyClass === 'string') //backwards compat
201 | this.applyButtonClasses = options.applyClass;
202 |
203 | if (typeof options.cancelButtonClasses === 'string')
204 | this.cancelButtonClasses = options.cancelButtonClasses;
205 |
206 | if (typeof options.cancelClass === 'string') //backwards compat
207 | this.cancelButtonClasses = options.cancelClass;
208 |
209 | if (typeof options.maxSpan === 'object')
210 | this.maxSpan = options.maxSpan;
211 |
212 | if (typeof options.dateLimit === 'object') //backwards compat
213 | this.maxSpan = options.dateLimit;
214 |
215 | if (typeof options.opens === 'string')
216 | this.opens = options.opens;
217 |
218 | if (typeof options.drops === 'string')
219 | this.drops = options.drops;
220 |
221 | if (typeof options.showWeekNumbers === 'boolean')
222 | this.showWeekNumbers = options.showWeekNumbers;
223 |
224 | if (typeof options.showISOWeekNumbers === 'boolean')
225 | this.showISOWeekNumbers = options.showISOWeekNumbers;
226 |
227 | if (typeof options.buttonClasses === 'string')
228 | this.buttonClasses = options.buttonClasses;
229 |
230 | if (typeof options.buttonClasses === 'object')
231 | this.buttonClasses = options.buttonClasses.join(' ');
232 |
233 | if (typeof options.showDropdowns === 'boolean')
234 | this.showDropdowns = options.showDropdowns;
235 |
236 | if (typeof options.minYear === 'number')
237 | this.minYear = options.minYear;
238 |
239 | if (typeof options.maxYear === 'number')
240 | this.maxYear = options.maxYear;
241 |
242 | if (typeof options.showCustomRangeLabel === 'boolean')
243 | this.showCustomRangeLabel = options.showCustomRangeLabel;
244 |
245 | if (typeof options.singleDatePicker === 'boolean') {
246 | this.singleDatePicker = options.singleDatePicker;
247 | if (this.singleDatePicker)
248 | this.endDate = this.startDate.clone();
249 | }
250 |
251 | if (typeof options.timePicker === 'boolean')
252 | this.timePicker = options.timePicker;
253 |
254 | if (typeof options.timePickerSeconds === 'boolean')
255 | this.timePickerSeconds = options.timePickerSeconds;
256 |
257 | if (typeof options.timePickerIncrement === 'number')
258 | this.timePickerIncrement = options.timePickerIncrement;
259 |
260 | if (typeof options.timePicker24Hour === 'boolean')
261 | this.timePicker24Hour = options.timePicker24Hour;
262 |
263 | if (typeof options.autoApply === 'boolean')
264 | this.autoApply = options.autoApply;
265 |
266 | if (typeof options.autoUpdateInput === 'boolean')
267 | this.autoUpdateInput = options.autoUpdateInput;
268 |
269 | if (typeof options.linkedCalendars === 'boolean')
270 | this.linkedCalendars = options.linkedCalendars;
271 |
272 | if (typeof options.isInvalidDate === 'function')
273 | this.isInvalidDate = options.isInvalidDate;
274 |
275 | if (typeof options.isCustomDate === 'function')
276 | this.isCustomDate = options.isCustomDate;
277 |
278 | if (typeof options.alwaysShowCalendars === 'boolean')
279 | this.alwaysShowCalendars = options.alwaysShowCalendars;
280 |
281 | // update day names order to firstDay
282 | if (this.locale.firstDay != 0) {
283 | var iterator = this.locale.firstDay;
284 | while (iterator > 0) {
285 | this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
286 | iterator--;
287 | }
288 | }
289 |
290 | var start, end, range;
291 |
292 | //if no start/end dates set, check if an input element contains initial values
293 | if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
294 | if ($(this.element).is(':text')) {
295 | var val = $(this.element).val(),
296 | split = val.split(this.locale.separator);
297 |
298 | start = end = null;
299 |
300 | if (split.length == 2) {
301 | start = moment(split[0], this.locale.format);
302 | end = moment(split[1], this.locale.format);
303 | } else if (this.singleDatePicker && val !== "") {
304 | start = moment(val, this.locale.format);
305 | end = moment(val, this.locale.format);
306 | }
307 | if (start !== null && end !== null) {
308 | this.setStartDate(start);
309 | this.setEndDate(end);
310 | }
311 | }
312 | }
313 |
314 | if (typeof options.ranges === 'object') {
315 | for (range in options.ranges) {
316 |
317 | if (typeof options.ranges[range][0] === 'string')
318 | start = moment(options.ranges[range][0], this.locale.format);
319 | else
320 | start = moment(options.ranges[range][0]);
321 |
322 | if (typeof options.ranges[range][1] === 'string')
323 | end = moment(options.ranges[range][1], this.locale.format);
324 | else
325 | end = moment(options.ranges[range][1]);
326 |
327 | // If the start or end date exceed those allowed by the minDate or maxSpan
328 | // options, shorten the range to the allowable period.
329 | if (this.minDate && start.isBefore(this.minDate))
330 | start = this.minDate.clone();
331 |
332 | var maxDate = this.maxDate;
333 | if (this.maxSpan && maxDate && start.clone().add(this.maxSpan).isAfter(maxDate))
334 | maxDate = start.clone().add(this.maxSpan);
335 | if (maxDate && end.isAfter(maxDate))
336 | end = maxDate.clone();
337 |
338 | // If the end of the range is before the minimum or the start of the range is
339 | // after the maximum, don't display this range option at all.
340 | if ((this.minDate && end.isBefore(this.minDate, this.timepicker ? 'minute' : 'day'))
341 | || (maxDate && start.isAfter(maxDate, this.timepicker ? 'minute' : 'day')))
342 | continue;
343 |
344 | //Support unicode chars in the range names.
345 | var elem = document.createElement('textarea');
346 | elem.innerHTML = range;
347 | var rangeHtml = elem.value;
348 |
349 | this.ranges[rangeHtml] = [start, end];
350 | }
351 |
352 | var list = '';
353 | for (range in this.ranges) {
354 | list += '- ' + range + '
';
355 | }
356 | if (this.showCustomRangeLabel) {
357 | list += '- ' + this.locale.customRangeLabel + '
';
358 | }
359 | list += '
';
360 | this.container.find('.ranges').prepend(list);
361 | }
362 |
363 | if (typeof cb === 'function') {
364 | this.callback = cb;
365 | }
366 |
367 | if (!this.timePicker) {
368 | this.startDate = this.startDate.startOf('day');
369 | this.endDate = this.endDate.endOf('day');
370 | this.container.find('.calendar-time').hide();
371 | }
372 |
373 | //can't be used together for now
374 | if (this.timePicker && this.autoApply)
375 | this.autoApply = false;
376 |
377 | if (this.autoApply) {
378 | this.container.addClass('auto-apply');
379 | }
380 |
381 | if (typeof options.ranges === 'object')
382 | this.container.addClass('show-ranges');
383 |
384 | if (this.singleDatePicker) {
385 | this.container.addClass('single');
386 | this.container.find('.drp-calendar.left').addClass('single');
387 | this.container.find('.drp-calendar.left').show();
388 | this.container.find('.drp-calendar.right').hide();
389 | if (!this.timePicker) {
390 | this.container.addClass('auto-apply');
391 | }
392 | }
393 |
394 | if ((typeof options.ranges === 'undefined' && !this.singleDatePicker) || this.alwaysShowCalendars) {
395 | this.container.addClass('show-calendar');
396 | }
397 |
398 | this.container.addClass('opens' + this.opens);
399 |
400 | //apply CSS classes and labels to buttons
401 | this.container.find('.applyBtn, .cancelBtn').addClass(this.buttonClasses);
402 | if (this.applyButtonClasses.length)
403 | this.container.find('.applyBtn').addClass(this.applyButtonClasses);
404 | if (this.cancelButtonClasses.length)
405 | this.container.find('.cancelBtn').addClass(this.cancelButtonClasses);
406 | this.container.find('.applyBtn').html(this.locale.applyLabel);
407 | this.container.find('.cancelBtn').html(this.locale.cancelLabel);
408 |
409 | //
410 | // event listeners
411 | //
412 |
413 | this.container.find('.drp-calendar')
414 | .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
415 | .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
416 | .on('mousedown.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
417 | .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this))
418 | .on('change.daterangepicker', 'select.yearselect', $.proxy(this.monthOrYearChanged, this))
419 | .on('change.daterangepicker', 'select.monthselect', $.proxy(this.monthOrYearChanged, this))
420 | .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.timeChanged, this))
421 |
422 | this.container.find('.ranges')
423 | .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
424 |
425 | this.container.find('.drp-buttons')
426 | .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
427 | .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
428 |
429 | if (this.element.is('input') || this.element.is('button')) {
430 | this.element.on({
431 | 'click.daterangepicker': $.proxy(this.show, this),
432 | // odoo cutomization, to align behavior of the daterange widget with the date picker one
433 | // 'focus.daterangepicker': $.proxy(this.show, this),
434 | 'keyup.daterangepicker': $.proxy(this.elementChanged, this),
435 | 'keydown.daterangepicker': $.proxy(this.keydown, this) //IE 11 compatibility
436 | });
437 | } else {
438 | this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
439 | this.element.on('keydown.daterangepicker', $.proxy(this.toggle, this));
440 | }
441 |
442 | //
443 | // if attached to a text input, set the initial value
444 | //
445 |
446 | this.updateElement();
447 |
448 | };
449 |
450 | DateRangePicker.prototype = {
451 |
452 | constructor: DateRangePicker,
453 |
454 | setStartDate: function(startDate) {
455 | if (typeof startDate === 'string')
456 | this.startDate = moment(startDate, this.locale.format);
457 |
458 | if (typeof startDate === 'object')
459 | this.startDate = moment(startDate);
460 |
461 | if (!this.timePicker)
462 | this.startDate = this.startDate.startOf('day');
463 |
464 | if (this.timePicker && this.timePickerIncrement)
465 | this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
466 |
467 | if (this.minDate && this.startDate.isBefore(this.minDate)) {
468 | this.startDate = this.minDate.clone();
469 | if (this.timePicker && this.timePickerIncrement)
470 | this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
471 | }
472 |
473 | if (this.maxDate && this.startDate.isAfter(this.maxDate)) {
474 | this.startDate = this.maxDate.clone();
475 | if (this.timePicker && this.timePickerIncrement)
476 | this.startDate.minute(Math.floor(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
477 | }
478 |
479 | if (!this.isShowing)
480 | this.updateElement();
481 |
482 | this.updateMonthsInView();
483 | },
484 |
485 | setEndDate: function(endDate) {
486 | if (typeof endDate === 'string')
487 | this.endDate = moment(endDate, this.locale.format);
488 |
489 | if (typeof endDate === 'object')
490 | this.endDate = moment(endDate);
491 |
492 | if (!this.timePicker)
493 | this.endDate = this.endDate.endOf('day');
494 |
495 | if (this.timePicker && this.timePickerIncrement)
496 | this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
497 |
498 | if (this.endDate.isBefore(this.startDate))
499 | this.endDate = this.startDate.clone();
500 |
501 | if (this.maxDate && this.endDate.isAfter(this.maxDate))
502 | this.endDate = this.maxDate.clone();
503 |
504 | if (this.maxSpan && this.startDate.clone().add(this.maxSpan).isBefore(this.endDate))
505 | this.endDate = this.startDate.clone().add(this.maxSpan);
506 |
507 | this.previousRightTime = this.endDate.clone();
508 |
509 | this.container.find('.drp-selected').html(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
510 |
511 | if (!this.isShowing)
512 | this.updateElement();
513 |
514 | this.updateMonthsInView();
515 | },
516 |
517 | isInvalidDate: function() {
518 | return false;
519 | },
520 |
521 | isCustomDate: function() {
522 | return false;
523 | },
524 |
525 | updateView: function() {
526 | if (this.timePicker) {
527 | this.renderTimePicker('left');
528 | this.renderTimePicker('right');
529 | if (!this.endDate) {
530 | this.container.find('.right .calendar-time select').attr('disabled', 'disabled').addClass('disabled');
531 | } else {
532 | this.container.find('.right .calendar-time select').removeAttr('disabled').removeClass('disabled');
533 | }
534 | }
535 | if (this.endDate)
536 | this.container.find('.drp-selected').html(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
537 | this.updateMonthsInView();
538 | this.updateCalendars();
539 | this.updateFormInputs();
540 | },
541 |
542 | updateMonthsInView: function() {
543 | if (this.endDate) {
544 |
545 | //if both dates are visible already, do nothing
546 | if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month &&
547 | (this.startDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.startDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM'))
548 | &&
549 | (this.endDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.endDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM'))
550 | ) {
551 | return;
552 | }
553 |
554 | this.leftCalendar.month = this.startDate.clone().date(2);
555 | if (!this.linkedCalendars && (this.endDate.month() != this.startDate.month() || this.endDate.year() != this.startDate.year())) {
556 | this.rightCalendar.month = this.endDate.clone().date(2);
557 | } else {
558 | this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');
559 | }
560 |
561 | } else {
562 | if (this.leftCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM') && this.rightCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM')) {
563 | this.leftCalendar.month = this.startDate.clone().date(2);
564 | this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');
565 | }
566 | }
567 | if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && this.rightCalendar.month > this.maxDate) {
568 | this.rightCalendar.month = this.maxDate.clone().date(2);
569 | this.leftCalendar.month = this.maxDate.clone().date(2).subtract(1, 'month');
570 | }
571 | },
572 |
573 | updateCalendars: function() {
574 |
575 | if (this.timePicker) {
576 | var hour, minute, second;
577 | if (this.endDate) {
578 | hour = parseInt(this.container.find('.left .hourselect').val(), 10);
579 | minute = parseInt(this.container.find('.left .minuteselect').val(), 10);
580 | if (isNaN(minute)) {
581 | minute = parseInt(this.container.find('.left .minuteselect option:last').val(), 10);
582 | }
583 | second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0;
584 | if (!this.timePicker24Hour) {
585 | var ampm = this.container.find('.left .ampmselect').val();
586 | if (ampm === 'PM' && hour < 12)
587 | hour += 12;
588 | if (ampm === 'AM' && hour === 12)
589 | hour = 0;
590 | }
591 | } else {
592 | hour = parseInt(this.container.find('.right .hourselect').val(), 10);
593 | minute = parseInt(this.container.find('.right .minuteselect').val(), 10);
594 | if (isNaN(minute)) {
595 | minute = parseInt(this.container.find('.right .minuteselect option:last').val(), 10);
596 | }
597 | second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0;
598 | if (!this.timePicker24Hour) {
599 | var ampm = this.container.find('.right .ampmselect').val();
600 | if (ampm === 'PM' && hour < 12)
601 | hour += 12;
602 | if (ampm === 'AM' && hour === 12)
603 | hour = 0;
604 | }
605 | }
606 | this.leftCalendar.month.hour(hour).minute(minute).second(second);
607 | this.rightCalendar.month.hour(hour).minute(minute).second(second);
608 | }
609 |
610 | this.renderCalendar('left');
611 | this.renderCalendar('right');
612 |
613 | //highlight any predefined range matching the current start and end dates
614 | this.container.find('.ranges li').removeClass('active');
615 | if (this.endDate == null) return;
616 |
617 | this.calculateChosenLabel();
618 | },
619 |
620 | renderCalendar: function(side) {
621 |
622 | //
623 | // Build the matrix of dates that will populate the calendar
624 | //
625 |
626 | var calendar = side == 'left' ? this.leftCalendar : this.rightCalendar;
627 | var month = calendar.month.month();
628 | var year = calendar.month.year();
629 | var hour = calendar.month.hour();
630 | var minute = calendar.month.minute();
631 | var second = calendar.month.second();
632 | var daysInMonth = moment([year, month]).daysInMonth();
633 | var firstDay = moment([year, month, 1]);
634 | var lastDay = moment([year, month, daysInMonth]);
635 | var lastMonth = moment(firstDay).subtract(1, 'month').month();
636 | var lastYear = moment(firstDay).subtract(1, 'month').year();
637 | var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
638 | var dayOfWeek = firstDay.day();
639 |
640 | //initialize a 6 rows x 7 columns array for the calendar
641 | var calendar = [];
642 | calendar.firstDay = firstDay;
643 | calendar.lastDay = lastDay;
644 |
645 | for (var i = 0; i < 6; i++) {
646 | calendar[i] = [];
647 | }
648 |
649 | //populate the calendar with date objects
650 | var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
651 | if (startDay > daysInLastMonth)
652 | startDay -= 7;
653 |
654 | if (dayOfWeek == this.locale.firstDay)
655 | startDay = daysInLastMonth - 6;
656 |
657 | var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]);
658 |
659 | var col, row;
660 | for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) {
661 | if (i > 0 && col % 7 === 0) {
662 | col = 0;
663 | row++;
664 | }
665 | calendar[row][col] = curDate.clone().hour(hour).minute(minute).second(second);
666 | curDate.hour(12);
667 |
668 | if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') {
669 | calendar[row][col] = this.minDate.clone();
670 | }
671 |
672 | if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') {
673 | calendar[row][col] = this.maxDate.clone();
674 | }
675 |
676 | }
677 |
678 | //make the calendar object available to hoverDate/clickDate
679 | if (side == 'left') {
680 | this.leftCalendar.calendar = calendar;
681 | } else {
682 | this.rightCalendar.calendar = calendar;
683 | }
684 |
685 | //
686 | // Display the calendar
687 | //
688 |
689 | var minDate = side == 'left' ? this.minDate : this.startDate;
690 | var maxDate = this.maxDate;
691 | var selected = side == 'left' ? this.startDate : this.endDate;
692 | var arrow = this.locale.direction == 'ltr' ? {left: 'chevron-left', right: 'chevron-right'} : {left: 'chevron-right', right: 'chevron-left'};
693 |
694 | var html = '';
695 | html += '';
696 | html += '';
697 |
698 | // add empty cell for week number
699 | if (this.showWeekNumbers || this.showISOWeekNumbers)
700 | html += ' | ';
701 |
702 | if ((!minDate || minDate.isBefore(calendar.firstDay)) && (!this.linkedCalendars || side == 'left')) {
703 | html += ' | ';
704 | } else {
705 | html += ' | ';
706 | }
707 |
708 | var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");
709 |
710 | if (this.showDropdowns) {
711 | var currentMonth = calendar[1][1].month();
712 | var currentYear = calendar[1][1].year();
713 | var maxYear = (maxDate && maxDate.year()) || (this.maxYear);
714 | var minYear = (minDate && minDate.year()) || (this.minYear);
715 | var inMinYear = currentYear == minYear;
716 | var inMaxYear = currentYear == maxYear;
717 |
718 | var monthHtml = '";
731 |
732 | var yearHtml = '';
739 |
740 | dateHtml = monthHtml + yearHtml;
741 | }
742 |
743 | html += '' + dateHtml + ' | ';
744 | if ((!maxDate || maxDate.isAfter(calendar.lastDay)) && (!this.linkedCalendars || side == 'right' || this.singleDatePicker)) {
745 | html += ' | ';
746 | } else {
747 | html += ' | ';
748 | }
749 |
750 | html += '
';
751 | html += '';
752 |
753 | // add week number label
754 | if (this.showWeekNumbers || this.showISOWeekNumbers)
755 | html += '' + this.locale.weekLabel + ' | ';
756 |
757 | $.each(this.locale.daysOfWeek, function(index, dayOfWeek) {
758 | html += '' + dayOfWeek + ' | ';
759 | });
760 |
761 | html += '
';
762 | html += '';
763 | html += '';
764 |
765 | //adjust maxDate to reflect the maxSpan setting in order to
766 | //grey out end dates beyond the maxSpan
767 | if (this.endDate == null && this.maxSpan) {
768 | var maxLimit = this.startDate.clone().add(this.maxSpan).endOf('day');
769 | if (!maxDate || maxLimit.isBefore(maxDate)) {
770 | maxDate = maxLimit;
771 | }
772 | }
773 |
774 | for (var row = 0; row < 6; row++) {
775 | html += '';
776 |
777 | // add week number
778 | if (this.showWeekNumbers)
779 | html += '' + calendar[row][0].week() + ' | ';
780 | else if (this.showISOWeekNumbers)
781 | html += '' + calendar[row][0].isoWeek() + ' | ';
782 |
783 | for (var col = 0; col < 7; col++) {
784 |
785 | var classes = [];
786 |
787 | //highlight today's date
788 | if (calendar[row][col].isSame(new Date(), "day"))
789 | classes.push('today');
790 |
791 | //highlight weekends
792 | if (calendar[row][col].isoWeekday() > 5)
793 | classes.push('weekend');
794 |
795 | //grey out the dates in other months displayed at beginning and end of this calendar
796 | if (calendar[row][col].month() != calendar[1][1].month())
797 | classes.push('off', 'ends');
798 |
799 | //don't allow selection of dates before the minimum date
800 | if (this.minDate && calendar[row][col].isBefore(this.minDate, 'day'))
801 | classes.push('off', 'disabled');
802 |
803 | //don't allow selection of dates after the maximum date
804 | if (maxDate && calendar[row][col].isAfter(maxDate, 'day'))
805 | classes.push('off', 'disabled');
806 |
807 | //don't allow selection of date if a custom function decides it's invalid
808 | if (this.isInvalidDate(calendar[row][col]))
809 | classes.push('off', 'disabled');
810 |
811 | //highlight the currently selected start date
812 | if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD'))
813 | classes.push('active', 'start-date');
814 |
815 | //highlight the currently selected end date
816 | if (this.endDate != null && calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD'))
817 | classes.push('active', 'end-date');
818 |
819 | //highlight dates in-between the selected dates
820 | if (this.endDate != null && calendar[row][col] > this.startDate && calendar[row][col] < this.endDate)
821 | classes.push('in-range');
822 |
823 | //apply custom classes for this date
824 | var isCustom = this.isCustomDate(calendar[row][col]);
825 | if (isCustom !== false) {
826 | if (typeof isCustom === 'string')
827 | classes.push(isCustom);
828 | else
829 | Array.prototype.push.apply(classes, isCustom);
830 | }
831 |
832 | var cname = '', disabled = false;
833 | for (var i = 0; i < classes.length; i++) {
834 | cname += classes[i] + ' ';
835 | if (classes[i] == 'disabled')
836 | disabled = true;
837 | }
838 | if (!disabled)
839 | cname += 'available';
840 |
841 | html += '' + calendar[row][col].date() + ' | ';
842 |
843 | }
844 | html += '
';
845 | }
846 |
847 | html += '';
848 | html += '
';
849 |
850 | this.container.find('.drp-calendar.' + side + ' .calendar-table').html(html);
851 |
852 | },
853 |
854 | renderTimePicker: function(side) {
855 |
856 | // Don't bother updating the time picker if it's currently disabled
857 | // because an end date hasn't been clicked yet
858 | if (side == 'right' && !this.endDate) return;
859 |
860 | var html, selected, minDate, maxDate = this.maxDate;
861 |
862 | if (this.maxSpan && (!this.maxDate || this.startDate.clone().add(this.maxSpan).isBefore(this.maxDate)))
863 | maxDate = this.startDate.clone().add(this.maxSpan);
864 |
865 | if (side == 'left') {
866 | selected = this.startDate.clone();
867 | minDate = this.minDate;
868 | } else if (side == 'right') {
869 | selected = this.endDate.clone();
870 | minDate = this.startDate;
871 |
872 | //Preserve the time already selected
873 | var timeSelector = this.container.find('.drp-calendar.right .calendar-time');
874 | if (timeSelector.html() != '') {
875 |
876 | selected.hour(!isNaN(selected.hour()) ? selected.hour() : timeSelector.find('.hourselect option:selected').val());
877 | selected.minute(!isNaN(selected.minute()) ? selected.minute() : timeSelector.find('.minuteselect option:selected').val());
878 | selected.second(!isNaN(selected.second()) ? selected.second() : timeSelector.find('.secondselect option:selected').val());
879 |
880 | if (!this.timePicker24Hour) {
881 | var ampm = timeSelector.find('.ampmselect option:selected').val();
882 | if (ampm === 'PM' && selected.hour() < 12)
883 | selected.hour(selected.hour() + 12);
884 | if (ampm === 'AM' && selected.hour() === 12)
885 | selected.hour(0);
886 | }
887 |
888 | }
889 |
890 | if (selected.isBefore(this.startDate))
891 | selected = this.startDate.clone();
892 |
893 | if (maxDate && selected.isAfter(maxDate))
894 | selected = maxDate.clone();
895 |
896 | }
897 |
898 | //
899 | // hours
900 | //
901 |
902 | html = ' ';
929 |
930 | //
931 | // minutes
932 | //
933 |
934 | html += ': ';
956 |
957 | //
958 | // seconds
959 | //
960 |
961 | if (this.timePickerSeconds) {
962 | html += ': ';
984 | }
985 |
986 | //
987 | // AM/PM
988 | //
989 |
990 | if (!this.timePicker24Hour) {
991 | html += '';
1009 | }
1010 |
1011 | this.container.find('.drp-calendar.' + side + ' .calendar-time').html(html);
1012 |
1013 | },
1014 |
1015 | updateFormInputs: function() {
1016 |
1017 | if (this.singleDatePicker || (this.endDate && (this.startDate.isBefore(this.endDate) || this.startDate.isSame(this.endDate)))) {
1018 | this.container.find('button.applyBtn').removeAttr('disabled');
1019 | } else {
1020 | this.container.find('button.applyBtn').attr('disabled', 'disabled');
1021 | }
1022 |
1023 | },
1024 |
1025 | move: function() {
1026 | var parentOffset = { top: 0, left: 0 },
1027 | containerTop;
1028 | var parentRightEdge = $(window).width();
1029 | if (!this.parentEl.is('body')) {
1030 | parentOffset = {
1031 | top: this.parentEl.offset().top - this.parentEl.scrollTop(),
1032 | left: this.parentEl.offset().left - this.parentEl.scrollLeft()
1033 | };
1034 | parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left;
1035 | }
1036 |
1037 | if (this.drops == 'up')
1038 | containerTop = this.element.offset().top - this.container.outerHeight() - parentOffset.top;
1039 | else
1040 | containerTop = this.element.offset().top + this.element.outerHeight() - parentOffset.top;
1041 |
1042 | // Force the container to it's actual width
1043 | this.container.css({
1044 | top: 0,
1045 | left: 0,
1046 | right: 'auto'
1047 | });
1048 | var containerWidth = this.container.outerWidth();
1049 |
1050 | this.container[this.drops == 'up' ? 'addClass' : 'removeClass']('drop-up');
1051 |
1052 | if (this.opens == 'left') {
1053 | var containerRight = parentRightEdge - this.element.offset().left - this.element.outerWidth();
1054 | if (containerWidth + containerRight > $(window).width()) {
1055 | this.container.css({
1056 | top: containerTop,
1057 | right: 'auto',
1058 | left: 9
1059 | });
1060 | } else {
1061 | this.container.css({
1062 | top: containerTop,
1063 | right: containerRight,
1064 | left: 'auto'
1065 | });
1066 | }
1067 | } else if (this.opens == 'center') {
1068 | var containerLeft = this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2
1069 | - containerWidth / 2;
1070 | if (containerLeft < 0) {
1071 | this.container.css({
1072 | top: containerTop,
1073 | right: 'auto',
1074 | left: 9
1075 | });
1076 | } else if (containerLeft + containerWidth > $(window).width()) {
1077 | this.container.css({
1078 | top: containerTop,
1079 | left: 'auto',
1080 | right: 0
1081 | });
1082 | } else {
1083 | this.container.css({
1084 | top: containerTop,
1085 | left: containerLeft,
1086 | right: 'auto'
1087 | });
1088 | }
1089 | } else {
1090 | var containerLeft = this.element.offset().left - parentOffset.left;
1091 | if (containerLeft + containerWidth > $(window).width()) {
1092 | this.container.css({
1093 | top: containerTop,
1094 | left: 'auto',
1095 | right: 0
1096 | });
1097 | } else {
1098 | this.container.css({
1099 | top: containerTop,
1100 | left: containerLeft,
1101 | right: 'auto'
1102 | });
1103 | }
1104 | }
1105 | },
1106 |
1107 | show: function(e) {
1108 | if (this.isShowing) return;
1109 |
1110 | // Create a click proxy that is private to this instance of datepicker, for unbinding
1111 | this._outsideClickProxy = $.proxy(function(e) { this.outsideClick(e); }, this);
1112 |
1113 | // Bind global datepicker mousedown for hiding and
1114 | $(document)
1115 | .on('mousedown.daterangepicker', this._outsideClickProxy)
1116 | // also support mobile devices
1117 | .on('touchend.daterangepicker', this._outsideClickProxy)
1118 | // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
1119 | .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
1120 | // and also close when focus changes to outside the picker (eg. tabbing between controls)
1121 | .on('focusin.daterangepicker', this._outsideClickProxy);
1122 |
1123 | // Reposition the picker if the window is resized while it's open
1124 | $(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this));
1125 |
1126 | this.oldStartDate = this.startDate.clone();
1127 | this.oldEndDate = this.endDate.clone();
1128 | this.previousRightTime = this.endDate.clone();
1129 |
1130 | this.updateView();
1131 | this.container.show();
1132 | this.move();
1133 | this.element.trigger('show.daterangepicker', this);
1134 | this.isShowing = true;
1135 | },
1136 |
1137 | hide: function(e) {
1138 | if (!this.isShowing) return;
1139 |
1140 | //incomplete date selection, revert to last values
1141 | if (!this.endDate) {
1142 | this.startDate = this.oldStartDate.clone();
1143 | this.endDate = this.oldEndDate.clone();
1144 | }
1145 |
1146 | //if a new date range was selected, invoke the user callback function
1147 | if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
1148 | this.callback(this.startDate.clone(), this.endDate.clone(), this.chosenLabel);
1149 |
1150 | //if picker is attached to a text input, update it
1151 | this.updateElement();
1152 |
1153 | $(document).off('.daterangepicker');
1154 | $(window).off('.daterangepicker');
1155 | this.container.hide();
1156 | this.element.trigger('hide.daterangepicker', this);
1157 | this.isShowing = false;
1158 | },
1159 |
1160 | toggle: function(e) {
1161 | if (this.isShowing) {
1162 | this.hide();
1163 | } else {
1164 | this.show();
1165 | }
1166 | },
1167 |
1168 | outsideClick: function(e) {
1169 | var target = $(e.target);
1170 | // if the page is clicked anywhere except within the daterangerpicker/button
1171 | // itself then call this.hide()
1172 | if (
1173 | // ie modal dialog fix
1174 | e.type == "focusin" ||
1175 | target.closest(this.element).length ||
1176 | target.closest(this.container).length ||
1177 | target.closest('.calendar-table').length
1178 | ) return;
1179 | this.hide();
1180 | this.element.trigger('outsideClick.daterangepicker', this);
1181 | },
1182 |
1183 | showCalendars: function() {
1184 | this.container.addClass('show-calendar');
1185 | this.move();
1186 | this.element.trigger('showCalendar.daterangepicker', this);
1187 | },
1188 |
1189 | hideCalendars: function() {
1190 | this.container.removeClass('show-calendar');
1191 | this.element.trigger('hideCalendar.daterangepicker', this);
1192 | },
1193 |
1194 | clickRange: function(e) {
1195 | var label = e.target.getAttribute('data-range-key');
1196 | this.chosenLabel = label;
1197 | if (label == this.locale.customRangeLabel) {
1198 | this.showCalendars();
1199 | } else {
1200 | var dates = this.ranges[label];
1201 | this.startDate = dates[0];
1202 | this.endDate = dates[1];
1203 |
1204 | if (!this.timePicker) {
1205 | this.startDate.startOf('day');
1206 | this.endDate.endOf('day');
1207 | }
1208 |
1209 | if (!this.alwaysShowCalendars)
1210 | this.hideCalendars();
1211 | this.clickApply();
1212 | }
1213 | },
1214 |
1215 | clickPrev: function(e) {
1216 | var cal = $(e.target).parents('.drp-calendar');
1217 | if (cal.hasClass('left')) {
1218 | this.leftCalendar.month.subtract(1, 'month');
1219 | if (this.linkedCalendars)
1220 | this.rightCalendar.month.subtract(1, 'month');
1221 | } else {
1222 | this.rightCalendar.month.subtract(1, 'month');
1223 | }
1224 | this.updateCalendars();
1225 | },
1226 |
1227 | clickNext: function(e) {
1228 | var cal = $(e.target).parents('.drp-calendar');
1229 | if (cal.hasClass('left')) {
1230 | this.leftCalendar.month.add(1, 'month');
1231 | } else {
1232 | this.rightCalendar.month.add(1, 'month');
1233 | if (this.linkedCalendars)
1234 | this.leftCalendar.month.add(1, 'month');
1235 | }
1236 | this.updateCalendars();
1237 | },
1238 |
1239 | hoverDate: function(e) {
1240 |
1241 | //ignore dates that can't be selected
1242 | if (!$(e.target).hasClass('available')) return;
1243 |
1244 | var title = $(e.target).attr('data-title');
1245 | var row = title.substr(1, 1);
1246 | var col = title.substr(3, 1);
1247 | var cal = $(e.target).parents('.drp-calendar');
1248 | var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1249 |
1250 | //highlight the dates between the start date and the date being hovered as a potential end date
1251 | var leftCalendar = this.leftCalendar;
1252 | var rightCalendar = this.rightCalendar;
1253 | var startDate = this.startDate;
1254 | if (!this.endDate) {
1255 | this.container.find('.drp-calendar tbody td').each(function(index, el) {
1256 |
1257 | //skip week numbers, only look at dates
1258 | if ($(el).hasClass('week')) return;
1259 |
1260 | var title = $(el).attr('data-title');
1261 | var row = title.substr(1, 1);
1262 | var col = title.substr(3, 1);
1263 | var cal = $(el).parents('.drp-calendar');
1264 | var dt = cal.hasClass('left') ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col];
1265 |
1266 | if ((dt.isAfter(startDate) && dt.isBefore(date)) || dt.isSame(date, 'day')) {
1267 | $(el).addClass('in-range');
1268 | } else {
1269 | $(el).removeClass('in-range');
1270 | }
1271 |
1272 | });
1273 | }
1274 |
1275 | },
1276 |
1277 | clickDate: function(e) {
1278 |
1279 | if (!$(e.target).hasClass('available')) return;
1280 |
1281 | var title = $(e.target).attr('data-title');
1282 | var row = title.substr(1, 1);
1283 | var col = title.substr(3, 1);
1284 | var cal = $(e.target).parents('.drp-calendar');
1285 | var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1286 |
1287 | //
1288 | // this function needs to do a few things:
1289 | // * alternate between selecting a start and end date for the range,
1290 | // * if the time picker is enabled, apply the hour/minute/second from the select boxes to the clicked date
1291 | // * if autoapply is enabled, and an end date was chosen, apply the selection
1292 | // * if single date picker mode, and time picker isn't enabled, apply the selection immediately
1293 | // * if one of the inputs above the calendars was focused, cancel that manual input
1294 | //
1295 |
1296 | if (this.endDate || date.isBefore(this.startDate, 'day')) { //picking start
1297 | if (this.timePicker) {
1298 | var hour = parseInt(this.container.find('.left .hourselect').val(), 10);
1299 | if (!this.timePicker24Hour) {
1300 | var ampm = this.container.find('.left .ampmselect').val();
1301 | if (ampm === 'PM' && hour < 12)
1302 | hour += 12;
1303 | if (ampm === 'AM' && hour === 12)
1304 | hour = 0;
1305 | }
1306 | var minute = parseInt(this.container.find('.left .minuteselect').val(), 10);
1307 | if (isNaN(minute)) {
1308 | minute = parseInt(this.container.find('.left .minuteselect option:last').val(), 10);
1309 | }
1310 | var second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0;
1311 | date = date.clone().hour(hour).minute(minute).second(second);
1312 | }
1313 | this.endDate = null;
1314 | this.setStartDate(date.clone());
1315 | } else if (!this.endDate && date.isBefore(this.startDate)) {
1316 | //special case: clicking the same date for start/end,
1317 | //but the time of the end date is before the start date
1318 | this.setEndDate(this.startDate.clone());
1319 | } else { // picking end
1320 | if (this.timePicker) {
1321 | var hour = parseInt(this.container.find('.right .hourselect').val(), 10);
1322 | if (!this.timePicker24Hour) {
1323 | var ampm = this.container.find('.right .ampmselect').val();
1324 | if (ampm === 'PM' && hour < 12)
1325 | hour += 12;
1326 | if (ampm === 'AM' && hour === 12)
1327 | hour = 0;
1328 | }
1329 | var minute = parseInt(this.container.find('.right .minuteselect').val(), 10);
1330 | if (isNaN(minute)) {
1331 | minute = parseInt(this.container.find('.right .minuteselect option:last').val(), 10);
1332 | }
1333 | var second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0;
1334 | date = date.clone().hour(hour).minute(minute).second(second);
1335 | }
1336 | this.setEndDate(date.clone());
1337 | if (this.autoApply) {
1338 | this.calculateChosenLabel();
1339 | this.clickApply();
1340 | }
1341 | }
1342 |
1343 | if (this.singleDatePicker) {
1344 | this.setEndDate(this.startDate);
1345 | if (!this.timePicker)
1346 | this.clickApply();
1347 | }
1348 |
1349 | this.updateView();
1350 |
1351 | //This is to cancel the blur event handler if the mouse was in one of the inputs
1352 | e.stopPropagation();
1353 |
1354 | },
1355 |
1356 | calculateChosenLabel: function () {
1357 | var customRange = true;
1358 | var i = 0;
1359 | for (var range in this.ranges) {
1360 | if (this.timePicker) {
1361 | var format = this.timePickerSeconds ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD HH:mm";
1362 | //ignore times when comparing dates if time picker seconds is not enabled
1363 | if (this.startDate.format(format) == this.ranges[range][0].format(format) && this.endDate.format(format) == this.ranges[range][1].format(format)) {
1364 | customRange = false;
1365 | this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').attr('data-range-key');
1366 | break;
1367 | }
1368 | } else {
1369 | //ignore times when comparing dates if time picker is not enabled
1370 | if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) {
1371 | customRange = false;
1372 | this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').attr('data-range-key');
1373 | break;
1374 | }
1375 | }
1376 | i++;
1377 | }
1378 | if (customRange) {
1379 | if (this.showCustomRangeLabel) {
1380 | this.chosenLabel = this.container.find('.ranges li:last').addClass('active').attr('data-range-key');
1381 | } else {
1382 | this.chosenLabel = null;
1383 | }
1384 | this.showCalendars();
1385 | }
1386 | },
1387 |
1388 | clickApply: function(e) {
1389 | this.hide();
1390 | this.element.trigger('apply.daterangepicker', this);
1391 | },
1392 |
1393 | clickCancel: function(e) {
1394 | this.startDate = this.oldStartDate;
1395 | this.endDate = this.oldEndDate;
1396 | this.hide();
1397 | this.element.trigger('cancel.daterangepicker', this);
1398 | },
1399 |
1400 | monthOrYearChanged: function(e) {
1401 | var isLeft = $(e.target).closest('.drp-calendar').hasClass('left'),
1402 | leftOrRight = isLeft ? 'left' : 'right',
1403 | cal = this.container.find('.drp-calendar.'+leftOrRight);
1404 |
1405 | // Month must be Number for new moment versions
1406 | var month = parseInt(cal.find('.monthselect').val(), 10);
1407 | var year = cal.find('.yearselect').val();
1408 |
1409 | if (!isLeft) {
1410 | if (year < this.startDate.year() || (year == this.startDate.year() && month < this.startDate.month())) {
1411 | month = this.startDate.month();
1412 | year = this.startDate.year();
1413 | }
1414 | }
1415 |
1416 | if (this.minDate) {
1417 | if (year < this.minDate.year() || (year == this.minDate.year() && month < this.minDate.month())) {
1418 | month = this.minDate.month();
1419 | year = this.minDate.year();
1420 | }
1421 | }
1422 |
1423 | if (this.maxDate) {
1424 | if (year > this.maxDate.year() || (year == this.maxDate.year() && month > this.maxDate.month())) {
1425 | month = this.maxDate.month();
1426 | year = this.maxDate.year();
1427 | }
1428 | }
1429 |
1430 | if (isLeft) {
1431 | this.leftCalendar.month.month(month).year(year);
1432 | if (this.linkedCalendars)
1433 | this.rightCalendar.month = this.leftCalendar.month.clone().add(1, 'month');
1434 | } else {
1435 | this.rightCalendar.month.month(month).year(year);
1436 | if (this.linkedCalendars)
1437 | this.leftCalendar.month = this.rightCalendar.month.clone().subtract(1, 'month');
1438 | }
1439 | this.updateCalendars();
1440 | },
1441 |
1442 | timeChanged: function(e) {
1443 |
1444 | var cal = $(e.target).closest('.drp-calendar'),
1445 | isLeft = cal.hasClass('left');
1446 |
1447 | var hour = parseInt(cal.find('.hourselect').val(), 10);
1448 | var minute = parseInt(cal.find('.minuteselect').val(), 10);
1449 | if (isNaN(minute)) {
1450 | minute = parseInt(cal.find('.minuteselect option:last').val(), 10);
1451 | }
1452 | var second = this.timePickerSeconds ? parseInt(cal.find('.secondselect').val(), 10) : 0;
1453 |
1454 | if (!this.timePicker24Hour) {
1455 | var ampm = cal.find('.ampmselect').val();
1456 | if (ampm === 'PM' && hour < 12)
1457 | hour += 12;
1458 | if (ampm === 'AM' && hour === 12)
1459 | hour = 0;
1460 | }
1461 |
1462 | if (isLeft) {
1463 | var start = this.startDate.clone();
1464 | start.hour(hour);
1465 | start.minute(minute);
1466 | start.second(second);
1467 | this.setStartDate(start);
1468 | if (this.singleDatePicker) {
1469 | this.endDate = this.startDate.clone();
1470 | } else if (this.endDate && this.endDate.format('YYYY-MM-DD') == start.format('YYYY-MM-DD') && this.endDate.isBefore(start)) {
1471 | this.setEndDate(start.clone());
1472 | }
1473 | } else if (this.endDate) {
1474 | var end = this.endDate.clone();
1475 | end.hour(hour);
1476 | end.minute(minute);
1477 | end.second(second);
1478 | this.setEndDate(end);
1479 | }
1480 |
1481 | //update the calendars so all clickable dates reflect the new time component
1482 | this.updateCalendars();
1483 |
1484 | //update the form inputs above the calendars with the new time
1485 | this.updateFormInputs();
1486 |
1487 | //re-render the time pickers because changing one selection can affect what's enabled in another
1488 | this.renderTimePicker('left');
1489 | this.renderTimePicker('right');
1490 |
1491 | },
1492 |
1493 | elementChanged: function() {
1494 | if (!this.element.is('input')) return;
1495 | if (!this.element.val().length) return;
1496 |
1497 | var dateString = this.element.val().split(this.locale.separator),
1498 | start = null,
1499 | end = null;
1500 |
1501 | if (dateString.length === 2) {
1502 | start = moment(dateString[0], this.locale.format);
1503 | end = moment(dateString[1], this.locale.format);
1504 | }
1505 |
1506 | if (this.singleDatePicker || start === null || end === null) {
1507 | start = moment(this.element.val(), this.locale.format);
1508 | end = start;
1509 | }
1510 |
1511 | if (!start.isValid() || !end.isValid()) return;
1512 |
1513 | this.setStartDate(start);
1514 | this.setEndDate(end);
1515 | this.updateView();
1516 | },
1517 |
1518 | keydown: function(e) {
1519 | //hide on tab or enter
1520 | if ((e.keyCode === 9) || (e.keyCode === 13)) {
1521 | this.hide();
1522 | }
1523 |
1524 | //hide on esc and prevent propagation
1525 | if (e.keyCode === 27) {
1526 | e.preventDefault();
1527 | e.stopPropagation();
1528 |
1529 | this.hide();
1530 | }
1531 | },
1532 |
1533 | updateElement: function() {
1534 | if (this.element.is('input') && this.autoUpdateInput) {
1535 | var newValue = this.startDate.format(this.locale.format);
1536 | if (!this.singleDatePicker) {
1537 | newValue += this.locale.separator + this.endDate.format(this.locale.format);
1538 | }
1539 | if (newValue !== this.element.val()) {
1540 | this.element.val(newValue).trigger('change');
1541 | }
1542 | }
1543 | },
1544 |
1545 | remove: function() {
1546 | this.container.remove();
1547 | this.element.off('.daterangepicker');
1548 | this.element.removeData();
1549 | }
1550 |
1551 | };
1552 |
1553 | $.fn.daterangepicker = function(options, callback) {
1554 | var implementOptions = $.extend(true, {}, $.fn.daterangepicker.defaultOptions, options);
1555 | this.each(function() {
1556 | var el = $(this);
1557 | if (el.data('daterangepicker'))
1558 | el.data('daterangepicker').remove();
1559 | el.data('daterangepicker', new DateRangePicker(el, implementOptions, callback));
1560 | });
1561 | return this;
1562 | };
1563 |
1564 | return DateRangePicker;
1565 |
1566 | }));
1567 |
--------------------------------------------------------------------------------
/static/js/moment-jalaali.js:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/jalaali/moment-jalaali/blob/master/build/moment-jalaali.js
3 | */
4 |
5 | ;(function(){
6 |
7 | /**
8 | * Require the module at `name`.
9 | *
10 | * @param {String} name
11 | * @return {Object} exports
12 | * @api public
13 | */
14 |
15 | function require(name) {
16 | var module = require.modules[name];
17 | if (!module) throw new Error('failed to require "' + name + '"');
18 |
19 | if (!('exports' in module) && typeof module.definition === 'function') {
20 | module.client = module.component = true;
21 | module.definition.call(this, module.exports = {}, module);
22 | delete module.definition;
23 | }
24 |
25 | return module.exports;
26 | }
27 |
28 | /**
29 | * Registered modules.
30 | */
31 |
32 | require.modules = {
33 | moment: { exports: moment }
34 | };
35 |
36 | /**
37 | * Register module at `name` with callback `definition`.
38 | *
39 | * @param {String} name
40 | * @param {Function} definition
41 | * @api private
42 | */
43 |
44 | require.register = function (name, definition) {
45 | require.modules[name] = {
46 | definition: definition
47 | };
48 | };
49 |
50 | /**
51 | * Define a module's exports immediately with `exports`.
52 | *
53 | * @param {String} name
54 | * @param {Generic} exports
55 | * @api private
56 | */
57 |
58 | require.define = function (name, exports) {
59 | require.modules[name] = {
60 | exports: exports
61 | };
62 | };
63 |
64 | require.register("jalaali-js", function (exports, module) {
65 | /*
66 | Expose functions.
67 | */
68 | module.exports =
69 | { toJalaali: toJalaali
70 | , toGregorian: toGregorian
71 | , isValidJalaaliDate: isValidJalaaliDate
72 | , isLeapJalaaliYear: isLeapJalaaliYear
73 | , jalaaliMonthLength: jalaaliMonthLength
74 | , jalCal: jalCal
75 | , j2d: j2d
76 | , d2j: d2j
77 | , g2d: g2d
78 | , d2g: d2g
79 | }
80 |
81 | /*
82 | Converts a Gregorian date to Jalaali.
83 | */
84 | function toJalaali(gy, gm, gd) {
85 | if (Object.prototype.toString.call(gy) === '[object Date]') {
86 | gd = gy.getDate()
87 | gm = gy.getMonth() + 1
88 | gy = gy.getFullYear()
89 | }
90 | return d2j(g2d(gy, gm, gd))
91 | }
92 |
93 | /*
94 | Converts a Jalaali date to Gregorian.
95 | */
96 | function toGregorian(jy, jm, jd) {
97 | return d2g(j2d(jy, jm, jd))
98 | }
99 |
100 | /*
101 | Checks whether a Jalaali date is valid or not.
102 | */
103 | function isValidJalaaliDate(jy, jm, jd) {
104 | return jy >= -61 && jy <= 3177 &&
105 | jm >= 1 && jm <= 12 &&
106 | jd >= 1 && jd <= jalaaliMonthLength(jy, jm)
107 | }
108 |
109 | /*
110 | Is this a leap year or not?
111 | */
112 | function isLeapJalaaliYear(jy) {
113 | return jalCal(jy).leap === 0
114 | }
115 |
116 | /*
117 | Number of days in a given month in a Jalaali year.
118 | */
119 | function jalaaliMonthLength(jy, jm) {
120 | if (jm <= 6) return 31
121 | if (jm <= 11) return 30
122 | if (isLeapJalaaliYear(jy)) return 30
123 | return 29
124 | }
125 |
126 | /*
127 | This function determines if the Jalaali (Persian) year is
128 | leap (366-day long) or is the common year (365 days), and
129 | finds the day in March (Gregorian calendar) of the first
130 | day of the Jalaali year (jy).
131 |
132 | @param jy Jalaali calendar year (-61 to 3177)
133 | @return
134 | leap: number of years since the last leap year (0 to 4)
135 | gy: Gregorian year of the beginning of Jalaali year
136 | march: the March day of Farvardin the 1st (1st day of jy)
137 | @see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm
138 | @see: http://www.fourmilab.ch/documents/calendar/
139 | */
140 | function jalCal(jy) {
141 | // Jalaali years starting the 33-year rule.
142 | var breaks = [ -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210
143 | , 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
144 | ]
145 | , bl = breaks.length
146 | , gy = jy + 621
147 | , leapJ = -14
148 | , jp = breaks[0]
149 | , jm
150 | , jump
151 | , leap
152 | , leapG
153 | , march
154 | , n
155 | , i
156 |
157 | if (jy < jp || jy >= breaks[bl - 1])
158 | throw new Error('Invalid Jalaali year ' + jy)
159 |
160 | // Find the limiting years for the Jalaali year jy.
161 | for (i = 1; i < bl; i += 1) {
162 | jm = breaks[i]
163 | jump = jm - jp
164 | if (jy < jm)
165 | break
166 | leapJ = leapJ + div(jump, 33) * 8 + div(mod(jump, 33), 4)
167 | jp = jm
168 | }
169 | n = jy - jp
170 |
171 | // Find the number of leap years from AD 621 to the beginning
172 | // of the current Jalaali year in the Persian calendar.
173 | leapJ = leapJ + div(n, 33) * 8 + div(mod(n, 33) + 3, 4)
174 | if (mod(jump, 33) === 4 && jump - n === 4)
175 | leapJ += 1
176 |
177 | // And the same in the Gregorian calendar (until the year gy).
178 | leapG = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150
179 |
180 | // Determine the Gregorian date of Farvardin the 1st.
181 | march = 20 + leapJ - leapG
182 |
183 | // Find how many years have passed since the last leap year.
184 | if (jump - n < 6)
185 | n = n - jump + div(jump + 4, 33) * 33
186 | leap = mod(mod(n + 1, 33) - 1, 4)
187 | if (leap === -1) {
188 | leap = 4
189 | }
190 |
191 | return { leap: leap
192 | , gy: gy
193 | , march: march
194 | }
195 | }
196 |
197 | /*
198 | Converts a date of the Jalaali calendar to the Julian Day number.
199 |
200 | @param jy Jalaali year (1 to 3100)
201 | @param jm Jalaali month (1 to 12)
202 | @param jd Jalaali day (1 to 29/31)
203 | @return Julian Day number
204 | */
205 | function j2d(jy, jm, jd) {
206 | var r = jalCal(jy)
207 | return g2d(r.gy, 3, r.march) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1
208 | }
209 |
210 | /*
211 | Converts the Julian Day number to a date in the Jalaali calendar.
212 |
213 | @param jdn Julian Day number
214 | @return
215 | jy: Jalaali year (1 to 3100)
216 | jm: Jalaali month (1 to 12)
217 | jd: Jalaali day (1 to 29/31)
218 | */
219 | function d2j(jdn) {
220 | var gy = d2g(jdn).gy // Calculate Gregorian year (gy).
221 | , jy = gy - 621
222 | , r = jalCal(jy)
223 | , jdn1f = g2d(gy, 3, r.march)
224 | , jd
225 | , jm
226 | , k
227 |
228 | // Find number of days that passed since 1 Farvardin.
229 | k = jdn - jdn1f
230 | if (k >= 0) {
231 | if (k <= 185) {
232 | // The first 6 months.
233 | jm = 1 + div(k, 31)
234 | jd = mod(k, 31) + 1
235 | return { jy: jy
236 | , jm: jm
237 | , jd: jd
238 | }
239 | } else {
240 | // The remaining months.
241 | k -= 186
242 | }
243 | } else {
244 | // Previous Jalaali year.
245 | jy -= 1
246 | k += 179
247 | if (r.leap === 1)
248 | k += 1
249 | }
250 | jm = 7 + div(k, 30)
251 | jd = mod(k, 30) + 1
252 | return { jy: jy
253 | , jm: jm
254 | , jd: jd
255 | }
256 | }
257 |
258 | /*
259 | Calculates the Julian Day number from Gregorian or Julian
260 | calendar dates. This integer number corresponds to the noon of
261 | the date (i.e. 12 hours of Universal Time).
262 | The procedure was tested to be good since 1 March, -100100 (of both
263 | calendars) up to a few million years into the future.
264 |
265 | @param gy Calendar year (years BC numbered 0, -1, -2, ...)
266 | @param gm Calendar month (1 to 12)
267 | @param gd Calendar day of the month (1 to 28/29/30/31)
268 | @return Julian Day number
269 | */
270 | function g2d(gy, gm, gd) {
271 | var d = div((gy + div(gm - 8, 6) + 100100) * 1461, 4)
272 | + div(153 * mod(gm + 9, 12) + 2, 5)
273 | + gd - 34840408
274 | d = d - div(div(gy + 100100 + div(gm - 8, 6), 100) * 3, 4) + 752
275 | return d
276 | }
277 |
278 | /*
279 | Calculates Gregorian and Julian calendar dates from the Julian Day number
280 | (jdn) for the period since jdn=-34839655 (i.e. the year -100100 of both
281 | calendars) to some millions years ahead of the present.
282 |
283 | @param jdn Julian Day number
284 | @return
285 | gy: Calendar year (years BC numbered 0, -1, -2, ...)
286 | gm: Calendar month (1 to 12)
287 | gd: Calendar day of the month M (1 to 28/29/30/31)
288 | */
289 | function d2g(jdn) {
290 | var j
291 | , i
292 | , gd
293 | , gm
294 | , gy
295 | j = 4 * jdn + 139361631
296 | j = j + div(div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908
297 | i = div(mod(j, 1461), 4) * 5 + 308
298 | gd = div(mod(i, 153), 5) + 1
299 | gm = mod(div(i, 153), 12) + 1
300 | gy = div(j, 1461) - 100100 + div(8 - gm, 6)
301 | return { gy: gy
302 | , gm: gm
303 | , gd: gd
304 | }
305 | }
306 |
307 | /*
308 | Utility helper functions.
309 | */
310 |
311 | function div(a, b) {
312 | return ~~(a / b)
313 | }
314 |
315 | function mod(a, b) {
316 | return a - ~~(a / b) * b
317 | }
318 | })
319 | require.register("moment-jalaali", function (exports, module) {
320 |
321 | module.exports = jMoment
322 |
323 | var moment = require('moment')
324 | , jalaali = require('jalaali-js')
325 |
326 | /************************************
327 | Constants
328 | ************************************/
329 |
330 | var formattingTokens = /(\[[^\[]*\])|(\\)?j(Mo|MM?M?M?|Do|DDDo|DD?D?D?|w[o|w]?|YYYYY|YYYY|YY|gg(ggg?)?|)|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g
331 | , localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS?|LL?L?L?|l{1,4})/g
332 |
333 | , parseTokenOneOrTwoDigits = /\d\d?/
334 | , parseTokenOneToThreeDigits = /\d{1,3}/
335 | , parseTokenThreeDigits = /\d{3}/
336 | , parseTokenFourDigits = /\d{1,4}/
337 | , parseTokenSixDigits = /[+\-]?\d{1,6}/
338 | , parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i
339 | , parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i
340 | , parseTokenT = /T/i
341 | , parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/
342 | , symbolMap = {
343 | '1': '۱',
344 | '2': '۲',
345 | '3': '۳',
346 | '4': '۴',
347 | '5': '۵',
348 | '6': '۶',
349 | '7': '۷',
350 | '8': '۸',
351 | '9': '۹',
352 | '0': '۰'
353 | }
354 | , numberMap = {
355 | '۱': '1',
356 | '۲': '2',
357 | '۳': '3',
358 | '۴': '4',
359 | '۵': '5',
360 | '۶': '6',
361 | '۷': '7',
362 | '۸': '8',
363 | '۹': '9',
364 | '۰': '0'
365 | }
366 |
367 |
368 | , unitAliases =
369 | { jm: 'jmonth'
370 | , jmonths: 'jmonth'
371 | , jy: 'jyear'
372 | , jyears: 'jyear'
373 | }
374 |
375 | , formatFunctions = {}
376 |
377 | , ordinalizeTokens = 'DDD w M D'.split(' ')
378 | , paddedTokens = 'M D w'.split(' ')
379 |
380 | , formatTokenFunctions =
381 | { jM: function () {
382 | return this.jMonth() + 1
383 | }
384 | , jMMM: function (format) {
385 | return this.localeData().jMonthsShort(this, format)
386 | }
387 | , jMMMM: function (format) {
388 | return this.localeData().jMonths(this, format)
389 | }
390 | , jD: function () {
391 | return this.jDate()
392 | }
393 | , jDDD: function () {
394 | return this.jDayOfYear()
395 | }
396 | , jw: function () {
397 | return this.jWeek()
398 | }
399 | , jYY: function () {
400 | return leftZeroFill(this.jYear() % 100, 2)
401 | }
402 | , jYYYY: function () {
403 | return leftZeroFill(this.jYear(), 4)
404 | }
405 | , jYYYYY: function () {
406 | return leftZeroFill(this.jYear(), 5)
407 | }
408 | , jgg: function () {
409 | return leftZeroFill(this.jWeekYear() % 100, 2)
410 | }
411 | , jgggg: function () {
412 | return this.jWeekYear()
413 | }
414 | , jggggg: function () {
415 | return leftZeroFill(this.jWeekYear(), 5)
416 | }
417 | }
418 |
419 | function padToken(func, count) {
420 | return function (a) {
421 | return leftZeroFill(func.call(this, a), count)
422 | }
423 | }
424 | function ordinalizeToken(func, period) {
425 | return function (a) {
426 | return this.localeData().ordinal(func.call(this, a), period)
427 | }
428 | }
429 |
430 | (function () {
431 | var i
432 | while (ordinalizeTokens.length) {
433 | i = ordinalizeTokens.pop()
434 | formatTokenFunctions['j' + i + 'o'] = ordinalizeToken(formatTokenFunctions['j' + i], i)
435 | }
436 | while (paddedTokens.length) {
437 | i = paddedTokens.pop()
438 | formatTokenFunctions['j' + i + i] = padToken(formatTokenFunctions['j' + i], 2)
439 | }
440 | formatTokenFunctions.jDDDD = padToken(formatTokenFunctions.jDDD, 3)
441 | }())
442 |
443 | /************************************
444 | Helpers
445 | ************************************/
446 |
447 | function extend(a, b) {
448 | var key
449 | for (key in b)
450 | if (b.hasOwnProperty(key))
451 | a[key] = b[key]
452 | return a
453 | }
454 |
455 | function leftZeroFill(number, targetLength) {
456 | var output = number + ''
457 | while (output.length < targetLength)
458 | output = '0' + output
459 | return output
460 | }
461 |
462 | function isArray(input) {
463 | return Object.prototype.toString.call(input) === '[object Array]'
464 | }
465 |
466 | // function compareArrays(array1, array2) {
467 | // var len = Math.min(array1.length, array2.length)
468 | // , lengthDiff = Math.abs(array1.length - array2.length)
469 | // , diffs = 0
470 | // , i
471 | // for (i = 0; i < len; i += 1)
472 | // if (~~array1[i] !== ~~array2[i])
473 | // diffs += 1
474 | // return diffs + lengthDiff
475 | // }
476 |
477 | function normalizeUnits(units) {
478 | if (units) {
479 | var lowered = units.toLowerCase()
480 | units = unitAliases[lowered] || lowered
481 | }
482 | return units
483 | }
484 |
485 | function setDate(m, year, month, date) {
486 | var d = m._d
487 | if (isNaN(year)) {
488 | m._isValid = false
489 | }
490 | if (m._isUTC) {
491 | /*eslint-disable new-cap*/
492 | m._d = new Date(Date.UTC(year, month, date,
493 | d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()))
494 | /*eslint-enable new-cap*/
495 | } else {
496 | m._d = new Date(year, month, date,
497 | d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())
498 | }
499 | }
500 |
501 | function objectCreate(parent) {
502 | function F() {}
503 | F.prototype = parent
504 | return new F()
505 | }
506 |
507 | function getPrototypeOf(object) {
508 | if (Object.getPrototypeOf)
509 | return Object.getPrototypeOf(object)
510 | else if (''.__proto__)
511 | return object.__proto__
512 | else
513 | return object.constructor.prototype
514 | }
515 |
516 | /************************************
517 | Languages
518 | ************************************/
519 | extend(getPrototypeOf(moment.localeData()),
520 | { _jMonths: [ 'فروردین'
521 | , 'اردیبهشت'
522 | , 'خرداد'
523 | , 'تیر'
524 | , 'مرداد'
525 | , 'شهریور'
526 | , 'مهر'
527 | , 'آبان'
528 | , 'آذر'
529 | , 'دی'
530 | , 'بهمن'
531 | , 'اسفند'
532 | ]
533 | , jMonths: function (m) {
534 | return this._jMonths[m.jMonth()]
535 | }
536 |
537 | , _jMonthsShort: [ 'Far'
538 | , 'Ord'
539 | , 'Kho'
540 | , 'Tir'
541 | , 'Amo'
542 | , 'Sha'
543 | , 'Meh'
544 | , 'Aab'
545 | , 'Aaz'
546 | , 'Dey'
547 | , 'Bah'
548 | , 'Esf'
549 | ]
550 | , jMonthsShort: function (m) {
551 | return this._jMonthsShort[m.jMonth()]
552 | }
553 |
554 | , jMonthsParse: function (monthName) {
555 | var i
556 | , mom
557 | , regex
558 | if (!this._jMonthsParse)
559 | this._jMonthsParse = []
560 | for (i = 0; i < 12; i += 1) {
561 | // Make the regex if we don't have it already.
562 | if (!this._jMonthsParse[i]) {
563 | mom = jMoment([2000, (2 + i) % 12, 25])
564 | regex = '^' + this.jMonths(mom, '') + '|^' + this.jMonthsShort(mom, '')
565 | this._jMonthsParse[i] = new RegExp(regex.replace('.', ''), 'i')
566 | }
567 | // Test the regex.
568 | if (this._jMonthsParse[i].test(monthName))
569 | return i
570 | }
571 | }
572 | }
573 | )
574 |
575 | /************************************
576 | Formatting
577 | ************************************/
578 |
579 | function makeFormatFunction(format) {
580 | var array = format.match(formattingTokens)
581 | , length = array.length
582 | , i
583 |
584 | for (i = 0; i < length; i += 1)
585 | if (formatTokenFunctions[array[i]])
586 | array[i] = formatTokenFunctions[array[i]]
587 |
588 | return function (mom) {
589 | var output = ''
590 | for (i = 0; i < length; i += 1)
591 | output += array[i] instanceof Function ? '[' + array[i].call(mom, format) + ']' : array[i]
592 | return output
593 | }
594 | }
595 |
596 | /************************************
597 | Parsing
598 | ************************************/
599 |
600 | function getParseRegexForToken(token, config) {
601 | switch (token) {
602 | case 'jDDDD':
603 | return parseTokenThreeDigits
604 | case 'jYYYY':
605 | return parseTokenFourDigits
606 | case 'jYYYYY':
607 | return parseTokenSixDigits
608 | case 'jDDD':
609 | return parseTokenOneToThreeDigits
610 | case 'jMMM':
611 | case 'jMMMM':
612 | return parseTokenWord
613 | case 'jMM':
614 | case 'jDD':
615 | case 'jYY':
616 | case 'jM':
617 | case 'jD':
618 | return parseTokenOneOrTwoDigits
619 | case 'DDDD':
620 | return parseTokenThreeDigits
621 | case 'YYYY':
622 | return parseTokenFourDigits
623 | case 'YYYYY':
624 | return parseTokenSixDigits
625 | case 'S':
626 | case 'SS':
627 | case 'SSS':
628 | case 'DDD':
629 | return parseTokenOneToThreeDigits
630 | case 'MMM':
631 | case 'MMMM':
632 | case 'dd':
633 | case 'ddd':
634 | case 'dddd':
635 | return parseTokenWord
636 | case 'a':
637 | case 'A':
638 | return moment.localeData(config._l)._meridiemParse
639 | case 'X':
640 | return parseTokenTimestampMs
641 | case 'Z':
642 | case 'ZZ':
643 | return parseTokenTimezone
644 | case 'T':
645 | return parseTokenT
646 | case 'MM':
647 | case 'DD':
648 | case 'YY':
649 | case 'HH':
650 | case 'hh':
651 | case 'mm':
652 | case 'ss':
653 | case 'M':
654 | case 'D':
655 | case 'd':
656 | case 'H':
657 | case 'h':
658 | case 'm':
659 | case 's':
660 | return parseTokenOneOrTwoDigits
661 | default:
662 | return new RegExp(token.replace('\\', ''))
663 | }
664 | }
665 |
666 | function addTimeToArrayFromToken(token, input, config) {
667 | var a
668 | , datePartArray = config._a
669 |
670 | switch (token) {
671 | case 'jM':
672 | case 'jMM':
673 | datePartArray[1] = input == null ? 0 : ~~input - 1
674 | break
675 | case 'jMMM':
676 | case 'jMMMM':
677 | a = moment.localeData(config._l).jMonthsParse(input)
678 | if (a != null)
679 | datePartArray[1] = a
680 | else
681 | config._isValid = false
682 | break
683 | case 'jD':
684 | case 'jDD':
685 | case 'jDDD':
686 | case 'jDDDD':
687 | if (input != null)
688 | datePartArray[2] = ~~input
689 | break
690 | case 'jYY':
691 | datePartArray[0] = ~~input + (~~input > 47 ? 1300 : 1400)
692 | break
693 | case 'jYYYY':
694 | case 'jYYYYY':
695 | datePartArray[0] = ~~input
696 | }
697 | if (input == null)
698 | config._isValid = false
699 | }
700 |
701 | function dateFromArray(config) {
702 | var g
703 | , j
704 | , jy = config._a[0]
705 | , jm = config._a[1]
706 | , jd = config._a[2]
707 |
708 | if ((jy == null) && (jm == null) && (jd == null))
709 | return [0, 0, 1]
710 | jy = jy != null ? jy : 0
711 | jm = jm != null ? jm : 0
712 | jd = jd != null ? jd : 1
713 | if (jd < 1 || jd > jMoment.jDaysInMonth(jy, jm) || jm < 0 || jm > 11)
714 | config._isValid = false
715 | g = toGregorian(jy, jm, jd)
716 | j = toJalaali(g.gy, g.gm, g.gd)
717 | if (isNaN(g.gy))
718 | config._isValid = false
719 | config._jDiff = 0
720 | if (~~j.jy !== jy)
721 | config._jDiff += 1
722 | if (~~j.jm !== jm)
723 | config._jDiff += 1
724 | if (~~j.jd !== jd)
725 | config._jDiff += 1
726 | return [g.gy, g.gm, g.gd]
727 | }
728 |
729 | function makeDateFromStringAndFormat(config) {
730 | /// Babak
731 | /// config._f can be a function
732 | var __f = typeof config._f ==='function'?config._f():config._f;
733 | var tokens = __f.match(formattingTokens)
734 | , string = config._i + ''
735 | , len = tokens.length
736 | , i
737 | , token
738 | , parsedInput
739 |
740 | config._a = []
741 |
742 | for (i = 0; i < len; i += 1) {
743 | token = tokens[i]
744 | parsedInput = (getParseRegexForToken(token, config).exec(string) || [])[0]
745 | if (parsedInput)
746 | string = string.slice(string.indexOf(parsedInput) + parsedInput.length)
747 | if (formatTokenFunctions[token])
748 | addTimeToArrayFromToken(token, parsedInput, config)
749 | }
750 | if (string)
751 | config._il = string
752 | return dateFromArray(config)
753 | }
754 |
755 | function makeDateFromStringAndArray(config, utc) {
756 | var len = config._f.length
757 | , i
758 | , format
759 | , tempMoment
760 | , bestMoment
761 | , currentScore
762 | , scoreToBeat
763 |
764 | if (len === 0) {
765 | return makeMoment(new Date(NaN))
766 | }
767 |
768 | for (i = 0; i < len; i += 1) {
769 | /// Babak
770 | /// format can be a function!
771 | /// format = config._f[i]
772 | format = typeof config._f[i]=='function'? config._f[i]():config._f[i];
773 | currentScore = 0
774 | tempMoment = makeMoment(config._i, format, config._l, config._strict, utc)
775 |
776 | if (!tempMoment.isValid()) continue
777 |
778 | // currentScore = compareArrays(tempMoment._a, tempMoment.toArray())
779 | currentScore += tempMoment._jDiff
780 | if (tempMoment._il)
781 | currentScore += tempMoment._il.length
782 | if (scoreToBeat == null || currentScore < scoreToBeat) {
783 | scoreToBeat = currentScore
784 | bestMoment = tempMoment
785 | }
786 | }
787 |
788 | return bestMoment
789 | }
790 |
791 | function removeParsedTokens(config) {
792 | var string = config._i + ''
793 | , input = ''
794 | , format = ''
795 | , array = config._f.match(formattingTokens)
796 | , len = array.length
797 | , i
798 | , match
799 | , parsed
800 |
801 | for (i = 0; i < len; i += 1) {
802 | match = array[i]
803 | parsed = (getParseRegexForToken(match, config).exec(string) || [])[0]
804 | if (parsed)
805 | string = string.slice(string.indexOf(parsed) + parsed.length)
806 | if (!(formatTokenFunctions[match] instanceof Function)) {
807 | format += match
808 | if (parsed)
809 | input += parsed
810 | }
811 | }
812 | config._i = input
813 | config._f = format
814 | }
815 |
816 | /************************************
817 | Week of Year
818 | ************************************/
819 |
820 | function jWeekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
821 | var end = firstDayOfWeekOfYear - firstDayOfWeek
822 | , daysToDayOfWeek = firstDayOfWeekOfYear - mom.day()
823 | , adjustedMoment
824 |
825 | if (daysToDayOfWeek > end) {
826 | daysToDayOfWeek -= 7
827 | }
828 | if (daysToDayOfWeek < end - 7) {
829 | daysToDayOfWeek += 7
830 | }
831 | adjustedMoment = jMoment(mom).add(daysToDayOfWeek, 'd')
832 | return { week: Math.ceil(adjustedMoment.jDayOfYear() / 7)
833 | , year: adjustedMoment.jYear()
834 | }
835 | }
836 |
837 | /************************************
838 | Top Level Functions
839 | ************************************/
840 | var maxTimestamp = 57724432199999
841 |
842 | function makeMoment(input, format, lang, strict, utc) {
843 | if (typeof lang === 'boolean') {
844 | strict = lang
845 | lang = undefined
846 | }
847 |
848 | if (format && typeof format === 'string')
849 | format = fixFormat(format, moment)
850 |
851 | var config =
852 | { _i: input
853 | , _f: format
854 | , _l: lang
855 | , _strict: strict
856 | , _isUTC: utc
857 | }
858 | , date
859 | , m
860 | , jm
861 | , origInput = input
862 | , origFormat = format
863 | if (format) {
864 | if (isArray(format)) {
865 | return makeDateFromStringAndArray(config, utc)
866 | } else {
867 | date = makeDateFromStringAndFormat(config)
868 | removeParsedTokens(config)
869 | /// Babak
870 | /// Could not figure out what this line if for
871 | /// so I just commented it!
872 | format = 'YYYY-MM-DD-' + config._f
873 | input = leftZeroFill(date[0], 4) + '-'
874 | + leftZeroFill(date[1] + 1, 2) + '-'
875 | + leftZeroFill(date[2], 2) + '-'
876 | + config._i
877 | }
878 | }
879 | if (utc)
880 | m = moment.utc(input, format, lang, strict)
881 | else
882 | m = moment(input, format, lang, strict)
883 | if (config._isValid === false)
884 | m._isValid = false
885 | m._jDiff = config._jDiff || 0
886 | jm = objectCreate(jMoment.fn)
887 | extend(jm, m)
888 | if (strict && format && jm.isValid()) {
889 | jm._isValid = jm.format(origFormat) === origInput
890 | }
891 | if (m._d.getTime() > maxTimestamp) {
892 | /// Babak
893 | /// Follwoing line is commented in order to prevent invalid date
894 | /// for date-picker maxDate values.
895 | //jm._isValid = false
896 | }
897 | return jm
898 | }
899 |
900 | function jMoment(input, format, lang, strict) {
901 | return makeMoment(input, format, lang, strict, false)
902 | }
903 |
904 | extend(jMoment, moment)
905 | jMoment.fn = objectCreate(moment.fn)
906 |
907 | jMoment.utc = function (input, format, lang, strict) {
908 |
909 | if (moment.isMoment(input)){
910 |
911 | return moment.utc(input);
912 | }
913 |
914 | return makeMoment(input, format, lang, strict, true)
915 | }
916 |
917 | jMoment.unix = function (input) {
918 | return makeMoment(input * 1000)
919 | }
920 |
921 | /************************************
922 | jMoment Prototype
923 | ************************************/
924 |
925 | function fixFormat(format, _moment) {
926 | var i = 5
927 | var replace = function (input) {
928 | return _moment.localeData().longDateFormat(input) || input
929 | }
930 | while (i > 0 && localFormattingTokens.test(format)) {
931 | i -= 1
932 | format = format.replace(localFormattingTokens, replace)
933 | }
934 | return format
935 | }
936 |
937 | jMoment.fn.format = function (format) {
938 |
939 | if (format) {
940 | format = fixFormat(format, this)
941 |
942 | if (!formatFunctions[format]) {
943 | formatFunctions[format] = makeFormatFunction(format)
944 | }
945 | format = formatFunctions[format](this)
946 | }
947 | return moment.fn.format.call(this, format)
948 | }
949 |
950 | jMoment.fn.jYear = function (input) {
951 | var lastDay
952 | , j
953 | , g
954 | if (typeof input === 'number') {
955 | j = toJalaali(this.year(), this.month(), this.date())
956 | lastDay = Math.min(j.jd, jMoment.jDaysInMonth(input, j.jm))
957 | g = toGregorian(input, j.jm, lastDay)
958 | setDate(this, g.gy, g.gm, g.gd)
959 | moment.updateOffset(this)
960 | return this
961 | } else {
962 | return toJalaali(this.year(), this.month(), this.date()).jy
963 | }
964 | }
965 |
966 | jMoment.fn.jMonth = function (input) {
967 | var lastDay
968 | , j
969 | , g
970 | if (input != null) {
971 | if (typeof input === 'string') {
972 | input = this.localeData().jMonthsParse(input)
973 | if (typeof input !== 'number')
974 | return this
975 | }
976 | j = toJalaali(this.year(), this.month(), this.date())
977 | lastDay = Math.min(j.jd, jMoment.jDaysInMonth(j.jy, input))
978 | this.jYear(j.jy + div(input, 12))
979 | input = mod(input, 12)
980 | if (input < 0) {
981 | input += 12
982 | this.jYear(this.jYear() - 1)
983 | }
984 | g = toGregorian(this.jYear(), input, lastDay)
985 | setDate(this, g.gy, g.gm, g.gd)
986 | moment.updateOffset(this)
987 | return this
988 | } else {
989 | return toJalaali(this.year(), this.month(), this.date()).jm
990 | }
991 | }
992 |
993 | jMoment.fn.jDate = function (input) {
994 | var j
995 | , g
996 | if (typeof input === 'number') {
997 | j = toJalaali(this.year(), this.month(), this.date())
998 | g = toGregorian(j.jy, j.jm, input)
999 | setDate(this, g.gy, g.gm, g.gd)
1000 | moment.updateOffset(this)
1001 | return this
1002 | } else {
1003 | return toJalaali(this.year(), this.month(), this.date()).jd
1004 | }
1005 | }
1006 |
1007 | jMoment.fn.jDayOfYear = function (input) {
1008 | var dayOfYear = Math.round((jMoment(this).startOf('day') - jMoment(this).startOf('jYear')) / 864e5) + 1
1009 | return input == null ? dayOfYear : this.add(input - dayOfYear, 'd')
1010 | }
1011 |
1012 | jMoment.fn.jWeek = function (input) {
1013 | var week = jWeekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).week
1014 | return input == null ? week : this.add((input - week) * 7, 'd')
1015 | }
1016 |
1017 | jMoment.fn.jWeekYear = function (input) {
1018 | var year = jWeekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year
1019 | return input == null ? year : this.add(input - year, 'y')
1020 | }
1021 |
1022 | jMoment.fn.add = function (val, units) {
1023 | var temp
1024 | if (units !== null && !isNaN(+units)) {
1025 | temp = val
1026 | val = units
1027 | units = temp
1028 | }
1029 | // Babak
1030 | // This line causes issues
1031 | // because it lowercases units and further call to
1032 | // original moment function will be disturbed.
1033 | // units = normalizeUnits(units)
1034 | var _units = normalizeUnits(units)
1035 | if (_units === 'jyear') {
1036 | this.jYear(this.jYear() + val)
1037 | } else if (_units === 'jmonth') {
1038 | this.jMonth(this.jMonth() + val)
1039 | } else {
1040 | moment.fn.add.call(this, val, units)
1041 | if (isNaN(this.jYear())) {
1042 | this._isValid = false
1043 | }
1044 | }
1045 | return this
1046 | }
1047 |
1048 | jMoment.fn.subtract = function (val, units) {
1049 | var temp
1050 | if (units !== null && !isNaN(+units)) {
1051 | temp = val
1052 | val = units
1053 | units = temp
1054 | }
1055 | /// Babak
1056 | /// normailze will change case of units
1057 | /// this will cause issues when calling the
1058 | /// original subtract.
1059 |
1060 | var _units = normalizeUnits(units)
1061 | if (_units === 'jyear') {
1062 | this.jYear(this.jYear() - val)
1063 | } else if (_units === 'jmonth') {
1064 | this.jMonth(this.jMonth() - val)
1065 | } else {
1066 | moment.fn.subtract.call(this, val, units)
1067 | }
1068 | return this
1069 | }
1070 |
1071 | jMoment.fn.startOf = function (units) {
1072 | var _units = normalizeUnits(units)
1073 | if (_units === 'jyear' || _units === 'jmonth') {
1074 | if (_units === 'jyear') {
1075 | this.jMonth(0)
1076 | }
1077 | this.jDate(1)
1078 | this.hours(0)
1079 | this.minutes(0)
1080 | this.seconds(0)
1081 | this.milliseconds(0)
1082 | return this
1083 | } else {
1084 | return moment.fn.startOf.call(this, units)
1085 | }
1086 | }
1087 |
1088 | jMoment.fn.endOf = function (units) {
1089 | units = normalizeUnits(units)
1090 | if (units === undefined || units === 'milisecond') {
1091 | return this
1092 | }
1093 | return this.startOf(units).add(1, (units === 'isoweek' ? 'week' : units)).subtract(1, 'ms')
1094 | }
1095 |
1096 | jMoment.fn.isSame = function (other, units) {
1097 | var _units = normalizeUnits(units)
1098 | if (_units === 'jyear' || _units === 'jmonth') {
1099 | /// parOdoo fixuo
1100 | /// we should use clone() otherwise
1101 | /// startOf will alter 'this'
1102 | return moment.fn.isSame.call(this.clone().startOf(_units), other.clone().startOf(_units))
1103 | }
1104 | return moment.fn.isSame.call(this, other, units)
1105 | }
1106 |
1107 | jMoment.fn.clone = function () {
1108 | return jMoment(this)
1109 | }
1110 |
1111 | jMoment.fn.jYears = jMoment.fn.jYear
1112 | jMoment.fn.jMonths = jMoment.fn.jMonth
1113 | jMoment.fn.jDates = jMoment.fn.jDate
1114 | jMoment.fn.jWeeks = jMoment.fn.jWeek
1115 |
1116 | /************************************
1117 | jMoment Statics
1118 | ************************************/
1119 |
1120 | jMoment.jDaysInMonth = function (year, month) {
1121 | year += div(month, 12)
1122 | month = mod(month, 12)
1123 | if (month < 0) {
1124 | month += 12
1125 | year -= 1
1126 | }
1127 | if (month < 6) {
1128 | return 31
1129 | } else if (month < 11) {
1130 | return 30
1131 | } else if (jMoment.jIsLeapYear(year)) {
1132 | return 30
1133 | } else {
1134 | return 29
1135 | }
1136 | }
1137 |
1138 | jMoment.jIsLeapYear = jalaali.isLeapJalaaliYear
1139 |
1140 | jMoment.fixPersian = function(){
1141 | //console.info("fixPersian1");
1142 | fa = moment.localeData('fa');
1143 | if (fa && typeof fa.$fixed =='undefined'){
1144 | fa.$fixed = true;
1145 | fa.postformat = function(s){return s};
1146 | fa.preparse = function(s){return s};
1147 | }
1148 | }
1149 | jMoment.loadPersian = function (args) {
1150 | var usePersianDigits = args !== undefined && args.hasOwnProperty('usePersianDigits') ? args.usePersianDigits : false
1151 | var dialect = args !== undefined && args.hasOwnProperty('dialect') ? args.dialect : 'persian'
1152 | moment.locale('fa')
1153 | console.error("loadPersian");
1154 | moment.updateLocale('fa'
1155 | , { months: ('ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر').split('_')
1156 | , monthsShort: ('ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر').split('_')
1157 | , weekdays:
1158 | {
1159 | 'persian': ('یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_آدینه_شنبه').split('_'),
1160 | 'persian-modern': ('یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه').split('_')
1161 | }[dialect]
1162 | , weekdaysShort:
1163 | {
1164 | 'persian': ('یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_آدینه_شنبه').split('_'),
1165 | 'persian-modern': ('یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه').split('_')
1166 | }[dialect]
1167 | , weekdaysMin:
1168 | {
1169 | 'persian': 'ی_د_س_چ_پ_آ_ش'.split('_'),
1170 | 'persian-modern': 'ی_د_س_چ_پ_ج_ش'.split('_')
1171 | }[dialect]
1172 | , longDateFormat:
1173 | { LT: 'HH:mm'
1174 | , L: 'jYYYY/jMM/jDD'
1175 | , LL: 'jD jMMMM jYYYY'
1176 | , LLL: 'jD jMMMM jYYYY LT'
1177 | , LLLL: 'dddd، jD jMMMM jYYYY LT'
1178 | }
1179 | , calendar:
1180 | { sameDay: '[امروز ساعت] LT'
1181 | , nextDay: '[فردا ساعت] LT'
1182 | , nextWeek: 'dddd [ساعت] LT'
1183 | , lastDay: '[دیروز ساعت] LT'
1184 | , lastWeek: 'dddd [ی پیش ساعت] LT'
1185 | , sameElse: 'L'
1186 | }
1187 | , relativeTime:
1188 | { future: 'در %s'
1189 | , past: '%s پیش'
1190 | , s: 'چند ثانیه'
1191 | , m: '1 دقیقه'
1192 | , mm: '%d دقیقه'
1193 | , h: '1 ساعت'
1194 | , hh: '%d ساعت'
1195 | , d: '1 روز'
1196 | , dd: '%d روز'
1197 | , M: '1 ماه'
1198 | , MM: '%d ماه'
1199 | , y: '1 سال'
1200 | , yy: '%d سال'
1201 | }
1202 | /*
1203 | , preparse: function (string) {
1204 | if (true) {
1205 | return string.replace(/[۰-۹]/g, function (match) {
1206 | return numberMap[match]
1207 | }).replace(/،/g, ',')
1208 | }
1209 | return string
1210 | }
1211 | , postformat: function (string) {
1212 | if (true) {
1213 | return string.replace(/\d/g, function (match) {
1214 | return symbolMap[match]
1215 | }).replace(/,/g, '،')
1216 | }
1217 | return string
1218 | }
1219 | */
1220 | , ordinal: '%dم'
1221 | , week:
1222 | { dow: 6 // Saturday is the first day of the week.
1223 | , doy: 12 // The week that contains Jan 1st is the first week of the year.
1224 | }
1225 | , meridiem: function (hour) {
1226 | return hour < 12 ? 'ق.ظ' : 'ب.ظ'
1227 | }
1228 | , jMonths:
1229 | {
1230 | 'persian': ('فروردین_اردیبهشت_خرداد_تیر_امرداد_شهریور_مهر_آبان_آذر_دی_بهمن_اسفند').split('_'),
1231 | 'persian-modern': ('فروردین_اردیبهشت_خرداد_تیر_مرداد_شهریور_مهر_آبان_آذر_دی_بهمن_اسفند').split('_')
1232 | }[dialect]
1233 | , jMonthsShort:
1234 | {
1235 | 'persian': 'فرو_ارد_خرد_تیر_امر_شهر_مهر_آبا_آذر_دی_بهم_اسف'.split('_'),
1236 | 'persian-modern': 'فرو_ارد_خرد_تیر_مرد_شهر_مهر_آبا_آذر_دی_بهم_اسف'.split('_')
1237 | }[dialect]
1238 | }
1239 | )
1240 | }
1241 |
1242 |
1243 | jMoment.jConvert = { toJalaali: toJalaali
1244 | , toGregorian: toGregorian
1245 | }
1246 |
1247 | /************************************
1248 | Jalaali Conversion
1249 | ************************************/
1250 |
1251 | function toJalaali(gy, gm, gd) {
1252 | try {
1253 | var j = jalaali.toJalaali(gy, gm + 1, gd)
1254 | j.jm -= 1
1255 | return j
1256 | } catch (e) {
1257 | return {
1258 | jy: NaN
1259 | , jm: NaN
1260 | , jd: NaN
1261 | }
1262 | }
1263 | }
1264 |
1265 | function toGregorian(jy, jm, jd) {
1266 | try {
1267 | var g = jalaali.toGregorian(jy, jm + 1, jd)
1268 | g.gm -= 1
1269 | return g
1270 | } catch (e) {
1271 | return {
1272 | gy: NaN
1273 | , gm: NaN
1274 | , gd: NaN
1275 | }
1276 | }
1277 | }
1278 |
1279 | /*
1280 | Utility helper functions.
1281 | */
1282 |
1283 | function div(a, b) {
1284 | return ~~(a / b)
1285 | }
1286 |
1287 | function mod(a, b) {
1288 | return a - ~~(a / b) * b
1289 | }
1290 | });
1291 |
1292 | if (typeof exports == "object") {
1293 | module.exports = require("moment-jalaali");
1294 | } else if (typeof define == "function" && define.amd) {
1295 | define([], function(){ return require("moment-jalaali"); });
1296 | } else {
1297 | this["moment"] = require("moment-jalaali");
1298 | }
1299 | })();
1300 |
--------------------------------------------------------------------------------
/static/js/mytime.js:
--------------------------------------------------------------------------------
1 | odoo.define('web.mytime', function (require) {
2 | "use strict";
3 | var core = require('web.core');
4 | var time = require('web.time');
5 | var session = require('web.session');
6 | var _t = core._t;
7 | /**
8 | * Returns the user prefered calendar code using user_context of
9 | * odoo session_info structure.
10 | * The returned code is a single character:
11 | * '' or 'g': Gregorian Calendar (Default)
12 | * 'j' : Persian Calendar (Jalali)
13 | * 'h' : Hijri Calendar (Hijri)
14 | * @param {*} user_context
15 | */
16 | time.getCalendar = function (user_context) {
17 | //return 'j';
18 | user_context = user_context ||
19 | ( ((typeof odoo == 'undefined' ? {} : odoo).session_info || {}).user_context );
20 | return user_context && typeof user_context.calendar === 'string'
21 | ? user_context.calendar.startsWith('j')?'j':''
22 | : user_context && user_context.lang=='fa_IR'
23 | ?'j'
24 | :core._t.database.parameters.code == 'fa_IR' ?
25 | 'j' :
26 | '';
27 | }
28 | time.getUserDateFormat = function (user_context) {
29 | user_context = user_context ||
30 | ( ((typeof odoo == 'undefined' ? {} : odoo).session_info || {}).user_context );
31 | return user_context && typeof user_context.date_format === 'string'
32 | ? user_context.date_format
33 | :'';
34 | }
35 |
36 | time.fixPersianLocale = function () {
37 |
38 | //debugger;
39 | //var ggg = session;
40 | return typeof moment != 'undefined' && moment.fixPersian && moment.fixPersian()
41 | }
42 | time.fixTempusDominusBootstrap4 = function () {
43 | var proto = $ && $.fn &&
44 | $.fn['datetimepicker'] && $.fn['datetimepicker'].Constructor &&
45 | $.fn['datetimepicker'].Constructor.prototype;
46 | if (proto && !proto.$fixed) {
47 | console.warn("fixTempusDominusBootstrap4");
48 | proto.$fixed = true;
49 | }
50 |
51 | }
52 | time._getLangDateFormat = time.getLangDateFormat;
53 | time.getLangDateFormat = function () {
54 | time.fixPersianLocale();
55 | if (time.getCalendar()=='j'){
56 | switch(time.getUserDateFormat()){
57 | case 'YYYY/M/D':
58 | return "jYYYY/jM/jD";
59 | default:
60 | return "jYYYY/jMM/jDD";
61 | }
62 | }
63 | return time._getLangDateFormat()
64 | }
65 |
66 |
67 | time._getLangDatetimeFormat = time.getLangDatetimeFormat;
68 | time.getLangDatetimeFormat = function () {
69 | time.fixPersianLocale();
70 | if (time.getCalendar()=='j'){
71 | switch(time.getUserDateFormat()){
72 | case 'YYYY/M/D':
73 | return "jYYYY/jM/jD HH:mm:ss";
74 | default:
75 | return "jYYYY/jMM/jDD HH:mm:ss";
76 | }
77 | }
78 | return time._getLangDatetimeFormat()
79 | }
80 |
81 | });
82 |
83 | if (typeof odoo!='undefined' && odoo.session_info && odoo.session_info.user_context){
84 |
85 | odoo.session_info.user_context.getCalendar == function(){
86 | var user_context = ((typeof odoo == 'undefined' ? {} : odoo).session_info || {}).user_context;
87 | return time.getCalendar(user_context);
88 | }
89 | odoo.session_info.user_context.getUserDateFormat == function(){
90 | var user_context = ((typeof odoo == 'undefined' ? {} : odoo).session_info || {}).user_context;
91 | return time.getUserDateFormat(user_context);
92 | }
93 | odoo.getCalendar = function(){
94 | var user_context = ((typeof odoo == 'undefined' ? {} : odoo).session_info || {}).user_context;
95 | return time.getCalendar(user_context);
96 | }
97 | odoo.getUserDateFormat = function(){
98 | var user_context = ((typeof odoo == 'undefined' ? {} : odoo).session_info || {}).user_context;
99 | return time.getUserDateFormat(user_context);
100 | }
101 |
102 | }
--------------------------------------------------------------------------------