Skip to content

Commit

Permalink
Added more functions
Browse files Browse the repository at this point in the history
  • Loading branch information
djalmajr committed Apr 19, 2023
1 parent 550b6d0 commit ef13449
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/formatDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { curry } from './curry';

export const createDateFormatter = curry((locale: string, date: Date | string) => {
return date ? new Intl.DateTimeFormat(locale).format(new Date(date)) : null;
});

export const formatBRDate = createDateFormatter('pt-BR');
229 changes: 229 additions & 0 deletions src/fromNow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { curry } from './curry';
import { Obj } from './types';

interface Interval {
divisor: number;
ge: number;
text?: string;
unit: Intl.RelativeTimeFormatUnit;
}

const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const MONTH = 30 * DAY;
const YEAR = 365 * DAY;

const intervals = [
{ ge: YEAR, divisor: YEAR, unit: 'year' },
{ ge: MONTH, divisor: MONTH, unit: 'month' },
{ ge: WEEK, divisor: WEEK, unit: 'week' },
{ ge: DAY, divisor: DAY, unit: 'day' },
{ ge: HOUR, divisor: HOUR, unit: 'hour' },
{ ge: MINUTE, divisor: MINUTE, unit: 'minute' },
{ ge: 30 * SECOND, divisor: SECOND, unit: 'seconds' },
{ ge: 0, divisor: 1 },
] as Interval[];

const locales: Obj = {
'en-US': 'in less than a minute',
'pt-BR': 'menos de um minuto',
};

const translates: Obj = {
'en-US': {
recent: {
past1: 'just now',
pastN: 'just now',
future1: 'just now',
futureN: 'just now',
},
second: {
past1: 'a second ago',
pastN: '# seconds ago',
future1: 'in a second',
futureN: 'in # seconds',
},
minute: {
past1: 'a minute ago',
pastN: '# minutes ago',
future1: 'in a minute',
futureN: 'in # minutes',
},
hour: {
past1: 'an hour ago',
pastN: '# hours ago',
future1: 'in an hour',
futureN: 'in # hours',
},
day: {
past1: 'yesterday',
pastN: '# days ago',
future1: 'tomorrow',
futureN: 'in # days',
},
week: {
past1: 'last week',
pastN: '# weeks ago',
future1: 'in a week',
futureN: 'in # weeks',
},
month: {
past1: 'last month',
pastN: '# months ago',
future1: 'in a month',
futureN: 'in # months',
},
year: {
past1: 'last year',
pastN: '# years ago',
future1: 'in a year',
futureN: 'in # years',
},
century: {
past1: 'last century',
pastN: '# centuries ago',
future1: 'in a century',
futureN: 'in # centuries',
},
millenium: {
past1: 'last millennium',
pastN: '# millennia ago',
future1: 'in a millennium',
futureN: 'in # millennia',
},
},
'pt-BR': {
recent: {
past1: 'há pouco tempo',
pastN: 'há pouco tempo',
future1: 'há pouco tempo',
futureN: 'há pouco tempo',
},
second: {
past1: 'há um segundo',
pastN: 'há # segundos',
future1: 'em um segundo',
futureN: 'em # segundos',
},
minute: {
past1: 'há um minuto',
pastN: 'há # minutos',
future1: 'in a minute',
futureN: 'in # minutes',
},
hour: {
past1: 'há uma hora',
pastN: 'há # horas',
future1: 'in an hour',
futureN: 'in # hours',
},
day: {
past1: 'ontem',
pastN: 'há # dias',
future1: 'amanhã',
futureN: 'em # dias',
},
week: {
past1: 'há uma semana',
pastN: 'há # semanas',
future1: 'em uma semana',
futureN: 'em # semanas',
},
month: {
past1: 'há um mês',
pastN: 'há # meses',
future1: 'em um mês',
futureN: 'em # meses',
},
year: {
past1: 'há um ano',
pastN: 'há # anos',
future1: 'em um ano',
futureN: 'em # anos',
},
century: {
past1: 'há um século',
pastN: 'há # século',
future1: 'em um século',
futureN: 'em # séculos',
},
millenium: {
past1: 'há um milênio',
pastN: 'há # milênios',
future1: 'em um milênio',
futureN: 'em # milênios',
},
},
};

/**
* Human readable elapsed or remaining time (example: 3 minutes ago)
* @author github.com/victornpb
* @see https://stackoverflow.com/a/67338038/938822
* @param {Date|Number|String} date A Date object, timestamp or string parsable with Date.parse()
* @param {Date|Number|String} [nowDate] A Date object, timestamp or string parsable with Date.parse()
* @param {Intl.RelativeTimeFormat} [lang] Language to format the relative date
* @return {string} Human readable elapsed or remaining time
*/
function fromNowIntl(lang: string, date: Date | number | string) {
const now = new Date(Date.now()).getTime();
const diff = now - new Date(date).getTime();
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });

