bootstrap-daterangepicker-rails 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe234cb59f9a330ab68b7807aeffd66904de7b04
4
- data.tar.gz: b9f117c35cd06d4263a10364b5ca6e7d0e063efb
3
+ metadata.gz: 74d416af849d9db6a14e6c7f8482f1004e50b412
4
+ data.tar.gz: 797929b9a3d13cf612c4e46eace69c57618cb678
5
5
  SHA512:
6
- metadata.gz: 6f7a5ee39f49513c5d802d9dcd78c8e933227077cbebbaca993685d2338b3ae46aed624c59220f6e3e320e0e04939acc2dbeb3d5a455665a8393060dc4139c1e
7
- data.tar.gz: 6720d6b7b099e688bdef175032929694dd4428b78969fa9a73a995e8815d16d84e0da2fe7a4acb56cee76e2636d9c5c8ad8f13c203eee902bb0a13d4c84951c7
6
+ metadata.gz: e439b6cfe45fee1e62511918638de8e8ca626615dae18f4637199937dd6905cb2c58bee20767434fe31f71f118f1dcd96d177952cdbc0d2b41b8e7743f311293
7
+ data.tar.gz: e0492dcca44816bfab49044b360a4b7f8a91ef42f3b78ed1a91d4d797facc2f74097139ab308af86e48939d0423e80b02d672bbe220b62b20716449c03cb7fa7
@@ -1,7 +1,7 @@
1
1
  module Bootstrap
2
2
  module Daterangepicker
3
3
  module Rails
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.8'
5
5
  end
6
6
  end
7
7
  end
@@ -25,7 +25,7 @@
25
25
  this.timePicker = false;
26
26
  this.timePickerIncrement = 30;
27
27
  this.timePicker12Hour = true;
28
- this.ranges = {};
28
+ this.ranges = {};
29
29
  this.opens = 'right';
30
30
 
31
31
  this.buttonClasses = ['btn', 'btn-small'];
@@ -42,13 +42,16 @@
42
42
  toLabel: 'To',
43
43
  weekLabel: 'W',
44
44
  customRangeLabel: 'Custom Range',
45
- daysOfWeek: moment()._lang._weekdaysMin,
46
- monthNames: moment()._lang._monthsShort,
45
+ daysOfWeek: moment()._lang._weekdaysMin.slice(),
46
+ monthNames: moment()._lang._monthsShort.slice(),
47
47
  firstDay: 0
48
48
  };
49
49
 
50
50
  this.cb = function () { };
51
51
 
52
+ // by default, the daterangepicker element is placed at the bottom of HTML body
53
+ this.parentEl = 'body';
54
+
52
55
  //element that triggered the date range picker
53
56
  this.element = $(element);
54
57
 
@@ -101,7 +104,9 @@
101
104
  '</div>' +
102
105
  '</div>';
103
106
 
104
- this.container = $(DRPTemplate).appendTo('body');
107
+ this.parentEl = (hasOptions && options.parentEl && $(options.parentEl)) || $(this.parentEl);
108
+ //the date range picker
109
+ this.container = $(DRPTemplate).appendTo(this.parentEl);
105
110
 
106
111
  if (hasOptions) {
107
112
 
@@ -264,8 +269,8 @@
264
269
  }
265
270
 
266
271
  //state
267
- this.oldStartDate = this.startDate;
268
- this.oldEndDate = this.endDate;
272
+ this.oldStartDate = this.startDate.clone();
273
+ this.oldEndDate = this.endDate.clone();
269
274
 
270
275
  this.leftCalendar = {
271
276
  month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]),
@@ -279,25 +284,24 @@
279
284
 
280
285
  //event listeners
281
286
  this.container.on('mousedown', $.proxy(this.mousedown, this));
282
- this.container.find('.calendar').on('click', '.prev', $.proxy(this.clickPrev, this));
283
- this.container.find('.calendar').on('click', '.next', $.proxy(this.clickNext, this));
284
- this.container.find('.ranges').on('click', 'button.applyBtn', $.proxy(this.clickApply, this));
285
- this.container.find('.ranges').on('click', 'button.cancelBtn', $.proxy(this.clickCancel, this));
286
-
287
- this.container.find('.calendar').on('click', 'td.available', $.proxy(this.clickDate, this));
288
- this.container.find('.calendar').on('mouseenter', 'td.available', $.proxy(this.enterDate, this));
289
- this.container.find('.calendar').on('mouseleave', 'td.available', $.proxy(this.updateView, this));
290
-
291
- this.container.find('.ranges').on('click', 'li', $.proxy(this.clickRange, this));
292
- this.container.find('.ranges').on('mouseenter', 'li', $.proxy(this.enterRange, this));
293
- this.container.find('.ranges').on('mouseleave', 'li', $.proxy(this.updateView, this));
294
287
 
295
- this.container.find('.calendar').on('change', 'select.yearselect', $.proxy(this.updateYear, this));
296
- this.container.find('.calendar').on('change', 'select.monthselect', $.proxy(this.updateMonth, this));
297
-
298
- this.container.find('.calendar').on('change', 'select.hourselect', $.proxy(this.updateTime, this));
299
- this.container.find('.calendar').on('change', 'select.minuteselect', $.proxy(this.updateTime, this));
300
- this.container.find('.calendar').on('change', 'select.ampmselect', $.proxy(this.updateTime, this));
288
+ this.container.find('.calendar')
289
+ .on('click', '.prev', $.proxy(this.clickPrev, this))
290
+ .on('click', '.next', $.proxy(this.clickNext, this))
291
+ .on('click', 'td.available', $.proxy(this.clickDate, this))
292
+ .on('mouseenter', 'td.available', $.proxy(this.enterDate, this))
293
+ .on('mouseleave', 'td.available', $.proxy(this.updateFormInputs, this))
294
+ .on('change', 'select.yearselect', $.proxy(this.updateMonthYear, this))
295
+ .on('change', 'select.monthselect', $.proxy(this.updateMonthYear, this))
296
+ .on('change', 'select.hourselect,select.minuteselect,select.ampmselect', $.proxy(this.updateTime, this));
297
+
298
+ this.container.find('.ranges')
299
+ .on('click', 'button.applyBtn', $.proxy(this.clickApply, this))
300
+ .on('click', 'button.cancelBtn', $.proxy(this.clickCancel, this))
301
+ .on('click', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this))
302
+ .on('click', 'li', $.proxy(this.clickRange, this))
303
+ .on('mouseenter', 'li', $.proxy(this.enterRange, this))
304
+ .on('mouseleave', 'li', $.proxy(this.updateFormInputs, this));
301
305
 
302
306
  this.element.on('keyup', $.proxy(this.updateFromControl, this));
303
307
 
@@ -317,7 +321,10 @@
317
321
  updateView: function () {
318
322
  this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year());
319
323
  this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year());
324
+ this.updateFormInputs();
325
+ },
320
326
 
327
+ updateFormInputs: function () {
321
328
  this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format));
322
329
  this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format));
323
330
 
@@ -339,11 +346,15 @@
339
346
  if (start == null || end == null) return;
340
347
  if (end.isBefore(start)) return;
341
348
 
349
+ this.oldStartDate = this.startDate.clone();
350
+ this.oldEndDate = this.endDate.clone();
351
+
342
352
  this.startDate = start;
343
353
  this.endDate = end;
344
354
 
345
- this.updateView();
346
- this.cb(this.startDate, this.endDate);
355
+ if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
356
+ this.notify();
357
+
347
358
  this.updateCalendars();
348
359
  },
349
360
 
@@ -353,17 +364,15 @@
353
364
  },
354
365
 
