datetoken
Version:
Parse relative datetime tokens into date objects
677 lines (661 loc) • 20.4 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/token/token.ts
function lookupIdentifier(ident) {
if (keywords.hasOwnProperty(ident)) {
return keywords[ident] ?? "ILLEGAL" /* ILLEGAL */;
}
return "ILLEGAL" /* ILLEGAL */;
}
var keywords, Token, NOW;
var init_token = __esm({
"src/token/token.ts"() {
"use strict";
keywords = {
now: "NOW" /* NOW */,
s: "MODIFIER" /* MODIFIER */,
m: "MODIFIER" /* MODIFIER */,
h: "MODIFIER" /* MODIFIER */,
d: "MODIFIER" /* MODIFIER */,
w: "MODIFIER" /* MODIFIER */,
M: "MODIFIER" /* MODIFIER */,
Y: "MODIFIER" /* MODIFIER */,
Q: "MODIFIER" /* MODIFIER */,
Q1: "MODIFIER" /* MODIFIER */,
Q2: "MODIFIER" /* MODIFIER */,
Q3: "MODIFIER" /* MODIFIER */,
Q4: "MODIFIER" /* MODIFIER */,
bw: "MODIFIER" /* MODIFIER */,
mon: "MODIFIER" /* MODIFIER */,
tue: "MODIFIER" /* MODIFIER */,
thu: "MODIFIER" /* MODIFIER */,
fri: "MODIFIER" /* MODIFIER */,
sat: "MODIFIER" /* MODIFIER */,
sun: "MODIFIER" /* MODIFIER */
};
Token = class {
type;
literal;
constructor(tokenType, tokenLiteral) {
this.type = tokenType;
this.literal = tokenLiteral;
}
};
NOW = new Token("NOW" /* NOW */, "now");
}
});
// src/token/index.ts
var init_token2 = __esm({
"src/token/index.ts"() {
"use strict";
init_token();
}
});
// src/ast/ast.ts
function newNowExpression() {
return new NowExpression(new Token("NOW" /* NOW */, "now"));
}
var dateFn, daysOfWeek, NowExpression, ModifierExpression, SnapExpression, AmountModifiers, SnapModifiers;
var init_ast = __esm({
"src/ast/ast.ts"() {
"use strict";
dateFn = __toESM(require("date-fns"), 1);
init_token2();
daysOfWeek = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
NowExpression = class {
token;
constructor(token) {
this.token = token;
}
operate(date) {
return date;
}
toString() {
return this.token.literal;
}
toJSON() {
return { type: "now" };
}
};
ModifierExpression = class {
token;
amount;
operator;
modifier;
constructor(token, amount = 1, operator, modifier) {
this.token = token;
this.amount = amount;
this.operator = operator;
this.modifier = modifier;
}
operate(date) {
switch (this.operator) {
case "+" /* PLUS */:
switch (this.modifier) {
case "s":
return dateFn.addSeconds(date, this.amount);
case "m":
return dateFn.addMinutes(date, this.amount);
case "h":
return dateFn.addHours(date, this.amount);
case "d":
return dateFn.addDays(date, this.amount);
case "w":
return dateFn.addWeeks(date, this.amount);
case "M":
return dateFn.addMonths(date, this.amount);
case "Y":
return dateFn.addYears(date, this.amount);
}
break;
case "-" /* MINUS */:
switch (this.modifier) {
case "s":
return dateFn.subSeconds(date, this.amount);
case "m":
return dateFn.subMinutes(date, this.amount);
case "h":
return dateFn.subHours(date, this.amount);
case "d":
return dateFn.subDays(date, this.amount);
case "w":
return dateFn.subWeeks(date, this.amount);
case "M":
return dateFn.subMonths(date, this.amount);
case "Y":
return dateFn.subYears(date, this.amount);
}
break;
}
return date;
}
toString() {
return `${this.operator}${this.amount}${this.modifier}`;
}
toJSON() {
return { type: "amount", amount: this.amount, modifier: this.modifier, operator: this.operator };
}
};
SnapExpression = class {
token;
modifier;
operator;
constructor(token, modifier, operator) {
this.token = token;
this.modifier = modifier;
this.operator = operator;
}
operate(date) {
switch (this.operator) {
case "/" /* SLASH */:
switch (this.modifier) {
case "s":
return dateFn.startOfSecond(date);
case "m":
return dateFn.startOfMinute(date);
case "h":
return dateFn.startOfHour(date);
case "d":
return dateFn.startOfDay(date);
case "w":
case "bw":
return dateFn.startOfWeek(date);
case "mon":
case "tue":
case "wed":
case "thu":
case "fri":
case "sat":
case "sun": {
const weekDayOrdinal = daysOfWeek.indexOf(this.modifier);
const todayOrdinal = dateFn.getDay(date);
const delta = ((todayOrdinal - weekDayOrdinal) % 7 + 7) % 7;
return dateFn.subDays(date, delta);
}
case "M":
return dateFn.startOfMonth(date);
case "Y":
return dateFn.startOfYear(date);
case "Q":
const month = dateFn.getMonth(date);
const quarter = Math.floor(month / 3);
return dateFn.setMonth(dateFn.startOfYear(date), 3 * quarter);
case "Q1":
return dateFn.startOfYear(date);
case "Q2":
return dateFn.setMonth(dateFn.startOfYear(date), 4 - 1);
case "Q3":
return dateFn.setMonth(dateFn.startOfYear(date), 7 - 1);
case "Q4":
return dateFn.setMonth(dateFn.startOfYear(date), 10 - 1);
}
break;
case "@" /* AT */:
switch (this.modifier) {
case "s":
return dateFn.endOfSecond(date);
case "m":
return dateFn.endOfMinute(date);
case "h":
return dateFn.endOfHour(date);
case "d":
return dateFn.endOfDay(date);
case "w":
return dateFn.endOfWeek(date);
case "M":
return dateFn.endOfMonth(date);
case "Y":
return dateFn.endOfYear(date);
case "bw": {
if (dateFn.isThisWeek(date) && !dateFn.isWeekend(date)) {
return date;
}
return dateFn.endOfDay(dateFn.addDays(dateFn.startOfWeek(date), 5));
}
case "mon":
case "tue":
case "wed":
case "thu":
case "fri":
case "sat":
case "sun": {
const weekDayOrdinal = daysOfWeek.indexOf(this.modifier);
const todayOrdinal = dateFn.getDay(date);
const delta = ((weekDayOrdinal - todayOrdinal) % 7 + 7) % 7;
return dateFn.addDays(date, delta);
}
case "Q":
const month = dateFn.getMonth(date);
const quarter = Math.floor(month / 3);
return dateFn.endOfMonth(dateFn.setMonth(date, 3 * quarter + 2));
case "Q1":
return dateFn.endOfMonth(dateFn.setMonth(date, 3 - 1));
case "Q2":
return dateFn.endOfMonth(dateFn.setMonth(date, 6 - 1));
case "Q3":
return dateFn.endOfMonth(dateFn.setMonth(date, 9 - 1));
case "Q4":
return dateFn.endOfMonth(dateFn.setMonth(date, 12 - 1));
}
break;
}
return date;
}
toString() {
return `${this.operator}${this.modifier}`;
}
toJSON() {
return { type: "snap", modifier: this.modifier, operator: this.operator };
}
};
((AmountModifiers2) => {
const values = ["s", "m", "h", "d", "w", "M", "Y"];
AmountModifiers2.valuesString = `(${values.map((v) => `"${v}"`).join(",")})`;
function checkModifier(modifier) {
return values.includes(modifier);
}
AmountModifiers2.checkModifier = checkModifier;
})(AmountModifiers || (AmountModifiers = {}));
((SnapModifiers2) => {
const values = [
"s",
"m",
"h",
"d",
"w",
"bw",
"M",
"Y",
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun",
"Q",
"Q1",
"Q2",
"Q3",
"Q4"
];
SnapModifiers2.valuesString = `(${values.map((v) => `"${v}"`).join(",")})`;
function checkModifier(modifier) {
return values.includes(modifier);
}
SnapModifiers2.checkModifier = checkModifier;
})(SnapModifiers || (SnapModifiers = {}));
}
});
// src/ast/index.ts
var init_ast2 = __esm({
"src/ast/index.ts"() {
"use strict";
init_ast();
}
});
// src/exceptions/index.ts
var InvalidTokenError;
var init_exceptions = __esm({
"src/exceptions/index.ts"() {
"use strict";
InvalidTokenError = class extends Error {
};
}
});
// src/lexer/lexer.ts
function isDigit(payload) {
return /^\d+$/.test(payload);
}
function isLetter(payload) {
return /^\w+$/.test(payload);
}
var Lexer;
var init_lexer = __esm({
"src/lexer/lexer.ts"() {
"use strict";
init_token2();
Lexer = class {
invalid;
position;
readPosition;
input;
currentChar;
constructor(input = "") {
this.invalid = !input || "string" !== typeof input || input.trim().length < 2;
if (this.invalid) {
this.position = this.readPosition = 0;
this.input = "";
this.currentChar = "";
} else {
this.position = this.readPosition = 0;
this.input = input.trim();
this.currentChar = "";
this.readChar();
}
}
isInvalid() {
return this.invalid;
}
nextToken() {
let token;
if (this.currentChar === "") {
return new Token("" /* END */, this.currentChar);
} else if (this.currentChar === "+") {
token = new Token("+" /* PLUS */, this.currentChar);
} else if (this.currentChar === "-") {
token = new Token("-" /* MINUS */, this.currentChar);
} else if (this.currentChar === "/") {
token = new Token("/" /* SLASH */, this.currentChar);
} else if (this.currentChar === "@") {
token = new Token("@" /* AT */, this.currentChar);
} else if (isDigit(this.currentChar)) {
return new Token("NUMBER" /* NUMBER */, this.readNumber());
} else if (isLetter(this.currentChar)) {
const literal = this.readWord();
return new Token(lookupIdentifier(literal), literal);
} else {
token = new Token("ILLEGAL" /* ILLEGAL */, this.currentChar);
}
this.readChar();
return token;
}
readChar() {
if (this.position >= this.input.length) {
this.readPosition = 0;
this.currentChar = "";
} else {
this.currentChar = this.input[this.readPosition] ?? "";
this.position = this.readPosition;
}
this.readPosition++;
}
peekChar() {
if (this.position >= this.input.length) {
return "";
}
return this.input[this.readPosition] ?? "";
}
readNumber() {
const pos = this.position;
while (isDigit(this.currentChar)) {
this.readChar();
}
return this.input.substring(pos, this.position);
}
readWord() {
const pos = this.position;
while (isLetter(this.currentChar) || isDigit(this.currentChar)) {
this.readChar();
}
return this.input.substring(pos, this.position);
}
};
}
});
// src/lexer/index.ts
var init_lexer2 = __esm({
"src/lexer/index.ts"() {
"use strict";
init_lexer();
}
});
// src/parser/parser.ts
var Parser;
var init_parser = __esm({
"src/parser/parser.ts"() {
"use strict";
init_ast2();
init_ast2();
init_token2();
Parser = class {
lexer;
errors;
currentToken;
peekToken;
constructor(lexer) {
this.errors = [];
this.lexer = lexer;
}
getErrors() {
return this.errors;
}
nextToken() {
this.currentToken = this.peekToken;
this.peekToken = this.lexer.nextToken();
}
parse() {
if (this.lexer.isInvalid()) {
this.addError("Invalid token");
}
const nodes = [];
this.nextToken();
this.nextToken();
while (this.currentToken.type !== "" /* END */) {
const node = this.parseExpression();
if (node) {
nodes.push(node);
}
this.nextToken();
}
return nodes;
}
addError(message) {
this.errors.push(message);
}
parseNowExpression() {
return newNowExpression();
}
parseModifierExpression() {
let curToken = this.currentToken;
const operator = curToken.literal;
let amount = 1;
let modifier;
this.nextToken();
curToken = this.currentToken;
if (curToken.type === "NUMBER" /* NUMBER */) {
amount = parseInt(curToken.literal, 10);
this.nextToken();
}
curToken = this.currentToken;
if (curToken.type === "MODIFIER" /* MODIFIER */) {
modifier = curToken.literal;
if (!AmountModifiers.checkModifier(modifier)) {
this.addError(
`Expected modifier literal as any of "${AmountModifiers.valuesString}", got "${curToken.literal}"`
);
}
return new ModifierExpression(curToken, amount, operator, modifier);
} else {
this.addError(`Expected NUMBER or MODIFIER, got "${curToken.literal}"`);
}
return void 0;
}
parseSnapExpression() {
let curToken = this.currentToken;
const operator = curToken.literal;
this.nextToken();
curToken = this.currentToken;
if (curToken.type !== "MODIFIER" /* MODIFIER */) {
this.addError(`Expected snap modifier literal, got "${curToken.literal}"`);
} else if (!SnapModifiers.checkModifier(curToken.literal)) {
this.addError(
`Expected snap modifier literal as any of "${SnapModifiers.valuesString}", got "${curToken.literal}"`
);
}
const modifier = curToken.literal;
return new SnapExpression(curToken, modifier, operator);
}
parseExpression() {
const curToken = this.currentToken;
if ("NOW" /* NOW */ === curToken.type) {
return this.parseNowExpression();
} else if ("+" /* PLUS */ === curToken.type || "-" /* MINUS */ === curToken.type) {
return this.parseModifierExpression();
} else if ("/" /* SLASH */ === curToken.type || "@" /* AT */ === curToken.type) {
return this.parseSnapExpression();
} else if ("ILLEGAL" /* ILLEGAL */ === curToken.type) {
this.addError(`Illegal operator: "${curToken.literal}"`);
}
return void 0;
}
};
}
});
// src/parser/index.ts
var init_parser2 = __esm({
"src/parser/index.ts"() {
"use strict";
init_parser();
}
});
// src/utils/time.ts
var Clock;
var init_time = __esm({
"src/utils/time.ts"() {
"use strict";
Clock = class _Clock {
getTime() {
return /* @__PURE__ */ new Date();
}
forward(millis) {
return /* @__PURE__ */ new Date();
}
static create() {
return new _Clock();
}
};
}
});
// src/models/models.ts
var Token3;
var init_models = __esm({
"src/models/models.ts"() {
"use strict";
init_ast2();
init_exceptions();
init_lexer2();
init_parser2();
init_time();
Token3 = class _Token {
get at() {
return this.startAt || this.clock.getTime();
}
set at(value) {
this.startAt = value;
}
get nodes() {
return this.expressionNodes;
}
get isSnapped() {
return this.expressionNodes.some((node) => node instanceof SnapExpression);
}
get isModified() {
return this.expressionNodes.some((node) => node instanceof ModifierExpression);
}
static fromString(value, at, clock) {
const lexer = new Lexer(value);
const parser = new Parser(lexer);
const nodes = parser.parse();
if (parser.getErrors().length > 0) {
throw new InvalidTokenError(parser.getErrors().join("\n"));
}
const token = new _Token(nodes);
if (at) {
token.startAt = at;
}
if (clock) {
token.clock = clock;
}
return token;
}
expressionNodes;
startAt;
clock = Clock.create();
constructor(nodes, at, clock = Clock.create()) {
this.expressionNodes = nodes;
if (at !== void 0) {
this.startAt = at;
}
this.clock = clock;
}
toDate() {
return this.expressionNodes.reduce((acc, node) => node.operate(acc), this.at);
}
toString() {
return this.expressionNodes.map((n) => n.toString()).join("");
}
toJSON() {
return this.nodes.map((node) => node.toJSON());
}
};
}
});
// src/models/index.ts
var models_exports = {};
__export(models_exports, {
Token: () => Token3,
TokenModel: () => Token3
});
var init_models2 = __esm({
"src/models/index.ts"() {
"use strict";
init_models();
}
});
// src/index.ts
var index_exports = {};
__export(index_exports, {
Token: () => Token3,
datetoken: () => datetoken,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
// src/utils/utils.ts
init_models2();
function tokenToDate(token, at, clock) {
return Token3.fromString(token, at, clock).toDate();
}
// src/index.ts
init_models2();
var datetoken = tokenToDate;
var index_default = tokenToDate;
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = tokenToDate;
module.exports.datetoken = tokenToDate;
module.exports.Token = (init_models2(), __toCommonJS(models_exports)).Token;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Token,
datetoken
});