From 009f050e90b65c94d5332eb9af66121231102624 Mon Sep 17 00:00:00 2001 From: Eleftherios Chaidemenos Date: Tue, 8 Aug 2023 18:33:39 +0300 Subject: [PATCH] Migrate to luxon --- packages/api/package.json | 3 - packages/api/scripts/backfill-daily-data.ts | 14 +- packages/api/scripts/utils/netcdf.ts | 9 +- packages/api/src/sensors/sensors.spec.ts | 18 +- packages/api/src/sites/sites.service.ts | 19 +- packages/api/src/sites/sites.spec.ts | 48 +++-- .../src/time-series/time-series.service.ts | 9 +- .../api/src/time-series/time-series.spec.ts | 22 ++- packages/api/src/utils/dates.ts | 8 +- packages/api/src/utils/hindcast-wind-wave.ts | 6 +- packages/api/src/utils/liveData.ts | 25 ++- packages/api/src/utils/sofar.ts | 6 +- packages/api/src/utils/spotter-time-series.ts | 23 ++- packages/api/src/utils/sst-time-series.ts | 24 ++- .../api/src/utils/uploads/upload-hobo-data.ts | 12 +- .../api/src/workers/backfill-site-data.ts | 14 +- .../api/src/workers/check-buoys-status.ts | 4 +- packages/api/src/workers/dailyData.ts | 16 +- packages/api/test/mock/daily-data.mock.ts | 66 ++++--- packages/api/test/mock/surveys.mock.ts | 6 +- packages/api/test/mock/time-series.mock.ts | 11 +- packages/website/package.json | 4 +- .../src/common/Chart/ChartWithTooltip.tsx | 14 +- .../MultipleSensorsCharts/AnalysisCard.tsx | 8 +- .../Chart/MultipleSensorsCharts/Chart.tsx | 4 +- .../DownloadCSVDialog.tsx | 6 +- .../Chart/MultipleSensorsCharts/helpers.ts | 41 ++-- .../Chart/MultipleSensorsCharts/index.tsx | 181 ++++++++++++------ .../src/common/Chart/Tooltip/index.test.tsx | 2 +- .../src/common/Chart/Tooltip/index.tsx | 8 +- packages/website/src/common/Chart/utils.ts | 43 ++--- .../website/src/common/Datepicker/index.tsx | 10 +- .../SiteDetails/Surveys/SurveyCard/index.tsx | 35 ++-- .../SiteDetails/Surveys/Timeline/Desktop.tsx | 2 +- .../SiteDetails/Surveys/Timeline/Tablet.tsx | 2 +- .../SiteDetails/WaterSampling/index.tsx | 6 +- .../website/src/common/SiteDetails/index.tsx | 2 +- .../website/src/common/SurveyForm/index.tsx | 11 +- packages/website/src/helpers/dates.ts | 68 ++----- .../ConfirmationDialog.tsx | 13 +- .../routes/SiteRoutes/Site/SiteInfo/index.tsx | 2 +- .../src/routes/SiteRoutes/Site/index.tsx | 8 +- .../SiteRoutes/SiteApplication/Form/index.tsx | 5 +- .../SiteRoutes/SurveyPoint/InfoCard/Info.tsx | 2 +- .../SiteRoutes/UploadData/HistoryTable.tsx | 20 +- .../src/routes/Surveys/View/SurveyDetails.tsx | 2 +- .../website/src/routes/Surveys/View/index.tsx | 14 +- yarn.lock | 11 +- 48 files changed, 484 insertions(+), 403 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 4b0f09d63..9e8471aa5 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -93,8 +93,6 @@ "lodash": "^4.17.15", "luxon": "^3.3.0", "md5-file": "^5.0.0", - "moment": "^2.27.0", - "moment-timezone": "^0.5.31", "multer": "^1.4.2", "node-xlsx": "^0.17.2", "objects-to-csv": "^1.3.6", @@ -117,7 +115,6 @@ "@types/jest": "24.9.0", "@types/lodash": "^4.14.156", "@types/luxon": "^3.3.1", - "@types/moment-timezone": "^0.5.13", "@types/multer": "^1.4.3", "@types/node": "^18.16.16", "@types/passport": "^1.0.4", diff --git a/packages/api/scripts/backfill-daily-data.ts b/packages/api/scripts/backfill-daily-data.ts index 2aab4e1c6..038f36259 100644 --- a/packages/api/scripts/backfill-daily-data.ts +++ b/packages/api/scripts/backfill-daily-data.ts @@ -1,6 +1,6 @@ import Bluebird from 'bluebird'; import yargs from 'yargs'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { getSitesDailyData } from '../src/workers/dailyData'; import AqualinkDataSource from '../ormconfig'; @@ -24,19 +24,13 @@ async function run() { const { d: days, s: sites } = argv; const backlogArray = Array.from(Array(days).keys()); const siteIds = sites && sites.map((site) => parseInt(`${site}`, 10)); - const today = moment() - .utc() - .hours(23) - .minutes(59) - .seconds(59) - .milliseconds(999); + const today = DateTime.utc().endOf('day'); const connection = await AqualinkDataSource.initialize(); // eslint-disable-next-line fp/no-mutating-methods await Bluebird.mapSeries(backlogArray.reverse(), async (past) => { - const date = moment(today); - date.day(today.day() - past - 1); + const date = today.set({ day: today.day - past - 1 }); try { - await getSitesDailyData(connection, date.toDate(), siteIds); + await getSitesDailyData(connection, date.toJSDate(), siteIds); } catch (error) { console.error(error); } diff --git a/packages/api/scripts/utils/netcdf.ts b/packages/api/scripts/utils/netcdf.ts index d7ccdefda..602f23616 100644 --- a/packages/api/scripts/utils/netcdf.ts +++ b/packages/api/scripts/utils/netcdf.ts @@ -1,5 +1,5 @@ import { times } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Extent, pointToIndex } from '../../src/utils/coordinates'; let netcdf4; @@ -43,11 +43,10 @@ export function getNOAAData(year: number = 2020, long: number, lat: number) { height, ); - const startDate = moment(new Date(year, 0, 1)); + const startDate = DateTime.fromJSDate(new Date(year, 0, 1)); return times(dateRange, (dateIndex) => { - const date = moment(startDate); - date.day(startDate.day() + dateIndex); + const date = startDate.set({ day: startDate.day + dateIndex }); const data: number[] = variables.sst.readSlice( dateIndex, 1, @@ -57,6 +56,6 @@ export function getNOAAData(year: number = 2020, long: number, lat: number) { 10, ); const filteredData = data.filter((value) => value <= 9999999); - return { date: date.toDate(), satelliteTemperature: filteredData[0] }; + return { date: date.toJSDate(), satelliteTemperature: filteredData[0] }; }); } diff --git a/packages/api/src/sensors/sensors.spec.ts b/packages/api/src/sensors/sensors.spec.ts index 4ca7658bc..edc4ca673 100644 --- a/packages/api/src/sensors/sensors.spec.ts +++ b/packages/api/src/sensors/sensors.spec.ts @@ -1,5 +1,5 @@ import { INestApplication } from '@nestjs/common'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import request from 'supertest'; import { californiaSite } from '../../test/mock/site.mock'; import { TestService } from '../../test/test.service'; @@ -25,8 +25,12 @@ export const sensorTests = () => { const rsp = await request(app.getHttpServer()) .get(`/sensors/${californiaSite.sensorId}/data`) .query({ - startDate: moment().subtract(5, 'days').startOf('day').toISOString(), - endDate: moment().endOf('day').toISOString(), + startDate: DateTime.now() + .minus({ days: 5 }) + .startOf('day') + .toJSDate() + .toISOString(), + endDate: DateTime.now().endOf('day').toJSDate().toISOString(), metrics: Metric.TOP_TEMPERATURE, }); @@ -53,8 +57,12 @@ export const sensorTests = () => { const rsp = await request(app.getHttpServer()) .get(`/sensors/${californiaSite.sensorId}/data`) .query({ - startDate: moment().subtract(5, 'days').startOf('day').toISOString(), - endDate: moment().endOf('day').toISOString(), + startDate: DateTime.now() + .minus({ days: 5 }) + .startOf('day') + .toJSDate() + .toISOString(), + endDate: DateTime.now().endOf('day').toJSDate().toISOString(), metrics: 'invalidMetric', }); diff --git a/packages/api/src/sites/sites.service.ts b/packages/api/src/sites/sites.service.ts index cf973c8b1..7a3e0b125 100644 --- a/packages/api/src/sites/sites.service.ts +++ b/packages/api/src/sites/sites.service.ts @@ -8,8 +8,8 @@ import { import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, Repository } from 'typeorm'; import { omit } from 'lodash'; -import moment from 'moment'; import Bluebird from 'bluebird'; +import { DateTime } from 'luxon'; import { Site, SiteStatus } from './sites.entity'; import { DailyData } from './daily-data.entity'; import { FilterSiteDto } from './dto/filter-site.dto'; @@ -284,7 +284,10 @@ export class SitesService { ): Promise { await getSite(id, this.sitesRepository); - if (!moment(start).isValid() || !moment(end).isValid()) { + if ( + (start && !DateTime.fromISO(start).isValid) || + (end && !DateTime.fromISO(end).isValid) + ) { throw new BadRequestException('Start or end is not a valid date'); } @@ -455,7 +458,7 @@ export class SitesService { id: number, excludeSpotterDatesDto: ExcludeSpotterDatesDto, ) { - const dateFormat = 'MM/DD/YYYY HH:mm'; + const dateFormat = 'LL/dd/yyyy HH:mm'; const { startDate, endDate } = excludeSpotterDatesDto; const site = await getSite(id, this.sitesRepository); @@ -494,11 +497,11 @@ export class SitesService { if (alreadyExists) { throw new ConflictException( - `Exclusion period [${moment(startDate).format(dateFormat)}, ${moment( - endDate, - ).format(dateFormat)}] already exists for spotter ${ - source.sensorId - }.`, + `Exclusion period [${DateTime.fromJSDate(startDate).toFormat( + dateFormat, + )}, ${DateTime.fromJSDate(endDate).toFormat( + dateFormat, + )}] already exists for spotter ${source.sensorId}.`, ); } diff --git a/packages/api/src/sites/sites.spec.ts b/packages/api/src/sites/sites.spec.ts index 904773ec7..d22659fd8 100644 --- a/packages/api/src/sites/sites.spec.ts +++ b/packages/api/src/sites/sites.spec.ts @@ -2,7 +2,7 @@ import request from 'supertest'; import { INestApplication } from '@nestjs/common'; import { omit, sortBy } from 'lodash'; import { DataSource } from 'typeorm'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { TestService } from '../../test/test.service'; import { mockBackfillSiteData, @@ -34,11 +34,23 @@ export const siteTests = () => { let siteId: number; const firstExclusionPeriod = { startDate: null, - endDate: moment().subtract(8, 'days').endOf('day').toISOString(), + endDate: DateTime.now() + .minus({ days: 8 }) + .endOf('day') + .toJSDate() + .toISOString(), }; const secondExclusionPeriod = { - startDate: moment().subtract(6, 'days').startOf('day').toISOString(), - endDate: moment().subtract(4, 'days').endOf('day').toISOString(), + startDate: DateTime.now() + .minus({ days: 6 }) + .startOf('day') + .toJSDate() + .toISOString(), + endDate: DateTime.now() + .minus({ days: 4 }) + .endOf('day') + .toJSDate() + .toISOString(), }; const siteDto = { site: { @@ -117,8 +129,12 @@ export const siteTests = () => { const rsp = await request(app.getHttpServer()) .get(`/sites/${californiaSite.id}/daily_data`) .query({ - start: moment().subtract(5, 'days').startOf('day').toISOString(), - end: moment().endOf('day').toISOString(), + start: DateTime.now() + .minus({ days: 5 }) + .startOf('day') + .toJSDate() + .toISOString(), + end: DateTime.now().endOf('day').toJSDate().toISOString(), }); expect(rsp.status).toBe(200); @@ -208,8 +224,12 @@ export const siteTests = () => { const rsp = await request(app.getHttpServer()) .get(`/sites/${athensSite.id}/spotter_data`) .query({ - startDate: moment().subtract(9, 'days').startOf('day').toISOString(), - endDate: moment().endOf('day').toISOString(), + startDate: DateTime.now() + .minus({ days: 9 }) + .startOf('day') + .toJSDate() + .toISOString(), + endDate: DateTime.now().endOf('day').toJSDate().toISOString(), }); expect(rsp.status).toBe(200); @@ -299,8 +319,8 @@ export const siteTests = () => { const rsp = await request(app.getHttpServer()) .get('/sites/0/daily_data') .query({ - start: moment().subtract(1, 'days').toISOString(), - end: moment().toISOString(), + start: DateTime.now().minus({ days: 1 }).toJSDate().toISOString(), + end: DateTime.now().toJSDate().toISOString(), }); expect(rsp.status).toBe(404); @@ -380,8 +400,8 @@ export const siteTests = () => { const rsp = await request(app.getHttpServer()) .post('/sites/0/exclusion_dates') .send({ - startDate: moment().subtract(1, 'days').toISOString(), - endDate: moment().toISOString(), + startDate: DateTime.now().minus({ days: 1 }).toJSDate().toISOString(), + endDate: DateTime.now().toJSDate().toISOString(), }); expect(rsp.status).toBe(404); @@ -392,8 +412,8 @@ export const siteTests = () => { const rsp = await request(app.getHttpServer()) .post(`/sites/${floridaSite.id}/exclusion_dates`) .send({ - startDate: moment().subtract(1, 'days').toISOString(), - endDate: moment().toISOString(), + startDate: DateTime.now().minus({ days: 1 }).toJSDate().toISOString(), + endDate: DateTime.now().toJSDate().toISOString(), }); expect(rsp.status).toBe(400); diff --git a/packages/api/src/time-series/time-series.service.ts b/packages/api/src/time-series/time-series.service.ts index e11e37237..9af0383d4 100644 --- a/packages/api/src/time-series/time-series.service.ts +++ b/packages/api/src/time-series/time-series.service.ts @@ -9,7 +9,6 @@ import { import { Repository } from 'typeorm'; import Bluebird from 'bluebird'; import type { Response } from 'express'; -import moment from 'moment'; import { BadRequestException, HttpException, @@ -194,8 +193,8 @@ export class TimeSeriesService { timeSeriesRepository: this.timeSeriesRepository, siteId, metrics, - start: chunks[i].start.toISO() as string, - end: chunks[i].end.toISO() as string, + start: chunks[i].start.toJSDate().toISOString(), + end: chunks[i].end.toJSDate().toISOString(), hourly, csv: true, order: 'DESC', @@ -247,9 +246,9 @@ export class TimeSeriesService { closeSync(fd); - const fileName = `data_site_${siteId}_${moment(startDate).format( + const fileName = `data_site_${siteId}_${minDate.toFormat( DATE_FORMAT, - )}_${moment(endDate).format(DATE_FORMAT)}.csv`; + )}_${maxDate.toFormat(DATE_FORMAT)}.csv`; const readStream = createReadStream(tempFileName); diff --git a/packages/api/src/time-series/time-series.spec.ts b/packages/api/src/time-series/time-series.spec.ts index 29332a438..00248a606 100644 --- a/packages/api/src/time-series/time-series.spec.ts +++ b/packages/api/src/time-series/time-series.spec.ts @@ -1,10 +1,10 @@ import request from 'supertest'; import { INestApplication } from '@nestjs/common'; import { max, min, union } from 'lodash'; -import moment from 'moment'; import { join } from 'path'; import { readFileSync } from 'fs'; import * as structuredClone from '@ungap/structured-clone'; +import { DateTime } from 'luxon'; import { TestService } from '../../test/test.service'; import { athensSite, californiaSite } from '../../test/mock/site.mock'; import { athensSurveyPointPiraeus } from '../../test/mock/survey-point.mock'; @@ -102,8 +102,14 @@ export const timeSeriesTests = () => { ) .query({ // Increase the search window to combat precision issues with the dates - start: moment(startDate).subtract(1, 'minute').toISOString(), - end: moment(endDate).add(1, 'day').toISOString(), + start: DateTime.fromISO(startDate) + .minus({ minutes: 1 }) + .toJSDate() + .toISOString(), + end: DateTime.fromISO(endDate) + .plus({ days: 1 }) + .toJSDate() + .toISOString(), metrics: hoboMetrics.concat(NOAAMetrics), hourly: false, }); @@ -129,8 +135,14 @@ export const timeSeriesTests = () => { .get(`/time-series/sites/${californiaSite.id}`) .query({ // Increase the search window to combat precision issues with the dates - start: moment(startDate).subtract(1, 'minute').toISOString(), - end: moment(endDate).add(1, 'day').toISOString(), + start: DateTime.fromISO(startDate) + .minus({ minutes: 1 }) + .toJSDate() + .toISOString(), + end: DateTime.fromISO(endDate) + .plus({ days: 1 }) + .toJSDate() + .toISOString(), metrics: spotterMetrics.concat(NOAAMetrics), hourly: false, }); diff --git a/packages/api/src/utils/dates.ts b/packages/api/src/utils/dates.ts index ecaa8b942..45bcd337c 100644 --- a/packages/api/src/utils/dates.ts +++ b/packages/api/src/utils/dates.ts @@ -1,9 +1,9 @@ -import moment from 'moment-timezone'; +import { DateTime } from 'luxon'; export function getStartEndDate(endDate: Date, hours: number = 24) { - const endMoment = moment(endDate); - const startMoment = endMoment.clone().subtract(hours, 'hours'); - return [startMoment.format(), endMoment.format()]; + const endMoment = DateTime.fromJSDate(endDate); + const startMoment = endMoment.minus({ hours }); + return [startMoment.toString(), endMoment.toString()]; } // Util function to get the [startDate, endDate] time interval for time series data. diff --git a/packages/api/src/utils/hindcast-wind-wave.ts b/packages/api/src/utils/hindcast-wind-wave.ts index 60646d1df..ab99c13bd 100644 --- a/packages/api/src/utils/hindcast-wind-wave.ts +++ b/packages/api/src/utils/hindcast-wind-wave.ts @@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common'; import Bluebird from 'bluebird'; import { Point } from 'geojson'; import { isNil } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { In, Repository } from 'typeorm'; import { SourceType } from '../sites/schemas/source-type.enum'; import { Site } from '../sites/sites.entity'; @@ -160,9 +160,9 @@ export const addWindWaveData = async ( .values([ { site, - timestamp: moment(sofarValue.timestamp) + timestamp: DateTime.fromISO(sofarValue.timestamp) .startOf('minute') - .toDate(), + .toJSDate(), metric, source, value: sofarValue.value, diff --git a/packages/api/src/utils/liveData.ts b/packages/api/src/utils/liveData.ts index e4ec4f89d..3489b16ea 100644 --- a/packages/api/src/utils/liveData.ts +++ b/packages/api/src/utils/liveData.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { Point } from 'geojson'; import { isNil, omitBy, sortBy } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Site } from '../sites/sites.entity'; import { SofarModels, sofarVariableIDs } from './constants'; import { getLatestData, getSofarHindcastData, getSpotterData } from './sofar'; @@ -103,27 +103,24 @@ export const getSstAnomaly = ( } const orderedMontlyMax = sortBy(historicalMonthlyMean, 'month'); - const now = moment().startOf('day'); + const now = DateTime.now().startOf('day'); // The date of the previous value. Subtract 15 days from the current date // and see in which month the result falls. The date we are looking for is // the 15th day of this month. - const previousDate = now - .clone() - .subtract(15, 'days') - .set('date', 15) - .startOf('day'); + const previousDate = now.minus({ days: 15 }).set({ day: 15 }).startOf('day'); // The date of the next value. It must fall on the next month of the previous // value. - const nextDate = previousDate.clone().add(1, 'month'); + const nextDate = previousDate.plus({ months: 1 }); - // We can index `orderedMontlyMax` with `moment.get('month')` since it returns - // a value between 0 and 11, with 0 corresponding to January and 11 corresponding to December - const previousValue = orderedMontlyMax[previousDate.get('month')].temperature; - const previousDistance = now.diff(previousDate, 'days'); - const nextValue = orderedMontlyMax[nextDate.get('month')].temperature; - const nextDistance = nextDate.diff(now, 'days'); + // We can index `orderedMontlyMax` with `DateTime.get('month')` since it returns + // a value between 1 and 12 + const previousValue = + orderedMontlyMax[previousDate.get('month') - 1].temperature; + const previousDistance = now.diff(previousDate, 'days').days; + const nextValue = orderedMontlyMax[nextDate.get('month') - 1].temperature; + const nextDistance = nextDate.diff(now, 'days').days; const deltaDays = previousDistance + nextDistance; const interpolated = diff --git a/packages/api/src/utils/sofar.ts b/packages/api/src/utils/sofar.ts index 7a137bacf..a86242ecd 100644 --- a/packages/api/src/utils/sofar.ts +++ b/packages/api/src/utils/sofar.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ /** Utility function to access the Sofar API and retrieve relevant data. */ import { isNil } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import axios from './retry-axios'; import { getStartEndDate } from './dates'; import { @@ -219,8 +219,8 @@ export async function getSpotterData( endDate && !startDate ? getStartEndDate(endDate) : [ - startDate && moment(startDate).format(), - endDate && moment(endDate).format(), + startDate && DateTime.fromJSDate(startDate).toString(), + endDate && DateTime.fromJSDate(endDate).toString(), ]; const { diff --git a/packages/api/src/utils/spotter-time-series.ts b/packages/api/src/utils/spotter-time-series.ts index 2ce34ef2c..b64d6f736 100644 --- a/packages/api/src/utils/spotter-time-series.ts +++ b/packages/api/src/utils/spotter-time-series.ts @@ -1,10 +1,10 @@ import { Logger } from '@nestjs/common'; import { get, times } from 'lodash'; -import moment from 'moment'; import { In, IsNull, Not, Repository } from 'typeorm'; import Bluebird from 'bluebird'; import { distance } from '@turf/turf'; import { Point } from 'geojson'; +import { DateTime } from 'luxon'; import { Site } from '../sites/sites.entity'; import { Sources } from '../sites/sources.entity'; import { TimeSeries } from '../time-series/time-series.entity'; @@ -71,7 +71,9 @@ const saveDataBatch = ( batch.map((data) => ({ metric, value: data.value, - timestamp: moment(data.timestamp).startOf('minute').toDate(), + timestamp: DateTime.fromISO(data.timestamp) + .startOf('minute') + .toJSDate(), source, })), ) @@ -133,8 +135,14 @@ export const addSpotterData = async ( Bluebird.map( times(days), async (i) => { - const startDate = moment().subtract(i, 'd').startOf('day').toDate(); - const endDate = moment().subtract(i, 'd').endOf('day').toDate(); + const startDate = DateTime.now() + .minus({ days: i }) + .startOf('day') + .toJSDate(); + const endDate = DateTime.now() + .minus({ days: i }) + .endOf('day') + .toJSDate(); if (!site.sensorId) { return DEFAULT_SPOTTER_DATA_VALUE; @@ -212,10 +220,11 @@ export const addSpotterData = async ( }) .then(() => { // After each successful execution, log the event - const startDate = moment() - .subtract(days - 1, 'd') + const startDate = DateTime.now() + .minus({ days: days - 1 }) .startOf('day'); - const endDate = moment().endOf('day'); + + const endDate = DateTime.now().endOf('day'); logger.debug( `Spotter data updated for ${site.sensorId} between ${startDate} and ${endDate}`, ); diff --git a/packages/api/src/utils/sst-time-series.ts b/packages/api/src/utils/sst-time-series.ts index 07de1c6b0..591afe18e 100644 --- a/packages/api/src/utils/sst-time-series.ts +++ b/packages/api/src/utils/sst-time-series.ts @@ -3,8 +3,7 @@ import Bluebird from 'bluebird'; import { In, Repository } from 'typeorm'; import { Point } from 'geojson'; import { flatten, groupBy, isNil, omit, times } from 'lodash'; -import moment from 'moment'; - +import { DateTime } from 'luxon'; import { Site } from '../sites/sites.entity'; import { Sources } from '../sites/sources.entity'; import { TimeSeries } from '../time-series/time-series.entity'; @@ -97,15 +96,14 @@ export const updateSST = async ( async (interval, index) => { const endDate = index !== 0 - ? moment() - .subtract(index * MAX_SOFAR_DATE_DIFF_DAYS, 'd') - // subtract 1 minute to be within the api date diff limit - .subtract(1, 'm') - .format() - : moment().subtract(1, 'm').format(); - const startDate = moment() - .subtract(index * MAX_SOFAR_DATE_DIFF_DAYS + interval, 'd') - .format(); + ? // subtract 1 minute to be within the api date diff limit + DateTime.now() + .minus({ days: index * MAX_SOFAR_DATE_DIFF_DAYS, minutes: 1 }) + .toString() + : DateTime.now().minus({ minutes: 1 }).toString(); + const startDate = DateTime.now() + .minus({ days: index * MAX_SOFAR_DATE_DIFF_DAYS }) + .toString(); const [SofarSSTRaw, sofarDegreeHeatingWeekRaw] = await Promise.all([ // Fetch satellite surface temperature data @@ -266,8 +264,8 @@ export const updateSST = async ( async (i) => { const endDate = i === 0 - ? moment().format() - : moment().subtract(i, 'd').endOf('day').format(); + ? DateTime.now().toString() + : DateTime.now().minus({ days: i }).endOf('day').toString(); logger.log(`Back-filling weekly alert for ${endDate}`); // Calculate max alert by fetching the max alert in the last 7 days diff --git a/packages/api/src/utils/uploads/upload-hobo-data.ts b/packages/api/src/utils/uploads/upload-hobo-data.ts index 1f317845e..664818879 100644 --- a/packages/api/src/utils/uploads/upload-hobo-data.ts +++ b/packages/api/src/utils/uploads/upload-hobo-data.ts @@ -9,11 +9,11 @@ import fs from 'fs'; import path from 'path'; import { CastingContext, CastingFunction } from 'csv-parse'; import { Point, GeoJSON } from 'geojson'; -import moment from 'moment'; import Bluebird from 'bluebird'; import { ExifParserFactory } from 'ts-exif-parser'; import parse from 'csv-parse/lib/sync'; +import { DateTime } from 'luxon'; import { Site, SiteStatus } from '../../sites/sites.entity'; import { SiteSurveyPoint } from '../../site-survey-points/site-survey-points.entity'; import { TimeSeries } from '../../time-series/time-series.entity'; @@ -489,9 +489,9 @@ const parseHoboData = async ( return [parseInt(siteId, 10), 0]; } - const start = moment(startDate.timestamp); - const end = moment(); - const diff = Math.min(end.diff(start, 'd'), 200); + const start = DateTime.fromJSDate(startDate.timestamp); + const end = DateTime.now(); + const diff = Math.min(end.diff(start, 'days').days, 200); return [startDate.source.site.id, diff]; }, @@ -560,8 +560,8 @@ const uploadSitePhotos = async ( ).parse(); const createdDate = data.tags && data.tags.CreateDate - ? moment.unix(data.tags.CreateDate).toDate() - : moment().toDate(); + ? DateTime.fromSeconds(data.tags.CreateDate).toJSDate() + : DateTime.now().toJSDate(); return { imagePath: path.join(colonyFolderPath, image), site: poi.site, diff --git a/packages/api/src/workers/backfill-site-data.ts b/packages/api/src/workers/backfill-site-data.ts index 68425a577..84ce3b811 100644 --- a/packages/api/src/workers/backfill-site-data.ts +++ b/packages/api/src/workers/backfill-site-data.ts @@ -1,26 +1,20 @@ import Bluebird from 'bluebird'; -import moment from 'moment'; import { Logger } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import { DateTime } from 'luxon'; import { getSitesDailyData } from './dailyData'; const logger = new Logger('Backfill Worker'); async function run(siteId: number, days: number, dataSource: DataSource) { const backlogArray = Array.from(Array(days).keys()); - const today = moment() - .utc() - .hours(23) - .minutes(59) - .seconds(59) - .milliseconds(999); + const today = DateTime.utc().endOf('day'); // eslint-disable-next-line fp/no-mutating-methods await Bluebird.mapSeries(backlogArray.reverse(), async (past) => { - const date = moment(today); - date.day(today.day() - past - 1); + const date = today.set({ day: today.day - past - 1 }); try { - await getSitesDailyData(dataSource, date.toDate(), [siteId]); + await getSitesDailyData(dataSource, date.toJSDate(), [siteId]); } catch (error) { logger.error(error); } diff --git a/packages/api/src/workers/check-buoys-status.ts b/packages/api/src/workers/check-buoys-status.ts index 88f7e3260..8748ee4d1 100644 --- a/packages/api/src/workers/check-buoys-status.ts +++ b/packages/api/src/workers/check-buoys-status.ts @@ -1,7 +1,7 @@ import { DataSource, In, MoreThan } from 'typeorm'; import { Logger } from '@nestjs/common'; import { difference } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Site, SiteStatus } from '../sites/sites.entity'; import { SourceType } from '../sites/schemas/source-type.enum'; import { LatestData } from '../time-series/latest-data.entity'; @@ -30,7 +30,7 @@ export async function checkBuoysStatus(connection: DataSource) { source: SourceType.SPOTTER, site: { id: In(siteIds) }, timestamp: MoreThan( - new Date(moment().subtract(2, 'd').format('YYYY-MM-DD')), + DateTime.now().minus({ days: 2 }).startOf('day').toJSDate(), ), }, }); diff --git a/packages/api/src/workers/dailyData.ts b/packages/api/src/workers/dailyData.ts index 06c60ef5a..ee1b78dee 100644 --- a/packages/api/src/workers/dailyData.ts +++ b/packages/api/src/workers/dailyData.ts @@ -12,7 +12,7 @@ import { import { DataSource, In, Repository } from 'typeorm'; import { Point } from 'geojson'; import Bluebird from 'bluebird'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Site } from '../sites/sites.entity'; import { DailyData } from '../sites/daily-data.entity'; import { @@ -371,18 +371,12 @@ export async function getSitesDailyData( } export async function runDailyUpdate(dataSource: DataSource) { - const today = moment() - .utc() - .hours(23) - .minutes(59) - .seconds(59) - .milliseconds(999); + const today = DateTime.utc().endOf('day'); - const yesterday = moment(today); - yesterday.day(today.day() - 1); - console.log(`Daily Update for data ending on ${yesterday.date()}`); + const yesterday = today.set({ day: today.day - 1 }); + console.log(`Daily Update for data ending on ${yesterday.day}`); try { - await getSitesDailyData(dataSource, yesterday.toDate()); + await getSitesDailyData(dataSource, yesterday.toJSDate()); console.log('Completed daily update.'); } catch (error) { console.error(error); diff --git a/packages/api/test/mock/daily-data.mock.ts b/packages/api/test/mock/daily-data.mock.ts index afe833a51..deb22e3bc 100644 --- a/packages/api/test/mock/daily-data.mock.ts +++ b/packages/api/test/mock/daily-data.mock.ts @@ -1,5 +1,5 @@ import { random, times } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { DeepPartial } from 'typeorm'; import { DailyData } from '../../src/sites/daily-data.entity'; import { SofarLiveDataDto } from '../../src/sites/dto/live-data.dto'; @@ -37,49 +37,49 @@ export const getMockLiveData = (siteId: number): SofarLiveDataDto => ({ dailyAlertLevel: random(4), weeklyAlertLevel: random(4), bottomTemperature: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(15, 35, true), }, topTemperature: { value: random(15, 35, true), - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), }, satelliteTemperature: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(15, 35, true), }, degreeHeatingDays: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(70, true), }, waveHeight: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(10, true), }, waveMeanDirection: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(359), }, waveMeanPeriod: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(10), }, windSpeed: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(10, true), }, windDirection: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(359), }, sstAnomaly: random(15, 35, true), spotterPosition: { latitude: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(15, 35, true), }, longitude: { - timestamp: moment().toISOString(), + timestamp: new Date().toISOString(), value: random(15, 35, true), }, }, @@ -89,70 +89,78 @@ export const getMockSpotterData = ( startDate: Date, endDate: Date, ): SpotterData => { - const start = moment(startDate); - const end = moment(endDate); - const diffDays = end.diff(start, 'days'); + const start = DateTime.fromJSDate(startDate); + const end = DateTime.fromJSDate(endDate); + const diffDays = end.diff(start, 'days').days; return { bottomTemperature: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(15, 35, true), })), topTemperature: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(15, 35, true), })), significantWaveHeight: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(15, 35, true), })), waveMeanPeriod: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(15, 35, true), })), waveMeanDirection: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(359), })), windSpeed: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(10, true), })), windDirection: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(359), })), latitude: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(-90, 90, true), })), longitude: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(-180, 180, true), })), barometerTop: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(900, 1100, true), })), barometerBottom: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(900, 1100, true), })), barometricTopDiff: [], surfaceTemperature: times(diffDays, (i) => ({ - timestamp: start.clone().add(i, 'days').toISOString(), + timestamp: start.plus({ days: i }).toJSDate().toISOString(), value: random(900, 1100, true), })), }; }; export const californiaDailyData: DeepPartial[] = times(10, (i) => { - const dataDate = moment().subtract(i, 'd').endOf('day').toISOString(); + const dataDate = DateTime.now() + .minus({ days: i }) + .endOf('day') + .toJSDate() + .toISOString(); return getMockDailyData(dataDate, californiaSite); }); export const athensDailyData: DeepPartial[] = times(10, (i) => { - const dataDate = moment().subtract(i, 'd').endOf('day').toISOString(); + const dataDate = DateTime.now() + .minus({ days: i }) + .endOf('day') + .toJSDate() + .toISOString(); return getMockDailyData(dataDate, athensSite); }); diff --git a/packages/api/test/mock/surveys.mock.ts b/packages/api/test/mock/surveys.mock.ts index b9d337c0d..6d76fa207 100644 --- a/packages/api/test/mock/surveys.mock.ts +++ b/packages/api/test/mock/surveys.mock.ts @@ -1,4 +1,4 @@ -import moment from 'moment'; +import { DateTime } from 'luxon'; import { DeepPartial } from 'typeorm'; import { Survey, WeatherConditions } from '../../src/surveys/surveys.entity'; import { californiaSite } from './site.mock'; @@ -6,7 +6,7 @@ import { siteManagerUserMock } from './user.mock'; export const californiaSurveyOne: DeepPartial = { comments: 'California Survey One', - diveDate: moment().subtract(6, 'days').toISOString(), + diveDate: DateTime.now().minus({ days: 6 }).toJSDate().toISOString(), temperature: null, weatherConditions: WeatherConditions.Calm, user: siteManagerUserMock, @@ -15,7 +15,7 @@ export const californiaSurveyOne: DeepPartial = { export const californiaSurveyTwo: DeepPartial = { comments: 'California Survey Two', - diveDate: moment().subtract(2, 'days').toISOString(), + diveDate: DateTime.now().minus({ days: 2 }).toJSDate().toISOString(), temperature: null, weatherConditions: WeatherConditions.Calm, user: siteManagerUserMock, diff --git a/packages/api/test/mock/time-series.mock.ts b/packages/api/test/mock/time-series.mock.ts index 9a951ff4f..da5565545 100644 --- a/packages/api/test/mock/time-series.mock.ts +++ b/packages/api/test/mock/time-series.mock.ts @@ -1,5 +1,5 @@ import { random, times } from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { DeepPartial } from 'typeorm'; import { SourceType } from '../../src/sites/schemas/source-type.enum'; import { Sources } from '../../src/sites/sources.entity'; @@ -75,11 +75,10 @@ const createTimeSeriesData = ( return metrics .map((metric) => times(10, (i) => { - const date = moment() - .subtract(i, 'days') - .set('hour', random(23)) - .set('minute', random(59)) - .toDate(); + const date = DateTime.now() + .minus({ days: i }) + .set({ hour: random(23), minute: random(59) }) + .toJSDate(); return { timestamp: date, diff --git a/packages/website/package.json b/packages/website/package.json index ad0d8d79f..b4c1bb289 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -38,9 +38,8 @@ "immutable": "^4.0.0-rc.12", "leaflet": "^1.7.1", "lodash": "^4.17.15", + "luxon": "^3.3.0", "material-table": "^1.65.0", - "moment": "^2.27.0", - "moment-timezone": "^0.5.31", "mutationobserver-shim": "^0.3.7", "notistack": "^1.0.10", "react": "^16.13.1", @@ -90,6 +89,7 @@ "@types/immutable": "^3.8.7", "@types/jest": "^24.0.0", "@types/lodash": "^4.14.149", + "@types/luxon": "^3.3.1", "@types/node": "^18.16.16", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", diff --git a/packages/website/src/common/Chart/ChartWithTooltip.tsx b/packages/website/src/common/Chart/ChartWithTooltip.tsx index 04192f6f4..03ca51e6a 100644 --- a/packages/website/src/common/Chart/ChartWithTooltip.tsx +++ b/packages/website/src/common/Chart/ChartWithTooltip.tsx @@ -7,7 +7,6 @@ import React, { import { Line } from 'react-chartjs-2'; import type { ChartTooltipModel } from 'chart.js'; import { head, isNumber, maxBy, minBy } from 'lodash'; -import moment from 'moment'; import Chart, { ChartProps } from '.'; import Tooltip, { TooltipData, TOOLTIP_WIDTH } from './Tooltip'; import { @@ -82,15 +81,10 @@ function ChartWithTooltip({ // We display the tooltip only if there are data to display at this point and it lands // between the chart's X axis limits. - if ( - nValues > 0 && - moment(date).isBetween( - moment(startDate || minDataDate), - moment(endDate || maxDataDate), - undefined, - '[]', - ) - ) { + const start = startDate || minDataDate || ''; + const end = endDate || maxDataDate || ''; + const isBetween = date >= start && date <= end; + if (nValues > 0 && isBetween) { setTooltipPosition({ top, left }); setTooltipData({ ...tooltipData, diff --git a/packages/website/src/common/Chart/MultipleSensorsCharts/AnalysisCard.tsx b/packages/website/src/common/Chart/MultipleSensorsCharts/AnalysisCard.tsx index 0abd79fa7..9ac8aa171 100644 --- a/packages/website/src/common/Chart/MultipleSensorsCharts/AnalysisCard.tsx +++ b/packages/website/src/common/Chart/MultipleSensorsCharts/AnalysisCard.tsx @@ -11,12 +11,12 @@ import { withStyles, WithStyles, } from '@material-ui/core'; -import moment from 'moment'; import { useSelector } from 'react-redux'; import classNames from 'classnames'; import { siteTimeSeriesDataLoadingSelector } from 'store/Sites/selectedSiteSlice'; import { formatNumber } from 'helpers/numberUtils'; +import { DateTime } from 'luxon'; import { calculateCardMetrics } from './helpers'; import { CardColumn } from './types'; import type { Dataset } from '..'; @@ -66,8 +66,10 @@ const AnalysisCard: FC = ({ }), ); - const formattedpickerStartDate = moment(pickerStartDate).format('MM/DD/YYYY'); - const formattedpickerEndDate = moment(pickerEndDate).format('MM/DD/YYYY'); + const formattedpickerStartDate = + DateTime.fromISO(pickerStartDate).toFormat('LL/dd/yyyy'); + const formattedpickerEndDate = + DateTime.fromISO(pickerEndDate).toFormat('LL/dd/yyyy'); return ( dates between{' '} - {moment(startDate).format('MM/DD/YYYY')} + {DateTime.fromISO(startDate).toFormat('LL/dd/yyyy')} {' '} and{' '} - {moment(endDate).format('MM/DD/YYYY')} + {DateTime.fromISO(endDate).toFormat('LL/dd/yyyy')} )} diff --git a/packages/website/src/common/Chart/MultipleSensorsCharts/helpers.ts b/packages/website/src/common/Chart/MultipleSensorsCharts/helpers.ts index f3e362f8c..31a60d175 100644 --- a/packages/website/src/common/Chart/MultipleSensorsCharts/helpers.ts +++ b/packages/website/src/common/Chart/MultipleSensorsCharts/helpers.ts @@ -1,6 +1,5 @@ import { utcToZonedTime } from 'date-fns-tz'; -import { minBy, maxBy, meanBy, inRange, isNumber } from 'lodash'; -import moment from 'moment'; +import { minBy, maxBy, meanBy, isNumber } from 'lodash'; import { DAILY_DATA_CURVE_COLOR, HISTORICAL_MONTHLY_MEAN_COLOR, @@ -26,7 +25,7 @@ import { findMarginalDate, generateHistoricalMonthlyMeanTimestamps, } from 'helpers/dates'; - +import { DateTime } from 'luxon'; import { CardColumn, OceanSenseDataset } from './types'; import type { Dataset } from '../index'; import { @@ -41,12 +40,11 @@ const filterSofarData = return data; } - return data?.filter(({ timestamp }) => - inRange( - moment(timestamp).valueOf(), - moment(from).valueOf(), - moment(to).valueOf() + 1, - ), + return data?.filter( + ({ timestamp }) => + DateTime.fromISO(timestamp).valueOf() >= + DateTime.fromISO(from).valueOf() && + DateTime.fromISO(timestamp).valueOf() <= DateTime.fromISO(to).valueOf(), ); }; @@ -80,11 +78,11 @@ export const calculateCardMetrics = ( // Show at least 3 ticks on the chart export const findChartPeriod = (startDate: string, endDate: string) => { - const from = moment(new Date(startDate).toISOString()); - const to = moment(new Date(endDate).toISOString()); + const from = DateTime.fromISO(startDate); + const to = DateTime.fromISO(endDate); const week = 7; const month = 30; - const diffDays = to.diff(from, 'days'); + const diffDays = to.diff(from, 'days').days; switch (true) { case diffDays < 2: @@ -178,10 +176,14 @@ export const availableRangeString = ( ): string | undefined => { const { minDate, maxDate } = range || {}; const formattedStartDate = minDate - ? moment(utcToZonedTime(minDate, timeZone || 'UTC')).format('MM/DD/YYYY') + ? DateTime.fromJSDate(utcToZonedTime(minDate, timeZone || 'UTC')).toFormat( + 'LL/dd/yyyy', + ) : undefined; const formattedEndDate = maxDate - ? moment(utcToZonedTime(maxDate, timeZone || 'UTC')).format('MM/DD/YYYY') + ? DateTime.fromJSDate(utcToZonedTime(maxDate, timeZone || 'UTC')).toFormat( + 'LL/dd/yyyy', + ) : undefined; return formattedStartDate && formattedEndDate ? `${sensor} range: ${formattedStartDate} - ${formattedEndDate}` @@ -194,18 +196,19 @@ export const availableRangeString = ( * @param endDate - The ending date */ export const moreThanOneYear = (startDate: string, endDate: string) => { - const from = moment(startDate); - const to = moment(endDate); - return to.diff(from, 'years') >= 1; + const from = DateTime.fromISO(startDate); + const to = DateTime.fromISO(endDate); + return to.diff(from, 'years').years >= 1; }; export const localizedEndOfDay = ( date?: string, timeZone?: string | null | undefined, ): string => - moment(date) - .tz(timeZone || 'UTC') + (date ? DateTime.fromISO(date) : DateTime.now()) + .setZone(timeZone || 'UTC') .endOf('day') + .toJSDate() .toISOString(); export const constructOceanSenseDatasets = ( diff --git a/packages/website/src/common/Chart/MultipleSensorsCharts/index.tsx b/packages/website/src/common/Chart/MultipleSensorsCharts/index.tsx index e6bc0cfe7..2d416b8bd 100644 --- a/packages/website/src/common/Chart/MultipleSensorsCharts/index.tsx +++ b/packages/website/src/common/Chart/MultipleSensorsCharts/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import isISODate from 'validator/lib/isISO8601'; import { Box, Container, makeStyles, Theme } from '@material-ui/core'; -import moment from 'moment'; import { camelCase, isNaN, snakeCase, sortBy } from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz'; @@ -24,12 +23,7 @@ import { } from 'store/Sites/selectedSiteSlice'; import { Metrics, MetricsKeys, Site, Sources } from 'store/Sites/types'; import { useQueryParam } from 'hooks/useQueryParams'; -import { - rangeOverlapWithRange, - isBefore, - setTimeZone, - subtractFromDate, -} from 'helpers/dates'; +import { rangeOverlapWithRange, isBefore, setTimeZone } from 'helpers/dates'; import { getSourceRanges } from 'helpers/siteUtils'; import { BaseSourceConfig } from 'utils/types'; import { @@ -45,6 +39,7 @@ import { getMetlogConfig, getPublicMetlogMetrics, } from 'constants/chartConfigs/metlogConfig'; +import { DateTime } from 'luxon'; import { constructOceanSenseDatasets, findChartWidth, @@ -102,14 +97,18 @@ const MultipleSensorsCharts = ({ const hasHuiData = availableSources.includes('hui'); - const chartStartDate = startDate || subtractFromDate(today, 'week'); - const chartEndDate = moment - .min( - moment(), - moment(endDate) - .tz(site.timezone || 'UTC') - .endOf('day'), - ) + const chartStartDate = + startDate || + DateTime.fromISO(today).minus({ weeks: 1 }).toJSDate().toISOString(); + const now = DateTime.now(); + const end = + endDate !== undefined + ? DateTime.fromISO(endDate) + .setZone(site.timezone || 'UTC') + .endOf('day') + : now; + const chartEndDate = (now.valueOf() < end.valueOf() ? now : end) + .toJSDate() .toISOString(); const hasOceanSenseId = Boolean(oceanSenseConfig?.[site.id]); @@ -229,11 +228,11 @@ const MultipleSensorsCharts = ({ if (!rangesLoading && !pickerStartDate && !pickerEndDate) { const { maxDate } = hoboBottomTemperatureRange?.data?.[0] || {}; const localizedMaxDate = localizedEndOfDay(maxDate, site.timezone); - const pastOneMonth = moment( - subtractFromDate(localizedMaxDate || today, 'month', 1), - ) - .tz(site.timezone || 'UTC') + const pastOneMonth = DateTime.fromISO(localizedMaxDate || today) + .minus({ months: 1 }) + .setZone(site.timezone || 'UTC') .startOf('day') + .toJSDate() .toISOString(); setPickerStartDate( startParam @@ -317,8 +316,10 @@ const MultipleSensorsCharts = ({ end: siteLocalEndDate, metrics: uniqueMetrics, hourly: - moment(siteLocalEndDate).diff(moment(siteLocalStartDate), 'days') > - 2, + DateTime.fromISO(siteLocalEndDate).diff( + DateTime.fromISO(siteLocalStartDate), + 'days', + ).days > 2, }), ); @@ -348,13 +349,17 @@ const MultipleSensorsCharts = ({ useEffect(() => { const pickerLocalEndDate = new Date( setTimeZone( - new Date(moment(pickerEndDate).format('MM/DD/YYYY')), + (pickerEndDate ? DateTime.fromISO(pickerEndDate) : DateTime.now()) + .startOf('day') + .toJSDate(), site?.timezone, ), ).toISOString(); const pickerLocalStartDate = new Date( setTimeZone( - new Date(moment(pickerStartDate).format('MM/DD/YYYY')), + (pickerStartDate ? DateTime.fromISO(pickerStartDate) : DateTime.now()) + .startOf('day') + .toJSDate(), site?.timezone, ), ).toISOString(); @@ -369,29 +374,38 @@ const MultipleSensorsCharts = ({ setStartDate( minDataDate - ? moment - .max(moment(minDataDate), moment(pickerLocalStartDate)) + ? DateTime.max( + DateTime.fromISO(minDataDate), + DateTime.fromISO(pickerLocalStartDate), + ) + .toJSDate() .toISOString() : pickerLocalStartDate, ); setEndDate( maxDataDate - ? moment - .min(moment(maxDataDate), moment(pickerLocalEndDate).endOf('day')) + ? DateTime.min( + DateTime.fromISO(maxDataDate), + DateTime.fromISO(pickerLocalEndDate).endOf('day'), + ) + .toJSDate() .toISOString() - : moment(pickerLocalEndDate).endOf('day').toISOString(), + : DateTime.fromISO(pickerLocalEndDate) + .endOf('day') + .toJSDate() + .toISOString(), ); }, [granularDailyData, pickerEndDate, pickerStartDate, site, timeSeriesData]); useEffect(() => { if (pickerStartDate && pickerEndDate && range === 'custom') { - const newStartParam = moment( + const newStartParam = DateTime.fromJSDate( utcToZonedTime(pickerStartDate, site.timezone || 'UTC'), - ).format('YYYY-MM-DD'); - const newEndParam = moment( + ).toFormat('yyyy-MM-dd'); + const newEndParam = DateTime.fromJSDate( utcToZonedTime(pickerEndDate, site.timezone || 'UTC'), - ).format('YYYY-MM-DD'); + ).toFormat('yyyy-MM-dd'); setStartParam(newStartParam); setEndParam(newEndParam); } @@ -441,16 +455,21 @@ const MultipleSensorsCharts = ({ const onRangeChange = (value: RangeValue) => { const { minDate, maxDate } = hoboBottomTemperatureRange?.data?.[0] || {}; - const localizedMinDate = new Date( - moment(minDate) - .tz(site.timezone || 'UTC') - .format('MM/DD/YYYY'), - ).toISOString(); - const localizedMaxDate = new Date( - moment(maxDate) - .tz(site.timezone || 'UTC') - .format('MM/DD/YYYY'), - ).toISOString(); + const localizedMinDate = ( + minDate ? DateTime.fromISO(minDate) : DateTime.now() + ) + .setZone(site.timezone || 'UTC') + .startOf('day') + .toJSDate() + .toISOString(); + const localizedMaxDate = ( + maxDate ? DateTime.fromISO(maxDate) : DateTime.now() + ) + .setZone(site.timezone || 'UTC') + .startOf('day') + .toJSDate() + .toISOString(); + setRange(value); if (value !== 'custom') { setStartParam(undefined); @@ -458,15 +477,40 @@ const MultipleSensorsCharts = ({ } switch (value) { case 'one_month': - setPickerEndDate(moment(localizedMaxDate).endOf('day').toISOString()); - setPickerStartDate(subtractFromDate(localizedMaxDate, 'month', 1)); + setPickerEndDate( + DateTime.fromISO(localizedMaxDate) + .endOf('day') + .toJSDate() + .toISOString(), + ); + setPickerStartDate( + DateTime.fromISO(localizedMaxDate) + .minus({ months: 1 }) + .toJSDate() + .toISOString(), + ); break; case 'one_year': - setPickerEndDate(moment(localizedMaxDate).endOf('day').toISOString()); - setPickerStartDate(subtractFromDate(localizedMaxDate, 'year')); + setPickerEndDate( + DateTime.fromISO(localizedMaxDate) + .endOf('day') + .toJSDate() + .toISOString(), + ); + setPickerStartDate( + DateTime.fromISO(localizedMaxDate) + .minus({ years: 1 }) + .toJSDate() + .toISOString(), + ); break; case 'max': - setPickerEndDate(moment(localizedMaxDate).endOf('day').toISOString()); + setPickerEndDate( + DateTime.fromISO(localizedMaxDate) + .endOf('day') + .toJSDate() + .toISOString(), + ); setPickerStartDate(localizedMinDate); break; default: @@ -483,21 +527,29 @@ const MultipleSensorsCharts = ({ case 'start': // Set picker start date only if input date is after zero time if ( - moment(dateString) - .startOf('day') - .isSameOrAfter(moment(0).startOf('day')) + DateTime.fromISO(dateString).startOf('day') >= + DateTime.fromMillis(0).startOf('day') ) { - setPickerStartDate(moment(dateString).startOf('day').toISOString()); + setPickerStartDate( + DateTime.fromISO(dateString) + .startOf('day') + .toJSDate() + .toISOString(), + ); } break; case 'end': // Set picker end date only if input date is before today if ( - moment(dateString) - .endOf('day') - .isSameOrBefore(moment().endOf('day')) + DateTime.fromISO(dateString).endOf('day') <= + DateTime.now().endOf('day') ) { - setPickerEndDate(moment(dateString).endOf('day').toISOString()); + setPickerEndDate( + DateTime.fromISO(dateString) + .endOf('day') + .toJSDate() + .toISOString(), + ); } break; default: @@ -542,7 +594,10 @@ const MultipleSensorsCharts = ({ site={site} datasets={tempAnalysisDatasets} pointId={pointId ? parseInt(pointId, 10) : undefined} - pickerStartDate={pickerStartDate || subtractFromDate(today, 'week')} + pickerStartDate={ + pickerStartDate || + DateTime.fromISO(today).minus({ weeks: 1 }).toJSDate().toISOString() + } pickerEndDate={pickerEndDate || today} chartStartDate={chartStartDate} chartEndDate={chartEndDate} @@ -578,7 +633,13 @@ const MultipleSensorsCharts = ({ showRangeButtons={false} chartWidth="large" site={site} - pickerStartDate={pickerStartDate || subtractFromDate(today, 'week')} + pickerStartDate={ + pickerStartDate || + DateTime.fromISO(today) + .minus({ weeks: 1 }) + .toJSDate() + .toISOString() + } pickerEndDate={pickerEndDate || today} chartStartDate={chartStartDate} chartEndDate={chartEndDate} @@ -620,7 +681,11 @@ const MultipleSensorsCharts = ({ chartWidth="large" site={site} pickerStartDate={ - pickerStartDate || subtractFromDate(today, 'week') + pickerStartDate || + DateTime.fromISO(today) + .minus({ weeks: 1 }) + .toJSDate() + .toISOString() } pickerEndDate={pickerEndDate || today} chartStartDate={chartStartDate} diff --git a/packages/website/src/common/Chart/Tooltip/index.test.tsx b/packages/website/src/common/Chart/Tooltip/index.test.tsx index af3e09383..630563692 100644 --- a/packages/website/src/common/Chart/Tooltip/index.test.tsx +++ b/packages/website/src/common/Chart/Tooltip/index.test.tsx @@ -19,7 +19,7 @@ test('renders as expected', () => { const { container } = render( !isDailyUpdated); const dateString = displayTimeInLocalTimezone({ isoDate: date, - format: `MM/DD/YY${hasHourlyData ? ' hh:mm A' : ''}`, + format: `MM/dd/yy${hasHourlyData ? ' HH:mm a' : ''}`, displayTimezone: hasHourlyData, timeZone: userTimeZone, timeZoneToDisplay: siteTimeZone, @@ -89,7 +89,11 @@ const Tooltip = ({ + {dateString} } diff --git a/packages/website/src/common/Chart/utils.ts b/packages/website/src/common/Chart/utils.ts index b4ad11c96..64318b219 100644 --- a/packages/website/src/common/Chart/utils.ts +++ b/packages/website/src/common/Chart/utils.ts @@ -1,14 +1,4 @@ -import moment from 'moment'; -import { - filter, - flatten, - inRange, - isNil, - isNumber, - map, - maxBy, - minBy, -} from 'lodash'; +import { filter, flatten, isNil, isNumber, map, maxBy, minBy } from 'lodash'; import { ChartDataSets, ChartPoint } from 'chart.js'; import { DEFAULT_SURVEY_CHART_POINT_COLOR, @@ -23,6 +13,7 @@ import type { } from 'store/Sites/types'; import { SurveyListItem } from 'store/Survey/types'; import { sortByDate } from 'helpers/dates'; +import { DateTime } from 'luxon'; import type { ChartProps, Dataset } from '.'; interface Context { @@ -78,27 +69,29 @@ export const filterHistoricalMonthlyMeanData = ( return historicalMonthlyMean; } - const start = moment(from); - const end = moment(to); + const start = DateTime.fromISO(from); + const end = DateTime.fromISO(to); const closestToStart = getHistoricalMonthlyMeanDataClosestToDate( historicalMonthlyMean, - new Date(start.toISOString()), + start.toJSDate(), )?.value; const closestToEnd = getHistoricalMonthlyMeanDataClosestToDate( historicalMonthlyMean, - new Date(end.toISOString()), + end.toJSDate(), )?.value; const closestToStartArray: HistoricalMonthlyMeanData[] = closestToStart - ? [{ date: start.toISOString(), value: closestToStart }] + ? [{ date: start.toJSDate().toISOString(), value: closestToStart }] : []; const closestToEndArray: HistoricalMonthlyMeanData[] = closestToEnd - ? [{ date: end.toISOString(), value: closestToEnd }] + ? [{ date: end.toJSDate().toISOString(), value: closestToEnd }] : []; - const filteredData = historicalMonthlyMean.filter((item) => - inRange(moment(item.date).valueOf(), start.valueOf(), end.valueOf() + 1), + const filteredData = historicalMonthlyMean.filter( + (item) => + DateTime.fromISO(item.date).valueOf() >= start.valueOf() && + DateTime.fromISO(item.date).valueOf() <= end.valueOf(), ); return [...closestToStartArray, ...filteredData, ...closestToEndArray]; @@ -236,8 +229,12 @@ const createGaps = (data: ChartPoint[], maxHoursGap: number): ChartPoint[] => { if ( currIndex !== 0 && currIndex !== nPoints - 1 && - Math.abs(moment(data[currIndex + 1].x).diff(moment(curr.x), 'hours')) > - maxHoursGap + Math.abs( + DateTime.fromISO(data[currIndex + 1].x as string).diff( + DateTime.fromISO(curr.x as string), + 'hours', + ).hours, + ) > maxHoursGap ) { return [ ...acc, @@ -245,8 +242,8 @@ const createGaps = (data: ChartPoint[], maxHoursGap: number): ChartPoint[] => { { y: undefined, x: new Date( - (moment(data[currIndex + 1].x).valueOf() + - moment(curr.x).valueOf()) / + (DateTime.fromISO(data[currIndex + 1].x as string).valueOf() + + DateTime.fromISO(curr.x as string).valueOf()) / 2, ).toISOString(), }, diff --git a/packages/website/src/common/Datepicker/index.tsx b/packages/website/src/common/Datepicker/index.tsx index 1be2c8aa8..01d125865 100644 --- a/packages/website/src/common/Datepicker/index.tsx +++ b/packages/website/src/common/Datepicker/index.tsx @@ -17,7 +17,7 @@ import { MuiPickersUtilsProvider, } from '@material-ui/pickers'; import DateFnsUtils from '@date-io/date-fns'; -import moment from 'moment'; +import { DateTime } from 'luxon'; const DatePicker = ({ value, @@ -44,10 +44,10 @@ const DatePicker = ({ disableToolbar format="MM/dd/yyyy" name="datePicker" - maxDate={moment() - .tz(timeZone || 'UTC') - .format('YYYY/MM/DD')} - minDate={moment(0).format('YYYY/MM/DD')} + maxDate={DateTime.now() + .setZone(timeZone || 'UTC') + .toFormat('yyyy/MM/dd')} + minDate={DateTime.fromMillis(0).toFormat('yyyy/MM/dd')} autoOk={autoOk} showTodayButton value={value || null} diff --git a/packages/website/src/common/SiteDetails/Surveys/SurveyCard/index.tsx b/packages/website/src/common/SiteDetails/Surveys/SurveyCard/index.tsx index aea807862..738230289 100644 --- a/packages/website/src/common/SiteDetails/Surveys/SurveyCard/index.tsx +++ b/packages/website/src/common/SiteDetails/Surveys/SurveyCard/index.tsx @@ -9,13 +9,13 @@ import { makeStyles, } from '@material-ui/core'; import { Link } from 'react-router-dom'; -import moment from 'moment'; import { useDispatch, useSelector } from 'react-redux'; import { SurveyListItem } from 'store/Survey/types'; import { userInfoSelector } from 'store/User/userSlice'; import { surveysRequest } from 'store/Survey/surveyListSlice'; import { formatNumber } from 'helpers/numberUtils'; import surveyServices from 'services/surveyServices'; +import { DateTime } from 'luxon'; import incomingStyles from '../styles'; import CustomLink from '../../../Link'; import LoadingSkeleton from '../../../LoadingSkeleton'; @@ -32,7 +32,6 @@ const SurveyCard = ({ }: SurveyCardProps) => { const classes = useStyles(); const isShowingFeatured = pointId === -1; - const displayDeleteButton = isAdmin && typeof siteId === 'number'; const user = useSelector(userInfoSelector); const dispatch = useDispatch(); @@ -185,21 +184,23 @@ const SurveyCard = ({ - {displayDeleteButton && ( - - {`Are you sure you would like to delete the survey for ${moment( - survey.diveDate, - ).format( - 'MM/DD/YYYY', - )}? It will delete all media associated with this survey.`} - } - onConfirm={onSurveyDelete} - onSuccess={onSurveyDeleteSuccess} - /> - - )} + {isAdmin && + typeof siteId === 'number' && + typeof survey?.diveDate === 'string' && ( + + {`Are you sure you would like to delete the survey for ${DateTime.fromISO( + survey.diveDate, + ).toFormat( + 'LL/dd/yyyy', + )}? It will delete all media associated with this survey.`} + } + onConfirm={onSurveyDelete} + onSuccess={onSurveyDeleteSuccess} + /> + + )} )} diff --git a/packages/website/src/common/SiteDetails/Surveys/Timeline/Desktop.tsx b/packages/website/src/common/SiteDetails/Surveys/Timeline/Desktop.tsx index 468c402fc..cc5616de2 100644 --- a/packages/website/src/common/SiteDetails/Surveys/Timeline/Desktop.tsx +++ b/packages/website/src/common/SiteDetails/Surveys/Timeline/Desktop.tsx @@ -65,7 +65,7 @@ const TimelineDesktop = ({ {displayTimeInLocalTimezone({ isoDate: survey.diveDate, - format: 'MM/DD/YYYY', + format: 'LL/dd/yyyy', displayTimezone: false, timeZone, })} diff --git a/packages/website/src/common/SiteDetails/Surveys/Timeline/Tablet.tsx b/packages/website/src/common/SiteDetails/Surveys/Timeline/Tablet.tsx index d67b433d7..5cbbf852f 100644 --- a/packages/website/src/common/SiteDetails/Surveys/Timeline/Tablet.tsx +++ b/packages/website/src/common/SiteDetails/Surveys/Timeline/Tablet.tsx @@ -54,7 +54,7 @@ const TimelineTablet = ({ {displayTimeInLocalTimezone({ isoDate: survey.diveDate, - format: 'MM/DD/YYYY', + format: 'LL/dd/yyyy', displayTimezone: false, timeZone, })} diff --git a/packages/website/src/common/SiteDetails/WaterSampling/index.tsx b/packages/website/src/common/SiteDetails/WaterSampling/index.tsx index 54872ba3a..097d7253f 100644 --- a/packages/website/src/common/SiteDetails/WaterSampling/index.tsx +++ b/packages/website/src/common/SiteDetails/WaterSampling/index.tsx @@ -12,8 +12,8 @@ import { colors } from 'layout/App/theme'; import { Metrics, Sources, TimeSeriesData } from 'store/Sites/types'; import { SurveyPoint } from 'store/Survey/types'; import requests from 'helpers/requests'; -import moment from 'moment'; import WarningIcon from '@material-ui/icons/Warning'; +import { DateTime } from 'luxon'; import { styles as incomingStyles } from '../styles'; import UpdateInfo from '../../UpdateInfo'; import { @@ -51,7 +51,9 @@ function WaterSamplingCard({ siteId, source }: WaterSamplingCardProps) { surveyPoint: point?.id, }, )}`; - const lastUpload = maxDate ? moment(maxDate).format('MM/DD/YYYY') : undefined; + const lastUpload = maxDate + ? DateTime.fromISO(maxDate).toFormat('LL/dd/yyyy') + : undefined; React.useEffect(() => { (async () => { diff --git a/packages/website/src/common/SiteDetails/index.tsx b/packages/website/src/common/SiteDetails/index.tsx index f948e060a..41d02548b 100644 --- a/packages/website/src/common/SiteDetails/index.tsx +++ b/packages/website/src/common/SiteDetails/index.tsx @@ -194,7 +194,7 @@ const SiteDetails = ({ { text: `${displayTimeInLocalTimezone({ isoDate: surveyDiveDate, - format: 'MMM DD[,] YYYY', + format: 'MMM dd, yyyy', displayTimezone: false, timeZone: site?.timezone, })}`, diff --git a/packages/website/src/common/SurveyForm/index.tsx b/packages/website/src/common/SurveyForm/index.tsx index db45ab235..d6af82061 100644 --- a/packages/website/src/common/SurveyForm/index.tsx +++ b/packages/website/src/common/SurveyForm/index.tsx @@ -1,6 +1,5 @@ import React, { useState, useCallback, ChangeEvent } from 'react'; import { useSelector } from 'react-redux'; -import moment from 'moment'; import { withStyles, WithStyles, @@ -28,6 +27,7 @@ import { useForm, Controller } from 'react-hook-form'; import { diveLocationSelector } from 'store/Survey/surveySlice'; import { SurveyData, SurveyState } from 'store/Survey/types'; import { setTimeZone } from 'helpers/dates'; +import { DateTime } from 'luxon'; interface SurveyFormFields { diveDate: string; @@ -115,8 +115,8 @@ const SurveyForm = ({ required: 'This is a required field', validate: { validDate: (value) => - moment(value, 'MM/DD/YYYY', true).isValid() || - 'Invalid date', + DateTime.fromFormat(value, 'LL/dd/yyyy', { zone: 'UTC' }) + .isValid || 'Invalid date', }, }} render={({ field }) => ( @@ -135,7 +135,10 @@ const SurveyForm = ({ ref={field.ref} onChange={(e) => { field.onChange(e); - setValue('diveTime', moment(e).format('HH:mm')); + setValue( + 'diveTime', + DateTime.fromJSDate(e || new Date(NaN)).toFormat('HH:mm'), + ); handleDiveDateTimeChange(e); }} KeyboardButtonProps={{ diff --git a/packages/website/src/helpers/dates.ts b/packages/website/src/helpers/dates.ts index e3bb6bd3f..64ab4cb74 100644 --- a/packages/website/src/helpers/dates.ts +++ b/packages/website/src/helpers/dates.ts @@ -1,4 +1,3 @@ -import moment from 'moment-timezone'; import { range as createRange, orderBy } from 'lodash'; import { zonedTimeToUtc } from 'date-fns-tz'; @@ -10,6 +9,7 @@ import { ValueWithTimestamp, } from 'store/Sites/types'; import { SurveyListItem } from 'store/Survey/types'; +import { DateTime } from 'luxon'; type DateString = string | null | undefined; @@ -33,33 +33,6 @@ export const isBefore = ( export const isBetween = (date: string, start: string, end: string) => isBefore(date, end) && isBefore(start, date); -export const subtractFromDate = ( - endDate: string, - amount: Range, - multiple?: number, -): string => { - switch (amount) { - case 'day': - return moment(endDate) - .subtract(multiple || 1, 'days') - .toISOString(); - case 'week': - return moment(endDate) - .subtract(multiple || 1, 'weeks') - .toISOString(); - case 'month': - return moment(endDate) - .subtract(multiple || 1, 'months') - .toISOString(); - case 'year': - return moment(endDate) - .subtract(multiple || 1, 'years') - .toISOString(); - default: - return endDate; - } -}; - export const toRelativeTime = (timestamp: Date | string | number) => { const minute = 60; const hour = 60 * minute; @@ -168,7 +141,7 @@ export function setTimeZone(date: Date | null, timeZone?: string | null) { } export const getTimeZoneName = (timeZone: string): string => { - const rawTimeZoneName = moment().tz(timeZone).format('z'); + const rawTimeZoneName = DateTime.now().setZone(timeZone).toFormat('z'); // Only add GMT prefix to raw time differences and not acronyms such as PST. const needsGMT = rawTimeZoneName.includes('+') || rawTimeZoneName.includes('-'); @@ -186,9 +159,9 @@ export const displayTimeInLocalTimezone = ({ const timeZoneName = getTimeZoneName( timeZoneToDisplay || timeZone || 'UTC', ); - const dateString = moment(isoDate) - .tz(timeZone || 'UTC') - .format(format); + const dateString = DateTime.fromISO(isoDate) + .setZone(timeZone || 'UTC') + .toFormat(format); return `${dateString}${displayTimezone ? ` ${timeZoneName}` : ''}`; } @@ -212,9 +185,9 @@ export const convertToLocalTime = ( timeZone?: string | null, ) => { // Hold only hour info ignoring the timezone - const dateStringIgnoreTimeZone = moment(isoTime) - .tz(timeZone || 'UTC') - .format('YYYY-MM-DD HH:mm:ss'); + const dateStringIgnoreTimeZone = DateTime.fromISO(isoTime) + .setZone(timeZone || 'UTC') + .toFormat('yyyy-MM-dd HH:mm:ss'); // Set the user's local time zone to the above datestring return zonedTimeToUtc( @@ -254,27 +227,28 @@ export const generateHistoricalMonthlyMeanTimestamps = ( return []; } - const firstDate = moment(startDate) - .tz(timeZone || 'UTC') - .subtract(1, 'months') - .set('date', 15) + const firstDate = (startDate ? DateTime.fromISO(startDate) : DateTime.now()) + .setZone(timeZone || 'UTC') + .minus({ months: 1 }) + .set({ day: 15 }) .startOf('day'); - const lastDate = moment(endDate) - .tz(timeZone || 'UTC') - .add(1, 'months') - .set('date', 15) + + const lastDate = (endDate ? DateTime.fromISO(endDate) : DateTime.now()) + .setZone(timeZone || 'UTC') + .plus({ months: 1 }) + .set({ day: 15 }) .startOf('day'); const monthsRange = createRange( - moment(lastDate).diff(moment(firstDate), 'months') + 1, + lastDate.diff(firstDate, 'months').months + 1, ); return monthsRange.map((months) => { - const date = moment(firstDate).add(months, 'months'); + const date = firstDate.plus({ months }); return { - date: date.toISOString(), - value: historicalMonthlyMean[date.month()].temperature, + date: date.toJSDate().toISOString(), + value: historicalMonthlyMean[date.month - 1].temperature, }; }); }; diff --git a/packages/website/src/routes/SiteRoutes/Site/SiteInfo/ExclusionDatesDialog/ConfirmationDialog.tsx b/packages/website/src/routes/SiteRoutes/Site/SiteInfo/ExclusionDatesDialog/ConfirmationDialog.tsx index 8b0d909ef..582f1bc64 100644 --- a/packages/website/src/routes/SiteRoutes/Site/SiteInfo/ExclusionDatesDialog/ConfirmationDialog.tsx +++ b/packages/website/src/routes/SiteRoutes/Site/SiteInfo/ExclusionDatesDialog/ConfirmationDialog.tsx @@ -7,8 +7,8 @@ import { Typography, makeStyles, } from '@material-ui/core'; -import moment from 'moment'; import { grey } from '@material-ui/core/colors'; +import { DateTime } from 'luxon'; const ConfirmationDialog = ({ open, @@ -20,8 +20,10 @@ const ConfirmationDialog = ({ handleMaintainPeriodAddition, }: ConfirmationDialogProps) => { const classes = useStyles(); - const startDate = moment(start).format('MM/DD/YYYY HH:mm'); - const endDate = moment(end).format('MM/DD/YYYY HH:mm'); + if (!start || !end) return null; + + const startDate = DateTime.fromJSDate(start).toFormat('LL/dd/yyyy HH:mm'); + const endDate = DateTime.fromJSDate(end).toFormat('LL/dd/yyyy HH:mm'); return ( @@ -80,9 +82,4 @@ interface ConfirmationDialogProps { handleMaintainPeriodAddition: () => void; } -ConfirmationDialog.defaultProps = { - start: undefined, - end: undefined, -}; - export default ConfirmationDialog; diff --git a/packages/website/src/routes/SiteRoutes/Site/SiteInfo/index.tsx b/packages/website/src/routes/SiteRoutes/Site/SiteInfo/index.tsx index e523d001e..a48ad5054 100644 --- a/packages/website/src/routes/SiteRoutes/Site/SiteInfo/index.tsx +++ b/packages/website/src/routes/SiteRoutes/Site/SiteInfo/index.tsx @@ -215,7 +215,7 @@ const SiteNavBar = ({ {`Last surveyed: ${displayTimeInLocalTimezone( { isoDate: lastSurvey, - format: 'MMM DD[,] YYYY', + format: 'MMM dd, yyyy', displayTimezone: false, timeZone: site.timezone, }, diff --git a/packages/website/src/routes/SiteRoutes/Site/index.tsx b/packages/website/src/routes/SiteRoutes/Site/index.tsx index efbddf824..fcf72bad0 100644 --- a/packages/website/src/routes/SiteRoutes/Site/index.tsx +++ b/packages/website/src/routes/SiteRoutes/Site/index.tsx @@ -33,13 +33,14 @@ import { Site as SiteType } from 'store/Sites/types'; import { useQueryParam } from 'hooks/useQueryParams'; import { isAdmin } from 'helpers/user'; import { findAdministeredSite } from 'helpers/findAdministeredSite'; -import { sortByDate, subtractFromDate } from 'helpers/dates'; +import { sortByDate } from 'helpers/dates'; import { findSurveyPointFromList } from 'helpers/siteUtils'; import SiteNavBar from 'common/NavBar'; import SiteFooter from 'common/Footer'; import SiteDetails from 'common/SiteDetails'; import { localizedEndOfDay } from 'common/Chart/MultipleSensorsCharts/helpers'; import LoadingSkeleton from 'common/LoadingSkeleton'; +import { DateTime } from 'luxon'; import SiteInfo from './SiteInfo'; import NotFoundPage from '../../NotFound/index'; @@ -186,7 +187,10 @@ const Site = ({ match, classes }: SiteProps) => { dispatch( siteOceanSenseDataRequest({ sensorID: 'oceansense-2', - startDate: subtractFromDate(today, 'month', 6), + startDate: DateTime.fromISO(today) + .minus({ months: 6 }) + .toJSDate() + .toISOString(), endDate: today, latest: true, }), diff --git a/packages/website/src/routes/SiteRoutes/SiteApplication/Form/index.tsx b/packages/website/src/routes/SiteRoutes/SiteApplication/Form/index.tsx index c8333ed11..52b1a7b37 100644 --- a/packages/website/src/routes/SiteRoutes/SiteApplication/Form/index.tsx +++ b/packages/website/src/routes/SiteRoutes/SiteApplication/Form/index.tsx @@ -14,8 +14,8 @@ import { MuiPickersUtilsProvider, } from '@material-ui/pickers'; import DateFnsUtils from '@date-io/date-fns'; -import moment from 'moment'; import { SiteApplication, SiteApplyParams } from 'store/Sites/types'; +import { DateTime } from 'luxon'; interface SiteApplicationFormFields { siteName: string; @@ -161,7 +161,8 @@ const Form = ({ required: 'This is a required field', validate: { validDate: (value) => - moment(value, 'MM/DD/YYYY', true).isValid() || 'Invalid date', + DateTime.fromFormat(value, 'LL/dd/yyyy').isValid || + 'Invalid date', }, }} render={({ field }) => ( diff --git a/packages/website/src/routes/SiteRoutes/SurveyPoint/InfoCard/Info.tsx b/packages/website/src/routes/SiteRoutes/SurveyPoint/InfoCard/Info.tsx index 0d563ccf4..b83468e63 100644 --- a/packages/website/src/routes/SiteRoutes/SurveyPoint/InfoCard/Info.tsx +++ b/packages/website/src/routes/SiteRoutes/SurveyPoint/InfoCard/Info.tsx @@ -40,7 +40,7 @@ const Info = ({ site, pointId, onEditButtonClick, classes }: InfoProps) => { isoDate: surveys[0]?.diveDate, displayTimezone: false, timeZone: site.timezone, - format: 'MMM DD[,] YYYY', + format: 'MMM dd, yyyy', }); return ( diff --git a/packages/website/src/routes/SiteRoutes/UploadData/HistoryTable.tsx b/packages/website/src/routes/SiteRoutes/UploadData/HistoryTable.tsx index 5f8821878..d2a8293f5 100644 --- a/packages/website/src/routes/SiteRoutes/UploadData/HistoryTable.tsx +++ b/packages/website/src/routes/SiteRoutes/UploadData/HistoryTable.tsx @@ -15,11 +15,11 @@ import { import { grey } from '@material-ui/core/colors'; import { startCase } from 'lodash'; import { Link } from 'react-router-dom'; -import moment from 'moment'; import { Site, SiteUploadHistory } from 'store/Sites/types'; import requests from 'helpers/requests'; import { pluralize } from 'helpers/stringUtils'; import DeleteButton from 'common/DeleteButton'; +import { DateTime } from 'luxon'; const tableHeaderTitles = [ 'NAME', @@ -42,9 +42,9 @@ const HistoryTable = ({ site, uploadHistory, onDelete }: HistoryTableProps) => { const classes = useStyles(); const { timezone } = site; const timezoneAbbreviation = timezone - ? moment().tz(timezone).zoneAbbr() + ? DateTime.local().setZone(timezone).toFormat('zzz') : undefined; - const dateFormat = 'MM/DD/YYYY'; + const dateFormat = 'LL/dd/yyyy'; const dataVisualizationButtonLink = ( start: string, @@ -96,7 +96,7 @@ const HistoryTable = ({ site, uploadHistory, onDelete }: HistoryTableProps) => { site.name, surveyPoint.name, startCase(sensorType), - moment(createdAt).format(dateFormat), + DateTime.fromISO(createdAt).toFormat(dateFormat), ]; return ( @@ -120,8 +120,8 @@ const HistoryTable = ({ site, uploadHistory, onDelete }: HistoryTableProps) => { color="primary" className={classes.dateIntervalButton} > - {moment(minDate).format(dateFormat)} -{' '} - {moment(maxDate).format(dateFormat)} + {DateTime.fromISO(minDate).toFormat(dateFormat)} -{' '} + {DateTime.fromISO(maxDate).toFormat(dateFormat)} @@ -133,11 +133,15 @@ const HistoryTable = ({ site, uploadHistory, onDelete }: HistoryTableProps) => { {file}"? Data between dates{' '} - {moment(minDate).format('MM/DD/YYYY HH:mm')} + {DateTime.fromISO(minDate).toFormat( + 'LL/dd/yyyy HH:mm', + )} {' '} and{' '} - {moment(maxDate).format('MM/DD/YYYY HH:mm')} + {DateTime.fromISO(maxDate).toFormat( + 'LL/dd/yyyy HH:mm', + )} {' '} will be lost. diff --git a/packages/website/src/routes/Surveys/View/SurveyDetails.tsx b/packages/website/src/routes/Surveys/View/SurveyDetails.tsx index 9ae777c45..81b230400 100644 --- a/packages/website/src/routes/Surveys/View/SurveyDetails.tsx +++ b/packages/website/src/routes/Surveys/View/SurveyDetails.tsx @@ -29,7 +29,7 @@ const SurveyDetails = ({ site, survey, classes }: SurveyDetailsProps) => { {displayTimeInLocalTimezone({ isoDate: survey.diveDate, - format: 'MM/DD/YYYY [at] h:mm A', + format: 'LL/dd/yyyy [at] h:mm A', displayTimezone: false, timeZone: site.timezone, })} diff --git a/packages/website/src/routes/Surveys/View/index.tsx b/packages/website/src/routes/Surveys/View/index.tsx index c36f74a5a..cba4ad9e3 100644 --- a/packages/website/src/routes/Surveys/View/index.tsx +++ b/packages/website/src/routes/Surveys/View/index.tsx @@ -15,7 +15,6 @@ import { WithStyles, } from '@material-ui/core'; import { useDispatch, useSelector } from 'react-redux'; -import moment from 'moment'; import { useSnackbar } from 'notistack'; import { surveyDetailsSelector, @@ -40,6 +39,7 @@ import { import { getSurveyPointsByName } from 'helpers/surveyMedia'; import ChartWithTooltip from 'common/Chart/ChartWithTooltip'; import { standardDailyDataDataset } from 'common/Chart/MultipleSensorsCharts/helpers'; +import { DateTime } from 'luxon'; import SurveyDetails from './SurveyDetails'; import SurveyMediaDetails from './MediaDetails'; @@ -94,8 +94,14 @@ const SurveyViewPage = ({ site, surveyId, classes }: SurveyViewPageProps) => { // Fetch HOBO and Spotter data near the dive date useEffect(() => { if (surveyDetails?.diveDate) { - const start = moment(surveyDetails.diveDate).startOf('day').toISOString(); - const end = moment(surveyDetails.diveDate).endOf('day').toISOString(); + const start = DateTime.fromISO(surveyDetails.diveDate) + .startOf('day') + .toJSDate() + .toISOString(); + const end = DateTime.fromISO(surveyDetails.diveDate) + .endOf('day') + .toJSDate() + .toISOString(); dispatch( siteTimeSeriesDataRequest({ siteId: `${site.id}`, @@ -201,7 +207,7 @@ const SurveyViewPage = ({ site, surveyId, classes }: SurveyViewPageProps) => { {`${displayTimeInLocalTimezone({ isoDate: surveyDetails?.diveDate, - format: 'MM/DD/YYYY', + format: 'LL/dd/yyyy', displayTimezone: false, timeZone: site.timezone, })} Survey Media`} diff --git a/yarn.lock b/yarn.lock index 8507daed5..eae52cb72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4990,13 +4990,6 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/moment-timezone@^0.5.13": - version "0.5.30" - resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.30.tgz#340ed45fe3e715f4a011f5cfceb7cb52aad46fc7" - integrity sha512-aDVfCsjYnAQaV/E9Qc24C5Njx1CoDjXsEgkxtp9NyXDpYu4CCbmclb6QhWloS9UTU/8YROUEEdEkWI0D7DxnKg== - dependencies: - moment-timezone "*" - "@types/multer@^1.4.3": version "1.4.7" resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e" @@ -14772,14 +14765,14 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment-timezone@*, moment-timezone@^0.5.31, moment-timezone@^0.5.x: +moment-timezone@^0.5.x: version "0.5.43" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790" integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ== dependencies: moment "^2.29.4" -moment@^2.10.2, moment@^2.27.0, moment@^2.29.4: +moment@^2.10.2, moment@^2.29.4: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==