355
366
  move: function () {
356
- var minWidth = $(this.container).find('.ranges').outerWidth();
357
- if ($(this.container).find('.calendar').is(':visible')) {
358
- var padding = 24; // FIXME: this works for the default styling, but isn't flexible
359
- minWidth += $(this.container).find('.calendar').outerWidth() * 2 + padding;
360
- }
367
+ var parentOffset = {
368
+ top: this.parentEl.offset().top - (this.parentEl.is('body') ? 0 : this.parentEl.scrollTop()),
369
+ left: this.parentEl.offset().left - (this.parentEl.is('body') ? 0 : this.parentEl.scrollLeft())
370
+ };
361
371
  if (this.opens == 'left') {
362
372
  this.container.css({
363
- top: this.element.offset().top + this.element.outerHeight(),
364
- right: $(window).width() - this.element.offset().left - this.element.outerWidth(),
365
- left: 'auto',
366
- 'min-width': minWidth
373
+ top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
374
+ right: $(window).width() - this.element.offset().left - this.element.outerWidth() - parentOffset.left,
375
+ left: 'auto'
367
376
  });
368
377
  if (this.container.offset().left < 0) {
369
378
  this.container.css({
@@ -373,10 +382,9 @@
373
382
  }
374
383
  } else {
375
384
  this.container.css({
376
- top: this.element.offset().top + this.element.outerHeight(),
377
- left: this.element.offset().left,
378
- right: 'auto',
379
- 'min-width': minWidth
385
+ top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
386
+ left: this.element.offset().left - parentOffset.left,
387
+ right: 'auto'
380
388
  });
381
389
  if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
382
390
  this.container.css({
@@ -396,9 +404,6 @@
396
404
  e.preventDefault();
397
405
  }
398
406
 
399
- this.oldStartDate = this.startDate;
400
- this.oldEndDate = this.endDate;
401
-
402
407
  $(document).on('mousedown', $.proxy(this.hide, this));
403
408
  this.element.trigger('shown', {target: e.target, picker: this});
404
409
  },
@@ -409,6 +414,9 @@
409
414
  if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
410
415
  this.notify();
411
416
 
417
+ this.oldStartDate = this.startDate.clone();
418
+ this.oldEndDate = this.endDate.clone();
419
+
412
420
  $(document).off('mousedown', this.hide);
413
421
  this.element.trigger('hidden', { picker: this });
414
422
  },
@@ -424,11 +432,20 @@
424
432
  }
425
433
  },
426
434
 
435
+ showCalendars: function() {
436
+ this.container.find('.calendar').show();
437
+ this.move();
438
+ },
439
+
440
+ updateInputText: function() {
441
+ if (this.element.is('input'))
442
+ this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
443
+ },
444
+
427
445
  clickRange: function (e) {
428
446
  var label = e.target.innerHTML;
429
447
  if (label == this.locale.customRangeLabel) {
430
- this.container.find('.calendar').show();
431
- this.move();
448
+ this.showCalendars();
432
449
  } else {
433
450
  var dates = this.ranges[label];
434
451
 
@@ -444,8 +461,7 @@
444
461
  this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
445
462
  this.updateCalendars();
446
463
 
447
- if (this.element.is('input'))
448
- this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
464
+ this.updateInputText();
449
465
 
450
466
  this.container.find('.calendar').hide();
451
467
  this.hide();
@@ -531,8 +547,7 @@
531
547
  },
532
548
 
533
549
  clickApply: function (e) {
534
- if (this.element.is('input'))
535
- this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
550
+ this.updateInputText();
536
551
  this.hide();
537
552
  },
538
553
 
@@ -544,29 +559,25 @@
544
559
  this.hide();
545
560
  },
546
561
 
547
- updateYear: function (e) {
548
- var year = parseInt($(e.target).val());
562
+ updateMonthYear: function (e) {
563
+
549
564
  var isLeft = $(e.target).closest('.calendar').hasClass('left');
565
+ var cal = this.container.find('.calendar.left');
566
+ if (!isLeft)
567
+ cal = this.container.find('.calendar.right');
568
+
569
+ // Month must be Number for new moment versions
570
+ var month = parseInt(cal.find('.monthselect').val(), 10);
571
+ var year = cal.find('.yearselect').val();
550
572
 
551
573
  if (isLeft) {
552
- this.leftCalendar.month.month(this.startDate.month()).year(year);
574
+ this.leftCalendar.month.month(month).year(year);
553
575
  } else {
554
- this.rightCalendar.month.month(this.endDate.month()).year(year);
576
+ this.rightCalendar.month.month(month).year(year);
555
577
  }
556
578
 
557
579
  this.updateCalendars();
558
- },
559
-
560
- updateMonth: function (e) {
561
- var month = parseInt($(e.target).val());
562
- var isLeft = $(e.target).closest('.calendar').hasClass('left');
563
580
 
564
- if (isLeft) {
565
- this.leftCalendar.month.month(month).year(this.startDate.year());
566
- } else {
567
- this.rightCalendar.month.month(month).year(this.endDate.year());
568
- }
569
- this.updateCalendars();
570
581
  },
571
582
 
572
583
  updateTime: function(e) {
@@ -583,19 +594,21 @@
583
594
  var ampm = cal.find('.ampmselect').val();
584
595
  if (ampm == 'PM' && hour < 12)
585
596
  hour += 12;
597
+ if (ampm == 'AM' && hour == 12)
598
+ hour = 0;
586
599
  }
587
600
 
588
601
  if (isLeft) {
589
- var start = this.startDate;
602
+ var start = this.startDate.clone();
590
603
  start.hour(hour);
591
604
  start.minute(minute);
592
605
  this.startDate = start;
593
606
  this.leftCalendar.month.hour(hour).minute(minute);
594
607
  } else {
595
- var end = this.endDate;
608
+ var end = this.endDate.clone();
596
609
  end.hour(hour);
597
610
  end.minute(minute);
598
- this.endDate = end;
611
+ this.endDate = end;
599
612
  this.rightCalendar.month.hour(hour).minute(minute);
600
613
  }
601
614
 
@@ -655,13 +668,14 @@
655
668
  if (dayOfWeek == this.locale.firstDay)
656
669
  startDay = daysInLastMonth - 6;
657
670
 
658
- var curDate = moment([lastYear, lastMonth, startDay, hour, minute]);
659
- for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('day', 1)) {
671
+ var curDate = moment([lastYear, lastMonth, startDay, 12, minute]);
672
+ for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('hour', 24)) {
660
673
  if (i > 0 && col % 7 == 0) {
661
674
  col = 0;
662
675
  row++;
663
676
  }
664
- calendar[row][col] = curDate;
677
+ calendar[row][col] = curDate.clone().hour(hour);
678
+ curDate.hour(12);
665
679
  }
666
680
 
667
681
  return calendar;
@@ -686,7 +700,7 @@
686
700
  var currentYear = selected.year();
687
701
  var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
688
702
  var minYear = (minDate && minDate.year()) || (currentYear - 50);
689
- var yearHtml = '<select class="yearselect">'
703
+ var yearHtml = '<select class="yearselect">';
690
704
 
691
705
  for (var y = minYear; y <= maxYear; y++) {
692
706
  yearHtml += '<option value="' + y + '"' +
@@ -711,7 +725,7 @@
711
725
  html += '<th></th>';
712
726
 
713
727
  if (!minDate || minDate.isBefore(calendar[1][1])) {
714
- html += '<th class="prev available"><i class="icon-arrow-left"></i></th>';
728
+ html += '<th class="prev available"><i class="icon-arrow-left glyphicon glyphicon-arrow-left"></i></th>';
715
729
  } else {
716
730
  html += '<th></th>';
717
731
  }
@@ -724,7 +738,7 @@
724
738
 
725
739
  html += '<th colspan="5" style="width: auto">' + dateHtml + '</th>';
726
740
  if (!maxDate || maxDate.isAfter(calendar[1][1])) {
727
- html += '<th class="next available"><i class="icon-arrow-right"></i></th>';
741
+ html += '<th class="next available"><i class="icon-arrow-right glyphicon glyphicon-arrow-right"></i></th>';
728
742
  } else {
729
743
  html += '<th></th>';
730
744
  }
@@ -851,4 +865,4 @@
851
865
  return this;
852
866
  };
853
867
 
854
- }(window.jQuery);
868
+ }(window.jQuery);
@@ -1,5 +1,5 @@
1
1
  // moment.js
