diff --git a/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.test.ts b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.test.ts new file mode 100644 index 000000000..d65385e4e --- /dev/null +++ b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; +import { convertStorageAndRateUnitsDisplay, displayStorageAndRateUnits } from './data-storage-unit-converter.service'; + +describe('data-storage-unit-converter', () => { + describe('convertStorageAndRateUnitsDisplay', () => { + it('convert from same base units', () => { + expect(convertStorageAndRateUnitsDisplay({ value: 1024 * 1024, fromUnit: 'B', toUnit: 'MiB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'KiB', toUnit: 'MiB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MiB', toUnit: 'KiB' })).toBe('1,024'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'MB', toUnit: 'GB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'MB', toUnit: 'MB' })).toBe('1,024'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MB', toUnit: 'KB' })).toBe('1,000'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'MiB', toUnit: 'GiB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'MB', toUnit: 'GB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'Mb', toUnit: 'Gb' })).toBe('1'); + }); + + it('convert between base units', () => { + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MB', toUnit: 'MiB' })).toBe('0.954'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MiB', toUnit: 'MB' })).toBe('1.049'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000 * 1000, fromUnit: 'B', toUnit: 'MiB' })).toBe('0.954'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'KB', toUnit: 'MiB' })).toBe('0.977'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'MiB', toUnit: 'MB' })).toBe('1,048.576'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MB', toUnit: 'Mb' })).toBe('8'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'KB', toUnit: 'Kb' })).toBe('8,000'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'KiB', toUnit: 'Kb' })).toBe('8,192'); + expect(convertStorageAndRateUnitsDisplay({ value: 8, fromUnit: 'Mb', toUnit: 'MB' })).toBe('1'); + + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'Mb', toUnit: 'KB' })).toBe('125'); + expect(convertStorageAndRateUnitsDisplay({ value: 125, fromUnit: 'KB', toUnit: 'Mb' })).toBe('1'); + + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MiB', toUnit: 'Kb' })).toBe('8,388.608'); + expect(convertStorageAndRateUnitsDisplay({ value: 8388.608, fromUnit: 'Kb', toUnit: 'MiB' })).toBe('1'); + }); + it('convert with unit display', () => { + expect(convertStorageAndRateUnitsDisplay({ value: 1024 * 1024, fromUnit: 'B', toUnit: 'MiB', appendUnit: true })).toBe('1MiB'); + }); + + // + }); + describe('displayStorageAndRateUnits', () => { + it('convert to correct display value', () => { + expect(displayStorageAndRateUnits({ + value: 1.234567, unit: 'MB', appendUnit: false, + })).toBe('1.235'); + expect(displayStorageAndRateUnits({ + value: 1.234567, unit: 'MB', appendUnit: true, + })).toBe('1.235MB'); + expect(displayStorageAndRateUnits({ + value: 1.234567, unit: 'MB', appendUnit: true, precision: 5, + })).toBe('1.23457MB'); + }); + }); +}); diff --git a/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.ts b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.ts new file mode 100644 index 000000000..f93634969 --- /dev/null +++ b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.ts @@ -0,0 +1,47 @@ +export type BibytesUnits = 'iB' | 'KiB' | 'MiB' | 'GiB' | 'TiB' | 'PiB' | 'EiB' | 'ZiB' | 'YiB'; +export type BytesUnits = 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB'; +export type BitsUnits = 'b' | 'Kb' | 'Mb' | 'Gb' | 'Tb' | 'Pb' | 'Eb' | 'Zb' | 'Yb'; +export type AllSupportedUnits = BibytesUnits | BytesUnits | BitsUnits; + +export function displayStorageAndRateUnits( + { value, unit, precision = 3, appendUnit = false }: + { value: number; unit: AllSupportedUnits; precision?: number ; appendUnit?: boolean }): string { + return value.toLocaleString(undefined, { + maximumFractionDigits: precision, + }) + (appendUnit ? unit : ''); +} + +export function convertStorageAndRateUnitsDisplay( + { value, fromUnit, toUnit, precision = 3, appendUnit = false }: + { value: number; fromUnit: AllSupportedUnits; toUnit: AllSupportedUnits; precision?: number; appendUnit?: boolean }): string { + return displayStorageAndRateUnits({ + precision, + unit: toUnit, + appendUnit, + value: convertStorageAndRateUnits({ + value, fromUnit, toUnit, + }), + }); +} + +export function convertStorageAndRateUnits( + { value, fromUnit, toUnit }: + { value: number; fromUnit: AllSupportedUnits; toUnit: AllSupportedUnits }): number { + const units = [ + 'iB', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', + 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', + 'b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb', + ]; + + const fromIndex = units.indexOf(fromUnit); + const fromFactor = fromIndex / 9 > 1 ? 1000 : 1024; + const fromDivisor = fromIndex / 9 > 2 ? 8 : 1; + const toIndex = units.indexOf(toUnit); + const toFactor = toIndex / 9 > 1 ? 1000 : 1024; + const toDivisor = toIndex / 9 > 2 ? 8 : 1; + + const fromBase = (fromFactor ** (fromIndex % 9)) / fromDivisor; + const toBase = (toFactor ** (toIndex % 9)) / toDivisor; + + return value * fromBase / toBase; +} diff --git a/src/tools/data-storage-unit-converter/data-storage-unit-converter.vue b/src/tools/data-storage-unit-converter/data-storage-unit-converter.vue new file mode 100644 index 000000000..457c8b6c3 --- /dev/null +++ b/src/tools/data-storage-unit-converter/data-storage-unit-converter.vue @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + Show unit? + + + + + + + + + diff --git a/src/tools/data-storage-unit-converter/index.ts b/src/tools/data-storage-unit-converter/index.ts new file mode 100644 index 000000000..b1ccc2a94 --- /dev/null +++ b/src/tools/data-storage-unit-converter/index.ts @@ -0,0 +1,16 @@ +import { ArrowsLeftRight } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Data Storage Unit converter', + path: '/data-storage-unit-converter', + description: 'Convert data storage or transfer units (bytes, bibytes, bits, kilobytes...)', + keywords: ['data', 'storage', 'unit', 'conversion', + 'bits', 'bytes', 'bibytes', 'binary', + 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', + 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', + 'b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb'], + component: () => import('./data-storage-unit-converter.vue'), + icon: ArrowsLeftRight, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.test.ts b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.test.ts new file mode 100644 index 000000000..9e6b9e865 --- /dev/null +++ b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { amountTransferable, neededRate, transferTimeSeconds } from './data-transfer-rate-converter.service'; + +describe('data-transfer-converter', () => { + describe('transferTimeSeconds', () => { + it('compute transfer time in seconds', () => { + expect(transferTimeSeconds({ + dataSize: 100, + dataSizeUnit: 'MB', + bitRate: 10, + bitRateUnit: 'Mb', + })).toBe(80); + }); + }); + describe('neededRate', () => { + it('compute neededRate', () => { + expect(neededRate({ + dataSize: 100, + dataSizeUnit: 'MB', + hours: 0, + minutes: 1, + seconds: 20, + bitRateUnit: 'Mb', + })).toBe(10); + }); + }); + describe('amountTransferable', () => { + it('compute amount transfered', () => { + expect(amountTransferable({ + bitRate: 10, + bitRateUnit: 'Mb', + hours: 1, + minutes: 0, + seconds: 0, + dataSizeUnit: 'MB', + })).toBe(4500); + }); + }); +}); diff --git a/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.ts b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.ts new file mode 100644 index 000000000..3ef1b7bd4 --- /dev/null +++ b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.ts @@ -0,0 +1,57 @@ +import { type AllSupportedUnits, type BitsUnits, convertStorageAndRateUnits } from '../data-storage-unit-converter/data-storage-unit-converter.service'; + +export function transferTimeSeconds({ + dataSize, + dataSizeUnit, + bitRate, + bitRateUnit, +}: { + dataSize: number + dataSizeUnit: AllSupportedUnits + bitRate: number + bitRateUnit: BitsUnits +}): number { + const dataSizeInBytes = convertStorageAndRateUnits({ value: dataSize, fromUnit: dataSizeUnit, toUnit: 'B' }); + const bitRateInBytes = convertStorageAndRateUnits({ value: bitRate, fromUnit: bitRateUnit, toUnit: 'B' }); + return bitRateInBytes > 0 ? dataSizeInBytes / bitRateInBytes : 0; +} + +export function neededRate({ + dataSize, + dataSizeUnit, + hours, + minutes, + seconds, + bitRateUnit, +}: { + dataSize: number + dataSizeUnit: AllSupportedUnits + hours: number + minutes: number + seconds: number + bitRateUnit: BitsUnits +}): number { + const dataSizeInBits = convertStorageAndRateUnits({ value: dataSize, fromUnit: dataSizeUnit, toUnit: 'b' }); + const timeInSeconds = hours * 3600 + minutes * 60 + seconds; + return convertStorageAndRateUnits({ value: timeInSeconds > 0 ? dataSizeInBits / timeInSeconds : 0, fromUnit: 'b', toUnit: bitRateUnit }); +} + +export function amountTransferable({ + bitRate, + bitRateUnit, + hours, + minutes, + seconds, + dataSizeUnit, +}: { + bitRate: number + bitRateUnit: BitsUnits + hours: number + minutes: number + seconds: number + dataSizeUnit: AllSupportedUnits +}): number { + const bitRateInBytes = convertStorageAndRateUnits({ value: bitRate, fromUnit: bitRateUnit, toUnit: 'B' }); + const timeInSeconds = hours * 3600 + minutes * 60 + seconds; + return convertStorageAndRateUnits({ value: bitRateInBytes * timeInSeconds, fromUnit: 'B', toUnit: dataSizeUnit }); +} diff --git a/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue new file mode 100644 index 000000000..b4e492be4 --- /dev/null +++ b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tools/data-transfer-rate-converter/index.ts b/src/tools/data-transfer-rate-converter/index.ts new file mode 100644 index 000000000..cf3f520a5 --- /dev/null +++ b/src/tools/data-transfer-rate-converter/index.ts @@ -0,0 +1,12 @@ +import { TransferIn } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Data Transfer Rate Calculator', + path: '/data-transfer-rate-converter', + description: 'Compute Data Transfer times, rates and amount of data', + keywords: ['data', 'transfer', 'rate', 'convert', 'time'], + component: () => import('./data-transfer-rate-converter.vue'), + icon: TransferIn, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index c9003fe81..498e8abe7 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,6 +2,8 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; import { tool as emailNormalizer } from './email-normalizer'; +import { tool as dataTransferRateConverter } from './data-transfer-rate-converter'; +import { tool as dataStorageUnitConverter } from './data-storage-unit-converter'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -164,7 +166,13 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Math', - components: [mathEvaluator, etaCalculator, percentageCalculator], + components: [ + mathEvaluator, + etaCalculator, + percentageCalculator, + dataTransferRateConverter, + dataStorageUnitConverter, + ], }, { name: 'Measurement',