diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 0d3b2eb..391285d 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,9 +4,11 @@
-
+
-
+
+
+
@@ -23,7 +25,7 @@
@@ -42,59 +44,66 @@
"state": "OPEN"
}
}
- {
- "prStates": [
+
+}]]>
{
"selectedUrlAndAccountId": {
"url": "https://github.com/iib0011/omni-tools.git",
@@ -123,55 +132,55 @@
- {
- "keyToString": {
- "ASKED_ADD_EXTERNAL_FILES": "true",
- "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "Docker.Dockerfile build.executor": "Run",
- "Docker.Dockerfile.executor": "Run",
- "Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
- "Playwright.JoinText Component.executor": "Run",
- "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.git.unshallow": "true",
- "Vitest.compute function (1).executor": "Run",
- "Vitest.compute function.executor": "Run",
- "Vitest.mergeText.executor": "Run",
- "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
- "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
- "Vitest.parsePageRanges.executor": "Run",
- "Vitest.removeDuplicateLines function.executor": "Run",
- "Vitest.removeDuplicateLines function.newlines option.executor": "Run",
- "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
- "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
- "Vitest.replaceText function.executor": "Run",
- "git-widget-placeholder": "main",
- "ignore.virus.scanning.warn.message": "true",
- "kotlin-language-version-configured": "true",
- "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/components/input",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "npm.build.executor": "Run",
- "npm.dev.executor": "Run",
- "npm.lint.executor": "Run",
- "npm.prebuild.executor": "Run",
- "npm.script:create:tool.executor": "Run",
- "npm.test.executor": "Run",
- "npm.test:e2e.executor": "Run",
- "npm.test:e2e:run.executor": "Run",
- "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
- "project.structure.last.edited": "Problems",
- "project.structure.proportion": "0.0",
- "project.structure.side.proportion": "0.2",
- "settings.editor.selected.configurable": "refactai_advanced_settings",
- "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
- "vue.rearranger.settings.migration": "true"
+
+}]]>
@@ -803,7 +813,6 @@
-
@@ -828,7 +837,8 @@
-
+
+
diff --git a/src/pages/time/time-between-dates.tsx b/src/pages/time/time-between-dates.tsx
deleted file mode 100644
index 5c680b9..0000000
--- a/src/pages/time/time-between-dates.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import TimeBetweenDates from '../tools/time/time-between-dates';
-
-export default TimeBetweenDates;
diff --git a/src/pages/tools/time/time-between-dates/index.tsx b/src/pages/tools/time/time-between-dates/index.tsx
index d21b0d8..a02ee70 100644
--- a/src/pages/tools/time/time-between-dates/index.tsx
+++ b/src/pages/tools/time/time-between-dates/index.tsx
@@ -1,12 +1,13 @@
-import { Box, Typography, Paper } from '@mui/material';
+import { Box, Paper, Typography } from '@mui/material';
import React, { useState } from 'react';
import ToolContent from '@components/ToolContent';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc';
import {
calculateTimeBetweenDates,
- formatTimeDifference,
- getTimeWithTimezone
+ formatTimeWithLargestUnit,
+ getTimeWithTimezone,
+ unitHierarchy
} from './service';
import * as Yup from 'yup';
import { CardExampleType } from '@components/examples/ToolExamples';
@@ -29,135 +30,6 @@ type InitialValuesType = {
endTimezone: string;
};
-// Helper function to format time based on largest unit
-const formatTimeWithLargestUnit = (
- difference: any,
- largestUnit: TimeUnit
-): string => {
- const unitHierarchy: TimeUnit[] = [
- 'years',
- 'months',
- 'days',
- 'hours',
- 'minutes',
- 'seconds',
- 'milliseconds'
- ];
-
- const largestUnitIndex = unitHierarchy.indexOf(largestUnit);
- const unitsToInclude = unitHierarchy.slice(largestUnitIndex);
-
- // Make a deep copy of the difference object to avoid modifying the original
- const convertedDifference = { ...difference };
-
- // Constants for time conversions - more precise values
- const HOURS_PER_DAY = 24;
- const DAYS_PER_MONTH = 30; // Approximation
- const MONTHS_PER_YEAR = 12;
- const DAYS_PER_YEAR = 365; // Approximation
- const MINUTES_PER_HOUR = 60;
- const SECONDS_PER_MINUTE = 60;
- const MS_PER_SECOND = 1000;
-
- // Apply conversions based on the selected largest unit
- if (largestUnit === 'months') {
- // Convert years to months
- convertedDifference.months += convertedDifference.years * MONTHS_PER_YEAR;
- convertedDifference.years = 0;
- } else if (largestUnit === 'days') {
- // Convert years and months to days
- convertedDifference.days +=
- convertedDifference.years * DAYS_PER_YEAR +
- convertedDifference.months * DAYS_PER_MONTH;
- convertedDifference.months = 0;
- convertedDifference.years = 0;
- } else if (largestUnit === 'hours') {
- // Convert years, months, and days to hours
- convertedDifference.hours +=
- convertedDifference.years * DAYS_PER_YEAR * HOURS_PER_DAY +
- convertedDifference.months * DAYS_PER_MONTH * HOURS_PER_DAY +
- convertedDifference.days * HOURS_PER_DAY;
- convertedDifference.days = 0;
- convertedDifference.months = 0;
- convertedDifference.years = 0;
- } else if (largestUnit === 'minutes') {
- // Convert years, months, days, and hours to minutes
- convertedDifference.minutes +=
- convertedDifference.years *
- DAYS_PER_YEAR *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR +
- convertedDifference.months *
- DAYS_PER_MONTH *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR +
- convertedDifference.days * HOURS_PER_DAY * MINUTES_PER_HOUR +
- convertedDifference.hours * MINUTES_PER_HOUR;
- convertedDifference.hours = 0;
- convertedDifference.days = 0;
- convertedDifference.months = 0;
- convertedDifference.years = 0;
- } else if (largestUnit === 'seconds') {
- // Convert years, months, days, hours, and minutes to seconds
- convertedDifference.seconds +=
- convertedDifference.years *
- DAYS_PER_YEAR *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE +
- convertedDifference.months *
- DAYS_PER_MONTH *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE +
- convertedDifference.days *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE +
- convertedDifference.hours * MINUTES_PER_HOUR * SECONDS_PER_MINUTE +
- convertedDifference.minutes * SECONDS_PER_MINUTE;
- convertedDifference.minutes = 0;
- convertedDifference.hours = 0;
- convertedDifference.days = 0;
- convertedDifference.months = 0;
- convertedDifference.years = 0;
- } else if (largestUnit === 'milliseconds') {
- // Convert everything to milliseconds
- convertedDifference.milliseconds +=
- convertedDifference.years *
- DAYS_PER_YEAR *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE *
- MS_PER_SECOND +
- convertedDifference.months *
- DAYS_PER_MONTH *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE *
- MS_PER_SECOND +
- convertedDifference.days *
- HOURS_PER_DAY *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE *
- MS_PER_SECOND +
- convertedDifference.hours *
- MINUTES_PER_HOUR *
- SECONDS_PER_MINUTE *
- MS_PER_SECOND +
- convertedDifference.minutes * SECONDS_PER_MINUTE * MS_PER_SECOND +
- convertedDifference.seconds * MS_PER_SECOND;
- convertedDifference.seconds = 0;
- convertedDifference.minutes = 0;
- convertedDifference.hours = 0;
- convertedDifference.days = 0;
- convertedDifference.months = 0;
- convertedDifference.years = 0;
- }
-
- return formatTimeDifference(convertedDifference, unitsToInclude);
-};
-
const initialValues: InitialValuesType = {
startDate: new Date().toISOString().split('T')[0],
startTime: '00:00',
@@ -178,17 +50,26 @@ const validationSchema = Yup.object({
const timezoneOptions = [
{ value: 'local', label: 'Local Time' },
- { value: 'GMT+0000', label: 'GMT+0000 (UTC)' },
- { value: 'GMT-0500', label: 'GMT-0500 (Eastern Standard Time)' },
- { value: 'GMT-0600', label: 'GMT-0600 (Central Standard Time)' },
- { value: 'GMT-0700', label: 'GMT-0700 (Mountain Standard Time)' },
- { value: 'GMT-0800', label: 'GMT-0800 (Pacific Standard Time)' },
- { value: 'GMT+0100', label: 'GMT+0100 (Central European Time)' },
- { value: 'GMT+0200', label: 'GMT+0200 (Eastern European Time)' },
- { value: 'GMT+0530', label: 'GMT+0530 (Indian Standard Time)' },
- { value: 'GMT+0800', label: 'GMT+0800 (China Standard Time)' },
- { value: 'GMT+0900', label: 'GMT+0900 (Japan Standard Time)' },
- { value: 'GMT+1000', label: 'GMT+1000 (Australian Eastern Standard Time)' }
+ ...Intl.supportedValuesOf('timeZone')
+ .map((tz) => {
+ const formatter = new Intl.DateTimeFormat('en', {
+ timeZone: tz,
+ timeZoneName: 'shortOffset'
+ });
+
+ const offset =
+ formatter
+ .formatToParts(new Date())
+ .find((part) => part.type === 'timeZoneName')?.value || '';
+
+ return {
+ value: offset.replace('UTC', 'GMT'),
+ label: `${offset.replace('UTC', 'GMT')} (${tz})`
+ };
+ })
+ .sort((a, b) =>
+ a.value.localeCompare(b.value, undefined, { numeric: true })
+ )
];
const exampleCards: CardExampleType[] = [
@@ -213,8 +94,8 @@ const exampleCards: CardExampleType[] = [
startTime: '12:00',
endDate: '2023-01-01',
endTime: '12:00',
- startTimezone: 'GMT-0500',
- endTimezone: 'GMT+0000'
+ startTimezone: 'GMT-5',
+ endTimezone: 'GMT'
},
sampleResult: '5 hours'
},
@@ -325,7 +206,6 @@ export default function TimeBetweenDates() {
]}
compute={(values) => {
try {
- // Create Date objects with timezone consideration
const startDateTime = getTimeWithTimezone(
values.startDate,
values.startTime,
@@ -344,36 +224,17 @@ export default function TimeBetweenDates() {
endDateTime
);
- // Format the result - use 'years' as the default largest unit
- if (typeof difference === 'number') {
- setResult(`${difference} milliseconds`);
- } else {
- // Auto-determine the best unit to display based on the time difference
- let bestUnit: TimeUnit = 'years';
+ // Auto-determine the best unit to display based on the time difference
+ const bestUnit: TimeUnit =
+ unitHierarchy.find((unit) => difference[unit] > 0) ||
+ 'milliseconds';
- if (difference.years > 0) {
- bestUnit = 'years';
- } else if (difference.months > 0) {
- bestUnit = 'months';
- } else if (difference.days > 0) {
- bestUnit = 'days';
- } else if (difference.hours > 0) {
- bestUnit = 'hours';
- } else if (difference.minutes > 0) {
- bestUnit = 'minutes';
- } else if (difference.seconds > 0) {
- bestUnit = 'seconds';
- } else {
- bestUnit = 'milliseconds';
- }
+ const formattedDifference = formatTimeWithLargestUnit(
+ difference,
+ bestUnit
+ );
- const formattedDifference = formatTimeWithLargestUnit(
- difference,
- bestUnit
- );
-
- setResult(formattedDifference);
- }
+ setResult(formattedDifference);
} catch (error) {
setResult(
`Error: ${
diff --git a/src/pages/tools/time/time-between-dates/service.ts b/src/pages/tools/time/time-between-dates/service.ts
index 9341fa2..be1a5ac 100644
--- a/src/pages/tools/time/time-between-dates/service.ts
+++ b/src/pages/tools/time/time-between-dates/service.ts
@@ -1,28 +1,20 @@
-type TimeUnit =
- | 'milliseconds'
- | 'seconds'
- | 'minutes'
- | 'hours'
- | 'days'
- | 'months'
- | 'years';
+export const unitHierarchy = [
+ 'years',
+ 'months',
+ 'days',
+ 'hours',
+ 'minutes',
+ 'seconds',
+ 'milliseconds'
+] as const;
-interface TimeDifference {
- milliseconds: number;
- seconds: number;
- minutes: number;
- hours: number;
- days: number;
- months: number;
- years: number;
-}
+export type TimeUnit = (typeof unitHierarchy)[number];
+export type TimeDifference = Record;
export const calculateTimeBetweenDates = (
startDate: Date,
- endDate: Date,
- unit: TimeUnit = 'milliseconds'
-): TimeDifference | number => {
- // Ensure endDate is after startDate
+ endDate: Date
+): TimeDifference => {
if (endDate < startDate) {
const temp = startDate;
startDate = endDate;
@@ -44,12 +36,6 @@ export const calculateTimeBetweenDates = (
const months = (endYear - startYear) * 12 + (endMonth - startMonth);
const years = Math.floor(months / 12);
- // If specific unit requested, return just that value
- if (unit !== 'milliseconds') {
- return { milliseconds, seconds, minutes, hours, days, months, years }[unit];
- }
-
- // Otherwise return the complete breakdown
return {
milliseconds,
seconds,
@@ -63,64 +49,32 @@ export const calculateTimeBetweenDates = (
export const formatTimeDifference = (
difference: TimeDifference,
- includeUnits: TimeUnit[] = [
- 'years',
- 'months',
- 'days',
- 'hours',
- 'minutes',
- 'seconds'
- ]
+ includeUnits: TimeUnit[] = unitHierarchy.slice(0, -1)
): string => {
- const parts: string[] = [];
+ const timeUnits: { key: TimeUnit; value: number; divisor?: number }[] = [
+ { key: 'years', value: difference.years },
+ { key: 'months', value: difference.months, divisor: 12 },
+ { key: 'days', value: difference.days, divisor: 30 },
+ { key: 'hours', value: difference.hours, divisor: 24 },
+ { key: 'minutes', value: difference.minutes, divisor: 60 },
+ { key: 'seconds', value: difference.seconds, divisor: 60 }
+ ];
- if (includeUnits.includes('years') && difference.years > 0) {
- parts.push(
- `${difference.years} ${difference.years === 1 ? 'year' : 'years'}`
- );
- }
-
- if (includeUnits.includes('months') && difference.months % 12 > 0) {
- const remainingMonths = difference.months % 12;
- parts.push(
- `${remainingMonths} ${remainingMonths === 1 ? 'month' : 'months'}`
- );
- }
-
- if (includeUnits.includes('days') && difference.days % 30 > 0) {
- const remainingDays = difference.days % 30;
- parts.push(`${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}`);
- }
-
- if (includeUnits.includes('hours') && difference.hours % 24 > 0) {
- const remainingHours = difference.hours % 24;
- parts.push(`${remainingHours} ${remainingHours === 1 ? 'hour' : 'hours'}`);
- }
-
- if (includeUnits.includes('minutes') && difference.minutes % 60 > 0) {
- const remainingMinutes = difference.minutes % 60;
- parts.push(
- `${remainingMinutes} ${remainingMinutes === 1 ? 'minute' : 'minutes'}`
- );
- }
-
- if (includeUnits.includes('seconds') && difference.seconds % 60 > 0) {
- const remainingSeconds = difference.seconds % 60;
- parts.push(
- `${remainingSeconds} ${remainingSeconds === 1 ? 'second' : 'seconds'}`
- );
- }
+ const parts = timeUnits
+ .filter(({ key }) => includeUnits.includes(key))
+ .map(({ key, value, divisor }) => {
+ const remaining = divisor ? value % divisor : value;
+ return remaining > 0 ? `${remaining} ${key}` : '';
+ })
+ .filter(Boolean);
if (parts.length === 0) {
if (includeUnits.includes('milliseconds')) {
- parts.push(
- `${difference.milliseconds} ${
- difference.milliseconds === 1 ? 'millisecond' : 'milliseconds'
- }`
- );
- } else {
- parts.push('0 seconds');
+ return `${difference.milliseconds} millisecond${
+ difference.milliseconds === 1 ? '' : 's'
+ }`;
}
+ return '0 seconds';
}
return parts.join(', ');
@@ -132,19 +86,68 @@ export const getTimeWithTimezone = (
timezone: string
): Date => {
// Combine date and time
- const dateTimeString = `${dateString}T${timeString}`;
+ const dateTimeString = `${dateString}T${timeString}Z`; // Append 'Z' to enforce UTC parsing
+ const utcDate = new Date(dateTimeString);
- // Create a date object in the local timezone
- const dateObject = new Date(dateTimeString);
-
- // If timezone is provided, adjust the date
- if (timezone && timezone !== 'local') {
- // Create date string with the timezone identifier
- const dateWithTimezone = new Date(
- dateTimeString + timezone.replace('GMT', '')
- );
- return dateWithTimezone;
+ if (isNaN(utcDate.getTime())) {
+ throw new Error('Invalid date or time format');
}
- return dateObject;
+ // If timezone is "local", return the local date
+ if (timezone === 'local') {
+ return utcDate;
+ }
+
+ // Extract offset from timezone (e.g., "GMT+5:30" or "GMT-4")
+ const match = timezone.match(/^GMT(?:([+-]\d{1,2})(?::(\d{2}))?)?$/);
+ if (!match) {
+ throw new Error('Invalid timezone format');
+ }
+
+ const offsetHours = match[1] ? parseInt(match[1], 10) : 0;
+ const offsetMinutes = match[2] ? parseInt(match[2], 10) : 0;
+
+ const totalOffsetMinutes =
+ offsetHours * 60 + (offsetHours < 0 ? -offsetMinutes : offsetMinutes);
+
+ // Adjust the UTC date by the timezone offset
+ return new Date(utcDate.getTime() - totalOffsetMinutes * 60 * 1000);
+};
+
+// Helper function to format time based on largest unit
+export const formatTimeWithLargestUnit = (
+ difference: TimeDifference,
+ largestUnit: TimeUnit
+): string => {
+ const conversionFactors: Record = {
+ years: 365.2425, // Leap years considered
+ months: 30.436875, // Average month length
+ days: 24, // Hours per day
+ hours: 60, // Minutes per hour
+ minutes: 60, // Seconds per minute
+ seconds: 1000, // Milliseconds per second
+ milliseconds: 1
+ };
+
+ const largestUnitIndex = unitHierarchy.indexOf(largestUnit);
+ const unitsToInclude = unitHierarchy.slice(largestUnitIndex);
+
+ // Deep copy to avoid mutating original object
+ const convertedDifference = { ...difference };
+
+ let carryOver = 0;
+ for (let i = 0; i < largestUnitIndex; i++) {
+ const unit = unitHierarchy[i];
+ const nextUnit = unitHierarchy[i + 1];
+
+ if (nextUnit) {
+ carryOver =
+ (convertedDifference[unit] || 0) * (conversionFactors[unit] || 1);
+ convertedDifference[nextUnit] =
+ (convertedDifference[nextUnit] || 0) + carryOver;
+ convertedDifference[unit] = 0;
+ }
+ }
+
+ return formatTimeDifference(convertedDifference, unitsToInclude);
};