Add new tool - epoch converter
This commit is contained in:
		
							parent
							
								
									d7578e23a5
								
							
						
					
					
						commit
						00b1148e3a
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -77,6 +77,7 @@ declare module '@vue/runtime-core' { | |||||||
|     EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default'] |     EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default'] | ||||||
|     EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.vue')['default'] |     EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.vue')['default'] | ||||||
|     Encryption: typeof import('./src/tools/encryption/encryption.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'] |     EtaCalculator: typeof import('./src/tools/eta-calculator/eta-calculator.vue')['default'] | ||||||
|     FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default'] |     FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default'] | ||||||
|     FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default'] |     FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default'] | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								src/tools/epoch-converter/epoch-converter.e2e.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/tools/epoch-converter/epoch-converter.e2e.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										36
									
								
								src/tools/epoch-converter/epoch-converter.service.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/tools/epoch-converter/epoch-converter.service.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										23
									
								
								src/tools/epoch-converter/epoch-converter.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/tools/epoch-converter/epoch-converter.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | } | ||||||
							
								
								
									
										115
									
								
								src/tools/epoch-converter/epoch-converter.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/tools/epoch-converter/epoch-converter.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | import { dateToEpoch, epochToDate } from './epoch-converter.service.ts'; | ||||||
|  | 
 | ||||||
|  | const epochInput = ref(''); | ||||||
|  | const dateOutput = ref(''); | ||||||
|  | const epochOutput = ref(''); | ||||||
|  | const error = ref<string | null>(null); | ||||||
|  | 
 | ||||||
|  | const dateParts = ref({ | ||||||
|  |   year: '', | ||||||
|  |   month: '', | ||||||
|  |   day: '', | ||||||
|  |   hour: '', | ||||||
|  |   minute: '', | ||||||
|  |   second: '', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function convertEpochToDate() { | ||||||
|  |   error.value = null; | ||||||
|  |   try { | ||||||
|  |     dateOutput.value = epochToDate(epochInput.value); | ||||||
|  |   } | ||||||
|  |   catch (e: any) { | ||||||
|  |     error.value = e.message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function convertDateToEpoch() { | ||||||
|  |   error.value = null; | ||||||
|  |   try { | ||||||
|  |     const { year, month, day, hour, minute, second } = dateParts.value; | ||||||
|  | 
 | ||||||
|  |     const isoString = `${year}-${pad(month)}-${pad(day)}T${pad(hour)}:${pad(minute)}:${pad(second)}`; | ||||||
|  |     epochOutput.value = dateToEpoch(isoString); | ||||||
|  |   } | ||||||
|  |   catch (e: any) { | ||||||
|  |     error.value = e.message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function pad(value: string): string { | ||||||
|  |   return value.toString().padStart(2, '0'); | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <c-card title="Epoch Time Converter (24 hour time format)" class="mx-auto max-w-4xl px-4"> | ||||||
|  |     <!-- Epoch to Date --> | ||||||
|  |     <div class="mb-8"> | ||||||
|  |       <div class="mb-2 font-semibold"> | ||||||
|  |         Epoch to Date | ||||||
|  |       </div> | ||||||
|  |       <c-input-text | ||||||
|  |         v-model:value="epochInput" | ||||||
|  |         placeholder="Enter epoch timestamp (e.g. 1718822594)" | ||||||
|  |         class="mb-4" | ||||||
|  |         raw-text | ||||||
|  |       /> | ||||||
|  |       <c-button @click="convertEpochToDate"> | ||||||
|  |         Convert to Date | ||||||
|  |       </c-button> | ||||||
|  | 
 | ||||||
|  |       <div v-if="dateOutput" class="mt-4 text-sm text-green-400"> | ||||||
|  |         Human-Readable Date: <strong>{{ dateOutput }}</strong> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <!-- Date to Epoch --> | ||||||
|  |     <div class="mb-8"> | ||||||
|  |       <div class="mb-2 font-semibold"> | ||||||
|  |         Date to Epoch | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <div class="mb-4 max-w-sm space-y-3"> | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <label class="w-24 text-sm font-medium">Year:</label> | ||||||
|  |           <c-input-text v-model:value="dateParts.year" placeholder="YYYY" class="ml-auto w-32" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <label class="w-24 text-sm font-medium">Month:</label> | ||||||
|  |           <c-input-text v-model:value="dateParts.month" placeholder="MM" class="ml-auto w-24" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <label class="w-24 text-sm font-medium">Day:</label> | ||||||
|  |           <c-input-text v-model:value="dateParts.day" placeholder="DD" class="ml-auto w-24" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <label class="w-24 text-sm font-medium">Hour:</label> | ||||||
|  |           <c-input-text v-model:value="dateParts.hour" placeholder="HH" class="ml-auto w-24" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <label class="w-24 text-sm font-medium">Minute:</label> | ||||||
|  |           <c-input-text v-model:value="dateParts.minute" placeholder="MM" class="ml-auto w-24" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex items-center"> | ||||||
|  |           <label class="w-24 text-sm font-medium">Second:</label> | ||||||
|  |           <c-input-text v-model:value="dateParts.second" placeholder="SS" class="ml-auto w-24" /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <c-button @click="convertDateToEpoch"> | ||||||
|  |         Convert to Epoch | ||||||
|  |       </c-button> | ||||||
|  | 
 | ||||||
|  |       <div v-if="epochOutput" class="mt-4 text-sm text-green-400"> | ||||||
|  |         Epoch Timestamp: <strong>{{ epochOutput }}</strong> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <c-alert v-if="error" type="error" class="mt-6"> | ||||||
|  |       {{ error }} | ||||||
|  |     </c-alert> | ||||||
|  |   </c-card> | ||||||
|  | </template> | ||||||
							
								
								
									
										12
									
								
								src/tools/epoch-converter/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/epoch-converter/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'), | ||||||
|  | }); | ||||||
| @ -1,7 +1,7 @@ | |||||||
| import { tool as base64FileConverter } from './base64-file-converter'; | import { tool as base64FileConverter } from './base64-file-converter'; | ||||||
| import { tool as base64StringConverter } from './base64-string-converter'; | import { tool as base64StringConverter } from './base64-string-converter'; | ||||||
| import { tool as basicAuthGenerator } from './basic-auth-generator'; | 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 emailNormalizer } from './email-normalizer'; | ||||||
| 
 | 
 | ||||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||||
| @ -118,6 +118,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|       jsonToXml, |       jsonToXml, | ||||||
|       markdownToHtml, |       markdownToHtml, | ||||||
|       gzipDecompressor, |       gzipDecompressor, | ||||||
|  |       epochConverter, | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user