Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ 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, specificOffset) {
export function parseDataToDateTime(parsed, parsedZone, opts, format, text, specificOffset) {
const { setZone, zone } = opts;
if ((parsed && Object.keys(parsed).length !== 0) || parsedZone) {
const interpretationZone = parsedZone || zone,
Expand Down Expand Up @@ -797,7 +797,7 @@ export default class DateTime {
const normalized = normalizeObject(obj, normalizeUnitWithLocalWeeks);
const { minDaysInFirstWeek, startOfWeek } = usesLocalWeekValues(normalized, loc);

const tsNow = Settings.now(),
const tsNow = opts.overrideNow ?? Settings.now(),
offsetProvis = !isUndefined(opts.specificOffset)
? opts.specificOffset
: zoneToUse.offset(tsNow),
Expand Down
33 changes: 33 additions & 0 deletions src/impl/regexParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,39 @@ export function parseISODate(s) {
);
}

// ISO Interval parsing

// Note: Do not optimize the outer non-capturing group, it is necessary, because the
// regex is combined with other regexes and contains |
const partialIsoIntervalEndDate = /(?:(?:(\d\d)-)?(\d\d)?|(?:W(\d\d)-)?(\d)|(\d{3}))/;
const isoIntervalEndDateTime = combineRegexes(partialIsoIntervalEndDate, isoTimeExtensionRegex);

const extractPartialIsoIntervalEndDate = simpleParse(
"month",
"day",
"weekNumber",
"weekDay",
"ordinal"
);

const extractISOIntervalPartialDateAndTime = combineExtractors(
extractPartialIsoIntervalEndDate,
extractISOTime,
extractISOOffset,
extractIANAZone
);

export function parseISOIntervalEnd(s) {
return parse(
s,
[isoIntervalEndDateTime, extractISOIntervalPartialDateAndTime],
[isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset],
[isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset],
[isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDateAndTime],
[isoTimeCombinedRegex, extractISOTimeAndOffset]
);
}

export function parseRFC2822Date(s) {
return parse(preprocessRFC2822(s), [rfc2822, extractRFC2822]);
}
Expand Down
25 changes: 22 additions & 3 deletions src/interval.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import DateTime, { friendlyDateTime } from "./datetime.js";
import DateTime, { friendlyDateTime, parseDataToDateTime } from "./datetime.js";
import Duration from "./duration.js";
import Settings from "./settings.js";
import { InvalidArgumentError, InvalidIntervalError } from "./errors.js";
import Invalid from "./impl/invalid.js";
import Formatter from "./impl/formatter.js";
import * as Formats from "./impl/formats.js";
import { parseISOIntervalEnd } from "./impl/regexParser.js";

const INVALID = "Invalid Interval";

Expand Down Expand Up @@ -134,24 +135,42 @@ export default class Interval {
* @return {Interval}
*/
static fromISO(text, opts) {
const { zone, setZone, ...restOpts } = opts || {};
const [s, e] = (text || "").split("/", 2);
if (s && e) {
let start, startIsValid;
try {
start = DateTime.fromISO(s, opts);
// we need to know the zone that was used in the string, so that we can
// default to it when parsing end, therefor use setZone: true
start = DateTime.fromISO(s, { ...restOpts, zone, setZone: true });
startIsValid = start.isValid;
} catch (e) {
startIsValid = false;
}

let end, endIsValid;
try {
end = DateTime.fromISO(e, opts);
const [vals, parsedZone] = parseISOIntervalEnd(e);
const endParseOpts = {
...restOpts,
overrideNow: startIsValid ? start.valueOf() : null,
zone: startIsValid ? start.zone : zone,
setZone: true,
};
end = parseDataToDateTime(vals, parsedZone, endParseOpts, "ISO 8601 Interval end", e);
endIsValid = end.isValid;
} catch (e) {
endIsValid = false;
}

// if we overrode the user's choice for setZone earlier, make up for it now
if (startIsValid && !setZone) {
start = start.setZone(zone);
}
if (endIsValid && !setZone) {
end = end.setZone(zone);
}

if (startIsValid && endIsValid) {
return Interval.fromDateTimes(start, end);
}
Expand Down
179 changes: 179 additions & 0 deletions test/interval/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,182 @@ test.each(badInputs)("Interval.fromISO will return invalid for [%s]", (s) => {
expect(i.isValid).toBe(false);
expect(i.invalidReason).toBe("unparsable");
});

describe("Interval.fromISO defaults missing values in end to start", () => {
test("Gregorian, end just time", () => {
const i = Interval.fromISO("1988-04-15T09/15:30");
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-04-15T15:30:00.000-04:00");
});
test("Gregorian, end just time and zone", () => {
const i = Interval.fromISO("1988-04-15T09/15:30-07:00");
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-04-15T18:30:00.000-04:00");
});
test("Gregorian, end just day", () => {
const i = Interval.fromISO("1988-04-15T09/17");
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-04-17T00:00:00.000-04:00");
});
test("Gregorian, end day and time", () => {
const i = Interval.fromISO("1988-04-15T09/17T15:30");
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-04-17T15:30:00.000-04:00");
});
test("Gregorian, end month and day", () => {
const i = Interval.fromISO("1988-04-15T09/05-17");
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-05-17T00:00:00.000-04:00");
});
test("Gregorian, end month, day and time", () => {
const i = Interval.fromISO("1988-04-15T09/05-17T15:30");
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-05-17T15:30:00.000-04:00");
});
test("Gregorian with zone in options and partial date", () => {
const i = Interval.fromISO("1988-04-15T09/19", { zone: "UTC-06:00" });
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-06:00");
expect(i.end.toISO()).toBe("1988-04-19T00:00:00.000-06:00");
});
test("Gregorian with zone in options and partial date and time", () => {
const i = Interval.fromISO("1988-04-15T09/19T13:00", { zone: "UTC-06:00" });
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-06:00");
expect(i.end.toISO()).toBe("1988-04-19T13:00:00.000-06:00");
});
test("Gregorian with zone in options and full date and time", () => {
const i = Interval.fromISO("1988-04-15T09/1989-03-01T13:00", { zone: "UTC-06:00" });
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-06:00");
expect(i.end.toISO()).toBe("1989-03-01T13:00:00.000-06:00");
});
test("Gregorian with zone in options and end zone", () => {
const i = Interval.fromISO("1988-04-15T09/16T15:00-07:00", { zone: "UTC-06:00" });
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-06:00");
expect(i.end.toISO()).toBe("1988-04-16T16:00:00.000-06:00");
});
test("Gregorian with zone in options, setZone and end zone", () => {
const i = Interval.fromISO("1988-04-15T09/16T15:00-07:00", {
zone: "UTC-06:00",
setZone: true,
});
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000-06:00");
expect(i.end.toISO()).toBe("1988-04-16T15:00:00.000-07:00");
});
test("Gregorian with start zone", () => {
const i = Interval.fromISO("1988-04-15T09:00:00+01:00/17T15:30");
expect(i.start.toISO()).toBe("1988-04-15T04:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-04-17T10:30:00.000-04:00");
});
test("Gregorian with start zone and zone in options", () => {
const i = Interval.fromISO("1988-04-15T09:00:00+01:00/15T15:00", { zone: "UTC-06:00" });
expect(i.start.toISO()).toBe("1988-04-15T02:00:00.000-06:00");
expect(i.end.toISO()).toBe("1988-04-15T08:00:00.000-06:00");
});
test("Gregorian with start zone and setZone", () => {
const i = Interval.fromISO("1988-04-15T09:00:00+01:00/15T15:00", { setZone: true });
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000+01:00");
expect(i.end.toISO()).toBe("1988-04-15T15:00:00.000+01:00");
});
test("Gregorian with two zones", () => {
const i = Interval.fromISO("1988-04-15T09:00:00+01:00/15T16:00+02:00");
expect(i.start.toISO()).toBe("1988-04-15T04:00:00.000-04:00");
expect(i.end.toISO()).toBe("1988-04-15T10:00:00.000-04:00");
});
test("Gregorian with two zones and setZone", () => {
const i = Interval.fromISO("1988-04-15T09:00:00+01:00/15T16:00+02:00", { setZone: true });
expect(i.start.toISO()).toBe("1988-04-15T09:00:00.000+01:00");
expect(i.end.toISO()).toBe("1988-04-15T16:00:00.000+02:00");
});

// Week dates
test("Week, end just time", () => {
const i = Interval.fromISO("2025-W20-1T09/15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-12T15:30:00.000-04:00");
});
test("Week, end just time and zone", () => {
const i = Interval.fromISO("2025-W20-1T09/15:30-07:00");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-12T18:30:00.000-04:00");
});
test("Week, end week day", () => {
const i = Interval.fromISO("2025-W20-1T09/3T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-14T15:30:00.000-04:00");
});
test("Week, end week number and week day", () => {
const i = Interval.fromISO("2025-W20-1T09/W21-3T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-21T15:30:00.000-04:00");
});

// Ordinal dates
test("Ordinal, end just time", () => {
const i = Interval.fromISO("2025-132T09/15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-12T15:30:00.000-04:00");
});
test("Ordinal, end just time and zone", () => {
const i = Interval.fromISO("2025-132T09/15:30-07:00");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-12T18:30:00.000-04:00");
});
test("Ordinal, end with ordinal", () => {
const i = Interval.fromISO("2025-132T09/135T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});

// Mixed
test("Gregorian, end just weekday", () => {
const i = Interval.fromISO("2025-05-12T09/4T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});
test("Gregorian, end weekNumber and weekday", () => {
const i = Interval.fromISO("2025-05-12T09/W21-1T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-19T15:30:00.000-04:00");
});
test("Gregorian, end just ordinal", () => {
const i = Interval.fromISO("2025-05-12T09/135T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});

test("Week date, end just day", () => {
const i = Interval.fromISO("2025-W20-1T09/15T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});
test("Week date, end month and day", () => {
const i = Interval.fromISO("2025-W20-1T09/06-15T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-06-15T15:30:00.000-04:00");
});
test("Week date, end just ordinal", () => {
const i = Interval.fromISO("2025-W20-1T09/135T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});

test("Ordinal, end just day", () => {
const i = Interval.fromISO("2025-132T09/15T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});
test("Ordinal, end month and day", () => {
const i = Interval.fromISO("2025-132T09/06-15T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-06-15T15:30:00.000-04:00");
});
test("Ordinal, end just weekday", () => {
const i = Interval.fromISO("2025-132T09/4T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-15T15:30:00.000-04:00");
});
test("Ordinal, end weekNumber and weekday", () => {
const i = Interval.fromISO("2025-132T09/W21-1T15:30");
expect(i.start.toISO()).toBe("2025-05-12T09:00:00.000-04:00");
expect(i.end.toISO()).toBe("2025-05-19T15:30:00.000-04:00");
});
});