mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-18 21:49:31 +02:00
refactor: time between dates
This commit is contained in:
196
.idea/workspace.xml
generated
196
.idea/workspace.xml
generated
@@ -4,9 +4,11 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: show new tools in landing">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: zoom on hover">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/time/time-between-dates.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/service.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -23,7 +25,7 @@
|
||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="chesterkxng" />
|
||||
<entry key="$PROJECT_DIR$" value="main" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
@@ -42,59 +44,66 @@
|
||||
"state": "OPEN"
|
||||
}
|
||||
}</component>
|
||||
<component name="GitHubPullRequestState">{
|
||||
"prStates": [
|
||||
<component name="GitHubPullRequestState"><![CDATA[{
|
||||
"prStates": [
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts51PkS9",
|
||||
"number": 22
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts51PkS9",
|
||||
"number": 22
|
||||
},
|
||||
"lastSeen": 1741207144695
|
||||
"lastSeen": 1741207144695
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NiNYl",
|
||||
"number": 32
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NiNYl",
|
||||
"number": 32
|
||||
},
|
||||
"lastSeen": 1741209723869
|
||||
"lastSeen": 1741209723869
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Nheyd",
|
||||
"number": 31
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Nheyd",
|
||||
"number": 31
|
||||
},
|
||||
"lastSeen": 1741213371410
|
||||
"lastSeen": 1741213371410
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NmRBs",
|
||||
"number": 33
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6NmRBs",
|
||||
"number": 33
|
||||
},
|
||||
"lastSeen": 1741282429036
|
||||
"lastSeen": 1741282429036
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts5zyFTs",
|
||||
"number": 15
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts5zyFTs",
|
||||
"number": 15
|
||||
},
|
||||
"lastSeen": 1741535540953
|
||||
"lastSeen": 1741535540953
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QQB3c",
|
||||
"number": 59
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QQB3c",
|
||||
"number": 59
|
||||
},
|
||||
"lastSeen": 1743018960900
|
||||
"lastSeen": 1743018960900
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QMPEg",
|
||||
"number": 58
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QMPEg",
|
||||
"number": 58
|
||||
},
|
||||
"lastSeen": 1743019452983
|
||||
"lastSeen": 1743019452983
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QZvRI",
|
||||
"number": 61
|
||||
},
|
||||
"lastSeen": 1743103196866
|
||||
}
|
||||
]
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="GithubPullRequestsUISettings">{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "https://github.com/iib0011/omni-tools.git",
|
||||
@@ -123,55 +132,55 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"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"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"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": "fork/TheLukasHenry/days-calculator-#51",
|
||||
"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"
|
||||
}
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="ReactDesignerToolWindowState">
|
||||
<option name="myId2Visible">
|
||||
<map>
|
||||
@@ -363,14 +372,7 @@
|
||||
<workItem from="1741971589699" duration="371000" />
|
||||
<workItem from="1743018497879" duration="3895000" />
|
||||
<workItem from="1743047367993" duration="986000" />
|
||||
</task>
|
||||
<task id="LOCAL-00122" summary="fix: bg">
|
||||
<option name="closed" value="true" />
|
||||
<created>1740504100676</created>
|
||||
<option name="number" value="00122" />
|
||||
<option name="presentableId" value="LOCAL-00122" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1740504100676</updated>
|
||||
<workItem from="1743103182313" duration="3430000" />
|
||||
</task>
|
||||
<task id="LOCAL-00123" summary="fix: bg">
|
||||
<option name="closed" value="true" />
|
||||
@@ -756,7 +758,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1743051792459</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="171" />
|
||||
<task id="LOCAL-00171" summary="chore: zoom on hover">
|
||||
<option name="closed" value="true" />
|
||||
<created>1743052111988</created>
|
||||
<option name="number" value="00171" />
|
||||
<option name="presentableId" value="LOCAL-00171" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1743052111988</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="172" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -803,7 +813,6 @@
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||
<MESSAGE value="fix: replace text service" />
|
||||
<MESSAGE value="chore: smooth scroll for use this tool and examles" />
|
||||
<MESSAGE value="feat: minify json" />
|
||||
<MESSAGE value="feat: stringify json" />
|
||||
@@ -828,7 +837,8 @@
|
||||
<MESSAGE value="chore: result file name" />
|
||||
<MESSAGE value="chore: text result extensions" />
|
||||
<MESSAGE value="chore: show new tools in landing" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="chore: show new tools in landing" />
|
||||
<MESSAGE value="chore: zoom on hover" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="chore: zoom on hover" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
@@ -1,3 +0,0 @@
|
||||
import TimeBetweenDates from '../tools/time/time-between-dates';
|
||||
|
||||
export default TimeBetweenDates;
|
@@ -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<InitialValuesType>[] = [
|
||||
@@ -213,8 +94,8 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
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: ${
|
||||
|
@@ -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<TimeUnit, number>;
|
||||
|
||||
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<TimeUnit, number> = {
|
||||
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);
|
||||
};
|
||||
|
Reference in New Issue
Block a user