diff --git a/package.json b/package.json index 87c251e1f9..996ddf2951 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,6 @@ "minio": "7.0.10", "mocha": "6.2.0", "moji": "0.5.1", - "moment": "2.24.0", "ms": "2.1.2", "nested-property": "1.0.1", "node-fetch": "2.6.0", diff --git a/src/client/app/common/views/components/poll-editor.vue b/src/client/app/common/views/components/poll-editor.vue index 49940134c7..51c73003d1 100644 --- a/src/client/app/common/views/components/poll-editor.vue +++ b/src/client/app/common/views/components/poll-editor.vue @@ -52,9 +52,11 @@ <script lang="ts"> import Vue from 'vue'; -import * as moment from 'moment'; import i18n from '../../../i18n'; import { erase } from '../../../../../prelude/array'; +import { addTimespan } from '../../../../../prelude/time'; +import { formatDateTimeString } from '../../../../../misc/format-time-string'; + export default Vue.extend({ i18n: i18n('common/views/components/poll-editor.vue'), data() { @@ -62,7 +64,7 @@ export default Vue.extend({ choices: ['', ''], multiple: false, expiration: 'infinite', - atDate: moment().add(1, 'day').toISOString().split('T')[0], + atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'), atTime: '00:00', after: 0, unit: 'second' @@ -95,7 +97,7 @@ export default Vue.extend({ get() { const at = () => { - return moment(`${this.atDate} ${this.atTime}`).valueOf(); + return new Date(`${this.atDate} ${this.atTime}`).getTime(); }; const after = () => { diff --git a/src/misc/format-time-string.ts b/src/misc/format-time-string.ts index caa31780ba..bfb2c397ae 100644 --- a/src/misc/format-time-string.ts +++ b/src/misc/format-time-string.ts @@ -20,7 +20,7 @@ function formatLocaleString(date: Date, format: string): string { }); } -function formatDateTimeString(date: Date, format: string): string { +export function formatDateTimeString(date: Date, format: string): string { return format .replace(/yyyy/g, date.getFullYear().toString()) .replace(/yy/g, date.getFullYear().toString().slice(-2)) diff --git a/src/prelude/time.ts b/src/prelude/time.ts new file mode 100644 index 0000000000..b1824b42ee --- /dev/null +++ b/src/prelude/time.ts @@ -0,0 +1,31 @@ +const dateTimeIntervals = { + 'days': 86400000, + 'hours': 3600000, +}; + +export function DateUTC(time: number[]): Date { + const r = new Date(0); + r.setUTCFullYear(time[0], time[1], time[2]); + if (time[3]) r.setUTCHours(time[3], ...time.slice(4)); + return r; +} + +export function isTimeSame(a: Date, b: Date): boolean { + return (a.getTime() - b.getTime()) === 0; +} + +export function isTimeBefore(a: Date, b: Date): boolean { + return (a.getTime() - b.getTime()) < 0; +} + +export function isTimeAfter(a: Date, b: Date): boolean { + return (a.getTime() - b.getTime()) > 0; +} + +export function addTimespan(x: Date, value: number, span: keyof typeof dateTimeIntervals): Date { + return new Date(x.getTime() + (value * dateTimeIntervals[span])); +} + +export function subtractTimespan(x: Date, value: number, span: keyof typeof dateTimeIntervals): Date { + return new Date(x.getTime() - (value * dateTimeIntervals[span])); +} diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts index a3bd80d16b..60ba1ebb46 100644 --- a/src/services/chart/core.ts +++ b/src/services/chart/core.ts @@ -4,18 +4,16 @@ * Tests located in test/chart */ -import * as moment from 'moment'; import * as nestedProperty from 'nested-property'; import autobind from 'autobind-decorator'; import Logger from '../logger'; import { Schema } from '../../misc/schema'; import { EntitySchema, getRepository, Repository, LessThan, MoreThanOrEqual } from 'typeorm'; import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error'; +import { DateUTC, isTimeSame, isTimeBefore, subtractTimespan } from '../../prelude/time'; const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test'); -const utc = moment.utc; - export type Obj = { [key: string]: any }; export type DeepPartial<T> = { @@ -131,8 +129,8 @@ export default abstract class Chart<T extends Record<string, any>> { } @autobind - private static momentToTimestamp(x: moment.Moment): Log['date'] { - return x.unix(); + private static dateToTimestamp(x: Date): Log['date'] { + return Math.floor(x.getTime() / 1000); } @autobind @@ -215,12 +213,12 @@ export default abstract class Chart<T extends Record<string, any>> { @autobind private getCurrentDate(): [number, number, number, number] { - const now = moment().utc(); + const now = new Date(); - const y = now.year(); - const m = now.month(); - const d = now.date(); - const h = now.hour(); + const y = now.getUTCFullYear(); + const m = now.getUTCMonth(); + const d = now.getUTCDate(); + const h = now.getUTCHours(); return [y, m, d, h]; } @@ -242,14 +240,14 @@ export default abstract class Chart<T extends Record<string, any>> { const [y, m, d, h] = this.getCurrentDate(); const current = - span == 'day' ? utc([y, m, d]) : - span == 'hour' ? utc([y, m, d, h]) : + span == 'day' ? DateUTC([y, m, d]) : + span == 'hour' ? DateUTC([y, m, d, h]) : null as never; // 現在(今日または今のHour)のログ const currentLog = await this.repository.findOne({ span: span, - date: Chart.momentToTimestamp(current), + date: Chart.dateToTimestamp(current), ...(group ? { group: group } : {}) }); @@ -290,7 +288,7 @@ export default abstract class Chart<T extends Record<string, any>> { log = await this.repository.save({ group: group, span: span, - date: Chart.momentToTimestamp(current), + date: Chart.dateToTimestamp(current), ...Chart.convertObjectToFlattenColumns(data) }); } catch (e) { @@ -358,8 +356,8 @@ export default abstract class Chart<T extends Record<string, any>> { const [y, m, d, h] = this.getCurrentDate(); const gt = - span == 'day' ? utc([y, m, d]).subtract(range, 'days') : - span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') : + span == 'day' ? subtractTimespan(DateUTC([y, m, d]), range, 'days') : + span == 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), range, 'hours') : null as never; // ログ取得 @@ -367,7 +365,7 @@ export default abstract class Chart<T extends Record<string, any>> { where: { group: group, span: span, - date: MoreThanOrEqual(Chart.momentToTimestamp(gt)) + date: MoreThanOrEqual(Chart.dateToTimestamp(gt)) }, order: { date: -1 @@ -392,13 +390,13 @@ export default abstract class Chart<T extends Record<string, any>> { } // 要求された範囲の最も古い箇所に位置するログが存在しなかったら - } else if (!utc(logs[logs.length - 1].date * 1000).isSame(gt)) { + } else if (!isTimeSame(new Date(logs[logs.length - 1].date * 1000), gt)) { // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する // (隙間埋めできないため) const outdatedLog = await this.repository.findOne({ group: group, span: span, - date: LessThan(Chart.momentToTimestamp(gt)) + date: LessThan(Chart.dateToTimestamp(gt)) }, { order: { date: -1 @@ -415,18 +413,18 @@ export default abstract class Chart<T extends Record<string, any>> { // 整形 for (let i = (range - 1); i >= 0; i--) { const current = - span == 'day' ? utc([y, m, d]).subtract(i, 'days') : - span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') : + span == 'day' ? subtractTimespan(DateUTC([y, m, d]), i, 'days') : + span == 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), i, 'hours') : null as never; - const log = logs.find(l => utc(l.date * 1000).isSame(current)); + const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current)); if (log) { const data = Chart.convertFlattenColumnsToObject(log as Record<string, any>); chart.unshift(data); } else { // 隙間埋め - const latest = logs.find(l => utc(l.date * 1000).isBefore(current)); + const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current)); const data = latest ? Chart.convertFlattenColumnsToObject(latest as Record<string, any>) : null; chart.unshift(this.getNewLog(data)); } diff --git a/yarn.lock b/yarn.lock index df6ecf76d2..d2f81d5280 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7354,7 +7354,7 @@ moment-timezone@^0.5.25: dependencies: moment ">= 2.9.0" -moment@2.24.0, "moment@>= 2.9.0": +"moment@>= 2.9.0": version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==