Skip to content

Commit 4f8632e

Browse files
committed
Fixed issues with UTC times being reset and timezone abbreviations.
(#582)
1 parent 4726560 commit 4f8632e

File tree

5 files changed

+55
-28
lines changed

5 files changed

+55
-28
lines changed

lib/date.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var LOCALE_ARRAY_FIELDS = [
1818
];
1919

2020
// Regex for stripping Timezone Abbreviations
21-
var TIMEZONE_ABBREVIATION_REG = /(\w{3})[()\s\d]*$/;
21+
var TIMEZONE_ABBREVIATION_REG = /\(([-+]\d{2,4}|\w{3,5})\)$/;
2222

2323
// One minute in milliseconds
2424
var MINUTES = 60 * 1000;
@@ -903,8 +903,16 @@ function getTimeDistanceForUnit(d1, d2, unit) {
903903
num = trunc(num / unit.multiplier);
904904
}
905905
// For higher order with potential ambiguity, use the numeric calculation
906-
// as a starting point, then iterate until we pass the target date.
906+
// as a starting point, then iterate until we pass the target date. Decrement
907+
// starting point by 1 to prevent overshooting the date due to inconsistencies
908+
// in ambiguous units numerically. For example, calculating the number of days
909+
// from the beginning of the year to August 5th at 11:59:59 by doing a simple
910+
// d2 - d1 will produce different results depending on whether or not a
911+
// timezone shift was encountered due to DST, however that should not have an
912+
// effect on our calculation here, so subtract by 1 to ensure that the
913+
// starting point has not already overshot our target date.
907914
if (unit.ambiguous) {
915+
num -= 1;
908916
d1 = cloneDate(d1);
909917
if (num) {
910918
advanceDate(d1, unit.name, num);
@@ -1499,11 +1507,6 @@ function getExtendedDate(contextDate, d, opt, forceClone) {
14991507
} else if (relative) {
15001508
updateDate(date, set, false, 1);
15011509
} else {
1502-
if (_utc(date)) {
1503-
// UTC times can traverse into other days or even months,
1504-
// so preemtively reset the time here to prevent this.
1505-
resetTime(date);
1506-
}
15071510
updateDate(date, set, true, 0, getOwn(options, 'prefer'), weekdayDir);
15081511
}
15091512
fireCallbacks();

test/suite/helpers/common.js

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ testGetPrivateProp = function(obj, name) {
4848
return obj[testGetPrivatePropKey(name)];
4949
}
5050

51+
testGetStringified = function(obj) {
52+
return typeof obj === 'object' && typeof JSON !== 'undefined' ? JSON.stringify(obj) : obj;
53+
}
54+
5155
testClone = function (obj) {
5256
if (obj == null || typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
5357
return obj;

test/suite/helpers/date.js

+27-15
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ getRelativeDate = function(year, month, day, hours, minutes, seconds, millisecon
5151

5252
// Relative dates that have no more specificity than months only walk
5353
// the bounds of months, they can't traverse into a new month if the
54-
// target month doesn't have the same number of days.
55-
if(day === undefined && month !== undefined) {
54+
// target month doesn't have the same number of days. Note including
55+
// explicitly passed 0 for days as well.
56+
if(!day && month !== undefined) {
5657
setDate = Math.min(setDate, testGetDaysInMonth(setYear, setMonth));
5758
if (setDate !== d.getDate()) {
5859
d.setDate(setDate);
@@ -116,6 +117,11 @@ testIsUTC = function(d) {
116117
return testGetPrivateProp(d, 'utc');
117118
}
118119

120+
testSetUTC = function(d) {
121+
d.setTime(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
122+
return d;
123+
}
124+
119125
testGetWeekday = function(weekday, weekOffset, hours, minutes, seconds, milliseconds) {
120126
var d = new Date();
121127
if(weekOffset) d.setDate(d.getDate() + (weekOffset * 7));
@@ -139,8 +145,8 @@ testGetEndOfRelativeMonth = function(month) {
139145

140146
testGetWeekdayInMonth = function(month, weekday, weekOffset, hours, minutes, seconds, milliseconds) {
141147
var d = new Date(), offset;
142-
d.setMonth(month);
143148
d.setDate(1);
149+
d.setMonth(month);
144150
offset = weekday - d.getDay();
145151
if (offset < 0) {
146152
offset += 7;
@@ -253,20 +259,26 @@ testSetLocale = function(code) {
253259
return Sugar.Date.setLocale(code);
254260
}
255261

256-
assertDateParsed = function(obj) {
257-
var d, arg, msg;
258-
if (arguments.length === 3) {
259-
arg = arguments[1];
260-
d = arguments[2];
261-
} else {
262-
d = arguments[1];
262+
assertDateParsed = function(d) {
263+
var opt, msg, expected, args = arguments;
264+
switch (args.length) {
265+
case 2:
266+
expected = args[1];
267+
break;
268+
case 3:
269+
opt = args[1];
270+
expected = args[2];
271+
break;
272+
case 4:
273+
opt = args[1];
274+
expected = args[2];
275+
msg = args[3];
276+
break;
263277
}
264-
if (typeof obj === 'object' && typeof JSON !== 'undefined') {
265-
msg = JSON.stringify(obj);
266-
} else {
267-
msg = obj;
278+
if (!msg) {
279+
msg = testGetStringified(d);
268280
}
269-
equal(testCreateDate(obj, arg), d, msg);
281+
equal(testCreateDate(d, opt), expected, msg);
270282
}
271283

272284
assertDateNotParsed = function(str, message) {

test/tests/date.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ namespace('Date', function () {
201201
assertDateParsed('this week tuesday at 5pm', { future: true }, testGetWeekday(2, 0, 17));
202202
assertDateParsed('today at 5pm', { future: true }, new Date(now.getFullYear(), now.getMonth(), now.getDate(), 17));
203203

204+
// Issue #582 "now" with "fromUTC"
205+
assertDateParsed('now', { fromUTC: true }, new Date(), 'now with fromUTC');
206+
204207
});
205208

206209
group('Create | Simple', function() {
@@ -1917,7 +1920,7 @@ namespace('Date', function () {
19171920
raisesError(function(){ run(d, 'format', ['{foo}']); }, 'unknown ldml token raises error', TypeError);
19181921

19191922
// Not all environments provide that so just make sure it returns the abbreviation or nothing.
1920-
equal(/\w{3}|^$/.test(run(d, 'format', ['{z}'])), true, 'Timezone abbreviation');
1923+
equal(/[-+]\d{2,4}|\w{3,5}|^$/.test(run(d, 'format', ['{z}'])), true, 'Timezone abbreviation');
19211924

19221925
test(new Date('January 4, 2010'), ['{D}'], '4', 'Day of the year');
19231926
test(new Date('January 4, 2010'), ['{DDD}'], '004', 'Day of the year padded');
@@ -2065,7 +2068,8 @@ namespace('Date', function () {
20652068
raisesError(function(){ run(d, 'format', ['%foo']); }, 'unknown strf token raises error', TypeError);
20662069

20672070
equal(/[+-]\d{4}/.test(run(d, 'format', ['%z'])), true, 'Timezone offset');
2068-
equal(/\w{3}/.test(run(d, 'format', ['%Z'])), true, 'Timezone abbreviation');
2071+
equal(/[-+]\d{2,4}|\w{3,5}|^$/.test(run(d, 'format', ['%Z'])), true, 'Timezone abbreviation');
2072+
equal(/[-+]\d{2,4}|\w{3,5}|^$/.test(run(d, 'format', ['{z}'])), true, 'Timezone abbreviation');
20692073

20702074
test(new Date('January 1, 2010'), ['%c', 'en-GB'], 'Fri 1 Jan 2010 0:00', 'Preferred stamp | UK');
20712075

test/tests/range/date.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,14 @@ namespace('Date Ranges', function () {
367367
equal(getRange(new Date(2001, 0), new Date(2004, 0)).years(), 3, '5 year range');
368368
equal(getRange(new Date(2001, 0), new Date(2001, 2)).months(), 2, 'January to March in months');
369369
equal(getRange(new Date(2001, 0), new Date(2001, 2)).days(), 59, 'January to March in days');
370-
equal(getRange(new Date(2001, 0), new Date(2001, 2)).hours(), 1416, 'January to March in hours');
371-
equal(getRange(new Date(2001, 0), new Date(2001, 2)).minutes(), 84960, 'January to March in minutes');
372-
equal(getRange(new Date(2001, 0), new Date(2001, 2)).seconds(), 5097600, 'January to March in seconds');
373-
equal(getRange(new Date(2001, 0), new Date(2001, 2)).milliseconds(), 5097600000, 'January to March in ms');
370+
371+
var range = getRange(new Date(2001, 0), new Date(2001, 2))
372+
var tzShift = (range.end.getTimezoneOffset() - range.start.getTimezoneOffset());
373+
374+
equal(range.hours(), 1416 + tzShift / 60, 'January to March in hours');
375+
equal(range.minutes(), 84960 + tzShift, 'January to March in minutes');
376+
equal(range.seconds(), 5097600 + tzShift * 60, 'January to March in seconds');
377+
equal(range.milliseconds(), 5097600000 + tzShift * 60 * 1000, 'January to March in ms');
374378

375379
equal(getRange(1, 2001).seconds(), 2, 'Number ranges are taken as milliseconds');
376380
equal(getRange('a', 'f').seconds(), NaN, 'String ranges return NaN for date units');

0 commit comments

Comments
 (0)