for (const interval of intervals) {
if (Math.abs(diff) >= interval.ge) {
const x = Math.round(Math.abs(diff) / interval.divisor);
return interval.unit
? rtf.format(diff < 0 ? x : -x, interval.unit)
: locales[lang] || locales['en-US'];
}
}

return date;
}

/**
* Human readable elapsed or remaining time (example: 3 minutes ago)
* @param {Date|Number|String} date A Date object, timestamp or string parsable with Date.parse()
* @return {string} Human readable elapsed or remaining time
* @author github.com/victornpb
* @see https://stackoverflow.com/a/67338038/938822
*/
function fromNowAlt(lang: string, date: Date | number | string) {
const units = [
{ ...translates[lang].recent, max: 30 * SECOND, divisor: 1 },
{ ...translates[lang].second, max: MINUTE, divisor: SECOND },
{ ...translates[lang].minute, max: HOUR, divisor: MINUTE },
{ ...translates[lang].hour, max: DAY, divisor: HOUR },
{ ...translates[lang].day, max: WEEK, divisor: DAY },
{ ...translates[lang].week, max: 4 * WEEK, divisor: WEEK },
{ ...translates[lang].month, max: YEAR, divisor: MONTH },
{ ...translates[lang].year, max: 100 * YEAR, divisor: YEAR },
{ ...translates[lang].century, max: 1000 * YEAR, divisor: 100 * YEAR },
{ ...translates[lang].millenium, max: Infinity, divisor: 1000 * YEAR },
];
const diff = Date.now() - new Date(date).getTime();
const diffAbs = Math.abs(diff);
for (const unit of units) {
if (diffAbs < unit.max) {
const isFuture = diff < 0;
const x = Math.round(Math.abs(diff) / unit.divisor);
if (x <= 1) return isFuture ? unit.future1 : unit.past1;
return (isFuture ? unit.futureN : unit.pastN).replace('#', x);
}
}
}

export const fromNow = curry((lang: string, date: Date | number | string) => {
try {
new Intl.RelativeTimeFormat(lang);
return fromNowIntl(lang, date);
} catch (err) {
return fromNowAlt(lang, date);
}
});

export const fromNowBR = fromNow('pt-BR');
45 changes: 45 additions & 0 deletions src/html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { set } from './set';
import { Obj } from './types';

const prefix = 'δ';
const isAttr = /([^\s\\>"'=]+)\s*=\s*(['"]?)$/;
const hasPfx = (str: string | null) => str?.search(prefix) !== -1;
const create = (tag: string, obj: Obj) => Object.assign(document.createElement(tag), obj);

export function html(strings: string[], ...values: unknown[]) {
const tmpl = values.reduce((r: string, _, i) => {
return `${r}${isAttr.test(r) ? prefix + i : `<!--${prefix + i}-->`}${strings[i + 1]}`;
}, strings[0]);

const root: Element = create('template', { innerHTML: tmpl }).content;
const walker = document.createTreeWalker(root, 1 | 128); // comment + element

let idx = 0;
let attr: string;
let node: Element;

while (idx < values.length) {
if (!(node = walker.nextNode() as Element)) break;
if (node.nodeType === Node.ELEMENT_NODE) {
const attrs = node.getAttributeNames().filter((n) => hasPfx(node.getAttribute(n)!));
for (attr of attrs) {
if (attr[0] === "@") set(`on${attr.slice(1)}`, values[idx++], node);
else if (attr[0] === "?") node.toggleAttribute(attr.slice(1), !!values[idx++]);
else if (attr[0] === ".") set(attr.slice(1), values[idx++], node);
else { node.setAttribute(attr, values[idx++] as never); continue; } // prettier-ignore
node.removeAttribute(attr);
}
} else if (node.nodeType === Node.COMMENT_NODE && hasPfx(node.textContent)) {
const val = values[idx++];
if (val instanceof NodeList) render(node, val as never);
else node.parentNode?.insertBefore(document.createTextNode(val as never), node);
}
}

return root.childNodes;
}

export function render(where: Element, what: ChildNode[]) {
if (where.nodeType === Node.ELEMENT_NODE) where.replaceChildren(...what);
else for (const node of what) where.parentNode?.insertBefore(node, where);
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ export * from './filterBy';
export * from './firstUpper';
export * from './flow';
export * from './formatCurrency';
export * from './formatDate';
export * from './formatPhone';
export * from './fromNow';
export * from './get';
export * from './groupBy';
export * from './groupByEveryN';
export * from './html';
export * from './includes';
export * from './isArray';
export * from './isEmpty';
Expand Down

0 comments on commit ef13449

Please sign in to comment.