From a3d75e57edbb255c94610109e5e370c38b8ca979 Mon Sep 17 00:00:00 2001 From: Lukas Herajt Date: Thu, 27 Mar 2025 10:14:05 -0400 Subject: [PATCH 1/4] added time between 2 dates --- src/pages/time/time-between-dates.tsx | 3 + src/pages/tools/time/index.ts | 5 +- .../tools/time/time-between-dates/index.tsx | 388 ++++++++++++++++++ .../tools/time/time-between-dates/meta.ts | 22 + .../tools/time/time-between-dates/service.ts | 150 +++++++ src/tools/defineTool.tsx | 1 + src/tools/index.ts | 6 + 7 files changed, 574 insertions(+), 1 deletion(-) create mode 100644 src/pages/time/time-between-dates.tsx create mode 100644 src/pages/tools/time/time-between-dates/index.tsx create mode 100644 src/pages/tools/time/time-between-dates/meta.ts create mode 100644 src/pages/tools/time/time-between-dates/service.ts diff --git a/src/pages/time/time-between-dates.tsx b/src/pages/time/time-between-dates.tsx new file mode 100644 index 0000000..5c680b9 --- /dev/null +++ b/src/pages/time/time-between-dates.tsx @@ -0,0 +1,3 @@ +import TimeBetweenDates from '../tools/time/time-between-dates'; + +export default TimeBetweenDates; diff --git a/src/pages/tools/time/index.ts b/src/pages/tools/time/index.ts index f229937..785dfe7 100644 --- a/src/pages/tools/time/index.ts +++ b/src/pages/tools/time/index.ts @@ -1,13 +1,16 @@ +import { tool as timeBetweenDates } from './time-between-dates/meta'; import { tool as daysDoHours } from './convert-days-to-hours/meta'; import { tool as hoursToDays } from './convert-hours-to-days/meta'; import { tool as convertSecondsToTime } from './convert-seconds-to-time/meta'; import { tool as convertTimetoSeconds } from './convert-time-to-seconds/meta'; import { tool as truncateClockTime } from './truncate-clock-time/meta'; + export const timeTools = [ daysDoHours, hoursToDays, convertSecondsToTime, convertTimetoSeconds, - truncateClockTime + truncateClockTime, + timeBetweenDates ]; diff --git a/src/pages/tools/time/time-between-dates/index.tsx b/src/pages/tools/time/time-between-dates/index.tsx new file mode 100644 index 0000000..15e88d8 --- /dev/null +++ b/src/pages/tools/time/time-between-dates/index.tsx @@ -0,0 +1,388 @@ +import { Box, Typography, Paper } 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 +} from './service'; +import * as Yup from 'yup'; +import { CardExampleType } from '@components/examples/ToolExamples'; + +type TimeUnit = + | 'milliseconds' + | 'seconds' + | 'minutes' + | 'hours' + | 'days' + | 'months' + | 'years'; + +type InitialValuesType = { + startDate: string; + startTime: string; + endDate: string; + endTime: string; + startTimezone: string; + 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', + endDate: new Date().toISOString().split('T')[0], + endTime: '12:00', + startTimezone: 'local', + endTimezone: 'local' +}; + +const validationSchema = Yup.object({ + startDate: Yup.string().required('Start date is required'), + startTime: Yup.string().required('Start time is required'), + endDate: Yup.string().required('End date is required'), + endTime: Yup.string().required('End time is required'), + startTimezone: Yup.string(), + endTimezone: Yup.string() +}); + +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)' } +]; + +const exampleCards: CardExampleType[] = [ + { + title: 'One Year Difference', + description: 'Calculate the time between dates that are one year apart', + sampleOptions: { + startDate: '2023-01-01', + startTime: '12:00', + endDate: '2024-01-01', + endTime: '12:00', + startTimezone: 'local', + endTimezone: 'local' + }, + sampleResult: '1 year' + }, + { + title: 'Different Timezones', + description: 'Calculate the time difference between New York and London', + sampleOptions: { + startDate: '2023-01-01', + startTime: '12:00', + endDate: '2023-01-01', + endTime: '12:00', + startTimezone: 'GMT-0500', + endTimezone: 'GMT+0000' + }, + sampleResult: '5 hours' + }, + { + title: 'Detailed Time Breakdown', + description: 'Show a detailed breakdown of a time difference', + sampleOptions: { + startDate: '2023-01-01', + startTime: '09:30', + endDate: '2023-01-03', + endTime: '14:45', + startTimezone: 'local', + endTimezone: 'local' + }, + sampleResult: '2 days, 5 hours, 15 minutes' + } +]; + +export default function TimeBetweenDates() { + const [result, setResult] = useState(''); + + return ( + + + {result} + + + ) : null + } + initialValues={initialValues} + validationSchema={validationSchema} + exampleCards={exampleCards} + toolInfo={{ + title: 'Time Between Dates Calculator', + description: + 'Calculate the exact time difference between two dates and times, with support for different timezones. This tool provides a detailed breakdown of the time difference in various units (years, months, days, hours, minutes, and seconds).' + }} + getGroups={({ values, updateField }) => [ + { + title: 'Start Date & Time', + component: ( + + updateField('startDate', val)} + type="date" + /> + updateField('startTime', val)} + type="time" + /> + updateField('startTimezone', val)} + options={timezoneOptions} + /> + + ) + }, + { + title: 'End Date & Time', + component: ( + + updateField('endDate', val)} + type="date" + /> + updateField('endTime', val)} + type="time" + /> + updateField('endTimezone', val)} + options={timezoneOptions} + /> + + ) + } + ]} + compute={(values) => { + try { + // Create Date objects with timezone consideration + const startDateTime = getTimeWithTimezone( + values.startDate, + values.startTime, + values.startTimezone + ); + + const endDateTime = getTimeWithTimezone( + values.endDate, + values.endTime, + values.endTimezone + ); + + // Calculate time difference + const difference = calculateTimeBetweenDates( + startDateTime, + 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'; + + 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 + ); + + setResult(formattedDifference); + } + } catch (error) { + setResult( + `Error: ${ + error instanceof Error + ? error.message + : 'Failed to calculate time difference' + }` + ); + } + }} + /> + ); +} diff --git a/src/pages/tools/time/time-between-dates/meta.ts b/src/pages/tools/time/time-between-dates/meta.ts new file mode 100644 index 0000000..3676dda --- /dev/null +++ b/src/pages/tools/time/time-between-dates/meta.ts @@ -0,0 +1,22 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('time', { + name: 'Time Between Dates', + path: 'time-between-dates', + icon: 'mdi:calendar-clock', + description: + 'Calculate the exact time difference between two dates and times, with support for different timezones. This tool provides a detailed breakdown of the time difference in various units (years, months, days, hours, minutes, and seconds).', + shortDescription: + 'Calculate the precise time duration between two dates with timezone support.', + keywords: [ + 'time', + 'dates', + 'difference', + 'duration', + 'calculator', + 'timezones', + 'interval' + ], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/time/time-between-dates/service.ts b/src/pages/tools/time/time-between-dates/service.ts new file mode 100644 index 0000000..9341fa2 --- /dev/null +++ b/src/pages/tools/time/time-between-dates/service.ts @@ -0,0 +1,150 @@ +type TimeUnit = + | 'milliseconds' + | 'seconds' + | 'minutes' + | 'hours' + | 'days' + | 'months' + | 'years'; + +interface TimeDifference { + milliseconds: number; + seconds: number; + minutes: number; + hours: number; + days: number; + months: number; + years: number; +} + +export const calculateTimeBetweenDates = ( + startDate: Date, + endDate: Date, + unit: TimeUnit = 'milliseconds' +): TimeDifference | number => { + // Ensure endDate is after startDate + if (endDate < startDate) { + const temp = startDate; + startDate = endDate; + endDate = temp; + } + + const milliseconds = endDate.getTime() - startDate.getTime(); + const seconds = Math.floor(milliseconds / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + // Approximate months and years + const startYear = startDate.getFullYear(); + const startMonth = startDate.getMonth(); + const endYear = endDate.getFullYear(); + const endMonth = endDate.getMonth(); + + 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, + minutes, + hours, + days, + months, + years + }; +}; + +export const formatTimeDifference = ( + difference: TimeDifference, + includeUnits: TimeUnit[] = [ + 'years', + 'months', + 'days', + 'hours', + 'minutes', + 'seconds' + ] +): string => { + const parts: string[] = []; + + 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'}` + ); + } + + if (parts.length === 0) { + if (includeUnits.includes('milliseconds')) { + parts.push( + `${difference.milliseconds} ${ + difference.milliseconds === 1 ? 'millisecond' : 'milliseconds' + }` + ); + } else { + parts.push('0 seconds'); + } + } + + return parts.join(', '); +}; + +export const getTimeWithTimezone = ( + dateString: string, + timeString: string, + timezone: string +): Date => { + // Combine date and time + const dateTimeString = `${dateString}T${timeString}`; + + // 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; + } + + return dateObject; +}; diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx index e32b940..5263c6f 100644 --- a/src/tools/defineTool.tsx +++ b/src/tools/defineTool.tsx @@ -21,6 +21,7 @@ export type ToolCategory = | 'video' | 'list' | 'json' + | 'time' | 'csv' | 'time' | 'pdf'; diff --git a/src/tools/index.ts b/src/tools/index.ts index e57bdcc..401cbb3 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -66,6 +66,12 @@ const categoriesConfig: { value: 'Tools for working with JSON data structures – prettify and minify JSON objects, flatten JSON arrays, stringify JSON values, analyze data, and much more' }, + { + type: 'time', + icon: 'mdi:clock-time-five', + value: + 'Tools for working with time and date – calculate time differences, convert between time zones, format dates, generate date sequences, and much more.' + }, { type: 'csv', icon: 'material-symbols-light:csv-outline', From dc44f44f5365ef36b937df673b00fcb4e23a7e69 Mon Sep 17 00:00:00 2001 From: Lukas Herajt Date: Thu, 27 Mar 2025 11:08:33 -0400 Subject: [PATCH 2/4] fixed Title of examples --- src/pages/tools/time/time-between-dates/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/tools/time/time-between-dates/index.tsx b/src/pages/tools/time/time-between-dates/index.tsx index 15e88d8..d21b0d8 100644 --- a/src/pages/tools/time/time-between-dates/index.tsx +++ b/src/pages/tools/time/time-between-dates/index.tsx @@ -238,6 +238,7 @@ export default function TimeBetweenDates() { return ( Date: Thu, 27 Mar 2025 20:19:43 +0000 Subject: [PATCH 3/4] refactor: time between dates --- .idea/workspace.xml | 196 ++++++++-------- src/pages/time/time-between-dates.tsx | 3 - .../tools/time/time-between-dates/index.tsx | 209 +++--------------- .../tools/time/time-between-dates/service.ts | 185 ++++++++-------- 4 files changed, 232 insertions(+), 361 deletions(-) delete mode 100644 src/pages/time/time-between-dates.tsx 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 @@