diff --git a/packages/origin-247-claim/src/utils/Duration.ts b/packages/origin-247-claim/src/utils/Duration.ts index 74f99dff..4779b912 100644 --- a/packages/origin-247-claim/src/utils/Duration.ts +++ b/packages/origin-247-claim/src/utils/Duration.ts @@ -58,6 +58,33 @@ export class Duration { } } + public getDurationUnit() { + return this.durationUnit; + } + + public roundDateUp(dateToRound: Date): number { + switch (this.durationUnit) { + // in case of month, we don't want to increment if it happens to be the beggining of the month + // otherwise, we want to take the start of next month + case 'mo': { + const luxonDate = DateTime.fromJSDate(dateToRound); + return luxonDate.equals(luxonDate.startOf('month')) + ? luxonDate.toMillis() + : DateTime.fromJSDate(dateToRound) + .plus({ month: 1 }) + .startOf('month') + .toMillis(); + } + case 'y': + return DateTime.fromJSDate(dateToRound) + .startOf('year') + .plus({ year: 1 }) + .toMillis(); + default: + return this.roundToClosestUpper(dateToRound); + } + } + public static readonly durationValidationRegex = /(^(\d+)(m|h|d|w)$)|(^(1)(mo|y)$)/; private static readonly durationRegex = /^(?\d+)(?m|h|d|w|mo|y)$/; @@ -65,6 +92,11 @@ export class Duration { const toClosest = this.getMilliSeconds(); return Math.floor(dateToRound.getTime() / toClosest) * toClosest; } + + private roundToClosestUpper(dateToRound: Date): number { + const toClosest = this.getMilliSeconds(); + return Math.ceil(dateToRound.getTime() / toClosest) * toClosest; + } } export class InvalidDurationSyntax extends Error { diff --git a/packages/origin-247-claim/src/utils/aggregate.spec.ts b/packages/origin-247-claim/src/utils/aggregate.spec.ts index e3a132aa..21892135 100644 --- a/packages/origin-247-claim/src/utils/aggregate.spec.ts +++ b/packages/origin-247-claim/src/utils/aggregate.spec.ts @@ -95,7 +95,7 @@ describe('aggregate function', () => { start: new Date('2021-05-13T08:00:00.000Z'), end: new Date('2021-07-15T10:00:00.000Z'), method: AggregateMethod.Sum, - window: new Duration('30d'), + window: new Duration('1mo'), timezoneOffset: 0 }; const aggregated = aggregate({ diff --git a/packages/origin-247-claim/src/utils/aggregate.ts b/packages/origin-247-claim/src/utils/aggregate.ts index 1e513374..41459734 100644 --- a/packages/origin-247-claim/src/utils/aggregate.ts +++ b/packages/origin-247-claim/src/utils/aggregate.ts @@ -89,26 +89,30 @@ export const aggregate = ({ end, timezoneOffset }); - const intervals = buildAggregateFrames(offsettedCommand); + const dataset = start + ? offsettedCommand.data.filter((d) => d.time > offsettedCommand.start!) + : offsettedCommand.data; + + const groupedByInterval: Record = {}; + dataset.forEach((d) => { + const roundedDate = new Date(window.roundDateUp(d.time)); + const stringifiedDate = roundedDate.toISOString(); + const exists = groupedByInterval[stringifiedDate]; + exists + ? (groupedByInterval[stringifiedDate] = [...exists, d.value]) + : (groupedByInterval[stringifiedDate] = [d.value]); + }); - return intervals - .map((interval, intervalN) => { - const resultsInInterval = offsettedCommand.data.filter( - (r, dataN) => - (r.time > interval.start && r.time <= interval.end) || - // because of `>` condition first value in dataset won't be included - // even if first interval `start` was generated out of this value time - (start === undefined && intervalN === 0 && dataN === 0) - ); - return { - start: interval.start, - end: interval.end, - results: resultsInInterval.map((r) => r.value) - }; - }) - .map((e) => ({ - start: e.start, - end: e.end, - value: aggregateFunction(e.results) - })); + const intervals = buildAggregateFrames(offsettedCommand); + const result = intervals.map((interval) => { + const dateKey = new Date(window.roundDateUp(interval.end)); + const exists = groupedByInterval[dateKey.toISOString()]; + const value = exists ? aggregateFunction(exists) : BigNumber.from(0); + return { + start: interval.start, + end: interval.end, + value: value + }; + }); + return result; };