Repository: IraSoro/peri (ts)

Issue:
Add a lib for working with dates
Currently, standard JS functions are used to calculate dates. The ```date-fns``` lib is also used to format dates.
However, all calculations occur in a not very clear and beautiful way. For example, initializing dates without taking into account the time zone goes like this
``` ts
const date = new Date();
date.setHours(0, 0, 0, 0);
```
Adding a few days to a date looks like this
```ts
date.setDate(date.getDate() + fewDats);
```
Getting the days between dates looks like this
```ts
const millisecondsInDay = 24 * 60 * 60 * 1000;
const diff = (firstDate.getTime() - secondDate.getTime()) / millisecondsInDay;
```

You need to find a library that makes date calculations simpler and more understandable.

Pull request:
Replaced custom `Date` operations with `date-fns` based
Closes #120 

In this PR I implemented two wrappers: translation for i18n and datetime for date-fns

In date-fns I implemented function for locale changing, This implementation is based on this implementation https://date-fns.org/v2.30.0/docs/I18na

Patch:
diff --git a/src/data/Storage.ts b/src/data/Storage.ts
index b73237b..7ed8137 100644
--- a/src/data/Storage.ts
+++ b/src/data/Storage.ts
@@ -1,4 +1,5 @@
 import { Storage, Drivers } from "@ionic/storage";
+import { addDays, startOfToday, subDays } from "date-fns";
 import type { Cycle } from "./ClassCycle";
 import { getAverageLengthOfPeriod } from "../state/CalculationLogics";
 
