refactor(date-converter): improved ux and layout
This commit is contained in:
		
							parent
							
								
									5fa811a583
								
							
						
					
					
						commit
						fd9ab59172
					
				| @ -31,6 +31,8 @@ export default defineConfig({ | |||||||
|     trace: 'on-first-retry', |     trace: 'on-first-retry', | ||||||
| 
 | 
 | ||||||
|     testIdAttribute: 'data-test-id', |     testIdAttribute: 'data-test-id', | ||||||
|  |     locale: 'en-GB', | ||||||
|  |     timezoneId: 'Europe/Paris', | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   /* Configure projects for major browsers */ |   /* Configure projects for major browsers */ | ||||||
|  | |||||||
| @ -25,7 +25,15 @@ export type ValidationAttrs = { | |||||||
|   validationStatus: string | undefined; |   validationStatus: string | undefined; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: UseValidationRule<T>[] }) { | export function useValidation<T>({ | ||||||
|  |   source, | ||||||
|  |   rules, | ||||||
|  |   watch: watchRefs = [], | ||||||
|  | }: { | ||||||
|  |   source: Ref<T>; | ||||||
|  |   rules: UseValidationRule<T>[]; | ||||||
|  |   watch?: Ref<unknown>[]; | ||||||
|  | }) { | ||||||
|   const state = reactive<{ |   const state = reactive<{ | ||||||
|     message: string; |     message: string; | ||||||
|     status: undefined | 'error'; |     status: undefined | 'error'; | ||||||
| @ -42,7 +50,7 @@ export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: Use | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   watch( |   watch( | ||||||
|     [source], |     [source, ...watchRefs], | ||||||
|     () => { |     () => { | ||||||
|       state.message = ''; |       state.message = ''; | ||||||
|       state.status = undefined; |       state.status = undefined; | ||||||
|  | |||||||
| @ -0,0 +1,33 @@ | |||||||
|  | import { test, expect } from '@playwright/test'; | ||||||
|  | 
 | ||||||
|  | test.describe('Date time converter - json to yaml', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('/date-converter'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Has correct title', async ({ page }) => { | ||||||
|  |     await expect(page).toHaveTitle('Date-time converter - IT Tools'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Format is auto detected from a date and the date is correctly converted', async ({ page }) => { | ||||||
|  |     const initialFormat = await page.getByTestId('date-time-converter-format-select').innerText(); | ||||||
|  |     expect(initialFormat.trim()).toEqual('Timestamp'); | ||||||
|  | 
 | ||||||
|  |     await page.getByTestId('date-time-converter-input').fill('2023-04-12T23:10:24+02:00'); | ||||||
|  | 
 | ||||||
|  |     const detectedFormat = await page.getByTestId('date-time-converter-format-select').innerText(); | ||||||
|  |     expect(detectedFormat.trim()).toEqual('ISO 8601'); | ||||||
|  | 
 | ||||||
|  |     expect((await page.getByTestId('JS locale date string').inputValue()).trim()).toEqual( | ||||||
|  |       'Wed Apr 12 2023 23:10:24 GMT+0200 (Central European Summer Time)', | ||||||
|  |     ); | ||||||
|  |     expect((await page.getByTestId('ISO 8601').inputValue()).trim()).toEqual('2023-04-12T23:10:24+02:00'); | ||||||
|  |     expect((await page.getByTestId('ISO 9075').inputValue()).trim()).toEqual('2023-04-12 23:10:24'); | ||||||
|  |     expect((await page.getByTestId('Unix timestamp').inputValue()).trim()).toEqual('1681333824'); | ||||||
|  |     expect((await page.getByTestId('RFC 7231').inputValue()).trim()).toEqual('Wed, 12 Apr 2023 21:10:24 GMT'); | ||||||
|  |     expect((await page.getByTestId('RFC 3339').inputValue()).trim()).toEqual('2023-04-12T23:10:24+02:00'); | ||||||
|  |     expect((await page.getByTestId('Timestamp').inputValue()).trim()).toEqual('1681333824000'); | ||||||
|  |     expect((await page.getByTestId('UTC format').inputValue()).trim()).toEqual('Wed, 12 Apr 2023 21:10:24 GMT'); | ||||||
|  |     expect((await page.getByTestId('Mongo ObjectID').inputValue()).trim()).toEqual('64371e400000000000000000'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										142
									
								
								src/tools/date-time-converter/date-time-converter.models.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/tools/date-time-converter/date-time-converter.models.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | |||||||
|  | import { describe, test, expect } from 'vitest'; | ||||||
|  | import { | ||||||
|  |   isISO8601DateTimeString, | ||||||
|  |   isISO9075DateString, | ||||||
|  |   isRFC3339DateString, | ||||||
|  |   isRFC7231DateString, | ||||||
|  |   isUnixTimestamp, | ||||||
|  |   isTimestamp, | ||||||
|  |   isUTCDateString, | ||||||
|  |   isMongoObjectId, | ||||||
|  | } from './date-time-converter.models'; | ||||||
|  | 
 | ||||||
|  | describe('date-time-converter models', () => { | ||||||
|  |   describe('isISO8601DateTimeString', () => { | ||||||
|  |     test('should return true for valid ISO 8601 date strings', () => { | ||||||
|  |       expect(isISO8601DateTimeString('2021-01-01T00:00:00.000Z')).toBe(true); | ||||||
|  |       expect(isISO8601DateTimeString('2023-04-12T14:56:00+01:00')).toBe(true); | ||||||
|  |       expect(isISO8601DateTimeString('20230412T145600+0100')).toBe(true); | ||||||
|  |       expect(isISO8601DateTimeString('20230412T145600Z')).toBe(true); | ||||||
|  |       expect(isISO8601DateTimeString('2016-02-01')).toBe(true); | ||||||
|  |       expect(isISO8601DateTimeString('2016')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid ISO 8601 date strings', () => { | ||||||
|  |       expect(isISO8601DateTimeString()).toBe(false); | ||||||
|  |       expect(isISO8601DateTimeString('')).toBe(false); | ||||||
|  |       expect(isISO8601DateTimeString('qsdqsd')).toBe(false); | ||||||
|  |       expect(isISO8601DateTimeString('2016-02-01-')).toBe(false); | ||||||
|  |       expect(isISO8601DateTimeString('2021-01-01T00:00:00.')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isISO9075DateString', () => { | ||||||
|  |     test('should return true for valid ISO 9075 date strings', () => { | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00Z')).toBe(true); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00.123456Z')).toBe(true); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00+01:00')).toBe(true); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00-05:00')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid ISO 9075 date strings', () => { | ||||||
|  |       expect(isISO9075DateString('2022/01/01T12:00:00Z')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00.123456789Z')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00+1:00')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00-05:')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('2022-01-01 12:00:00-05:00:00')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('2022-01-01')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('12:00:00Z')).toBe(false); | ||||||
|  |       expect(isISO9075DateString('2022-01-01T12:00:00Zfoo')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isRFC3339DateString', () => { | ||||||
|  |     test('should return true for valid RFC 3339 date strings', () => { | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00Z')).toBe(true); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00.123456789Z')).toBe(true); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00.123456789+01:00')).toBe(true); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00-05:00')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid RFC 3339 date strings', () => { | ||||||
|  |       expect(isRFC3339DateString('2022/01/01T12:00:00Z')).toBe(false); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00.123456789+1:00')).toBe(false); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00-05:')).toBe(false); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00-05:00:00')).toBe(false); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01')).toBe(false); | ||||||
|  |       expect(isRFC3339DateString('12:00:00Z')).toBe(false); | ||||||
|  |       expect(isRFC3339DateString('2022-01-01T12:00:00Zfoo')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isRFC7231DateString', () => { | ||||||
|  |     test('should return true for valid RFC 7231 date strings', () => { | ||||||
|  |       expect(isRFC7231DateString('Sun, 06 Nov 1994 08:49:37 GMT')).toBe(true); | ||||||
|  |       expect(isRFC7231DateString('Tue, 22 Apr 2014 07:00:00 GMT')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid RFC 7231 date strings', () => { | ||||||
|  |       expect(isRFC7231DateString('06 Nov 1994 08:49:37 GMT')).toBe(false); | ||||||
|  |       expect(isRFC7231DateString('Sun, 06 Nov 94 08:49:37 GMT')).toBe(false); | ||||||
|  |       expect(isRFC7231DateString('Sun, 06 Nov 1994 8:49:37 GMT')).toBe(false); | ||||||
|  |       expect(isRFC7231DateString('Sun, 06 Nov 1994 08:49:37 GMT-0500')).toBe(false); | ||||||
|  |       expect(isRFC7231DateString('Sun, 06 November 1994 08:49:37 GMT')).toBe(false); | ||||||
|  |       expect(isRFC7231DateString('Sunday, 06 Nov 1994 08:49:37 GMT')).toBe(false); | ||||||
|  |       expect(isRFC7231DateString('06 Nov 1994')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isUnixTimestamp', () => { | ||||||
|  |     test('should return true for valid Unix timestamps', () => { | ||||||
|  |       expect(isUnixTimestamp('1649789394')).toBe(true); | ||||||
|  |       expect(isUnixTimestamp('1234567890')).toBe(true); | ||||||
|  |       expect(isUnixTimestamp('0')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid Unix timestamps', () => { | ||||||
|  |       expect(isUnixTimestamp('foo')).toBe(false); | ||||||
|  |       expect(isUnixTimestamp('')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isTimestamp', () => { | ||||||
|  |     test('should return true for valid Unix timestamps in milliseconds', () => { | ||||||
|  |       expect(isTimestamp('1649792026123')).toBe(true); | ||||||
|  |       expect(isTimestamp('1234567890000')).toBe(true); | ||||||
|  |       expect(isTimestamp('0')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid Unix timestamps in milliseconds', () => { | ||||||
|  |       expect(isTimestamp('foo')).toBe(false); | ||||||
|  |       expect(isTimestamp('')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isUTCDateString', () => { | ||||||
|  |     test('should return true for valid UTC date strings', () => { | ||||||
|  |       expect(isUTCDateString('Sun, 06 Nov 1994 08:49:37 GMT')).toBe(true); | ||||||
|  |       expect(isUTCDateString('Tue, 22 Apr 2014 07:00:00 GMT')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid UTC date strings', () => { | ||||||
|  |       expect(isUTCDateString('06 Nov 1994 08:49:37 GMT')).toBe(false); | ||||||
|  |       expect(isUTCDateString('16497920261')).toBe(false); | ||||||
|  |       expect(isUTCDateString('foo')).toBe(false); | ||||||
|  |       expect(isUTCDateString('')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('isMongoObjectId', () => { | ||||||
|  |     test('should return true for valid Mongo ObjectIds', () => { | ||||||
|  |       expect(isMongoObjectId('507f1f77bcf86cd799439011')).toBe(true); | ||||||
|  |       expect(isMongoObjectId('507f1f77bcf86cd799439012')).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     test('should return false for invalid Mongo ObjectIds', () => { | ||||||
|  |       expect(isMongoObjectId('507f1f77bcf86cd79943901')).toBe(false); | ||||||
|  |       expect(isMongoObjectId('507f1f77bcf86cd79943901z')).toBe(false); | ||||||
|  |       expect(isMongoObjectId('foo')).toBe(false); | ||||||
|  |       expect(isMongoObjectId('')).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										46
									
								
								src/tools/date-time-converter/date-time-converter.models.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/tools/date-time-converter/date-time-converter.models.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | import _ from 'lodash'; | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |   isISO8601DateTimeString, | ||||||
|  |   isISO9075DateString, | ||||||
|  |   isRFC3339DateString, | ||||||
|  |   isRFC7231DateString, | ||||||
|  |   isUnixTimestamp, | ||||||
|  |   isTimestamp, | ||||||
|  |   isUTCDateString, | ||||||
|  |   isMongoObjectId, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const ISO8601_REGEX = | ||||||
|  |   /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; | ||||||
|  | const ISO9075_REGEX = | ||||||
|  |   /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,6})?(([+-])([0-9]{2}):([0-9]{2})|Z)?$/; | ||||||
|  | 
 | ||||||
|  | const RFC3339_REGEX = | ||||||
|  |   /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,9})?(([+-])([0-9]{2}):([0-9]{2})|Z)$/; | ||||||
|  | 
 | ||||||
|  | const RFC7231_REGEX = /^[A-Za-z]{3},\s[0-9]{2}\s[A-Za-z]{3}\s[0-9]{4}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\sGMT$/; | ||||||
|  | 
 | ||||||
|  | function createRegexMatcher(regex: RegExp) { | ||||||
|  |   return (date?: string) => !_.isNil(date) && regex.test(date); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const isISO8601DateTimeString = createRegexMatcher(ISO8601_REGEX); | ||||||
|  | const isISO9075DateString = createRegexMatcher(ISO9075_REGEX); | ||||||
|  | const isRFC3339DateString = createRegexMatcher(RFC3339_REGEX); | ||||||
|  | const isRFC7231DateString = createRegexMatcher(RFC7231_REGEX); | ||||||
|  | const isUnixTimestamp = createRegexMatcher(/^[0-9]{1,10}$/); | ||||||
|  | const isTimestamp = createRegexMatcher(/^[0-9]{1,13}$/); | ||||||
|  | const isMongoObjectId = createRegexMatcher(/^[0-9a-fA-F]{24}$/); | ||||||
|  | 
 | ||||||
|  | function isUTCDateString(date?: string) { | ||||||
|  |   if (_.isNil(date)) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     return new Date(date).toUTCString() === date; | ||||||
|  |   } catch (_ignored) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | export type ToDateMapper = (value: string) => Date; | ||||||
|  | 
 | ||||||
|  | export type DateFormat = { | ||||||
|  |   name: string; | ||||||
|  |   fromDate: (date: Date) => string; | ||||||
|  |   toDate: (value: string) => Date; | ||||||
|  |   formatMatcher: (dateString: string) => boolean; | ||||||
|  | }; | ||||||
| @ -1,44 +1,38 @@ | |||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <n-card> |     <n-form-item :show-label="false" v-bind="validation.attrs"> | ||||||
|       <n-space justify="center"> |       <n-input-group> | ||||||
|         <n-form-item label="Use current date-time ?" label-placement="left" :show-feedback="false"> |  | ||||||
|           <n-switch v-model:value="useCurrentDate" /> |  | ||||||
|         </n-form-item> |  | ||||||
|       </n-space> |  | ||||||
|       <n-form-item |  | ||||||
|         :feedback="inputInvalid ? 'Invalid date for the current format' : ''" |  | ||||||
|         :validation-status="inputInvalid ? 'error' : undefined" |  | ||||||
|       > |  | ||||||
|         <n-input-group style="flex-grow: 1"> |  | ||||||
|           <n-select |  | ||||||
|             v-model:value="inputFormat" |  | ||||||
|             style="width: 200px" |  | ||||||
|             :options="formats.map(({ name }, i) => ({ label: name, value: i }))" |  | ||||||
|             :disabled="useCurrentDate" |  | ||||||
|           /> |  | ||||||
| 
 |  | ||||||
|         <n-input |         <n-input | ||||||
|           v-model:value="inputDate" |           v-model:value="inputDate" | ||||||
|           :on-input="onDateInputChanged" |           :on-input="onDateInputChanged" | ||||||
|             :disabled="useCurrentDate" |           placeholder="Put you date string here..." | ||||||
|             placeholder="Your date string..." |           clearable | ||||||
|  |           :input-props="{ 'data-test-id': 'date-time-converter-input' }" | ||||||
|  |         /> | ||||||
|  | 
 | ||||||
|  |         <n-select | ||||||
|  |           v-model:value="formatIndex" | ||||||
|  |           style="flex: 0 0 170px" | ||||||
|  |           :options="formats.map(({ name }, i) => ({ label: name, value: i }))" | ||||||
|  |           data-test-id="date-time-converter-format-select" | ||||||
|         /> |         /> | ||||||
|       </n-input-group> |       </n-input-group> | ||||||
|     </n-form-item> |     </n-form-item> | ||||||
|     <n-divider style="margin-top: 0" /> |     <n-divider style="margin-top: 0" /> | ||||||
|       <div v-for="{ name, fromDate } in formats" :key="name" style="margin: 5px 0"> |     <div v-for="{ name, fromDate } in formats" :key="name" mt-1> | ||||||
|       <n-input-group> |       <n-input-group> | ||||||
|         <n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label> |         <n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label> | ||||||
|           <input-copyable :value="fromDate(baseDate)" /> |         <input-copyable | ||||||
|  |           :value="formatDateUsingFormatter(fromDate, normalizedDate)" | ||||||
|  |           placeholder="Invalid date..." | ||||||
|  |           :input-props="{ 'data-test-id': name }" | ||||||
|  |         /> | ||||||
|       </n-input-group> |       </n-input-group> | ||||||
|     </div> |     </div> | ||||||
|     </n-card> |  | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { useRafFn } from '@vueuse/core'; |  | ||||||
| import { | import { | ||||||
|   formatISO, |   formatISO, | ||||||
|   formatISO9075, |   formatISO9075, | ||||||
| @ -47,95 +41,132 @@ import { | |||||||
|   fromUnixTime, |   fromUnixTime, | ||||||
|   getTime, |   getTime, | ||||||
|   getUnixTime, |   getUnixTime, | ||||||
|   isDate, |  | ||||||
|   parseISO, |   parseISO, | ||||||
|   parseJSON, |   parseJSON, | ||||||
|  |   isDate, | ||||||
|  |   isValid, | ||||||
| } from 'date-fns'; | } from 'date-fns'; | ||||||
| import { ref } from 'vue'; | import { withDefaultOnError } from '@/utils/defaults'; | ||||||
| import InputCopyable from '../../components/InputCopyable.vue'; | import { useValidation } from '@/composable/validation'; | ||||||
|  | import type { DateFormat, ToDateMapper } from './date-time-converter.types'; | ||||||
|  | import { | ||||||
|  |   isISO8601DateTimeString, | ||||||
|  |   isISO9075DateString, | ||||||
|  |   isRFC3339DateString, | ||||||
|  |   isRFC7231DateString, | ||||||
|  |   isTimestamp, | ||||||
|  |   isUTCDateString, | ||||||
|  |   isUnixTimestamp, | ||||||
|  |   isMongoObjectId, | ||||||
|  | } from './date-time-converter.models'; | ||||||
| 
 | 
 | ||||||
| const useCurrentDate = ref(true); |  | ||||||
| const inputDate = ref(''); | const inputDate = ref(''); | ||||||
| const inputFormat = ref(6); |  | ||||||
| const inputInvalid = ref(false); |  | ||||||
| const baseDate = ref(new Date()); |  | ||||||
| 
 | 
 | ||||||
| useRafFn(() => { | const toDate: ToDateMapper = (date) => new Date(date); | ||||||
|   if (useCurrentDate.value) { |  | ||||||
|     baseDate.value = new Date(); |  | ||||||
|   } |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| function onDateInputChanged(value: string) { | function formatDateUsingFormatter(formatter: (date: Date) => string, date?: Date) { | ||||||
|   const { toDate } = formats[inputFormat.value]; |   if (!date || !validation.isValid) { | ||||||
|   inputInvalid.value = false; |     return ''; | ||||||
| 
 |  | ||||||
|   try { |  | ||||||
|     const formatted: Date | string = toDate(value); |  | ||||||
| 
 |  | ||||||
|     if (!isDate(formatted) || isNaN(formatted.getTime())) { |  | ||||||
|       throw new Error('Invalid date'); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|     baseDate.value = formatted; |   return withDefaultOnError(() => formatter(date), ''); | ||||||
|   } catch (_) { |  | ||||||
|     inputInvalid.value = true; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Format = { | const formats: DateFormat[] = [ | ||||||
|   name: string; |  | ||||||
|   fromDate: (date: Date) => string; |  | ||||||
|   toDate: (value: string) => Date; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const toDate: Format['toDate'] = (date) => new Date(date); |  | ||||||
| 
 |  | ||||||
| const formats: Format[] = [ |  | ||||||
|   { |   { | ||||||
|     name: 'JS locale date string', |     name: 'JS locale date string', | ||||||
|     fromDate: (date) => date.toString(), |     fromDate: (date) => date.toString(), | ||||||
|     toDate, |     toDate, | ||||||
|  |     formatMatcher: () => false, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'ISO 8601', |     name: 'ISO 8601', | ||||||
|     fromDate: formatISO, |     fromDate: formatISO, | ||||||
|     toDate: parseISO, |     toDate: parseISO, | ||||||
|  |     formatMatcher: (date) => isISO8601DateTimeString(date), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'ISO 9075', |     name: 'ISO 9075', | ||||||
|     fromDate: formatISO9075, |     fromDate: formatISO9075, | ||||||
|     toDate: parseISO, |     toDate: parseISO, | ||||||
|  |     formatMatcher: (date) => isISO9075DateString(date), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'RFC 3339', |     name: 'RFC 3339', | ||||||
|     fromDate: formatRFC3339, |     fromDate: formatRFC3339, | ||||||
|     toDate, |     toDate, | ||||||
|  |     formatMatcher: (date) => isRFC3339DateString(date), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'RFC 7231', |     name: 'RFC 7231', | ||||||
|     fromDate: formatRFC7231, |     fromDate: formatRFC7231, | ||||||
|     toDate, |     toDate, | ||||||
|   }, |     formatMatcher: (date) => isRFC7231DateString(date), | ||||||
|   { |  | ||||||
|     name: 'Timestamp', |  | ||||||
|     fromDate: (date) => String(getTime(date)), |  | ||||||
|     toDate: (ms) => parseJSON(+ms), |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Unix timestamp', |     name: 'Unix timestamp', | ||||||
|     fromDate: (date) => String(getUnixTime(date)), |     fromDate: (date) => String(getUnixTime(date)), | ||||||
|     toDate: (sec) => fromUnixTime(+sec), |     toDate: (sec) => fromUnixTime(+sec), | ||||||
|  |     formatMatcher: (date) => isUnixTimestamp(date), | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'Timestamp', | ||||||
|  |     fromDate: (date) => String(getTime(date)), | ||||||
|  |     toDate: (ms) => parseJSON(+ms), | ||||||
|  |     formatMatcher: (date) => isTimestamp(date), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'UTC format', |     name: 'UTC format', | ||||||
|     fromDate: (date) => date.toUTCString(), |     fromDate: (date) => date.toUTCString(), | ||||||
|     toDate, |     toDate, | ||||||
|  |     formatMatcher: (date) => isUTCDateString(date), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Mongo ObjectID', |     name: 'Mongo ObjectID', | ||||||
|     fromDate: (date) => Math.floor(date.getTime() / 1000).toString(16) + '0000000000000000', |     fromDate: (date) => Math.floor(date.getTime() / 1000).toString(16) + '0000000000000000', | ||||||
|     toDate: (objectId) => new Date(parseInt(objectId.substring(0, 8), 16) * 1000), |     toDate: (objectId) => new Date(parseInt(objectId.substring(0, 8), 16) * 1000), | ||||||
|  |     formatMatcher: (date) => isMongoObjectId(date), | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  | 
 | ||||||
|  | const formatIndex = ref(6); | ||||||
|  | const now = useNow(); | ||||||
|  | 
 | ||||||
|  | const normalizedDate = computed(() => { | ||||||
|  |   if (!inputDate.value) { | ||||||
|  |     return now.value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const { toDate } = formats[formatIndex.value]; | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     return toDate(inputDate.value); | ||||||
|  |   } catch (_ignored) { | ||||||
|  |     return undefined; | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function onDateInputChanged(value: string) { | ||||||
|  |   const matchingIndex = formats.findIndex(({ formatMatcher }) => formatMatcher(value)); | ||||||
|  |   if (matchingIndex !== -1) { | ||||||
|  |     formatIndex.value = matchingIndex; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const validation = useValidation({ | ||||||
|  |   source: inputDate, | ||||||
|  |   watch: [formatIndex], | ||||||
|  |   rules: [ | ||||||
|  |     { | ||||||
|  |       message: 'This date is invalid for this format', | ||||||
|  |       validator: (value) => | ||||||
|  |         withDefaultOnError(() => { | ||||||
|  |           if (value === '') return true; | ||||||
|  | 
 | ||||||
|  |           const maybeDate = formats[formatIndex.value].toDate(value); | ||||||
|  |           return isDate(maybeDate) && isValid(maybeDate); | ||||||
|  |         }, false), | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user