Skip to content

Commit

Permalink
fix: ORV2-1771-v2 - Fix expiry date calculations (#925)
Browse files Browse the repository at this point in the history
Co-authored-by: erikataot <[email protected]>
  • Loading branch information
zgong-gov and erikataot authored Dec 20, 2023
1 parent 57b2fa9 commit efb7216
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 94 deletions.
15 changes: 13 additions & 2 deletions frontend/src/common/helpers/formatDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import localizedFormat from "dayjs/plugin/localizedFormat";
import advancedFormat from "dayjs/plugin/advancedFormat";
import isLeapYear from "dayjs/plugin/isLeapYear";

// Need to add these plugins here
dayjs.extend(utc); // for using utc
dayjs.extend(timezone); // for using timezones
dayjs.extend(localizedFormat); // for using localized datetime formats (eg. LLL)
dayjs.extend(advancedFormat); // for using advanced datetime formats
dayjs.extend(isLeapYear); // for using isLeapYear calculations

export const DATE_FORMATS = {
DATEONLY: "YYYY-MM-DD",
Expand Down Expand Up @@ -135,3 +133,16 @@ export const getStartOfDate = (date: Dayjs | string) => {
.second(0)
.millisecond(0);
};

/**
* Get the end of any datetime (ie. the date + time of 23:59:59 pm).
* @param date Any Dayjs object
* @returns Dayjs object representing the end of the datetime (with time 23:59:59 pm)
*/
export const getEndOfDate = (date: Dayjs | string) => {
return dayjs(date)
.hour(23)
.minute(59)
.second(59)
.millisecond(999);
};
44 changes: 7 additions & 37 deletions frontend/src/common/pages/ErrorFallback.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
import { Grid, Typography, Button, Container } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { Navigate } from "react-router-dom";
import { useEffect } from "react";

import { HOME } from "../../routes/constants";
import { ERROR_ROUTES } from "../../routes/constants";

/**
* React-Error-Boundary fallback component.
* Renders when there is a React error
* Used code from: https://github.com/bvaughn/react-error-boundary
*/
export const ErrorFallback = ({ error }: any) => {
const navigate = useNavigate();

export const ErrorFallback = ({
error,
}: any) => {
useEffect(() => {
// Call resetErrorBoundary() to reset the error boundary and retry the render.
console.log("ErrorFallback: ", error.message || error);
console.error("ErrorFallback: ", error.message || error);
}, []);

return (
<Container className="feature-container" sx={{ paddingTop: "24px" }}>
<Grid
container
direction="row"
justifyContent="center"
alignItems="center"
>
<Grid container item xs={12} justifyContent="center">
<Typography variant="h4" align="center">
Unexpected Error
</Typography>
</Grid>
<Grid container item xs={12} justifyContent="center">
<Typography variant="h5" align="center" margin={"20px"}>
Please call XXX-XXXX
</Typography>
</Grid>

<Grid item>
<Button
variant="contained"
onClick={() => {
navigate(HOME);
}}
>
Go to home page
</Button>
</Grid>
</Grid>
</Container>
<Navigate to={ERROR_ROUTES.UNEXPECTED} />
);
};
2 changes: 1 addition & 1 deletion frontend/src/features/permits/apiManager/permitsAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const getApplicationsInProgress = async (): Promise<
DATE_FORMATS.DATEONLY_SHORT_NAME,
),
expiryDate: toLocal(
application.permitData.startDate,
application.permitData.expiryDate,
DATE_FORMATS.DATEONLY_SHORT_NAME,
),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import dayjs, { Dayjs } from "dayjs";
import { getUserGuidFromSession } from "../../../common/apiManager/httpRequestHandler";
import { BCeIDUserDetailContext } from "../../../common/authentication/OnRouteBCContext";
import { TROS_COMMODITIES } from "../constants/termOversizeConstants";
import { getStartOfDate, now } from "../../../common/helpers/formatDate";
import { getEndOfDate, getStartOfDate, now } from "../../../common/helpers/formatDate";
import { Nullable } from "../../../common/types/common";
import { PERMIT_STATUSES } from "../types/PermitStatus";
import { calculateFeeByDuration } from "./feeSummary";
import { PERMIT_TYPES } from "../types/PermitType";
import { getExpiryDate } from "./permitState";
import {
Address,
CompanyProfile,
Expand All @@ -24,7 +25,6 @@ import {
MailingAddress,
VehicleDetails,
} from "../types/application";
import { getExpiryDate } from "./permitState";

/**
* Get default values for contact details, or populate with existing contact details and/or user details
Expand Down Expand Up @@ -124,7 +124,7 @@ export const getStartDateOrDefault = (
startDate?: Nullable<Dayjs | string>,
): Dayjs => {
return applyWhenNotNullable(
(date) => dayjs(date),
(date) => getStartOfDate(dayjs(date)),
startDate,
getStartOfDate(defaultStartDate),
);
Expand All @@ -136,7 +136,7 @@ export const getExpiryDateOrDefault = (
expiryDate?: Nullable<Dayjs | string>,
): Dayjs => {
return applyWhenNotNullable(
(date) => dayjs(date),
(date) => getEndOfDate(dayjs(date)),
expiryDate,
getExpiryDate(startDateOrDefault, durationOrDefault),
);
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/features/permits/helpers/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
DATE_FORMATS,
dayjsToLocalStr,
dayjsToUtcStr,
getEndOfDate,
getStartOfDate,
now,
toLocalDayjs,
Expand Down Expand Up @@ -89,7 +90,7 @@ export const mapApplicationResponseToApplication = (
response: ApplicationResponse,
): Application => {
const startDateOrDefault = applyWhenNotNullable(
(datetimeStr: string): Dayjs => toLocalDayjs(datetimeStr),
(datetimeStr: string): Dayjs => getStartOfDate(toLocalDayjs(datetimeStr)),
response.permitData.startDate,
getStartOfDate(now()),
);
Expand All @@ -100,7 +101,7 @@ export const mapApplicationResponseToApplication = (
);

const expiryDateOrDefault = applyWhenNotNullable(
(datetimeStr: string): Dayjs => toLocalDayjs(datetimeStr),
(datetimeStr: string): Dayjs => getEndOfDate(toLocalDayjs(datetimeStr)),
response.permitData.expiryDate,
getExpiryDate(startDateOrDefault, durationOrDefault),
);
Expand Down Expand Up @@ -196,7 +197,7 @@ export const clonePermit = (permit: Permit): Permit => {
*/
export const transformPermitToApplication = (permit: Permit) => {
const startDateOrDefault = applyWhenNotNullable(
(datetimeStr: string): Dayjs => toLocalDayjs(datetimeStr),
(datetimeStr: string): Dayjs => getStartOfDate(toLocalDayjs(datetimeStr)),
permit.permitData.startDate,
getStartOfDate(now()),
);
Expand All @@ -207,7 +208,7 @@ export const transformPermitToApplication = (permit: Permit) => {
);

const expiryDateOrDefault = applyWhenNotNullable(
(datetimeStr: string): Dayjs => toLocalDayjs(datetimeStr),
(datetimeStr: string): Dayjs => getEndOfDate(toLocalDayjs(datetimeStr)),
permit.permitData.expiryDate,
getExpiryDate(startDateOrDefault, durationOrDefault),
);
Expand Down
56 changes: 15 additions & 41 deletions frontend/src/features/permits/helpers/permitState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import dayjs, { Dayjs } from "dayjs";

import { getDateDiffInDays, getStartOfDate, now, toLocalDayjs } from "../../../common/helpers/formatDate";
import { Permit } from "../types/permit";
import {
getDateDiffInDays,
getEndOfDate,
getStartOfDate,
now,
toLocalDayjs,
} from "../../../common/helpers/formatDate";

// Enum indicating the state of a permit
export const PERMIT_STATES = {
Expand All @@ -21,16 +27,16 @@ export type PermitState = typeof PERMIT_STATES[keyof typeof PERMIT_STATES];
export const daysLeftBeforeExpiry = (permit: Permit) => {
// Perform datetime calculations in local datetime
const currDate = now();
const permitStartDate = toLocalDayjs(permit.permitData.startDate);
const originalEndDate = getOriginalEndDate(permitStartDate, permit.permitData.permitDuration);
const permitStartDate = getStartOfDate(toLocalDayjs(permit.permitData.startDate));
const permitExpiryDate = getExpiryDate(permitStartDate, permit.permitData.permitDuration);

if (currDate.isBefore(permitStartDate)) {
return permit.permitData.permitDuration; // full number of days in the duration
}

// Active permit (current datetime is between the start date and end date)
const tomorrow = dayjs(getStartOfDate(currDate)).add(1, "day");
return getDateDiffInDays(originalEndDate, tomorrow);
return getDateDiffInDays(permitExpiryDate, tomorrow);
};

/**
Expand All @@ -41,10 +47,10 @@ export const daysLeftBeforeExpiry = (permit: Permit) => {
export const getPermitState = (permit: Permit): PermitState => {
// Perform datetime calculations in local datetime
const currDate = now();
const permitStartDate = toLocalDayjs(permit.permitData.startDate);
const permitExpiryDate = toLocalDayjs(permit.permitData.expiryDate);
const permitStartDate = getStartOfDate(toLocalDayjs(permit.permitData.startDate));
const permitExpiryDate = getExpiryDate(permitStartDate, permit.permitData.permitDuration);

if (currDate.isAfter(permitExpiryDate) || currDate.isSame(permitExpiryDate)) {
if (currDate.isAfter(permitExpiryDate)) {
return PERMIT_STATES.EXPIRED;
}

Expand All @@ -60,45 +66,13 @@ export const getPermitState = (permit: Permit): PermitState => {
return PERMIT_STATES.ISSUED;
};

/**
* Gets the original end date based on start date and duration.
* @param startDate Dayjs object representing start date
* @param duration Number representing duration period
* @returns End date based solely on the start date and addition of duration period
*/
const getOriginalEndDate = (startDate: Dayjs, duration: number) => {
return dayjs(startDate).add(duration, "day");
};

/**
* Check to see if we need to handle leap year.
* If the given year is a leap year, and start date is on/before leap year day (Feb 29) and the end date is on/after Feb 29.
* @param startDate Start date of a permit
* @param endDate Original end date (startDate + duration of permit)
* @returns Whether or not we need to handle leap year in calculating expiry date.
*/
const considerLeapYear = (startDate: Dayjs, endDate: Dayjs) => {
const isEndDateInLeapYear = endDate.isLeapYear();
const endDateYear = endDate.year();
return isEndDateInLeapYear &&
(startDate.isBefore(`${endDateYear}-02-29`, "day") || startDate.isSame(`${endDateYear}-02-29`, "day")) &&
(endDate.isAfter(`${endDateYear}-02-29`, "day") || endDate.isSame(`${endDateYear}-02-29`, "day"));
};

/**
* Get the expiry date of a permit given its start date and duration.
* @param startDate Dayjs object representing start date
* @param duration Number representing duration period
*/
export const getExpiryDate = (startDate: Dayjs, duration: number) => {
const originalEndDate = dayjs(getOriginalEndDate(startDate, duration));

if (considerLeapYear(startDate, originalEndDate)) {
return originalEndDate; // "Include" the end date as part of the duration period
}

// "Exclude" the end date from the duration period if not based on leap year condition
return originalEndDate.subtract(1, "day");
return getEndOfDate(dayjs(startDate).add(duration - 1, "day"));
};

/**
Expand All @@ -108,5 +82,5 @@ export const getExpiryDate = (startDate: Dayjs, duration: number) => {
*/
export const hasPermitExpired = (expiryDate: string): boolean => {
if (!expiryDate) return false;
return dayjs().isAfter(expiryDate, "date");
return now().isAfter(getEndOfDate(expiryDate));
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
import {
DATE_FORMATS,
dayjsToLocalStr,
getEndOfDate,
getStartOfDate,
toLocalDayjs,
utcToLocalDayjs,
} from "../../../../../common/helpers/formatDate";
Expand Down Expand Up @@ -83,11 +85,11 @@ export const getDefaultFormDataFromPermit = (
permitData: {
...permit.permitData,
startDate: applyWhenNotNullable(
(startAt) => toLocalDayjs(startAt),
(startAt) => getStartOfDate(toLocalDayjs(startAt)),
permit.permitData?.startDate,
),
expiryDate: applyWhenNotNullable(
(endAt) => toLocalDayjs(endAt),
(endAt) => getEndOfDate(toLocalDayjs(endAt)),
permit.permitData?.expiryDate,
),
companyName: getDefaultRequiredVal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import userEvent from "@testing-library/user-event";
import { ThemeProvider } from "@mui/material/styles";

import { APPLICATIONS_API_ROUTES } from "../../../../../apiManager/endpoints/endpoints";
import { dayjsToUtcStr, now, toLocalDayjs } from "../../../../../../../common/helpers/formatDate";
import { dayjsToUtcStr, getEndOfDate, getStartOfDate, now, toLocalDayjs } from "../../../../../../../common/helpers/formatDate";
import { renderWithClient } from "../../../../../../../common/helpers/testHelper";
import { Application } from "../../../../../types/application";
import { bcGovTheme } from "../../../../../../../themes/bcGovTheme";
Expand All @@ -31,8 +31,8 @@ export const defaultApplicationData = {
updatedDateTime: now(),
permitData: {
...permitData,
startDate: toLocalDayjs(permitData.startDate),
expiryDate: toLocalDayjs(permitData.expiryDate),
startDate: getStartOfDate(toLocalDayjs(permitData.startDate)),
expiryDate: getEndOfDate(toLocalDayjs(permitData.expiryDate)),
},
} as Application;

Expand Down

0 comments on commit efb7216

Please sign in to comment.