diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 50bbd88b9..d1129a602 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -21,6 +21,7 @@ import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
import { LanguageService } from './services/language.service';
import { ThemeService } from './services/theme.service';
+import { TimeService } from './services/time.service';
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe';
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
@@ -42,6 +43,7 @@ const providers = [
EnterpriseService,
LanguageService,
ThemeService,
+ TimeService,
ShortenStringPipe,
FiatShortenerPipe,
FiatCurrencyPipe,
diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts
index f0c73c80b..6360bca4a 100644
--- a/frontend/src/app/components/time/time.component.ts
+++ b/frontend/src/app/components/time/time.component.ts
@@ -1,29 +1,6 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core';
import { StateService } from '../../services/state.service';
-import { dates } from '../../shared/i18n/dates';
-import { DatePipe } from '@angular/common';
-
-const datePipe = new DatePipe(navigator.language || 'en-US');
-
-const intervals = {
- year: 31536000,
- month: 2592000,
- week: 604800,
- day: 86400,
- hour: 3600,
- minute: 60,
- second: 1
-};
-
-const precisionThresholds = {
- year: 100,
- month: 18,
- week: 12,
- day: 31,
- hour: 48,
- minute: 90,
- second: 90
-};
+import { TimeService } from '../../services/time.service';
@Component({
selector: 'app-time',
@@ -52,6 +29,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
constructor(
private ref: ChangeDetectorRef,
private stateService: StateService,
+ private timeService: TimeService,
) {}
ngOnInit() {
@@ -79,7 +57,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
}
calculateTime(): void {
- const { text, tooltip } = TimeComponent.calculate(
+ const { text, tooltip } = this.timeService.calculate(
this.time,
this.kind,
this.relative,
@@ -95,238 +73,4 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
this.text = text;
this.tooltip = tooltip;
}
-
- static calculate(
- time: number,
- kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within',
- relative: boolean = false,
- precision: number = 0,
- minUnit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' = 'second',
- showTooltip: boolean = false,
- units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
- dateString?: string,
- lowercaseStart: boolean = false,
- numUnits: number = 1,
- fractionDigits: number = 0,
- ): { text: string, tooltip: string } {
- if (time == null) {
- return { text: '', tooltip: '' };
- }
-
- let seconds: number;
- let tooltip: string = '';
- switch (kind) {
- case 'since':
- seconds = Math.floor((+new Date() - +new Date(dateString || time * 1000)) / 1000);
- tooltip = datePipe.transform(new Date(dateString || time * 1000), 'yyyy-MM-dd HH:mm');
- break;
- case 'until':
- case 'within':
- seconds = (+new Date(time) - +new Date()) / 1000;
- tooltip = datePipe.transform(new Date(time), 'yyyy-MM-dd HH:mm');
- break;
- default:
- seconds = Math.floor(time);
- tooltip = '';
- }
-
- if (!showTooltip || relative) {
- tooltip = '';
- }
-
- if (seconds < 1 && kind === 'span') {
- return { tooltip, text: $localize`:@@date-base.immediately:Immediately` };
- } else if (seconds < 60) {
- if (relative || kind === 'since') {
- if (lowercaseStart) {
- return { tooltip, text: $localize`:@@date-base.just-now:Just now`.charAt(0).toLowerCase() + $localize`:@@date-base.just-now:Just now`.slice(1) };
- }
- return { tooltip, text: $localize`:@@date-base.just-now:Just now` };
- } else if (kind === 'until' || kind === 'within') {
- seconds = 60;
- }
- }
-
- let counter: number;
- const result = [];
- let usedUnits = 0;
- for (const [index, unit] of units.entries()) {
- let precisionUnit = units[Math.min(units.length - 1, index + precision)];
- counter = Math.floor(seconds / intervals[unit]);
- const precisionCounter = Math.round(seconds / intervals[precisionUnit]);
- if (precisionCounter > precisionThresholds[precisionUnit]) {
- precisionUnit = unit;
- }
- if (units.indexOf(precisionUnit) === units.indexOf(minUnit)) {
- counter = Math.max(1, counter);
- }
- if (counter > 0) {
- let rounded;
- const roundFactor = Math.pow(10,fractionDigits || 0);
- if ((kind === 'until' || kind === 'within') && usedUnits < numUnits) {
- rounded = Math.floor((seconds / intervals[precisionUnit]) * roundFactor) / roundFactor;
- } else {
- rounded = Math.round((seconds / intervals[precisionUnit]) * roundFactor) / roundFactor;
- }
- if ((kind !== 'until' && kind !== 'within')|| numUnits === 1) {
- return { tooltip, text: TimeComponent.formatTime(kind, precisionUnit, rounded) };
- } else {
- if (!usedUnits) {
- result.push(TimeComponent.formatTime(kind, precisionUnit, rounded));
- } else {
- result.push(TimeComponent.formatTime('', precisionUnit, rounded));
- }
- seconds -= (rounded * intervals[precisionUnit]);
- usedUnits++;
- if (usedUnits >= numUnits) {
- return { tooltip, text: result.join(', ') };
- }
- }
- }
- }
- return { tooltip, text: result.join(', ') };
- }
-
- static formatTime(kind, unit, number): string {
- const dateStrings = dates(number);
- switch (kind) {
- case 'since':
- if (number === 1) {
- switch (unit) { // singular (1 day)
- case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
- case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
- case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
- case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
- case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
- case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
- case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
- }
- } else {
- switch (unit) { // plural (2 days)
- case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
- case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
- case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
- case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
- case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
- case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
- case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
- }
- }
- break;
- case 'until':
- if (number === 1) {
- switch (unit) { // singular (In ~1 day)
- case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
- case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
- case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
- case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
- case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
- case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
- case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
- }
- } else {
- switch (unit) { // plural (In ~2 days)
- case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
- case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
- case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
- case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
- case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
- case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
- case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
- }
- }
- break;
- case 'within':
- if (number === 1) {
- switch (unit) { // singular (In ~1 day)
- case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break;
- case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break;
- case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break;
- case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break;
- case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break;
- case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`;
- case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`;
- }
- } else {
- switch (unit) { // plural (In ~2 days)
- case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break;
- case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break;
- case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break;
- case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break;
- case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break;
- case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break;
- case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break;
- }
- }
- break;
- case 'span':
- if (number === 1) {
- switch (unit) { // singular (1 day)
- case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
- case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
- case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
- case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
- case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
- case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
- case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
- }
- } else {
- switch (unit) { // plural (2 days)
- case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
- case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
- case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
- case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
- case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
- case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
- case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
- }
- }
- break;
- case 'before':
- if (number === 1) {
- switch (unit) { // singular (1 day)
- case 'year': return $localize`:@@time-before:${dateStrings.i18nYear}:DATE: before`; break;
- case 'month': return $localize`:@@time-before:${dateStrings.i18nMonth}:DATE: before`; break;
- case 'week': return $localize`:@@time-before:${dateStrings.i18nWeek}:DATE: before`; break;
- case 'day': return $localize`:@@time-before:${dateStrings.i18nDay}:DATE: before`; break;
- case 'hour': return $localize`:@@time-before:${dateStrings.i18nHour}:DATE: before`; break;
- case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinute}:DATE: before`; break;
- case 'second': return $localize`:@@time-before:${dateStrings.i18nSecond}:DATE: before`; break;
- }
- } else {
- switch (unit) { // plural (2 days)
- case 'year': return $localize`:@@time-before:${dateStrings.i18nYears}:DATE: before`; break;
- case 'month': return $localize`:@@time-before:${dateStrings.i18nMonths}:DATE: before`; break;
- case 'week': return $localize`:@@time-before:${dateStrings.i18nWeeks}:DATE: before`; break;
- case 'day': return $localize`:@@time-before:${dateStrings.i18nDays}:DATE: before`; break;
- case 'hour': return $localize`:@@time-before:${dateStrings.i18nHours}:DATE: before`; break;
- case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinutes}:DATE: before`; break;
- case 'second': return $localize`:@@time-before:${dateStrings.i18nSeconds}:DATE: before`; break;
- }
- }
- break;
- default:
- if (number === 1) {
- switch (unit) { // singular (1 day)
- case 'year': return dateStrings.i18nYear; break;
- case 'month': return dateStrings.i18nMonth; break;
- case 'week': return dateStrings.i18nWeek; break;
- case 'day': return dateStrings.i18nDay; break;
- case 'hour': return dateStrings.i18nHour; break;
- case 'minute': return dateStrings.i18nMinute; break;
- case 'second': return dateStrings.i18nSecond; break;
- }
- } else {
- switch (unit) { // plural (2 days)
- case 'year': return dateStrings.i18nYears; break;
- case 'month': return dateStrings.i18nMonths; break;
- case 'week': return dateStrings.i18nWeeks; break;
- case 'day': return dateStrings.i18nDays; break;
- case 'hour': return dateStrings.i18nHours; break;
- case 'minute': return dateStrings.i18nMinutes; break;
- case 'second': return dateStrings.i18nSeconds; break;
- }
- }
- }
- }
}
diff --git a/frontend/src/app/components/utxo-graph/utxo-graph.component.ts b/frontend/src/app/components/utxo-graph/utxo-graph.component.ts
index 91dc70240..310ff0356 100644
--- a/frontend/src/app/components/utxo-graph/utxo-graph.component.ts
+++ b/frontend/src/app/components/utxo-graph/utxo-graph.component.ts
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NgZone, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { EChartsOption } from '../../graphs/echarts';
-import { BehaviorSubject, Subscription } from 'rxjs';
+import { Subscription } from 'rxjs';
import { Utxo } from '../../interfaces/electrs.interface';
import { StateService } from '../../services/state.service';
import { Router } from '@angular/router';
@@ -8,6 +8,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
import { renderSats } from '../../shared/common.utils';
import { colorToHex, hexToColor, mix } from '../block-overview-graph/utils';
import { TimeComponent } from '../time/time.component';
+import { TimeService } from '../../services/time.service';
const newColorHex = '1bd8f4';
const oldColorHex = '9339f4';
@@ -55,6 +56,7 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
private zone: NgZone,
private router: Router,
private relativeUrlPipe: RelativeUrlPipe,
+ private timeService: TimeService,
) {
// re-render the chart every 10 seconds, to keep the age colors up to date
this.updateInterval = setInterval(() => {
@@ -276,7 +278,7 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
${valueStr}
- ${utxo.status.confirmed ? 'Confirmed ' + TimeComponent.calculate(utxo.status.block_time, 'since', true, 1, 'minute').text : 'Pending'}
+ ${utxo.status.confirmed ? 'Confirmed ' + this.timeService.calculate(utxo.status.block_time, 'since', true, 1, 'minute').text : 'Pending'}
`;
},
}