Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"concat-stream": "^2.0.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"dayjs": "^1.11.18",
"decimal.js": "^10.4.3",
"dotenv": "^16.0.1",
"express": "^5.0.0",
Expand Down
18 changes: 8 additions & 10 deletions src/backend/src/services/work-packages.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
WorkPackagePreview,
WorkPackageStage,
User,
WorkPackageSelection
WorkPackageSelection,
toUtcMidnight
} from 'shared';
import prisma from '../prisma/prisma';
import {
Expand Down Expand Up @@ -208,9 +209,7 @@ export default class WorkPackagesService {
.map((element) => element.wbsElement.workPackageNumber)
.reduce((prev, curr) => Math.max(prev, curr), 0) + 1;

// make the date object but add 12 hours so that the time isn't 00:00 to avoid timezone problems
const date = new Date(startDate.split('T')[0]);
date.setTime(date.getTime() + 12 * 60 * 60 * 1000);
const date = toUtcMidnight(startDate);

const changesToCreate = crId
? [
Expand Down Expand Up @@ -255,7 +254,7 @@ export default class WorkPackagesService {
null,
stage,
null,
new Date(startDate),
date,
null,
duration,
[],
Expand Down Expand Up @@ -342,13 +341,14 @@ export default class WorkPackagesService {

const blockedByElems = await validateBlockedBys(blockedBy, organization.organizationId);

const normalizedEdit = toUtcMidnight(startDate);
const changes = await getWorkPackageChanges(
originalWorkPackage.wbsElement.name,
name,
originalWorkPackage.stage,
stage,
originalWorkPackage.startDate,
new Date(startDate),
normalizedEdit,
originalWorkPackage.duration,
duration,
originalWorkPackage.blockedBy,
Expand All @@ -363,10 +363,8 @@ export default class WorkPackagesService {
wbsElementId,
userId
);

// make the date object but add 12 hours so that the time isn't 00:00 to avoid timezone problems
const date = new Date(startDate);
date.setTime(date.getTime() + 12 * 60 * 60 * 1000);
// Store at 00:00 UTC (canonical)
const date = normalizedEdit;

// set the status of the wbs element to active if an edit is made to a completed version
const status =
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"classnames": "^2.3.1",
"customize-cra": "^1.0.0",
"date-fns": "^4.1.0",
"dayjs": "^1.11.10",
"dayjs": "^1.11.18",
"file-saver": "^2.0.5",
"google-auth-library": "^9.15.0",
"pdf-lib": "^1.17.1",
Expand Down
8 changes: 2 additions & 6 deletions src/frontend/src/apis/work-packages.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ export const getSingleWorkPackage = (wbsNum: WbsNumber) => {
* @param payload Payload containing all the necessary data to create a work package.
*/
export const createSingleWorkPackage = (payload: WorkPackageCreateArgs) => {
return axios.post<WorkPackage>(apiUrls.workPackagesCreate(), {
...payload
});
return axios.post<WorkPackage>(apiUrls.workPackagesCreate(), payload);
};

/**
Expand All @@ -66,9 +64,7 @@ export const createSingleWorkPackage = (payload: WorkPackageCreateArgs) => {
* @returns Promise that will resolve to either a success status code or a fail status code.
*/
export const editWorkPackage = (payload: WorkPackageEditArgs) => {
return axios.post<{ message: string }>(apiUrls.workPackagesEdit(), {
...payload
});
return axios.post<{ message: string }>(apiUrls.workPackagesEdit(), payload);
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import TotalAmountSpentModal from '../FinanceComponents/TotalAmountSpentModal';
import { DatePicker } from '@mui/x-date-pickers';
import ListAltIcon from '@mui/icons-material/ListAlt';
import WorkIcon from '@mui/icons-material/Work';
import { isAdmin } from 'shared';
import { isAdmin, dateToUtcMidnight } from 'shared';
import { useGetAllCars } from '../../../hooks/cars.hooks';
import NERAutocomplete from '../../../components/NERAutocomplete';

Expand Down Expand Up @@ -232,7 +232,7 @@ const AdminFinanceDashboard: React.FC<AdminFinanceDashboardProps> = ({ startDate
},
field: { clearable: true }
}}
onChange={(newValue: Date | null) => setStartDateState(newValue ?? undefined)}
onChange={(newValue: Date | null) => setStartDateState(newValue ? dateToUtcMidnight(newValue) : undefined)}
/>

<Box sx={{ display: 'flex', alignItems: 'center' }}>
Expand All @@ -251,7 +251,7 @@ const AdminFinanceDashboard: React.FC<AdminFinanceDashboardProps> = ({ startDate
},
field: { clearable: true }
}}
onChange={(newValue: Date | null) => setEndDateState(newValue ?? undefined)}
onChange={(newValue: Date | null) => setEndDateState(newValue ? dateToUtcMidnight(newValue) : undefined)}
/>
<Box sx={{ ml: 0 }}></Box>
<NERButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useGetUsersTeams } from '../../../hooks/teams.hooks';
import FinanceDashboardTeamView from './FinanceDashboardTeamView';
import { useGetAllCars } from '../../../hooks/cars.hooks';
import NERAutocomplete from '../../../components/NERAutocomplete';
import { dateToUtcMidnight } from 'shared';

interface GeneralFinanceDashboardProps {
startDate?: Date;
Expand Down Expand Up @@ -156,7 +157,7 @@ const GeneralFinanceDashboard: React.FC<GeneralFinanceDashboardProps> = ({ start
},
field: { clearable: true }
}}
onChange={(newValue: Date | null) => setStartDateState(newValue ?? undefined)}
onChange={(newValue: Date | null) => setStartDateState(newValue ? dateToUtcMidnight(newValue) : undefined)}
/>

<Box sx={{ display: 'flex', alignItems: 'center' }}>
Expand All @@ -175,7 +176,7 @@ const GeneralFinanceDashboard: React.FC<GeneralFinanceDashboardProps> = ({ start
},
field: { clearable: true }
}}
onChange={(newValue: Date | null) => setEndDateState(newValue ?? undefined)}
onChange={(newValue: Date | null) => setEndDateState(newValue ? dateToUtcMidnight(newValue) : undefined)}
/>
</Box>
);
Expand Down
9 changes: 6 additions & 3 deletions src/frontend/src/utils/datetime.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ export const dateFormatMonthDate = (date: Date) => {
return dayjs(date).format('MMM D');
};

/**
* Transforms a Date object to a YYYY-MM-DD format using Day.js
* @param date the Date object to transform
* @returns the date string in YYYY-MM-DD format representing the user's calendar date
*/
export const transformDate = (date: Date) => {
const month = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : (date.getMonth() + 1).toString();
const day = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate().toString();
return `${date.getFullYear().toString()}/${month}/${day}`;
return dayjs(date).format('YYYY-MM-DD');
};

export const formatDate = (date: Date) => {
Expand Down
47 changes: 47 additions & 0 deletions src/shared/src/date-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,55 @@
* See the LICENSE file in the repository root folder for details.
*/

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { Availability } from './types/user-types';

dayjs.extend(utc);

/**
* @returns Date object representing the user's local midnight converted to UTC
*/
export const toUtcMidnight = (input: string): Date => {
return dayjs(input).startOf('day').utc().toDate();
};

/**
* Converts a Date object to UTC midnight, preserving the calendar day in the user's timezone
* @param date - The Date object from DatePicker (in user's local timezone)
* @returns Date object representing the selected day at UTC midnight
*/
export const dateToUtcMidnight = (date: Date): Date => {
const year = date.getFullYear();
const month = date.getMonth();
const day = date.getDate();
return dayjs.utc().year(year).month(month).date(day).startOf('day').toDate();
};

/**
* @param utcDate - The UTC date from the database
* @returns Date string in local timezone (YYYY-MM-DD format)
*/
export const fromUtcMidnight = (utcDate: Date): string => {
return dayjs.utc(utcDate).local().format('YYYY-MM-DD');
};

/**
* @returns Date object representing today at UTC midnight
*/
export const getCurrentUtcMidnight = (): Date => {
return dayjs().startOf('day').utc().toDate();
};

/**
* @param date1 - First date to compare
* @param date2 - Second date to compare
* @returns true if both dates represent the same calendar day
*/
export const isSameCalendarDay = (date1: Date, date2: Date): boolean => {
return dayjs(date1).format('YYYY-MM-DD') === dayjs(date2).format('YYYY-MM-DD');
};

/**
* Add the given number of weeks to the given date and return the outcome.
* @param start the start date
Expand Down
5 changes: 3 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7903,6 +7903,7 @@ __metadata:
concat-stream: ^2.0.0
cookie-parser: ^1.4.5
cors: ^2.8.5
dayjs: ^1.11.18
decimal.js: ^10.4.3
dotenv: ^16.0.1
express: ^5.0.0
Expand Down Expand Up @@ -9558,7 +9559,7 @@ __metadata:
languageName: node
linkType: hard

"dayjs@npm:^1.11.10":
"dayjs@npm:^1.11.18":
version: 1.11.18
resolution: "dayjs@npm:1.11.18"
checksum: cc90054bad30ab011417a7a474b2ffa70e7a28ca6f834d7e86fe53a408a40a14c174f26155072628670e9eda4c48c4ed0d847d2edf83d47c0bfb78be15bbf2dd
Expand Down Expand Up @@ -11975,7 +11976,7 @@ __metadata:
classnames: ^2.3.1
customize-cra: ^1.0.0
date-fns: ^4.1.0
dayjs: ^1.11.10
dayjs: ^1.11.18
file-saver: ^2.0.5
google-auth-library: ^9.15.0
jest-fail-on-console: ^3.0.2
Expand Down