From 3a126c18f1d74712230b5d7744d95bfa9a0e2481 Mon Sep 17 00:00:00 2001 From: Tim Wood Date: Thu, 21 Apr 2016 12:24:03 -0500 Subject: [PATCH] Implement timezone interface See https://github.com/moment/moment-rfcs/pull/1 --- src/lib/create/date-from-array.js | 12 --- src/lib/create/from-anything.js | 46 ++++++----- src/lib/create/from-array.js | 14 +--- src/lib/create/from-string-and-array.js | 3 - src/lib/create/from-string.js | 3 +- src/lib/create/local.js | 7 +- src/lib/create/utc.js | 9 ++- src/lib/duration/bubble.js | 1 - src/lib/moment/add-subtract.js | 22 ++--- src/lib/moment/constructor.js | 33 +------- src/lib/moment/creation-data.js | 2 +- src/lib/moment/get-set.js | 9 +-- src/lib/moment/moment.js | 2 + src/lib/timezone/fixed-offset.js | 15 ++++ src/lib/timezone/local.js | 24 ++++++ src/lib/timezone/update-offset.js | 9 +++ src/lib/units/day-of-week.js | 2 +- src/lib/units/day-of-year.js | 1 - src/lib/units/month.js | 6 +- src/lib/units/offset.js | 83 +++++-------------- src/lib/units/timestamp.js | 2 + src/lib/units/timezone.js | 4 +- src/moment.js | 14 ++-- src/test/moment/create.js | 23 +----- src/test/moment/creation-data.js | 2 +- src/test/moment/getters_setters.js | 62 +++++--------- src/test/moment/start_end_of.js | 90 ++++++++------------- src/test/moment/utc.js | 5 +- src/test/moment/utc_offset.js | 82 +++++++------------ src/test/moment/zones.js | 102 ------------------------ 30 files changed, 228 insertions(+), 461 deletions(-) create mode 100644 src/lib/timezone/fixed-offset.js create mode 100644 src/lib/timezone/local.js create mode 100644 src/lib/timezone/update-offset.js diff --git a/src/lib/create/date-from-array.js b/src/lib/create/date-from-array.js index 180f55c71..d186a316d 100644 --- a/src/lib/create/date-from-array.js +++ b/src/lib/create/date-from-array.js @@ -1,15 +1,3 @@ -export function createDate (y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); - - //the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { - date.setFullYear(y); - } - return date; -} - export function createUTCDate (y) { var date = new Date(Date.UTC.apply(null, arguments)); diff --git a/src/lib/create/from-anything.js b/src/lib/create/from-anything.js index 022c6eb17..3b8b2e162 100644 --- a/src/lib/create/from-anything.js +++ b/src/lib/create/from-anything.js @@ -45,8 +45,6 @@ export function prepareConfig (config) { configFromStringAndArray(config); } else if (format) { configFromStringAndFormat(config); - } else if (isDate(input)) { - config._d = input; } else { configFromInput(config); } @@ -59,43 +57,49 @@ export function prepareConfig (config) { } function configFromInput(config) { - var input = config._i; + var input = config._i, + type = typeof input; if (input === undefined) { config._d = new Date(hooks.now()); + config._offset = 0; } else if (isDate(input)) { config._d = new Date(input.valueOf()); - } else if (typeof input === 'string') { + config._offset = 0; + } else if (type === 'string') { configFromString(config); } else if (isArray(input)) { config._a = map(input.slice(0), function (obj) { return parseInt(obj, 10); }); configFromArray(config); - } else if (typeof(input) === 'object') { + } else if (type === 'object') { configFromObject(config); - } else if (typeof(input) === 'number') { + } else if (type === 'number') { // from milliseconds config._d = new Date(input); + config._offset = 0; } else { hooks.createFromInputFallback(config); } } -export function createLocalOrUTC (input, format, locale, strict, isUTC) { - var c = {}; +export function createWithTimeZone (timeZone) { + return function (input, format, locale, strict) { + var c = {}; - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c._isAMomentObject = true; - c._useUTC = c._isUTC = isUTC; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._z = timeZone; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; - return createFromConfig(c); + return createFromConfig(c); + }; } diff --git a/src/lib/create/from-array.js b/src/lib/create/from-array.js index 25dbc28fc..3c2a2066e 100644 --- a/src/lib/create/from-array.js +++ b/src/lib/create/from-array.js @@ -1,5 +1,5 @@ import { hooks } from '../utils/hooks'; -import { createDate, createUTCDate } from './date-from-array'; +import { createUTCDate } from './date-from-array'; import { daysInYear } from '../units/year'; import { weekOfYear, weeksInYear, dayOfYearFromWeeks } from '../units/week-calendar-utils'; import { YEAR, MONTH, DATE, HOUR, MINUTE, SECOND, MILLISECOND } from '../units/constants'; @@ -10,10 +10,7 @@ import getParsingFlags from './parsing-flags'; function currentDateArray(config) { // hooks is actually the exported moment object var nowValue = new Date(hooks.now()); - if (config._useUTC) { - return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; - } - return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; } // convert an array to a date. @@ -70,12 +67,7 @@ export function configFromArray (config) { config._a[HOUR] = 0; } - config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } + config._d = createUTCDate.apply(null, input); if (config._nextDay) { config._a[HOUR] = 24; diff --git a/src/lib/create/from-string-and-array.js b/src/lib/create/from-string-and-array.js index 1d8a7a806..07d119536 100644 --- a/src/lib/create/from-string-and-array.js +++ b/src/lib/create/from-string-and-array.js @@ -22,9 +22,6 @@ export function configFromStringAndArray(config) { for (i = 0; i < config._f.length; i++) { currentScore = 0; tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } tempConfig._f = config._f[i]; configFromStringAndFormat(tempConfig); diff --git a/src/lib/create/from-string.js b/src/lib/create/from-string.js index 6852049f2..704d2f4cd 100644 --- a/src/lib/create/from-string.js +++ b/src/lib/create/from-string.js @@ -99,6 +99,7 @@ export function configFromString(config) { if (matched !== null) { config._d = new Date(+matched[1]); + config._offset = 0; return; } @@ -115,6 +116,6 @@ hooks.createFromInputFallback = deprecate( 'release. Please refer to ' + 'https://github.com/moment/moment/issues/1407 for more info.', function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + config._d = new Date(config._i); } ); diff --git a/src/lib/create/local.js b/src/lib/create/local.js index 88c1e2692..3f1e8ed54 100644 --- a/src/lib/create/local.js +++ b/src/lib/create/local.js @@ -1,5 +1,4 @@ -import { createLocalOrUTC } from './from-anything'; +import LocalTimeZone from '../timezone/local'; +import { createWithTimeZone } from './from-anything'; -export function createLocal (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, false); -} +export var createLocal = createWithTimeZone(new LocalTimeZone()); diff --git a/src/lib/create/utc.js b/src/lib/create/utc.js index 96139530e..3d1b21d28 100644 --- a/src/lib/create/utc.js +++ b/src/lib/create/utc.js @@ -1,5 +1,8 @@ -import { createLocalOrUTC } from './from-anything'; +import FixedOffsetTimeZone from '../timezone/fixed-offset'; +import { createWithTimeZone } from './from-anything'; -export function createUTC (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); +var create = createWithTimeZone(new FixedOffsetTimeZone(0)); + +export function createUTC () { + return create.apply(null, arguments).utcOffset(0); } diff --git a/src/lib/duration/bubble.js b/src/lib/duration/bubble.js index 0c4a336ec..d13316a22 100644 --- a/src/lib/duration/bubble.js +++ b/src/lib/duration/bubble.js @@ -1,6 +1,5 @@ import absFloor from '../utils/abs-floor'; import absCeil from '../utils/abs-ceil'; -import { createUTCDate } from '../create/date-from-array'; export function bubble () { var milliseconds = this._milliseconds; diff --git a/src/lib/moment/add-subtract.js b/src/lib/moment/add-subtract.js index 588153bbe..5df39dc3a 100644 --- a/src/lib/moment/add-subtract.js +++ b/src/lib/moment/add-subtract.js @@ -1,11 +1,9 @@ -import { get, set } from './get-set'; import { setMonth } from '../units/month'; import { createDuration } from '../duration/create'; import { deprecateSimple } from '../utils/deprecate'; -import { hooks } from '../utils/hooks'; +import updateOffset from '../timezone/update-offset'; import absRound from '../utils/abs-round'; - // TODO: remove 'name' arg after deprecation is removed function createAdder(direction, name) { return function (val, period) { @@ -23,32 +21,28 @@ function createAdder(direction, name) { }; } -export function addSubtract (mom, duration, isAdding, updateOffset) { +export function addSubtract (mom, duration, isAdding) { var milliseconds = duration._milliseconds, days = absRound(duration._days), - months = absRound(duration._months); + months = absRound(duration._months), + date = mom._d; if (!mom.isValid()) { // No op return; } - updateOffset = updateOffset == null ? true : updateOffset; - if (milliseconds) { - mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + date.setTime(date.valueOf() + milliseconds * isAdding); } if (days) { - set(mom, 'Date', get(mom, 'Date') + days * isAdding); + date.setUTCDate(date.getUTCDate() + days * isAdding); } if (months) { - setMonth(mom, get(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - hooks.updateOffset(mom, days || months); + setMonth(mom, date.getUTCMonth() + months * isAdding); } + updateOffset(mom, days || months); } export var add = createAdder(1, 'add'); export var subtract = createAdder(-1, 'subtract'); - diff --git a/src/lib/moment/constructor.js b/src/lib/moment/constructor.js index 964c0aef6..569f12304 100644 --- a/src/lib/moment/constructor.js +++ b/src/lib/moment/constructor.js @@ -1,15 +1,8 @@ -import { hooks } from '../utils/hooks'; -import hasOwnProp from '../utils/has-own-prop'; import isUndefined from '../utils/is-undefined'; import getParsingFlags from '../create/parsing-flags'; - -// Plugins that add properties should also add the key here (null value), -// so we can properly clone ourselves. -var momentProperties = hooks.momentProperties = []; +import updateOffset from '../timezone/update-offset'; export function copyConfig(to, from) { - var i, prop, val; - if (!isUndefined(from._isAMomentObject)) { to._isAMomentObject = from._isAMomentObject; } @@ -28,9 +21,6 @@ export function copyConfig(to, from) { if (!isUndefined(from._tzm)) { to._tzm = from._tzm; } - if (!isUndefined(from._isUTC)) { - to._isUTC = from._isUTC; - } if (!isUndefined(from._offset)) { to._offset = from._offset; } @@ -40,33 +30,16 @@ export function copyConfig(to, from) { if (!isUndefined(from._locale)) { to._locale = from._locale; } - - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (!isUndefined(val)) { - to[prop] = val; - } - } - } + to._z = from._z; return to; } -var updateInProgress = false; - // Moment prototype object export function Moment(config) { copyConfig(this, config); this._d = new Date(config._d != null ? config._d.getTime() : NaN); - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - hooks.updateOffset(this); - updateInProgress = false; - } + updateOffset(this, this._offset == null); } export function isMoment (obj) { diff --git a/src/lib/moment/creation-data.js b/src/lib/moment/creation-data.js index 7e2d69aa1..00378619e 100644 --- a/src/lib/moment/creation-data.js +++ b/src/lib/moment/creation-data.js @@ -3,7 +3,7 @@ export function creationData() { input: this._i, format: this._f, locale: this._locale, - isUTC: this._isUTC, + timeZone: this._z, strict: this._strict }; } diff --git a/src/lib/moment/get-set.js b/src/lib/moment/get-set.js index ef43f391a..1257049dd 100644 --- a/src/lib/moment/get-set.js +++ b/src/lib/moment/get-set.js @@ -1,13 +1,12 @@ import { normalizeUnits } from '../units/aliases'; -import { hooks } from '../utils/hooks'; +import updateOffset from '../timezone/update-offset'; import isFunction from '../utils/is-function'; export function makeGetSet (unit, keepTime) { return function (value) { if (value != null) { set(this, unit, value); - hooks.updateOffset(this, keepTime); - return this; + return updateOffset(this, keepTime); } else { return get(this, unit); } @@ -16,12 +15,12 @@ export function makeGetSet (unit, keepTime) { export function get (mom, unit) { return mom.isValid() ? - mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + mom._d['getUTC' + unit]() : NaN; } export function set (mom, unit, value) { if (mom.isValid()) { - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + mom._d['setUTC' + unit](value); } } diff --git a/src/lib/moment/moment.js b/src/lib/moment/moment.js index 12eb5f15a..a9930f62d 100644 --- a/src/lib/moment/moment.js +++ b/src/lib/moment/moment.js @@ -1,5 +1,6 @@ import { createLocal } from '../create/local'; import { createUTC } from '../create/utc'; +import { createWithTimeZone } from '../create/from-anything'; import { createInvalid } from '../create/valid'; import { isMoment } from './constructor'; import { min, max } from './min-max'; @@ -23,6 +24,7 @@ export { createUnix, createLocal, createInZone, + createWithTimeZone, createInvalid, momentPrototype }; diff --git a/src/lib/timezone/fixed-offset.js b/src/lib/timezone/fixed-offset.js new file mode 100644 index 000000000..8d1730c1f --- /dev/null +++ b/src/lib/timezone/fixed-offset.js @@ -0,0 +1,15 @@ +export default function FixedOffsetTimeZone (offset) { + this._offset = offset; +} + +FixedOffsetTimeZone.prototype.parse = function (timestamp) { + return this._offset; +}; + +FixedOffsetTimeZone.prototype.abbr = function (timestamp) { + return 'UTC'; +}; + +FixedOffsetTimeZone.prototype.name = function (timestamp) { + return 'Coordinated Universal Time'; +}; diff --git a/src/lib/timezone/local.js b/src/lib/timezone/local.js new file mode 100644 index 000000000..7a88c175c --- /dev/null +++ b/src/lib/timezone/local.js @@ -0,0 +1,24 @@ +export default function LocalTimeZone () {} + +function getTimezoneOffset (date) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(date.getTimezoneOffset() / 15) * 15; +} + +LocalTimeZone.prototype.parse = function (timestamp) { + var asUtc = new Date(timestamp); + return getTimezoneOffset(new Date( + asUtc.getUTCFullYear(), + asUtc.getUTCMonth(), + asUtc.getUTCDate(), + asUtc.getUTCHours(), + asUtc.getUTCMinutes(), + asUtc.getUTCSeconds(), + asUtc.getUTCMilliseconds() + )); +}; + +LocalTimeZone.prototype.abbr = function (timestamp) { + return ''; +}; diff --git a/src/lib/timezone/update-offset.js b/src/lib/timezone/update-offset.js new file mode 100644 index 000000000..d7a7e4f19 --- /dev/null +++ b/src/lib/timezone/update-offset.js @@ -0,0 +1,9 @@ +export default function updateOffset (instance, keepLocalTime) { + var oldOffset = instance._offset || 0; + var newOffset = instance._z.parse(+instance._d); + if (oldOffset !== newOffset && !keepLocalTime) { + instance._d.setTime(+instance._d + (newOffset - oldOffset) * 60000); + } + instance._offset = newOffset; + return instance; +} diff --git a/src/lib/units/day-of-week.js b/src/lib/units/day-of-week.js index 18e2a42ba..faf883dba 100644 --- a/src/lib/units/day-of-week.js +++ b/src/lib/units/day-of-week.js @@ -210,7 +210,7 @@ export function getSetDayOfWeek (input) { if (!this.isValid()) { return input != null ? this : NaN; } - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + var day = this._d.getUTCDay(); if (input != null) { input = parseWeekday(input, this.localeData()); return this.add(input - day, 'd'); diff --git a/src/lib/units/day-of-year.js b/src/lib/units/day-of-year.js index 62d4de8b4..878b73ec6 100644 --- a/src/lib/units/day-of-year.js +++ b/src/lib/units/day-of-year.js @@ -2,7 +2,6 @@ import { addFormatToken } from '../format/format'; import { addUnitAlias } from './aliases'; import { addRegexToken, match3, match1to3 } from '../parse/regex'; import { daysInYear } from './year'; -import { createUTCDate } from '../create/date-from-array'; import { addParseToken } from '../parse/token'; import toInt from '../utils/to-int'; diff --git a/src/lib/units/month.js b/src/lib/units/month.js index f1cef1550..e76f327a1 100644 --- a/src/lib/units/month.js +++ b/src/lib/units/month.js @@ -6,6 +6,7 @@ import { addRegexToken, match1to2, match2, matchWord, regexEscape } from '../par import { addParseToken } from '../parse/token'; import { hooks } from '../utils/hooks'; import { MONTH } from './constants'; +import updateOffset from '../timezone/update-offset'; import toInt from '../utils/to-int'; import isArray from '../utils/is-array'; import indexOf from '../utils/index-of'; @@ -176,15 +177,14 @@ export function setMonth (mom, value) { } dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + mom._d.setUTCMonth(value, dayOfMonth); return mom; } export function getSetMonth (value) { if (value != null) { setMonth(this, value); - hooks.updateOffset(this, true); - return this; + return updateOffset(this, true); } else { return get(this, 'Month'); } diff --git a/src/lib/units/offset.js b/src/lib/units/offset.js index 22305d17e..6c7a892df 100644 --- a/src/lib/units/offset.js +++ b/src/lib/units/offset.js @@ -1,18 +1,19 @@ import zeroFill from '../utils/zero-fill'; import { createDuration } from '../duration/create'; -import { addSubtract } from '../moment/add-subtract'; import { isMoment, copyConfig } from '../moment/constructor'; import { addFormatToken } from '../format/format'; import { addRegexToken, matchOffset, matchShortOffset } from '../parse/regex'; import { addParseToken } from '../parse/token'; import { createLocal } from '../create/local'; -import { prepareConfig } from '../create/from-anything'; +import { prepareConfig, createWithTimeZone } from '../create/from-anything'; import { createUTC } from '../create/utc'; +import FixedOffsetTimeZone from '../timezone/fixed-offset'; +import LocalTimeZone from '../timezone/local'; +import updateOffset from '../timezone/update-offset'; import isDate from '../utils/is-date'; import toInt from '../utils/to-int'; import isUndefined from '../utils/is-undefined'; import compareArrays from '../utils/compare-arrays'; -import { hooks } from '../utils/hooks'; // FORMATTING @@ -36,8 +37,7 @@ offset('ZZ', ''); addRegexToken('Z', matchShortOffset); addRegexToken('ZZ', matchShortOffset); addParseToken(['Z', 'ZZ'], function (input, array, config) { - config._useUTC = true; - config._tzm = offsetFromString(matchShortOffset, input); + config._offset = config._tzm = offsetFromString(matchShortOffset, input); }); // HELPERS @@ -58,31 +58,11 @@ function offsetFromString(matcher, string) { // Return a moment from input, that is local/utc/zone equivalent to model. export function cloneWithOffset(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); - // Use low-level api, because this fn is low-level api. - res._d.setTime(res._d.valueOf() + diff); - hooks.updateOffset(res, false); - return res; - } else { - return createLocal(input).local(); - } -} - -function getDateOffset (m) { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + var output = createLocal(input); + output._z = model._z; + return updateOffset(output); } -// HOOKS - -// This function will be called whenever a moment is mutated. -// It is intended to keep the offset in sync with the timezone. -hooks.updateOffset = function () {}; - // MOMENTS // keepLocalTime = true means only change the timezone, without @@ -96,8 +76,6 @@ hooks.updateOffset = function () {}; // _changeInProgress == true case, then we have to adjust, because // there is no such time in the given timezone. export function getSetOffset (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; if (!this.isValid()) { return input != null ? this : NaN; } @@ -107,26 +85,10 @@ export function getSetOffset (input, keepLocalTime) { } else if (Math.abs(input) < 16) { input = input * 60; } - if (!this._isUTC && keepLocalTime) { - localAdjust = getDateOffset(this); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - addSubtract(this, createDuration(input - offset, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - hooks.updateOffset(this, true); - this._changeInProgress = null; - } - } - return this; + this._z = new FixedOffsetTimeZone(input); + return updateOffset(this, keepLocalTime); } else { - return this._isUTC ? offset : getDateOffset(this); + return this._offset || 0; } } @@ -136,9 +98,7 @@ export function getSetZone (input, keepLocalTime) { input = -input; } - this.utcOffset(input, keepLocalTime); - - return this; + return this.utcOffset(input, keepLocalTime); } else { return -this.utcOffset(); } @@ -149,15 +109,8 @@ export function setOffsetToUTC (keepLocalTime) { } export function setOffsetToLocal (keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.subtract(getDateOffset(this), 'm'); - } - } - return this; + this._z = new LocalTimeZone(); + return updateOffset(this, keepLocalTime); } export function setOffsetToParsedOffset () { @@ -196,7 +149,7 @@ export function isDaylightSavingTimeShifted () { c = prepareConfig(c); if (c._a) { - var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + var other = cloneWithOffset(c._a, c); this._isDSTShifted = this.isValid() && compareArrays(c._a, other.toArray()) > 0; } else { @@ -207,13 +160,13 @@ export function isDaylightSavingTimeShifted () { } export function isLocal () { - return this.isValid() ? !this._isUTC : false; + return this.isValid() && this._z instanceof LocalTimeZone; } export function isUtcOffset () { - return this.isValid() ? this._isUTC : false; + return this.isValid() && this._z instanceof FixedOffsetTimeZone; } export function isUtc () { - return this.isValid() ? this._isUTC && this._offset === 0 : false; + return this.isValid() && this._z instanceof FixedOffsetTimeZone && this._offset === 0; } diff --git a/src/lib/units/timestamp.js b/src/lib/units/timestamp.js index a49e1e4b8..0afc1d029 100644 --- a/src/lib/units/timestamp.js +++ b/src/lib/units/timestamp.js @@ -14,7 +14,9 @@ addRegexToken('x', matchSigned); addRegexToken('X', matchTimestamp); addParseToken('X', function (input, array, config) { config._d = new Date(parseFloat(input, 10) * 1000); + config._offset = 0; }); addParseToken('x', function (input, array, config) { config._d = new Date(toInt(input)); + config._offset = 0; }); diff --git a/src/lib/units/timezone.js b/src/lib/units/timezone.js index 20c81cd2c..4ea226809 100644 --- a/src/lib/units/timezone.js +++ b/src/lib/units/timezone.js @@ -8,9 +8,9 @@ addFormatToken('zz', 0, 0, 'zoneName'); // MOMENTS export function getZoneAbbr () { - return this._isUTC ? 'UTC' : ''; + return this._z.abbr(+this); } export function getZoneName () { - return this._isUTC ? 'Coordinated Universal Time' : ''; + return typeof this._z.name === 'function' ? this._z.name(+this) : this._z.abbr(); } diff --git a/src/moment.js b/src/moment.js index ebe17525a..95597226c 100644 --- a/src/moment.js +++ b/src/moment.js @@ -13,12 +13,13 @@ import { max, now, isMoment, - momentPrototype as fn, - createUTC as utc, - createUnix as unix, - createLocal as local, - createInvalid as invalid, - createInZone as parseZone + momentPrototype as fn, + createUTC as utc, + createUnix as unix, + createLocal as local, + createInvalid as invalid, + createInZone as parseZone, + createWithTimeZone as withTimeZone } from './lib/moment/moment'; import { @@ -60,6 +61,7 @@ moment.duration = duration; moment.isMoment = isMoment; moment.weekdays = weekdays; moment.parseZone = parseZone; +moment.withTimeZone = withTimeZone; moment.localeData = localeData; moment.isDuration = isDuration; moment.monthsShort = monthsShort; diff --git a/src/test/moment/create.js b/src/test/moment/create.js index e6222fe24..8b1a2787d 100644 --- a/src/test/moment/create.js +++ b/src/test/moment/create.js @@ -103,16 +103,6 @@ test('cloning moment works with weird clones', function (assert) { assert.equal(+extend({}, nowu).clone(), +nowu, 'cloning extend-ed utc now is utc now'); }); -test('cloning respects moment.momentProperties', function (assert) { - var m = moment(); - - assert.equal(m.clone()._special, undefined, 'cloning ignores extra properties'); - m._special = 'bacon'; - moment.momentProperties.push('_special'); - assert.equal(m.clone()._special, 'bacon', 'cloning respects momentProperties'); - moment.momentProperties.pop(); -}); - test('undefined', function (assert) { assert.ok(moment().toDate() instanceof Date, 'undefined'); }); @@ -402,15 +392,10 @@ test('explicit cloning', function (assert) { assert.equal(momentA.month(), 5, 'Calling moment() on a moment will create a clone'); }); -test('cloning carrying over utc mode', function (assert) { - assert.equal(moment().local().clone()._isUTC, false, 'An explicit cloned local moment should have _isUTC == false'); - assert.equal(moment().utc().clone()._isUTC, true, 'An cloned utc moment should have _isUTC == true'); - assert.equal(moment().clone()._isUTC, false, 'An explicit cloned local moment should have _isUTC == false'); - assert.equal(moment.utc().clone()._isUTC, true, 'An explicit cloned utc moment should have _isUTC == true'); - assert.equal(moment(moment().local())._isUTC, false, 'An implicit cloned local moment should have _isUTC == false'); - assert.equal(moment(moment().utc())._isUTC, true, 'An implicit cloned utc moment should have _isUTC == true'); - assert.equal(moment(moment())._isUTC, false, 'An implicit cloned local moment should have _isUTC == false'); - assert.equal(moment(moment.utc())._isUTC, true, 'An implicit cloned utc moment should have _isUTC == true'); +test('cloning carrying over zone', function (assert) { + var momentA = moment(); + assert.equal(momentA._z, momentA.clone()._z, 'An explicitly cloned moment should copy the zone'); + assert.equal(momentA._z, moment(momentA)._z, 'An implicitly cloned moment should copy the zone'); }); test('parsing iso', function (assert) { diff --git a/src/test/moment/creation-data.js b/src/test/moment/creation-data.js index 15391462e..79570cd78 100644 --- a/src/test/moment/creation-data.js +++ b/src/test/moment/creation-data.js @@ -11,7 +11,7 @@ test('valid date', function (assert) { assert.equal(orig.input, '1992-10-22', 'original input is not correct.'); assert.equal(orig.format, 'YYYY-MM-DD', 'original format is defined.'); assert.equal(orig.locale._abbr, 'en', 'default locale is en'); - assert.equal(orig.isUTC, false, 'not a UTC date'); + assert.ok(orig.timeZone, 'has a zone property'); }); test('valid date at fr locale', function (assert) { diff --git a/src/test/moment/getters_setters.js b/src/test/moment/getters_setters.js index 8f23e6882..56811530b 100644 --- a/src/test/moment/getters_setters.js +++ b/src/test/moment/getters_setters.js @@ -253,67 +253,45 @@ test('string setters', function (assert) { }); test('setters across DST +1', function (assert) { - var oldUpdateOffset = moment.updateOffset, - // Based on a real story somewhere in America/Los_Angeles - dstAt = moment('2014-03-09T02:00:00-08:00').parseZone(), + var create = moment.withTimeZone({ + parse: function (timestamp) { + // Based on a real story somewhere in America/Los_Angeles + return timestamp < Date.UTC(2014, 2, 9, 2) ? -480 : -420; + } + }), m; - moment.updateOffset = function (mom, keepTime) { - if (mom.isBefore(dstAt)) { - mom.utcOffset(-8, keepTime); - } else { - mom.utcOffset(-7, keepTime); - } - }; - - m = moment('2014-03-15T00:00:00-07:00').parseZone(); - m.year(2013); + m = create('2014-03-15T00:00:00-07:00').year(2013); assert.equal(m.format(), '2013-03-15T00:00:00-08:00', 'year across +1'); - m = moment('2014-03-15T00:00:00-07:00').parseZone(); - m.month(0); + m = create('2014-03-15T00:00:00-07:00').month(0); assert.equal(m.format(), '2014-01-15T00:00:00-08:00', 'month across +1'); - m = moment('2014-03-15T00:00:00-07:00').parseZone(); - m.date(1); + m = create('2014-03-15T00:00:00-07:00').date(1); assert.equal(m.format(), '2014-03-01T00:00:00-08:00', 'date across +1'); - m = moment('2014-03-09T03:05:00-07:00').parseZone(); - m.hour(0); + m = create('2014-03-09T03:05:00-07:00').hour(0); assert.equal(m.format(), '2014-03-09T00:05:00-08:00', 'hour across +1'); - - moment.updateOffset = oldUpdateOffset; }); test('setters across DST -1', function (assert) { - var oldUpdateOffset = moment.updateOffset, - // Based on a real story somewhere in America/Los_Angeles - dstAt = moment('2014-11-02T02:00:00-07:00').parseZone(), + var create = moment.withTimeZone({ + parse: function (timestamp) { + // Based on a real story somewhere in America/Los_Angeles + return timestamp < Date.UTC(2014, 10, 2, 2) ? -420 : -480; + } + }), m; - moment.updateOffset = function (mom, keepTime) { - if (mom.isBefore(dstAt)) { - mom.utcOffset(-7, keepTime); - } else { - mom.utcOffset(-8, keepTime); - } - }; - - m = moment('2014-11-15T00:00:00-08:00').parseZone(); - m.year(2013); + m = create('2014-11-15T00:00:00-08:00').year(2013); assert.equal(m.format(), '2013-11-15T00:00:00-07:00', 'year across -1'); - m = moment('2014-11-15T00:00:00-08:00').parseZone(); - m.month(0); + m = create('2014-11-15T00:00:00-08:00').month(0); assert.equal(m.format(), '2014-01-15T00:00:00-07:00', 'month across -1'); - m = moment('2014-11-15T00:00:00-08:00').parseZone(); - m.date(1); + m = create('2014-11-15T00:00:00-08:00').date(1); assert.equal(m.format(), '2014-11-01T00:00:00-07:00', 'date across -1'); - m = moment('2014-11-02T03:30:00-08:00').parseZone(); - m.hour(0); + m = create('2014-11-02T03:30:00-08:00').hour(0); assert.equal(m.format(), '2014-11-02T00:30:00-07:00', 'hour across -1'); - - moment.updateOffset = oldUpdateOffset; }); diff --git a/src/test/moment/start_end_of.js b/src/test/moment/start_end_of.js index 519c7641e..0ac5de9fb 100644 --- a/src/test/moment/start_end_of.js +++ b/src/test/moment/start_end_of.js @@ -309,81 +309,55 @@ test('end of second', function (assert) { }); test('startOf across DST +1', function (assert) { - var oldUpdateOffset = moment.updateOffset, - // Based on a real story somewhere in America/Los_Angeles - dstAt = moment('2014-03-09T02:00:00-08:00').parseZone(), + var create = moment.withTimeZone({ + parse: function (timestamp) { + // Based on a real story somewhere in America/Los_Angeles + return timestamp < Date.UTC(2014, 2, 9, 2) ? -480 : -420; + } + }), m; - moment.updateOffset = function (mom, keepTime) { - if (mom.isBefore(dstAt)) { - mom.utcOffset(-8, keepTime); - } else { - mom.utcOffset(-7, keepTime); - } - }; + m = create('2014-03-15T00:00:00-07:00').startOf('y'); + assert.equal(m.format(), '2014-01-01T00:00:00-08:00', 'startOf("year") across +1'); - m = moment('2014-03-15T00:00:00-07:00').parseZone(); - m.startOf('y'); - assert.equal(m.format(), '2014-01-01T00:00:00-08:00', 'startOf(\'year\') across +1'); + m = create('2014-03-15T00:00:00-07:00').startOf('M'); + assert.equal(m.format(), '2014-03-01T00:00:00-08:00', 'startOf("month") across +1'); - m = moment('2014-03-15T00:00:00-07:00').parseZone(); - m.startOf('M'); - assert.equal(m.format(), '2014-03-01T00:00:00-08:00', 'startOf(\'month\') across +1'); + m = create('2014-03-09T09:00:00-07:00').startOf('d'); + assert.equal(m.format(), '2014-03-09T00:00:00-08:00', 'startOf("day") across +1'); - m = moment('2014-03-09T09:00:00-07:00').parseZone(); - m.startOf('d'); - assert.equal(m.format(), '2014-03-09T00:00:00-08:00', 'startOf(\'day\') across +1'); + m = create('2014-03-09T03:05:00-07:00').startOf('h'); + assert.equal(m.format(), '2014-03-09T03:00:00-07:00', 'startOf("hour") after +1'); - m = moment('2014-03-09T03:05:00-07:00').parseZone(); - m.startOf('h'); - assert.equal(m.format(), '2014-03-09T03:00:00-07:00', 'startOf(\'hour\') after +1'); - - m = moment('2014-03-09T01:35:00-08:00').parseZone(); - m.startOf('h'); - assert.equal(m.format(), '2014-03-09T01:00:00-08:00', 'startOf(\'hour\') before +1'); - - // There is no such time as 2:30-7 to try startOf('hour') across that - - moment.updateOffset = oldUpdateOffset; + m = create('2014-03-09T01:35:00-08:00').startOf('h'); + assert.equal(m.format(), '2014-03-09T01:00:00-08:00', 'startOf("hour") before +1'); }); test('startOf across DST -1', function (assert) { - var oldUpdateOffset = moment.updateOffset, - // Based on a real story somewhere in America/Los_Angeles - dstAt = moment('2014-11-02T02:00:00-07:00').parseZone(), + var create = moment.withTimeZone({ + parse: function (timestamp) { + // Based on a real story somewhere in America/Los_Angeles + return timestamp < Date.UTC(2014, 10, 2, 2) ? -420 : -480; + } + }), m; - moment.updateOffset = function (mom, keepTime) { - if (mom.isBefore(dstAt)) { - mom.utcOffset(-7, keepTime); - } else { - mom.utcOffset(-8, keepTime); - } - }; + m = create('2014-11-15T00:00:00-08:00').startOf('y'); + assert.equal(m.format(), '2014-01-01T00:00:00-07:00', 'startOf("year") across -1'); - m = moment('2014-11-15T00:00:00-08:00').parseZone(); - m.startOf('y'); - assert.equal(m.format(), '2014-01-01T00:00:00-07:00', 'startOf(\'year\') across -1'); + m = create('2014-11-15T00:00:00-08:00').startOf('M'); + assert.equal(m.format(), '2014-11-01T00:00:00-07:00', 'startOf("month") across -1'); - m = moment('2014-11-15T00:00:00-08:00').parseZone(); - m.startOf('M'); - assert.equal(m.format(), '2014-11-01T00:00:00-07:00', 'startOf(\'month\') across -1'); - - m = moment('2014-11-02T09:00:00-08:00').parseZone(); - m.startOf('d'); - assert.equal(m.format(), '2014-11-02T00:00:00-07:00', 'startOf(\'day\') across -1'); + m = create('2014-11-02T09:00:00-08:00').startOf('d'); + assert.equal(m.format(), '2014-11-02T00:00:00-07:00', 'startOf("day") across -1'); // note that utc offset is -8 - m = moment('2014-11-02T01:30:00-08:00').parseZone(); - m.startOf('h'); - assert.equal(m.format(), '2014-11-02T01:00:00-08:00', 'startOf(\'hour\') after +1'); + m = create('2014-11-02T01:30:00-08:00').startOf('h'); + assert.equal(m.format(), '2014-11-02T01:00:00-08:00', 'startOf("hour") after +1'); // note that utc offset is -7 - m = moment('2014-11-02T01:30:00-07:00').parseZone(); - m.startOf('h'); - assert.equal(m.format(), '2014-11-02T01:00:00-07:00', 'startOf(\'hour\') before +1'); - - moment.updateOffset = oldUpdateOffset; + m = create('2014-11-02T01:30:00-07:00').startOf('h'); + assert.equal(m.format(), '2014-11-02T01:00:00-07:00', 'startOf("hour") before +1'); }); test('endOf millisecond and no-arg', function (assert) { diff --git a/src/test/moment/utc.js b/src/test/moment/utc.js index 64a0bb12c..eea4bb16d 100644 --- a/src/test/moment/utc.js +++ b/src/test/moment/utc.js @@ -61,11 +61,10 @@ test('creating with utc without timezone', function (assert) { test('cloning with utc offset', function (assert) { var m = moment.utc('2012-01-02T08:20:00'); - assert.equal(moment.utc(m)._isUTC, true, 'the local offset should be converted to UTC'); - assert.equal(moment.utc(m.clone().utc())._isUTC, true, 'the local offset should stay in UTC'); + assert.equal(moment.utc(m).utcOffset(), 0, 'the utc offset should be 0'); + assert.equal(moment.utc(m.clone().utc()).utcOffset(), 0, 'the utc offset should stay 0'); m.utcOffset(120); - assert.equal(moment.utc(m)._isUTC, true, 'the explicit utc offset should stay in UTC'); assert.equal(moment.utc(m).utcOffset(), 0, 'the explicit utc offset should have an offset of 0'); }); diff --git a/src/test/moment/utc_offset.js b/src/test/moment/utc_offset.js index 8f2a8f298..e79064cbd 100644 --- a/src/test/moment/utc_offset.js +++ b/src/test/moment/utc_offset.js @@ -120,23 +120,16 @@ test('distance from the unix epoch', function (assert) { }); test('update offset after changing any values', function (assert) { - var oldOffset = moment.updateOffset, - m = moment.utc([2000, 6, 1]); - - moment.updateOffset = function (mom, keepTime) { - if (mom.__doChange) { - if (+mom > 962409600000) { - mom.utcOffset(-120, keepTime); - } else { - mom.utcOffset(-60, keepTime); - } - } - }; + var m = moment.utc([2000, 6, 1]); assert.equal(m.format('ZZ'), '+0000', 'should be at +0000'); assert.equal(m.format('HH:mm'), '00:00', 'should start 12AM at +0000 timezone'); - m.__doChange = true; + m._z = { + parse: function (timestamp) { + return timestamp > Date.UTC(2000, 6, 1) ? -120 : -60; + } + }; m.add(1, 'h'); assert.equal(m.format('ZZ'), '-0200', 'should be at -0200'); @@ -146,11 +139,8 @@ test('update offset after changing any values', function (assert) { assert.equal(m.format('ZZ'), '-0100', 'should be at -0100'); assert.equal(m.format('HH:mm'), '23:00', '12AM at +0000 should be 11PM at -0100 timezone'); - - moment.updateOffset = oldOffset; }); -////////////////// test('getters and setters', function (assert) { var a = moment([2011, 5, 20]); @@ -313,16 +303,13 @@ test('same / before / after', function (assert) { }); test('add / subtract over dst', function (assert) { - var oldOffset = moment.updateOffset, - m = moment.utc([2000, 2, 31, 3]); - - moment.updateOffset = function (mom, keepTime) { - if (mom.clone().utc().month() > 2) { - mom.utcOffset(60, keepTime); - } else { - mom.utcOffset(0, keepTime); - } - }; + var m = moment.utc([2000, 2, 31, 3]); + + function parse (instance) { + return new Date(+instance).getUTCMonth() > 2 ? 60 : 0; + } + + m._z = {parse: parse, offset: parse}; assert.equal(m.hour(), 3, 'should start at 00:00'); @@ -349,38 +336,29 @@ test('add / subtract over dst', function (assert) { m.subtract(1, 'month'); assert.equal(m.hour(), 3, 'subtracting 1 month should have the same hour'); - - moment.updateOffset = oldOffset; }); test('isDST', function (assert) { - var oldOffset = moment.updateOffset; - - moment.updateOffset = function (mom, keepTime) { - if (mom.month() > 2 && mom.month() < 9) { - mom.utcOffset(60, keepTime); - } else { - mom.utcOffset(0, keepTime); + var withSummerDst = moment.withTimeZone({ + parse: function (timestamp) { + var month = new Date(timestamp).getUTCMonth(); + return (month > 2 && month < 9) ? 60 : 0; } - }; + }); - assert.ok(!moment().month(0).isDST(), 'Jan should not be summer dst'); - assert.ok(moment().month(6).isDST(), 'Jul should be summer dst'); - assert.ok(!moment().month(11).isDST(), 'Dec should not be summer dst'); - - moment.updateOffset = function (mom) { - if (mom.month() > 2 && mom.month() < 9) { - mom.utcOffset(0); - } else { - mom.utcOffset(60); + var withWinterDst = moment.withTimeZone({ + parse: function (timestamp) { + var month = new Date(timestamp).getUTCMonth(); + return (month > 2 && month < 9) ? 0 : 60; } - }; - - assert.ok(moment().month(0).isDST(), 'Jan should be winter dst'); - assert.ok(!moment().month(6).isDST(), 'Jul should not be winter dst'); - assert.ok(moment().month(11).isDST(), 'Dec should be winter dst'); - - moment.updateOffset = oldOffset; + }); + + assert.ok(!withSummerDst().month(0).isDST(), 'Jan should not be summer dst'); + assert.ok(withSummerDst().month(6).isDST(), 'Jul should be summer dst'); + assert.ok(!withSummerDst().month(11).isDST(), 'Dec should not be summer dst'); + assert.ok(withWinterDst().month(0).isDST(), 'Jan should be winter dst'); + assert.ok(!withWinterDst().month(6).isDST(), 'Jul should not be winter dst'); + assert.ok(withWinterDst().month(11).isDST(), 'Dec should be winter dst'); }); test('zone names', function (assert) { diff --git a/src/test/moment/zones.js b/src/test/moment/zones.js index 11ede1184..0d9250811 100644 --- a/src/test/moment/zones.js +++ b/src/test/moment/zones.js @@ -110,37 +110,6 @@ test('distance from the unix epoch', function (assert) { assert.equal(+zoneA, +zoneE, 'moment should equal moment.zone(1000)'); }); -test('update offset after changing any values', function (assert) { - var oldOffset = moment.updateOffset, - m = moment.utc([2000, 6, 1]); - - moment.updateOffset = function (mom, keepTime) { - if (mom.__doChange) { - if (+mom > 962409600000) { - mom.zone(120, keepTime); - } else { - mom.zone(60, keepTime); - } - } - }; - - assert.equal(m.format('ZZ'), '+0000', 'should be at +0000'); - assert.equal(m.format('HH:mm'), '00:00', 'should start 12AM at +0000 timezone'); - - m.__doChange = true; - m.add(1, 'h'); - - assert.equal(m.format('ZZ'), '-0200', 'should be at -0200'); - assert.equal(m.format('HH:mm'), '23:00', '1AM at +0000 should be 11PM at -0200 timezone'); - - m.subtract(1, 'h'); - - assert.equal(m.format('ZZ'), '-0100', 'should be at -0100'); - assert.equal(m.format('HH:mm'), '23:00', '12AM at +0000 should be 11PM at -0100 timezone'); - - moment.updateOffset = oldOffset; -}); - test('getters and setters', function (assert) { var a = moment([2011, 5, 20]); @@ -292,77 +261,6 @@ test('same / before / after', function (assert) { assert.ok(zoneA.isBefore(zoneC, 'hour'), 'isBefore:hour should work with two moments with different offsets'); }); -test('add / subtract over dst', function (assert) { - var oldOffset = moment.updateOffset, - m = moment.utc([2000, 2, 31, 3]); - - moment.updateOffset = function (mom, keepTime) { - if (mom.clone().utc().month() > 2) { - mom.zone(-60, keepTime); - } else { - mom.zone(0, keepTime); - } - }; - - assert.equal(m.hour(), 3, 'should start at 00:00'); - - m.add(24, 'hour'); - - assert.equal(m.hour(), 4, 'adding 24 hours should disregard dst'); - - m.subtract(24, 'hour'); - - assert.equal(m.hour(), 3, 'subtracting 24 hours should disregard dst'); - - m.add(1, 'day'); - - assert.equal(m.hour(), 3, 'adding 1 day should have the same hour'); - - m.subtract(1, 'day'); - - assert.equal(m.hour(), 3, 'subtracting 1 day should have the same hour'); - - m.add(1, 'month'); - - assert.equal(m.hour(), 3, 'adding 1 month should have the same hour'); - - m.subtract(1, 'month'); - - assert.equal(m.hour(), 3, 'subtracting 1 month should have the same hour'); - - moment.updateOffset = oldOffset; -}); - -test('isDST', function (assert) { - var oldOffset = moment.updateOffset; - - moment.updateOffset = function (mom, keepTime) { - if (mom.month() > 2 && mom.month() < 9) { - mom.zone(-60, keepTime); - } else { - mom.zone(0, keepTime); - } - }; - - assert.ok(!moment().month(0).isDST(), 'Jan should not be summer dst'); - assert.ok(moment().month(6).isDST(), 'Jul should be summer dst'); - assert.ok(!moment().month(11).isDST(), 'Dec should not be summer dst'); - - moment.updateOffset = function (mom) { - if (mom.month() > 2 && mom.month() < 9) { - mom.zone(0); - } else { - mom.zone(-60); - } - }; - - assert.ok(moment().month(0).isDST(), 'Jan should be winter dst'); - assert.ok(!moment().month(6).isDST(), 'Jul should not be winter dst'); - assert.ok(moment().month(11).isDST(), 'Dec should be winter dst'); - - moment.updateOffset = oldOffset; -}); - test('zone names', function (assert) { test.expectedDeprecations(); assert.equal(moment().zoneAbbr(), '', 'Local zone abbr should be empty'); -- 2.47.2