diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2021-04-14 13:48:56 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2021-04-14 13:48:56 +0200 |
| commit | 130f56e4b4530d096403250f675ec42031ca64f2 (patch) | |
| tree | a696588a0712414bfd8605d4c39c6367600046f2 /gui/src/shared | |
| parent | 937a8a3ea26dddb761e85e74228069f6f1b6a591 (diff) | |
| parent | 1f3b51141b7da5cb2c38618708f606326413f5de (diff) | |
| download | mullvadvpn-130f56e4b4530d096403250f675ec42031ca64f2.tar.xz mullvadvpn-130f56e4b4530d096403250f675ec42031ca64f2.zip | |
Merge branch 'remove-moment-js'
Diffstat (limited to 'gui/src/shared')
| -rw-r--r-- | gui/src/shared/account-expiry.ts | 53 | ||||
| -rw-r--r-- | gui/src/shared/date-helper.ts | 121 | ||||
| -rw-r--r-- | gui/src/shared/logging.ts | 17 | ||||
| -rw-r--r-- | gui/src/shared/notifications/close-to-account-expiry.ts | 16 |
4 files changed, 155 insertions, 52 deletions
diff --git a/gui/src/shared/account-expiry.ts b/gui/src/shared/account-expiry.ts index a76eb6ecc4..7ca382efb2 100644 --- a/gui/src/shared/account-expiry.ts +++ b/gui/src/shared/account-expiry.ts @@ -1,52 +1,27 @@ -import moment from 'moment'; -import { sprintf } from 'sprintf-js'; -import { messages } from './gettext'; +import { DateComponent, DateType, formatRelativeDate, dateByAddingComponent } from './date-helper'; import { capitalize } from './string-helpers'; -type DateArgument = string | Date | moment.Moment; - -export function hasExpired(expiry: DateArgument): boolean { - return moment(expiry).isSameOrBefore(new Date()); +export function hasExpired(expiry: DateType): boolean { + return new Date(expiry).getTime() < Date.now(); } -export function formatDate(date: DateArgument, locale: string): string { - return moment(date).locale(locale).format('lll'); +export function closeToExpiry(expiry: DateType): boolean { + return ( + !hasExpired(expiry) && + new Date(expiry) <= dateByAddingComponent(new Date(), DateComponent.day, 3) + ); } -export function formatDurationUntilExpiry(expiry: DateArgument, locale: string): string { - const expiryMoment = moment(expiry).locale(locale); - const daysDiff = expiryMoment.diff(new Date(), 'days'); - - // Below three months we want to show the duration in days. Moments fromNow() method starts - // measuring duration in months from 26 days and up. - // https://momentjs.com/docs/#/displaying/fromnow/ - if (daysDiff >= 26 && daysDiff <= 90) { - return sprintf( - // TRANSLATORS: The remaining time left on the account measured in days. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(duration)s - The remaining time measured in days. - messages.pgettext('account-expiry', '%(duration)s days'), - { duration: daysDiff }, - ); - } else { - return expiryMoment.fromNow(true); - } +export function formatDate(date: DateType, locale: string): string { + return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format( + new Date(date), + ); } export function formatRemainingTime( - expiry: DateArgument, - locale: string, + expiry: DateType, shouldCapitalizeFirstLetter?: boolean, ): string { - const duration = formatDurationUntilExpiry(expiry, locale); - - const remaining = sprintf( - // TRANSLATORS: The remaining time left on the account displayed across the app. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(duration)s - a localized remaining time (in minutes, hours, or days) until the account expiry - messages.pgettext('account-expiry', '%(duration)s left'), - { duration }, - ); - + const remaining = formatRelativeDate(new Date(), expiry, true); return shouldCapitalizeFirstLetter ? capitalize(remaining) : remaining; } diff --git a/gui/src/shared/date-helper.ts b/gui/src/shared/date-helper.ts new file mode 100644 index 0000000000..0011ed8fc2 --- /dev/null +++ b/gui/src/shared/date-helper.ts @@ -0,0 +1,121 @@ +import { sprintf } from 'sprintf-js'; +import { messages } from './gettext'; + +export type DateType = Date | string; + +export enum DateComponent { + day, + hour, + minute, +} + +export function dateByAddingComponent(date: DateType, component: DateComponent, value: number) { + const modifiedDate = new Date(date); + switch (component) { + case DateComponent.day: + modifiedDate.setDate(modifiedDate.getDate() + value); + break; + case DateComponent.hour: + modifiedDate.setHours(modifiedDate.getHours() + value); + break; + case DateComponent.minute: + modifiedDate.setMinutes(modifiedDate.getMinutes() + value); + break; + } + + return modifiedDate; +} + +export class DateDiff { + private readonly fromDate: Date; + private readonly toDate: Date; + + public constructor(fromDate: DateType, toDate: DateType) { + this.fromDate = new Date(fromDate); + this.toDate = new Date(toDate); + } + + get milliseconds(): number { + return this.toDate.getTime() - this.fromDate.getTime(); + } + + get seconds(): number { + return this.floor(this.milliseconds / 1000); + } + + get minutes(): number { + return this.floor(this.seconds / 60); + } + + get hours(): number { + return this.floor(this.minutes / 60); + } + + get days(): number { + return this.floor(this.hours / 24); + } + + get months(): number { + const months = new Date(Math.abs(this.milliseconds)).getUTCMonth(); + const monthsWithSign = this.milliseconds >= 0 ? months : -months; + return this.years * 12 + monthsWithSign; + } + + get years(): number { + const years = new Date(Math.abs(this.milliseconds)).getUTCFullYear() - 1970; + return this.milliseconds >= 0 ? years : -years; + } + + private floor(n: number): number { + return n >= 0 ? Math.floor(n) : Math.ceil(n); + } +} + +export function formatRelativeDate( + fromDate: DateType, + toDate: DateType, + withSuffix = false, +): string { + const diff = new DateDiff(fromDate, toDate); + const years = Math.abs(diff.years); + const months = Math.abs(diff.months); + const days = Math.abs(diff.days); + const hours = Math.abs(diff.hours); + const minutes = Math.abs(diff.minutes); + + if (!withSuffix) { + if (years > 0) { + return sprintf(messages.ngettext('1 year', '%d years', years), years); + } else if (months >= 3) { + return sprintf(messages.ngettext('1 month', '%d months', months), months); + } else if (days > 0) { + return sprintf(messages.ngettext('1 day', '%d days', days), days); + } else { + return messages.gettext('less than a day'); + } + } else if (diff.milliseconds > 0) { + if (years > 0) { + return sprintf(messages.ngettext('1 year left', '%d years left', years), years); + } else if (months >= 3) { + return sprintf(messages.ngettext('1 month left', '%d months left', months), months); + } else if (days > 0) { + return sprintf(messages.ngettext('1 day left', '%d days left', days), days); + } else { + return messages.gettext('less than a day left'); + } + } else { + if (years > 0) { + return sprintf(messages.ngettext('a year ago', '%d years ago', years), years); + } else if (months > 0) { + return sprintf(messages.ngettext('a month ago', '%d months ago', months), months); + } else if (days > 0) { + return sprintf(messages.ngettext('a day ago', '%d days ago', days), days); + } else if (hours > 0) { + return sprintf(messages.ngettext('an hour ago', '%d hours ago', hours), hours); + } else if (minutes > 0) { + return sprintf(messages.ngettext('a minute ago', '%d minutes ago', minutes), minutes); + } else { + return messages.gettext('less than a minute ago'); + } + } +} diff --git a/gui/src/shared/logging.ts b/gui/src/shared/logging.ts index 84eaa5f25c..f8c71fc41e 100644 --- a/gui/src/shared/logging.ts +++ b/gui/src/shared/logging.ts @@ -1,4 +1,3 @@ -import moment from 'moment'; import { ILogInput, ILogOutput, LogLevel } from './logging-types'; export class Logger { @@ -13,7 +12,7 @@ export class Logger { } public log(level: LogLevel, ...data: unknown[]) { - const time = moment().format('YYYY-MM-DD HH:mm:ss.SSS'); + const time = this.getDateString(); const stringifiedData = data.map(this.stringifyData).join(' '); const message = `[${time}][${LogLevel[level]}] ${stringifiedData}`; @@ -30,6 +29,20 @@ export class Logger { this.outputs.forEach((output) => output.dispose?.()); } + private getDateString(): string { + const date = new Date(); + const year = date.getFullYear(); + const month = Number(date.getMonth() + 1) + .toString() + .padStart(2, '0'); + const day = Number(date.getDate()).toString().padStart(2, '0'); + const hour = Number(date.getHours()).toString().padStart(2, '0'); + const minute = Number(date.getMinutes()).toString().padStart(2, '0'); + const second = Number(date.getSeconds()).toString().padStart(2, '0'); + const millisecond = Number(date.getMilliseconds()).toString().padStart(3, '0'); + return `${year}-${month}-${day} ${hour}:${minute}:${second}.${millisecond}`; + } + private stringifyData(data: unknown): string { return typeof data === 'string' ? data : JSON.stringify(data); } diff --git a/gui/src/shared/notifications/close-to-account-expiry.ts b/gui/src/shared/notifications/close-to-account-expiry.ts index 2e3c5ae49e..e0f13b33b3 100644 --- a/gui/src/shared/notifications/close-to-account-expiry.ts +++ b/gui/src/shared/notifications/close-to-account-expiry.ts @@ -1,8 +1,8 @@ -import moment from 'moment'; import { sprintf } from 'sprintf-js'; import { links } from '../../config.json'; import { messages } from '../../shared/gettext'; -import { formatDurationUntilExpiry, formatRemainingTime, hasExpired } from '../account-expiry'; +import { closeToExpiry, formatRemainingTime } from '../account-expiry'; +import { formatRelativeDate } from '../date-helper'; import { InAppNotification, InAppNotificationProvider, @@ -19,13 +19,7 @@ export class CloseToAccountExpiryNotificationProvider implements InAppNotificationProvider, SystemNotificationProvider { public constructor(private context: CloseToAccountExpiryNotificationContext) {} - public mayDisplay() { - const willHaveExpiredInThreeDays = moment(this.context.accountExpiry).isSameOrBefore( - moment().add(3, 'days'), - ); - - return !hasExpired(this.context.accountExpiry) && willHaveExpiredInThreeDays; - } + public mayDisplay = () => closeToExpiry(this.context.accountExpiry); public getSystemNotification(): SystemNotification { const message = sprintf( @@ -37,7 +31,7 @@ export class CloseToAccountExpiryNotificationProvider 'Account credit expires in %(duration)s. Buy more credit.', ), { - duration: formatDurationUntilExpiry(this.context.accountExpiry, this.context.locale), + duration: formatRelativeDate(new Date(), this.context.accountExpiry), }, ); @@ -56,7 +50,7 @@ export class CloseToAccountExpiryNotificationProvider public getInAppNotification(): InAppNotification { const subtitle = sprintf( messages.pgettext('in-app-notifications', '%(duration)s. Buy more credit.'), - { duration: formatRemainingTime(this.context.accountExpiry, this.context.locale, true) }, + { duration: formatRemainingTime(this.context.accountExpiry, true) }, ); return { |
