├── 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 = ''; 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 += ''; 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 += ''; 756 | 757 | $.each(this.locale.daysOfWeek, function(index, dayOfWeek) { 758 | html += ''; 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 += ''; 780 | else if (this.showISOWeekNumbers) 781 | html += ''; 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 += ''; 842 | 843 | } 844 | html += ''; 845 | } 846 | 847 | html += ''; 848 | html += '
' + dateHtml + '
' + this.locale.weekLabel + '' + dayOfWeek + '
' + calendar[row][0].week() + '' + calendar[row][0].isoWeek() + '' + calendar[row][col].date() + '
'; 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 | } --------------------------------------------------------------------------------