2
- // version : 2.0.0
2
+ // version : 2.1.0
3
3
  // author : Tim Wood
4
4
  // license : MIT
5
5
  // momentjs.com
@@ -7,45 +7,43 @@
7
7
  (function (undefined) {
8
8
 
9
9
  /************************************
10
- Constants
11
- ************************************/
10
+ Constants
11
+ ************************************/
12
12
 
13
13
  var moment,
14
- VERSION = "2.0.0",
14
+ VERSION = "2.1.0",
15
15
  round = Math.round, i,
16
- // internal storage for language config files
16
+ // internal storage for language config files
17
17
  languages = {},
18
18
 
19
- // check for nodeJS
19
+ // check for nodeJS
20
20
  hasModule = (typeof module !== 'undefined' && module.exports),
21
21
 
22
- // ASP.NET json date format regex
22
+ // ASP.NET json date format regex
23
23
  aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
24
+ aspNetTimeSpanJsonRegex = /(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/,
24
25
 
25
- // format tokens
26
- formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,
26
+ // format tokens
27
+ formattingTokens = /(\[[^\[]*\])|(\\)?(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,
27
28
  localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
28
29
 
29
- // parsing tokens
30
- parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,
31
-
32
- // parsing token regexes
30
+ // parsing token regexes
33
31
  parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
34
32
  parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
35
33
  parseTokenThreeDigits = /\d{3}/, // 000 - 999
36
34
  parseTokenFourDigits = /\d{1,4}/, // 0 - 9999
37
35
  parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
38
- parseTokenWord = /[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i, // any word (or two) characters or numbers including two word month in arabic.
36
+ parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
39
37
  parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
40
38
  parseTokenT = /T/i, // T (ISO seperator)
41
39
  parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
42
40
 
43
- // preliminary iso regex
44
- // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
41
+ // preliminary iso regex
42
+ // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
45
43
  isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
46
44
  isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
47
45
 
48
- // iso time formats and regexes
46
+ // iso time formats and regexes
49
47
  isoTimes = [
50
48
  ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
51
49
  ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
@@ -53,11 +51,11 @@
53
51
  ['HH', /(T| )\d\d/]
54
52
  ],
55
53
 
56
- // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
54
+ // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
57
55
  parseTimezoneChunker = /([\+\-]|\d\d)/gi,
58
56
 
59
- // getter and setter names
60
- proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
57
+ // getter and setter names
58
+ proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
61
59
  unitMillisecondFactors = {
62
60
  'Milliseconds' : 1,
63
61
  'Seconds' : 1e3,
@@ -68,10 +66,21 @@
68
66
  'Years' : 31536e6
69
67
  },
70
68
 
71
- // format function strings
69
+ unitAliases = {
70
+ ms : 'millisecond',
71
+ s : 'second',
72
+ m : 'minute',
73
+ h : 'hour',
74
+ d : 'day',
75
+ w : 'week',
76
+ M : 'month',
77
+ y : 'year'
78
+ },
79
+
80
+ // format function strings
72
81
  formatFunctions = {},
73
82
 
74
- // tokens to ordinalize and pad
83
+ // tokens to ordinalize and pad
75
84
  ordinalizeTokens = 'DDD w W M D d'.split(' '),
76
85
  paddedTokens = 'M D H h m s w W'.split(' '),
77
86
 
@@ -118,6 +127,30 @@
118
127
  YYYYY : function () {
119
128
  return leftZeroFill(this.year(), 5);
120
129
  },
130
+ gg : function () {
131
+ return leftZeroFill(this.weekYear() % 100, 2);
132
+ },
133
+ gggg : function () {
134
+ return this.weekYear();
135
+ },
136
+ ggggg : function () {
137
+ return leftZeroFill(this.weekYear(), 5);
138
+ },
139
+ GG : function () {
140
+ return leftZeroFill(this.isoWeekYear() % 100, 2);
141
+ },
142
+ GGGG : function () {
143
+ return this.isoWeekYear();
144
+ },
145
+ GGGGG : function () {
146
+ return leftZeroFill(this.isoWeekYear(), 5);
147
+ },
148
+ e : function () {
149
+ return this.weekday();
150
+ },
151
+ E : function () {
152
+ return this.isoWeekday();
153
+ },
121
154
  a : function () {
122
155
  return this.lang().meridiem(this.hours(), this.minutes(), true);
123
156
  },
@@ -163,6 +196,12 @@
163
196
  }
164
197
  return b + leftZeroFill(~~(10 * a / 6), 4);
165
198
  },
199
+ z : function () {
200
+ return this.zoneAbbr();
201
+ },
202
+ zz : function () {
203
+ return this.zoneName();
204
+ },
166
205
  X : function () {
167
206
  return this.unix();
168
207
  }
@@ -173,15 +212,15 @@
173
212
  return leftZeroFill(func.call(this, a), count);
174
213
  };
175
214
  }
176
- function ordinalizeToken(func) {
215
+ function ordinalizeToken(func, period) {
177
216
  return function (a) {
178
- return this.lang().ordinal(func.call(this, a));
217
+ return this.lang().ordinal(func.call(this, a), period);
179
218
  };
180
219
  }
181
220
 
182
221
  while (ordinalizeTokens.length) {
183
222
  i = ordinalizeTokens.pop();
184
- formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i]);
223
+ formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
185
224
  }
186
225
  while (paddedTokens.length) {
187
226
  i = paddedTokens.pop();
@@ -191,8 +230,8 @@
191
230
 
192
231
 
193
232
  /************************************
194
- Constructors
195
- ************************************/
233
+ Constructors
234
+ ************************************/
196
235
 
197
236
  function Language() {
198
237
 
@@ -205,8 +244,7 @@
205
244
 
206
245
  // Duration Constructor
207
246
  function Duration(duration) {
208
- var data = this._data = {},
209
- years = duration.years || duration.year || duration.y || 0,
247
+ var years = duration.years || duration.year || duration.y || 0,
210
248
  months = duration.months || duration.month || duration.M || 0,
211
249
  weeks = duration.weeks || duration.week || duration.w || 0,
212
250
  days = duration.days || duration.day || duration.d || 0,
@@ -215,6 +253,9 @@
215
253
  seconds = duration.seconds || duration.second || duration.s || 0,
216
254
  milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0;
217
255
 
256
+ // store reference to input for deterministic cloning
257
+ this._input = duration;
258
+
218
259
  // representation for dateAddRemove
219
260
  this._milliseconds = milliseconds +
220
261
  seconds * 1e3 + // 1000
@@ -230,35 +271,15 @@
230
271
  this._months = months +
231
272
  years * 12;
232
273
 
233
- // The following code bubbles up values, see the tests for
234
- // examples of what that means.
235
- data.milliseconds = milliseconds % 1000;
236
- seconds += absRound(milliseconds / 1000);
237
-
238
- data.seconds = seconds % 60;
239
- minutes += absRound(seconds / 60);
240
-
241
- data.minutes = minutes % 60;
242
- hours += absRound(minutes / 60);
243
-
244
- data.hours = hours % 24;
245
- days += absRound(hours / 24);
246
-
247
- days += weeks * 7;
248
- data.days = days % 30;
274
+ this._data = {};
249
275
 
250
- months += absRound(days / 30);
251
-
252
- data.months = months % 12;
253
- years += absRound(months / 12);
254
-
255
- data.years = years;
276
+ this._bubble();
256
277
  }
257
278
 
258
279
 
259
280
  /************************************
260
- Helpers
261
- ************************************/
281
+ Helpers
282
+ ************************************/
262
283
 
263
284
 
264
285
  function extend(a, b) {
@@ -289,23 +310,35 @@
289
310
  }
290
311
 
291
312
  // helper function for _.addTime and _.subtractTime
292
- function addOrSubtractDurationFromMoment(mom, duration, isAdding) {
293
- var ms = duration._milliseconds,
294
- d = duration._days,
295
- M = duration._months,
313
+ function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) {
314
+ var milliseconds = duration._milliseconds,
315
+ days = duration._days,
316
+ months = duration._months,
317
+ minutes,
318
+ hours,
296
319
  currentDate;
297
320
 
298
- if (ms) {
299
- mom._d.setTime(+mom + ms * isAdding);
321
+ if (milliseconds) {
322
+ mom._d.setTime(+mom._d + milliseconds * isAdding);
323
+ }
324
+ // store the minutes and hours so we can restore them
325
+ if (days || months) {
326
+ minutes = mom.minute();
327
+ hours = mom.hour();
328
+ }
329
+ if (days) {
330
+ mom.date(mom.date() + days * isAdding);
331
+ }
332
+ if (months) {
333
+ mom.month(mom.month() + months * isAdding);
300
334
  }
301
- if (d) {
302
- mom.date(mom.date() + d * isAdding);
335
+ if (milliseconds && !ignoreUpdateOffset) {
336
+ moment.updateOffset(mom);
303
337
  }
304
- if (M) {
305
- currentDate = mom.date();
306
- mom.date(1)
307
- .month(mom.month() + M * isAdding)
308
- .date(Math.min(currentDate, mom.daysInMonth()));
338
+ // restore the minutes and hours after possibly changing dst
339
+ if (days || months) {
340
+ mom.minute(minutes);
341
+ mom.hour(hours);
309
342
  }
310
343
  }
311
344
 
@@ -328,10 +361,14 @@
328
361
  return diffs + lengthDiff;
329
362
  }
330
363
 
364
+ function normalizeUnits(units) {
365
+ return units ? unitAliases[units] || units.toLowerCase().replace(/(.)s$/, '$1') : units;
366
+ }
367
+
331
368
 
332
369
  /************************************
333
- Languages
334
- ************************************/
370
+ Languages
371
+ ************************************/
335
372
 
336
373
 
337
374
  Language.prototype = {
@@ -358,7 +395,7 @@
358
395
  },
359
396
 
360
397
  monthsParse : function (monthName) {
361
- var i, mom, regex, output;
398
+ var i, mom, regex;
362
399
 
363
400
  if (!this._monthsParse) {
364
401
  this._monthsParse = [];
@@ -393,6 +430,27 @@
393
430
  return this._weekdaysMin[m.day()];
394
431
  },
395
432
 
433
+ weekdaysParse : function (weekdayName) {
434
+ var i, mom, regex;
435
+
436
+ if (!this._weekdaysParse) {
437
+ this._weekdaysParse = [];
438
+ }
439
+
440
+ for (i = 0; i < 7; i++) {
441
+ // make the regex if we don't have it already
442
+ if (!this._weekdaysParse[i]) {
443
+ mom = moment([2000, 1]).day(i);
444
+ regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
445
+ this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
446
+ }
447
+ // test the regex
448
+ if (this._weekdaysParse[i].test(weekdayName)) {
449
+ return i;
450
+ }
451
+ }
452
+ },
453
+
396
454
  _longDateFormat : {
397
455
  LT : "h:mm A",
398
456
  L : "MM/DD/YYYY",
@@ -411,6 +469,11 @@
411
469
  return output;
412
470
  },
413
471
 
472
+ isPM : function (input) {
473
+ return ((input + '').toLowerCase()[0] === 'p');
474
+ },
475
+
476
+ _meridiemParse : /[ap]\.?m?\.?/i,
414
477
  meridiem : function (hours, minutes, isLower) {
415
478
  if (hours > 11) {
416
479
  return isLower ? 'pm' : 'PM';
@@ -424,7 +487,7 @@
424
487
  nextDay : '[Tomorrow at] LT',
425
488
  nextWeek : 'dddd [at] LT',
426
489
  lastDay : '[Yesterday at] LT',
427
- lastWeek : '[last] dddd [at] LT',
490
+ lastWeek : '[Last] dddd [at] LT',
428
491
  sameElse : 'L'
429
492
  },
430
493
  calendar : function (key, mom) {
@@ -472,7 +535,7 @@
472
535
  },
473
536
 
474
537
  week : function (mom) {
475
- return weekOfYear(mom, this._week.dow, this._week.doy);
538
+ return weekOfYear(mom, this._week.dow, this._week.doy).week;
476
539
  },
477
540
  _week : {
478
541
  dow : 0, // Sunday is the first day of the week.
@@ -504,15 +567,20 @@
504
567
  return moment.fn._lang;
505
568
  }
506
569
  if (!languages[key] && hasModule) {
507
- require('./lang/' + key);
570
+ try {
571
+ require('./lang/' + key);
572
+ } catch (e) {
573
+ // call with no params to set to default
574
+ return moment.fn._lang;
575
+ }
508
576
  }
509
577
  return languages[key];
510
578
  }
511
579
 
512
580
 
513
581
  /************************************
514
- Formatting
515
- ************************************/
582
+ Formatting
583
+ ************************************/
516
584
 
517
585
 
518
586
  function removeFormattingTokens(input) {
@@ -536,7 +604,7 @@
536
604
  return function (mom) {
537
605
  var output = "";
538
606
  for (i = 0; i < length; i++) {
539
- output += typeof array[i].call === 'function' ? array[i].call(mom, format) : array[i];
607
+ output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
540
608
  }
541
609
  return output;
542
610
  };
@@ -563,146 +631,143 @@
563
631
 
564
632
 
565
633
  /************************************
566
- Parsing
567
- ************************************/
634
+ Parsing
635
+ ************************************/
568
636
 
569
637
 
570
- // get the regex to find the next token
571
- function getParseRegexForToken(token) {
638
+ // get the regex to find the next token
639
+ function getParseRegexForToken(token, config) {
572
640
  switch (token) {
573
- case 'DDDD':
574
- return parseTokenThreeDigits;
575
- case 'YYYY':
576
- return parseTokenFourDigits;
577
- case 'YYYYY':
578
- return parseTokenSixDigits;
579
- case 'S':
580
- case 'SS':
581
- case 'SSS':
582
- case 'DDD':
583
- return parseTokenOneToThreeDigits;
584
- case 'MMM':
585
- case 'MMMM':
586
- case 'dd':
587
- case 'ddd':
588
- case 'dddd':
589
- case 'a':
590
- case 'A':
591
- return parseTokenWord;
592
- case 'X':
593
- return parseTokenTimestampMs;
594
- case 'Z':
595
- case 'ZZ':
596
- return parseTokenTimezone;
597
- case 'T':
598
- return parseTokenT;
599
- case 'MM':
600
- case 'DD':
601
- case 'YY':
602
- case 'HH':
603
- case 'hh':
604
- case 'mm':
605
- case 'ss':
606
- case 'M':
607
- case 'D':
608
- case 'd':
609
- case 'H':
610
- case 'h':
611
- case 'm':
612
- case 's':
613
- return parseTokenOneOrTwoDigits;
614
- default :
615
- return new RegExp(token.replace('\\', ''));
641
+ case 'DDDD':
642
+ return parseTokenThreeDigits;
643
+ case 'YYYY':
644
+ return parseTokenFourDigits;
645
+ case 'YYYYY':
646
+ return parseTokenSixDigits;
647
+ case 'S':
648
+ case 'SS':
649
+ case 'SSS':
650
+ case 'DDD':
651
+ return parseTokenOneToThreeDigits;
652
+ case 'MMM':
653
+ case 'MMMM':
654
+ case 'dd':
655
+ case 'ddd':
656
+ case 'dddd':
657
+ return parseTokenWord;
658
+ case 'a':
659
+ case 'A':
660
+ return getLangDefinition(config._l)._meridiemParse;
661
+ case 'X':
662
+ return parseTokenTimestampMs;
663
+ case 'Z':
664
+ case 'ZZ':
665
+ return parseTokenTimezone;
666
+ case 'T':
667
+ return parseTokenT;
668
+ case 'MM':
669
+ case 'DD':
670
+ case 'YY':
671
+ case 'HH':
672
+ case 'hh':
673
+ case 'mm':
674
+ case 'ss':
675
+ case 'M':
676
+ case 'D':
677
+ case 'd':
678
+ case 'H':
679
+ case 'h':
680
+ case 'm':
681
+ case 's':
682
+ return parseTokenOneOrTwoDigits;
683
+ default :
684
+ return new RegExp(token.replace('\\', ''));
616
685
  }
617
686
  }
618
687
 
688
+ function timezoneMinutesFromString(string) {
689
+ var tzchunk = (parseTokenTimezone.exec(string) || [])[0],
690
+ parts = (tzchunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
691
+ minutes = +(parts[1] * 60) + ~~parts[2];
692
+
693
+ return parts[0] === '+' ? -minutes : minutes;
694
+ }
695
+
619
696
  // function to convert string input to date
620
697
  function addTimeToArrayFromToken(token, input, config) {
621
- var a, b,
622
- datePartArray = config._a;
698
+ var a, datePartArray = config._a;
623
699
 
624
700
  switch (token) {
625
- // MONTH
626
- case 'M' : // fall through to MM
627
- case 'MM' :
628
- datePartArray[1] = (input == null) ? 0 : ~~input - 1;
629
- break;
630
- case 'MMM' : // fall through to MMMM
631
- case 'MMMM' :
632
- a = getLangDefinition(config._l).monthsParse(input);
633
- // if we didn't find a month name, mark the date as invalid.
634
- if (a != null) {
635
- datePartArray[1] = a;
636
- } else {
637
- config._isValid = false;
638
- }
639
- break;
640
- // DAY OF MONTH
641
- case 'D' : // fall through to DDDD
642
- case 'DD' : // fall through to DDDD
643
- case 'DDD' : // fall through to DDDD
644
- case 'DDDD' :
645
- if (input != null) {
646
- datePartArray[2] = ~~input;
647
- }
648
- break;
649
- // YEAR
650
- case 'YY' :
651
- datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000);
652
- break;
653
- case 'YYYY' :
654
- case 'YYYYY' :
655
- datePartArray[0] = ~~input;
656
- break;
657
- // AM / PM
658
- case 'a' : // fall through to A
659
- case 'A' :
660
- config._isPm = ((input + '').toLowerCase() === 'pm');
661
- break;
662
- // 24 HOUR
663
- case 'H' : // fall through to hh
664
- case 'HH' : // fall through to hh
665
- case 'h' : // fall through to hh
666
- case 'hh' :
667
- datePartArray[3] = ~~input;
668
- break;
669
- // MINUTE
670
- case 'm' : // fall through to mm
671
- case 'mm' :
672
- datePartArray[4] = ~~input;
673
- break;
674
- // SECOND
675
- case 's' : // fall through to ss
676
- case 'ss' :
677
- datePartArray[5] = ~~input;
678
- break;
679
- // MILLISECOND
680
- case 'S' :
681
- case 'SS' :
682
- case 'SSS' :
683
- datePartArray[6] = ~~ (('0.' + input) * 1000);
684
- break;
685
- // UNIX TIMESTAMP WITH MS
686
- case 'X':
687
- config._d = new Date(parseFloat(input) * 1000);
688
- break;
689
- // TIMEZONE
690
- case 'Z' : // fall through to ZZ
691
- case 'ZZ' :
692
- config._useUTC = true;
693
- a = (input + '').match(parseTimezoneChunker);
694
- if (a && a[1]) {
695
- config._tzh = ~~a[1];
696
- }
697
- if (a && a[2]) {
698
- config._tzm = ~~a[2];
699
- }
700
- // reverse offsets
701
- if (a && a[0] === '+') {
702
- config._tzh = -config._tzh;
703
- config._tzm = -config._tzm;
704
- }
705
- break;
701
+ // MONTH
702
+ case 'M' : // fall through to MM
703
+ case 'MM' :
704
+ datePartArray[1] = (input == null) ? 0 : ~~input - 1;
705
+ break;
706
+ case 'MMM' : // fall through to MMMM
707
+ case 'MMMM' :
708
+ a = getLangDefinition(config._l).monthsParse(input);
709
+ // if we didn't find a month name, mark the date as invalid.
710
+ if (a != null) {
711
+ datePartArray[1] = a;
712
+ } else {
713
+ config._isValid = false;
714
+ }
715
+ break;
716
+ // DAY OF MONTH
717
+ case 'D' : // fall through to DDDD
718
+ case 'DD' : // fall through to DDDD
719
+ case 'DDD' : // fall through to DDDD
720
+ case 'DDDD' :
721
+ if (input != null) {
722
+ datePartArray[2] = ~~input;
723
+ }
724
+ break;
725
+ // YEAR
726
+ case 'YY' :
727
+ datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000);
728
+ break;
729
+ case 'YYYY' :
730
+ case 'YYYYY' :
731
+ datePartArray[0] = ~~input;
732
+ break;
733
+ // AM / PM
734
+ case 'a' : // fall through to A
735
+ case 'A' :
736
+ config._isPm = getLangDefinition(config._l).isPM(input);
737
+ break;
738
+ // 24 HOUR
739
+ case 'H' : // fall through to hh
740
+ case 'HH' : // fall through to hh
741
+ case 'h' : // fall through to hh
742
+ case 'hh' :
743
+ datePartArray[3] = ~~input;
744
+ break;
745
+ // MINUTE
746
+ case 'm' : // fall through to mm
747
+ case 'mm' :
748
+ datePartArray[4] = ~~input;
749
+ break;
750
+ // SECOND
751
+ case 's' : // fall through to ss
752
+ case 'ss' :
753
+ datePartArray[5] = ~~input;
754
+ break;
755
+ // MILLISECOND
756
+ case 'S' :
757
+ case 'SS' :
758
+ case 'SSS' :
759
+ datePartArray[6] = ~~ (('0.' + input) * 1000);
760
+ break;
761
+ // UNIX TIMESTAMP WITH MS
762
+ case 'X':
763
+ config._d = new Date(parseFloat(input) * 1000);
764
+ break;
765
+ // TIMEZONE
766
+ case 'Z' : // fall through to ZZ
767
+ case 'ZZ' :
768
+ config._useUTC = true;
769
+ config._tzm = timezoneMinutesFromString(input);
770
+ break;
706
771
  }
707
772
 
708
773
  // if the input is null, the date is not valid
@@ -727,8 +792,8 @@
727
792
  }
728
793
 
729
794
  // add the offsets to the time to be parsed so that we can have a clean array for checking isValid
730
- input[3] += config._tzh || 0;
731
- input[4] += config._tzm || 0;
795
+ input[3] += ~~((config._tzm || 0) / 60);
796
+ input[4] += ~~((config._tzm || 0) % 60);
732
797
 
733
798
  date = new Date(0);
734
799
 
@@ -753,7 +818,7 @@
753
818
  config._a = [];
754
819
 
755
820
  for (i = 0; i < tokens.length; i++) {
756
- parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0];
821
+ parsedInput = (getParseRegexForToken(tokens[i], config).exec(string) || [])[0];
757
822
  if (parsedInput) {
758
823
  string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
759
824
  }
@@ -762,6 +827,12 @@
762
827
  addTimeToArrayFromToken(tokens[i], parsedInput, config);
763
828
  }
764
829
  }
830
+
831
+ // add remaining unparsed input to the string
832
+ if (string) {
833
+ config._il = string;
834
+ }
835
+
765
836
  // handle am pm
766
837
  if (config._isPm && config._a[3] < 12) {
767
838
  config._a[3] += 12;
@@ -782,22 +853,22 @@
782
853
 
783
854
  scoreToBeat = 99,
784
855
  i,
785
- currentDate,
786
856
  currentScore;
787
857
 
788
- while (config._f.length) {
858
+ for (i = 0; i < config._f.length; i++) {
789
859
  tempConfig = extend({}, config);
790
- tempConfig._f = config._f.pop();
860
+ tempConfig._f = config._f[i];
791
861
  makeDateFromStringAndFormat(tempConfig);
792
862
  tempMoment = new Moment(tempConfig);
793
863
 
794
- if (tempMoment.isValid()) {
795
- bestMoment = tempMoment;
796
- break;
797
- }
798
-
799
864
  currentScore = compareArrays(tempConfig._a, tempMoment.toArray());
800
865
 
866
+ // if there is any input that was not parsed
867
+ // add a penalty for that format
868
+ if (tempMoment._il) {
869
+ currentScore += tempMoment._il.length;
870
+ }
871
+
801
872
  if (currentScore < scoreToBeat) {
802
873
  scoreToBeat = currentScore;
803
874
  bestMoment = tempMoment;
@@ -810,9 +881,12 @@
810
881
  // date from iso format
811
882
  function makeDateFromString(config) {
812
883
  var i,
813
- string = config._i;
814
- if (isoRegex.exec(string)) {
815
- config._f = 'YYYY-MM-DDT';
884
+ string = config._i,
885
+ match = isoRegex.exec(string);
886
+
887
+ if (match) {
888
+ // match[2] should be "T" or undefined
889
+ config._f = 'YYYY-MM-DD' + (match[2] || " ");
816
890
  for (i = 0; i < 4; i++) {
817
891
  if (isoTimes[i][1].exec(string)) {
818
892
  config._f += isoTimes[i][0];
@@ -848,11 +922,11 @@
848
922
 
849
923
 
850
924
  /************************************
851
- Relative Time
852
- ************************************/
925
+ Relative Time
926
+ ************************************/
853
927
 
854
928
 
855
- // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
929
+ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
856
930
  function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
857
931
  return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
858
932
  }
@@ -881,20 +955,21 @@
881
955
 
882
956
 
883
957
  /************************************
884
- Week of Year
885
- ************************************/
958
+ Week of Year
959
+ ************************************/
886
960
 
887
961
 
888
- // firstDayOfWeek 0 = sun, 6 = sat
889
- // the day of the week that starts the week
890
- // (usually sunday or monday)
891
- // firstDayOfWeekOfYear 0 = sun, 6 = sat
892
- // the first week is the week that contains the first
893
- // of this day of the week
894
- // (eg. ISO weeks use thursday (4))
962
+ // firstDayOfWeek 0 = sun, 6 = sat
963
+ // the day of the week that starts the week
964
+ // (usually sunday or monday)
965
+ // firstDayOfWeekOfYear 0 = sun, 6 = sat
966
+ // the first week is the week that contains the first
967
+ // of this day of the week
968
+ // (eg. ISO weeks use thursday (4))
895
969
  function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
896
970
  var end = firstDayOfWeekOfYear - firstDayOfWeek,
897
- daysToDayOfWeek = firstDayOfWeekOfYear - mom.day();
971
+ daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
972
+ adjustedMoment;
898
973
 
899
974
 
900
975
  if (daysToDayOfWeek > end) {
@@ -905,13 +980,17 @@
905
980
  daysToDayOfWeek += 7;
906
981
  }
907
982
 
908
- return Math.ceil(moment(mom).add('d', daysToDayOfWeek).dayOfYear() / 7);
983
+ adjustedMoment = moment(mom).add('d', daysToDayOfWeek);
984
+ return {
985
+ week: Math.ceil(adjustedMoment.dayOfYear() / 7),
986
+ year: adjustedMoment.year()
987
+ };
909
988
  }
910
989
 
911
990
 
912
991
  /************************************
913
- Top Level Functions
914
- ************************************/
992
+ Top Level Functions
993
+ ************************************/
915
994
 
916
995
  function makeMoment(config) {
917
996
  var input = config._i,
@@ -970,7 +1049,9 @@
970
1049
  moment.duration = function (input, key) {
971
1050
  var isDuration = moment.isDuration(input),
972
1051
  isNumber = (typeof input === 'number'),
973
- duration = (isDuration ? input._data : (isNumber ? {} : input)),
1052
+ duration = (isDuration ? input._input : (isNumber ? {} : input)),
1053
+ matched = aspNetTimeSpanJsonRegex.exec(input),
1054
+ sign,
974
1055
  ret;
975
1056
 
976
1057
  if (isNumber) {
@@ -979,6 +1060,16 @@
979
1060
  } else {
980
1061
  duration.milliseconds = input;
981
1062
  }
1063
+ } else if (matched) {
1064
+ sign = (matched[1] === "-") ? -1 : 1;
1065
+ duration = {
1066
+ y: 0,
1067
+ d: ~~matched[2] * sign,
1068
+ h: ~~matched[3] * sign,
1069
+ m: ~~matched[4] * sign,
1070
+ s: ~~matched[5] * sign,
1071
+ ms: ~~matched[6] * sign
1072
+ };
982
1073
  }
983
1074
 
984
1075
  ret = new Duration(duration);
@@ -996,12 +1087,14 @@
996
1087
  // default format
997
1088
  moment.defaultFormat = isoFormat;
998
1089
 
1090
+ // This function will be called whenever a moment is mutated.
1091
+ // It is intended to keep the offset in sync with the timezone.
1092
+ moment.updateOffset = function () {};
1093
+
999
1094
  // This function will load languages and then set the global language. If
1000
1095
  // no arguments are passed in, it will simply return the current global
1001
1096
  // language key.
1002
1097
  moment.lang = function (key, values) {
1003
- var i;
1004
-
1005
1098
  if (!key) {
1006
1099
  return moment.fn._lang._abbr;
1007
1100
  }
@@ -1033,8 +1126,8 @@
1033
1126
 
1034
1127
 
1035
1128
  /************************************
1036
- Moment Prototype
1037
- ************************************/
1129
+ Moment Prototype
1130
+ ************************************/
1038
1131
 
1039
1132
 
1040
1133
  moment.fn = Moment.prototype = {
@@ -1044,11 +1137,11 @@
1044
1137
  },
1045
1138
 
1046
1139
  valueOf : function () {
1047
- return +this._d;
1140
+ return +this._d + ((this._offset || 0) * 60000);
1048
1141
  },
1049
1142
 
1050
1143
  unix : function () {
1051
- return Math.floor(+this._d / 1000);
1144
+ return Math.floor(+this / 1000);
1052
1145
  },
1053
1146
 
1054
1147
  toString : function () {
@@ -1056,11 +1149,11 @@
1056
1149
  },
1057
1150
 
1058
1151
  toDate : function () {
1059
- return this._d;
1152
+ return this._offset ? new Date(+this) : this._d;
1060
1153
  },
1061
1154
 
1062
- toJSON : function () {
1063
- return moment.utc(this).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
1155
+ toISOString : function () {
1156
+ return formatMoment(moment(this).utc(), 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
1064
1157
  },
1065
1158
 
1066
1159
  toArray : function () {
@@ -1088,11 +1181,11 @@
1088
1181
  },
1089
1182
 
1090
1183
  utc : function () {
1091
- this._isUTC = true;
1092
- return this;
1184
+ return this.zone(0);
1093
1185
  },
1094
1186
 
1095
1187
  local : function () {
1188
+ this.zone(0);
1096
1189
  this._isUTC = false;
1097
1190
  return this;
1098
1191
  },
@@ -1127,30 +1220,35 @@
1127
1220
  },
1128
1221
 
1129
1222
  diff : function (input, units, asFloat) {
1130
- var that = this._isUTC ? moment(input).utc() : moment(input).local(),
1223
+ var that = this._isUTC ? moment(input).zone(this._offset || 0) : moment(input).local(),
1131
1224
  zoneDiff = (this.zone() - that.zone()) * 6e4,
1132
1225
  diff, output;
1133
1226
 
1134
- if (units) {
1135
- // standardize on singular form
1136
- units = units.replace(/s$/, '');
1137
- }
1227
+ units = normalizeUnits(units);
1138
1228
 
1139
1229
  if (units === 'year' || units === 'month') {
1230
+ // average number of days in the months in the given dates
1140
1231
  diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
1232
+ // difference in months
1141
1233
  output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
1142
- output += ((this - moment(this).startOf('month')) - (that - moment(that).startOf('month'))) / diff;
1234
+ // adjust by taking difference in days, average number of days
1235
+ // and dst in the given months.
1236
+ output += ((this - moment(this).startOf('month')) -
1237
+ (that - moment(that).startOf('month'))) / diff;
1238
+ // same as above but with zones, to negate all dst
1239
+ output -= ((this.zone() - moment(this).startOf('month').zone()) -
1240
+ (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
1143
1241
  if (units === 'year') {
1144
1242
  output = output / 12;
1145
1243
  }
1146
1244
  } else {
1147
- diff = (this - that) - zoneDiff;
1245
+ diff = (this - that);
1148
1246
  output = units === 'second' ? diff / 1e3 : // 1000
1149
1247
  units === 'minute' ? diff / 6e4 : // 1000 * 60
1150
- units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
1151
- units === 'day' ? diff / 864e5 : // 1000 * 60 * 60 * 24
1152
- units === 'week' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
1153
- diff;
1248
+ units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
1249
+ units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
1250
+ units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
1251
+ diff;
1154
1252
  }
1155
1253
  return asFloat ? output : absRound(output);
1156
1254
  },
@@ -1166,11 +1264,11 @@
1166
1264
  calendar : function () {
1167
1265
  var diff = this.diff(moment().startOf('day'), 'days', true),
1168
1266
  format = diff < -6 ? 'sameElse' :
1169
- diff < -1 ? 'lastWeek' :
1170
- diff < 0 ? 'lastDay' :
1171
- diff < 1 ? 'sameDay' :
1172
- diff < 2 ? 'nextDay' :
1173
- diff < 7 ? 'nextWeek' : 'sameElse';
1267
+ diff < -1 ? 'lastWeek' :
1268
+ diff < 0 ? 'lastDay' :
1269
+ diff < 1 ? 'sameDay' :
1270
+ diff < 2 ? 'nextDay' :
1271
+ diff < 7 ? 'nextWeek' : 'sameElse';
1174
1272
  return this.format(this.lang().calendar(format, this));
1175
1273
  },
1176
1274
 
@@ -1180,52 +1278,86 @@
1180
1278
  },
1181
1279
 
1182
1280
  isDST : function () {
1183
- return (this.zone() < moment([this.year()]).zone() ||
1184
- this.zone() < moment([this.year(), 5]).zone());
1281
+ return (this.zone() < this.clone().month(0).zone() ||
1282
+ this.zone() < this.clone().month(5).zone());
1185
1283
  },
1186
1284
 
1187
1285
  day : function (input) {
1188
1286
  var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
1189
- return input == null ? day :
1190
- this.add({ d : input - day });
1287
+ if (input != null) {
1288
+ if (typeof input === 'string') {
1289
+ input = this.lang().weekdaysParse(input);
1290
+ if (typeof input !== 'number') {
1291
+ return this;
1292
+ }
1293
+ }
1294
+ return this.add({ d : input - day });
1295
+ } else {
1296
+ return day;
1297
+ }
1298
+ },
1299
+
1300
+ month : function (input) {
1301
+ var utc = this._isUTC ? 'UTC' : '',
1302
+ dayOfMonth,
1303
+ daysInMonth;
1304
+
1305
+ if (input != null) {
1306
+ if (typeof input === 'string') {
1307
+ input = this.lang().monthsParse(input);
1308
+ if (typeof input !== 'number') {
1309
+ return this;
1310
+ }
1311
+ }
1312
+
1313
+ dayOfMonth = this.date();
1314
+ this.date(1);
1315
+ this._d['set' + utc + 'Month'](input);
1316
+ this.date(Math.min(dayOfMonth, this.daysInMonth()));
1317
+
1318
+ moment.updateOffset(this);
1319
+ return this;
1320
+ } else {
1321
+ return this._d['get' + utc + 'Month']();
1322
+ }
1191
1323
  },
1192
1324
 
1193
1325
  startOf: function (units) {
1194
- units = units.replace(/s$/, '');
1326
+ units = normalizeUnits(units);
1195
1327
  // the following switch intentionally omits break keywords
1196
1328
  // to utilize falling through the cases.
1197
1329
  switch (units) {
1198
- case 'year':
1199
- this.month(0);
1330
+ case 'year':
1331
+ this.month(0);
1200
1332
  /* falls through */
1201
- case 'month':
1202
- this.date(1);
1333
+ case 'month':
1334
+ this.date(1);
1203
1335
  /* falls through */
1204
- case 'week':
1205
- case 'day':
1206
- this.hours(0);
1336
+ case 'week':
1337
+ case 'day':
1338
+ this.hours(0);
1207
1339
  /* falls through */
1208
- case 'hour':
1209
- this.minutes(0);
1340
+ case 'hour':
1341
+ this.minutes(0);
1210
1342
  /* falls through */
1211
- case 'minute':
1212
- this.seconds(0);
1343
+ case 'minute':
1344
+ this.seconds(0);
1213
1345
  /* falls through */
1214
- case 'second':
1215
- this.milliseconds(0);
1346
+ case 'second':
1347
+ this.milliseconds(0);
1216
1348
  /* falls through */
1217
1349
  }
1218
1350
 
1219
1351
  // weeks are a special case
1220
1352
  if (units === 'week') {
1221
- this.day(0);
1353
+ this.weekday(0);
1222
1354
  }
1223
1355
 
1224
1356
  return this;
1225
1357
  },
1226
1358
 
1227
1359
  endOf: function (units) {
1228
- return this.startOf(units).add(units.replace(/s?$/, 's'), 1).subtract('ms', 1);
1360
+ return this.startOf(units).add(units, 1).subtract('ms', 1);
1229
1361
  },
1230
1362
 
1231
1363
  isAfter: function (input, units) {
@@ -1243,8 +1375,42 @@
1243
1375
  return +this.clone().startOf(units) === +moment(input).startOf(units);
1244
1376
  },
1245
1377
 
1246
- zone : function () {
1247
- return this._isUTC ? 0 : this._d.getTimezoneOffset();
1378
+ min: function (other) {
1379
+ other = moment.apply(null, arguments);
1380
+ return other < this ? this : other;
1381
+ },
1382
+
1383
+ max: function (other) {
1384
+ other = moment.apply(null, arguments);
1385
+ return other > this ? this : other;
1386
+ },
1387
+
1388
+ zone : function (input) {
1389
+ var offset = this._offset || 0;
1390
+ if (input != null) {
1391
+ if (typeof input === "string") {
1392
+ input = timezoneMinutesFromString(input);
1393
+ }
1394
+ if (Math.abs(input) < 16) {
1395
+ input = input * 60;
1396
+ }
1397
+ this._offset = input;
1398
+ this._isUTC = true;
1399
+ if (offset !== input) {
1400
+ addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true);
1401
+ }
1402
+ } else {
1403
+ return this._isUTC ? offset : this._d.getTimezoneOffset();
1404
+ }
1405
+ return this;
1406
+ },
1407
+
1408
+ zoneAbbr : function () {
1409
+ return this._isUTC ? "UTC" : "";
1410
+ },
1411
+
1412
+ zoneName : function () {
1413
+ return this._isUTC ? "Coordinated Universal Time" : "";
1248
1414
  },
1249
1415
 
1250
1416
  daysInMonth : function () {
@@ -1256,9 +1422,14 @@
1256
1422
  return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
1257
1423
  },
1258
1424
 
1259
- isoWeek : function (input) {
1260
- var week = weekOfYear(this, 1, 4);
1261
- return input == null ? week : this.add("d", (input - week) * 7);
1425
+ weekYear : function (input) {
1426
+ var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
1427
+ return input == null ? year : this.add("y", (input - year));
1428
+ },
1429
+
1430
+ isoWeekYear : function (input) {
1431
+ var year = weekOfYear(this, 1, 4).year;
1432
+ return input == null ? year : this.add("y", (input - year));
1262
1433
  },
1263
1434
 
1264
1435
  week : function (input) {
@@ -1266,6 +1437,23 @@
1266
1437
  return input == null ? week : this.add("d", (input - week) * 7);
1267
1438
  },
1268
1439
 
1440
+ isoWeek : function (input) {
1441
+ var week = weekOfYear(this, 1, 4).week;
1442
+ return input == null ? week : this.add("d", (input - week) * 7);
1443
+ },
1444
+
1445
+ weekday : function (input) {
1446
+ var weekday = (this._d.getDay() + 7 - this.lang()._week.dow) % 7;
1447
+ return input == null ? weekday : this.add("d", input - weekday);
1448
+ },
1449
+
1450
+ isoWeekday : function (input) {
1451
+ // behaves the same as moment#day except
1452
+ // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
1453
+ // as a setter, sunday should belong to the previous week.
1454
+ return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
1455
+ },
1456
+
1269
1457
  // If passed a language key, it will set the language for this
1270
1458
  // instance. Otherwise, it will return the language configuration
1271
1459
  // variables for this instance.
@@ -1285,6 +1473,7 @@
1285
1473
  var utc = this._isUTC ? 'UTC' : '';
1286
1474
  if (input != null) {
1287
1475
  this._d['set' + utc + key](input);
1476
+ moment.updateOffset(this);
1288
1477
  return this;
1289
1478
  } else {
1290
1479
  return this._d['get' + utc + key]();
@@ -1302,23 +1491,58 @@
1302
1491
 
1303
1492
  // add plural methods
1304
1493
  moment.fn.days = moment.fn.day;
1494
+ moment.fn.months = moment.fn.month;
1305
1495
  moment.fn.weeks = moment.fn.week;
1306
1496
  moment.fn.isoWeeks = moment.fn.isoWeek;
1307
1497
 
1498
+ // add aliased format methods
1499
+ moment.fn.toJSON = moment.fn.toISOString;
1500
+
1308
1501
  /************************************
1309
- Duration Prototype
1310
- ************************************/
1502
+ Duration Prototype
1503
+ ************************************/
1311
1504
 
1312
1505
 
1313
1506
  moment.duration.fn = Duration.prototype = {
1507
+ _bubble : function () {
1508
+ var milliseconds = this._milliseconds,
1509
+ days = this._days,
1510
+ months = this._months,
1511
+ data = this._data,
1512
+ seconds, minutes, hours, years;
1513
+
1514
+ // The following code bubbles up values, see the tests for
1515
+ // examples of what that means.
1516
+ data.milliseconds = milliseconds % 1000;
1517
+
1518
+ seconds = absRound(milliseconds / 1000);
1519
+ data.seconds = seconds % 60;
1520
+
1521
+ minutes = absRound(seconds / 60);
1522
+ data.minutes = minutes % 60;
1523
+
1524
+ hours = absRound(minutes / 60);
1525
+ data.hours = hours % 24;
1526
+
1527
+ days += absRound(hours / 24);
1528
+ data.days = days % 30;
1529
+
1530
+ months += absRound(days / 30);
1531
+ data.months = months % 12;
1532
+
1533
+ years = absRound(months / 12);
1534
+ data.years = years;
1535
+ },
1536
+
1314
1537
  weeks : function () {
1315
1538
  return absRound(this.days() / 7);
1316
1539
  },
1317
1540
 
1318
1541
  valueOf : function () {
1319
1542
  return this._milliseconds +
1320
- this._days * 864e5 +
1321
- this._months * 2592e6;
1543
+ this._days * 864e5 +
1544
+ (this._months % 12) * 2592e6 +
1545
+ ~~(this._months / 12) * 31536e6;
1322
1546
  },
1323
1547
 
1324
1548
  humanize : function (withSuffix) {
@@ -1332,6 +1556,41 @@
1332
1556
  return this.lang().postformat(output);
1333
1557
  },
1334
1558
 
1559
+ add : function (input, val) {
1560
+ // supports only 2.0-style add(1, 's') or add(moment)
1561
+ var dur = moment.duration(input, val);
1562
+
1563
+ this._milliseconds += dur._milliseconds;
1564
+ this._days += dur._days;
1565
+ this._months += dur._months;
1566
+
1567
+ this._bubble();
1568
+
1569
+ return this;
1570
+ },
1571
+
1572
+ subtract : function (input, val) {
1573
+ var dur = moment.duration(input, val);
1574
+
1575
+ this._milliseconds -= dur._milliseconds;
1576
+ this._days -= dur._days;
1577
+ this._months -= dur._months;
1578
+
1579
+ this._bubble();
1580
+
1581
+ return this;
1582
+ },
1583
+
1584
+ get : function (units) {
1585
+ units = normalizeUnits(units);
1586
+ return this[units.toLowerCase() + 's']();
1587
+ },
1588
+
1589
+ as : function (units) {
1590
+ units = normalizeUnits(units);
1591
+ return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
1592
+ },
1593
+
1335
1594
  lang : moment.fn.lang
1336
1595
  };
1337
1596
 
@@ -1355,29 +1614,32 @@
1355
1614
  }
1356
1615
 
1357
1616
  makeDurationAsGetter('Weeks', 6048e5);
1617
+ moment.duration.fn.asMonths = function () {
1618
+ return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12;
1619
+ };
1358
1620
 
1359
1621
 
1360
1622
  /************************************
1361
- Default Lang
1362
- ************************************/
1623
+ Default Lang
1624
+ ************************************/
1363
1625
 
1364
1626
 
1365
- // Set default language, other languages will inherit from English.
1627
+ // Set default language, other languages will inherit from English.
1366
1628
  moment.lang('en', {
1367
1629
  ordinal : function (number) {
1368
1630
  var b = number % 10,
1369
1631
  output = (~~ (number % 100 / 10) === 1) ? 'th' :
1370
- (b === 1) ? 'st' :
1371
- (b === 2) ? 'nd' :
1372
- (b === 3) ? 'rd' : 'th';
1632
+ (b === 1) ? 'st' :
1633
+ (b === 2) ? 'nd' :
1634
+ (b === 3) ? 'rd' : 'th';
1373
1635
  return number + output;
1374
1636
  }
1375
1637
  });
1376
1638
 
1377
1639
 
1378
1640
  /************************************
1379
- Exposing Moment
1380
- ************************************/
1641
+ Exposing Moment
1642
+ ************************************/
1381
1643
 
1382
1644
 
1383
1645
  // CommonJS module is defined
@@ -1397,4 +1659,4 @@
1397
1659
  return moment;
1398
1660
  });
1399
1661
  }
1400
- }).call(this);
1662
+ }).call(this);