sproutcore 1.0.1042 → 1.0.1043

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -152,11 +152,34 @@ SC.Scanner = SC.Object.extend(
152
152
  A class representation of a date and time. It's basically a wrapper around
153
153
  the Date javascript object, KVO friendly and with common date/time
154
154
  manipulation methods.
155
+
156
+ This object differs from the standard JS Date object, however, in that it
157
+ supports time zones other than UTC and that local to the machine on which
158
+ it is running. Any time zone can be specified when creating an SC.DateTime
159
+ object, e.g
160
+
161
+ // Creates a DateTime representing 5am in Washington, DC and 10am in London
162
+ var d = SC.DateTime.create({ hour: 5, timezone: 300 }); // -5 hours from UTC
163
+ var e = SC.DateTime.create({ hour: 10, timezone: 0 }); // same time, specified in UTC
164
+
165
+ and it is true that d.isEqual(e).
166
+
167
+ The time zone specified upon creation is permanent, and any calls to get() on that
168
+ instance will return values expressed in that time zone. So,
169
+
170
+ d.get('hour') returns 5.
171
+ e.get('hour') returns 10.
172
+
173
+ but
174
+
175
+ d.get('milliseconds') === e.get('milliseconds') is true, since they are technically the same position in time.
155
176
 
156
177
  @extends SC.Object
157
178
  @extends SC.Freezable
158
179
  @extends SC.Copyable
159
180
  @author Martin Ottenwaelter
181
+ @author Jonathan Lewis
182
+ @author Josh Holt
160
183
  @since SproutCore 1.0
161
184
  */
162
185
  SC.DateTime = SC.Object.extend(SC.Freezable, SC.Copyable,
@@ -171,8 +194,10 @@ SC.DateTime = SC.Object.extend(SC.Freezable, SC.Copyable,
171
194
  */
172
195
  _ms: 0,
173
196
 
174
- /**
197
+ /** @read-only
175
198
  The offset, in minutes, between UTC and the object's timezone.
199
+ All calls to get() will use this time zone to translate date/time
200
+ values into the zone specified here.
176
201
 
177
202
  @property
178
203
  @type {Integer}
@@ -196,11 +221,23 @@ SC.DateTime = SC.Object.extend(SC.Freezable, SC.Copyable,
196
221
 
197
222
  (Parts copied from the Ruby On Rails documentation)
198
223
 
224
+ If a time zone is passed in the options hash, all dates and times are assumed
225
+ to be local to it, and the returned DateTime instance has that time zone. If
226
+ none is passed, it defaults to SC.DateTime.timezone.
227
+
228
+ Note that passing only a time zone does not affect the actual milliseconds since
229
+ Jan 1, 1970, only the time zone in which it is expressed when displayed.
230
+
199
231
  @see SC.DateTime#create for the list of options you can pass
200
232
  @returns {DateTime} copy of receiver
201
233
  */
202
- adjust: function(options) {
203
- return this.constructor._adjust(options, this._ms, this.timezone)._createFromCurrentState();
234
+ adjust: function(options, resetCascadingly) {
235
+ var timezone;
236
+
237
+ options = options ? SC.clone(options) : {};
238
+ timezone = (options.timezone !== undefined) ? options.timezone : (this.timezone !== undefined) ? this.timezone : 0;
239
+
240
+ return this.constructor._adjust(options, this._ms, timezone, resetCascadingly)._createFromCurrentState();
204
241
  },
205
242
 
206
243
  /**
@@ -212,7 +249,7 @@ SC.DateTime = SC.Object.extend(SC.Freezable, SC.Copyable,
212
249
  @returns {DateTime} copy of the receiver
213
250
  */
214
251
  advance: function(options) {
215
- return this.constructor._advance(options, this._ms, this.timezone)._createFromCurrentState();
252
+ return this.constructor._advance(options, this._ms, this.timezone)._createFromCurrentState();
216
253
  },
217
254
 
218
255
  /**
@@ -271,7 +308,7 @@ SC.DateTime = SC.Object.extend(SC.Freezable, SC.Copyable,
271
308
  starting with the first Sunday as the first
272
309
  day of the first week (00..53)
273
310
  - %W - Week number of the current year,
274
- starting with the first Monday as the first
311
+ starting with the first Monday as the first
275
312
  day of the first week (00..53)
276
313
  - %w - Day of the week (Sunday is 0, 0..6)
277
314
  - %x - Preferred representation for the date alone, no time
@@ -344,12 +381,16 @@ SC.DateTime = SC.Object.extend(SC.Freezable, SC.Copyable,
344
381
 
345
382
  If you don't pass any argument, the target timezone is assumed to be 0,
346
383
  ie UTC.
384
+
385
+ Note that this method does not change the underlying position in time,
386
+ but only the time zone in which it is displayed. In other words, the underlying
387
+ number of milliseconds since Jan 1, 1970 does not change.
347
388
 
348
389
  @return {DateTime}
349
390
  */
350
391
  toTimezone: function(timezone) {
351
392
  if (timezone === undefined) timezone = 0;
352
- return this.advance({ timezone: timezone-this.timezone });
393
+ return this.advance({ timezone: timezone - this.timezone });
353
394
  }
354
395
 
355
396
  });
@@ -424,6 +465,14 @@ SC.DateTime.mixin(SC.Comparable,
424
465
  The unique internal Date object used to make computations. Better
425
466
  performance is obtained by having only one Date object for the whole
426
467
  application and manipulating it with setTime() and getTime().
468
+
469
+ Note that since this is used for internal calculations across many
470
+ DateTime instances, it is not guaranteed to store the date/time that
471
+ any one DateTime instance represents. So it might be that
472
+
473
+ this._date.getTime() !== this._ms
474
+
475
+ Be sure to set it before using for internal calculations if necessary.
427
476
 
428
477
  @property
429
478
  @type {Date}
@@ -477,106 +526,239 @@ SC.DateTime.mixin(SC.Comparable,
477
526
  _DT_CACHE_MAX_LENGTH: 1000,
478
527
 
479
528
  /** @private
480
- @see SC.DateTime#unknownProperty
529
+ Both args are optional, but will only overwrite _date and _tz if defined.
530
+ This method does not affect the DateTime instance's actual time, but simply
531
+ initializes the one _date instance to a time relevant for a calculation.
532
+ (this._date is just a resource optimization)
533
+
534
+ This is mainly used as a way to store a recursion starting state during
535
+ internal calculations.
536
+
537
+ 'milliseconds' is time since Jan 1, 1970.
538
+ 'timezone' is the current time zone we want to be working in internally.
539
+
540
+ Returns a hash of the previous milliseconds and time zone in case they
541
+ are wanted for later restoration.
481
542
  */
482
- _setState: function(start, timezone) {
483
- if (start !== undefined) this._date.setTime(start);
543
+ _setCalcState: function(ms, timezone) {
544
+ var previous = {
545
+ milliseconds: this._date.getTime(),
546
+ timezone: this._tz
547
+ };
548
+
549
+ if (ms !== undefined) this._date.setTime(ms);
484
550
  if (timezone !== undefined) this._tz = timezone;
551
+
552
+ return previous;
485
553
  },
486
-
554
+
555
+ /**
556
+ By this time, any time zone setting on 'hash' will be ignored.
557
+ 'timezone' will be used, or the last this._tz.
558
+ */
559
+ _setCalcStateFromHash: function(hash, timezone) {
560
+ var tz = (timezone !== undefined) ? timezone : this._tz; // use the last-known time zone if necessary
561
+ var ms = this._toMilliseconds(hash, this._ms, tz); // convert the hash (local to specified time zone) to milliseconds (in UTC)
562
+ return this._setCalcState(ms, tz); // now call the one we really wanted
563
+ },
564
+
487
565
  /** @private
488
566
  @see SC.DateTime#unknownProperty
489
567
  */
490
568
  _get: function(key, start, timezone) {
569
+ var ms, tz, doy, m, y, firstDayOfWeek, dayOfWeek, dayOfYear, prefix, suffix;
570
+ var currentWeekday, targetWeekday;
491
571
  var d = this._date;
492
- this._setState(start, timezone);
493
-
494
- // simple keys
495
- switch (key) {
496
- case 'year': return d.getFullYear(); //TODO: investigate why some libraries do getFullYear().toString() or getFullYear()+""
497
- case 'month': return d.getMonth()+1; // January is 0 in JavaScript
498
- case 'day': return d.getDate();
499
- case 'dayOfWeek': return d.getDay();
500
- case 'hour': return d.getHours();
501
- case 'minute': return d.getMinutes();
502
- case 'second': return d.getSeconds();
503
- case 'millisecond': return d.getMilliseconds();
504
- case 'milliseconds': return d.getTime() + this._tz*60000;
505
- case 'timezone': return this._tz;
572
+ var originalTime, v = null;
573
+
574
+ // Set up an absolute date/time using the given milliseconds since Jan 1, 1970.
575
+ // Only do it if we're given a time value, though, otherwise we want to use the
576
+ // last one we had because this _get() method is recursive.
577
+ //
578
+ // Note that because these private time calc methods are recursive, and because all DateTime instances
579
+ // share an internal this._date and this._tz state for doing calculations, methods
580
+ // that modify this._date or this._tz should restore the last state before exiting
581
+ // to avoid obscure calculation bugs. So we save the original state here, and restore
582
+ // it before returning at the end.
583
+ originalTime = this._setCalcState(start, timezone); // save so we can restore it to how it was before we got here
584
+
585
+ // Check this first because it is an absolute value -- no tweaks necessary when calling for milliseconds
586
+ if (key === 'milliseconds') {
587
+ v = d.getTime();
506
588
  }
507
-
508
- // isLeapYear
509
- if (key === 'isLeapYear') {
510
- var y = this._get('year');
511
- return (y%4 === 0 && y%100 !== 0) || y%400 === 0;
589
+ else if (key === 'timezone') {
590
+ v = this._tz;
512
591
  }
513
592
 
514
- // daysInMonth
515
- if (key === 'daysInMonth') {
516
- switch (this._get('month')) {
517
- case 4:
518
- case 6:
519
- case 9:
520
- case 11:
521
- return 30;
522
- case 2:
523
- return this._get('isLeapYear') ? 29 : 28;
524
- default:
525
- return 31;
593
+ // 'nextWeekday' or 'lastWeekday'.
594
+ // We want to do this calculation in local time, before shifting UTC below.
595
+ if (v === null) {
596
+ prefix = key.slice(0, 4);
597
+ suffix = key.slice(4);
598
+ if (prefix === 'last' || prefix === 'next') {
599
+ currentWeekday = d.getDay();
600
+ targetWeekday = this._englishDayNames.indexOf(suffix);
601
+ if (targetWeekday >= 0) {
602
+ var delta = targetWeekday - currentWeekday;
603
+ if (prefix === 'last' && delta >= 0) delta -= 7;
604
+ if (prefix === 'next' && delta < 0) delta += 7;
605
+ this._advance({ day: delta });
606
+ v = this._createFromCurrentState();
607
+ }
526
608
  }
527
609
  }
528
610
 
529
- // dayOfYear
530
- if (key === 'dayOfYear') {
531
- var ms = d.getTime(); // save time
532
- var doy = this._get('day');
533
- this._adjust({day: 1});
534
- for (var m = this._get('month') - 1; m > 0; m--) {
535
- doy += this._adjust({month: m})._get('daysInMonth');
611
+ if (v === null) {
612
+ // need to adjust for alternate display time zone.
613
+ // Before calculating, we need to get everything into a common time zone to
614
+ // negate the effects of local machine time (so we can use all the 'getUTC...() methods on Date).
615
+ if (timezone !== undefined) {
616
+ this._setCalcState(d.getTime() - (timezone * 60000), 0); // make this instance's time zone the new UTC temporarily
536
617
  }
537
- d.setTime(ms); // restore time
538
- return doy;
539
- }
540
618
 
541
- // week, week0 or week1
542
- if (key.slice(0, 4) === 'week') {
543
- // firstDayOfWeek should be 0 (Sunday) or 1 (Monday)
544
- var firstDayOfWeek = key.length === 4 ? 1 : parseInt(key.slice('4'), 10);
545
- var dayOfWeek = this._get('dayOfWeek');
546
- var dayOfYear = this._get('dayOfYear') - 1;
547
- if (firstDayOfWeek === 0) {
548
- return parseInt((dayOfYear - dayOfWeek + 7) / 7, 10);
549
- } else {
550
- return parseInt((dayOfYear - (dayOfWeek - 1 + 7) % 7 + 7) / 7, 10);
551
- }
552
- }
619
+ // simple keys
620
+ switch (key) {
621
+ case 'year':
622
+ v = d.getUTCFullYear(); //TODO: investigate why some libraries do getFullYear().toString() or getFullYear()+""
623
+ break;
624
+ case 'month':
625
+ v = d.getUTCMonth()+1; // January is 0 in JavaScript
626
+ break;
627
+ case 'day':
628
+ v = d.getUTCDate();
629
+ break;
630
+ case 'dayOfWeek':
631
+ v = d.getUTCDay();
632
+ break;
633
+ case 'hour':
634
+ v = d.getUTCHours();
635
+ break;
636
+ case 'minute':
637
+ v = d.getUTCMinutes();
638
+ break;
639
+ case 'second':
640
+ v = d.getUTCSeconds();
641
+ break;
642
+ case 'millisecond':
643
+ v = d.getUTCMilliseconds();
644
+ break;
645
+ }
646
+
647
+ // isLeapYear
648
+ if ((v === null) && (key === 'isLeapYear')) {
649
+ y = this._get('year');
650
+ v = (y%4 === 0 && y%100 !== 0) || y%400 === 0;
651
+ }
652
+
653
+ // daysInMonth
654
+ if ((v === null) && (key === 'daysInMonth')) {
655
+ switch (this._get('month')) {
656
+ case 4:
657
+ case 6:
658
+ case 9:
659
+ case 11:
660
+ v = 30;
661
+ break;
662
+ case 2:
663
+ v = this._get('isLeapYear') ? 29 : 28;
664
+ break;
665
+ default:
666
+ v = 31;
667
+ break;
668
+ }
669
+ }
553
670
 
554
- // nextWeekday or lastWeekday
555
- var prefix = key.slice(0, 4);
556
- var suffix = key.slice(4);
557
- if (prefix === 'last' || prefix === 'next') {
558
- var currentWeekday = d.getDay();
559
- var targetWeekday = this._englishDayNames.indexOf(suffix);
560
- if (targetWeekday >= 0) {
561
- var delta = targetWeekday - currentWeekday;
562
- if (prefix === 'last' && delta >= 0) delta -= 7;
563
- if (prefix === 'next' && delta < 0) delta += 7;
564
- this._advance({day: delta})._adjust({hour: 0});
565
- return this._createFromCurrentState();
671
+ // dayOfYear
672
+ if ((v === null) && (key === 'dayOfYear')) {
673
+ ms = d.getTime(); // save time
674
+ doy = this._get('day');
675
+ this._setCalcStateFromHash({ day: 1 });
676
+ for (m = this._get('month') - 1; m > 0; m--) {
677
+ this._setCalcStateFromHash({ month: m });
678
+ doy += this._get('daysInMonth');
679
+ }
680
+ d.setTime(ms); // restore time
681
+ v = doy;
682
+ }
683
+
684
+ // week, week0 or week1
685
+ if ((v === null) && (key.slice(0, 4) === 'week')) {
686
+ // firstDayOfWeek should be 0 (Sunday) or 1 (Monday)
687
+ firstDayOfWeek = key.length === 4 ? 1 : parseInt(key.slice('4'), 10);
688
+ dayOfWeek = this._get('dayOfWeek');
689
+ dayOfYear = this._get('dayOfYear') - 1;
690
+ if (firstDayOfWeek === 0) {
691
+ v = parseInt((dayOfYear - dayOfWeek + 7) / 7, 10);
692
+ }
693
+ else {
694
+ v = parseInt((dayOfYear - (dayOfWeek - 1 + 7) % 7 + 7) / 7, 10);
695
+ }
566
696
  }
567
697
  }
568
698
 
569
- return null;
699
+ // restore the internal calculation state in case someone else was in the
700
+ // middle of a calculation (we might be recursing).
701
+ this._setCalcState(originalTime.milliseconds, originalTime.timezone);
702
+
703
+ return v;
704
+ },
705
+
706
+ /**
707
+ Sets the internal calculation state to something specified.
708
+ */
709
+ _adjust: function(options, start, timezone, resetCascadingly) {
710
+ var opts = options ? SC.clone(options) : {};
711
+ var ms = this._toMilliseconds(options, start, timezone, resetCascadingly);
712
+ this._setCalcState(ms, timezone);
713
+ return this; // for chaining
570
714
  },
571
715
 
572
716
  /** @private
573
- @see SC.DateTime#adjust
717
+ @see SC.DateTime#advance
574
718
  */
575
- _adjust: function(options, start, timezone, resetCascadingly) {
719
+ _advance: function(options, start, timezone) {
576
720
  var opts = options ? SC.clone(options) : {};
721
+ var tz;
722
+
723
+ for (var key in opts) {
724
+ opts[key] += this._get(key, start, timezone);
725
+ }
577
726
 
727
+ // The time zone can be advanced by a delta as well, so try to use the
728
+ // new value if there is one.
729
+ tz = (opts.timezone !== undefined) ? opts.timezone : timezone; // watch out for zero, which is acceptable as a time zone
730
+
731
+ return this._adjust(opts, start, tz, NO);
732
+ },
733
+
734
+ /* @private
735
+ Converts a standard date/time options hash to an integer representing that position
736
+ in time relative to Jan 1, 1970
737
+ */
738
+ _toMilliseconds: function(options, start, timezone, resetCascadingly) {
739
+ var opts = options ? SC.clone(options) : {};
578
740
  var d = this._date;
579
- this._setState(start, timezone);
741
+ var previousMilliseconds = d.getTime(); // rather than create a new Date object, we'll reuse the instance we have for calculations, then restore it
742
+ var ms, tz;
743
+
744
+ // Initialize our internal for-calculations Date object to our current date/time.
745
+ // Note that this object was created in the local machine time zone, so when we set
746
+ // its params later, it will be assuming these values to be in the same time zone as it is.
747
+ // It's ok for start to be null, in which case we'll just keep whatever we had in 'd' before.
748
+ if (!SC.none(start)) {
749
+ d.setTime(start); // using milliseconds here specifies an absolute location in time, regardless of time zone, so that's nice
750
+ }
751
+
752
+ // We have to get all time expressions, both in 'options' (assume to be in time zone 'timezone')
753
+ // and in 'd', to the same time zone before we can any calculations correctly. So because the Date object provides
754
+ // a suite of UTC getters and setters, we'll temporarily redefine 'timezone' as our new
755
+ // 'UTC', so we don't have to worry about local machine time. We do this by subtracting
756
+ // milliseconds for the time zone offset. Then we'll do all our calculations, then convert
757
+ // it back to real UTC.
758
+
759
+ // (Zero time zone is considered a valid value.)
760
+ tz = (timezone !== undefined) ? timezone : (this.timezone !== undefined) ? this.timezone : 0;
761
+ d.setTime(d.getTime() - (tz * 60000)); // redefine 'UTC' to establish a new local absolute so we can use all the 'getUTC...()' Date methods
580
762
 
581
763
  // the time options (hour, minute, sec, millisecond)
582
764
  // reset cascadingly (see documentation)
@@ -593,33 +775,36 @@ SC.DateTime.mixin(SC.Comparable,
593
775
  opts.millisecond = 0;
594
776
  }
595
777
  }
596
-
597
- if (!SC.none(opts.year)) d.setFullYear(opts.year);
598
- if (!SC.none(opts.month)) d.setMonth(opts.month-1); // January is 0 in JavaScript
599
- if (!SC.none(opts.day)) d.setDate(opts.day);
600
- if (!SC.none(opts.hour)) d.setHours(opts.hour);
601
- if (!SC.none(opts.minute)) d.setMinutes(opts.minute);
602
- if (!SC.none(opts.second)) d.setSeconds(opts.second);
603
- if (!SC.none(opts.millisecond)) d.setMilliseconds(opts.millisecond);
604
- if (!SC.none(opts.timezone)) this._tz = opts.timezone;
605
778
 
606
- return this;
607
- },
608
-
609
- /** @private
610
- @see SC.DateTime#advance
611
- */
612
- _advance: function(options, start, timezone) {
613
- var opts = options ? SC.clone(options) : {};
614
- this._setState(start, timezone);
615
-
616
- if (!SC.none(opts.timezone)) {
617
- if (SC.none(opts.minute)) opts.minute = 0;
618
- opts.minute -= opts.timezone;
619
- }
620
- for (var key in opts) opts[key] += this._get(key);
779
+ // Get the current values for any not provided in the options hash.
780
+ // Since everything is in 'UTC' now, use the UTC accessors. We do this because,
781
+ // according to javascript Date spec, you have to set year, month, and day together
782
+ // if you're setting any one of them. So we'll use the provided Date.UTC() method
783
+ // to get milliseconds, and we need to get any missing values first...
784
+ if (SC.none(opts.year)) opts.year = d.getUTCFullYear();
785
+ if (SC.none(opts.month)) opts.month = d.getUTCMonth() + 1; // January is 0 in JavaScript
786
+ if (SC.none(opts.day)) opts.day = d.getUTCDate();
787
+ if (SC.none(opts.hour)) opts.hour = d.getUTCHours();
788
+ if (SC.none(opts.minute)) opts.minute = d.getUTCMinutes();
789
+ if (SC.none(opts.second)) opts.second = d.getUTCSeconds();
790
+ if (SC.none(opts.millisecond)) opts.millisecond = d.getUTCMilliseconds();
791
+
792
+ // Ask the JS Date to calculate milliseconds for us (still in redefined UTC). It
793
+ // is best to set them all together because, for example, a day value means different things
794
+ // to the JS Date object depending on which month or year it is. It can now handle that stuff
795
+ // internally as it's made to do.
796
+ ms = Date.UTC(opts.year, opts.month - 1, opts.day, opts.hour, opts.minute, opts.second, opts.millisecond);
797
+
798
+ // Now that we've done all our calculations in a common time zone, add back the offset
799
+ // to move back to real UTC.
800
+ d.setTime(ms + (tz * 60000));
801
+ ms = d.getTime(); // now get the corrected milliseconds value
621
802
 
622
- return this._adjust(opts, start, timezone, NO);
803
+ // Restore what was there previously before leaving in case someone called this method
804
+ // in the middle of another calculation.
805
+ d.setTime(previousMilliseconds);
806
+
807
+ return ms;
623
808
  },
624
809
 
625
810
  /**
@@ -647,31 +832,41 @@ SC.DateTime.mixin(SC.Comparable,
647
832
  */
648
833
  create: function() {
649
834
  var arg = arguments.length === 0 ? {} : arguments[0];
835
+ var timezone;
650
836
 
837
+ // if simply milliseconds since Jan 1, 1970 are given, just use those
651
838
  if (SC.typeOf(arg) === SC.T_NUMBER) {
652
- arg = { milliseconds: arg, timezone: 0 };
653
- } else if (SC.none(arg.timezone)) {
654
- arg.timezone = this.timezone;
839
+ arg = { milliseconds: arg };
655
840
  }
656
-
841
+
842
+ // Default to local machine time zone if none is given
843
+ timezone = (arg.timezone !== undefined) ? arg.timezone : this.timezone;
844
+ if (timezone === undefined) timezone = 0;
845
+
846
+ // Desired case: create with milliseconds if we have them.
847
+ // If we don't, convert what we have to milliseconds and recurse.
657
848
  if (!SC.none(arg.milliseconds)) {
849
+
658
850
  // quick implementation of a FIFO set for the cache
659
- var key = 'nu'+arg.milliseconds+arg.timezone, cache = this._dt_cache;
851
+ var key = 'nu' + arg.milliseconds + timezone, cache = this._dt_cache;
660
852
  var ret = cache[key];
661
853
  if (!ret) {
662
854
  var previousKey, idx = this._dt_cache_index, C = this;
663
- ret = cache[key] = new C([{_ms: arg.milliseconds, timezone: arg.timezone}]);
855
+ ret = cache[key] = new C([{ _ms: arg.milliseconds, timezone: timezone }]);
664
856
  idx = this._dt_cache_index = (idx + 1) % this._DT_CACHE_MAX_LENGTH;
665
857
  previousKey = cache[idx];
666
858
  if (previousKey !== undefined && cache[previousKey]) delete cache[previousKey];
667
859
  cache[idx] = key;
668
860
  }
669
861
  return ret;
670
- } else {
862
+ }
863
+ // otherwise, convert what we have to milliseconds and try again
864
+ else {
671
865
  var now = new Date();
672
- return this.create({
673
- milliseconds: this._adjust(arg, now.getTime())._date.getTime(),
674
- timezone: arg.timezone
866
+
867
+ return this.create({ // recursive call with new arguments
868
+ milliseconds: this._toMilliseconds(arg, now.getTime(), timezone, arg.resetCascadingly),
869
+ timezone: timezone
675
870
  });
676
871
  }
677
872
 
@@ -775,9 +970,19 @@ SC.DateTime.mixin(SC.Comparable,
775
970
  },
776
971
 
777
972
  /** @private
778
- @see SC.DateTime#toFormattedString
973
+ @see SC.DateTime#_toFormattedString
779
974
  */
780
- __toFormattedString: function(part) {
975
+ __toFormattedString: function(part, start, timezone) {
976
+ var hour, offset;
977
+
978
+ // Note: all calls to _get() here should include only one
979
+ // argument, since _get() is built for recursion and behaves differently
980
+ // if arguments 2 and 3 are included.
981
+ //
982
+ // This method is simply a helper for this._toFormattedString() (one underscore);
983
+ // this is only called from there, and _toFormattedString() has already
984
+ // set up the appropriate internal date/time/timezone state for it.
985
+
781
986
  switch(part[1]) {
782
987
  case 'a': return this.abbreviatedDayNames[this._get('dayOfWeek')];
783
988
  case 'A': return this.dayNames[this._get('dayOfWeek')];
@@ -788,10 +993,10 @@ SC.DateTime.mixin(SC.Comparable,
788
993
  case 'h': return this._get('hour');
789
994
  case 'H': return this._pad(this._get('hour'));
790
995
  case 'i':
791
- var hour = this._get('hour');
996
+ hour = this._get('hour');
792
997
  return (hour === 12 || hour === 0) ? 12 : (hour + 12) % 12;
793
998
  case 'I':
794
- var hour = this._get('hour');
999
+ hour = this._get('hour');
795
1000
  return this._pad((hour === 12 || hour === 0) ? 12 : (hour + 12) % 12);
796
1001
  case 'j': return this._pad(this._get('dayOfYear'), 3);
797
1002
  case 'm': return this._pad(this._get('month'));
@@ -807,7 +1012,7 @@ SC.DateTime.mixin(SC.Comparable,
807
1012
  case 'y': return this._pad(this._get('year') % 100);
808
1013
  case 'Y': return this._get('year');
809
1014
  case 'Z':
810
- var offset = -1 * this._tz;
1015
+ offset = -1 * timezone;
811
1016
  return (offset >= 0 ? '+' : '-')
812
1017
  + this._pad(parseInt(Math.abs(offset)/60, 10))
813
1018
  + ':'
@@ -820,11 +1025,15 @@ SC.DateTime.mixin(SC.Comparable,
820
1025
  @see SC.DateTime#toFormattedString
821
1026
  */
822
1027
  _toFormattedString: function(format, start, timezone) {
823
- this._setState(start, timezone);
824
-
825
1028
  var that = this;
1029
+ var tz = (timezone !== undefined) ? timezone : (this.timezone !== undefined) ? this.timezone : 0;
1030
+
1031
+ // need to move into local time zone for these calculations
1032
+ this._setCalcState(start - (timezone * 60000), 0); // so simulate a shifted 'UTC' time
1033
+
826
1034
  return format.replace(/\%([aAbBcdHIjmMpSUWwxXyYZ\%])/g, function() {
827
- return that.__toFormattedString.call(that, arguments);
1035
+ var v = that.__toFormattedString.call(that, arguments, start, timezone);
1036
+ return v;
828
1037
  });
829
1038
  },
830
1039