ng-time-past-pipe
Version:
Reactive textual representation of the time that has been passed between a given date and now in your Angular App.
351 lines (342 loc) • 12.3 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, InjectFlags, Pipe, Inject, Injectable } from '@angular/core';
import * as i1 from 'rxjs';
import { interval } from 'rxjs';
import { filter, map } from 'rxjs/operators';
/**
* Custom `TimeDiffGenerator` Injection Token
*
* @public
* @api
*/
const CUSTOM_TIME_DIFF_GENERATOR = new InjectionToken('Custom Time Diff Generator');
/**
* Return a respective textual representation of the input, as the input is a timespan that has been passed.
*
* @param diff The time diff object
* @public
* @api
*/
const defaultTimeDiffGenerator = (diff) => {
if (diff.seconds === 0) {
return 'about now';
}
return diff.isFuture ? getFutureDiffString(diff) : getPastDiffString(diff);
};
const getPastDiffString = (diff) => {
const { seconds, minutes, hours, months, days, years } = diff;
if (seconds <= 5) {
return 'a few seconds ago';
}
else if (seconds <= 59) {
return seconds + ' seconds ago';
}
else if (seconds <= 90) {
return 'about a minute ago';
}
if (minutes <= 45) {
return minutes + ' minutes ago';
}
else if (minutes <= 90) {
return 'one hour ago';
}
if (hours <= 22) {
return hours + ' hours ago';
}
else if (hours <= 36) {
return 'a day ago';
}
if (days <= 25) {
return days + ' days ago';
}
else if (days <= 45) {
return 'a month ago';
}
if (days <= 345) {
return months + ' months ago';
}
else if (days <= 545) {
return 'a year ago';
}
return years + ' years ago';
};
const getFutureDiffString = (diff) => {
const { seconds, minutes, hours, months, days, years } = diff;
if (seconds <= 59) {
return 'in ' + seconds + ' seconds';
}
if (seconds <= 90) {
return 'in one minute';
}
else if (minutes <= 59) {
return 'in ' + minutes + ' minutes';
}
if (minutes <= 90) {
return 'in one hour';
}
else if (hours <= 22) {
return 'in ' + hours + ' hours';
}
if (hours <= 36) {
return 'in one day';
}
else if (days <= 25) {
return 'in ' + days + ' days';
}
if (days <= 45) {
return 'in one month';
}
else if (days <= 345) {
return 'in ' + months + ' months';
}
if (days <= 545) {
return 'in one year';
}
return 'in ' + years + ' years';
};
/**
* Provides the TimeDiffGenerator preferring a custom provider for internal usage
*
* @internal
*/
const TIME_DIFF_GENERATOR = new InjectionToken('Time Diff Generator', {
factory: () => {
const customGenerator = inject(CUSTOM_TIME_DIFF_GENERATOR, InjectFlags.Optional);
return customGenerator !== null && customGenerator !== void 0 ? customGenerator : defaultTimeDiffGenerator;
},
});
/**
* TimeDiff Factory
*
* @param seconds The time difference in seconds. Negative values are considered as a future event
* @internal
*/
const createTimeDiff = (seconds) => {
const isFuture = seconds < 0;
if (isFuture) {
seconds = Math.abs(seconds);
}
const diff = { seconds, isFuture };
diff.minutes = Math.round(seconds / 60);
diff.hours = Math.round(diff.minutes / 60);
diff.days = Math.round(diff.hours / 24);
diff.months = Math.round(diff.days / 30.416);
diff.years = Math.round(diff.days / 365);
return diff;
};
/**
* Custom `UpdateIntervalGenerator` Injection Token
*
* @public
* @api
*/
const CUSTOM_UPDATE_INTERVAL_GENERATOR = new InjectionToken('Custom Update Interval Generator');
/**
* Determinate the point of time on when the output should be checked for a update
*
* @param diff The time diff object
* @return A point of time in future in seconds
* @public
* @api
*/
const defaultUpdateIntervalGenerator = (diff) => {
if (diff.seconds < 60) { // less than 1 min, update every second
return 1;
}
else if (diff.seconds < 3600) { // less than an hour, update every 30 secs
return 30;
}
else if (diff.seconds < 86400) { // less than a day, update every 5 min
return 300;
}
// update every hour
return 3600;
};
/**
* Provides the `UpdateIntervalGenerator` preferring a custom provider for internal usage
*
* @internal
*/
const UPDATE_INTERVAL_GENERATOR = new InjectionToken('Update Interval Generator', {
factory: () => {
const customGenerator = inject(CUSTOM_UPDATE_INTERVAL_GENERATOR, InjectFlags.Optional);
return customGenerator !== null && customGenerator !== void 0 ? customGenerator : defaultUpdateIntervalGenerator;
},
});
/**
* Optimistic parse a given input to seconds that past between it and now
*
* @param value A value of type string, number or date
* @return The time past in seconds between now and input value
* @internal
*/
const parseInputValue = (value) => {
let dateValueTime;
if (typeof value === 'number') {
if (value <= 0) {
// Negative number will always be handled as seconds in the future
return value;
}
const length = Math.ceil(Math.log10(value + 1));
if (length < 10 && length > 0) {
return value; // Guessing the input is already the passed seconds
}
if (length === 10) {
value *= 1000;
} // Guessing UnixTimestamp
dateValueTime = value; // All other lengths are considered intentional and therefore processed
}
else {
// Use Date constructor to determine the microseconds
dateValueTime = (value instanceof Date ? value : new Date(value)).getTime();
}
return Math.floor((Date.now() - dateValueTime) / 1000);
};
/**
* Strict TAInput Type Validator
*
* @param value The optimistic input value to validate
* @internal
*/
const validateTAInputType = (value) => {
return (typeof value === 'number' || typeof value === 'string' || value instanceof Date);
};
const TIME_PAST_TICKER = new InjectionToken('TimePastTimer', {
factory: () => interval(1000),
providedIn: 'root'
});
class TimePastPipe {
/**
* TimePastPipe Class Constructor
*/
constructor(changeDetectorRef, ticker, timeDiffGenerator, updateIntervalGenerator) {
this.changeDetectorRef = changeDetectorRef;
this.ticker = ticker;
this.timeDiffGenerator = timeDiffGenerator;
this.updateIntervalGenerator = updateIntervalGenerator;
this.currentPeriod = 1;
this.intervalTimer = this.ticker.pipe(filter((tick) => tick % this.currentPeriod === 0), map((tick) => tick / this.currentPeriod));
this.intervalSubscription = this.intervalTimer.subscribe(() => {
this.changeDetectorRef.markForCheck();
});
}
/**
* Transform anything that can be parsed to a Date in the past, to a string that represent the relative
* time that has been passed between now and this point of time.
*
* @param value A value that can be parsed to a Date in the past or future
* @param overflow Overflow to time in past when initial date was in future
* @return The textual representation of the time that has been passed between the given Date
* and the current.
*/
transform(value, overflow = true) {
if (this.isValidInput(value) === false) {
return value;
}
const seconds = parseInputValue(value);
this.initialSeconds || (this.initialSeconds = seconds);
if (this.lastSeconds === seconds || (overflow === false && this.initialSeconds < 0 && seconds > 0)) {
return this.lastResult;
}
// The ChangeDetector should not call transform again while the new value is being resolved
this.changeDetectorRef.detach();
this.lastSeconds = seconds;
const timeDiff = createTimeDiff(seconds);
const result = (this.lastResult = this.timeDiffGenerator(timeDiff));
// Make sure the update interval refreshed as well
this.currentPeriod = this.updateIntervalGenerator(timeDiff);
// Reattach the ChangeDetector so that further changes are being transformed
this.changeDetectorRef.reattach();
return result;
}
/**
* Validate the Input Value and log a warning per value when it fails
*
* @param value
* @private
*/
isValidInput(value) {
const validationResult = validateTAInputType(value);
if (validationResult === false && this.lastInput !== value) {
console.warn(`[TimePastPipe] Invalid Input of type ${typeof value} (${value}).`);
}
this.lastInput = value;
return validationResult;
}
/**
* Clear interval ticker subscription
*/
ngOnDestroy() {
if (this.intervalSubscription) {
this.intervalSubscription.unsubscribe();
}
}
}
TimePastPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.3", ngImport: i0, type: TimePastPipe, deps: [{ token: i0.ChangeDetectorRef }, { token: TIME_PAST_TICKER }, { token: TIME_DIFF_GENERATOR }, { token: UPDATE_INTERVAL_GENERATOR }], target: i0.ɵɵFactoryTarget.Pipe });
TimePastPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.0.3", ngImport: i0, type: TimePastPipe, isStandalone: true, name: "timePast", pure: false });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.3", ngImport: i0, type: TimePastPipe, decorators: [{
type: Pipe,
args: [{
standalone: true,
name: 'timePast',
pure: false,
}]
}], ctorParameters: function () {
return [{ type: i0.ChangeDetectorRef }, { type: i1.Observable, decorators: [{
type: Inject,
args: [TIME_PAST_TICKER]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [TIME_DIFF_GENERATOR]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [UPDATE_INTERVAL_GENERATOR]
}] }];
} });
/**
* @deprecated Use TimePastPipe instead
*/
const NgTimePastPipePipe = TimePastPipe;
/**
* Public TimePast Service Class
*
* @public
* @api
*/
class TimePastService {
constructor(timeDiffGenerator) {
this.timeDiffGenerator = timeDiffGenerator;
}
/**
* Transform anything that can be parsed to a Date in the past, to a string that represent the relative
* time that has been passed between now and this point of time.
*
* @param value A value that can be parsed to a Date in the past
* @return The textual representation of the time that has been passed between the given Date
* and the current.
*/
timePast(value) {
if (validateTAInputType(value) === false) {
return undefined;
}
const seconds = parseInputValue(value);
const timeDiff = createTimeDiff(seconds);
return this.timeDiffGenerator(timeDiff);
}
}
TimePastService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.3", ngImport: i0, type: TimePastService, deps: [{ token: TIME_DIFF_GENERATOR }], target: i0.ɵɵFactoryTarget.Injectable });
TimePastService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.3", ngImport: i0, type: TimePastService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.3", ngImport: i0, type: TimePastService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () {
return [{ type: undefined, decorators: [{
type: Inject,
args: [TIME_DIFF_GENERATOR]
}] }];
} });
/**
* Generated bundle index. Do not edit.
*/
export { CUSTOM_TIME_DIFF_GENERATOR, CUSTOM_UPDATE_INTERVAL_GENERATOR, NgTimePastPipePipe, TimePastPipe, TimePastService, defaultTimeDiffGenerator, defaultUpdateIntervalGenerator };
//# sourceMappingURL=ng-time-past-pipe.mjs.map