@@ -66,12 +67,12 @@ export const storage = {
 // _todayOvulation(6).catch((err) => console.error(err));
 // _tomorrowOvulation(6).catch((err) => console.error(err));
 // _menstrualPhase(6).catch((err) => console.error(err));
-// _randomMenstrualPhase(6).catch((err) => console.error(err));
 // _follicularPhase(6).catch((err) => console.error(err));
-// _randomFollicularPhase(6).catch((err) => console.error(err));
 // _lutealPhase(6).catch((err) => console.error(err));
-// _randomLutealPhase(6).catch((err) => console.error(err));
 // _delayOfCycle(6).catch((err) => console.error(err));
+// _randomMenstrualPhase(6).catch((err) => console.error(err));
+// _randomFollicularPhase(6).catch((err) => console.error(err));
+// _randomLutealPhase(6).catch((err) => console.error(err));
 // _randomDelayOfCycle(6).catch((err) => console.error(err));
 
 function _emptyArrayOfCycles(): Promise<void> {
@@ -79,13 +80,12 @@ function _emptyArrayOfCycles(): Promise<void> {
 }
 
 function _todayPeriod(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-
   const cycles: Cycle[] = [];
+  let date = startOfToday();
+
+  for (let i = 0; i < countOfCycles; i++) {
+    date = subDays(date, 28);
 
-  for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -97,14 +97,11 @@ function _todayPeriod(countOfCycles: number): Promise<void> {
 }
 
 function _todayOvulation(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 15);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 15);
 
-  for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
+  for (let i = 0; i < countOfCycles; i++) {
+    date = subDays(date, 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -116,14 +113,11 @@ function _todayOvulation(countOfCycles: number): Promise<void> {
 }
 
 function _tomorrowOvulation(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 16);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 16);
 
   for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
+    date = subDays(date, 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -135,14 +129,11 @@ function _tomorrowOvulation(countOfCycles: number): Promise<void> {
 }
 
 function _menstrualPhase(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 25);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 25);
 
-  for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
+  for (let i = 0; i < countOfCycles; i++) {
+    date = subDays(date, 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -154,14 +145,11 @@ function _menstrualPhase(countOfCycles: number): Promise<void> {
 }
 
 function _follicularPhase(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 20);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 20);
 
   for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
+    date = subDays(date, 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -173,14 +161,11 @@ function _follicularPhase(countOfCycles: number): Promise<void> {
 }
 
 function _lutealPhase(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 10);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 10);
 
   for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
+    date = subDays(date, 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -192,14 +177,11 @@ function _lutealPhase(countOfCycles: number): Promise<void> {
 }
 
 function _delayOfCycle(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() - 5);
-
   const cycles: Cycle[] = [];
+  let date = subDays(startOfToday(), 5);
 
   for (let i = 0; i < countOfCycles; ++i) {
-    date.setDate(date.getDate() - 28);
+    date = subDays(date, 28);
     cycles.push({
       cycleLength: 28,
       periodLength: 6,
@@ -217,15 +199,12 @@ function random(min: number, max: number) {
 }
 
 function _randomMenstrualPhase(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 26);
-
   const cycles: Cycle[] = [];
+  let date: Date = addDays(startOfToday(), 26);
 
   for (let i = 0; i < countOfCycles; ++i) {
     const cycleLength = random(26, 30);
-    date.setDate(date.getDate() - cycleLength);
+    date = subDays(date, cycleLength);
     cycles.push({
       cycleLength: cycleLength,
       periodLength: random(4, 6),
@@ -240,15 +219,12 @@ function _randomMenstrualPhase(countOfCycles: number): Promise<void> {
 }
 
 function _randomFollicularPhase(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 20);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 20);
 
   for (let i = 0; i < countOfCycles; ++i) {
     const cycleLength = random(26, 30);
-    date.setDate(date.getDate() - cycleLength);
+    date = subDays(date, cycleLength);
     cycles.push({
       cycleLength: cycleLength,
       periodLength: random(4, 6),
@@ -263,15 +239,12 @@ function _randomFollicularPhase(countOfCycles: number): Promise<void> {
 }
 
 function _randomLutealPhase(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() + 10);
-
   const cycles: Cycle[] = [];
+  let date = addDays(startOfToday(), 10);
 
   for (let i = 0; i < countOfCycles; ++i) {
     const cycleLength = random(26, 30);
-    date.setDate(date.getDate() - cycleLength);
+    date = subDays(date, cycleLength);
     cycles.push({
       cycleLength: cycleLength,
       periodLength: random(4, 6),
@@ -286,15 +259,12 @@ function _randomLutealPhase(countOfCycles: number): Promise<void> {
 }
 
 function _randomDelayOfCycle(countOfCycles: number): Promise<void> {
-  const date: Date = new Date();
-  date.setHours(0, 0, 0, 0);
-  date.setDate(date.getDate() - 5);
-
   const cycles: Cycle[] = [];
+  let date = subDays(startOfToday(), 5);
 
   for (let i = 0; i < countOfCycles; ++i) {
     const cycleLength = random(26, 30);
-    date.setDate(date.getDate() - cycleLength);
+    date = subDays(date, cycleLength);
     cycles.push({
       cycleLength: cycleLength,
       periodLength: random(4, 6),
diff --git a/src/index.tsx b/src/index.tsx
index e752735..0891ea0 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,69 +1,39 @@
 import React from "react";
 import { createRoot } from "react-dom/client";
 
-import i18n from "i18next";
-import { initReactI18next } from "react-i18next";
-import intervalPlural from "i18next-intervalplural-postprocessor";
-
-import en from "./languages/en";
-import ru from "./languages/ru";
-import { storage } from "./data/Storage";
-
 import App from "./App";
 import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
 import reportWebVitals from "./reportWebVitals";
 
-async function initTranslation() {
-  const selectedLanguage =
-    (await storage.getUnsafe.language()) || navigator.language.split("-")[0];
+import { init as initTranslation } from "./utils/translation";
+import { init as initDateTimeLocale } from "./utils/datetime";
 
-  console.log(selectedLanguage);
+async function init() {
+  await initTranslation();
+  await initDateTimeLocale();
 
-  await i18n
-    .use(initReactI18next)
-    .use(intervalPlural)
-    .init({
-      resources: {
-        en: {
-          translation: en,
-        },
-        ru: {
-          translation: ru,
-        },
-      },
-      lng: selectedLanguage,
-      fallbackLng: "en",
-      interpolation: {
-        escapeValue: false,
-      },
-    });
+  const container = document.getElementById("root");
 
-  await storage.set.language(selectedLanguage);
-}
+  if (!container) {
+    throw new Error("Can't find element in the document with id `root`");
+  }
 
-initTranslation()
-  .then(() => {
-    const container = document.getElementById("root");
+  const root = createRoot(container);
+  root.render(
+    <React.StrictMode>
+      <App />
+    </React.StrictMode>,
+  );
 
-    if (!container) {
-      throw new Error("Can't find element in the document with id `root`");
-    }
+  // If you want your app to work offline and load faster, you can change
+  // unregister() to register() below. Note this comes with some pitfalls.
+  // Learn more about service workers: https://cra.link/PWA
+  serviceWorkerRegistration.unregister();
 
-    const root = createRoot(container);
-    root.render(
-      <React.StrictMode>
-        <App />
-      </React.StrictMode>,
-    );
-
-    // If you want your app to work offline and load faster, you can change
-    // unregister() to register() below. Note this comes with some pitfalls.
-    // Learn more about service workers: https://cra.link/PWA
-    serviceWorkerRegistration.unregister();
+  // If you want to start measuring performance in your app, pass a function
+  // to log results (for example: reportWebVitals(console.log))
+  // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+  reportWebVitals();
+}
 
-    // If you want to start measuring performance in your app, pass a function
-    // to log results (for example: reportWebVitals(console.log))
-    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-    reportWebVitals();
-  })
-  .catch((err) => console.error(err));
+init().catch((err) => console.error(err));
diff --git a/src/modals/Menu.tsx b/src/modals/Menu.tsx
index e46ea52..276d253 100644
--- a/src/modals/Menu.tsx
+++ b/src/modals/Menu.tsx
@@ -24,26 +24,28 @@ import {
   isNewVersionAvailable,
 } from "../data/AppVersion";
 import { CyclesContext } from "../state/Context";
+import {
+  changeTranslation,
+  getCurrentTranslation,
+  supportedLanguages,
+} from "../utils/translation";
+import { changeDateTimeLocale } from "../utils/datetime";
 
 import "./Menu.css";
 
 const LanguageSwitcher = () => {
-  const { t, i18n } = useTranslation();
-
-  type LanguageCode = string;
-  type Language = string;
-  const supportedLanguages = new Map<LanguageCode, Language>([
-    ["en", "english"],
-    ["ru", "русский"],
-  ]);
-
-  const changeLanguage = async (languageCode: string) => {
-    await i18n.changeLanguage(languageCode);
-    await storage.set.language(languageCode);
+  const { t } = useTranslation();
+
+  const changeLanguage = async (language: string) => {
+    await changeTranslation(language);
+    changeDateTimeLocale(language);
+    await storage.set.language(language);
+
+    console.log(`Application language has been changed to ${language}`);
   };
 
   const languages = [];
-  for (const [code, language] of supportedLanguages.entries()) {
+  for (const [code, language] of supportedLanguages) {
     languages.push(
       <IonSelectOption
         key={code}
@@ -62,8 +64,7 @@ const LanguageSwitcher = () => {
       />
       <IonLabel color="dark">{t("Language")}</IonLabel>
       <IonSelect
-        value={i18n.language}
-        placeholder={supportedLanguages.get(i18n.language)}
+        value={getCurrentTranslation()}
         interface="popover"
         onIonChange={(event) => {
           changeLanguage(event.target.value as string).catch((err) => {
diff --git a/src/modals/WelcomeModal.tsx b/src/modals/WelcomeModal.tsx
index b1189ab..e1b532e 100644
--- a/src/modals/WelcomeModal.tsx
+++ b/src/modals/WelcomeModal.tsx
@@ -9,9 +9,11 @@ import {
   IonDatetime,
 } from "@ionic/react";
 import { useTranslation } from "react-i18next";
+import { startOfDay, startOfToday } from "date-fns";
 
 import { CyclesContext } from "../state/Context";
 import { getNewCyclesHistory } from "../state/CalculationLogics";
+import { getCurrentTranslation } from "../utils/translation";
 
 interface PropsWelcomeModal {
   isOpen: boolean;
@@ -68,18 +70,12 @@ const Welcome = (props: PropsWelcomeModal) => {
             style={{ borderRadius: "20px", maxHeight: "340px" }}
             ref={datetimeRef}
             presentation="date"
-            locale={t("locale")}
+            locale={getCurrentTranslation()}
             size="cover"
             multiple
             firstDayOfWeek={1}
-            isDateEnabled={(dateISO) => {
-              const currentDate = new Date();
-              currentDate.setHours(0, 0, 0, 0);
-
-              const date = new Date(dateISO);
-              date.setHours(0, 0, 0, 0);
-
-              return date <= currentDate;
+            isDateEnabled={(selectedDate) => {
+              return startOfDay(new Date(selectedDate)) <= startOfToday();
             }}
           />
         </div>
diff --git a/src/pages/TabDetails.tsx b/src/pages/TabDetails.tsx
index 6b9c042..9b2713b 100644
--- a/src/pages/TabDetails.tsx
+++ b/src/pages/TabDetails.tsx
@@ -8,16 +8,18 @@ import {
   IonCol,
 } from "@ionic/react";
 import { useTranslation } from "react-i18next";
-import "./TabDetails.css";
+import { addDays, startOfDay } from "date-fns";
 
 import {
   getAverageLengthOfCycle,
   getAverageLengthOfPeriod,
   getDayOfCycle,
   getLastStartDate,
-  getFormattedDate,
 } from "../state/CalculationLogics";
 import { CyclesContext } from "../state/Context";
+import { format } from "../utils/datetime";
+
+import "./TabDetails.css";
 
 interface InfoOneCycle {
   lengthOfCycleString: string;
@@ -48,13 +50,9 @@ function useInfoForOneCycle(idx: number): InfoOneCycle {
 
   const periodLenNumber: number = cycles[idx].periodLength;
 
-  const dateStart: Date = new Date(cycles[idx].startDate);
-  const dateFinish: Date = new Date(cycles[idx].startDate);
-  dateFinish.setDate(dateFinish.getDate() + cycleLenNumber - 1);
-  const dates = `${getFormattedDate(
-    dateStart,
-    t("locale"),
-  )} - ${getFormattedDate(dateFinish, t("locale"))}`;
+  const startDate = startOfDay(new Date(cycles[idx].startDate));
+  const endDate = addDays(startDate, cycleLenNumber - 1);
+  const dates = `${format(startDate, "MMMM d")} - ${format(endDate, "MMMM d")}`;
 
   return {
     lengthOfCycleNumber: cycleLenNumber,
@@ -116,7 +114,7 @@ const CurrentCycle = () => {
           buffer={setBufferProgress(dayOfCycle)}
         />
         <IonLabel>
-          <p style={datesStyle}>{getFormattedDate(startDate, t("locale"))}</p>
+          <p style={datesStyle}>{format(startDate, "MMMM d")}</p>
         </IonLabel>
       </div>
     </div>
diff --git a/src/pages/TabHome.tsx b/src/pages/TabHome.tsx
index 9e5da32..5f10813 100644
--- a/src/pages/TabHome.tsx
+++ b/src/pages/TabHome.tsx
@@ -32,6 +32,7 @@ import {
   getPastFuturePeriodDays,
   isPeriodToday,
 } from "../state/CalculationLogics";
+import { getCurrentTranslation } from "../utils/translation";
 
 import { chevronForwardOutline } from "ionicons/icons";
 
@@ -191,7 +192,7 @@ const TabHome = (props: HomeProps) => {
                 style={{ borderRadius: "20px" }}
                 ref={datetimeRef}
                 presentation="date"
-                locale={t("locale")}
+                locale={getCurrentTranslation()}
                 size="cover"
                 multiple
                 firstDayOfWeek={1}
diff --git a/src/state/CalculationLogics.ts b/src/state/CalculationLogics.ts
index 2c8c849..00655e9 100644
--- a/src/state/CalculationLogics.ts
+++ b/src/state/CalculationLogics.ts
@@ -1,9 +1,8 @@
 import i18n from "i18next";
-import { Cycle } from "../data/ClassCycle";
-import { format } from "date-fns";
-import { ru } from "date-fns/locale";
+import { addDays, differenceInDays, startOfDay, startOfToday } from "date-fns";
 
-const millisecondsInDay = 24 * 60 * 60 * 1000;
+import { Cycle } from "../data/ClassCycle";
+import { format } from "../utils/datetime";
 
 export function getLastStartDate(cycles: Cycle[]) {
   if (cycles.length === 0) {
@@ -92,14 +91,9 @@ export function getDaysBeforePeriod(cycles: Cycle[]) {
   const startDate = cycles[0].startDate;
   const cycleLength = getAverageLengthOfCycle(cycles);
 
-  const dateOfFinish = new Date(startDate);
-  dateOfFinish.setDate(dateOfFinish.getDate() + cycleLength);
-  dateOfFinish.setHours(0, 0, 0, 0);
-  const now = new Date();
-  now.setHours(0, 0, 0, 0);
-  const dayBefore = Math.round(
-    (Number(dateOfFinish) - Number(now)) / millisecondsInDay,
-  );
+  const dateOfFinish = addDays(startOfDay(new Date(startDate)), cycleLength);
+  const now = startOfToday();
+  const dayBefore = differenceInDays(dateOfFinish, now);
 
   if (dayBefore > 0) {
     return {
@@ -130,16 +124,10 @@ export function getDayOfCycle(cycles: Cycle[]) {
     return 0;
   }
 
-  const start = new Date(cycles[0].startDate);
-  start.setHours(0, 0, 0, 0);
-  const currentDate = new Date();
-  currentDate.setHours(0, 0, 0, 0);
-
-  const diff =
-    Math.ceil((currentDate.getTime() - start.getTime()) / millisecondsInDay) +
-    1;
+  const start = startOfDay(new Date(cycles[0].startDate));
+  const currentDate = startOfToday();
 
-  return diff;
+  return differenceInDays(currentDate, start) + 1;
 }
 
 export function getPhase(cycles: Cycle[]) {
@@ -278,11 +266,9 @@ export function getNewCyclesHistory(periodDays: string[]) {
     },
   ];
   for (let i = 1; i < periodDays.length; i++) {
-    const date = new Date(periodDays[i]);
-    const prevDate = new Date(periodDays[i - 1]);
-    const diffInDays = Math.abs(
-      (date.getTime() - prevDate.getTime()) / millisecondsInDay,
-    );
+    const date = startOfDay(new Date(periodDays[i]));
+    const prevDate = startOfDay(new Date(periodDays[i - 1]));
+    const diffInDays = differenceInDays(date, prevDate);
 
     if (diffInDays <= 1) {
       newCycles[0].periodLength++;
@@ -305,12 +291,10 @@ export function getLastPeriodDays(cycles: Cycle[]) {
   const periodDays: string[] = [];
 
   for (const cycle of cycles) {
-    const startOfCycle = new Date(cycle.startDate);
-    startOfCycle.setHours(0, 0, 0, 0);
+    const startOfCycle = startOfDay(new Date(cycle.startDate));
 
     for (let i = 0; i < cycle.periodLength; i++) {
-      const newDate = new Date(startOfCycle);
-      newDate.setDate(startOfCycle.getDate() + i);
+      const newDate = addDays(startOfCycle, i);
       periodDays.push(format(newDate, "yyyy-MM-dd"));
     }
   }
@@ -318,19 +302,17 @@ export function getLastPeriodDays(cycles: Cycle[]) {
 }
 
 export function getActiveDates(dateString: string, cycles: Cycle[]) {
-  const date = new Date(dateString);
-  date.setHours(0, 0, 0, 0);
-
-  const now = new Date();
-  now.setHours(0, 0, 0, 0);
+  const date = startOfDay(new Date(dateString));
+  const now = startOfToday();
 
   if (cycles.length === 0) {
     return date <= now;
   }
 
-  const endPeriod = new Date(cycles[0].startDate);
-  endPeriod.setHours(0, 0, 0, 0);
-  endPeriod.setDate(endPeriod.getDate() + cycles[0].periodLength - 1);
+  const endPeriod = addDays(
+    startOfDay(new Date(cycles[0].startDate)),
+    cycles[0].periodLength - 1,
+  );
 
   return date <= endPeriod || date <= now;
 }
@@ -340,41 +322,32 @@ export function getMarkModalActiveDates(dateString: string, cycles: Cycle[]) {
     return true;
   }
 
-  const date = new Date(dateString);
-  date.setHours(0, 0, 0, 0);
-
-  const futureCycleFinish: Date = new Date(cycles[0].startDate);
-  futureCycleFinish.setDate(
-    futureCycleFinish.getDate() + cycles[0].periodLength - 1,
+  const date = startOfDay(new Date(dateString));
+  const futureCycleFinish = addDays(
+    startOfDay(new Date(cycles[0].startDate)),
+    cycles[0].periodLength - 1,
   );
-  futureCycleFinish.setHours(0, 0, 0, 0);
 
   return date.getTime() > futureCycleFinish.getTime();
 }
 
 export function getPastFuturePeriodDays(cycles: Cycle[]) {
-  const nowDate = new Date();
-  nowDate.setHours(0, 0, 0, 0);
-
+  const nowDate = startOfToday();
   const periodDates = getLastPeriodDays(cycles);
   const lengthOfPeriod = getAverageLengthOfPeriod(cycles);
 
   if (cycles.length !== 0) {
-    const endOfCurrentCycle = new Date(cycles[0].startDate);
-    endOfCurrentCycle.setDate(
-      endOfCurrentCycle.getDate() + cycles[0].periodLength,
+    const endOfCurrentCycle = addDays(
+      startOfDay(new Date(cycles[0].startDate)),
+      cycles[0].periodLength,
     );
-    endOfCurrentCycle.setHours(0, 0, 0, 0);
     if (endOfCurrentCycle >= nowDate) {
       return [];
     }
   }
 
   for (let day = 0; day < (lengthOfPeriod || 5); day++) {
-    const periodDay = new Date(nowDate);
-    periodDay.setHours(0, 0, 0, 0);
-    periodDay.setDate(periodDay.getDate() + day);
-
+    const periodDay = addDays(nowDate, day);
     periodDates.push(format(periodDay, "yyyy-MM-dd"));
   }
 
@@ -384,29 +357,27 @@ export function getPastFuturePeriodDays(cycles: Cycle[]) {
 export function isForecastPeriodDays(date: Date, cycles: Cycle[]) {
   const lengthOfCycle = getAverageLengthOfCycle(cycles);
   const lengthOfPeriod = getAverageLengthOfPeriod(cycles);
-  const nowDate = new Date();
-  nowDate.setHours(0, 0, 0, 0);
-  date.setHours(0, 0, 0, 0);
+  const nowDate = startOfToday();
+  date = startOfDay(date);
 
   if (date <= nowDate) {
     return false;
   }
 
-  const nextCycleStart = new Date(cycles[0].startDate);
-  nextCycleStart.setDate(nextCycleStart.getDate() + lengthOfCycle);
-  const nextCycleFinish = new Date(cycles[0].startDate);
-  nextCycleFinish.setDate(
-    nextCycleFinish.getDate() + lengthOfCycle + lengthOfPeriod - 1,
+  const nextCycleStart = addDays(
+    startOfDay(new Date(cycles[0].startDate)),
+    lengthOfCycle,
+  );
+  const nextCycleFinish = addDays(
+    startOfDay(new Date(cycles[0].startDate)),
+    lengthOfCycle + lengthOfPeriod - 1,
   );
 
   if (date >= nextCycleStart && date < nextCycleFinish) {
     return true;
   }
 
-  const delayDate = new Date();
-  delayDate.setHours(0, 0, 0, 0);
-  delayDate.setDate(delayDate.getDate() + lengthOfPeriod - 1);
-
+  const delayDate = addDays(startOfToday(), lengthOfPeriod - 1);
   const dayOfCycle = getDayOfCycle(cycles);
 
   if (dayOfCycle > lengthOfCycle && date <= delayDate) {
@@ -432,27 +403,11 @@ export function isForecastPeriodToday(date: Date, cycles: Cycle[]) {
   return false;
 }
 
-export function getFormattedDate(date: Date, language: string) {
-  const formattedDates = new Map<string, string>([
-    ["en-GB", format(date, "MMM d")],
-    [
-      "ru",
-      format(date, "d MMMM", {
-        locale: ru,
-      }),
-    ],
-  ]);
-  return formattedDates.get(language) ?? format(date, "MMM d");
-}
-
 export function isPeriodToday(cycles: Cycle[]) {
   if (cycles.length === 0) {
     return false;
   }
 
-  const nowDate = new Date();
-  nowDate.setHours(0, 0, 0, 0);
-
   const dayOfCycle = getDayOfCycle(cycles);
 
   return dayOfCycle <= cycles[0].periodLength;
diff --git a/src/utils/datetime.ts b/src/utils/datetime.ts
new file mode 100644
index 0000000..ad8b948
--- /dev/null
+++ b/src/utils/datetime.ts
@@ -0,0 +1,49 @@
+import { format as dateFnsFormat } from "date-fns";
+import { enUS, ru } from "date-fns/locale";
+
+import { storage } from "../data/Storage";
+
+const locales = new Map([
+  ["en", enUS],
+  ["ru", ru],
+]);
+
+const defaultLocale = "en";
+let currentLocale = defaultLocale;
+
+export async function init() {
+  const appLanguage = await storage.get.language();
+
+  if (!locales.has(appLanguage)) {
+    console.log(
+      `Application language ${appLanguage} is not supported now by datetime, set default ${defaultLocale}`,
+    );
+    return;
+  }
+
+  currentLocale = appLanguage;
+  console.log(`Set datetime locale ${currentLocale}`);
+}
+
+export function changeDateTimeLocale(language: string) {
+  if (!locales.has(language)) {
+    console.warn(`Language ${language} is not supported yet`);
+    return;
+  }
+  currentLocale = language;
+}
+
+function modifyFormatString(formatString: string) {
+  if (formatString === "MMMM d" && ["ru"].includes(currentLocale)) {
+    return "d MMMM";
+  }
+  return formatString;
+}
+
+export function format(date: Date, formatString: string) {
+  formatString = modifyFormatString(formatString);
+
+  return dateFnsFormat(date, formatString, {
+    locale: locales.get(currentLocale),
+  });
+}
diff --git a/src/utils/translation.ts b/src/utils/translation.ts
new file mode 100644
index 0000000..72d0b87
--- /dev/null
+++ b/src/utils/translation.ts
@@ -0,0 +1,60 @@
+import i18n, { InitOptions } from "i18next";
+import { initReactI18next } from "react-i18next";
+import intervalPlural from "i18next-intervalplural-postprocessor";
+
+import en from "./translations/en";
+import ru from "./translations/ru";
+import { storage } from "../data/Storage";
+
+export const supportedLanguages = new Map([
+  ["en", "english"],
+  ["ru", "русский"],
+]);
+
+const defaultLanguageCode = "en";
+let currentLanguage = defaultLanguageCode;
+
+export async function init() {
+  await i18n
+    .use(initReactI18next)
+    .use(intervalPlural)
+    .init({
+      resources: {
+        en: {
+          translation: en,
+        },
+        ru: {
+          translation: ru,
+        },
+      },
+      lng: (await storage.getUnsafe.language()) || navigator.language,
+      fallbackLng: {
+        ru: ["ru"],
+        default: [defaultLanguageCode],
+      },
+    } satisfies InitOptions);
+
+  const appLanguage = i18n.languages.at(-1);
+
+  if (!appLanguage) {
+    throw new Error("Can't get language from i18next");
+  }
+
+  currentLanguage = appLanguage;
+  await storage.set.language(appLanguage);
+
+  console.log(`App language is ${appLanguage}`);
+}
+
+export function changeTranslation(language: string) {
+  if (!supportedLanguages.has(language)) {
+    console.warn(`Language ${language} is not supported yet`);
+    return;
+  }
+  currentLanguage = language;
+  return i18n.changeLanguage(language);
+}
+
+export function getCurrentTranslation(): string {
+  return currentLanguage;
+}
diff --git a/src/languages/en.tsx b/src/utils/translations/en.ts
similarity index 92%
rename from src/languages/en.tsx
rename to src/utils/translations/en.ts
index 08838a1..b82c724 100644
--- a/src/languages/en.tsx
+++ b/src/utils/translations/en.ts
@@ -1,5 +1,4 @@
 const en = {
-  locale: "en-GB",
   Days: "(0)[Days];(1)[Day];(2-inf)[Days];",
   day_ordinal_one: "{{count}}st day",
   day_ordinal_two: "{{count}}nd day",
diff --git a/src/languages/ru.tsx b/src/utils/translations/ru.ts
similarity index 99%
rename from src/languages/ru.tsx
rename to src/utils/translations/ru.ts
index 79a136a..b63fab2 100644
--- a/src/languages/ru.tsx
+++ b/src/utils/translations/ru.ts
@@ -1,5 +1,4 @@
 const ru = {
-  locale: "ru",
   // Home Tab
   Home: "Главная",
   "Period in": "Месячные через",


Test patch:
diff --git a/src/tests/CalculationLogic.test.ts b/src/tests/CalculationLogic.test.ts
index cb039dd..b4ae484 100644
--- a/src/tests/CalculationLogic.test.ts
+++ b/src/tests/CalculationLogic.test.ts
@@ -1,7 +1,13 @@
 import i18n from "i18next";
+import {
+  addDays,
+  startOfDay,
+  startOfToday,
+  startOfTomorrow,
+  startOfYesterday,
+  subDays,
+} from "date-fns";
 import { Cycle } from "../data/ClassCycle";
-import { format } from "date-fns";
-import { ru } from "date-fns/locale";
 import {
   getOvulationStatus,
   getPregnancyChance,
@@ -17,8 +23,8 @@ import {
   getPastFuturePeriodDays,
   getLastStartDate,
   getLengthOfLastPeriod,
-  getFormattedDate,
 } from "../state/CalculationLogics";
+import { format } from "../utils/datetime";
 
 describe("getOvulationStatus", () => {
   test("cycles array is empty", () => {
@@ -28,14 +34,12 @@ describe("getOvulationStatus", () => {
   test("a few days before ovulation", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 24);
 
     const cycles: Cycle[] = [];
+    let date = addDays(startOfToday(), 24);
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -53,14 +57,12 @@ describe("getOvulationStatus", () => {
   test("ovulation is tomorrow", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementationOnce((key) => key);
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 16);
 
     const cycles: Cycle[] = [];
+    let date = addDays(startOfToday(), 16);
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -73,14 +75,12 @@ describe("getOvulationStatus", () => {
   test("ovulation is today", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementationOnce((key) => key);
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 15);
 
     const cycles: Cycle[] = [];
+    let date: Date = addDays(startOfToday(), 15);
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -93,14 +93,12 @@ describe("getOvulationStatus", () => {
   test("if ovulation is possible", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementationOnce((key) => key);
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 14);
 
     const cycles: Cycle[] = [];
+    let date = addDays(startOfToday(), 14);
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -113,14 +111,12 @@ describe("getOvulationStatus", () => {
   test("ovulation is finished", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementationOnce((key) => key);
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 10);
 
     const cycles: Cycle[] = [];
+    let date = addDays(startOfToday(), 10);
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -140,14 +136,11 @@ describe("getPregnancyChance", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 15);
-
+    let date = addDays(startOfToday(), 15);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -160,14 +153,11 @@ describe("getPregnancyChance", () => {
   test("pregnancy chance is low", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 20);
-
+    let date = addDays(startOfToday(), 20);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -187,14 +177,12 @@ describe("getDayOfCycle", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 15);
+    let date = addDays(startOfToday(), 15);
 
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -309,14 +297,11 @@ describe("getDaysBeforePeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 10);
-
+    let date = addDays(startOfToday(), 10);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -337,14 +322,11 @@ describe("getDaysBeforePeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 1);
-
+    let date = startOfTomorrow();
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -365,14 +347,12 @@ describe("getDaysBeforePeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate());
+    let date = startOfToday();
 
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -390,14 +370,11 @@ describe("getDaysBeforePeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() - 1);
-
+    let date = startOfYesterday();
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -418,14 +395,11 @@ describe("getDaysBeforePeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() - 10);
-
+    let date = subDays(startOfToday(), 10);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -446,14 +420,11 @@ describe("getDaysBeforePeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 25);
-
+    let date = addDays(startOfToday(), 25);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -534,14 +505,11 @@ describe("getPhase", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 26);
-
+    let date = addDays(startOfToday(), 26);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -556,14 +524,11 @@ describe("getPhase", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 20);
-
+    let date = addDays(startOfToday(), 20);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -578,14 +543,11 @@ describe("getPhase", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 14);
-
+    let date = addDays(startOfToday(), 14);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -600,14 +562,11 @@ describe("getPhase", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 5);
-
+    let date = addDays(startOfToday(), 5);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -740,23 +699,20 @@ describe("getActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const now = new Date();
-    now.setHours(0, 0, 0, 0);
-    expect(getActiveDates(format(now, "yyyy-MM-dd"), [])).toEqual(true);
+    expect(getActiveDates(format(startOfToday(), "yyyy-MM-dd"), [])).toEqual(
+      true,
+    );
   });
 
   test("now is menstrual phase items and checking 2th day of period", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 26);
-
+    let date = addDays(startOfToday(), 26);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -764,9 +720,7 @@ describe("getActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 1);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 1);
     expect(getActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles)).toEqual(
       true,
     );
@@ -776,14 +730,11 @@ describe("getActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 26);
-
+    let date = addDays(startOfToday(), 26);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -791,9 +742,7 @@ describe("getActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 7);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 7);
     expect(getActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles)).toEqual(
       false,
     );
@@ -803,14 +752,11 @@ describe("getActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 20);
-
+    let date = addDays(startOfToday(), 20);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -818,9 +764,7 @@ describe("getActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 7);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 7);
     expect(getActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles)).toEqual(
       true,
     );
@@ -830,14 +774,11 @@ describe("getActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 20);
-
+    let date = addDays(startOfToday(), 20);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -845,9 +786,7 @@ describe("getActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 15);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 15);
     expect(getActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles)).toEqual(
       false,
     );
@@ -857,14 +796,11 @@ describe("getActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() - 5);
-
+    let date = subDays(startOfToday(), 5);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -872,9 +808,7 @@ describe("getActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 10);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 10);
     expect(getActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles)).toEqual(
       true,
     );
@@ -884,14 +818,11 @@ describe("getActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() - 5);
-
+    let date = subDays(startOfToday(), 5);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -899,9 +830,7 @@ describe("getActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 40);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 40);
     expect(getActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles)).toEqual(
       false,
     );
@@ -919,14 +848,11 @@ describe("getMarkModalActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 20);
-
+    let date = addDays(startOfToday(), 20);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -934,9 +860,7 @@ describe("getMarkModalActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 5);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 5);
     expect(
       getMarkModalActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles),
     ).toEqual(false);
@@ -946,14 +870,11 @@ describe("getMarkModalActiveDates", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 20);
-
+    let date = addDays(startOfToday(), 20);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -961,9 +882,7 @@ describe("getMarkModalActiveDates", () => {
       });
     }
 
-    const dateCheck = new Date(cycles[0].startDate);
-    dateCheck.setDate(dateCheck.getDate() + 40);
-
+    const dateCheck = addDays(startOfDay(new Date(cycles[0].startDate)), 40);
     expect(
       getMarkModalActiveDates(format(dateCheck, "yyyy-MM-dd"), cycles),
     ).toEqual(true);
@@ -976,14 +895,9 @@ describe("getPastFuturePeriodDays", () => {
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
     const periodDates: string[] = [];
-    const nowDate = new Date();
-    nowDate.setHours(0, 0, 0, 0);
-
+    const nowDate = startOfToday();
     for (let day = 0; day < 5; day++) {
-      const periodDay = new Date(nowDate);
-      periodDay.setHours(0, 0, 0, 0);
-      periodDay.setDate(periodDay.getDate() + day);
-
+      const periodDay = addDays(nowDate, day);
       periodDates.push(format(periodDay, "yyyy-MM-dd"));
     }
     expect(getPastFuturePeriodDays([])).toEqual(periodDates);
@@ -993,14 +907,11 @@ describe("getPastFuturePeriodDays", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 10);
-
+    let date = addDays(startOfToday(), 10);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 5,
@@ -1009,14 +920,9 @@ describe("getPastFuturePeriodDays", () => {
     }
 
     const periodDates = getLastPeriodDays(cycles);
-    const nowDate = new Date();
-    nowDate.setHours(0, 0, 0, 0);
-
+    const nowDate = startOfToday();
     for (let day = 0; day < 5; day++) {
-      const periodDay = new Date(nowDate);
-      periodDay.setHours(0, 0, 0, 0);
-      periodDay.setDate(periodDay.getDate() + day);
-
+      const periodDay = addDays(nowDate, day);
       periodDates.push(format(periodDay, "yyyy-MM-dd"));
     }
 
@@ -1027,14 +933,11 @@ describe("getPastFuturePeriodDays", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() - 5);
-
+    let date = subDays(startOfToday(), 5);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 5,
@@ -1043,14 +946,10 @@ describe("getPastFuturePeriodDays", () => {
     }
 
     const periodDates = getLastPeriodDays(cycles);
-    const nowDate = new Date();
-    nowDate.setHours(0, 0, 0, 0);
+    const nowDate = startOfToday();
 
     for (let day = 0; day < 5; day++) {
-      const periodDay = new Date(nowDate);
-      periodDay.setHours(0, 0, 0, 0);
-      periodDay.setDate(periodDay.getDate() + day);
-
+      const periodDay = addDays(nowDate, day);
       periodDates.push(format(periodDay, "yyyy-MM-dd"));
     }
 
@@ -1067,14 +966,11 @@ describe("getLastStartDate", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 15);
-
+    let date = addDays(startOfToday(), 15);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -1095,14 +991,11 @@ describe("getLengthOfLastPeriod", () => {
     // @ts-expect-error mocked `t` method
     jest.spyOn(i18n, "t").mockImplementation((key) => key);
 
-    const date: Date = new Date();
-    date.setHours(0, 0, 0, 0);
-    date.setDate(date.getDate() + 15);
-
+    let date = addDays(startOfToday(), 15);
     const cycles: Cycle[] = [];
 
     for (let i = 0; i < 6; ++i) {
-      date.setDate(date.getDate() - 28);
+      date = subDays(date, 28);
       cycles.push({
         cycleLength: 28,
         periodLength: 6,
@@ -1113,27 +1006,3 @@ describe("getLengthOfLastPeriod", () => {
     expect(getLengthOfLastPeriod(cycles)).toEqual(cycles[0].periodLength);
   });
 });
-
-describe("getFormattedDate", () => {
-  test("English date format", () => {
-    const today = new Date();
-    today.setHours(0, 0, 0, 0);
-    expect(getFormattedDate(today, "en-GB")).toEqual(format(today, "MMM d"));
-  });
-
-  test("Russian date format", () => {
-    const today = new Date();
-    today.setHours(0, 0, 0, 0);
-    expect(getFormattedDate(today, "ru")).toEqual(
-      format(today, "d MMMM", {
-        locale: ru,
-      }),
-    );
-  });
-
-  test("Unknown date format", () => {
-    const today = new Date();
-    today.setHours(0, 0, 0, 0);
-    expect(getFormattedDate(today, "1")).toEqual(format(today, "MMM d"));
-  });
-});
diff --git a/src/tests/datetime.test.ts b/src/tests/datetime.test.ts
new file mode 100644
index 0000000..2fc59d1
--- /dev/null
+++ b/src/tests/datetime.test.ts
@@ -0,0 +1,19 @@
+import { format as dateFnsFormat } from "date-fns";
+import { changeDateTimeLocale, format } from "../utils/datetime";
+import { ru } from "date-fns/locale";
+
+test("Show day before month on specific language", () => {
+  changeDateTimeLocale("en");
+
+  expect(format(new Date(), "MMMM d")).toEqual(
+    dateFnsFormat(new Date(), "MMMM d"),
+  );
+
+  changeDateTimeLocale("ru");
+
+  expect(format(new Date(), "MMMM d")).toEqual(
+    dateFnsFormat(new Date(), "d MMMM", {
+      locale: ru,
+    }),
+  );
+});
diff --git a/src/tests/translation.test.ts b/src/tests/translation.test.ts
new file mode 100644
index 0000000..cc81f01
--- /dev/null
+++ b/src/tests/translation.test.ts
@@ -0,0 +1,16 @@
+import i18n from "i18next";
+import { changeTranslation, getCurrentTranslation } from "../utils/translation";
+
+test("Change translation", async () => {
+  // @ts-expect-error It doesn't matter what we return here
+  jest.spyOn(i18n, "changeLanguage").mockResolvedValue({});
+
+  await changeTranslation("en");
+  expect(getCurrentTranslation()).toEqual("en");
+
+  await changeTranslation("ru");
+  expect(getCurrentTranslation()).toEqual("ru");
+
+  await changeTranslation("some-unknown-lang-code");
+  expect(getCurrentTranslation()).toEqual("ru");
+});