Skip to content

Commit 710703b

Browse files
[feature] Add internal date-default hook for Moment Timezone (#6451)
[feature] Add internal date-default hook
1 parent 863ed94 commit 710703b

4 files changed

Lines changed: 277 additions & 17 deletions

File tree

‎src/lib/create/from-array.js‎

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { hooks } from '../utils/hooks';
22
import { createDate, createUTCDate } from './date-from-array';
33
import { daysInYear } from '../units/year';
44
import {
5-
weekOfYear,
5+
weekOfYearFromDate,
66
weeksInYear,
77
dayOfYearFromWeeks,
88
} from '../units/week-calendar-utils';
@@ -19,9 +19,46 @@ import { createLocal } from './local';
1919
import defaults from '../utils/defaults';
2020
import getParsingFlags from './parsing-flags';
2121

22-
function currentDateArray(config) {
22+
function currentDateArray(config, now, forWeek) {
23+
var hadWeekContext = Object.prototype.hasOwnProperty.call(
24+
config,
25+
'_isDefaultDatePartsForWeek'
26+
),
27+
weekContext = config._isDefaultDatePartsForWeek;
28+
2329
// hooks is actually the exported moment object
24-
var nowValue = new Date(hooks.now());
30+
config._isDefaultDatePartsForWeek = !!forWeek;
31+
try {
32+
return hooks._getDefaultDateParts(config, now, forWeek);
33+
} finally {
34+
if (hadWeekContext) {
35+
config._isDefaultDatePartsForWeek = weekContext;
36+
} else {
37+
delete config._isDefaultDatePartsForWeek;
38+
}
39+
}
40+
}
41+
42+
function currentDateNow(config) {
43+
var now = config._defaultDatePartsNow;
44+
45+
if (!now) {
46+
return hooks.now();
47+
}
48+
if (!now.hasValue) {
49+
now.value = hooks.now();
50+
now.hasValue = true;
51+
}
52+
return now.value;
53+
}
54+
55+
// Internal hook for extensions that supply omitted calendar fields.
56+
function getDefaultDateParts(config, now, forWeek) {
57+
var useWeekDefaults = forWeek || config._isDefaultDatePartsForWeek,
58+
nowValue = useWeekDefaults ? createLocal(now) : new Date(now);
59+
if (useWeekDefaults) {
60+
return [nowValue.year(), nowValue.month(), nowValue.date()];
61+
}
2562
if (config._useUTC) {
2663
return [
2764
nowValue.getUTCFullYear(),
@@ -32,6 +69,8 @@ function currentDateArray(config) {
3269
return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
3370
}
3471

72+
hooks._getDefaultDateParts = getDefaultDateParts;
73+
3574
// convert an array to a date.
3675
// the array should mirror the parameters below
3776
// note: all values past the year are optional and will default to the lowest possible value.
@@ -40,6 +79,7 @@ export function configFromArray(config) {
4079
var i,
4180
date,
4281
input = [],
82+
now,
4383
currentDate,
4484
expectedWeekday,
4585
yearToUse,
@@ -49,16 +89,24 @@ export function configFromArray(config) {
4989
return;
5090
}
5191

52-
currentDate = currentDateArray(config);
92+
if (
93+
config._a[YEAR] == null ||
94+
config._a[MONTH] == null ||
95+
config._a[DATE] == null
96+
) {
97+
now = currentDateNow(config);
98+
currentDate = currentDateArray(config, now);
99+
}
53100

54101
//compute day of the year from weeks and weekdays
55102
if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
56-
dayOfYearFromWeekInfo(config);
103+
dayOfYearFromWeekInfo(config, currentDateArray(config, now, true));
57104
}
58105

59106
//if the day of the year is set, figure out what it is
60107
if (config._dayOfYear != null) {
61-
yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
108+
yearToUse =
109+
config._a[YEAR] != null ? config._a[YEAR] : currentDate[YEAR];
62110

63111
if (
64112
config._dayOfYear > daysInYear(yearToUse) ||
@@ -132,7 +180,7 @@ export function configFromArray(config) {
132180
}
133181
}
134182

135-
function dayOfYearFromWeekInfo(config) {
183+
function dayOfYearFromWeekInfo(config, currentDate) {
136184
var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;
137185

138186
w = config._w;
@@ -147,7 +195,13 @@ function dayOfYearFromWeekInfo(config) {
147195
weekYear = defaults(
148196
w.GG,
149197
config._a[YEAR],
150-
weekOfYear(createLocal(), 1, 4).year
198+
weekOfYearFromDate(
199+
currentDate[YEAR],
200+
currentDate[MONTH],
201+
currentDate[DATE],
202+
1,
203+
4
204+
).year
151205
);
152206
week = defaults(w.W, 1);
153207
weekday = defaults(w.E, 1);
@@ -158,7 +212,13 @@ function dayOfYearFromWeekInfo(config) {
158212
dow = config._locale._week.dow;
159213
doy = config._locale._week.doy;
160214

161-
curWeek = weekOfYear(createLocal(), dow, doy);
215+
curWeek = weekOfYearFromDate(
216+
currentDate[YEAR],
217+
currentDate[MONTH],
218+
currentDate[DATE],
219+
dow,
220+
doy
221+
);
162222

163223
weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
164224

‎src/lib/create/from-string-and-array.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export function configFromStringAndArray(config) {
1313
currentScore,
1414
validFormatFound,
1515
bestFormatIsValid = false,
16+
defaultDatePartsNow = {},
1617
configfLen = config._f.length;
1718

1819
if (configfLen === 0) {
@@ -28,6 +29,7 @@ export function configFromStringAndArray(config) {
2829
if (config._useUTC != null) {
2930
tempConfig._useUTC = config._useUTC;
3031
}
32+
tempConfig._defaultDatePartsNow = defaultDatePartsNow;
3133
tempConfig._f = config._f[i];
3234
configFromStringAndFormat(tempConfig);
3335

‎src/lib/units/week-calendar-utils.js‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ export function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
3636
};
3737
}
3838

39-
export function weekOfYear(mom, dow, doy) {
40-
var weekOffset = firstWeekOffset(mom.year(), dow, doy),
41-
week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
39+
function weekOfYearFromDayOfYear(year, dayOfYear, dow, doy) {
40+
var weekOffset = firstWeekOffset(year, dow, doy),
41+
week = Math.floor((dayOfYear - weekOffset - 1) / 7) + 1,
4242
resWeek,
4343
resYear;
4444

4545
if (week < 1) {
46-
resYear = mom.year() - 1;
46+
resYear = year - 1;
4747
resWeek = week + weeksInYear(resYear, dow, doy);
48-
} else if (week > weeksInYear(mom.year(), dow, doy)) {
49-
resWeek = week - weeksInYear(mom.year(), dow, doy);
50-
resYear = mom.year() + 1;
48+
} else if (week > weeksInYear(year, dow, doy)) {
49+
resWeek = week - weeksInYear(year, dow, doy);
50+
resYear = year + 1;
5151
} else {
52-
resYear = mom.year();
52+
resYear = year;
5353
resWeek = week;
5454
}
5555

@@ -59,6 +59,20 @@ export function weekOfYear(mom, dow, doy) {
5959
};
6060
}
6161

62+
export function weekOfYear(mom, dow, doy) {
63+
return weekOfYearFromDayOfYear(mom.year(), mom.dayOfYear(), dow, doy);
64+
}
65+
66+
export function weekOfYearFromDate(year, month, date, dow, doy) {
67+
var dayOfYear =
68+
Math.round(
69+
(createUTCDate(year, month, date) - createUTCDate(year, 0, 1)) /
70+
864e5
71+
) + 1;
72+
73+
return weekOfYearFromDayOfYear(year, dayOfYear, dow, doy);
74+
}
75+
6276
export function weeksInYear(year, dow, doy) {
6377
var weekOffset = firstWeekOffset(year, dow, doy),
6478
weekOffsetNext = firstWeekOffset(year + 1, dow, doy);

‎src/test/moment/now.js‎

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,190 @@ test('now - custom value', function (assert) {
7272
}
7373
});
7474

75+
test('custom default date parts', function (assert) {
76+
var oldNow = moment.now,
77+
oldGetDefaultDateParts = moment._getDefaultDateParts,
78+
oldLocale = moment.locale(),
79+
customTime = Date.UTC(2025, 0, 2, 1),
80+
calls = 0,
81+
weekCalls = 0,
82+
receivedConfig,
83+
receivedNow,
84+
receivedThis;
85+
86+
moment.now = function () {
87+
return customTime;
88+
};
89+
moment.locale('en');
90+
moment._getDefaultDateParts = function (config, now, forWeek) {
91+
calls++;
92+
receivedConfig = config;
93+
receivedNow = now;
94+
receivedThis = this;
95+
if (forWeek) {
96+
weekCalls++;
97+
}
98+
if (!config._useUTC) {
99+
return [2024, 11, 25];
100+
}
101+
return [2030, 6, 1];
102+
};
103+
104+
try {
105+
assert.equal(
106+
moment('01:23', 'HH:mm').format('YYYY-MM-DD HH:mm'),
107+
'2024-12-25 01:23',
108+
'custom date parts should supply omitted local fields'
109+
);
110+
assert.equal(receivedNow, customTime, 'should receive the sampled now');
111+
assert.strictEqual(
112+
receivedThis,
113+
moment,
114+
'should use Moment as the receiver'
115+
);
116+
assert.equal(
117+
receivedConfig._useUTC,
118+
false,
119+
'should receive the parsing config'
120+
);
121+
assert.equal(
122+
moment('1', 'e').format('YYYY-MM-DD'),
123+
'2024-12-23',
124+
'locale week defaults should use the custom date parts'
125+
);
126+
assert.equal(
127+
moment('1', 'E').format('YYYY-MM-DD'),
128+
'2024-01-01',
129+
'ISO week-year defaults should use the custom date parts'
130+
);
131+
assert.equal(
132+
moment.utc('01:23', 'HH:mm').format('YYYY-MM-DD HH:mm'),
133+
'2030-07-01 01:23',
134+
'custom date parts should supply omitted UTC fields'
135+
);
136+
assert.equal(
137+
moment.utc('1', 'e').format('YYYY-MM-DD'),
138+
'2030-07-01',
139+
'UTC locale week defaults should use the custom date parts'
140+
);
141+
assert.equal(
142+
moment.utc('1', 'E').format('YYYY-MM-DD'),
143+
'2029-12-31',
144+
'UTC ISO week-year defaults should use the custom date parts'
145+
);
146+
assert.equal(
147+
moment('1 -08:00', 'e Z').utc().format('YYYY-MM-DD'),
148+
'2030-07-01',
149+
'explicit-offset week defaults should use the custom date parts'
150+
);
151+
assert.equal(
152+
moment().valueOf(),
153+
customTime,
154+
'no-input construction should retain current-instant semantics'
155+
);
156+
assert.equal(
157+
calls,
158+
12,
159+
'no-input construction should not request defaults'
160+
);
161+
assert.equal(weekCalls, 5, 'week parsing should identify hook calls');
162+
} finally {
163+
moment.now = oldNow;
164+
moment._getDefaultDateParts = oldGetDefaultDateParts;
165+
moment.locale(oldLocale);
166+
}
167+
});
168+
169+
test('default week date preserves updateOffset', function (assert) {
170+
var oldNow = moment.now,
171+
oldGetDefaultDateParts = moment._getDefaultDateParts,
172+
oldUpdateOffset = moment.updateOffset,
173+
oldLocale = moment.locale(),
174+
nowCalls = 0,
175+
calls = 0;
176+
177+
moment.now = function () {
178+
nowCalls++;
179+
return Date.UTC(2025, 0, 5);
180+
};
181+
moment.locale('en');
182+
moment._getDefaultDateParts = function (config, now) {
183+
return oldGetDefaultDateParts.call(this, config, now);
184+
};
185+
moment.updateOffset = function (mom) {
186+
calls++;
187+
if (calls === 1) {
188+
mom._d = new Date(2024, 11, 25);
189+
}
190+
};
191+
192+
try {
193+
assert.equal(
194+
moment.utc('1', 'e').format('YYYY-MM-DD'),
195+
'2024-12-23',
196+
'two-argument wrappers should retain UTC updateOffset behavior'
197+
);
198+
assert.ok(calls > 0, 'should create an offset-adjusted current moment');
199+
200+
calls = 0;
201+
assert.equal(
202+
moment('1 -08:00', 'e Z').utc().format('YYYY-MM-DD'),
203+
'2024-12-23',
204+
'two-argument wrappers should retain offset updateOffset behavior'
205+
);
206+
assert.ok(calls > 0, 'should adjust the offset current moment');
207+
assert.equal(nowCalls, 2, 'should sample now once per parse');
208+
} finally {
209+
moment.now = oldNow;
210+
moment._getDefaultDateParts = oldGetDefaultDateParts;
211+
moment.updateOffset = oldUpdateOffset;
212+
moment.locale(oldLocale);
213+
}
214+
});
215+
216+
test('format arrays share one current instant', function (assert) {
217+
var oldNow = moment.now,
218+
calls = 0;
219+
220+
moment.now = function () {
221+
calls++;
222+
return Date.UTC(2025, 0, 5);
223+
};
224+
225+
try {
226+
moment('01:23', ['HH:mm', 'H:mm']);
227+
assert.equal(calls, 1, 'should sample now once for every candidate');
228+
} finally {
229+
moment.now = oldNow;
230+
}
231+
});
232+
233+
test('complete dates with day of year do not need current date', function (assert) {
234+
var oldNow = moment.now,
235+
calls = 0;
236+
237+
moment.now = function () {
238+
calls++;
239+
return Date.UTC(2025, 0, 5);
240+
};
241+
242+
try {
243+
assert.equal(
244+
moment('2020-01-02 123', 'YYYY-MM-DD DDD').format('YYYY-MM-DD'),
245+
'2020-05-02',
246+
'day of year should take precedence over the calendar date'
247+
);
248+
assert.equal(
249+
moment('2020-01-02 123', ['YYYY-MM-DD DDDD']).format('YYYY-MM-DD'),
250+
'2020-05-02',
251+
'format arrays should accept a complete date with day of year'
252+
);
253+
assert.equal(calls, 0, 'complete dates should not sample now');
254+
} finally {
255+
moment.now = oldNow;
256+
}
257+
});
258+
75259
test('empty object, empty array', function (assert) {
76260
function assertIsNow(gen, msg) {
77261
var before = +new Date(),

0 commit comments

Comments
 (0)