Skip to content

Instantly share code, notes, and snippets.

@ljnmedium
Last active February 28, 2022 18:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ljnmedium/626579cd9c4b38686b7f189466c4c350 to your computer and use it in GitHub Desktop.
Save ljnmedium/626579cd9c4b38686b7f189466c4c350 to your computer and use it in GitHub Desktop.
export interface TempExpDocument {
id: string;
text: string;
predString?: string; // Prediction string form the model
preds?: Array<NormalizedTempExp>; // TempExps in tuple format
absPreds?: Array<any>; // TempExps in absolute format for the referenced date
chartData?: any;
}
export class ValUnitTuple {
value: number;
unit: string;
constructor(unit: string, value: number) {
this.unit = unit;
this.value = value;
}
}
export class NormalizedTempExp {
type: 'ABS'|'REL'|'DUR'|'FREQ'|'NONE';
mode?: 'DIR'|'IND'|undefined;
value: Array<ValUnitTuple>;
anchor?: 'FROM'|'TO'|'BEFORE'|'AFTER'|undefined;
tense?: 'PAST'|'FUTURE'|'PREV'|'NEXT'|'CURRENT'|undefined;
partial?: 'START'|'MID'|'END'|undefined;
approximation?: 'LESS'|'MORE'|'APPROX'|undefined;
ordinal?: 'FIRST'|'LAST'|undefined;
constructor() {
this.type = 'NONE';
this.value = [];
}
public setAttribute(key: string, value: any) {
if (key == 'type') {
this.type = value;
} else if (key == 'mode') {
this.mode = value;
} else if (key == 'value') {
this.value = value;
} else if (key == 'anchor') {
this.anchor = value;
} else if (key == 'tense') {
this.tense = value;
} else if (key == 'partial') {
this.partial = value;
} else if (key == 'approximation') {
this.approximation = value;
} else if (key == 'ordinal') {
this.ordinal = value;
}
}
}
export const KEYWORD_TO_ATTR: {[key: string]: string} = {
ABS: 'type',
REL: 'type',
DUR: 'type',
FREQ: 'type',
NONE: 'type',
DIR: 'mode',
IND: 'mode',
FROM: 'anchor',
TO: 'anchor',
BEFORE: 'anchor',
AFTER: 'anchor',
PREV: 'tense',
NEXT: 'tense',
PAST: 'tense',
FUTURE: 'tense',
CURRENT: 'tense',
START: 'partial',
MID: 'partial',
END: 'partial',
LESS: 'approximation',
MORE: 'approximation',
APPROX: 'approximation',
FIRST: 'ordinal',
LAST: 'ordinal',
};
const DUMMY_SEPARATOR = '-';
const TIMESTRING_REGEXP = /^(S|DC|Y|Q|M|W|D)(\d)+$/g;
export function getNormalizedTempExp(myStr: string) {
/*
* Convert a string into normalized forms
* myStr: E.g. REL IND - FROM PREV M1; REL IND - FROM PREV M1;
*/
// Get the single terms
const myTerms = myStr.split('; ');
const myResults = [];
for (const myTerm of myTerms) {
// For each term, break down the string into single attributes
const attrs = myTerm.split(' ').filter((x: string) => (x.length > 0 && x != DUMMY_SEPARATOR));
if (attrs.length > 0) {
const myResult = new NormalizedTempExp();
const valList = [];
for (const attr of attrs) {
if (KEYWORD_TO_ATTR[attr]) {
// Fixed terms like "DIR", "IND", "NONE"... can be assigned to their attributes
myResult.setAttribute(KEYWORD_TO_ATTR[attr], attr);
} else if (attr.match(TIMESTRING_REGEXP)) {
// Dynamic terms like "M12", "Y1980" ... can be assigned to the attribute "value"
const unit = (attr.slice(0, 2) == 'DC') ? 'DC' : attr.slice(0, 1);
const numVal = Number.parseInt(attr.slice(unit.length), 10);
valList.push(new ValUnitTuple(unit, numVal));
}
}
myResult.setAttribute('value', valList);
myResults.push(myResult);
}
}
return myResults;
}
export function getRealDate(tempExp: NormalizedTempExp, refDate: any) {
let startDate = undefined;
let endDate = undefined;
let usedUnits: any = [];
let result: any = {start: startDate, end: endDate, anchor: tempExp.anchor, approximation: tempExp.approximation, partial: tempExp.partial};
switch (tempExp.type) {
case 'ABS':
startDate = {year: 0, month: 0, day: 0};
endDate = {year: 0, month: 0, day: 0};
result = {start: startDate, end: endDate, anchor: tempExp.anchor, approximation: tempExp.approximation, partial: tempExp.partial};
usedUnits = [];
for (const myTuple of tempExp.value) {
const unit = myTuple.unit;
const numVal = myTuple.value;
if (unit === 'S') {
startDate.year = (numVal > 0) ? (numVal * 100 - 99) : (numVal * 100);
endDate.year = (numVal > 0) ? (numVal * 100) : (numVal * 100 + 99);
usedUnits.push('Y');
} else if (unit === 'DC') {
startDate.year = numVal;
endDate.year = numVal + 9;
usedUnits.push('Y');
} else if (unit === 'Y') {
startDate.year = numVal;
endDate.year = numVal;
usedUnits.push('Y');
} else if (unit === 'Q') {
startDate.month = numVal * 3 - 2;
endDate.month = numVal * 3;
usedUnits.push('M');
} else if (unit === 'M') {
startDate.month = numVal;
endDate.month = numVal;
usedUnits.push('M');
} else if (unit === 'W') {
startDate.day = numVal * 7 - 6;
endDate.day = numVal * 7;
usedUnits.push('D');
} else if (unit === 'D') {
startDate.day = numVal;
endDate.day = numVal;
usedUnits.push('D');
}
}
if (usedUnits.indexOf('D') >= 0 && usedUnits.indexOf('M') < 0) {
startDate.year = refDate.year;
startDate.month = refDate.month;
endDate.year = refDate.year;
endDate.month = refDate.month;
} else if (usedUnits.indexOf('M') >= 0 && usedUnits.indexOf('Y') < 0) {
startDate.year = refDate.year;
endDate.year = refDate.year;
if (usedUnits.indexOf('D') < 0) {
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
}
}
if (usedUnits.indexOf('Y') >= 0) {
if (usedUnits.indexOf('M') < 0) {
startDate.month = 1;
endDate.month = 12;
}
if (usedUnits.indexOf('D') < 0) {
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
}
}
switch (tempExp.tense) {
case 'PAST':
if (usedUnits.indexOf('D') >= 0 && usedUnits.indexOf('M') < 0) {
if (startDate.day >= refDate.day) {
startDate.month -= 1;
endDate.month -= 1;
}
} else if (usedUnits.indexOf('M') >= 0 && usedUnits.indexOf('Y') < 0) {
if (startDate.month >= refDate.month ) {
startDate.year -= 1;
endDate.year -= 1;
}
}
break;
case 'FUTURE':
if (usedUnits.indexOf('D') >= 0 && usedUnits.indexOf('M') < 0) {
if (startDate.day <= refDate.day) {
startDate.month += 1;
endDate.month += 1;
}
} else if (usedUnits.indexOf('M') >= 0 && usedUnits.indexOf('Y') < 0) {
if (startDate.month <= refDate.month) {
startDate.year += 1;
endDate.year += 1;
}
}
break;
}
result.start = adjustInvalidDate(result.start);
result.end = adjustInvalidDate(result.end);
return result;
case 'REL':
startDate = {year: refDate.year, month: refDate.month, day: refDate.day};
endDate = {year: refDate.year, month: refDate.month, day: refDate.day};
result = {start: startDate, end: endDate, anchor: tempExp.anchor, approximation: tempExp.approximation, partial: tempExp.partial};
usedUnits = [];
switch (tempExp.tense) {
case 'CURRENT':
for (const myTuple of tempExp.value) {
const unit = myTuple.unit;
if (unit === 'S') {
startDate.year = Math.ceil(startDate.year / 100) * 100 - 99;
endDate.year = Math.ceil(endDate.year / 100) * 100;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'DC') {
startDate.year = Math.floor(startDate.year / 10) * 10;
endDate.year = Math.ceil(endDate.year / 10) * 10 + 9;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'Y') {
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'Q') {
startDate.month = Math.ceil(startDate.month / 3) * 3 - 2;
endDate.month = Math.ceil(endDate.month / 3) * 3;
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
usedUnits.push('M');
} else if (unit === 'M') {
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
usedUnits.push('M');
} else if (unit === 'W') {
startDate.day = Math.ceil(startDate.day / 7) * 7 - 6;
endDate.day = Math.ceil(endDate.day / 7) * 7;
usedUnits.push('D');
} else if (unit === 'D') {
usedUnits.push('D');
}
}
break;
case 'PAST':
for (const myTuple of tempExp.value) {
const unit = myTuple.unit;
const numVal = myTuple.value;
if (unit === 'S') {
startDate.year = Math.ceil(startDate.year / 100 - numVal) * 100 - 99;
endDate.year = Math.ceil(endDate.year / 100 - numVal) * 100;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'DC') {
startDate.year = Math.floor(startDate.year / 10 - numVal) * 10;
endDate.year = Math.ceil(endDate.year / 10 - numVal) * 10 + 9;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'Y') {
startDate.year = startDate.year - numVal;
endDate.year = endDate.year - numVal;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'Q') {
startDate.month = Math.ceil(startDate.month / 3 - numVal) * 3 - 2;
endDate.month = Math.ceil(endDate.month / 3 - numVal) * 3;
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
usedUnits.push('M');
} else if (unit === 'M') {
startDate.month = numVal;
endDate.month = numVal;
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year); ;
usedUnits.push('M');
} else if (unit === 'W') {
startDate.day = Math.ceil(startDate.day / 7 - numVal) * 7 - 6;
endDate.day = Math.ceil(endDate.day / 7 - numVal) * 7;
usedUnits.push('D');
} else if (unit === 'D') {
startDate.day = startDate.day - numVal;
endDate.day = endDate.day - numVal;
usedUnits.push('D');
}
}
break;
case 'FUTURE':
for (const myTuple of tempExp.value) {
const unit = myTuple.unit;
const numVal = myTuple.value;
if (unit === 'S') {
startDate.year = Math.ceil(startDate.year / 100 + numVal) * 100 - 99;
endDate.year = Math.ceil(endDate.year / 100 + numVal) * 100;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'DC') {
startDate.year = Math.floor(startDate.year / 10 + numVal) * 10;
endDate.year = Math.ceil(endDate.year / 10 + numVal) * 10 + 9;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'Y') {
startDate.year = startDate.year + numVal;
endDate.year = endDate.year + numVal;
startDate.month = 1;
endDate.month = 12;
startDate.day = 1;
endDate.day = 31;
usedUnits.push('Y');
} else if (unit === 'Q') {
startDate.month = Math.ceil(startDate.month / 3 + numVal) * 3 - 2;
endDate.month = Math.ceil(endDate.month / 3 + numVal) * 3;
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
usedUnits.push('M');
} else if (unit === 'M') {
startDate.month = startDate.month + numVal;
endDate.month = endDate.month + numVal;
startDate.day = 1;
endDate.day = getEndOfMonth(endDate.month, endDate.year);
usedUnits.push('M');
} else if (unit === 'W') {
startDate.day = Math.ceil(startDate.day / 7 + numVal) * 7 - 6;
endDate.day = Math.ceil(endDate.day / 7 + numVal) * 7;
usedUnits.push('D');
} else if (unit === 'D') {
startDate.day = startDate.day + numVal;
endDate.day = endDate.day + numVal;
usedUnits.push('D');
}
}
break;
case 'PREV':
for (const myTuple of tempExp.value) {
const unit = myTuple.unit;
const numVal = myTuple.value;
if (unit === 'S') {
startDate.year -= numVal * 100;
endDate.year -= numVal * 100;
usedUnits.push('Y');
} else if (unit === 'DC') {
startDate.year -= numVal * 10;
endDate.year -= numVal * 10;
usedUnits.push('Y');
} else if (unit === 'Y') {
startDate.year -= numVal;
endDate.year -= numVal;
usedUnits.push('Y');
} else if (unit === 'Q') {
startDate.month -= numVal * 3;
endDate.month -= numVal * 3;
usedUnits.push('M');
} else if (unit === 'M') {
startDate.month -= numVal;
endDate.month -= numVal;
usedUnits.push('M');
} else if (unit === 'W') {
startDate.day -= numVal * 7;
endDate.day -= numVal * 7;
usedUnits.push('D');
} else if (unit === 'D') {
startDate.day -= numVal;
endDate.day -= numVal;
usedUnits.push('D');
}
}
break;
case 'NEXT':
for (const myTuple of tempExp.value) {
const unit = myTuple.unit;
const numVal = myTuple.value;
if (unit === 'S') {
startDate.year += numVal * 100;
endDate.year += numVal * 100;
usedUnits.push('Y');
} else if (unit === 'DC') {
startDate.year += numVal * 10;
endDate.year += numVal * 10;
usedUnits.push('Y');
} else if (unit === 'Y') {
startDate.year += numVal;
endDate.year += numVal;
usedUnits.push('Y');
} else if (unit === 'Q') {
startDate.month += numVal * 3;
endDate.month += numVal * 3;
usedUnits.push('M');
} else if (unit === 'M') {
startDate.month += numVal;
endDate.month += numVal;
usedUnits.push('M');
} else if (unit === 'W') {
startDate.day += numVal * 7;
endDate.day += numVal * 7;
usedUnits.push('D');
} else if (unit === 'D') {
startDate.day += numVal;
endDate.day += numVal;
usedUnits.push('D');
}
}
break;
}
result.start = adjustInvalidDate(result.start);
result.end = adjustInvalidDate(result.end);
return result;
default:
return result;
}
}
export function getEndOfMonth(month: number, year: number) {
while (month > 12 || month < 1) {
if (month > 12) {
month -= 12;
year += 1;
} else if (month < 1) {
month += 12;
year -= 1;
}
}
if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && (year % 100 != 0)))) {
return 29;
}
if (month == 2) {
return 28;
}
if ([4, 6, 9, 11].indexOf(month) >= 0) {
return 30;
}
return 31;
}
export function adjustInvalidDate(myDate: any) {
let year = myDate.year;
let month = myDate.month;
let day = myDate.day;
while (month > 12 || month < 1 || day > getEndOfMonth(month, year) || day < 1) {
if (day > getEndOfMonth(month, year)) {
day -= getEndOfMonth(month, year);
month += 1;
} else if (day < 1) {
day += getEndOfMonth(month - 1, year);
month -= 1;
}
if (month > 12) {
month -= 12;
year += 1;
} else if (month < 1) {
month += 12;
year -= 1;
}
}
return {year, month, day};
}
export function mergeTemporalExpressions(tempExps: Array<any>) {
for (let i = tempExps.length - 2; i >= 0; i--) {
if (tempExps[i].anchor === 'FROM' && tempExps[i+1].anchor === 'TO') {
const merged = {
start: tempExps[i].start,
end: tempExps[i + 1].end,
approximation: tempExps[i].approximation || tempExps[i + 1].approximation,
partial: tempExps[i].partials || tempExps[i + 1].partial,
anchor: undefined,
};
tempExps = tempExps.slice(0, i).concat([merged]).concat(tempExps.slice(i+2));
}
}
// Sort the temporal expressions
try {
tempExps = tempExps.sort((a: any, b: any) => (a.start.year - b.start.year) * 1000 + (a.start.month - b.start.month) * 32 + (a.start.day - b.start.day));
} catch (err) {
// Do nothing
console.log(err);
}
if (tempExps.length > 0) {
const tmp = tempExps[tempExps.length - 1];
const now = new Date();
if (tempExps[tempExps.length - 1].anchor === 'FROM' &&
new Date(`${pad(tmp.start.year, 4)}-${pad(tmp.start.month, 2)}-${pad(tmp.start.day, 2)}`).getTime() < now.getTime()) {
tmp.end = {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate(),
};
}
}
return tempExps;
}
export function pad(myStr: any, maxLength: number) {
return myStr.toString().padStart(maxLength, '0');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment