Skip to content

Commit

Permalink
chore: merge pull request PapillonApp#133 from NonozgYtb/feat/date-ti…
Browse files Browse the repository at this point in the history
…metable

Change weekNumber to epochWeekNumber
  • Loading branch information
Vexcited committed Sep 5, 2024
2 parents 0700a40 + 172f092 commit a639a79
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 208 deletions.
10 changes: 7 additions & 3 deletions src/services/homework.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { type Account, AccountService } from "@/stores/account/types";
import { useHomeworkStore } from "@/stores/homework";
import type { Homework } from "./shared/Homework";
import {error} from "@/utils/logger/logger";
import { error } from "@/utils/logger/logger";
import { translateToWeekNumber } from "pawnote";
import { pronoteFirstDate } from "./pronote/timetable";
import { dateToEpochWeekNumber, epochWNToPronoteWN } from "@/utils/epochWeekNumber";

/**
* Updates the state and cache for the homework of given week number.
*/
export async function updateHomeworkForWeekInCache <T extends Account> (account: T, weekNumber: number): Promise<void> {
export async function updateHomeworkForWeekInCache <T extends Account> (account: T, date: Date): Promise<void> {
let homeworks: Homework[] = [];

try {
switch (account.service) {
case AccountService.Pronote: {
const { getHomeworkForWeek } = await import("./pronote/homework");
const weekNumber = translateToWeekNumber(date, account.instance?.instance.firstDate || pronoteFirstDate);
homeworks = await getHomeworkForWeek(account, weekNumber);
break;
}
Expand All @@ -24,7 +28,7 @@ export async function updateHomeworkForWeekInCache <T extends Account> (account:
console.info(`[updateHomeworkForWeekInCache]: updating to empty since ${account.service} not implemented.`);
}

useHomeworkStore.getState().updateHomeworks(weekNumber, homeworks);
useHomeworkStore.getState().updateHomeworks(dateToEpochWeekNumber(date), homeworks);
}
catch (err) {
error("not updated, see:" + err, "updateHomeworkForWeekInCache");
Expand Down
4 changes: 3 additions & 1 deletion src/services/pronote/timetable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ErrorServiceUnauthenticated } from "../shared/errors";
import pronote from "pawnote";
import { info } from "@/utils/logger/logger";

export const pronoteFirstDate = new Date("2024-09-01");

const decodeTimetableClass = (c: pronote.TimetableClassLesson | pronote.TimetableClassDetention | pronote.TimetableClassActivity): TimetableClass => {
const base = {
startTimestamp: c.startDate.getTime(),
Expand Down Expand Up @@ -58,4 +60,4 @@ export const getTimetableForWeek = async (account: PronoteAccount, weekNumber: n
});

return timetable.classes.map(decodeTimetableClass);
};
};
10 changes: 5 additions & 5 deletions src/services/timetable.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { type Account, AccountService } from "@/stores/account/types";
import { useTimetableStore } from "@/stores/timetable";
import { epochWNToPronoteWN } from "@/utils/epochWeekNumber";

/**
* Updates the state and cache for the timetable of given week number.
*/
export async function updateTimetableForWeekInCache <T extends Account> (account: T, weekNumber: number): Promise<void> {
export async function updateTimetableForWeekInCache <T extends Account> (account: T, epochWeekNumber: number): Promise<void> {
switch (account.service) {
case AccountService.Pronote: {
const { getTimetableForWeek } = await import("./pronote/timetable");
const weekNumber = epochWNToPronoteWN(epochWeekNumber, account);
const timetable = await getTimetableForWeek(account, weekNumber);
useTimetableStore.getState().updateClasses(weekNumber, timetable);
useTimetableStore.getState().updateClasses(epochWeekNumber, timetable);
break;
}
case AccountService.Local: {
const timetable = [];
useTimetableStore.getState().updateClasses(weekNumber, [
{}
]);
useTimetableStore.getState().updateClasses(epochWeekNumber, []);
break;
}
default: {
Expand Down
2 changes: 1 addition & 1 deletion src/stores/account/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const useCurrentAccount = create<CurrentAccountStore>()((set, get) => ({
log("reloaded all external accounts", "[switchTo]");

set({ linkedAccounts });
log(`done reading ${account.name}and rehydrating stores.`, "[switchTo]");
log(`done reading ${account.name} and rehydrating stores.`, "[switchTo]");
},

linkExistingExternalAccount: (account) => {
Expand Down
8 changes: 4 additions & 4 deletions src/stores/homework/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ export const useHomeworkStore = create<HomeworkStore>()(
persist(
(set) => ({
homeworks: {},
updateHomeworks: (weekNumber, homeworks) => {
log(`updating homeworks for week ${weekNumber}`, "homework:updateHomeworks");
updateHomeworks: (epochWeekNumber, homeworks) => {
log(`updating homeworks for week ${epochWeekNumber}`, "homework:updateHomeworks");

set((state) => {
return {
homeworks: {
...state.homeworks,
[weekNumber]: homeworks
[epochWeekNumber]: homeworks
}
};
});

log(`updated homeworks for week ${weekNumber}`, "homework:updateHomeworks");
log(`updated homeworks for week ${epochWeekNumber}`, "homework:updateHomeworks");
}
}),
{
Expand Down
2 changes: 1 addition & 1 deletion src/stores/homework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import type { Homework } from "@/services/shared/Homework";

export interface HomeworkStore {
homeworks: Record<number, Homework[]>
updateHomeworks: (weekNumber: number, homeworks: Homework[]) => void
updateHomeworks: (epochWeekNumber: number, homeworks: Homework[]) => void
}
2 changes: 1 addition & 1 deletion src/stores/timetable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ export const useTimetableStore = create<TimetableStore>()(
storage: createJSONStorage(() => AsyncStorage)
}
)
);
);
3 changes: 2 additions & 1 deletion src/stores/timetable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import type { Timetable } from "@/services/shared/Timetable";

export interface TimetableStore {
timetables: Record<number, Timetable>,
updateClasses: (weekNumber: number, classes: Timetable) => void
updateClasses: (epochWeekNumber: number, classes: Timetable) => void
}

103 changes: 103 additions & 0 deletions src/utils/epochWeekNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
-----------------------------------------------------------------------------------------------
Papillon's custom system for week number since 01/01/1970
This part is for handleing the date conversion from JS date, Pronote's week number and our
"epochWeekNumber" (which try to represent the total number of weeks since the UNIX epoch, aka
1st January 1970). It can be a little be messy but it's the best way I found to handle the date
conversion between the different systems. I tried to make it as simple as possible and modular,
and I added a ton of comments to help u. If you still have a question, feel free to ask me. - NonozgYtb ;)
-----------------------------------------------------------------------------------------------
*/

import { pronoteFirstDate } from "@/services/pronote/timetable";
import type { PronoteAccount } from "@/stores/account/types";
import { translateToWeekNumber } from "pawnote";

const EPOCH_WN_CONFIG = {
setHour: 6, // We are in Europe, so we set the hour to 6 UTC to avoid any problem with the timezone (= 2h in the morning in Summer Paris timezone)
setStartDay: 1, // We set the first day of the week to Monday to ensure that the week number is the same for the whole world
setMiddleDay: 3, // We set the middle day of the week to Wednesday to ensure <... same than above ...>
setEndDay: 7, // We set the last day of the week to Sunday to ensure <...>
numberOfMsInAWeek: 1000 /* ms */ * 60 /* s */ * 60 /* min */ * 24 /* h */ * 7, /* days */
adjustEpochInitialDate: 259200000, // =(((new Date(0)).getDay()-1) * EPOCH_WN_CONFIG.numberOfMsInAWeek/7) // We need to substract this for having a good range cause 01/01/1970 was not a Monday and the "-1" is to have Monday as the first day of the week
};

/**
* For comparing days and week, we need to have a common day to start the week, aka here Wednesday, 6:0:0:0
*!It's internal and should not be used outside of this file.
*/
const dayToWeekCommonDay = (date: Date): Date => {
const _date = new Date(date);
_date.setHours(EPOCH_WN_CONFIG.setHour, 0, 0, 0);
_date.setDate(_date.getDate() - ( (7 + _date.getDay() - 1) %7 ) + EPOCH_WN_CONFIG.setMiddleDay - 1);
// the (7+ ... -1 ) %7 is to have Monday as the first day (0) of the week and Sunday as last day (7) cause JS start the week on Sunday ¯\_(ツ)_/¯
// In details : the 7+ is to avoid negative value, the -1 is to have Monday as the first day of the week and the %7 is to have the right day number (0 to 6)
// In details : setMiddleDay - 1 is to have the middle day of the week, aka Wednesday
// Its to avoid the fact that Sunday is in the next week with simpler JS code.
return _date;
};

export const epochWNToDate = (epochWeekNumber: number)=>dayToWeekCommonDay(weekNumberToMiddleDate(epochWeekNumber));

export const epochWNToPronoteWN = (epochWeekNumber: number, account: PronoteAccount) =>
translateToWeekNumber(epochWNToDate(epochWeekNumber), account.instance?.instance.firstDate || pronoteFirstDate) || 1;

/**
* Convert a date to a week number.
*/
export const dateToEpochWeekNumber = (date: Date): number => {
const commonDay = dayToWeekCommonDay(date);
const epochWeekNumber = Math.floor((commonDay.getTime() + EPOCH_WN_CONFIG.adjustEpochInitialDate - ( (EPOCH_WN_CONFIG.setMiddleDay - 1) /7 ) * EPOCH_WN_CONFIG.numberOfMsInAWeek) / EPOCH_WN_CONFIG.numberOfMsInAWeek);
// this is the opposite of the weekNumberToMiddleDate function
return epochWeekNumber;
};

/**
* Check if the test date is in the same week as the reference date.
* If we need to check if the test date is "near" our reference date, we can use the numberOfWeeksBefore and numberOfWeeksAfter parameters.
* So if I want to check if the test date is in the same week or the next week, I can call isInTheWeek(referenceDate, testDate, 0, 1).
*/
export const isInTheWeek = (referenceDate: Date, testDate: Date, numberOfWeeksBefore = 0, numberOfWeeksAfter = 0): boolean => {
const referenceWeek = dateToEpochWeekNumber(referenceDate);
const testWeek = dateToEpochWeekNumber(testDate);
return testWeek >= referenceWeek - numberOfWeeksBefore && testWeek <= referenceWeek + numberOfWeeksAfter;
};

export const weekNumberToDateRange = (epochWeekNumber: number, numberOfWeeksBefore = 0, numberOfWeeksAfter = 0): { start: Date; end: Date } => {
const start = new Date(
epochWeekNumber * EPOCH_WN_CONFIG.numberOfMsInAWeek
- EPOCH_WN_CONFIG.adjustEpochInitialDate
- numberOfWeeksBefore * EPOCH_WN_CONFIG.numberOfMsInAWeek
);
const end = new Date(
epochWeekNumber * EPOCH_WN_CONFIG.numberOfMsInAWeek
+ ( 6/7 ) * EPOCH_WN_CONFIG.numberOfMsInAWeek // 6/7 is to have the end of the week, aka Sunday (we are in Europe so we dont need to worry if we want to include the Sunday)
- EPOCH_WN_CONFIG.adjustEpochInitialDate
+ numberOfWeeksAfter * EPOCH_WN_CONFIG.numberOfMsInAWeek
);
return { start, end };
};

export const weekNumberToMiddleDate = (epochWeekNumber: number): Date => {
const date = new Date(
epochWeekNumber * EPOCH_WN_CONFIG.numberOfMsInAWeek
+ ( (EPOCH_WN_CONFIG.setMiddleDay - 1) /7 ) * EPOCH_WN_CONFIG.numberOfMsInAWeek // (setMiddleDay-1)/7 is to have the middle of the week, aka Wednesday
- EPOCH_WN_CONFIG.adjustEpochInitialDate
);
return date;
};

export const epochWMToCalendarWeekNumber = (epochWeekNumber: number): number => {
const date = weekNumberToMiddleDate(epochWeekNumber);
// Set Day to Sunday and make it the 7th day of the week
date.setUTCDate(date.getUTCDate() + 4 - (date.getUTCDay()||7));
// Get first day of year
var yearStart = new Date(Date.UTC(date.getUTCFullYear(),0,1));
// Calculate full weeks to nearest Thursday
var weekNo = Math.ceil(( ( (date.getTime() - yearStart.getTime()) / 86400000) + 1)/7);
// Return array of year and week number
return weekNo;
};
63 changes: 9 additions & 54 deletions src/views/account/Home/Elements/HomeworksElement.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,31 @@
import { NativeItem, NativeList, NativeListHeader, NativeText } from "@/components/Global/NativeComponents";
import { NativeList, NativeListHeader } from "@/components/Global/NativeComponents";
import { useCurrentAccount } from "@/stores/account";
import { AccountService } from "@/stores/account/types";
import { useTheme } from "@react-navigation/native";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useHomeworkStore } from "@/stores/homework";
import { toggleHomeworkState, updateHomeworkForWeekInCache } from "@/services/homework";
import HomeworkItem from "../../Homeworks/Atoms/Item";
import { Homework } from "@/services/shared/Homework";
import { debounce } from "lodash";
import { PapillonNavigation } from "@/router/refs";
import RedirectButton from "@/components/Home/RedirectButton";
import { dateToEpochWeekNumber } from "@/utils/epochWeekNumber";

const HomeworksElement = () => {
const { colors } = useTheme();
const insets = useSafeAreaInsets();
const account = useCurrentAccount(store => store.account!);
const homeworks = useHomeworkStore(store => store.homeworks);

const [currentWeek, setCurrentWeek] = useState(0);

const currentDay = new Date(/* "2024-05-27" */);
const [firstDate, setFirstDate] = useState(new Date("2024-09-01"));

const [hwList, setHwList] = useState([]);

useEffect(() => {
if (account.instance) {
if (account.service === AccountService.Pronote) {
setFirstDate(new Date(account.instance.instance.firstDate));
}
}
}, [account]);

const getWeekNumber = (date: Date) => {
const firstDayOfYear = new Date(firstDate);
const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
};

const [currentlyUpdating, setCurrentlyUpdating] = useState(false);
const actualDay = useMemo(()=>new Date(), []);

const updateHomeworks = useCallback(async () => {
await updateHomeworkForWeekInCache(account, currentWeek);
}, [account, currentWeek]);
await updateHomeworkForWeekInCache(account, actualDay);
}, [account, actualDay]);

const debouncedUpdateHomeworks = useMemo(() => debounce(updateHomeworks, 500), [updateHomeworks]);

useEffect(() => {
debouncedUpdateHomeworks();
}, [account.instance, currentWeek]);

useEffect(() => {
setCurrentWeek(getWeekNumber(currentDay));
}, [currentDay, currentlyUpdating]);
}, [account.instance, actualDay]);

const handleDonePress = useCallback(
async (homework: Homework) => {
Expand All @@ -63,24 +35,7 @@ const HomeworksElement = () => {
[account, updateHomeworks]
);

// Calcul de la plage de dates de la semaine actuelle
const getStartAndEndOfWeek = (date: Date) => {
const startOfWeek = new Date(date);
startOfWeek.setDate(date.getDate() - date.getDay()); // Premier jour de la semaine (dimanche)
const endOfWeek = new Date(startOfWeek);
endOfWeek.setDate(startOfWeek.getDate() + 6); // Dernier jour de la semaine (samedi)
return { startOfWeek, endOfWeek };
};

const { startOfWeek, endOfWeek } = useMemo(() => getStartAndEndOfWeek(currentDay), [currentDay]);

// Filtrage des devoirs pour la semaine actuelle
const homeworksForCurrentWeek = homeworks[currentWeek]?.filter(hw => {
const hwDate = new Date(hw.due);
return hwDate >= startOfWeek && hwDate <= endOfWeek;
});

if (!homeworksForCurrentWeek || homeworksForCurrentWeek.length === 0) {
if (!homeworks[dateToEpochWeekNumber(actualDay)] || homeworks[dateToEpochWeekNumber(actualDay)]?.filter(hw => new Date(hw.due).getDate() === actualDay.getDate()).length === 0) {
return null;
}

Expand All @@ -92,12 +47,12 @@ const HomeworksElement = () => {
)}
/>
<NativeList>
{homeworksForCurrentWeek.map((hw, index) => (
{homeworks[dateToEpochWeekNumber(actualDay)]?.filter(hw => new Date(hw.due).getDate() === actualDay.getDate()).map((hw, index) => (
<HomeworkItem
homework={hw}
key={index}
index={index}
total={homeworksForCurrentWeek.length}
total={homeworks[dateToEpochWeekNumber(actualDay)].length}
onDonePressHandler={() => {
handleDonePress(hw);
}}
Expand Down
Loading

0 comments on commit a639a79

Please sign in to comment.