feat: convert from using dst offsets to use timezone code

feat: select timezones from current selected country
This commit is contained in:
Sérgio Salgado 2021-11-22 17:30:37 +00:00
parent cc8c48ff4c
commit 7d6fb17158
16 changed files with 86 additions and 117 deletions

View file

@ -1,5 +1,6 @@
const _ = require('lodash/fp') const _ = require('lodash/fp')
const moment = require('moment') const { format } = require('date-fns')
const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz')
const db = require('./db') const db = require('./db')
const pgp = require('pg-promise')() const pgp = require('pg-promise')()
@ -112,10 +113,13 @@ function simpleGetMachineLogs (deviceId, from = new Date(0).toISOString(), until
} }
function logDateFormat (timezone, logs, fields) { function logDateFormat (timezone, logs, fields) {
const offset = timezone.split(':')[1]
return _.map(log => { 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 =>
format(utcToZonedTime(zonedTimeToUtc(log[field], process.env.TZ), timezone), 'yyyy-MM-ddTHH:mm:ss.SSS'),
fields
)
const fieldsToOverride = _.zipObject(fields, values) const fieldsToOverride = _.zipObject(fields, values)
return { return {

View file

@ -230,7 +230,7 @@ function plugins (settings, deviceId) {
const localeConfig = configManager.getLocale(deviceId, settings.config) const localeConfig = configManager.getLocale(deviceId, settings.config)
const fiatCode = localeConfig.fiatCurrency const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = localeConfig.cryptoCurrencies const cryptoCodes = localeConfig.cryptoCurrencies
const timezone = localeConfig.timezone.split(':') const timezone = localeConfig.timezone
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c))
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
@ -239,13 +239,12 @@ function plugins (settings, deviceId) {
const currentConfigVersionPromise = fetchCurrentConfigVersion() const currentConfigVersionPromise = fetchCurrentConfigVersion()
const currentAvailablePromoCodes = loyalty.getNumberOfAvailablePromoCodes() const currentAvailablePromoCodes = loyalty.getNumberOfAvailablePromoCodes()
const supportsBatchingPromise = cryptoCodes.map(c => wallet.supportsBatching(settings, c)) const supportsBatchingPromise = cryptoCodes.map(c => wallet.supportsBatching(settings, c))
const timezoneObj = { utcOffset: timezone[0], dstOffset: timezone[1] }
const promises = [ const promises = [
buildAvailableCassettes(), buildAvailableCassettes(),
pingPromise, pingPromise,
currentConfigVersionPromise, currentConfigVersionPromise,
timezoneObj timezone
].concat( ].concat(
supportsBatchingPromise, supportsBatchingPromise,
tickerPromises, tickerPromises,

View file

@ -18,6 +18,8 @@ const { getCustomerById } = require('../customers')
const machineLoader = require('../machine-loader') const machineLoader = require('../machine-loader')
const { loadLatestConfig } = require('../new-settings-loader') const { loadLatestConfig } = require('../new-settings-loader')
const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz')
function updateCustomer (req, res, next) { function updateCustomer (req, res, next) {
const id = req.params.id const id = req.params.id
const machineVersion = req.query.version const machineVersion = req.query.version
@ -129,14 +131,12 @@ function buildSms (data, receiptOptions) {
.then(([customer, deviceConfig]) => { .then(([customer, deviceConfig]) => {
const formattedTx = _.mapKeys(_.camelCase)(tx) const formattedTx = _.mapKeys(_.camelCase)(tx)
const localeConfig = configManager.getLocale(formattedTx.deviceId, config) const localeConfig = configManager.getLocale(formattedTx.deviceId, config)
const timezone = localeConfig.timezone.split(':') const timezone = localeConfig.timezone
const dstOffset = timezone[1]
const cashInCommission = new BN(1).plus(new BN(formattedTx.commissionPercentage)) const cashInCommission = new BN(1).plus(new BN(formattedTx.commissionPercentage))
const rate = new BN(formattedTx.rawTickerPrice).multipliedBy(cashInCommission).decimalPlaces(2) const rate = new BN(formattedTx.rawTickerPrice).multipliedBy(cashInCommission).decimalPlaces(2)
const date = new Date() const date = utcToZonedTime(zonedTimeToUtc(new Date(), process.env.TZ), timezone)
date.setMinutes(date.getMinutes() + parseInt(dstOffset))
const dateString = `${date.toISOString().replace('T', ' ').slice(0, 19)}` const dateString = `${date.toISOString().replace('T', ' ').slice(0, 19)}`
const data = { const data = {

View file

@ -10465,6 +10465,16 @@
"whatwg-url": "^8.0.0" "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": { "debug": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",

View file

@ -19,6 +19,8 @@
"classnames": "2.2.6", "classnames": "2.2.6",
"countries-and-timezones": "^2.4.0", "countries-and-timezones": "^2.4.0",
"d3": "^6.2.0", "d3": "^6.2.0",
"date-fns": "^2.26.0",
"date-fns-tz": "^1.1.6",
"downshift": "3.3.4", "downshift": "3.3.4",
"file-saver": "2.0.2", "file-saver": "2.0.2",
"formik": "2.2.0", "formik": "2.2.0",
@ -36,8 +38,8 @@
"react": "^16.12.0", "react": "^16.12.0",
"react-copy-to-clipboard": "^5.0.2", "react-copy-to-clipboard": "^5.0.2",
"react-dom": "^16.10.2", "react-dom": "^16.10.2",
"react-material-ui-carousel": "^2.2.7",
"react-dropzone": "^11.4.2", "react-dropzone": "^11.4.2",
"react-material-ui-carousel": "^2.2.7",
"react-number-format": "^4.4.1", "react-number-format": "^4.4.1",
"react-otp-input": "^2.3.0", "react-otp-input": "^2.3.0",
"react-router-dom": "5.1.2", "react-router-dom": "5.1.2",

View file

@ -31,12 +31,12 @@ const GraphTooltip = ({
formatDate( formatDate(
dateInterval[1], dateInterval[1],
null, null,
period.code === 'day' ? 'MMM D, HH:mm' : 'MMM D' period.code === 'day' ? 'MMM d, HH:mm' : 'MMM d'
), ),
formatDate( formatDate(
dateInterval[0], dateInterval[0],
null, null,
period.code === 'day' ? 'HH:mm' : 'MMM D' period.code === 'day' ? 'HH:mm' : 'MMM d'
) )
] ]
: [ : [

View file

@ -1,5 +1,6 @@
import { Box } from '@material-ui/core' import { Box } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { getTimezoneOffset } from 'date-fns-tz'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
@ -47,7 +48,7 @@ const HourOfDayBarGraphHeader = ({
cashOut: <div className={classes.cashOutIcon}></div> cashOut: <div className={classes.cashOutIcon}></div>
} }
const offset = parseInt(timezone.split(':')[1]) * MINUTE const offset = getTimezoneOffset(timezone)
const txsPerWeekday = R.reduce( const txsPerWeekday = R.reduce(
(acc, value) => { (acc, value) => {

View file

@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import * as d3 from 'd3' import * as d3 from 'd3'
import { getTimezoneOffset } from 'date-fns-tz'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react' import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react'
@ -38,7 +39,7 @@ const Graph = ({
[] []
) )
const offset = parseInt(timezone.split(':')[1]) * MINUTE const offset = getTimezoneOffset(timezone)
const getTickIntervals = (domain, interval) => { const getTickIntervals = (domain, interval) => {
const ticks = [] const ticks = []

View file

@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import * as d3 from 'd3' import * as d3 from 'd3'
import { getTimezoneOffset } from 'date-fns-tz'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react' import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react'
@ -40,7 +41,7 @@ const Graph = ({
[] []
) )
const offset = parseInt(timezone.split(':')[1]) * MINUTE const offset = getTimezoneOffset(timezone)
const NOW = Date.now() + offset const NOW = Date.now() + offset
const periodDomains = { const periodDomains = {

View file

@ -1,4 +1,5 @@
import * as d3 from 'd3' import * as d3 from 'd3'
import { getTimezoneOffset } from 'date-fns-tz'
import moment from 'moment' import moment from 'moment'
import React, { useEffect, useRef, useCallback } from 'react' import React, { useEffect, useRef, useCallback } from 'react'
@ -11,7 +12,7 @@ const RefScatterplot = ({ data: realData, timeFrame, timezone }) => {
const margin = { top: 25, right: 0, bottom: 25, left: 15 } const margin = { top: 25, right: 0, bottom: 25, left: 15 }
const width = 555 - margin.left - margin.right const width = 555 - margin.left - margin.right
const height = 150 - margin.top - margin.bottom const height = 150 - margin.top - margin.bottom
const dstOffset = parseInt(timezone.split(':')[1]) const offset = getTimezoneOffset(timezone)
// finds maximum value for the Y axis. Minimum value is 100. If value is multiple of 1000, add 100 // finds maximum value for the Y axis. Minimum value is 100. If value is multiple of 1000, add 100
// (this is because the Y axis looks best with multiples of 100) // (this is because the Y axis looks best with multiples of 100)
const findMaxY = () => { const findMaxY = () => {
@ -32,7 +33,7 @@ const RefScatterplot = ({ data: realData, timeFrame, timezone }) => {
default: default:
return moment return moment
.utc(v) .utc(v)
.add(dstOffset, 'minutes') .add(offset, 'minutes')
.format('HH:mm') .format('HH:mm')
} }
} }

View file

@ -158,6 +158,8 @@ const Locales = ({ name: SCREEN_KEY }) => {
setValue(curr) setValue(curr)
} }
console.log('config', config)
const onEditingDefault = (it, editing) => setEditingDefault(editing) const onEditingDefault = (it, editing) => setEditingDefault(editing)
const onEditingOverrides = (it, editing) => setEditingOverrides(editing) const onEditingOverrides = (it, editing) => setEditingOverrides(editing)
@ -187,7 +189,7 @@ const Locales = ({ name: SCREEN_KEY }) => {
save={handleSave} save={handleSave}
validationSchema={LocaleSchema} validationSchema={LocaleSchema}
data={R.of(locale)} data={R.of(locale)}
elements={mainFields(data, onChangeCoin)} elements={mainFields(data, onChangeCoin, R.of(locale))}
setEditing={onEditingDefault} setEditing={onEditingDefault}
forceDisable={isEditingOverrides} forceDisable={isEditingOverrides}
/> />

View file

@ -1,18 +1,19 @@
import * as ct from 'countries-and-timezones' import * as ct from 'countries-and-timezones'
// import { useFormikContext } from 'formik'
import * as R from 'ramda' import * as R from 'ramda'
import * as Yup from 'yup' import * as Yup from 'yup'
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js' import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
import { getTzLabels } from 'src/utils/timezones' // import { getTzLabels } from 'src/utils/timezones'
const getFields = (getData, names, onChange, auxElements = []) => { const getFields = (getData, names, onChange, auxElements = [], locale) => {
return R.filter( return R.filter(
it => R.includes(it.name, names), it => R.includes(it.name, names),
allFields(getData, onChange, auxElements) allFields(getData, onChange, auxElements, locale)
) )
} }
const allFields = (getData, onChange, auxElements = []) => { const allFields = (getData, onChange, auxElements = [], locale) => {
const getView = (data, code, compare) => it => { const getView = (data, code, compare) => it => {
if (!data) return '' if (!data) return ''
@ -33,14 +34,19 @@ const allFields = (getData, onChange, auxElements = []) => {
const suggestionFilter = it => const suggestionFilter = it =>
R.differenceWith((x, y) => x.deviceId === y, it, overridenMachines) R.differenceWith((x, y) => x.deviceId === y, it, overridenMachines)
const localeData = (locale && locale[0]) || {}
const machineData = getData(['machines']) const machineData = getData(['machines'])
const countryData = getData(['countries']) const countryData = getData(['countries'])
const currencyData = getData(['currencies']) const currencyData = getData(['currencies'])
const languageData = getData(['languages']) const languageData = getData(['languages'])
const cryptoData = getData(['cryptoCurrencies']) const cryptoData = getData(['cryptoCurrencies'])
const timezonesData = R.values(ct.getAllTimezones())
const tzLabels = getTzLabels(timezonesData) const countryTimezones = ct.getTimezonesForCountry(localeData?.country) ?? []
const timezonesData =
R.values(
countryTimezones.map(it => ({ label: it.name, code: it.name })) ?? []
) ?? []
const findSuggestion = it => { const findSuggestion = it => {
const machine = R.find(R.propEq('deviceId', it.machine))(machineData) const machine = R.find(R.propEq('deviceId', it.machine))(machineData)
@ -117,10 +123,10 @@ const allFields = (getData, onChange, auxElements = []) => {
name: 'timezone', name: 'timezone',
width: 320, width: 320,
size: 'sm', size: 'sm',
view: getView(tzLabels, 'label'), view: getView(timezonesData, 'label'),
input: Autocomplete, input: Autocomplete,
inputProps: { inputProps: {
options: tzLabels, options: timezonesData,
valueProp: 'code', valueProp: 'code',
labelProp: 'label' labelProp: 'label'
} }
@ -128,13 +134,15 @@ const allFields = (getData, onChange, auxElements = []) => {
] ]
} }
const mainFields = (auxData, configureCoin) => { const mainFields = (auxData, configureCoin, locale) => {
const getData = R.path(R.__, auxData) const getData = R.path(R.__, auxData)
return getFields( return getFields(
getData, getData,
['country', 'fiatCurrency', 'languages', 'cryptoCurrencies', 'timezone'], ['country', 'fiatCurrency', 'languages', 'cryptoCurrencies', 'timezone'],
configureCoin configureCoin,
undefined,
locale
) )
} }

View file

@ -218,7 +218,7 @@ const Transactions = () => {
{ {
header: 'Date (UTC)', header: 'Date (UTC)',
view: it => view: it =>
timezone && formatDate(it.created, timezone, 'YYYY-MM-DD HH:mm:ss'), timezone && formatDate(it.created, timezone, 'yyyy-MM-dd HH:mm:ss'),
textAlign: 'right', textAlign: 'right',
size: 'sm', size: 'sm',
width: 195 width: 195

View file

@ -1,89 +1,23 @@
import moment from 'moment' import { format } from 'date-fns'
import * as R from 'ramda' import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
// import moment from 'moment'
// import * as R from 'ramda'
const getPossibleUTCDSTPairs = timezones => // const buildLabel = tz => {
R.map( // return `(UTC${tz.utcOffsetStr}) ${R.map(it => it.city, tz.cities).join(', ')}`
it => ({ // }
utcOffset: it.utcOffset,
dstOffset: it.dstOffset,
utcOffsetStr: it.utcOffsetStr,
dstOffsetStr: it.dstOffsetStr
}),
R.uniqBy(
it => [it.utcOffset, it.dstOffset, it.utcOffsetStr, it.dstOffsetStr],
timezones
)
)
const getFormattedTimezones = timezones => const formatDate = (date, timezone, pattern) => {
R.sort( const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
R.ascend(R.prop('utcOffset')), const newDate = utcToZonedTime(
R.map( zonedTimeToUtc(date, browserTimezone),
it => ({ timezone
utcOffset: it.utcOffset,
dstOffset: it.dstOffset,
utcOffsetStr: it.utcOffsetStr,
dstOffsetStr: it.dstOffsetStr,
cities: R.map(
ite => {
const regionCityPair = R.split('/', ite.name)
return {
region: regionCityPair[0],
city: R.replace(/_/g, ' ', regionCityPair[1]),
country: ite.country
}
},
R.filter(
itx =>
R.eqProps('utcOffset', it, itx) &&
R.eqProps('dstOffset', it, itx) &&
!R.isNil(itx.country) &&
!R.includes('Etc', itx.name) &&
R.includes('/', itx.name),
timezones
)
)
}),
getPossibleUTCDSTPairs(timezones)
)
) )
return format(newDate, pattern)
const getFinalTimezones = timezones => {
const formattedTimezones = getFormattedTimezones(timezones)
const nonEmptyTimezones = R.filter(
it => !R.isEmpty(it.cities),
formattedTimezones
)
const nonDuplicateCities = R.map(
it => ({
...it,
cities: R.uniqBy(R.prop('country'), R.uniqBy(R.prop('city'), it.cities))
}),
nonEmptyTimezones
)
return nonDuplicateCities
} }
const buildLabel = tz => { const formatDateNonUtc = (date, pattern) => {
return `(UTC${tz.utcOffsetStr}) ${R.map(it => it.city, tz.cities).join(', ')}` return format(date, pattern)
} }
const getTzLabels = timezones => export { formatDate, formatDateNonUtc }
R.map(
it => ({ label: buildLabel(it), code: `${it.utcOffset}:${it.dstOffset}` }),
getFinalTimezones(timezones)
)
const formatDate = (date, timezoneCode, format) => {
const dstOffset = timezoneCode?.split(':')[1] ?? 0
return moment
.utc(date)
.utcOffset(parseInt(dstOffset))
.format(format)
}
const formatDateNonUtc = (date, format) => {
return moment(date).format(format)
}
export { getTzLabels, formatDate, formatDateNonUtc }

12
package-lock.json generated
View file

@ -8464,10 +8464,14 @@
"integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ=="
}, },
"date-fns": { "date-fns": {
"version": "2.16.1", "version": "2.26.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.26.0.tgz",
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", "integrity": "sha512-VQI812dRi3cusdY/fhoBKvc6l2W8BPWU1FNVnFH9Nttjx4AFBRzfSVb/Eyc7jBT6e9sg1XtAGsYpBQ6c/jygbg=="
"dev": true },
"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=="
}, },
"date-time": { "date-time": {
"version": "2.1.0", "version": "2.1.0",

View file

@ -26,6 +26,8 @@
"cookie-parser": "^1.4.3", "cookie-parser": "^1.4.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dataloader": "^2.0.0", "dataloader": "^2.0.0",
"date-fns": "^2.26.0",
"date-fns-tz": "^1.1.6",
"ethereumjs-tx": "^1.3.3", "ethereumjs-tx": "^1.3.3",
"ethereumjs-util": "^5.2.0", "ethereumjs-util": "^5.2.0",
"ethereumjs-wallet": "^0.6.3", "ethereumjs-wallet": "^0.6.3",
@ -68,8 +70,8 @@
"pify": "^3.0.0", "pify": "^3.0.0",
"pretty-ms": "^2.1.0", "pretty-ms": "^2.1.0",
"promise-sequential": "^1.1.1", "promise-sequential": "^1.1.1",
"request-promise": "^4.2.6",
"queue-promise": "^2.2.1", "queue-promise": "^2.2.1",
"request-promise": "^4.2.6",
"semver": "^7.1.3", "semver": "^7.1.3",
"serve-static": "^1.12.4", "serve-static": "^1.12.4",
"socket.io": "^2.0.3", "socket.io": "^2.0.3",