diff --git a/lib/admin/server.js b/lib/admin/server.js index 0aa93706..cc5d5415 100644 --- a/lib/admin/server.js +++ b/lib/admin/server.js @@ -1,5 +1,5 @@ +const { intervalToDuration, secondsToMilliseconds, formatDuration } = require('date-fns/fp') const _ = require('lodash/fp') -const moment = require('moment') const ticker = require('../ticker') const settingsLoader = require('./settings-loader') @@ -37,8 +37,8 @@ function machinesLastPing () { if (downRows.length === 1) { const row = downRows[0] - const age = moment.duration(row.age, 'seconds') - return `${row.name} down for ${age.humanize()}` + const age = intervalToDuration({ start: 0, end: secondsToMilliseconds(row.age) }) + return `${row.name} down for ${formatDuration(age)}` } return 'Multiple machines down' @@ -54,9 +54,9 @@ function status () { return Promise.all([checkWasConfigured(), db.oneOrNone(sql, ['ping']), machinesLastPing()]) .then(([wasConfigured, statusRow, machineStatus]) => { - const age = statusRow && moment.duration(statusRow.age, 'seconds') + const age = statusRow && intervalToDuration({ start: 0, end: secondsToMilliseconds(statusRow.age) }) const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false - const lastPing = statusRow && age.humanize() + const lastPing = statusRow && formatDuration(age) return settingsLoader.loadLatest() .catch(() => null) diff --git a/lib/customers.js b/lib/customers.js index c7aa00e3..28808f2e 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -6,7 +6,7 @@ const makeDir = require('make-dir') const path = require('path') const fs = require('fs') const util = require('util') -const moment = require('moment') +const { sub, differenceInHours } = require('date-fns/fp') const db = require('./db') const BN = require('./bn') @@ -215,14 +215,14 @@ function getDailyVolumeMinusCurrentTxQueries (id, txId) { } function getHoursTillLimitClear (cashInDate, cashOutDate) { - let startDate = moment() - startDate = startDate.subtract(1, 'days') + let startDate = new Date() + startDate = sub({ days: 1 }, startDate) - const cashInMoment = moment(cashInDate || startDate) - const cashOutMoment = moment(cashOutDate || startDate) + const cashInMoment = new Date(cashInDate || startDate) + const cashOutMoment = new Date(cashOutDate || startDate) - const cashInDuration = moment.duration(cashInMoment.diff(startDate)).asHours() - const cashOutDuration = moment.duration(cashOutMoment.diff(startDate)).asHours() + const cashInDuration = differenceInHours(cashInMoment, startDate) + const cashOutDuration = differenceInHours(cashOutMoment, startDate) return _.ceil(_.max([cashInDuration, cashOutDuration, 0])) } diff --git a/lib/logs.js b/lib/logs.js index 50e806b7..3b8421bf 100644 --- a/lib/logs.js +++ b/lib/logs.js @@ -1,5 +1,6 @@ const _ = require('lodash/fp') -const moment = require('moment') +const { format } = require('date-fns/fp') +const { utcToZonedTime } = require('date-fns-tz/fp') const db = require('./db') const pgp = require('pg-promise')() @@ -112,12 +113,17 @@ function simpleGetMachineLogs (deviceId, from = new Date(0).toISOString(), until } function logDateFormat (timezone, logs, fields) { - const offset = timezone.split(':')[1] - return _.map(log => { - const values = _.map(field => moment.utc(log[field]).utcOffset(parseInt(offset)).format('YYYY-MM-DDTHH:mm:ss.SSS'), fields) + const values = _.map( + field => + { + if (_.isNil(log[field])) return null + const date = utcToZonedTime(timezone, log[field]) + return `${format('yyyy-MM-dd', date)}T${format('HH:mm:ss.SSS', date)}` + }, + fields + ) const fieldsToOverride = _.zipObject(fields, values) - return { ...log, ...fieldsToOverride diff --git a/lib/plugins.js b/lib/plugins.js index fe7bd663..6406d657 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -3,6 +3,8 @@ const argv = require('minimist')(process.argv.slice(2)) const crypto = require('crypto') const pgp = require('pg-promise')() const dateFormat = require('dateformat') +const { getTimezoneOffset } = require('date-fns-tz') +const { millisecondsToMinutes } = require('date-fns/fp') const BN = require('./bn') const dbm = require('./postgresql_interface') @@ -230,7 +232,7 @@ function plugins (settings, deviceId) { const localeConfig = configManager.getLocale(deviceId, settings.config) const fiatCode = localeConfig.fiatCurrency const cryptoCodes = localeConfig.cryptoCurrencies - const timezone = localeConfig.timezone.split(':') + const timezone = millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)) const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) @@ -239,13 +241,12 @@ function plugins (settings, deviceId) { const currentConfigVersionPromise = fetchCurrentConfigVersion() const currentAvailablePromoCodes = loyalty.getNumberOfAvailablePromoCodes() const supportsBatchingPromise = cryptoCodes.map(c => wallet.supportsBatching(settings, c)) - const timezoneObj = { utcOffset: timezone[0], dstOffset: timezone[1] } const promises = [ buildAvailableCassettes(), pingPromise, currentConfigVersionPromise, - timezoneObj + timezone ].concat( supportsBatchingPromise, tickerPromises, diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 1093a91d..2ea098e3 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -4,6 +4,7 @@ const semver = require('semver') const sms = require('../sms') const _ = require('lodash/fp') const BN = require('../bn') +const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz/fp') const compliance = require('../compliance') const complianceTriggers = require('../compliance-triggers') @@ -129,14 +130,12 @@ function buildSms (data, receiptOptions) { .then(([customer, deviceConfig]) => { const formattedTx = _.mapKeys(_.camelCase)(tx) const localeConfig = configManager.getLocale(formattedTx.deviceId, config) - const timezone = localeConfig.timezone.split(':') - const dstOffset = timezone[1] + const timezone = localeConfig.timezone const cashInCommission = new BN(1).plus(new BN(formattedTx.commissionPercentage)) const rate = new BN(formattedTx.rawTickerPrice).multipliedBy(cashInCommission).decimalPlaces(2) - const date = new Date() - date.setMinutes(date.getMinutes() + parseInt(dstOffset)) + const date = utcToZonedTime(timezone, zonedTimeToUtc(process.env.TZ, new Date())) const dateString = `${date.toISOString().replace('T', ' ').slice(0, 19)}` const data = { diff --git a/new-lamassu-admin/package-lock.json b/new-lamassu-admin/package-lock.json index cad5fe31..161c8a4e 100644 --- a/new-lamassu-admin/package-lock.json +++ b/new-lamassu-admin/package-lock.json @@ -10465,6 +10465,16 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.26.0.tgz", + "integrity": "sha512-VQI812dRi3cusdY/fhoBKvc6l2W8BPWU1FNVnFH9Nttjx4AFBRzfSVb/Eyc7jBT6e9sg1XtAGsYpBQ6c/jygbg==" + }, + "date-fns-tz": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.1.6.tgz", + "integrity": "sha512-nyy+URfFI3KUY7udEJozcoftju+KduaqkVfwyTIE0traBiVye09QnyWKLZK7drRr5h9B7sPJITmQnS3U6YOdQg==" + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -18316,11 +18326,6 @@ "minimist": "^1.2.5" } }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/new-lamassu-admin/package.json b/new-lamassu-admin/package.json index 6d4a0e43..bb59ee3d 100644 --- a/new-lamassu-admin/package.json +++ b/new-lamassu-admin/package.json @@ -19,6 +19,8 @@ "classnames": "2.2.6", "countries-and-timezones": "^2.4.0", "d3": "^6.2.0", + "date-fns": "^2.26.0", + "date-fns-tz": "^1.1.6", "downshift": "3.3.4", "file-saver": "2.0.2", "formik": "2.2.0", @@ -29,15 +31,14 @@ "lamassu-coins": "git+https://github.com/lamassu/lamassu-coins.git", "libphonenumber-js": "^1.7.50", "match-sorter": "^4.2.0", - "moment": "2.24.0", "pretty-ms": "^2.1.0", "qrcode.react": "0.9.3", "ramda": "^0.26.1", "react": "^16.12.0", "react-copy-to-clipboard": "^5.0.2", "react-dom": "^16.10.2", - "react-material-ui-carousel": "^2.2.7", "react-dropzone": "^11.4.2", + "react-material-ui-carousel": "^2.2.7", "react-number-format": "^4.4.1", "react-otp-input": "^2.3.0", "react-router-dom": "5.1.2", diff --git a/new-lamassu-admin/src/components/LogsDownloaderPopper.js b/new-lamassu-admin/src/components/LogsDownloaderPopper.js index 9d64082f..9c02760a 100644 --- a/new-lamassu-admin/src/components/LogsDownloaderPopper.js +++ b/new-lamassu-admin/src/components/LogsDownloaderPopper.js @@ -1,8 +1,8 @@ import { useLazyQuery } from '@apollo/react-hooks' import { makeStyles, ClickAwayListener } from '@material-ui/core' import classnames from 'classnames' +import { format, isSameDay } from 'date-fns/fp' import FileSaver from 'file-saver' -import moment from 'moment' import * as R from 'ramda' import React, { useState, useCallback } from 'react' @@ -65,12 +65,13 @@ const DateContainer = ({ date, children, ...props }) => { {date && ( <>
-
{date.format('D')}
+
{format('d', date)}
- {`${date.format( - 'MMM' - )} ${date.format('YYYY')}`} - {date.format('dddd')} + {`${format( + 'MMM', + date + )} ${format('yyyy', date)}`} + {format('EEEE', date)}
@@ -186,8 +187,8 @@ const LogsDownloaderPopover = ({ } if (!range || !range.from) return - if (range.from && !range.until) range.until = moment() - if (moment(range.from).isSame(range.until, 'day')) range.until = moment() + if (range.from && !range.until) range.until = new Date() + if (isSameDay(range.until, range.from)) range.until = new Date() if (selectedRadio === RANGE) { fetchLogs({ @@ -203,7 +204,7 @@ const LogsDownloaderPopover = ({ const createLogsFile = (logs, range) => { const formatDateFile = date => { - return formatDate(date, timezone, 'YYYY-MM-DD_HH-mm') + return formatDate(date, timezone, 'yyyy-MM-dd_HH-mm') } const blob = new window.Blob([logs], { @@ -277,7 +278,7 @@ const LogsDownloaderPopover = ({ )} diff --git a/new-lamassu-admin/src/components/date-range-picker/Calendar.js b/new-lamassu-admin/src/components/date-range-picker/Calendar.js index f93df5dd..c6151d73 100644 --- a/new-lamassu-admin/src/components/date-range-picker/Calendar.js +++ b/new-lamassu-admin/src/components/date-range-picker/Calendar.js @@ -1,5 +1,18 @@ import { makeStyles } from '@material-ui/core/styles' -import moment from 'moment' +import { + add, + differenceInMonths, + format, + getDay, + getDaysInMonth, + isAfter, + isSameDay, + isSameMonth, + lastDayOfMonth, + startOfMonth, + startOfWeek, + sub +} from 'date-fns/fp' import * as R from 'ramda' import React, { useState } from 'react' @@ -72,49 +85,36 @@ const styles = { const useStyles = makeStyles(styles) const Calendar = ({ minDate, maxDate, handleSelect, ...props }) => { - const [currentDisplayedMonth, setCurrentDisplayedMonth] = useState(moment()) + const [currentDisplayedMonth, setCurrentDisplayedMonth] = useState(new Date()) const classes = useStyles() - const weekdays = moment.weekdaysMin().map(day => day.slice(0, 1)) - const monthLength = month => - Number.parseInt( - moment(month) - .endOf('month') - .format('D') - ) + const weekdays = Array.from(Array(7)).map((_, i) => + format('EEEEE', add({ days: i }, startOfWeek(new Date()))) + ) + + const monthLength = month => getDaysInMonth(month) const monthdays = month => { - const lastMonth = moment(month).subtract(1, 'month') - const lastMonthRange = R.range( - 0, - moment(month) - .startOf('month') - .weekday() - ).reverse() + const lastMonth = sub({ months: 1 }, month) + const lastMonthRange = R.range(0, getDay(startOfMonth(month))).reverse() const lastMonthDays = R.map(i => - moment(lastMonth) - .endOf('month') - .subtract(i, 'days') + sub({ days: i }, lastDayOfMonth(lastMonth)) )(lastMonthRange) const thisMonthRange = R.range(0, monthLength(month)) - const thisMonthDays = R.map(i => - moment(month) - .startOf('month') - .add(i, 'days') - )(thisMonthRange) + const thisMonthDays = R.map(i => add({ days: i }, startOfMonth(month)))( + thisMonthRange + ) - const nextMonth = moment(month).add(1, 'month') + const nextMonth = add({ months: 1 }, month) const nextMonthRange = R.range( 0, 42 - lastMonthDays.length - thisMonthDays.length ) - const nextMonthDays = R.map(i => - moment(nextMonth) - .startOf('month') - .add(i, 'days') - )(nextMonthRange) + const nextMonthDays = R.map(i => add({ days: i }, startOfMonth(nextMonth)))( + nextMonthRange + ) return R.concat(R.concat(lastMonthDays, thisMonthDays), nextMonthDays) } @@ -122,22 +122,24 @@ const Calendar = ({ minDate, maxDate, handleSelect, ...props }) => { const getRow = (month, row) => monthdays(month).slice(row * 7 - 7, row * 7) const handleNavPrev = currentMonth => { - const prevMonth = moment(currentMonth).subtract(1, 'month') + const prevMonth = sub({ months: 1 }, currentMonth) if (!minDate) setCurrentDisplayedMonth(prevMonth) else { setCurrentDisplayedMonth( - prevMonth.isSameOrAfter(minDate, 'month') + isSameMonth(minDate, prevMonth) || + differenceInMonths(minDate, prevMonth) > 0 ? prevMonth : currentDisplayedMonth ) } } const handleNavNext = currentMonth => { - const nextMonth = moment(currentMonth).add(1, 'month') + const nextMonth = add({ months: 1 }, currentMonth) if (!maxDate) setCurrentDisplayedMonth(nextMonth) else { setCurrentDisplayedMonth( - nextMonth.isSameOrBefore(maxDate, 'month') + isSameMonth(maxDate, nextMonth) || + differenceInMonths(nextMonth, maxDate) > 0 ? nextMonth : currentDisplayedMonth ) @@ -153,9 +155,10 @@ const Calendar = ({ minDate, maxDate, handleSelect, ...props }) => { - {`${currentDisplayedMonth.format( - 'MMMM' - )} ${currentDisplayedMonth.format('YYYY')}`} + {`${format('MMMM', currentDisplayedMonth)} ${format( + 'yyyy', + currentDisplayedMonth + )}`}