Skip to content

Commit

Permalink
alternative fix to #1082 (#1083)
Browse files Browse the repository at this point in the history
  • Loading branch information
icambron committed Dec 8, 2021
1 parent 804bdb8 commit 3c92310
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 15 deletions.
11 changes: 7 additions & 4 deletions src/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,14 @@ function adjustTime(inst, dur) {

// helper useful in turning the results of parsing into real dates
// by handling the zone options
function parseDataToDateTime(parsed, parsedZone, opts, format, text) {
function parseDataToDateTime(parsed, parsedZone, opts, format, text, specificOffset) {
const { setZone, zone } = opts;
if (parsed && Object.keys(parsed).length !== 0) {
const interpretationZone = parsedZone || zone,
inst = DateTime.fromObject(parsed, {
...opts,
zone: interpretationZone,
specificOffset,
});
return setZone ? inst : inst.setZone(zone);
} else {
Expand Down Expand Up @@ -643,7 +644,9 @@ export default class DateTime {
}

const tsNow = Settings.now(),
offsetProvis = zoneToUse.offset(tsNow),
offsetProvis = !isUndefined(opts.specificOffset)
? opts.specificOffset
: zoneToUse.offset(tsNow),
normalized = normalizeObject(obj, normalizeUnit),
containsOrdinal = !isUndefined(normalized.ordinal),
containsGregorYear = !isUndefined(normalized.year),
Expand Down Expand Up @@ -821,11 +824,11 @@ export default class DateTime {
numberingSystem,
defaultToEN: true,
}),
[vals, parsedZone, invalid] = parseFromTokens(localeToUse, text, fmt);
[vals, parsedZone, specificOffset, invalid] = parseFromTokens(localeToUse, text, fmt);
if (invalid) {
return DateTime.invalid(invalid);
} else {
return parseDataToDateTime(vals, parsedZone, opts, `format ${fmt}`, text);
return parseDataToDateTime(vals, parsedZone, opts, `format ${fmt}`, text, specificOffset);
}
}

Expand Down
28 changes: 17 additions & 11 deletions src/impl/tokenParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,13 +318,17 @@ function dateTimeFromMatches(matches) {
}
};

let zone;
if (!isUndefined(matches.Z)) {
zone = new FixedOffsetZone(matches.Z);
} else if (!isUndefined(matches.z)) {
let zone = null;
let specificOffset;
if (!isUndefined(matches.z)) {
zone = IANAZone.create(matches.z);
} else {
zone = null;
}

if (!isUndefined(matches.Z)) {
if (!zone) {
zone = new FixedOffsetZone(matches.Z);
}
specificOffset = matches.Z;
}

if (!isUndefined(matches.q)) {
Expand Down Expand Up @@ -356,7 +360,7 @@ function dateTimeFromMatches(matches) {
return r;
}, {});

return [vals, zone];
return [vals, zone, specificOffset];
}

let dummyDateTimeCache = null;
Expand Down Expand Up @@ -411,17 +415,19 @@ export function explainFromTokens(locale, input, format) {
const [regexString, handlers] = buildRegex(units),
regex = RegExp(regexString, "i"),
[rawMatches, matches] = match(input, regex, handlers),
[result, zone] = matches ? dateTimeFromMatches(matches) : [null, null];
[result, zone, specificOffset] = matches
? dateTimeFromMatches(matches)
: [null, null, undefined];
if (hasOwnProperty(matches, "a") && hasOwnProperty(matches, "H")) {
throw new ConflictingSpecificationError(
"Can't include meridiem when specifying 24-hour format"
);
}
return { input, tokens, regex, rawMatches, matches, result, zone };
return { input, tokens, regex, rawMatches, matches, result, zone, specificOffset };
}
}

export function parseFromTokens(locale, input, format) {
const { result, zone, invalidReason } = explainFromTokens(locale, input, format);
return [result, zone, invalidReason];
const { result, zone, specificOffset, invalidReason } = explainFromTokens(locale, input, format);
return [result, zone, specificOffset, invalidReason];
}
125 changes: 125 additions & 0 deletions test/datetime/tokenParse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,131 @@ test("DateTime.fromFormat() with setZone parses fixed offsets and sets it", () =
}
});

