momentjs-rails 1.7.2 → 2.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/README.md +1 -1
  2. data/{changelog.md → news.md} +30 -12
  3. data/test/dummy/Rakefile +7 -0
  4. data/test/dummy/config.ru +4 -0
  5. data/test/dummy/config/application.rb +65 -0
  6. data/test/dummy/config/boot.rb +10 -0
  7. data/test/dummy/config/environment.rb +5 -0
  8. data/test/dummy/config/environments/development.rb +31 -0
  9. data/test/dummy/config/environments/production.rb +64 -0
  10. data/test/dummy/config/environments/test.rb +35 -0
  11. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  12. data/test/dummy/config/initializers/inflections.rb +15 -0
  13. data/test/dummy/config/initializers/mime_types.rb +5 -0
  14. data/test/dummy/config/initializers/secret_token.rb +7 -0
  15. data/test/dummy/config/initializers/session_store.rb +8 -0
  16. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  17. data/test/dummy/config/locales/en.yml +5 -0
  18. data/test/dummy/config/routes.rb +2 -0
  19. data/test/dummy/log/test.log +76 -0
  20. data/test/dummy/tmp/cache/assets/D1C/680/sprockets%2F16ed4d5afdb2919596e6f8790e2116c2 +0 -0
  21. data/test/dummy/tmp/cache/assets/D35/CA0/sprockets%2F493e64bce2d302801dc01cf7cd096a58 +0 -0
  22. data/test/dummy/tmp/cache/assets/D3F/5F0/sprockets%2F4a6c53eae7c6e41a69a191578bbb6417 +0 -0
  23. data/test/dummy/tmp/cache/assets/D58/BD0/sprockets%2F4de9ddc9b725c715a54c98697b2528fe +0 -0
  24. data/test/dummy/tmp/cache/assets/D75/510/sprockets%2Fe690787ac522f47a6be024bfcc1767ee +0 -0
  25. data/test/dummy/tmp/cache/assets/E10/250/sprockets%2Fe4badb9ddfda484eb9671bf88567be61 +0 -0
  26. data/test/integration/navigation_test.rb +14 -0
  27. data/test/momentjs-rails_test.rb +7 -0
  28. data/test/test_helper.rb +15 -0
  29. data/vendor/assets/javascripts/moment.js +542 -355
  30. data/vendor/assets/javascripts/moment/ar-ma.js +46 -0
  31. data/vendor/assets/javascripts/moment/ar.js +45 -0
  32. data/vendor/assets/javascripts/moment/bg.js +71 -61
  33. data/vendor/assets/javascripts/moment/ca.js +51 -59
  34. data/vendor/assets/javascripts/moment/cs.js +145 -0
  35. data/vendor/assets/javascripts/moment/cv.js +45 -53
  36. data/vendor/assets/javascripts/moment/da.js +41 -49
  37. data/vendor/assets/javascripts/moment/de.js +41 -49
  38. data/vendor/assets/javascripts/moment/en-ca.js +44 -53
  39. data/vendor/assets/javascripts/moment/en-gb.js +48 -53
  40. data/vendor/assets/javascripts/moment/eo.js +55 -0
  41. data/vendor/assets/javascripts/moment/es.js +51 -59
  42. data/vendor/assets/javascripts/moment/et.js +44 -61
  43. data/vendor/assets/javascripts/moment/eu.js +46 -50
  44. data/vendor/assets/javascripts/moment/fi.js +84 -89
  45. data/vendor/assets/javascripts/moment/fr-ca.js +39 -49
  46. data/vendor/assets/javascripts/moment/fr.js +43 -49
  47. data/vendor/assets/javascripts/moment/gl.js +51 -59
  48. data/vendor/assets/javascripts/moment/he.js +46 -0
  49. data/vendor/assets/javascripts/moment/hu.js +77 -98
  50. data/vendor/assets/javascripts/moment/id.js +57 -0
  51. data/vendor/assets/javascripts/moment/is.js +103 -121
  52. data/vendor/assets/javascripts/moment/it.js +41 -49
  53. data/vendor/assets/javascripts/moment/ja.js +43 -56
  54. data/vendor/assets/javascripts/moment/ko.js +42 -54
  55. data/vendor/assets/javascripts/moment/lv.js +67 -0
  56. data/vendor/assets/javascripts/moment/nb.js +41 -49
  57. data/vendor/assets/javascripts/moment/ne.js +95 -0
  58. data/vendor/assets/javascripts/moment/nl.js +52 -57
  59. data/vendor/assets/javascripts/moment/pl.js +73 -70
  60. data/vendor/assets/javascripts/moment/pt-br.js +42 -54
  61. data/vendor/assets/javascripts/moment/pt.js +45 -53
  62. data/vendor/assets/javascripts/moment/ro.js +40 -49
  63. data/vendor/assets/javascripts/moment/ru.js +105 -114
  64. data/vendor/assets/javascripts/moment/sl.js +134 -0
  65. data/vendor/assets/javascripts/moment/sv.js +48 -53
  66. data/vendor/assets/javascripts/moment/th.js +48 -0
  67. data/vendor/assets/javascripts/moment/tr.js +69 -75
  68. data/vendor/assets/javascripts/moment/tzm-la.js +45 -0
  69. data/vendor/assets/javascripts/moment/tzm.js +45 -0
  70. data/vendor/assets/javascripts/moment/uk.js +122 -0
  71. data/vendor/assets/javascripts/moment/zh-cn.js +53 -62
  72. data/vendor/assets/javascripts/moment/zh-tw.js +53 -62
  73. metadata +121 -54
  74. data/vendor/assets/javascripts/moment/jp.js +0 -64
  75. data/vendor/assets/javascripts/moment/kr.js +0 -61
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+
3
+ class NavigationTest < ActionDispatch::IntegrationTest
4
+ test 'can access momentjs' do
5
+ get '/assets/moment.js'
6
+ assert_response :success
7
+ end
8
+
9
+ test 'can access momentjs translation' do
10
+ get 'assets/moment/fr.js'
11
+ assert_response :success
12
+ end
13
+ end
14
+
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class MomentjsRailsTest < ActiveSupport::TestCase
4
+ test "truth" do
5
+ assert_kind_of Module, Momentjs::Rails
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
6
+
7
+ Rails.backtrace_cleaner.remove_silencers!
8
+
9
+ # Load support files
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
+
12
+ # Load fixtures from the engine
13
+ if ActiveSupport::TestCase.method_defined?(:fixture_path=)
14
+ ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
15
+ end
@@ -1,5 +1,5 @@
1
1
  // moment.js
2
- // version : 1.7.2
2
+ // version : 2.0.0
3
3
  // author : Tim Wood
4
4
  // license : MIT
5
5
  // momentjs.com
@@ -11,27 +11,20 @@
11
11
  ************************************/
12
12
 
13
13
  var moment,
