diff --git a/components.d.ts b/components.d.ts index e5f8d862..3478013f 100644 --- a/components.d.ts +++ b/components.d.ts @@ -77,6 +77,7 @@ declare module '@vue/runtime-core' { EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default'] EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.vue')['default'] Encryption: typeof import('./src/tools/encryption/encryption.vue')['default'] + EpochConverter: typeof import('./src/tools/epoch-converter/epoch-converter.vue')['default'] EtaCalculator: typeof import('./src/tools/eta-calculator/eta-calculator.vue')['default'] FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default'] FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default'] diff --git a/src/tools/epoch-converter/epoch-converter.e2e.spec.ts b/src/tools/epoch-converter/epoch-converter.e2e.spec.ts new file mode 100644 index 00000000..d393e8e2 --- /dev/null +++ b/src/tools/epoch-converter/epoch-converter.e2e.spec.ts @@ -0,0 +1,31 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Tool - Epoch converter', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/epoch-converter'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('Epoch converter - IT Tools'); + }); + + test('Converts epoch to human-readable date', async ({ page }) => { + await page.getByPlaceholder('Enter epoch timestamp').fill('1750452480'); + await page.getByRole('button', { name: 'Convert to Date' }).click(); + + await expect(page.getByText('6/23/2025, 12:31:00 PM')).toBeVisible(); + }); + + test('Converts known date to epoch timestamp', async ({ page }) => { + await page.getByPlaceholder('YYYY').fill('2025'); + await page.getByPlaceholder('MM').first().fill('06'); + await page.getByPlaceholder('DD').fill('20'); + await page.getByPlaceholder('HH').fill('13'); + await page.getByPlaceholder('MM').nth(1).fill('48'); + await page.getByPlaceholder('SS').fill('00'); + + await page.getByRole('button', { name: 'Convert to Epoch' }).click(); + + await expect(page.getByText('1750452480')).toBeVisible(); + }); +}); diff --git a/src/tools/epoch-converter/epoch-converter.service.test.ts b/src/tools/epoch-converter/epoch-converter.service.test.ts new file mode 100644 index 00000000..8fdd31ba --- /dev/null +++ b/src/tools/epoch-converter/epoch-converter.service.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { dateToEpoch, epochToDate } from './epoch-converter.service'; + +describe('epochToDate', () => { + it('converts known epoch seconds to correct local date string', () => { + const epoch = 1750707060; + const expectedDate = '6/23/2025, 12:31:00 PM'; + const result = epochToDate(epoch); + expect(result).toBe(expectedDate); + }); + + it('throws for invalid string input', () => { + expect(() => epochToDate('not-a-number')).toThrowError(TypeError); + }); + + it('throws for NaN input', () => { + expect(() => epochToDate(Number.NaN)).toThrowError(TypeError); + }); +}); + +describe('dateToEpoch', () => { + it('converts a known date to correct epoch (2025-06-20 13:48:00)', () => { + const input = '2025-06-20T13:48:00'; + const expectedEpoch = 1750452480; + const result = dateToEpoch(input); + expect(result).toBe(expectedEpoch); + }); + + it('throws for invalid date string', () => { + expect(() => dateToEpoch('not-a-date')).toThrowError(TypeError); + }); + + it('throws for empty string', () => { + expect(() => dateToEpoch('')).toThrowError(TypeError); + }); +}); diff --git a/src/tools/epoch-converter/epoch-converter.service.ts b/src/tools/epoch-converter/epoch-converter.service.ts new file mode 100644 index 00000000..3f527f86 --- /dev/null +++ b/src/tools/epoch-converter/epoch-converter.service.ts @@ -0,0 +1,23 @@ +// Convert Epoch to Human Readable Date +export function epochToDate(epoch: string | number): string { + const num = typeof epoch === 'string' ? Number.parseInt(epoch, 10) : epoch; + + if (Number.isNaN(num)) { + throw new TypeError('Invalid epoch timestamp'); + } + + const timestamp = num < 1e12 ? num * 1000 : num; + + return new Date(timestamp).toLocaleString(); +} + +// Convert Human Readable Date to Epoch +export function dateToEpoch(dateString: string): number { + const date = new Date(dateString); + + if (Number.isNaN(date.getTime())) { + throw new TypeError('Invalid date string'); + } + + return Math.floor(date.getTime() / 1000); +} diff --git a/src/tools/epoch-converter/epoch-converter.vue b/src/tools/epoch-converter/epoch-converter.vue new file mode 100644 index 00000000..aaaa5abd --- /dev/null +++ b/src/tools/epoch-converter/epoch-converter.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/tools/epoch-converter/index.ts b/src/tools/epoch-converter/index.ts new file mode 100644 index 00000000..e06e5503 --- /dev/null +++ b/src/tools/epoch-converter/index.ts @@ -0,0 +1,12 @@ +import { ArrowsShuffle } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Epoch converter', + path: '/epoch-converter', + description: 'Converts epoch to human readable date and vice versa', + keywords: ['epoch', 'converter'], + component: () => import('./epoch-converter.vue'), + icon: ArrowsShuffle, + createdAt: new Date('2025-06-19'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index 3b24024e..67a1e6f6 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,7 +1,7 @@ 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 gzipDecompressor } from './gzip-decompressor'; +import { tool as epochConverter } from './epoch-converter'; import { tool as emailNormalizer } from './email-normalizer'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -118,6 +118,7 @@ export const toolsByCategory: ToolCategory[] = [ jsonToXml, markdownToHtml, gzipDecompressor, + epochConverter, ], }, {