ember-rails 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # ember-rails
2
+
3
+ ember-rails allows you to include [Ember.JS](http://emberjs.com/) into your Rails 3.1 application.
4
+
5
+ The gem will also pre-compile your handlebars templates when building your asset pipeline. It includes development and production copies of Ember.
6
+
7
+ ### Getting started
8
+
9
+ Add the gem to your application Gemfile:
10
+
11
+ gem "ember-rails"
12
+
13
+ Run `bundle install` and add the following line to
14
+ `app/assets/javascripts/application.js`:
15
+
16
+ //= require ember
17
+
18
+ If you want to include the new date-time helpers provided by ember, you
19
+ can use:
20
+
21
+ //= require ember-datetime
22
+
23
+ Ember-rails also provides a way to run Ember in development mode, you
24
+ can switch out your require statements to use the dev copies like so:
25
+
26
+ //= require ember-dev
27
+ //= require ember-datetime-dev
28
+
29
+ Ask Rails to serve HandlebarsJS and pre-compile templates to Ember
30
+ by putting each template in a dedicated ".js.hjs" file
31
+ (e.g. `app/assets/javascripts/templates/admin_panel.js.hjs`)
32
+ and including the assets in your layout:
33
+
34
+ <%= javascript_include_tag "templates/admin_panel" %>
35
+
36
+ Bundle all templates together thanks to Sprockets,
37
+ e.g create `app/assets/javascripts/templates/all.js` with:
38
+
39
+ //= require_tree .
40
+
41
+ Now a single line in the layout loads everything:
42
+
43
+ <%= javascript_include_tag "templates/all" %>
@@ -0,0 +1,12 @@
1
+ require 'sprockets/engines'
2
+ require 'ember-rails/hjs_template'
3
+
4
+ module EmberRails
5
+ class Engine < Rails::Engine
6
+ end
7
+
8
+ # Registers the HandlebarsJS template engine so that
9
+ # an asset file having the extension ".hjs" is processed
10
+ # by the asset pipeline and converted to javascript code.
11
+ Sprockets.register_engine '.hjs', HjsTemplate
12
+ end
@@ -0,0 +1,44 @@
1
+ require 'tilt/template'
2
+ require "execjs"
3
+
4
+ module EmberRails
5
+
6
+ # = Sprockets engine for HandlebarsJS templates
7
+ class HjsTemplate < Tilt::Template
8
+
9
+ def self.default_mime_type
10
+ 'application/javascript'
11
+ end
12
+
13
+ def initialize_engine
14
+ end
15
+
16
+ def prepare
17
+ end
18
+
19
+ # Generates Javascript code from a HandlebarsJS template.
20
+ # The Ember template name is derived from the lowercase logical asset path
21
+ # by replacing non-alphanum characheters by underscores.
22
+ def evaluate(scope, locals, &block)
23
+ "Ember.TEMPLATES[\"#{scope.logical_path}\"] = Handlebars.template(#{precompile(data)});\n"
24
+ end
25
+
26
+ private
27
+
28
+ def precompile(template)
29
+ runtime.call("EmberRails.precompile", template)
30
+ end
31
+
32
+ def runtime
33
+ Thread.current[:hjs_runtime] ||= ExecJS.compile(ember)
34
+ end
35
+
36
+ def ember
37
+ [ "ember-precompiler.js", "ember.min.js" ].map do |name|
38
+ File.read(File.expand_path(File.join(__FILE__, "..", "..", "..", "vendor/assets/javascripts/#{name}")))
39
+ end.join("\n")
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,3 @@
1
+ module EmberRails
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,1187 @@
1
+
2
+ (function(exports) {
3
+ // ==========================================================================
4
+ // Project: Ember - JavaScript Application Framework
5
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
6
+ // Portions ©2008-2011 Apple Inc. All rights reserved.
7
+ // License: Licensed under MIT license (see license.js)
8
+ // ==========================================================================
9
+ var get = Ember.get, set = Ember.set;
10
+
11
+ // simple copy op needed for just this code.
12
+ function copy(opts) {
13
+ var ret = {};
14
+ for(var key in opts) {
15
+ if (opts.hasOwnProperty(key)) ret[key] = opts[key];
16
+ }
17
+ return ret;
18
+ }
19
+
20
+ /**
21
+ Standard error thrown by `Ember.Scanner` when it runs out of bounds
22
+
23
+ @static
24
+ @constant
25
+ @type Error
26
+ */
27
+ Ember.SCANNER_OUT_OF_BOUNDS_ERROR = "Out of bounds.";
28
+
29
+ /**
30
+ Standard error thrown by `Ember.Scanner` when you pass a value not an integer.
31
+
32
+ @static
33
+ @constant
34
+ @type Error
35
+ */
36
+ Ember.SCANNER_INT_ERROR = "Not an int.";
37
+
38
+ /**
39
+ Standard error thrown by `Ember.Scanner` when it cannot find a string to skip.
40
+
41
+ @static
42
+ @constant
43
+ @type Error
44
+ */
45
+ Ember.SCANNER_SKIP_ERROR = "Did not find the string to skip.";
46
+
47
+ /**
48
+ Standard error thrown by `Ember.Scanner` when it can any kind a string in the
49
+ matching array.
50
+
51
+ @static
52
+ @constant
53
+ @type Error
54
+ */
55
+ Ember.SCANNER_SCAN_ARRAY_ERROR = "Did not find any string of the given array to scan.";
56
+
57
+ /**
58
+ Standard error thrown when trying to compare two dates in different
59
+ timezones.
60
+
61
+ @static
62
+ @constant
63
+ @type Error
64
+ */
65
+ Ember.DATETIME_COMPAREDATE_TIMEZONE_ERROR = "Can't compare the dates of two DateTimes that don't have the same timezone.";
66
+
67
+ /**
68
+ Standard ISO8601 date format
69
+
70
+ @static
71
+ @type String
72
+ @default '%Y-%m-%dT%H:%M:%S%Z'
73
+ @constant
74
+ */
75
+ Ember.DATETIME_ISO8601 = '%Y-%m-%dT%H:%M:%S%Z';
76
+
77
+
78
+ /**
79
+ @ignore
80
+ @private
81
+
82
+ A Scanner reads a string and interprets the characters into numbers. You
83
+ assign the scanner's string on initialization and the scanner progresses
84
+ through the characters of that string from beginning to end as you request
85
+ items.
86
+
87
+ Scanners are used by `DateTime` to convert strings into `DateTime` objects.
88
+
89
+ @extends Ember.Object
90
+ @since Ember 0.9
91
+ @author Martin Ottenwaelter
92
+ */
93
+ var Scanner = Ember.Object.extend({
94
+
95
+ /**
96
+ The string to scan. You usually pass it to the create method:
97
+
98
+ Scanner.create({string: 'May, 8th'});
99
+
100
+ @type String
101
+ */
102
+ string: null,
103
+
104
+ /**
105
+ The current scan location. It is incremented by the scanner as the
106
+ characters are processed.
107
+ The default is 0: the beginning of the string.
108
+
109
+ @type Integer
110
+ */
111
+ scanLocation: 0,
112
+
113
+ /**
114
+ Reads some characters from the string, and increments the scan location
115
+ accordingly.
116
+
117
+ @param {Integer} len The amount of characters to read
118
+ @throws {Ember.SCANNER_OUT_OF_BOUNDS_ERROR} If asked to read too many characters
119
+ @returns {String} The characters
120
+ */
121
+ scan: function(len) {
122
+ if (this.scanLocation + len > this.length) {
123
+ throw new Error(Ember.SCANNER_OUT_OF_BOUNDS_ERROR);
124
+ }
125
+ var str = this.string.substr(this.scanLocation, len);
126
+ this.scanLocation += len;
127
+ return str;
128
+ },
129
+
130
+ /**
131
+ Reads some characters from the string and interprets it as an integer.
132
+
133
+ @param {Integer} min_len The minimum amount of characters to read
134
+ @param {Integer} [max_len] The maximum amount of characters to read (defaults to the minimum)
135
+ @throws {Ember.SCANNER_INT_ERROR} If asked to read non numeric characters
136
+ @returns {Integer} The scanned integer
137
+ */
138
+ scanInt: function(min_len, max_len) {
139
+ if (max_len === undefined) max_len = min_len;
140
+ var str = this.scan(max_len);
141
+ var re = new RegExp("^\\d{" + min_len + "," + max_len + "}");
142
+ var match = str.match(re);
143
+ if (!match) throw new Error(Ember.SCANNER_INT_ERROR);
144
+ if (match[0].length < max_len) {
145
+ this.scanLocation += match[0].length - max_len;
146
+ }
147
+ return parseInt(match[0], 10);
148
+ },
149
+
150
+ /**
151
+ Attempts to skip a given string.
152
+
153
+ @param {String} str The string to skip
154
+ @throws {Ember.SCANNER_SKIP_ERROR} If the given string could not be scanned
155
+ @returns {Boolean} YES if the given string was successfully scanned, NO otherwise
156
+ */
157
+ skipString: function(str) {
158
+ if (this.scan(str.length) !== str) {
159
+ throw new Error(Ember.SCANNER_SKIP_ERROR);
160
+ }
161
+
162
+ return YES;
163
+ },
164
+
165
+ /**
166
+ Attempts to scan any string in a given array.
167
+
168
+ @param {Array} ary the array of strings to scan
169
+ @throws {Ember.SCANNER_SCAN_ARRAY_ERROR} If no string of the given array is found
170
+ @returns {Integer} The index of the scanned string of the given array
171
+ */
172
+ scanArray: function(ary) {
173
+ for (var i = 0, len = ary.length; i < len; i++) {
174
+ if (this.scan(ary[i].length) === ary[i]) {
175
+ return i;
176
+ }
177
+ this.scanLocation -= ary[i].length;
178
+ }
179
+ throw new Error(Ember.SCANNER_SCAN_ARRAY_ERROR);
180
+ }
181
+
182
+ });
183
+
184
+
185
+ /** @class
186
+
187
+ A class representation of a date and time. It's basically a wrapper around
188
+ the Date javascript object, KVO-friendly and with common date/time
189
+ manipulation methods.
190
+
191
+ This object differs from the standard JS Date object, however, in that it
192
+ supports time zones other than UTC and that local to the machine on which
193
+ it is running. Any time zone can be specified when creating an
194
+ `Ember.DateTime` object, e.g.
195
+
196
+ // Creates a DateTime representing 5am in Washington, DC and 10am in
197
+ // London
198
+ var d = Ember.DateTime.create({ hour: 5, timezone: 300 }); // -5 hours from UTC
199
+ var e = Ember.DateTime.create({ hour: 10, timezone: 0 }); // same time, specified in UTC
200
+
201
+ and it is true that `d.isEqual(e)`.
202
+
203
+ The time zone specified upon creation is permanent, and any calls to
204
+ `get()` on that instance will return values expressed in that time zone. So,
205
+
206
+ d.hour returns 5.
207
+ e.hour returns 10.
208
+
209
+ but
210
+
211
+ d.milliseconds === e.milliseconds
212
+
213
+ is true, since they are technically the same position in time.
214
+
215
+ @extends Ember.Object
216
+ @extends Ember.Freezable
217
+ @extends Ember.Copyable
218
+ @author Martin Ottenwaelter
219
+ @author Jonathan Lewis
220
+ @author Josh Holt
221
+ @since Ember 1.0
222
+ */
223
+ Ember.DateTime = Ember.Object.extend(Ember.Freezable, Ember.Copyable,
224
+ /** @scope Ember.DateTime.prototype */ {
225
+
226
+ /**
227
+ @private
228
+
229
+ Internal representation of a date: the number of milliseconds
230
+ since January, 1st 1970 00:00:00.0 UTC.
231
+
232
+ @property
233
+ @type {Integer}
234
+ */
235
+ _ms: 0,
236
+
237
+ /** @read-only
238
+ The offset, in minutes, between UTC and the object's timezone.
239
+ All calls to `get()` will use this time zone to translate date/time
240
+ values into the zone specified here.
241
+
242
+ @type Integer
243
+ */
244
+ timezone: 0,
245
+
246
+ /**
247
+ A `Ember.DateTime` instance is frozen by default for better performance.
248
+
249
+ @type Boolean
250
+ */
251
+ isFrozen: YES,
252
+
253
+ /**
254
+ Returns a new `Ember.DateTime` object where one or more of the elements have
255
+ been changed according to the options parameter. The time options (hour,
256
+ minute, sec, usec) reset cascadingly, so if only the hour is passed, then
257
+ minute, sec, and usec is set to 0. If the hour and minute is passed, then
258
+ sec and usec is set to 0.
259
+
260
+ If a time zone is passed in the options hash, all dates and times are
261
+ assumed to be local to it, and the returned `Ember.DateTime` instance has
262
+ that time zone. If none is passed, it defaults to `Ember.DateTime.timezone`.
263
+
264
+ Note that passing only a time zone does not affect the actual milliseconds
265
+ since Jan 1, 1970, only the time zone in which it is expressed when
266
+ displayed.
267
+
268
+ @see Ember.DateTime#create for the list of options you can pass
269
+ @returns {Ember.DateTime} copy of receiver
270
+ */
271
+ adjust: function(options, resetCascadingly) {
272
+ var timezone;
273
+
274
+ options = options ? copy(options) : {};
275
+ timezone = (options.timezone !== undefined) ? options.timezone : (this.timezone !== undefined) ? this.timezone : 0;
276
+
277
+ return this.constructor._adjust(options, this._ms, timezone, resetCascadingly)._createFromCurrentState();
278
+ },
279
+
280
+ /**
281
+ Returns a new `Ember.DateTime` object advanced according the the given
282
+ parameters. Don't use floating point values, it might give unpredicatble results.
283
+
284
+ @see Ember.DateTime#create for the list of options you can pass
285
+ @param {Hash} options the amount of date/time to advance the receiver
286
+ @returns {DateTime} copy of the receiver
287
+ */
288
+ advance: function(options) {
289
+ return this.constructor._advance(options, this._ms, this.timezone)._createFromCurrentState();
290
+ },
291
+
292
+ /**
293
+ Generic getter.
294
+
295
+ The properties you can get are:
296
+ - `year`
297
+ - `month` (January is 1, contrary to JavaScript Dates for which January is 0)
298
+ - `day`
299
+ - `dayOfWeek` (Sunday is 0)
300
+ - `hour`
301
+ - `minute`
302
+ - `second`
303
+ - `millisecond`
304
+ - `milliseconds`, the number of milliseconds since
305
+ January, 1st 1970 00:00:00.0 UTC
306
+ - `isLeapYear`, a boolean value indicating whether the receiver's year
307
+ is a leap year
308
+ - `daysInMonth`, the number of days of the receiver's current month
309
+ - `dayOfYear`, January 1st is 1, December 31th is 365 for a common year
310
+ - `week` or `week1`, the week number of the current year, starting with
311
+ the first Sunday as the first day of the first week (00..53)
312
+ - `week0`, the week number of the current year, starting with
313
+ the first Monday as the first day of the first week (00..53)
314
+ - `lastMonday`, `lastTuesday`, etc., `nextMonday`,
315
+ `nextTuesday`, etc., the date of the last or next weekday in
316
+ comparison to the receiver.
317
+
318
+ @param {String} key the property name to get
319
+ @return the value asked for
320
+ */
321
+ unknownProperty: function(key) {
322
+ return this.constructor._get(key, this._ms, this.timezone);
323
+ },
324
+
325
+ /**
326
+ Formats the receiver according to the given format string. Should behave
327
+ like the C strftime function.
328
+
329
+ The format parameter can contain the following characters:
330
+ - %a -- The abbreviated weekday name (``Sun'')
331
+ - %A -- The full weekday name (``Sunday'')
332
+ - %b -- The abbreviated month name (``Jan'')
333
+ - %B -- The full month name (``January'')
334
+ - %c -- The preferred local date and time representation
335
+ - %d -- Day of the month (01..31)
336
+ - %D -- Day of the month (0..31)
337
+ - %h -- Hour of the day, 24-hour clock (0..23)
338
+ - %H -- Hour of the day, 24-hour clock (00..23)
339
+ - %i -- Hour of the day, 12-hour clock (1..12)
340
+ - %I -- Hour of the day, 12-hour clock (01..12)
341
+ - %j -- Day of the year (001..366)
342
+ - %m -- Month of the year (01..12)
343
+ - %M -- Minute of the hour (00..59)
344
+ - %p -- Meridian indicator (``AM'' or ``PM'')
345
+ - %S -- Second of the minute (00..60)
346
+ - %s -- Milliseconds of the second (000..999)
347
+ - %U -- Week number of the current year,
348
+ starting with the first Sunday as the first
349
+ day of the first week (00..53)
350
+ - %W -- Week number of the current year,
351
+ starting with the first Monday as the first
352
+ day of the first week (00..53)
353
+ - %w -- Day of the week (Sunday is 0, 0..6)
354
+ - %x -- Preferred representation for the date alone, no time
355
+ - %X -- Preferred representation for the time alone, no date
356
+ - %y -- Year without a century (00..99)
357
+ - %Y -- Year with century
358
+ - %Z -- Time zone (ISO 8601 formatted)
359
+ - %% -- Literal ``%'' character
360
+
361
+ @param {String} format the format string
362
+ @return {String} the formatted string
363
+ */
364
+ toFormattedString: function(fmt) {
365
+ return this.constructor._toFormattedString(fmt, this._ms, this.timezone);
366
+ },
367
+
368
+ /**
369
+ Formats the receiver according ISO 8601 standard. It is equivalent to
370
+ calling toFormattedString with the `'%Y-%m-%dT%H:%M:%S%Z'` format string.
371
+
372
+ @return {String} the formatted string
373
+ */
374
+ toISO8601: function(){
375
+ return this.constructor._toFormattedString(Ember.DATETIME_ISO8601, this._ms, this.timezone);
376
+ },
377
+
378
+ /**
379
+ @private
380
+
381
+ Creates a string representation of the receiver.
382
+
383
+ (Debuggers often call the `toString` method. Because of the way
384
+ `Ember.DateTime` is designed, calling `Ember.DateTime._toFormattedString` would
385
+ have a nasty side effect. We shouldn't therefore call any of
386
+ `Ember.DateTime`'s methods from `toString`)
387
+
388
+ @returns {String}
389
+ */
390
+ toString: function() {
391
+ return "UTC: " +
392
+ new Date(this._ms).toUTCString() +
393
+ ", timezone: " +
394
+ this.timezone;
395
+ },
396
+
397
+ /**
398
+ Returns `YES` if the passed `Ember.DateTime` is equal to the receiver, ie: if their
399
+ number of milliseconds since January, 1st 1970 00:00:00.0 UTC are equal.
400
+ This is the preferred method for testing equality.
401
+
402
+ @see Ember.DateTime#compare
403
+ @param {Ember.DateTime} aDateTime the DateTime to compare to
404
+ @returns {Boolean}
405
+ */
406
+ isEqual: function(aDateTime) {
407
+ return this.constructor.compare(this, aDateTime) === 0;
408
+ },
409
+
410
+ /**
411
+ Returns a copy of the receiver. Because of the way `Ember.DateTime` is designed,
412
+ it just returns the receiver.
413
+
414
+ @returns {Ember.DateTime}
415
+ */
416
+ copy: function() {
417
+ return this;
418
+ },
419
+
420
+ /**
421
+ Returns a copy of the receiver with the timezone set to the passed
422
+ timezone. The returned value is equal to the receiver (ie `Ember.Compare`
423
+ returns 0), it is just the timezone representation that changes.
424
+
425
+ If you don't pass any argument, the target timezone is assumed to be 0,
426
+ ie UTC.
427
+
428
+ Note that this method does not change the underlying position in time,
429
+ but only the time zone in which it is displayed. In other words, the underlying
430
+ number of milliseconds since Jan 1, 1970 does not change.
431
+
432
+ @return {Ember.DateTime}
433
+ */
434
+ toTimezone: function(timezone) {
435
+ if (timezone === undefined) timezone = 0;
436
+ return this.advance({ timezone: timezone - this.timezone });
437
+ }
438
+
439
+ });
440
+
441
+ Ember.DateTime.reopenClass(Ember.Comparable,
442
+ /** @scope Ember.DateTime */ {
443
+
444
+ /**
445
+ The default format (ISO 8601) in which DateTimes are stored in a record.
446
+ Change this value if your backend sends and receives dates in another
447
+ format.
448
+
449
+ This value can also be customized on a per-attribute basis with the format
450
+ property. For example:
451
+
452
+ Ember.Record.attr(Ember.DateTime, { format: '%d/%m/%Y %H:%M:%S' })
453
+
454
+ @type String
455
+ @default Ember.DATETIME_ISO8601
456
+ */
457
+ recordFormat: Ember.DATETIME_ISO8601,
458
+
459
+ /**
460
+ @type Array
461
+ @default ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
462
+ */
463
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
464
+
465
+ /**
466
+ @private
467
+
468
+ The English day names used for the 'lastMonday', 'nextTuesday', etc., getters.
469
+
470
+ @type Array
471
+ */
472
+ _englishDayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
473
+
474
+ /**
475
+ @type Array
476
+ @default ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
477
+ */
478
+ abbreviatedDayNames: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
479
+
480
+ /**
481
+ @type Array
482
+ @default ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
483
+ */
484
+ monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
485
+
486
+ /**
487
+ @type Array
488
+ @default ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
489
+ */
490
+ abbreviatedMonthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
491
+
492
+ /**
493
+ @private
494
+
495
+ The unique internal `Date` object used to make computations. Better
496
+ performance is obtained by having only one Date object for the whole
497
+ application and manipulating it with `setTime()` and `getTime()`.
498
+
499
+ Note that since this is used for internal calculations across many
500
+ `Ember.DateTime` instances, it is not guaranteed to store the date/time that
501
+ any one `Ember.DateTime` instance represents. So it might be that
502
+
503
+ this._date.getTime() !== this._ms
504
+
505
+ Be sure to set it before using for internal calculations if necessary.
506
+
507
+ @type Date
508
+ */
509
+ _date: new Date(),
510
+
511
+ /**
512
+ @private
513
+
514
+ The offset, in minutes, between UTC and the currently manipulated
515
+ `Ember.DateTime` instance.
516
+
517
+ @type Integer
518
+ */
519
+ _tz: 0,
520
+
521
+ /**
522
+ The offset, in minutes, between UTC and the local system time. This
523
+ property is computed at loading time and should never be changed.
524
+
525
+ @type Integer
526
+ @default new Date().getTimezoneOffset()
527
+ @constant
528
+ */
529
+ timezone: new Date().getTimezoneOffset(),
530
+
531
+ /**
532
+ @private
533
+
534
+ A cache of `Ember.DateTime` instances. If you attempt to create a `Ember.DateTime`
535
+ instance that has already been created, then it will return the cached
536
+ value.
537
+
538
+ @type Array
539
+ */
540
+ _dt_cache: {},
541
+
542
+ /**
543
+ @private
544
+
545
+ The index of the lastest cached value. Used with `_DT_CACHE_MAX_LENGTH` to
546
+ limit the size of the cache.
547
+
548
+ @type Integer
549
+ */
550
+ _dt_cache_index: -1,
551
+
552
+ /**
553
+ @private
554
+
555
+ The maximum length of `_dt_cache`. If this limit is reached, then the cache
556
+ is overwritten, starting with the oldest element.
557
+
558
+ @type Integer
559
+ */
560
+ _DT_CACHE_MAX_LENGTH: 1000,
561
+
562
+ /**
563
+ @private
564
+
565
+ Both args are optional, but will only overwrite `_date` and `_tz` if
566
+ defined. This method does not affect the DateTime instance's actual time,
567
+ but simply initializes the one `_date` instance to a time relevant for a
568
+ calculation. (`this._date` is just a resource optimization)
569
+
570
+ This is mainly used as a way to store a recursion starting state during
571
+ internal calculations.
572
+
573
+ 'milliseconds' is time since Jan 1, 1970.
574
+ 'timezone' is the current time zone we want to be working in internally.
575
+
576
+ Returns a hash of the previous milliseconds and time zone in case they
577
+ are wanted for later restoration.
578
+ */
579
+ _setCalcState: function(ms, timezone) {
580
+ var previous = {
581
+ milliseconds: this._date.getTime(),
582
+ timezone: this._tz
583
+ };
584
+
585
+ if (ms !== undefined) this._date.setTime(ms);
586
+ if (timezone !== undefined) this._tz = timezone;
587
+
588
+ return previous;
589
+ },
590
+
591
+ /**
592
+ @private
593
+
594
+ By this time, any time zone setting on 'hash' will be ignored.
595
+ 'timezone' will be used, or the last this._tz.
596
+ */
597
+ _setCalcStateFromHash: function(hash, timezone) {
598
+ var tz = (timezone !== undefined) ? timezone : this._tz; // use the last-known time zone if necessary
599
+ var ms = this._toMilliseconds(hash, this._ms, tz); // convert the hash (local to specified time zone) to milliseconds (in UTC)
600
+ return this._setCalcState(ms, tz); // now call the one we really wanted
601
+ },
602
+
603
+ /**
604
+ @private
605
+ @see Ember.DateTime#unknownProperty
606
+ */
607
+ _get: function(key, start, timezone) {
608
+ var ms, tz, doy, m, y, firstDayOfWeek, dayOfWeek, dayOfYear, prefix, suffix;
609
+ var currentWeekday, targetWeekday;
610
+ var d = this._date;
611
+ var originalTime, v = null;
612
+
613
+ // Set up an absolute date/time using the given milliseconds since Jan 1, 1970.
614
+ // Only do it if we're given a time value, though, otherwise we want to use the
615
+ // last one we had because this `_get()` method is recursive.
616
+ //
617
+ // Note that because these private time calc methods are recursive, and because all DateTime instances
618
+ // share an internal this._date and `this._tz` state for doing calculations, methods
619
+ // that modify `this._date` or `this._tz` should restore the last state before exiting
620
+ // to avoid obscure calculation bugs. So we save the original state here, and restore
621
+ // it before returning at the end.
622
+ originalTime = this._setCalcState(start, timezone); // save so we can restore it to how it was before we got here
623
+
624
+ // Check this first because it is an absolute value -- no tweaks necessary when calling for milliseconds
625
+ if (key === 'milliseconds') {
626
+ v = d.getTime();
627
+ }
628
+ else if (key === 'timezone') {
629
+ v = this._tz;
630
+ }
631
+
632
+ // 'nextWeekday' or 'lastWeekday'.
633
+ // We want to do this calculation in local time, before shifting UTC below.
634
+ if (v === null) {
635
+ prefix = key.slice(0, 4);
636
+ suffix = key.slice(4);
637
+ if (prefix === 'last' || prefix === 'next') {
638
+ currentWeekday = this._get('dayOfWeek', start, timezone);
639
+ targetWeekday = this._englishDayNames.indexOf(suffix);
640
+ if (targetWeekday >= 0) {
641
+ var delta = targetWeekday - currentWeekday;
642
+ if (prefix === 'last' && delta >= 0) delta -= 7;
643
+ if (prefix === 'next' && delta < 0) delta += 7;
644
+ this._advance({ day: delta }, start, timezone);
645
+ v = this._createFromCurrentState();
646
+ }
647
+ }
648
+ }
649
+
650
+ if (v === null) {
651
+ // need to adjust for alternate display time zone.
652
+ // Before calculating, we need to get everything into a common time zone to
653
+ // negate the effects of local machine time (so we can use all the 'getUTC...() methods on Date).
654
+ if (timezone !== undefined) {
655
+ this._setCalcState(d.getTime() - (timezone * 60000), 0); // make this instance's time zone the new UTC temporarily
656
+ }
657
+
658
+ // simple keys
659
+ switch (key) {
660
+ case 'year':
661
+ v = d.getUTCFullYear(); //TODO: investigate why some libraries do getFullYear().toString() or getFullYear()+""
662
+ break;
663
+ case 'month':
664
+ v = d.getUTCMonth()+1; // January is 0 in JavaScript
665
+ break;
666
+ case 'day':
667
+ v = d.getUTCDate();
668
+ break;
669
+ case 'dayOfWeek':
670
+ v = d.getUTCDay();
671
+ break;
672
+ case 'hour':
673
+ v = d.getUTCHours();
674
+ break;
675
+ case 'minute':
676
+ v = d.getUTCMinutes();
677
+ break;
678
+ case 'second':
679
+ v = d.getUTCSeconds();
680
+ break;
681
+ case 'millisecond':
682
+ v = d.getUTCMilliseconds();
683
+ break;
684
+ }
685
+
686
+ // isLeapYear
687
+ if ((v === null) && (key === 'isLeapYear')) {
688
+ y = this._get('year');
689
+ v = (y%4 === 0 && y%100 !== 0) || y%400 === 0;
690
+ }
691
+
692
+ // daysInMonth
693
+ if ((v === null) && (key === 'daysInMonth')) {
694
+ switch (this._get('month')) {
695
+ case 4:
696
+ case 6:
697
+ case 9:
698
+ case 11:
699
+ v = 30;
700
+ break;
701
+ case 2:
702
+ v = this._get('isLeapYear') ? 29 : 28;
703
+ break;
704
+ default:
705
+ v = 31;
706
+ break;
707
+ }
708
+ }
709
+
710
+ // dayOfYear
711
+ if ((v === null) && (key === 'dayOfYear')) {
712
+ ms = d.getTime(); // save time
713
+ doy = this._get('day');
714
+ this._setCalcStateFromHash({ day: 1 });
715
+ for (m = this._get('month') - 1; m > 0; m--) {
716
+ this._setCalcStateFromHash({ month: m });
717
+ doy += this._get('daysInMonth');
718
+ }
719
+ d.setTime(ms); // restore time
720
+ v = doy;
721
+ }
722
+
723
+ // week, week0 or week1
724
+ if ((v === null) && (key.slice(0, 4) === 'week')) {
725
+ // firstDayOfWeek should be 0 (Sunday) or 1 (Monday)
726
+ firstDayOfWeek = key.length === 4 ? 1 : parseInt(key.slice('4'), 10);
727
+ dayOfWeek = this._get('dayOfWeek');
728
+ dayOfYear = this._get('dayOfYear') - 1;
729
+ if (firstDayOfWeek === 0) {
730
+ v = parseInt((dayOfYear - dayOfWeek + 7) / 7, 10);
731
+ }
732
+ else {
733
+ v = parseInt((dayOfYear - (dayOfWeek - 1 + 7) % 7 + 7) / 7, 10);
734
+ }
735
+ }
736
+ }
737
+
738
+ // restore the internal calculation state in case someone else was in the
739
+ // middle of a calculation (we might be recursing).
740
+ this._setCalcState(originalTime.milliseconds, originalTime.timezone);
741
+
742
+ return v;
743
+ },
744
+
745
+ /**
746
+ @private
747
+
748
+ Sets the internal calculation state to something specified.
749
+ */
750
+ _adjust: function(options, start, timezone, resetCascadingly) {
751
+ var opts = options ? copy(options) : {};
752
+ var ms = this._toMilliseconds(options, start, timezone, resetCascadingly);
753
+ this._setCalcState(ms, timezone);
754
+ return this; // for chaining
755
+ },
756
+
757
+ /**
758
+ @private
759
+ @see Ember.DateTime#advance
760
+ */
761
+ _advance: function(options, start, timezone) {
762
+ var opts = options ? copy(options) : {};
763
+ var tz;
764
+
765
+ for (var key in opts) {
766
+ opts[key] += this._get(key, start, timezone);
767
+ }
768
+
769
+ // The time zone can be advanced by a delta as well, so try to use the
770
+ // new value if there is one.
771
+ tz = (opts.timezone !== undefined) ? opts.timezone : timezone; // watch out for zero, which is acceptable as a time zone
772
+
773
+ return this._adjust(opts, start, tz, NO);
774
+ },
775
+
776
+ /*
777
+ @private
778
+
779
+ Converts a standard date/time options hash to an integer representing that position
780
+ in time relative to Jan 1, 1970
781
+ */
782
+ _toMilliseconds: function(options, start, timezone, resetCascadingly) {
783
+ var opts = options ? copy(options) : {};
784
+ var d = this._date;
785
+ var previousMilliseconds = d.getTime(); // rather than create a new Date object, we'll reuse the instance we have for calculations, then restore it
786
+ var ms, tz;
787
+
788
+ // Initialize our internal for-calculations Date object to our current date/time.
789
+ // Note that this object was created in the local machine time zone, so when we set
790
+ // its params later, it will be assuming these values to be in the same time zone as it is.
791
+ // It's ok for start to be null, in which case we'll just keep whatever we had in 'd' before.
792
+ if (!Ember.none(start)) {
793
+ d.setTime(start); // using milliseconds here specifies an absolute location in time, regardless of time zone, so that's nice
794
+ }
795
+
796
+ // We have to get all time expressions, both in 'options' (assume to be in time zone 'timezone')
797
+ // and in 'd', to the same time zone before we can any calculations correctly. So because the Date object provides
798
+ // a suite of UTC getters and setters, we'll temporarily redefine 'timezone' as our new
799
+ // 'UTC', so we don't have to worry about local machine time. We do this by subtracting
800
+ // milliseconds for the time zone offset. Then we'll do all our calculations, then convert
801
+ // it back to real UTC.
802
+
803
+ // (Zero time zone is considered a valid value.)
804
+ tz = (timezone !== undefined) ? timezone : (this.timezone !== undefined) ? this.timezone : 0;
805
+ d.setTime(d.getTime() - (tz * 60000)); // redefine 'UTC' to establish a new local absolute so we can use all the 'getUTC...()' Date methods
806
+
807
+ // the time options (hour, minute, sec, millisecond)
808
+ // reset cascadingly (see documentation)
809
+ if (resetCascadingly === undefined || resetCascadingly === YES) {
810
+ if ( !Ember.none(opts.hour) && Ember.none(opts.minute)) {
811
+ opts.minute = 0;
812
+ }
813
+ if (!(Ember.none(opts.hour) && Ember.none(opts.minute))
814
+ && Ember.none(opts.second)) {
815
+ opts.second = 0;
816
+ }
817
+ if (!(Ember.none(opts.hour) && Ember.none(opts.minute) && Ember.none(opts.second))
818
+ && Ember.none(opts.millisecond)) {
819
+ opts.millisecond = 0;
820
+ }
821
+ }
822
+
823
+ // Get the current values for any not provided in the options hash.
824
+ // Since everything is in 'UTC' now, use the UTC accessors. We do this because,
825
+ // according to javascript Date spec, you have to set year, month, and day together
826
+ // if you're setting any one of them. So we'll use the provided Date.UTC() method
827
+ // to get milliseconds, and we need to get any missing values first...
828
+ if (Ember.none(opts.year)) opts.year = d.getUTCFullYear();
829
+ if (Ember.none(opts.month)) opts.month = d.getUTCMonth() + 1; // January is 0 in JavaScript
830
+ if (Ember.none(opts.day)) opts.day = d.getUTCDate();
831
+ if (Ember.none(opts.hour)) opts.hour = d.getUTCHours();
832
+ if (Ember.none(opts.minute)) opts.minute = d.getUTCMinutes();
833
+ if (Ember.none(opts.second)) opts.second = d.getUTCSeconds();
834
+ if (Ember.none(opts.millisecond)) opts.millisecond = d.getUTCMilliseconds();
835
+
836
+ // Ask the JS Date to calculate milliseconds for us (still in redefined UTC). It
837
+ // is best to set them all together because, for example, a day value means different things
838
+ // to the JS Date object depending on which month or year it is. It can now handle that stuff
839
+ // internally as it's made to do.
840
+ ms = Date.UTC(opts.year, opts.month - 1, opts.day, opts.hour, opts.minute, opts.second, opts.millisecond);
841
+
842
+ // Now that we've done all our calculations in a common time zone, add back the offset
843
+ // to move back to real UTC.
844
+ d.setTime(ms + (tz * 60000));
845
+ ms = d.getTime(); // now get the corrected milliseconds value
846
+
847
+ // Restore what was there previously before leaving in case someone called this method
848
+ // in the middle of another calculation.
849
+ d.setTime(previousMilliseconds);
850
+
851
+ return ms;
852
+ },
853
+
854
+ /**
855
+ Returns a new `Ember.DateTime` object advanced according the the given parameters.
856
+ The parameters can be:
857
+
858
+ - none, to create a `Ember.DateTime` instance initialized to the current
859
+ date and time in the local timezone,
860
+ - a integer, the number of milliseconds since
861
+ January, 1st 1970 00:00:00.0 UTC
862
+ - a options hash that can contain any of the following properties: year,
863
+ month, day, hour, minute, second, millisecond, timezone
864
+
865
+ Note that if you attempt to create a `Ember.DateTime` instance that has already
866
+ been created, then, for performance reasons, a cached value may be
867
+ returned.
868
+
869
+ The timezone option is the offset, in minutes, between UTC and local time.
870
+ If you don't pass a timezone option, the date object is created in the
871
+ local timezone. If you want to create a UTC+2 (CEST) date, for example,
872
+ then you should pass a timezone of -120.
873
+
874
+ @param options one of the three kind of parameters descibed above
875
+ @returns {Ember.DateTime} the Ember.DateTime instance that corresponds to the
876
+ passed parameters, possibly fetched from cache
877
+ */
878
+ create: function() {
879
+ var arg = arguments.length === 0 ? {} : arguments[0];
880
+ var timezone;
881
+
882
+ // if simply milliseconds since Jan 1, 1970 are given, just use those
883
+ if (Ember.typeOf(arg) === 'number') {
884
+ arg = { milliseconds: arg };
885
+ }
886
+
887
+ // Default to local machine time zone if none is given
888
+ timezone = (arg.timezone !== undefined) ? arg.timezone : this.timezone;
889
+ if (timezone === undefined) timezone = 0;
890
+
891
+ // Desired case: create with milliseconds if we have them.
892
+ // If we don't, convert what we have to milliseconds and recurse.
893
+ if (!Ember.none(arg.milliseconds)) {
894
+
895
+ // quick implementation of a FIFO set for the cache
896
+ var key = 'nu' + arg.milliseconds + timezone, cache = this._dt_cache;
897
+ var ret = cache[key];
898
+ if (!ret) {
899
+ var previousKey, idx = this._dt_cache_index;
900
+ ret = cache[key] = this._super({ _ms: arg.milliseconds, timezone: timezone });
901
+ idx = this._dt_cache_index = (idx + 1) % this._DT_CACHE_MAX_LENGTH;
902
+ previousKey = cache[idx];
903
+ if (previousKey !== undefined && cache[previousKey]) delete cache[previousKey];
904
+ cache[idx] = key;
905
+ }
906
+ return ret;
907
+ }
908
+ // otherwise, convert what we have to milliseconds and try again
909
+ else {
910
+ var now = new Date();
911
+
912
+ return this.create({ // recursive call with new arguments
913
+ milliseconds: this._toMilliseconds(arg, now.getTime(), timezone, arg.resetCascadingly),
914
+ timezone: timezone
915
+ });
916
+ }
917
+ },
918
+
919
+ /**
920
+ @private
921
+
922
+ Calls the `create()` method with the current internal `_date` value.
923
+
924
+ @return {Ember.DateTime} the Ember.DateTime instance returned by create()
925
+ */
926
+ _createFromCurrentState: function() {
927
+ return this.create({
928
+ milliseconds: this._date.getTime(),
929
+ timezone: this._tz
930
+ });
931
+ },
932
+
933
+ /**
934
+ Returns a `Ember.DateTime` object created from a given string parsed with a given
935
+ format. Returns `null` if the parsing fails.
936
+
937
+ @see Ember.DateTime#toFormattedString for a description of the format parameter
938
+ @param {String} str the string to parse
939
+ @param {String} fmt the format to parse the string with
940
+ @returns {DateTime} the DateTime corresponding to the string parameter
941
+ */
942
+ parse: function(str, fmt) {
943
+ // Declared as an object not a literal since in some browsers the literal
944
+ // retains state across function calls
945
+ var re = new RegExp('(?:%([aAbBcdDhHiIjmMpsSUWwxXyYZ%])|(.))', "g");
946
+ var d, parts, opts = {}, check = {}, scanner = Scanner.create({string: str});
947
+
948
+ if (Ember.none(fmt)) fmt = Ember.DATETIME_ISO8601;
949
+
950
+ try {
951
+ while ((parts = re.exec(fmt)) !== null) {
952
+ switch(parts[1]) {
953
+ case 'a': check.dayOfWeek = scanner.scanArray(this.abbreviatedDayNames); break;
954
+ case 'A': check.dayOfWeek = scanner.scanArray(this.dayNames); break;
955
+ case 'b': opts.month = scanner.scanArray(this.abbreviatedMonthNames) + 1; break;
956
+ case 'B': opts.month = scanner.scanArray(this.monthNames) + 1; break;
957
+ case 'c': throw new Error("%c is not implemented");
958
+ case 'd':
959
+ case 'D': opts.day = scanner.scanInt(1, 2); break;
960
+ case 'h':
961
+ case 'H': opts.hour = scanner.scanInt(1, 2); break;
962
+ case 'i':
963
+ case 'I': opts.hour = scanner.scanInt(1, 2); break;
964
+ case 'j': throw new Error("%j is not implemented");
965
+ case 'm': opts.month = scanner.scanInt(1, 2); break;
966
+ case 'M': opts.minute = scanner.scanInt(1, 2); break;
967
+ case 'p': opts.meridian = scanner.scanArray(['AM', 'PM']); break;
968
+ case 'S': opts.second = scanner.scanInt(1, 2); break;
969
+ case 's': opts.millisecond = scanner.scanInt(1, 3); break;
970
+ case 'U': throw new Error("%U is not implemented");
971
+ case 'W': throw new Error("%W is not implemented");
972
+ case 'w': throw new Error("%w is not implemented");
973
+ case 'x': throw new Error("%x is not implemented");
974
+ case 'X': throw new Error("%X is not implemented");
975
+ case 'y': opts.year = scanner.scanInt(2); opts.year += (opts.year > 70 ? 1900 : 2000); break;
976
+ case 'Y': opts.year = scanner.scanInt(4); break;
977
+ case 'Z':
978
+ var modifier = scanner.scan(1);
979
+ if (modifier === 'Z') {
980
+ opts.timezone = 0;
981
+ } else if (modifier === '+' || modifier === '-' ) {
982
+ var h = scanner.scanInt(2);
983
+ if (scanner.scan(1) !== ':') scanner.scan(-1);
984
+ var m = scanner.scanInt(2);
985
+ opts.timezone = (modifier === '+' ? -1 : 1) * (h*60 + m);
986
+ }
987
+ break;
988
+ case '%': scanner.skipString('%'); break;
989
+ default: scanner.skipString(parts[0]); break;
990
+ }
991
+ }
992
+ } catch (e) {
993
+ Ember.Logger.log('Ember.DateTime.createFromString ' + e.toString());
994
+ return null;
995
+ }
996
+
997
+ if (!Ember.none(opts.meridian) && !Ember.none(opts.hour)) {
998
+ if (opts.meridian === 1) opts.hour = (opts.hour + 12) % 24;
999
+ delete opts.meridian;
1000
+ }
1001
+
1002
+ if (!Ember.none(opts.day) && (opts.day < 1 || opts.day > 31)){
1003
+ return null;
1004
+ }
1005
+
1006
+ // Check the month and day are valid and within bounds
1007
+ if (!Ember.none(opts.month)){
1008
+ if (opts.month < 1 || opts.month > 12){
1009
+ return null;
1010
+ }
1011
+ if (!Ember.none(opts.day)){
1012
+ if ( opts.month === 2 && opts.day > 29 ){
1013
+ return null;
1014
+ }
1015
+ if (jQuery.inArray(opts.month, [4,6,9,11]) > -1 && opts.day > 30) {
1016
+ return null;
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ d = this.create(opts);
1022
+
1023
+ if (!Ember.none(check.dayOfWeek) && get(d,'dayOfWeek') !== check.dayOfWeek) {
1024
+ return null;
1025
+ }
1026
+
1027
+ return d;
1028
+ },
1029
+
1030
+ /**
1031
+ @private
1032
+
1033
+ Converts the x parameter into a string padded with 0s so that the string’s
1034
+ length is at least equal to the len parameter.
1035
+
1036
+ @param {Object} x the object to convert to a string
1037
+ @param {Integer} the minimum length of the returned string
1038
+ @returns {String} the padded string
1039
+ */
1040
+ _pad: function(x, len) {
1041
+ var str = '' + x;
1042
+ if (len === undefined) len = 2;
1043
+ while (str.length < len) str = '0' + str;
1044
+ return str;
1045
+ },
1046
+
1047
+ /**
1048
+ @private
1049
+ @see Ember.DateTime#_toFormattedString
1050
+ */
1051
+ __toFormattedString: function(part, start, timezone) {
1052
+ var hour, offset;
1053
+
1054
+ // Note: all calls to _get() here should include only one
1055
+ // argument, since _get() is built for recursion and behaves differently
1056
+ // if arguments 2 and 3 are included.
1057
+ //
1058
+ // This method is simply a helper for this._toFormattedString() (one underscore);
1059
+ // this is only called from there, and _toFormattedString() has already
1060
+ // set up the appropriate internal date/time/timezone state for it.
1061
+
1062
+ switch(part[1]) {
1063
+ case 'a': return this.abbreviatedDayNames[this._get('dayOfWeek')];
1064
+ case 'A': return this.dayNames[this._get('dayOfWeek')];
1065
+ case 'b': return this.abbreviatedMonthNames[this._get('month')-1];
1066
+ case 'B': return this.monthNames[this._get('month')-1];
1067
+ case 'c': return this._date.toString();
1068
+ case 'd': return this._pad(this._get('day'));
1069
+ case 'D': return this._get('day');
1070
+ case 'h': return this._get('hour');
1071
+ case 'H': return this._pad(this._get('hour'));
1072
+ case 'i':
1073
+ hour = this._get('hour');
1074
+ return (hour === 12 || hour === 0) ? 12 : (hour + 12) % 12;
1075
+ case 'I':
1076
+ hour = this._get('hour');
1077
+ return this._pad((hour === 12 || hour === 0) ? 12 : (hour + 12) % 12);
1078
+ case 'j': return this._pad(this._get('dayOfYear'), 3);
1079
+ case 'm': return this._pad(this._get('month'));
1080
+ case 'M': return this._pad(this._get('minute'));
1081
+ case 'p': return this._get('hour') > 11 ? 'PM' : 'AM';
1082
+ case 'S': return this._pad(this._get('second'));
1083
+ case 's': return this._pad(this._get('millisecond'), 3);
1084
+ case 'u': return this._pad(this._get('utc')); //utc
1085
+ case 'U': return this._pad(this._get('week0'));
1086
+ case 'W': return this._pad(this._get('week1'));
1087
+ case 'w': return this._get('dayOfWeek');
1088
+ case 'x': return this._date.toDateString();
1089
+ case 'X': return this._date.toTimeString();
1090
+ case 'y': return this._pad(this._get('year') % 100);
1091
+ case 'Y': return this._get('year');
1092
+ case 'Z':
1093
+ offset = -1 * timezone;
1094
+ return (offset >= 0 ? '+' : '-')
1095
+ + this._pad(parseInt(Math.abs(offset)/60, 10))
1096
+ + ':'
1097
+ + this._pad(Math.abs(offset)%60);
1098
+ case '%': return '%';
1099
+ }
1100
+ },
1101
+
1102
+ /**
1103
+ @private
1104
+ @see Ember.DateTime#toFormattedString
1105
+ */
1106
+ _toFormattedString: function(format, start, timezone) {
1107
+ var that = this;
1108
+ var tz = (timezone !== undefined) ? timezone : (this.timezone !== undefined) ? this.timezone : 0;
1109
+
1110
+ // need to move into local time zone for these calculations
1111
+ this._setCalcState(start - (timezone * 60000), 0); // so simulate a shifted 'UTC' time
1112
+
1113
+ return format.replace(/\%([aAbBcdDhHiIjmMpsSUWwxXyYZ\%])/g, function() {
1114
+ var v = that.__toFormattedString.call(that, arguments, start, timezone);
1115
+ return v;
1116
+ });
1117
+ },
1118
+
1119
+ /**
1120
+ This will tell you which of the two passed `DateTime` is greater by
1121
+ comparing their number of milliseconds since
1122
+ January, 1st 1970 00:00:00.0 UTC.
1123
+
1124
+ @param {Ember.DateTime} a the first DateTime instance
1125
+ @param {Ember.DateTime} b the second DateTime instance
1126
+ @returns {Integer} -1 if a < b,
1127
+ +1 if a > b,
1128
+ 0 if a == b
1129
+ */
1130
+ compare: function(a, b) {
1131
+ var ma = get(a, 'milliseconds');
1132
+ var mb = get(b, 'milliseconds');
1133
+ return ma < mb ? -1 : ma === mb ? 0 : 1;
1134
+ },
1135
+
1136
+ /**
1137
+ This will tell you which of the two passed DateTime is greater
1138
+ by only comparing the date parts of the passed objects. Only dates
1139
+ with the same timezone can be compared.
1140
+
1141
+ @param {Ember.DateTime} a the first DateTime instance
1142
+ @param {Ember.DateTime} b the second DateTime instance
1143
+ @returns {Integer} -1 if a < b,
1144
+ +1 if a > b,
1145
+ 0 if a == b
1146
+ @throws {Ember.DATETIME_COMPAREDATE_TIMEZONE_ERROR} if the passed arguments
1147
+ don't have the same timezone
1148
+ */
1149
+ compareDate: function(a, b) {
1150
+ if (get(a, 'timezone') !== get(b,'timezone')) {
1151
+ throw new Error(Ember.DATETIME_COMPAREDATE_TIMEZONE_ERROR);
1152
+ }
1153
+
1154
+ var ma = get(a.adjust({hour: 0}), 'milliseconds');
1155
+ var mb = get(b.adjust({hour: 0}), 'milliseconds');
1156
+ return ma < mb ? -1 : ma === mb ? 0 : 1;
1157
+ }
1158
+
1159
+ });
1160
+
1161
+ /**
1162
+ Adds a transform to format the DateTime value to a String value according
1163
+ to the passed format string.
1164
+
1165
+ valueBinding: Ember.Binding.dateTime('%Y-%m-%d %H:%M:%S')
1166
+ .from('MyApp.myController.myDateTime');
1167
+
1168
+ @param {String} format format string
1169
+ @returns {Ember.Binding} this
1170
+ */
1171
+ Ember.Binding.dateTime = function(format) {
1172
+ return this.transform(function(value, binding) {
1173
+ return value ? value.toFormattedString(format) : null;
1174
+ });
1175
+ };
1176
+
1177
+
1178
+ })({});
1179
+
1180
+
1181
+ (function(exports) {
1182
+ // ==========================================================================
1183
+ // Project: Ember DateTime
1184
+ // Copyright: ©2010 Strobe Inc. and contributors
1185
+ // License: Licensed under MIT license (see license.js)
1186
+ // ==========================================================================
1187
+ })({});