Merge pull request #4379 from backstage/fix-date-calculations

[Cost Insights] Fix incorrect date calculations in European time zones
This commit is contained in:
Fredrik Adelöw
2021-02-04 07:56:58 +01:00
committed by GitHub
6 changed files with 21 additions and 29 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
Fixed date calculations incorrectly converting to UTC in some cases. This should be a transparent change.
@@ -76,13 +76,13 @@ describe('getPreviousPeriodTotalCost', () => {
change: changeOf(MockAggregatedDailyCosts),
trendline: trendlineOf(MockAggregatedDailyCosts),
};
const exclusiveEndDate = '2020-09-30';
const inclusiveEndDate = '2020-09-30';
expect(
getPreviousPeriodTotalCost(
mockGroupDailyCost.aggregation,
Duration.P30D,
exclusiveEndDate,
inclusiveEndDate,
),
).toEqual(100_000);
).toEqual(96_600);
});
});
+1 -5
View File
@@ -22,7 +22,6 @@ import {
GrowthType,
MetricData,
Duration,
DEFAULT_DATE_FORMAT,
DateAggregation,
} from '../types';
import dayjs, { OpUnitType } from 'dayjs';
@@ -73,10 +72,7 @@ export function getPreviousPeriodTotalCost(
inclusiveEndDate: string,
): number {
const dayjsDuration = dayjs.duration(duration);
const startDate = inclusiveStartDateOf(
duration,
dayjs(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT),
);
const startDate = inclusiveStartDateOf(duration, inclusiveEndDate);
// dayjs doesn't allow adding an ISO 8601 period to dates.
const [amount, type]: [number, OpUnitType] = dayjsDuration.days()
? [dayjsDuration.days(), 'day']
+6 -13
View File
@@ -24,23 +24,21 @@ export const DEFAULT_DURATION = Duration.P30D;
* Derive the start date of a given period, assuming two repeating intervals.
*
* @param duration see comment on Duration enum
* @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
* @param inclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate
*/
export function inclusiveStartDateOf(
duration: Duration,
exclusiveEndDate: string,
inclusiveEndDate: string,
): string {
switch (duration) {
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(exclusiveEndDate)
.utc()
return moment(inclusiveEndDate)
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(exclusiveEndDate)
.utc()
return moment(inclusiveEndDate)
.startOf('quarter')
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
@@ -57,13 +55,9 @@ export function exclusiveEndDateOf(
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(inclusiveEndDate)
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
return moment(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(quarterEndDate(inclusiveEndDate))
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
default:
@@ -76,7 +70,6 @@ export function inclusiveEndDateOf(
inclusiveEndDate: string,
): string {
return moment(exclusiveEndDateOf(duration, inclusiveEndDate))
.utc()
.subtract(1, 'day')
.format(DEFAULT_DATE_FORMAT);
}
@@ -94,7 +87,7 @@ export function intervalsOf(
}
export function quarterEndDate(inclusiveEndDate: string): string {
const endDate = moment(inclusiveEndDate).utc();
const endDate = moment(inclusiveEndDate);
const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT);
if (endOfQuarter === inclusiveEndDate) {
return endDate.format(DEFAULT_DATE_FORMAT);
@@ -16,7 +16,7 @@
import moment from 'moment';
import pluralize from 'pluralize';
import { Duration, DEFAULT_DATE_FORMAT } from '../types';
import { Duration } from '../types';
import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
export type Period = {
@@ -92,11 +92,8 @@ export function formatPercent(n: number): string {
}
export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) {
const exclusiveEndDate = moment(inclusiveEndDate)
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
const start = moment(
inclusiveStartDateOf(Duration.P3M, exclusiveEndDate),
inclusiveStartDateOf(Duration.P3M, inclusiveEndDate),
).format('[Q]Q YYYY');
const end = moment(inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)).format(
'[Q]Q YYYY',
+4 -3
View File
@@ -36,7 +36,7 @@ import {
getDefaultState as getDefaultLoadingState,
} from '../utils/loading';
import { findAlways } from '../utils/assert';
import { inclusiveStartDateOf } from './duration';
import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
type mockAlertRenderer<T> = (alert: T) => T;
type mockEntityRenderer<T> = (entity: T) => T;
@@ -228,8 +228,9 @@ export function aggregationFor(
baseline: number,
): DateAggregation[] {
const { duration, endDate } = parseIntervals(intervals);
const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
const days = dayjs(endDate).diff(
inclusiveStartDateOf(duration, endDate),
inclusiveStartDateOf(duration, inclusiveEndDate),
'day',
);
@@ -244,7 +245,7 @@ export function aggregationFor(
return [...Array(days).keys()].reduce(
(values: DateAggregation[], i: number): DateAggregation[] => {
const last = values.length ? values[values.length - 1].amount : baseline;
const date = dayjs(inclusiveStartDateOf(duration, endDate))
const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate))
.add(i, 'day')
.format(DEFAULT_DATE_FORMAT);
const amount = Math.max(0, last + nextDelta());