14
- VERSION = "1.7.2",
14
+ VERSION = "2.0.0",
15
15
  round = Math.round, i,
16
16
  // internal storage for language config files
17
17
  languages = {},
18
- currentLanguage = 'en',
19
18
 
20
19
  // check for nodeJS
21
20
  hasModule = (typeof module !== 'undefined' && module.exports),
22
21
 
23
- // Parameters to check for on the lang config. This list of properties
24
- // will be inherited from English if not provided in a language
25
- // definition. monthsParse is also a lang config property, but it
26
- // cannot be inherited and as such cannot be enumerated here.
27
- langConfigProperties = 'months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'),
28
-
29
22
  // ASP.NET json date format regex
30
23
  aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
31
24
 
32
25
  // format tokens
33
- formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|.)/g,
34
- localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?)/g,
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,
27
+ localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
35
28
 
36
29
  // parsing tokens
37
30
  parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,
@@ -41,21 +34,23 @@
41
34
  parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
42
35
  parseTokenThreeDigits = /\d{3}/, // 000 - 999
43
36
  parseTokenFourDigits = /\d{1,4}/, // 0 - 9999
44
- parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers
37
+ 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.
45
39
  parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
46
40
  parseTokenT = /T/i, // T (ISO seperator)
41
+ parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
47
42
 
48
43
  // preliminary iso regex
49
44
  // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
50
- isoRegex = /^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
45
+ isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
51
46
  isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
52
47
 
53
48
  // iso time formats and regexes
54
49
  isoTimes = [
55
- ['HH:mm:ss.S', /T\d\d:\d\d:\d\d\.\d{1,3}/],
56
- ['HH:mm:ss', /T\d\d:\d\d:\d\d/],
57
- ['HH:mm', /T\d\d:\d\d/],
58
- ['HH', /T\d\d/]
50
+ ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
51
+ ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
52
+ ['HH:mm', /(T| )\d\d:\d\d/],
53
+ ['HH', /(T| )\d\d/]
59
54
  ],
60
55
 
61
56
  // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
@@ -77,57 +72,42 @@
77
72
  formatFunctions = {},
78
73
 
79
74
  // tokens to ordinalize and pad
80
- ordinalizeTokens = 'DDD w M D d'.split(' '),
81
- paddedTokens = 'M D H h m s w'.split(' '),
82
-
83
- /*
84
- * moment.fn.format uses new Function() to create an inlined formatting function.
85
- * Results are a 3x speed boost
86
- * http://jsperf.com/momentjs-cached-format-functions
87
- *
88
- * These strings are appended into a function using replaceFormatTokens and makeFormatFunction
89
- */
75
+ ordinalizeTokens = 'DDD w W M D d'.split(' '),
76
+ paddedTokens = 'M D H h m s w W'.split(' '),
77
+
90
78
  formatTokenFunctions = {
91
- // a = placeholder
92
- // b = placeholder
93
- // t = the current moment being formatted
94
- // v = getValueAtKey function
95
- // o = language.ordinal function
96
- // p = leftZeroFill function
97
- // m = language.meridiem value or function
98
79
  M : function () {
99
80
  return this.month() + 1;
100
81
  },
101
82
  MMM : function (format) {
102
- return getValueFromArray("monthsShort", this.month(), this, format);
83
+ return this.lang().monthsShort(this, format);
103
84
  },
104
85
  MMMM : function (format) {
105
- return getValueFromArray("months", this.month(), this, format);
86
+ return this.lang().months(this, format);
106
87
  },
107
88
  D : function () {
108
89
  return this.date();
109
90
  },
110
91
  DDD : function () {
111
- var a = new Date(this.year(), this.month(), this.date()),
112
- b = new Date(this.year(), 0, 1);
113
- return ~~(((a - b) / 864e5) + 1.5);
92
+ return this.dayOfYear();
114
93
  },
115
94
  d : function () {
116
95
  return this.day();
117
96
  },
118
97
  dd : function (format) {
119
- return getValueFromArray("weekdaysMin", this.day(), this, format);
98
+ return this.lang().weekdaysMin(this, format);
120
99
  },
121
100
  ddd : function (format) {
122
- return getValueFromArray("weekdaysShort", this.day(), this, format);
101
+ return this.lang().weekdaysShort(this, format);
123
102
  },
124
103
  dddd : function (format) {
125
- return getValueFromArray("weekdays", this.day(), this, format);
104
+ return this.lang().weekdays(this, format);
126
105
  },
127
106
  w : function () {
128
- var a = new Date(this.year(), this.month(), this.date() - this.day() + 5),
129
- b = new Date(a.getFullYear(), 0, 4);
130
- return ~~((a - b) / 864e5 / 7 + 1.5);
107
+ return this.week();
108
+ },
109
+ W : function () {
110
+ return this.isoWeek();
131
111
  },
132
112
  YY : function () {
133
113
  return leftZeroFill(this.year() % 100, 2);
@@ -135,6 +115,9 @@
135
115
  YYYY : function () {
136
116
  return leftZeroFill(this.year(), 4);
137
117
  },
118
+ YYYYY : function () {
119
+ return leftZeroFill(this.year(), 5);
120
+ },
138
121
  a : function () {
139
122
  return this.lang().meridiem(this.hours(), this.minutes(), true);
140
123
  },
@@ -179,14 +162,12 @@
179
162
  b = "-";
180
163
  }
181
164
  return b + leftZeroFill(~~(10 * a / 6), 4);
165
+ },
166
+ X : function () {
167
+ return this.unix();
182
168
  }
183
169
  };
184
170
 