test("DateTime.fromFormat() prefers IANA zone id", () => {
const i = DateTime.fromFormat(
"2021-11-12T09:07:13.000+08:00[Australia/Perth]",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ[z]",
{ setZone: true }
);
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(11);
expect(i.day).toBe(12);
expect(i.hour).toBe(9);
expect(i.minute).toBe(7);
expect(i.second).toBe(13);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(480); //+08:00
expect(i.zoneName).toBe("Australia/Perth");
});

test("DateTime.fromFormat() ignores numerical offsets when they conflict with the zone", () => {
// +11:00 is not a valid offset for the Australia/Perth time zone
const i = DateTime.fromFormat(
"2021-11-12T09:07:13.000+11:00[Australia/Perth]",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ[z]",
{ setZone: true }
);
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(11);
expect(i.day).toBe(12);
expect(i.hour).toBe(9);
expect(i.minute).toBe(7);
expect(i.second).toBe(13);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(480); //+08:00
expect(i.zoneName).toBe("Australia/Perth");
});

test("DateTime.fromFormat() ignores numerical offsets when they are are wrong right now", () => {
// DST is not in effect at this timestamp, so +10:00 is the correct offset
const i = DateTime.fromFormat(
"2021-10-03T01:30:00.000+11:00[Australia/Sydney]",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ[z]",
{ setZone: true }
);
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(10);
expect(i.day).toBe(3);
expect(i.hour).toBe(1);
expect(i.minute).toBe(30);
expect(i.second).toBe(0);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(600); //+10:00
expect(i.zoneName).toBe("Australia/Sydney");
});

test("DateTime.fromFormat() maintains offset that belongs to time zone during overlap", () => {
// On this day, 02:30 exists for both offsets, due to DST ending.
let i = DateTime.fromFormat(
"2021-04-04T02:30:00.000+11:00[Australia/Sydney]",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ[z]",
{ setZone: true }
);
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(4);
expect(i.day).toBe(4);
expect(i.hour).toBe(2);
expect(i.minute).toBe(30);
expect(i.second).toBe(0);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(660); //+11:00
expect(i.zoneName).toBe("Australia/Sydney");

i = DateTime.fromFormat(
"2021-04-04T02:30:00.000+10:00[Australia/Sydney]",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ[z]",
{ setZone: true }
);
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(4);
expect(i.day).toBe(4);
expect(i.hour).toBe(2);
expect(i.minute).toBe(30);
expect(i.second).toBe(0);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(600); //+10:00
expect(i.zoneName).toBe("Australia/Sydney");
});

test("DateTime.format() uses local zone when setZone is false and offset in input", () => {
const i = DateTime.fromFormat("2021-11-12T09:07:13.000+08:00", "yyyy-MM-dd'T'HH:mm:ss.SSSZZ", {
setZone: false,
});
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(11);
expect(i.day).toBe(11);
expect(i.hour).toBe(20);
expect(i.minute).toBe(7);
expect(i.second).toBe(13);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(-300);
expect(i.zoneName).toBe("America/New_York");
});

test("DateTime.format() uses local zone when setZone is false and zone id in input", () => {
const i = DateTime.fromFormat(
"2021-11-12T09:07:13.000+08:00[Australia/Perth]",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ[z]",
{ setZone: false }
);
expect(i.isValid).toBe(true);
expect(i.year).toBe(2021);
expect(i.month).toBe(11);
expect(i.day).toBe(11);
expect(i.hour).toBe(20);
expect(i.minute).toBe(7);
expect(i.second).toBe(13);
expect(i.millisecond).toBe(0);
expect(i.offset).toBe(-300);
expect(i.zoneName).toBe("America/New_York");
});

test("DateTime.fromFormat() parses localized macro tokens", () => {
const formatGroups = [
{
Expand Down

0 comments on commit 3c92310

Please sign in to comment.