diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd1588a3..93f36c170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ for further information about branching and tagging conventions. ## [Unreleased] #### Added - Print error with meta in console +- Added `dayjs` to project instead `momentjs` +- New methods to `date.util.js` for not wrapped methods to using with dates + +### Changed +- Wrapped all using methods for dates by `date.util.js` + +### Removed +- Removed `date-helpers.js` from helpers +- Removed `moment` and `moment-timezone` from project ## [1.13.0-rc.3] - 2021-11-09 #### Fixed diff --git a/package.json b/package.json index 0a82d548a..1d9c9ac79 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "d3-selection": "^1.4.1", "d3-shape": "^1.3.7", "d3-transition": "^1.3.2", + "dayjs": "^1.11.6", "detect-browser": "5.1.0", "eslint-loader": "^3.0.3", "eslint-plugin-chai-friendly": "^0.5.0", @@ -48,8 +49,6 @@ "iban": "^0.0.14", "iso-country-codes": "^0.0.5", "lodash": "^4.17.20", - "moment": "^2.24.0", - "moment-timezone": "^0.5.28", "normalize-url": "5.0.0", "nprogress": "^0.2.0", "qrcode.vue": "^1.7.0", diff --git a/src/i18n/index.js b/src/i18n/index.js index b41265547..d07f9b7ef 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -9,13 +9,11 @@ import i18nextBrowserLanguageDetector from 'i18next-browser-languagedetector' // TODO: make i18n.language and i18n.languages responsive -import moment from 'moment-timezone' - import _isObject from 'lodash/isObject' import _get from 'lodash/get' import _merge from 'lodash/merge' -import { MathUtil } from '@/js/utils' +import { MathUtil, DateUtil } from '@/js/utils' class I18n { constructor () { @@ -170,20 +168,25 @@ class I18n { switch (format.toLowerCase()) { case 'date': - return moment(param) - .format(_get(lngConfig, 'date.presets.datetime')) + return DateUtil.format( + DateUtil.tz(param), _get(lngConfig, 'date.presets.datetime') + ) case 'dmy': - return moment(param) - .format(_get(lngConfig, 'date.presets.dmy')) + return DateUtil.format( + DateUtil.tz(param), _get(lngConfig, 'date.presets.dmy') + ) case 'dmyt': - return moment(param) - .format(_get(lngConfig, 'date.presets.dmyt')) + return DateUtil.format( + DateUtil.tz(param), _get(lngConfig, 'date.presets.dmyt') + ) case 'calendar': - return moment(param) - .calendar(null, _get(lngConfig, 'date.calendar')) + return DateUtil.toHuman( + DateUtil.tz(param), undefined, _get(lngConfig, 'date.calendar') + ) case 'calendar-inline': - return moment(param) - .calendar(null, _get(lngConfig, 'date.calendarInline')) + return DateUtil.toHuman( + DateUtil.tz(param), undefined, _get(lngConfig, 'date.calendarInline') + ) case 'money': const value = (_isObject(param) ? param.value : param) || '0' const defaultFormat = diff --git a/src/i18n/index.spec.js b/src/i18n/index.spec.js index dbc90b28d..8d9dc1b85 100644 --- a/src/i18n/index.spec.js +++ b/src/i18n/index.spec.js @@ -1,5 +1,5 @@ import { i18n } from '@/i18n' -import moment from 'moment-timezone' +import { DateUtil } from '@/js/utils' const mockEn = { 'config': { @@ -87,7 +87,7 @@ const mockEn = { describe('the i18n is properly configured', () => { beforeEach(async () => { - moment.tz.setDefault('UTC') // Set timezone to 'UTC', so tests won't convert to local timezone + DateUtil.setDefaultTimezone('UTC') // Set timezone to 'UTC', so tests won't convert to local timezone await i18n.init() await i18n.changeLanguage('en') i18n._appendResources('en', mockEn) diff --git a/src/js/formers/KycGeneralFormer.js b/src/js/formers/KycGeneralFormer.js index 805833836..a0e85f55e 100644 --- a/src/js/formers/KycGeneralFormer.js +++ b/src/js/formers/KycGeneralFormer.js @@ -5,9 +5,9 @@ import { KycGeneralRecord } from '@/js/records/entities/kyc-general.record' import { KycRequestRecord } from '@/js/records/requests/kyc-request.record' import { KycRecoveryRequestRecord } from '@/js/records/requests/kyc-recovery-request.record' import { BlobRecord } from '@/js/records/entities/blob.record' -import { toRFC3339 } from '@/js/helpers/date-helpers' import { createPrivateBlob, getCurrentAccId } from '@/js/helpers/api-helpers' import { isUSResidence, buildKycRecoveryOp } from '@/js/helpers/kyc-helpers' +import { DateUtil } from '@/js/utils' import { keyValues } from '@/key-values' import get from 'lodash/get' @@ -150,7 +150,7 @@ export class KycGeneralFormer extends Former { const blobValue = JSON.stringify({ first_name: str(attrs.firstName), last_name: str(attrs.lastName), - date_of_birth: toRFC3339(attrs.dateOfBirth), + date_of_birth: DateUtil.toRFC3339(attrs.dateOfBirth), address: { line_1: str(address.line1), line_2: str(address.line2), diff --git a/src/js/helpers/date-helpers.js b/src/js/helpers/date-helpers.js deleted file mode 100644 index acbc69aaa..000000000 --- a/src/js/helpers/date-helpers.js +++ /dev/null @@ -1,19 +0,0 @@ -import moment from 'moment' - -export function toRFC3339 (date) { - if (!date) { - return '' - } - - // moment don't allow you to format date with "Z" in the end instead of - // the timezone - return moment(date).format('YYYY-MM-DDTHH:mm:ss') + 'Z' -} - -export function fromRFC3339 (date) { - if (!date) { - return '' - } - - return moment(date).format('YYYY-MM-DD') -} diff --git a/src/js/records/entities/poll.record.js b/src/js/records/entities/poll.record.js index d52444553..6a47eb554 100644 --- a/src/js/records/entities/poll.record.js +++ b/src/js/records/entities/poll.record.js @@ -1,5 +1,5 @@ -import moment from 'moment' import _get from 'lodash/get' +import { DateUtil } from '@/js/utils' const STATES = Object.freeze({ open: 1, @@ -52,16 +52,16 @@ export class PollRecord { } get isStarted () { - return moment().isSameOrAfter(this.startsAt) + return DateUtil.isSameOrAfter(undefined, this.startsAt) } get isEnded () { - return moment().isSameOrAfter(this.endsAt) + return DateUtil.isSameOrAfter(undefined, this.endsAt) } get isInProgress () { return this.isOpen && - moment().isBetween(this.startsAt, this.endsAt) + DateUtil.isBetween(undefined, this.startsAt, this.endsAt) } static get states () { diff --git a/src/js/records/entities/sale.record.js b/src/js/records/entities/sale.record.js index a03c67d46..edc2b1c34 100644 --- a/src/js/records/entities/sale.record.js +++ b/src/js/records/entities/sale.record.js @@ -1,7 +1,7 @@ -import moment from 'moment' import _get from 'lodash/get' import { SALE_DEFINITION_TYPES } from '@/js/const/sale-definition-types.const' +import { DateUtil } from '@/js/utils' const STATES = { Open: 1, @@ -114,16 +114,16 @@ export class SaleRecord { get isOpened () { return this._isInState(STATES.Open) } get isClosed () { return this._isInState(STATES.Closed) } get isCanceled () { return this._isInState(STATES.Canceled) } - get isUpcoming () { return moment(this.startTime).isAfter(moment()) } + get isUpcoming () { return DateUtil.isAfter(this.startTime) } /** progress info: **/ get daysToGo () { - return moment(this.startTime).diff(moment(), 'days') + return DateUtil.diff(this.startTime, undefined, 'days') } get daysToEnd () { - return moment(this.endTime).diff(moment(), 'days') + return DateUtil.diff(this.endTime, undefined, 'days') } get daysAfterEnd () { @@ -135,7 +135,7 @@ export class SaleRecord { } get startsIn () { - return moment(this.startTime).diff(moment().startOf('day'), 'days') + return DateUtil.diff(this.startTime, DateUtil.startOf('day'), 'days') } get hardCapProgress () { diff --git a/src/js/utils/date.util.js b/src/js/utils/date.util.js index 2249d1bb0..04cd0fae6 100644 --- a/src/js/utils/date.util.js +++ b/src/js/utils/date.util.js @@ -1,87 +1,306 @@ -import moment from 'moment' +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +import duration from 'dayjs/plugin/duration' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import calendar from 'dayjs/plugin/calendar' +import isSameOrAfter from 'dayjs/plugin/isSameOrAfter' +import isBetween from 'dayjs/plugin/isBetween' +import updateLocale from 'dayjs/plugin/updateLocale' +import timezone from 'dayjs/plugin/timezone' -const INPUT_DATE_FORMAT = 'DD/MM/YYYY' +import ru from 'dayjs/locale/ru' + +dayjs.extend(utc) +dayjs.extend(duration) +dayjs.extend(customParseFormat) +dayjs.extend(calendar) +dayjs.extend(isSameOrAfter) +dayjs.extend(isBetween) +dayjs.extend(updateLocale) +dayjs.extend(timezone) + +dayjs.updateLocale('ru', { + weekdays: [ + 'Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', + ], + calendar: { + lastDay: '[Вчера в] H:mm', + sameDay: '[Сегодня в] H:mm', + nextDay: '[Завтра в] H:mm', + lastWeek: function () { + const format = 'dddd H:mm' + switch (this.day()) { + case 0: + return `Прошлое ${this.format(format).toLowerCase()}` + case 3: + case 5: + case 6: + return `Прошлая ${this.format(format).toLowerCase()}` + default: + return `Прошлый ${this.format(format).toLowerCase()}` + } + }, + nextWeek: 'dddd H:mm', + sameElse: 'D MMMM YYYY HH:mm', + }, +}) + +const LOCALES = { + ru, +} export class DateUtil { - static get ISOFormat () { - return 'YYYY-MM-DDT00:00:00+00:00' + static _dayjs (date, format) { + return format ? dayjs(date, format) : dayjs(date) } /** - * Returns minimum acceptable date for the app - * @returns {string} + * Returns date instance of DateUtil + * @param {string|date|object} [date] - date + * @param {string} [format] - if date is provided in custom unknown + * format + * @returns {object} + */ + static date (date, format) { + return this._dayjs(date, format) + } + + /** + * Returns date instance of DateUtil in utc mode + * The local time will be kept + * @param {string|date|object} [date] - date + * @param {string} [format] - if date is provided in custom unknown + * format + * @returns {object} + */ + static utc (date, format) { + return this._dayjs(date, format).utc(true) + } + + /** + * Returns a new date instance of DateUtil in the given timezone + * @param {string|date|object} [date] - date + * @param {string} [timezone] - your custom timezone + * @returns {object} */ - static get minDate () { - return this.toISO('01/01/1900') + static tz (date, timezone) { + return dayjs.tz(date, timezone) + } + + /** + * Returns new duration instance of DateUtil + * @param {object} units - config with units as keys + * @returns {object} + */ + static duration (units) { + return dayjs.duration(units) + } + + /** + * Returns milliseconds of duration instance of DateUtil + * @param {object} duration - duration instance + * @returns {number} + */ + static millisecondOf (duration) { + return duration.asMilliseconds() } /** formatters: **/ /** - * Returns provided date in human-friendly format - * @param {string|date} date - date to format - * @param {string|null} [format] - if date is provided in custom unknown - * format + * Returns date in given format + * @param {string|date|object} date - date to format + * @param {string} format - format, e.g. 'MM-DD-YYYY HH:mm:ss' * @returns {string} */ - static toHuman (date, format = null) { - return this._moment(date, format).calendar(null, { - sameDay: '[Today at] HH:mm', - lastDay: '[Yesterday at] HH:mm', - lastWeek: '[Last] dddd [at] HH:mm', - nextWeek: '[Next] dddd [at] HH:mm', - sameElse: 'DD/MM/YYYY', - }) + static format (date, format) { + return this._dayjs(date).format(format) } /** - * Returns provided date in ISO format - * @param {string|date} date - date to format - * @param {string|null} [format] - if date is provided in custom unknown + * Returns provided date in UNIX timestamp (in seconds) + * @param {string|date|object} [date] - date to format + * @param {string} [format] - if date is provided in custom unknown * format * @returns {string} */ - static toISO (date, format = null) { - return this._moment(date, format).format(this.ISOFormat) + static toTimestamp (date, format) { + return this._dayjs(date, format).unix() } /** - * Returns provided date in UNIX timestamp (in seconds) - * @param {string|date} date - date to format - * @param {string|null} [format] - if date is provided in custom unknown - * ormat + * Return instance of the native Date object + * @param {string|date|object} [date] - date to format + * @param {string} [format] - if date is provided in custom unknown + * format + * @returns {date} + */ + static toDate (date, format) { + return this._dayjs(date, format).toDate() + } + + /** + * Returns date as string in ISO 8601 utc format + * @param {string|date|object} [date] - date to format + * @param {string} [format] - if date is provided in custom unknown + * format * @returns {string} */ - static toTimestamp (date, format = null) { - return this._moment(date, format).format('X') + static toISO (date, format) { + return this._dayjs(date, format).toISOString() + } + + /** + * Returns utc date as string in RFC 3399 utc format + * The local time will be kept + * @param {string|date|object} [date] - date to format + * @returns {object} + */ + static toRFC3339 (date) { + if (!date) { + return '' + } + + return this._dayjs(date).utc(true).format('YYYY-MM-DDTHH:mm:ss') + 'Z' } /** - * Returns provided date in miliseconds - * @param {string|date} date - date to format - * @param {string|null} [format] - if date is provided in custom unknown + * Returns provided date in human-friendly format + * @param {string|date|object} [date] - date to format + * @param {string} [format] - if date is provided in custom unknown * format + * @param {object} [calendar] - config of specifying calendar output formats, + * e.g. { nextDay: '[Tomorrow]' } * @returns {string} */ - static toMs (date, format = null) { - return this._moment(date, format).format('x') + static toHuman (date, format, calendar) { + return this._dayjs(date, format).calendar(dayjs.tz(), calendar) + } + + /** + * Returns the difference between two date-time in the specified unit + * Default: returns difference in the milliseconds + * @param {string|number|date|object} [targetDate] - target date + * @param {string|number|date|object} [comparisonDate] - comparison date + * @param {string} [unit] - string of unit time (e.g. 'year') + * @param {boolean} [isFloat] - set it if you want a floating point number + * @returns {number} + */ + static diff (targetDate, comparisonDate, unit, isFloat = false) { + return this._dayjs(targetDate).diff( + this._dayjs(comparisonDate), unit, isFloat + ) + } + + /** queries: **/ + + /** + * Returns true/false depending on whether the target date is earlier than the + * comparison date + * @param {string|number|date|object} [targetDate] - target date + * @param {string|number|date|object} [comparisonDate] - comparison date + * @returns {boolean} + */ + static isBefore (targetDate, comparisonDate) { + return this._dayjs(targetDate).isBefore(comparisonDate) + } + + /** + * Returns true/false depending on whether the target date is later than the + * comparison date + * @param {string|number|date|object} [targetDate] - target date + * @param {string|number|date|object} [comparisonDate] - comparison date + * @returns {boolean} + */ + static isAfter (targetDate, comparisonDate) { + return this._dayjs(targetDate).isAfter(comparisonDate) + } + + /** + * Returns true/false depending on whether the target date is same or is later + * than the comparsion date + * @param {string|number|date|object} [targetDate] - target date + * @param {string|number|date|object} [comparisonDate] - comparison date + * @returns {boolean} + */ + static isSameOrAfter (targetDate, comparisonDate) { + return this._dayjs(targetDate).isSameOrAfter(comparisonDate) + } + + /** + * Returns true/false depending on whether the target date is in the given + * range + * @param {string|number|date|object} [targetDate] - target date + * @param {string|number|date|object} [startDate] - start date of range + * @param {string|number|date|object} [endDate] - end date of range + * @param {string} [unit] - string of unit time (e.g. 'year') + * @param {string} [inclusivity] - inclusivity, e.g. + * '()' or '[)' or '(]' or '[]' + * @returns {boolean} + */ + static isBetween (targetDate, startDate, endDate, unit, inclusivity) { + return this._dayjs(targetDate).isBetween( + startDate, endDate, unit, inclusivity + ) } + /** manipulations: **/ + /** - * Returns provided date in input format - * @param {string|date} date - date to format - * @param {string|null} [format] - if date is provided in custom unknown + * Returns a cloned instance of DateUtil and set it to the start of a unit + * of time + * @param {string} unit - string of unit time (e.g. 'year') + * @param {string|number|date|object} [date] - date to manipulate + * @param {string} [format] - if date is provided in custom unknown * format + * @returns {object} + */ + static startOf (unit, date, format) { + return this._dayjs(date, format).startOf(unit) + } + + /** + * Returns a new date instance of DateUtil by adding time unit to date + * @param {string|number|date|object} targetDate - target date + * @param {number} value - amount of time unit + * @param {string} [unit] - string of unit time (e.g. 'year') + * @returns {object} + */ + static add (targetDate, value, unit) { + return this._dayjs(targetDate).add(value, unit) + } + + /** + * Returns a new date instance of DateUtil by subtracting time unit from date + * @param {string|number|date|object} targetDate - target date + * @param {number} value - amount of time unit + * @param {string} [unit] - string of unit time (e.g. 'year') + * @returns {object} + */ + static subtract (targetDate, value, unit) { + return this._dayjs(targetDate).subtract(value, unit) + } + + /** + * Sets a new locale and returns the name of new locale + * @param {string|object} [preset] - a name of the new locale to set + * the locale from a preset, or an object with its own locale properties + * @param {object} [object] - object with own locale properties in case + * if name of locale defined by first parameter + * @param {boolean} [isLocal] * @returns {string} */ - static toInput (date, format = null) { - return this._moment(date, format).format(INPUT_DATE_FORMAT) + static locale (preset, object, isLocal = false) { + return !object && typeof preset === 'string' + ? dayjs.locale(LOCALES[preset]) + : dayjs.locale(preset, object, isLocal) } - static _moment (date, format) { - if (format) { - return moment(date, format) - } - return moment(date) + /** + * Change default timezone from local time zone to your custom timezone + * @param {string} [timezone] - your custom timezone + */ + static setDefaultTimezone (timezone) { + dayjs.tz.setDefault(timezone) } } diff --git a/src/main.js b/src/main.js index c03325c6b..79a0b26b3 100644 --- a/src/main.js +++ b/src/main.js @@ -8,7 +8,6 @@ import VueHead from 'vue-head' import log from 'loglevel' import config from './config' import NProgress from 'nprogress' -import moment from 'moment' import router from '@/vue-router' import { buildStore } from '@/vuex' @@ -29,6 +28,7 @@ import { formatDateDMYT } from '@/vue/filters/formatDateDMYT' import { abbreviate } from '@/vue/filters/abbreviate' import { cropAddress } from '@/vue/filters/cropAddress' import { ErrorTracker } from '@/js/helpers/error-tracker' +import { DateUtil } from '@/js/utils' import { vueRoutes } from './vue-router/routes' import { useBrowserUpdateBanner } from './browser-update' import { keyValues } from '@/key-values' @@ -41,7 +41,7 @@ async function init () { await initApi() await keyValues.load() - i18n.onLanguageChanged(lang => moment.locale(lang)) + i18n.onLanguageChanged(lang => DateUtil.locale(lang)) await i18n.init() log.setDefaultLevel(config.LOG_LEVEL) diff --git a/src/validators.js b/src/validators.js index 127b7d895..c53c3b983 100644 --- a/src/validators.js +++ b/src/validators.js @@ -1,12 +1,11 @@ import WAValidator from 'wallet-address-validator' -import moment from 'moment' import iban from 'iban' import cardValidator from 'card-validator' import _isString from 'lodash/isString' import { base, Document } from '@tokend/js-sdk' -import { MathUtil } from '@/js/utils' +import { MathUtil, DateUtil } from '@/js/utils' import * as validators from 'vuelidate/lib/validators' @@ -35,10 +34,10 @@ export const amountRange = (from, to) => value => MathUtil.compare(to, value) >= 0 ) export const minDate = (minDate) => value => { - return moment(value).isAfter(moment(minDate)) + return DateUtil.isAfter(value, minDate) } export const maxDate = (maxDate) => value => { - return moment(value).isBefore(moment(maxDate)) + return DateUtil.isBefore(value, maxDate) } export const address = (asset) => value => { switch (asset) { diff --git a/src/vue/common/TimeoutTicker.vue b/src/vue/common/TimeoutTicker.vue index 61ac6f49d..fe6bcd6db 100644 --- a/src/vue/common/TimeoutTicker.vue +++ b/src/vue/common/TimeoutTicker.vue @@ -11,7 +11,7 @@