185
- function getValueFromArray(key, index, m, format) {
186
- var lang = m.lang();
187
- return lang[key].call ? lang[key](m, format) : lang[key][index];
188
- }
189
-
190
171
  function padToken(func, count) {
191
172
  return function (a) {
192
173
  return leftZeroFill(func.call(this, a), count);
@@ -194,8 +175,7 @@
194
175
  }
195
176
  function ordinalizeToken(func) {
196
177
  return function (a) {
197
- var b = func.call(this, a);
198
- return b + this.lang().ordinal(b);
178
+ return this.lang().ordinal(func.call(this, a));
199
179
  };
200
180
  }
201
181
 
@@ -214,26 +194,26 @@
214
194
  Constructors
215
195
  ************************************/
216
196
 
197
+ function Language() {
198
+
199
+ }
217
200
 
218
201
  // Moment prototype object
219
- function Moment(date, isUTC, lang) {
220
- this._d = date;
221
- this._isUTC = !!isUTC;
222
- this._a = date._a || null;
223
- this._lang = lang || false;
202
+ function Moment(config) {
203
+ extend(this, config);
224
204
  }
225
205
 
226
206
  // Duration Constructor
227
207
  function Duration(duration) {
228
208
  var data = this._data = {},
229
- years = duration.years || duration.y || 0,
230
- months = duration.months || duration.M || 0,
231
- weeks = duration.weeks || duration.w || 0,
232
- days = duration.days || duration.d || 0,
233
- hours = duration.hours || duration.h || 0,
234
- minutes = duration.minutes || duration.m || 0,
235
- seconds = duration.seconds || duration.s || 0,
236
- milliseconds = duration.milliseconds || duration.ms || 0;
209
+ years = duration.years || duration.year || duration.y || 0,
210
+ months = duration.months || duration.month || duration.M || 0,
211
+ weeks = duration.weeks || duration.week || duration.w || 0,
212
+ days = duration.days || duration.day || duration.d || 0,
213
+ hours = duration.hours || duration.hour || duration.h || 0,
214
+ minutes = duration.minutes || duration.minute || duration.m || 0,
215
+ seconds = duration.seconds || duration.second || duration.s || 0,
216
+ milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0;
237
217
 
238
218
  // representation for dateAddRemove
239
219
  this._milliseconds = milliseconds +
@@ -273,8 +253,6 @@
273
253
  years += absRound(months / 12);
274
254
 
275
255
  data.years = years;
276
-
277
- this._lang = false;
278
256
  }
279
257
 
280
258
 
@@ -283,6 +261,15 @@
283
261
  ************************************/
284
262
 
285
263
 
264
+ function extend(a, b) {
265
+ for (var i in b) {
266
+ if (b.hasOwnProperty(i)) {
267
+ a[i] = b[i];
268
+ }
269
+ }
270
+ return a;
271
+ }
272
+
286
273
  function absRound(number) {
287
274
  if (number < 0) {
288
275
  return Math.ceil(number);
@@ -341,66 +328,169 @@
341
328
  return diffs + lengthDiff;
342
329
  }
343
330
 
344
- // convert an array to a date.
345
- // the array should mirror the parameters below
346
- // note: all values past the year are optional and will default to the lowest possible value.
347
- // [year, month, day , hour, minute, second, millisecond]
348
- function dateFromArray(input, asUTC, hoursOffset, minutesOffset) {
349
- var i, date, forValid = [];
350
- for (i = 0; i < 7; i++) {
351
- forValid[i] = input[i] = (input[i] == null) ? (i === 2 ? 1 : 0) : input[i];
352
- }
353
- // we store whether we used utc or not in the input array
354
- input[7] = forValid[7] = asUTC;
355
- // if the parser flagged the input as invalid, we pass the value along
356
- if (input[8] != null) {
357
- forValid[8] = input[8];
358
- }
359
- // add the offsets to the time to be parsed so that we can have a clean array
360
- // for checking isValid
361
- input[3] += hoursOffset || 0;
362
- input[4] += minutesOffset || 0;
363
- date = new Date(0);
364
- if (asUTC) {
365
- date.setUTCFullYear(input[0], input[1], input[2]);
366
- date.setUTCHours(input[3], input[4], input[5], input[6]);
367
- } else {
368
- date.setFullYear(input[0], input[1], input[2]);
369
- date.setHours(input[3], input[4], input[5], input[6]);
331
+
332
+ /************************************
333
+ Languages
334
+ ************************************/
335
+
336
+
337
+ Language.prototype = {
338
+ set : function (config) {
339
+ var prop, i;
340
+ for (i in config) {
341
+ prop = config[i];
342
+ if (typeof prop === 'function') {
343
+ this[i] = prop;
344
+ } else {
345
+ this['_' + i] = prop;
346
+ }
347
+ }
348
+ },
349
+
350
+ _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
351
+ months : function (m) {
352
+ return this._months[m.month()];
353
+ },
354
+
355
+ _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
356
+ monthsShort : function (m) {
357
+ return this._monthsShort[m.month()];
358
+ },
359
+
360
+ monthsParse : function (monthName) {
361
+ var i, mom, regex, output;
362
+
363
+ if (!this._monthsParse) {
364
+ this._monthsParse = [];
365
+ }
366
+
367
+ for (i = 0; i < 12; i++) {
368
+ // make the regex if we don't have it already
369
+ if (!this._monthsParse[i]) {
370
+ mom = moment([2000, i]);
371
+ regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
372
+ this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
373
+ }
374
+ // test the regex
375
+ if (this._monthsParse[i].test(monthName)) {
376
+ return i;
377
+ }
378
+ }
379
+ },
380
+
381
+ _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
382
+ weekdays : function (m) {
383
+ return this._weekdays[m.day()];
384
+ },
385
+
386
+ _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
387
+ weekdaysShort : function (m) {
388
+ return this._weekdaysShort[m.day()];
389
+ },
390
+
391
+ _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
392
+ weekdaysMin : function (m) {
393
+ return this._weekdaysMin[m.day()];
394
+ },
395
+
396
+ _longDateFormat : {
397
+ LT : "h:mm A",
398
+ L : "MM/DD/YYYY",
399
+ LL : "MMMM D YYYY",
400
+ LLL : "MMMM D YYYY LT",
401
+ LLLL : "dddd, MMMM D YYYY LT"
402
+ },
403
+ longDateFormat : function (key) {
404
+ var output = this._longDateFormat[key];
405
+ if (!output && this._longDateFormat[key.toUpperCase()]) {
406
+ output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
407
+ return val.slice(1);
408
+ });
409
+ this._longDateFormat[key] = output;
410
+ }
411
+ return output;
412
+ },
413
+
414
+ meridiem : function (hours, minutes, isLower) {
415
+ if (hours > 11) {
416
+ return isLower ? 'pm' : 'PM';
417
+ } else {
418
+ return isLower ? 'am' : 'AM';
419
+ }
420
+ },
421
+
422
+ _calendar : {
423
+ sameDay : '[Today at] LT',
424
+ nextDay : '[Tomorrow at] LT',
425
+ nextWeek : 'dddd [at] LT',
426
+ lastDay : '[Yesterday at] LT',
427
+ lastWeek : '[last] dddd [at] LT',
428
+ sameElse : 'L'
429
+ },
430
+ calendar : function (key, mom) {
431
+ var output = this._calendar[key];
432
+ return typeof output === 'function' ? output.apply(mom) : output;
433
+ },
434
+
435
+ _relativeTime : {
436
+ future : "in %s",
437
+ past : "%s ago",
438
+ s : "a few seconds",
439
+ m : "a minute",
440
+ mm : "%d minutes",
441
+ h : "an hour",
442
+ hh : "%d hours",
443
+ d : "a day",
444
+ dd : "%d days",
445
+ M : "a month",
446
+ MM : "%d months",
447
+ y : "a year",
448
+ yy : "%d years"
449
+ },
450
+ relativeTime : function (number, withoutSuffix, string, isFuture) {
451
+ var output = this._relativeTime[string];
452
+ return (typeof output === 'function') ?
453
+ output(number, withoutSuffix, string, isFuture) :
454
+ output.replace(/%d/i, number);
455
+ },
456
+ pastFuture : function (diff, output) {
457
+ var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
458
+ return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
459
+ },
460
+
461
+ ordinal : function (number) {
462
+ return this._ordinal.replace("%d", number);
463
+ },
464
+ _ordinal : "%d",
465
+
466
+ preparse : function (string) {
467
+ return string;
468
+ },
469
+
470
+ postformat : function (string) {
471
+ return string;
472
+ },
473
+
474
+ week : function (mom) {
475
+ return weekOfYear(mom, this._week.dow, this._week.doy);
476
+ },
477
+ _week : {
478
+ dow : 0, // Sunday is the first day of the week.
479
+ doy : 6 // The week that contains Jan 1st is the first week of the year.
370
480
  }
371
- date._a = forValid;
372
- return date;
373
- }
481
+ };
374
482
 
375
483
  // Loads a language definition into the `languages` cache. The function
376
484
  // takes a key and optionally values. If not in the browser and no values
377
485
  // are provided, it will load the language file module. As a convenience,
378
486
  // this function also returns the language values.
379
487
  function loadLang(key, values) {
380
- var i, m,
381
- parse = [];
382
-
383
- if (!values && hasModule) {
384
- values = require('./lang/' + key);
385
- }
386
-
387
- for (i = 0; i < langConfigProperties.length; i++) {
388
- // If a language definition does not provide a value, inherit
389
- // from English
390
- values[langConfigProperties[i]] = values[langConfigProperties[i]] ||
391
- languages.en[langConfigProperties[i]];
392
- }
393
-
394
- for (i = 0; i < 12; i++) {
395
- m = moment([2000, i]);
396
- parse[i] = new RegExp('^' + (values.months[i] || values.months(m, '')) +
397
- '|^' + (values.monthsShort[i] || values.monthsShort(m, '')).replace('.', ''), 'i');
488
+ values.abbr = key;
489
+ if (!languages[key]) {
490
+ languages[key] = new Language();
398
491
  }
399
- values.monthsParse = values.monthsParse || parse;
400
-
401
- languages[key] = values;
402
-
403
- return values;
492
+ languages[key].set(values);
493
+ return languages[key];
404
494
  }
405
495
 
406
496
  // Determines which language definition to use and returns it.
@@ -408,15 +498,15 @@
408
498
  // With no parameters, it will return the global language. If you
409
499
  // pass in a language key, such as 'en', it will return the
410
500
  // definition for 'en', so long as 'en' has already been loaded using
411
- // moment.lang. If you pass in a moment or duration instance, it
412
- // will decide the language based on that, or default to the global
413
- // language.
414
- function getLangDefinition(m) {
415
- var langKey = (typeof m === 'string') && m ||
416
- m && m._lang ||
417
- null;
418
-
419
- return langKey ? (languages[langKey] || loadLang(langKey)) : moment;
501
+ // moment.lang.
502
+ function getLangDefinition(key) {
503
+ if (!key) {
504
+ return moment.fn._lang;
505
+ }
506
+ if (!languages[key] && hasModule) {
507
+ require('./lang/' + key);
508
+ }
509
+ return languages[key];
420
510
  }
421
511
 
422
512
 
@@ -457,7 +547,7 @@
457
547
  var i = 5;
458
548
 
459
549
  function replaceLongDateFormatTokens(input) {
460
- return m.lang().longDateFormat[input] || input;
550
+ return m.lang().longDateFormat(input) || input;
461
551
  }
462
552
 
463
553
  while (i-- && localFormattingTokens.test(format)) {
@@ -484,6 +574,8 @@
484
574
  return parseTokenThreeDigits;
485
575
  case 'YYYY':
486
576
  return parseTokenFourDigits;
577
+ case 'YYYYY':
578
+ return parseTokenSixDigits;
487
579
  case 'S':
488
580
  case 'SS':
489
581
  case 'SSS':
@@ -497,6 +589,8 @@
497
589
  case 'a':
498
590
  case 'A':
499
591
  return parseTokenWord;
592
+ case 'X':
593
+ return parseTokenTimestampMs;
500
594
  case 'Z':
501
595
  case 'ZZ':
502
596
  return parseTokenTimezone;
@@ -523,8 +617,9 @@
523
617
  }
524
618
 
525
619
  // function to convert string input to date
526
- function addTimeToArrayFromToken(token, input, datePartArray, config) {
527
- var a, b;
620
+ function addTimeToArrayFromToken(token, input, config) {
621
+ var a, b,
622
+ datePartArray = config._a;
528
623
 
529
624
  switch (token) {
530
625
  // MONTH
@@ -534,16 +629,12 @@
534
629
  break;
535
630
  case 'MMM' : // fall through to MMMM
536
631
  case 'MMMM' :
537
- for (a = 0; a < 12; a++) {
538
- if (getLangDefinition().monthsParse[a].test(input)) {
539
- datePartArray[1] = a;
540
- b = true;
541
- break;
542
- }
543
- }
632
+ a = getLangDefinition(config._l).monthsParse(input);
544
633
  // if we didn't find a month name, mark the date as invalid.
545
- if (!b) {
546
- datePartArray[8] = false;
634
+ if (a != null) {
635
+ datePartArray[1] = a;
636
+ } else {
637
+ config._isValid = false;
547
638
  }
548
639
  break;
549
640
  // DAY OF MONTH
@@ -557,15 +648,16 @@
557
648
  break;
558
649
  // YEAR
559
650
  case 'YY' :
560
- datePartArray[0] = ~~input + (~~input > 70 ? 1900 : 2000);
651
+ datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000);
561
652
  break;
562
653
  case 'YYYY' :
563
- datePartArray[0] = ~~Math.abs(input);
654
+ case 'YYYYY' :
655
+ datePartArray[0] = ~~input;
564
656
  break;
565
657
  // AM / PM
566
658
  case 'a' : // fall through to A
567
659
  case 'A' :
568
- config.isPm = ((input + '').toLowerCase() === 'pm');
660
+ config._isPm = ((input + '').toLowerCase() === 'pm');
569
661
  break;
570
662
  // 24 HOUR
571
663
  case 'H' : // fall through to hh
@@ -590,45 +682,76 @@
590
682
  case 'SSS' :
591
683
  datePartArray[6] = ~~ (('0.' + input) * 1000);
592
684
  break;
685
+ // UNIX TIMESTAMP WITH MS
686
+ case 'X':
687
+ config._d = new Date(parseFloat(input) * 1000);
688
+ break;
593
689
  // TIMEZONE
594
690
  case 'Z' : // fall through to ZZ
595
691
  case 'ZZ' :
596
- config.isUTC = true;
692
+ config._useUTC = true;
597
693
  a = (input + '').match(parseTimezoneChunker);
598
694
  if (a && a[1]) {
599
- config.tzh = ~~a[1];
695
+ config._tzh = ~~a[1];
600
696
  }
601
697
  if (a && a[2]) {
602
- config.tzm = ~~a[2];
698
+ config._tzm = ~~a[2];
603
699
  }
604
700
  // reverse offsets
605
701
  if (a && a[0] === '+') {
606
- config.tzh = -config.tzh;
607
- config.tzm = -config.tzm;
702
+ config._tzh = -config._tzh;
703
+ config._tzm = -config._tzm;
608
704
  }
609
705
  break;
610
706
  }
611
707
 
612
708
  // if the input is null, the date is not valid
613
709
  if (input == null) {
614
- datePartArray[8] = false;
710
+ config._isValid = false;
615
711
  }
616
712
  }
617
713
 
714
+ // convert an array to a date.
715
+ // the array should mirror the parameters below
716
+ // note: all values past the year are optional and will default to the lowest possible value.
717
+ // [year, month, day , hour, minute, second, millisecond]
718
+ function dateFromArray(config) {
719
+ var i, date, input = [];
720
+
721
+ if (config._d) {
722
+ return;
723
+ }
724
+
725
+ for (i = 0; i < 7; i++) {
726
+ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
727
+ }
728
+
729
+ // 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;
732
+
733
+ date = new Date(0);
734
+
735
+ if (config._useUTC) {
736
+ date.setUTCFullYear(input[0], input[1], input[2]);
737
+ date.setUTCHours(input[3], input[4], input[5], input[6]);
738
+ } else {
739
+ date.setFullYear(input[0], input[1], input[2]);
740
+ date.setHours(input[3], input[4], input[5], input[6]);
741
+ }
742
+
743
+ config._d = date;
744
+ }
745
+
618
746
  // date from string and format string
619
- function makeDateFromStringAndFormat(string, format) {
747
+ function makeDateFromStringAndFormat(config) {
620
748
  // This array is used to make a Date, either with `new Date` or `Date.UTC`
621
- // We store some additional data on the array for validation
622
- // datePartArray[7] is true if the Date was created with `Date.UTC` and false if created with `new Date`
623
- // datePartArray[8] is false if the Date is invalid, and undefined if the validity is unknown.
624
- var datePartArray = [0, 0, 1, 0, 0, 0, 0],
625
- config = {
626
- tzh : 0, // timezone hour offset
627
- tzm : 0 // timezone minute offset
628
- },
629
- tokens = format.match(formattingTokens),
749
+ var tokens = config._f.match(formattingTokens),
750
+ string = config._i,
630
751
  i, parsedInput;
631
752
 
753
+ config._a = [];
754
+
632
755
  for (i = 0; i < tokens.length; i++) {
633
756
  parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0];
634
757
  if (parsedInput) {
@@ -636,58 +759,91 @@
636
759
  }
637
760
  // don't parse if its not a known token
638
761
  if (formatTokenFunctions[tokens[i]]) {
639
- addTimeToArrayFromToken(tokens[i], parsedInput, datePartArray, config);
762
+ addTimeToArrayFromToken(tokens[i], parsedInput, config);
640
763
  }
641
764
  }
642
765
  // handle am pm
643
- if (config.isPm && datePartArray[3] < 12) {
644
- datePartArray[3] += 12;
766
+ if (config._isPm && config._a[3] < 12) {
767
+ config._a[3] += 12;
645
768
  }
646
769
  // if is 12 am, change hours to 0
647
- if (config.isPm === false && datePartArray[3] === 12) {
648
- datePartArray[3] = 0;
770
+ if (config._isPm === false && config._a[3] === 12) {
771
+ config._a[3] = 0;
649
772
  }
650
773
  // return
651
- return dateFromArray(datePartArray, config.isUTC, config.tzh, config.tzm);
774
+ dateFromArray(config);
652
775
  }
653
776
 
654
777
  // date from string and array of format strings
655
- function makeDateFromStringAndArray(string, formats) {
656
- var output,
657
- inputParts = string.match(parseMultipleFormatChunker) || [],
658
- formattedInputParts,
778
+ function makeDateFromStringAndArray(config) {
779
+ var tempConfig,
780
+ tempMoment,
781
+ bestMoment,
782
+
659
783
  scoreToBeat = 99,
660
784
  i,
661
785
  currentDate,
662
786
  currentScore;
663
- for (i = 0; i < formats.length; i++) {
664
- currentDate = makeDateFromStringAndFormat(string, formats[i]);
665
- formattedInputParts = formatMoment(new Moment(currentDate), formats[i]).match(parseMultipleFormatChunker) || [];
666
- currentScore = compareArrays(inputParts, formattedInputParts);
787
+
788
+ while (config._f.length) {
789
+ tempConfig = extend({}, config);
790
+ tempConfig._f = config._f.pop();
791
+ makeDateFromStringAndFormat(tempConfig);
792
+ tempMoment = new Moment(tempConfig);
793
+
794
+ if (tempMoment.isValid()) {
795
+ bestMoment = tempMoment;
796
+ break;
797
+ }
798
+
799
+ currentScore = compareArrays(tempConfig._a, tempMoment.toArray());
800
+
667
801
  if (currentScore < scoreToBeat) {
668
802
  scoreToBeat = currentScore;
669
- output = currentDate;
803
+ bestMoment = tempMoment;
670
804
  }
671
805
  }
672
- return output;
806
+
807
+ extend(config, bestMoment);
673
808
  }
674
809
 
675
810
  // date from iso format
676
- function makeDateFromString(string) {
677
- var format = 'YYYY-MM-DDT',
678
- i;
811
+ function makeDateFromString(config) {
812
+ var i,
813
+ string = config._i;
679
814
  if (isoRegex.exec(string)) {
815
+ config._f = 'YYYY-MM-DDT';
680
816
  for (i = 0; i < 4; i++) {
681
817
  if (isoTimes[i][1].exec(string)) {
682
- format += isoTimes[i][0];
818
+ config._f += isoTimes[i][0];
683
819
  break;
684
820
  }
685
821
  }
686
- return parseTokenTimezone.exec(string) ?
687
- makeDateFromStringAndFormat(string, format + ' Z') :
688
- makeDateFromStringAndFormat(string, format);
822
+ if (parseTokenTimezone.exec(string)) {
823
+ config._f += " Z";
824
+ }
825
+ makeDateFromStringAndFormat(config);
826
+ } else {
827
+ config._d = new Date(string);
828
+ }
829
+ }
830
+
831
+ function makeDateFromInput(config) {
832
+ var input = config._i,
833
+ matched = aspNetJsonRegex.exec(input);
834
+
835
+ if (input === undefined) {
836
+ config._d = new Date();
837
+ } else if (matched) {
838
+ config._d = new Date(+matched[1]);
839
+ } else if (typeof input === 'string') {
840
+ makeDateFromString(config);
841
+ } else if (isArray(input)) {
842
+ config._a = input.slice(0);
843
+ dateFromArray(config);
844
+ } else {
845
+ config._d = input instanceof Date ? new Date(+input) : new Date(input);
689
846
  }
690
- return new Date(string);
691
847
  }
692
848
 
693
849
 
@@ -698,10 +854,7 @@
698
854
 
699
855
  // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
700
856
  function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
701
- var rt = lang.relativeTime[string];
702
- return (typeof rt === 'function') ?
703
- rt(number || 1, !!withoutSuffix, string, isFuture) :
704
- rt.replace(/%d/i, number || 1);
857
+ return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
705
858
  }
706
859
 
707
860
  function relativeTime(milliseconds, withoutSuffix, lang) {
@@ -727,54 +880,85 @@
727
880
  }
728
881
 
729
882
 
883
+ /************************************
884
+ Week of Year
885
+ ************************************/
886
+
887
+
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))
895
+ function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
896
+ var end = firstDayOfWeekOfYear - firstDayOfWeek,
897
+ daysToDayOfWeek = firstDayOfWeekOfYear - mom.day();
898
+
899
+
900
+ if (daysToDayOfWeek > end) {
901
+ daysToDayOfWeek -= 7;
902
+ }
903
+
904
+ if (daysToDayOfWeek < end - 7) {
905
+ daysToDayOfWeek += 7;
906
+ }
907
+
908
+ return Math.ceil(moment(mom).add('d', daysToDayOfWeek).dayOfYear() / 7);
909
+ }
910
+
911
+
730
912
  /************************************
731
913
  Top Level Functions
732
914
  ************************************/
733
915
 
916
+ function makeMoment(config) {
917
+ var input = config._i,
918
+ format = config._f;
734
919
 
735
- moment = function (input, format) {
736
920
  if (input === null || input === '') {
737
921
  return null;
738
922
  }
739
- var date,
740
- matched;
741
- // parse Moment object
923
+
924
+ if (typeof input === 'string') {
925
+ config._i = input = getLangDefinition().preparse(input);
926
+ }
927
+
742
928
  if (moment.isMoment(input)) {
743
- return new Moment(new Date(+input._d), input._isUTC, input._lang);
744
- // parse string and format
929
+ config = extend({}, input);
930
+ config._d = new Date(+input._d);
745
931
  } else if (format) {
746
932
  if (isArray(format)) {
747
- date = makeDateFromStringAndArray(input, format);
933
+ makeDateFromStringAndArray(config);
748
934
  } else {
749
- date = makeDateFromStringAndFormat(input, format);
935
+ makeDateFromStringAndFormat(config);
750
936
  }
751
- // evaluate it as a JSON-encoded date
752
937
  } else {
753
- matched = aspNetJsonRegex.exec(input);
754
- date = input === undefined ? new Date() :
755
- matched ? new Date(+matched[1]) :
756
- input instanceof Date ? input :
757
- isArray(input) ? dateFromArray(input) :
758
- typeof input === 'string' ? makeDateFromString(input) :
759
- new Date(input);
938
+ makeDateFromInput(config);
760
939
  }
761
940
 
762
- return new Moment(date);
941
+ return new Moment(config);
942
+ }
943
+
944
+ moment = function (input, format, lang) {
945
+ return makeMoment({
946
+ _i : input,
947
+ _f : format,
948
+ _l : lang,
949
+ _isUTC : false
950
+ });
763
951
  };
764
952
 
765
953
  // creating with utc
766
- moment.utc = function (input, format) {
767
- if (isArray(input)) {
768
- return new Moment(dateFromArray(input, true), true);
769
- }
770
- // if we don't have a timezone, we need to add one to trigger parsing into utc
771
- if (typeof input === 'string' && !parseTokenTimezone.exec(input)) {
772
- input += ' +0000';
773
- if (format) {
774
- format += ' Z';
775
- }
776
- }
777
- return moment(input, format).utc();
954
+ moment.utc = function (input, format, lang) {
955
+ return makeMoment({
956
+ _useUTC : true,
957
+ _isUTC : true,
958
+ _l : lang,
959
+ _i : input,
960
+ _f : format
961
+ });
778
962
  };
779
963
 
780
964
  // creating with unix timestamp (in seconds)
@@ -799,20 +983,13 @@
799
983
 
800
984
  ret = new Duration(duration);
801
985
 
802
- if (isDuration) {
986
+ if (isDuration && input.hasOwnProperty('_lang')) {
803
987
  ret._lang = input._lang;
804
988
  }
805
989
 
806
990
  return ret;
807
991
  };
808
992
 
809
- // humanizeDuration
810
- // This method is deprecated in favor of the new Duration object. Please
811
- // see the moment.duration method.
812
- moment.humanizeDuration = function (num, type, withSuffix) {
813
- return moment.duration(num, type === true ? null : type).humanize(type === true ? true : withSuffix);
814
- };
815
-
816
993
  // version number
817
994
  moment.version = VERSION;
818
995
 
@@ -826,24 +1003,23 @@
826
1003
  var i;
827
1004
 
828
1005
  if (!key) {
829
- return currentLanguage;
1006
+ return moment.fn._lang._abbr;
830
1007
  }
831
- if (values || !languages[key]) {
1008
+ if (values) {
832
1009
  loadLang(key, values);
1010
+ } else if (!languages[key]) {
1011
+ getLangDefinition(key);
833
1012
  }
834
- if (languages[key]) {
835
- // deprecated, to get the language definition variables, use the
836
- // moment.fn.lang method or the getLangDefinition function.
837
- for (i = 0; i < langConfigProperties.length; i++) {
838
- moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]];
839
- }
840
- moment.monthsParse = languages[key].monthsParse;
841
- currentLanguage = key;
842
- }
1013
+ moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
843
1014
  };
844
1015
 
845
1016
  // returns language data
846
- moment.langData = getLangDefinition;
1017
+ moment.langData = function (key) {
1018
+ if (key && key._lang && key._lang._abbr) {
1019
+ key = key._lang._abbr;
1020
+ }
1021
+ return getLangDefinition(key);
1022
+ };
847
1023
 
848
1024
  // compare moment object
849
1025
  moment.isMoment = function (obj) {
@@ -855,59 +1031,6 @@
855
1031
  return obj instanceof Duration;
856
1032
  };
857
1033
 
858
- // Set default language, other languages will inherit from English.
859
- moment.lang('en', {
860
- months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
861
- monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
862
- weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
863
- weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
864
- weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
865
- longDateFormat : {
866
- LT : "h:mm A",
867
- L : "MM/DD/YYYY",
868
- LL : "MMMM D YYYY",
869
- LLL : "MMMM D YYYY LT",
870
- LLLL : "dddd, MMMM D YYYY LT"
871
- },
872
- meridiem : function (hours, minutes, isLower) {
873
- if (hours > 11) {
874
- return isLower ? 'pm' : 'PM';
875
- } else {
876
- return isLower ? 'am' : 'AM';
877
- }
878
- },
879
- calendar : {
880
- sameDay : '[Today at] LT',
881
- nextDay : '[Tomorrow at] LT',
882
- nextWeek : 'dddd [at] LT',
883
- lastDay : '[Yesterday at] LT',
884
- lastWeek : '[last] dddd [at] LT',
885
- sameElse : 'L'
886
- },
887
- relativeTime : {
888
- future : "in %s",
889
- past : "%s ago",
890
- s : "a few seconds",
891
- m : "a minute",
892
- mm : "%d minutes",
893
- h : "an hour",
894
- hh : "%d hours",
895
- d : "a day",
896
- dd : "%d days",
897
- M : "a month",
898
- MM : "%d months",
899
- y : "a year",
900
- yy : "%d years"
901
- },
902
- ordinal : function (number) {
903
- var b = number % 10;
904
- return (~~ (number % 100 / 10) === 1) ? 'th' :
905
- (b === 1) ? 'st' :
906
- (b === 2) ? 'nd' :
907
- (b === 3) ? 'rd' : 'th';
908
- }
909
- });
910
-
911
1034
 
912
1035
  /************************************
913
1036
  Moment Prototype
@@ -929,13 +1052,17 @@
929
1052
  },
930
1053
 
931
1054
  toString : function () {
932
- return this._d.toString();
1055
+ return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
933
1056
  },
934
1057
 
935
1058
  toDate : function () {
936
1059
  return this._d;
937
1060
  },
938
1061
 
1062
+ toJSON : function () {
1063
+ return moment.utc(this).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
1064
+ },
1065
+
939
1066
  toArray : function () {
940
1067
  var m = this;
941
1068
  return [
@@ -945,21 +1072,19 @@
945
1072
  m.hours(),
946
1073
  m.minutes(),
947
1074
  m.seconds(),
948
- m.milliseconds(),
949
- !!this._isUTC
1075
+ m.milliseconds()
950
1076
  ];
951
1077
  },
952
1078
 
953
1079
  isValid : function () {
954
- if (this._a) {
955
- // if the parser finds that the input is invalid, it sets
956
- // the eighth item in the input array to false.
957
- if (this._a[8] != null) {
958
- return !!this._a[8];
1080
+ if (this._isValid == null) {
1081
+ if (this._a) {
1082
+ this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray());
1083
+ } else {
1084
+ this._isValid = !isNaN(this._d.getTime());
959
1085
  }
960
- return !compareArrays(this._a, (this._a[7] ? moment.utc(this._a) : moment(this._a)).toArray());
961
1086
  }
962
- return !isNaN(this._d.getTime());
1087
+ return !!this._isValid;
963
1088
  },
964
1089
 
965
1090
  utc : function () {
@@ -973,46 +1098,65 @@
973
1098
  },
974
1099
 
975
1100
  format : function (inputString) {
976
- return formatMoment(this, inputString ? inputString : moment.defaultFormat);
1101
+ var output = formatMoment(this, inputString || moment.defaultFormat);
1102
+ return this.lang().postformat(output);
977
1103
  },
978
1104
 
979
1105
  add : function (input, val) {
980
- var dur = val ? moment.duration(+val, input) : moment.duration(input);
1106
+ var dur;
1107
+ // switch args to support add('s', 1) and add(1, 's')
1108
+ if (typeof input === 'string') {
1109
+ dur = moment.duration(+val, input);
1110
+ } else {
1111
+ dur = moment.duration(input, val);
1112
+ }
981
1113
  addOrSubtractDurationFromMoment(this, dur, 1);
982
1114
  return this;
983
1115
  },
984
1116
 
985
1117
  subtract : function (input, val) {
986
- var dur = val ? moment.duration(+val, input) : moment.duration(input);
1118
+ var dur;
1119
+ // switch args to support subtract('s', 1) and subtract(1, 's')
1120
+ if (typeof input === 'string') {
1121
+ dur = moment.duration(+val, input);
1122
+ } else {
1123
+ dur = moment.duration(input, val);
1124
+ }
987
1125
  addOrSubtractDurationFromMoment(this, dur, -1);
988
1126
  return this;
989
1127
  },
990
1128
 
991
- diff : function (input, val, asFloat) {
992
- var inputMoment = this._isUTC ? moment(input).utc() : moment(input).local(),
993
- zoneDiff = (this.zone() - inputMoment.zone()) * 6e4,
994
- diff = this._d - inputMoment._d - zoneDiff,
995
- year = this.year() - inputMoment.year(),
996
- month = this.month() - inputMoment.month(),
997
- date = this.date() - inputMoment.date(),
998
- output;
999
- if (val === 'months') {
1000
- output = year * 12 + month + date / 30;
1001
- } else if (val === 'years') {
1002
- output = year + (month + date / 30) / 12;
1129
+ diff : function (input, units, asFloat) {
1130
+ var that = this._isUTC ? moment(input).utc() : moment(input).local(),
1131
+ zoneDiff = (this.zone() - that.zone()) * 6e4,
1132
+ diff, output;
1133
+
1134
+ if (units) {
1135
+ // standardize on singular form
1136
+ units = units.replace(/s$/, '');
1137
+ }
1138
+
1139
+ if (units === 'year' || units === 'month') {
1140
+ diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
1141
+ output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
1142
+ output += ((this - moment(this).startOf('month')) - (that - moment(that).startOf('month'))) / diff;
1143
+ if (units === 'year') {
1144
+ output = output / 12;
1145
+ }
1003
1146
  } else {
1004
- output = val === 'seconds' ? diff / 1e3 : // 1000
1005
- val === 'minutes' ? diff / 6e4 : // 1000 * 60
1006
- val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60
1007
- val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24
1008
- val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
1147
+ diff = (this - that) - zoneDiff;
1148
+ output = units === 'second' ? diff / 1e3 : // 1000
1149
+ 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
1009
1153
  diff;
1010
1154
  }
1011
- return asFloat ? output : round(output);
1155
+ return asFloat ? output : absRound(output);
1012
1156
  },
1013
1157
 
1014
1158
  from : function (time, withoutSuffix) {
1015
- return moment.duration(this.diff(time)).lang(this._lang).humanize(!withoutSuffix);
1159
+ return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
1016
1160
  },
1017
1161
 
1018
1162
  fromNow : function (withoutSuffix) {
@@ -1020,16 +1164,14 @@
1020
1164
  },
1021
1165
 
1022
1166
  calendar : function () {
1023
- var diff = this.diff(moment().sod(), 'days', true),
1024
- calendar = this.lang().calendar,
1025
- allElse = calendar.sameElse,
1026
- format = diff < -6 ? allElse :
1027
- diff < -1 ? calendar.lastWeek :
1028
- diff < 0 ? calendar.lastDay :
1029
- diff < 1 ? calendar.sameDay :
1030
- diff < 2 ? calendar.nextDay :
1031
- diff < 7 ? calendar.nextWeek : allElse;
1032
- return this.format(typeof format === 'function' ? format.apply(this) : format);
1167
+ var diff = this.diff(moment().startOf('day'), 'days', true),
1168
+ 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';
1174
+ return this.format(this.lang().calendar(format, this));
1033
1175
  },
1034
1176
 
1035
1177
  isLeapYear : function () {
@@ -1048,16 +1190,18 @@
1048
1190
  this.add({ d : input - day });
1049
1191
  },
1050
1192
 
1051
- startOf: function (val) {
1193
+ startOf: function (units) {
1194
+ units = units.replace(/s$/, '');
1052
1195
  // the following switch intentionally omits break keywords
1053
1196
  // to utilize falling through the cases.
1054
- switch (val.replace(/s$/, '')) {
1197
+ switch (units) {
1055
1198
  case 'year':
1056
1199
  this.month(0);
1057
1200
  /* falls through */
1058
1201
  case 'month':
1059
1202
  this.date(1);
1060
1203
  /* falls through */
1204
+ case 'week':
1061
1205
  case 'day':
1062
1206
  this.hours(0);
1063
1207
  /* falls through */
@@ -1071,20 +1215,32 @@
1071
1215
  this.milliseconds(0);
1072
1216
  /* falls through */
1073
1217
  }
1218
+
1219
+ // weeks are a special case
1220
+ if (units === 'week') {
1221
+ this.day(0);
1222
+ }
1223
+
1074
1224
  return this;
1075
1225
  },
1076
1226
 
1077
- endOf: function (val) {
1078
- return this.startOf(val).add(val.replace(/s?$/, 's'), 1).subtract('ms', 1);
1227
+ endOf: function (units) {
1228
+ return this.startOf(units).add(units.replace(/s?$/, 's'), 1).subtract('ms', 1);
1079
1229
  },
1080
1230
 
1081
- sod: function () {
1082
- return this.clone().startOf('day');
1231
+ isAfter: function (input, units) {
1232
+ units = typeof units !== 'undefined' ? units : 'millisecond';
1233
+ return +this.clone().startOf(units) > +moment(input).startOf(units);
1083
1234
  },
1084
1235
 
1085
- eod: function () {
1086
- // end of day = start of day plus 1 day, minus 1 millisecond
1087
- return this.clone().endOf('day');
1236
+ isBefore: function (input, units) {
1237
+ units = typeof units !== 'undefined' ? units : 'millisecond';
1238
+ return +this.clone().startOf(units) < +moment(input).startOf(units);
1239
+ },
1240
+
1241
+ isSame: function (input, units) {
1242
+ units = typeof units !== 'undefined' ? units : 'millisecond';
1243
+ return +this.clone().startOf(units) === +moment(input).startOf(units);
1088
1244
  },
1089
1245
 
1090
1246
  zone : function () {
@@ -1095,14 +1251,29 @@
1095
1251
  return moment.utc([this.year(), this.month() + 1, 0]).date();
1096
1252
  },
1097
1253
 
1254
+ dayOfYear : function (input) {
1255
+ var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
1256
+ return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
1257
+ },
1258
+
1259
+ isoWeek : function (input) {
1260
+ var week = weekOfYear(this, 1, 4);
1261
+ return input == null ? week : this.add("d", (input - week) * 7);
1262
+ },
1263
+
1264
+ week : function (input) {
1265
+ var week = this.lang().week(this);
1266
+ return input == null ? week : this.add("d", (input - week) * 7);
1267
+ },
1268
+
1098
1269
  // If passed a language key, it will set the language for this
1099
1270
  // instance. Otherwise, it will return the language configuration
1100
1271
  // variables for this instance.
1101
- lang : function (lang) {
1102
- if (lang === undefined) {
1103
- return getLangDefinition(this);
1272
+ lang : function (key) {
1273
+ if (key === undefined) {
1274
+ return this._lang;
1104
1275
  } else {
1105
- this._lang = lang;
1276
+ this._lang = getLangDefinition(key);
1106
1277
  return this;
1107
1278
  }
1108
1279
  }
@@ -1110,7 +1281,7 @@
1110
1281
 
1111
1282
  // helper for adding shortcuts
1112
1283
  function makeGetterAndSetter(name, key) {
1113
- moment.fn[name] = function (input) {
1284
+ moment.fn[name] = moment.fn[name + 's'] = function (input) {
1114
1285
  var utc = this._isUTC ? 'UTC' : '';
1115
1286
  if (input != null) {
1116
1287
  this._d['set' + utc + key](input);
@@ -1123,12 +1294,16 @@
1123
1294
 
1124
1295
  // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
1125
1296
  for (i = 0; i < proxyGettersAndSetters.length; i ++) {
1126
- makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase(), proxyGettersAndSetters[i]);
1297
+ makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]);
1127
1298
  }
1128
1299
 
1129
1300
  // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
1130
1301
  makeGetterAndSetter('year', 'FullYear');
1131
1302
 
1303
+ // add plural methods
1304
+ moment.fn.days = moment.fn.day;
1305
+ moment.fn.weeks = moment.fn.week;
1306
+ moment.fn.isoWeeks = moment.fn.isoWeek;
1132
1307
 
1133
1308
  /************************************
1134
1309
  Duration Prototype
@@ -1148,19 +1323,13 @@
1148
1323
 
1149
1324
  humanize : function (withSuffix) {
1150
1325
  var difference = +this,
1151
- rel = this.lang().relativeTime,
1152
- output = relativeTime(difference, !withSuffix, this.lang()),
1153
- fromNow = difference <= 0 ? rel.past : rel.future;
1326
+ output = relativeTime(difference, !withSuffix, this.lang());
1154
1327
 
1155
1328
  if (withSuffix) {
1156
- if (typeof fromNow === 'function') {
1157
- output = fromNow(output);
1158
- } else {
1159
- output = fromNow.replace(/%s/i, output);
1160
- }
1329
+ output = this.lang().pastFuture(difference, output);
1161
1330
  }
1162
1331
 
1163
- return output;
1332
+ return this.lang().postformat(output);
1164
1333
  },
1165
1334
 
1166
1335
  lang : moment.fn.lang
@@ -1188,6 +1357,24 @@
1188
1357
  makeDurationAsGetter('Weeks', 6048e5);
1189
1358
 
1190
1359
 
1360
+ /************************************
1361
+ Default Lang
1362
+ ************************************/
1363
+
1364
+
1365
+ // Set default language, other languages will inherit from English.
1366
+ moment.lang('en', {
1367
+ ordinal : function (number) {
1368
+ var b = number % 10,
1369
+ output = (~~ (number % 100 / 10) === 1) ? 'th' :
1370
+ (b === 1) ? 'st' :
1371
+ (b === 2) ? 'nd' :
1372
+ (b === 3) ? 'rd' : 'th';
1373
+ return number + output;
1374
+ }
1375
+ });
1376
+
1377
+
1191
1378
  /************************************
1192
1379
  Exposing Moment
1193
1380
  ************************************/