diff --git a/src/components/desktop/DateTimeSelect.vue b/src/components/desktop/DateTimeSelect.vue index 43ef7912..0d798fa1 100644 --- a/src/components/desktop/DateTimeSelect.vue +++ b/src/components/desktop/DateTimeSelect.vue @@ -89,6 +89,7 @@ import { ThemeType } from '@/core/theme.ts'; import { NumeralSystem } from '@/core/numeral.ts'; import { type DateTime, + type DateFormatOrder, MeridiemIndicator, KnownDateTimeFormat } from '@/core/datetime.ts'; @@ -120,6 +121,8 @@ const theme = useTheme(); const { tt, getCurrentNumeralSystemType, + getLongDateFormatOrder, + getShortDateFormatOrder, parseDateTimeFromLongDateTime, parseDateTimeFromShortDateTime, formatDateTimeToLongDateTime @@ -144,6 +147,8 @@ const secondInput = useTemplateRef('secondInput'); const isDarkMode = computed(() => theme.global.name.value === ThemeType.Dark); const numeralSystem = computed(() => getCurrentNumeralSystemType()); +const longDateFormatOrder = computed(() => getLongDateFormatOrder()); +const shortDateFormatOrder = computed(() => getShortDateFormatOrder()); const dateTime = computed({ get: () => { @@ -245,10 +250,10 @@ function onPaste(event: ClipboardEvent): void { text = text.trim(); - const formats = KnownDateTimeFormat.detect(text); + const formats = KnownDateTimeFormat.detect(text, longDateFormatOrder.value, shortDateFormatOrder.value); let dt: DateTime | undefined = undefined; - if (formats && formats.length === 1) { + if (formats && (formats.length === 1 || (formats.length > 1 && formats[0]!.type === longDateFormatOrder.value && formats[0]!.type === shortDateFormatOrder.value))) { dt = parseDateTimeFromKnownDateTimeFormat(text, formats[0] as KnownDateTimeFormat); if (dt) { diff --git a/src/core/datetime.ts b/src/core/datetime.ts index 749550d3..28eeb4ee 100644 --- a/src/core/datetime.ts +++ b/src/core/datetime.ts @@ -345,108 +345,18 @@ export class MeridiemIndicator { } } -export class KnownDateTimeFormat { - private static readonly allInstances: KnownDateTimeFormat[] = []; - - public static readonly DefaultDateTime = new KnownDateTimeFormat('YYYY-MM-DD HH:mm:ss', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - public static readonly DefaultDateTimeWithTimezone = new KnownDateTimeFormat('YYYY-MM-DD HH:mm:ssZ', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$/); - public static readonly DefaultDateTimeWithoutSecond = new KnownDateTimeFormat('YYYY-MM-DD HH:mm', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]$/); - public static readonly DefaultDate = new KnownDateTimeFormat('YYYY-MM-DD', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/); - - public static readonly RFC3339 = new KnownDateTimeFormat('YYYY-MM-DDTHH:mm:ssZ', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$/); - - public static readonly YYYYMMDDSlashWithTime = new KnownDateTimeFormat('YYYY/MM/DD HH:mm:ss', /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - public static readonly MMDDYYYYSlashWithTime = new KnownDateTimeFormat('MM/DD/YYYY HH:mm:ss', /^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - public static readonly DDMMYYYYSlashWithTime = new KnownDateTimeFormat('DD/MM/YYYY HH:mm:ss', /^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - - public static readonly YYYYMMDDDotWithTime = new KnownDateTimeFormat('YYYY.MM.DD HH:mm:ss', /^\d{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - public static readonly MMDDYYYYDotWithTime = new KnownDateTimeFormat('MM.DD.YYYY HH:mm:ss', /^(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1])\.\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - public static readonly DDMMYYYYDotWithTime = new KnownDateTimeFormat('DD.MM.YYYY HH:mm:ss', /^(0[1-9]|[1-2][0-9]|3[0-1])\.(0[1-9]|1[0-2])\.\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); - - public static readonly MMDDYYYYDash = new KnownDateTimeFormat('MM-DD-YYYY', /^(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])-\d{4}$/); - public static readonly DDMMYYYYDash = new KnownDateTimeFormat('DD-MM-YYYY', /^(0[1-9]|[1-2][0-9]|3[0-1])-(0[1-9]|1[0-2])-\d{4}$/); - - public static readonly YYYYMMDDSlash = new KnownDateTimeFormat('YYYY/MM/DD', /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])$/); - public static readonly MMDDYYYYSlash = new KnownDateTimeFormat('MM/DD/YYYY', /^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/\d{4}$/); - public static readonly DDMMYYYYSlash = new KnownDateTimeFormat('DD/MM/YYYY', /^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/\d{4}$/); - - public static readonly YYYYMDSlash = new KnownDateTimeFormat('YYYY/M/D', /^\d{4}\/([1-9]|1[0-2])\/([1-9]|[1-2][0-9]|3[0-1])$/); - public static readonly MDYYYYSlash = new KnownDateTimeFormat('M/D/YYYY', /^([1-9]|1[0-2])\/([1-9]|[1-2][0-9]|3[0-1])\/\d{4}$/); - public static readonly DMYYYYSlash = new KnownDateTimeFormat('D/M/YYYY', /^([1-9]|[1-2][0-9]|3[0-1])\/([1-9]|1[0-2])\/\d{4}$/); - - public static readonly YYYYMMDDDot = new KnownDateTimeFormat('YYYY.MM.DD', /^\d{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1])$/); - public static readonly MMDDYYYYDot = new KnownDateTimeFormat('MM.DD.YYYY', /^(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1])\.\d{4}$/); - public static readonly DDMMYYYYDot = new KnownDateTimeFormat('DD.MM.YYYY', /^(0[1-9]|[1-2][0-9]|3[0-1])\.(0[1-9]|1[0-2])\.\d{4}$/); - - public static readonly YYYYMDDot = new KnownDateTimeFormat('YYYY.M.D', /^\d{4}\.([1-9]|1[0-2])\.([1-9]|[1-2][0-9]|3[0-1])$/); - public static readonly MDYYYYDot = new KnownDateTimeFormat('M.D.YYYY', /^([1-9]|1[0-2])\.([1-9]|[1-2][0-9]|3[0-1])\.\d{4}$/); - public static readonly DMYYYYDot = new KnownDateTimeFormat('D.M.YYYY', /^([1-9]|[1-2][0-9]|3[0-1])\.([1-9]|1[0-2])\.\d{4}$/); - - public static readonly YYYYMMDD = new KnownDateTimeFormat('YYYYMMDD', /^\d{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$/); - - public readonly format: string; - private readonly regex: RegExp; - - private constructor(format: string, regex: RegExp) { - this.format = format; - this.regex = regex; - - KnownDateTimeFormat.allInstances.push(this); - } - - public isValid(dateTime: string): boolean { - return this.regex.test(dateTime); - } - - public static values(): KnownDateTimeFormat[] { - return KnownDateTimeFormat.allInstances; - } - - public static detect(dateTime: string): KnownDateTimeFormat[] | undefined { - const result: KnownDateTimeFormat[] = []; - - for (const format of KnownDateTimeFormat.allInstances) { - if (format.isValid(dateTime)) { - result.push(format); - } - } - - return result.length > 0 ? result : undefined; - } - - public static detectMulti(dateTimes: string[]): KnownDateTimeFormat[] | undefined { - const detectedCounts: Record = {}; - - for (const dateTime of dateTimes) { - const detectedFormats = KnownDateTimeFormat.detect(dateTime); - - if (detectedFormats) { - for (const format of detectedFormats) { - detectedCounts[format.format] = (detectedCounts[format.format] || 0) + 1; - } - } else { - return undefined; - } - } - - const result: KnownDateTimeFormat[] = []; - - for (const format of KnownDateTimeFormat.allInstances) { - if (detectedCounts[format.format] === dateTimes.length) { - result.push(format); - } - } - - return result.length > 0 ? result : undefined; - } -} - export const LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE: number = 0; +export enum DateFormatOrder { + YMD = 1, + MDY = 2, + DMY = 3 +} + export interface DateFormat { readonly type: number; readonly key: string; - readonly isMonthAfterYear: boolean; + readonly order: DateFormatOrder; } type DateFormatTypeName = 'YYYYMMDD' | 'MMDDYYYY' | 'DDMMYYYY'; @@ -456,22 +366,22 @@ export class LongDateFormat implements DateFormat { private static readonly allInstancesByType: Record = {}; private static readonly allInstancesByTypeName: Record = {}; - public static readonly YYYYMMDD = new LongDateFormat(1, 'YYYYMMDD', 'yyyy_mm_dd', true); - public static readonly MMDDYYYY = new LongDateFormat(2, 'MMDDYYYY', 'mm_dd_yyyy', false); - public static readonly DDMMYYYY = new LongDateFormat(3, 'DDMMYYYY', 'dd_mm_yyyy', false); + public static readonly YYYYMMDD = new LongDateFormat(1, 'YYYYMMDD', 'yyyy_mm_dd', DateFormatOrder.YMD); + public static readonly MMDDYYYY = new LongDateFormat(2, 'MMDDYYYY', 'mm_dd_yyyy', DateFormatOrder.MDY); + public static readonly DDMMYYYY = new LongDateFormat(3, 'DDMMYYYY', 'dd_mm_yyyy', DateFormatOrder.DMY); public static readonly Default = LongDateFormat.YYYYMMDD; public readonly type: number; public readonly typeName: string; public readonly key: string; - public readonly isMonthAfterYear: boolean; + public readonly order: DateFormatOrder; - private constructor(type: number, typeName: DateFormatTypeName, key: string, isMonthAfterYear: boolean) { + private constructor(type: number, typeName: DateFormatTypeName, key: string, order: DateFormatOrder) { this.type = type; this.typeName = typeName; this.key = key; - this.isMonthAfterYear = isMonthAfterYear; + this.order = order; LongDateFormat.allInstances.push(this); LongDateFormat.allInstancesByType[type] = this; @@ -496,22 +406,22 @@ export class ShortDateFormat implements DateFormat { private static readonly allInstancesByType: Record = {}; private static readonly allInstancesByTypeName: Record = {}; - public static readonly YYYYMMDD = new ShortDateFormat(1, 'YYYYMMDD', 'yyyy_mm_dd', true); - public static readonly MMDDYYYY = new ShortDateFormat(2, 'MMDDYYYY', 'mm_dd_yyyy', false); - public static readonly DDMMYYYY = new ShortDateFormat(3, 'DDMMYYYY', 'dd_mm_yyyy', false); + public static readonly YYYYMMDD = new ShortDateFormat(1, 'YYYYMMDD', 'yyyy_mm_dd', DateFormatOrder.YMD); + public static readonly MMDDYYYY = new ShortDateFormat(2, 'MMDDYYYY', 'mm_dd_yyyy', DateFormatOrder.MDY); + public static readonly DDMMYYYY = new ShortDateFormat(3, 'DDMMYYYY', 'dd_mm_yyyy', DateFormatOrder.DMY); public static readonly Default = ShortDateFormat.YYYYMMDD; public readonly type: number; public readonly typeName: string; public readonly key: string; - public readonly isMonthAfterYear: boolean; + public readonly order: DateFormatOrder; - private constructor(type: number, typeName: DateFormatTypeName, key: string, isMonthAfterYear: boolean) { + private constructor(type: number, typeName: DateFormatTypeName, key: string, order: DateFormatOrder) { this.type = type; this.typeName = typeName; this.key = key; - this.isMonthAfterYear = isMonthAfterYear; + this.order = order; ShortDateFormat.allInstances.push(this); ShortDateFormat.allInstancesByType[type] = this; @@ -626,6 +536,163 @@ export class ShortTimeFormat implements TimeFormat { } } +export class KnownDateTimeFormat { + private static readonly allInstances: KnownDateTimeFormat[] = []; + private static readonly allYMDInstances: KnownDateTimeFormat[] = []; + private static readonly allMDYInstances: KnownDateTimeFormat[] = []; + private static readonly allDMYInstances: KnownDateTimeFormat[] = []; + + public static readonly DefaultDateTime = new KnownDateTimeFormat('YYYY-MM-DD HH:mm:ss', DateFormatOrder.YMD, /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + public static readonly DefaultDateTimeWithTimezone = new KnownDateTimeFormat('YYYY-MM-DD HH:mm:ssZ', DateFormatOrder.YMD, /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$/); + public static readonly DefaultDateTimeWithoutSecond = new KnownDateTimeFormat('YYYY-MM-DD HH:mm', DateFormatOrder.YMD, /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]$/); + public static readonly DefaultDate = new KnownDateTimeFormat('YYYY-MM-DD', DateFormatOrder.YMD, /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/); + + public static readonly RFC3339 = new KnownDateTimeFormat('YYYY-MM-DDTHH:mm:ssZ', DateFormatOrder.YMD, /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$/); + + public static readonly YYYYMMDDSlashWithTime = new KnownDateTimeFormat('YYYY/MM/DD HH:mm:ss', DateFormatOrder.YMD, /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + public static readonly MMDDYYYYSlashWithTime = new KnownDateTimeFormat('MM/DD/YYYY HH:mm:ss', DateFormatOrder.MDY, /^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + public static readonly DDMMYYYYSlashWithTime = new KnownDateTimeFormat('DD/MM/YYYY HH:mm:ss', DateFormatOrder.DMY, /^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + + public static readonly YYYYMMDDDotWithTime = new KnownDateTimeFormat('YYYY.MM.DD HH:mm:ss', DateFormatOrder.YMD, /^\d{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + public static readonly MMDDYYYYDotWithTime = new KnownDateTimeFormat('MM.DD.YYYY HH:mm:ss', DateFormatOrder.MDY, /^(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1])\.\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + public static readonly DDMMYYYYDotWithTime = new KnownDateTimeFormat('DD.MM.YYYY HH:mm:ss', DateFormatOrder.DMY, /^(0[1-9]|[1-2][0-9]|3[0-1])\.(0[1-9]|1[0-2])\.\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/); + + public static readonly MMDDYYYYDash = new KnownDateTimeFormat('MM-DD-YYYY', DateFormatOrder.MDY, /^(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])-\d{4}$/); + public static readonly DDMMYYYYDash = new KnownDateTimeFormat('DD-MM-YYYY', DateFormatOrder.DMY, /^(0[1-9]|[1-2][0-9]|3[0-1])-(0[1-9]|1[0-2])-\d{4}$/); + + public static readonly YYYYMMDDSlash = new KnownDateTimeFormat('YYYY/MM/DD', DateFormatOrder.YMD, /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])$/); + public static readonly MMDDYYYYSlash = new KnownDateTimeFormat('MM/DD/YYYY', DateFormatOrder.MDY, /^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/\d{4}$/); + public static readonly DDMMYYYYSlash = new KnownDateTimeFormat('DD/MM/YYYY', DateFormatOrder.DMY, /^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/\d{4}$/); + + public static readonly YYYYMDSlash = new KnownDateTimeFormat('YYYY/M/D', DateFormatOrder.YMD, /^\d{4}\/([1-9]|1[0-2])\/([1-9]|[1-2][0-9]|3[0-1])$/); + public static readonly MDYYYYSlash = new KnownDateTimeFormat('M/D/YYYY', DateFormatOrder.MDY, /^([1-9]|1[0-2])\/([1-9]|[1-2][0-9]|3[0-1])\/\d{4}$/); + public static readonly DMYYYYSlash = new KnownDateTimeFormat('D/M/YYYY', DateFormatOrder.DMY, /^([1-9]|[1-2][0-9]|3[0-1])\/([1-9]|1[0-2])\/\d{4}$/); + + public static readonly YYYYMMDDDot = new KnownDateTimeFormat('YYYY.MM.DD', DateFormatOrder.YMD, /^\d{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1])$/); + public static readonly MMDDYYYYDot = new KnownDateTimeFormat('MM.DD.YYYY', DateFormatOrder.MDY, /^(0[1-9]|1[0-2])\.(0[1-9]|[1-2][0-9]|3[0-1])\.\d{4}$/); + public static readonly DDMMYYYYDot = new KnownDateTimeFormat('DD.MM.YYYY', DateFormatOrder.DMY, /^(0[1-9]|[1-2][0-9]|3[0-1])\.(0[1-9]|1[0-2])\.\d{4}$/); + + public static readonly YYYYMDDot = new KnownDateTimeFormat('YYYY.M.D', DateFormatOrder.YMD, /^\d{4}\.([1-9]|1[0-2])\.([1-9]|[1-2][0-9]|3[0-1])$/); + public static readonly MDYYYYDot = new KnownDateTimeFormat('M.D.YYYY', DateFormatOrder.MDY, /^([1-9]|1[0-2])\.([1-9]|[1-2][0-9]|3[0-1])\.\d{4}$/); + public static readonly DMYYYYDot = new KnownDateTimeFormat('D.M.YYYY', DateFormatOrder.DMY, /^([1-9]|[1-2][0-9]|3[0-1])\.([1-9]|1[0-2])\.\d{4}$/); + + public static readonly YYYYMMDD = new KnownDateTimeFormat('YYYYMMDD', DateFormatOrder.YMD, /^\d{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$/); + + public readonly format: string; + public readonly type: DateFormatOrder; + private readonly regex: RegExp; + + private constructor(format: string, type: DateFormatOrder, regex: RegExp) { + this.format = format; + this.type = type; + this.regex = regex; + + if (type === DateFormatOrder.YMD) { + KnownDateTimeFormat.allYMDInstances.push(this); + } else if (type === DateFormatOrder.MDY) { + KnownDateTimeFormat.allMDYInstances.push(this); + } else if (type === DateFormatOrder.DMY) { + KnownDateTimeFormat.allDMYInstances.push(this); + } + + KnownDateTimeFormat.allInstances.push(this); + } + + public isValid(dateTime: string): boolean { + return this.regex.test(dateTime); + } + + public static values(): KnownDateTimeFormat[] { + return KnownDateTimeFormat.allInstances; + } + + public static detect(dateTime: string, longDateTimeFormatOrder: DateFormatOrder, shortDateTimeFormatOrder: DateFormatOrder): KnownDateTimeFormat[] | undefined { + const allFormats: KnownDateTimeFormat[] = KnownDateTimeFormat.getAllFormatsByOrder(longDateTimeFormatOrder, shortDateTimeFormatOrder); + return KnownDateTimeFormat.detectSingle(dateTime, allFormats); + } + + public static detectMulti(dateTimes: string[], longDateTimeFormatOrder: DateFormatOrder, shortDateTimeFormatOrder: DateFormatOrder): KnownDateTimeFormat[] | undefined { + const detectedCounts: Record = {}; + const allFormats: KnownDateTimeFormat[] = KnownDateTimeFormat.getAllFormatsByOrder(longDateTimeFormatOrder, shortDateTimeFormatOrder); + + for (const dateTime of dateTimes) { + const detectedFormats = KnownDateTimeFormat.detectSingle(dateTime, allFormats); + + if (detectedFormats) { + for (const format of detectedFormats) { + detectedCounts[format.format] = (detectedCounts[format.format] || 0) + 1; + } + } else { + return undefined; + } + } + + const result: KnownDateTimeFormat[] = []; + + for (const format of KnownDateTimeFormat.allInstances) { + if (detectedCounts[format.format] === dateTimes.length) { + result.push(format); + } + } + + return result.length > 0 ? result : undefined; + } + + private static detectSingle(dateTime: string, allFormats: KnownDateTimeFormat[]): KnownDateTimeFormat[] | undefined { + const result: KnownDateTimeFormat[] = []; + + for (const format of allFormats) { + if (format.isValid(dateTime)) { + result.push(format); + } + } + + return result.length > 0 ? result : undefined; + } + + private static getAllFormatsByOrder(longDateTimeFormatOrder: DateFormatOrder, shortDateTimeFormatOrder: DateFormatOrder): KnownDateTimeFormat[] { + if (longDateTimeFormatOrder === DateFormatOrder.YMD && (shortDateTimeFormatOrder === DateFormatOrder.YMD || shortDateTimeFormatOrder === DateFormatOrder.MDY)) { + return [ + ...KnownDateTimeFormat.allYMDInstances, + ...KnownDateTimeFormat.allMDYInstances, + ...KnownDateTimeFormat.allDMYInstances + ]; + } else if (longDateTimeFormatOrder === DateFormatOrder.YMD && shortDateTimeFormatOrder === DateFormatOrder.DMY) { + return [ + ...KnownDateTimeFormat.allYMDInstances, + ...KnownDateTimeFormat.allDMYInstances, + ...KnownDateTimeFormat.allMDYInstances + ]; + } else if (longDateTimeFormatOrder === DateFormatOrder.MDY && (shortDateTimeFormatOrder === DateFormatOrder.MDY || shortDateTimeFormatOrder === DateFormatOrder.YMD)) { + return [ + ...KnownDateTimeFormat.allMDYInstances, + ...KnownDateTimeFormat.allYMDInstances, + ...KnownDateTimeFormat.allDMYInstances + ]; + } else if (longDateTimeFormatOrder === DateFormatOrder.MDY && shortDateTimeFormatOrder === DateFormatOrder.DMY) { + return [ + ...KnownDateTimeFormat.allMDYInstances, + ...KnownDateTimeFormat.allDMYInstances, + ...KnownDateTimeFormat.allYMDInstances + ]; + } else if (longDateTimeFormatOrder === DateFormatOrder.DMY && (shortDateTimeFormatOrder === DateFormatOrder.DMY || shortDateTimeFormatOrder === DateFormatOrder.YMD)) { + return [ + ...KnownDateTimeFormat.allDMYInstances, + ...KnownDateTimeFormat.allYMDInstances, + ...KnownDateTimeFormat.allMDYInstances + ]; + } else if (longDateTimeFormatOrder === DateFormatOrder.DMY && shortDateTimeFormatOrder === DateFormatOrder.MDY) { + return [ + ...KnownDateTimeFormat.allDMYInstances, + ...KnownDateTimeFormat.allMDYInstances, + ...KnownDateTimeFormat.allYMDInstances + ]; + } else { + return KnownDateTimeFormat.allInstances; + } + } +} + export enum DateRangeScene { Normal = 0, TrendAnalysis = 1, diff --git a/src/core/import_transaction.ts b/src/core/import_transaction.ts index 95c6482a..15b4d1ea 100644 --- a/src/core/import_transaction.ts +++ b/src/core/import_transaction.ts @@ -1,6 +1,6 @@ import { type TypeAndName, type TypeAndDisplayName, entries, keys } from './base.ts'; import { KnownAmountFormat } from './numeral.ts'; -import { KnownDateTimeFormat } from './datetime.ts'; +import { type DateFormatOrder, KnownDateTimeFormat } from './datetime.ts'; import { KnownDateTimezoneFormat } from './timezone.ts'; import { TransactionType } from './transaction.ts'; @@ -165,7 +165,7 @@ export class ImportTransactionDataMapping { return result; } - public parseFileAutoDetectedTimeFormat(fileData: string[][] | undefined): string | undefined { + public parseFileAutoDetectedTimeFormat(fileData: string[][] | undefined, longDateTimeFormatOrder: DateFormatOrder, shortDateTimeFormatOrder: DateFormatOrder): string | undefined { if (!fileData || !fileData.length || !this.isColumnMappingSet(ImportTransactionColumnType.TransactionTime)) { return undefined; } @@ -193,7 +193,7 @@ export class ImportTransactionDataMapping { } } - const detectedFormats = KnownDateTimeFormat.detectMulti(allDateTimes); + const detectedFormats = KnownDateTimeFormat.detectMulti(allDateTimes, longDateTimeFormatOrder, shortDateTimeFormatOrder); if (!detectedFormats || !detectedFormats.length || detectedFormats.length > 1) { return undefined; diff --git a/src/lib/datetime.ts b/src/lib/datetime.ts index e0ef8df6..18718a54 100644 --- a/src/lib/datetime.ts +++ b/src/lib/datetime.ts @@ -654,7 +654,7 @@ export function parseDateTimeFromUnixTimeWithTimezoneOffset(unixTime: number, ut } export function parseDateTimeFromKnownDateTimeFormat(dateTime: string, format: KnownDateTimeFormat): DateTime | undefined { - const m = moment(dateTime, format.format); + const m = moment(dateTime, format.format, true); if (!m.isValid()) { return undefined; @@ -664,7 +664,7 @@ export function parseDateTimeFromKnownDateTimeFormat(dateTime: string, format: K } export function parseDateTimeFromString(dateTime: string, format: string): DateTime | undefined { - const m = moment(dateTime, format); + const m = moment(dateTime, format, true); if (!m.isValid()) { return undefined; diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index beba7a30..eec38c70 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -61,6 +61,7 @@ import { ShortDateFormat, LongTimeFormat, ShortTimeFormat, + DateFormatOrder, DateRange, DateRangeScene, LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE @@ -1760,12 +1761,22 @@ export function useI18n() { return t(`currency.name.${currencyCode}`); } + function getLongDateFormatOrder(): DateFormatOrder { + return getLocalizedDateTimeType(LongDateFormat.all(), LongDateFormat.values(), userStore.currentUserLongDateFormat, 'longDateFormat', LongDateFormat.Default).order; + } + + function getShortDateFormatOrder(): DateFormatOrder { + return getLocalizedDateTimeType(ShortDateFormat.all(), ShortDateFormat.values(), userStore.currentUserShortDateFormat, 'shortDateFormat', ShortDateFormat.Default).order; + } + function isLongDateMonthAfterYear(): boolean { - return getLocalizedDateTimeType(LongDateFormat.all(), LongDateFormat.values(), userStore.currentUserLongDateFormat, 'longDateFormat', LongDateFormat.Default).isMonthAfterYear; + const order: DateFormatOrder = getLongDateFormatOrder(); + return order === DateFormatOrder.YMD; } function isShortDateMonthAfterYear(): boolean { - return getLocalizedDateTimeType(ShortDateFormat.all(), ShortDateFormat.values(), userStore.currentUserShortDateFormat, 'shortDateFormat', ShortDateFormat.Default).isMonthAfterYear; + const order: DateFormatOrder = getShortDateFormatOrder(); + return order === DateFormatOrder.YMD; } function isLongTime24HourFormat(): boolean { @@ -2422,6 +2433,8 @@ export function useI18n() { getCurrentDigitGroupingType, getCurrentFiscalYearFormatType, getCurrencyName, + getLongDateFormatOrder, + getShortDateFormatOrder, isLongDateMonthAfterYear, isShortDateMonthAfterYear, isLongTime24HourFormat, diff --git a/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue b/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue index 6dacff34..99991832 100644 --- a/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue +++ b/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue @@ -230,7 +230,7 @@ import { useI18n } from '@/locales/helpers.ts'; import { type NameValue, type NameNumeralValue, type TypeAndDisplayName, itemAndIndex, entries } from '@/core/base.ts'; import { type NumeralSystem, KnownAmountFormat } from '@/core/numeral.ts'; -import { KnownDateTimeFormat } from '@/core/datetime.ts'; +import { type DateFormatOrder, KnownDateTimeFormat } from '@/core/datetime.ts'; import { KnownDateTimezoneFormat } from '@/core/timezone.ts'; import { TransactionType } from '@/core/transaction.ts'; import { ImportTransactionColumnType, ImportTransactionDataMapping } from '@/core/import_transaction.ts'; @@ -286,6 +286,8 @@ const { tt, ti, getCurrentNumeralSystemType, + getLongDateFormatOrder, + getShortDateFormatOrder, getAllImportTransactionColumnTypes } = useI18n(); @@ -296,6 +298,8 @@ const countPerPage = ref(10); const parsedFileDataColumnMapping = ref(ImportTransactionDataMapping.createEmpty()); const numeralSystem = computed(() => getCurrentNumeralSystemType()); +const longDateFormatOrder = computed(() => getLongDateFormatOrder()); +const shortDateFormatOrder = computed(() => getShortDateFormatOrder()); const allImportTransactionColumnTypes = computed(() => getAllImportTransactionColumnTypes()); const menus = computed(() => [ @@ -402,7 +406,7 @@ const parsedFileLinesTablePageOptions = computed(() => getTa const parsedFileAllTransactionTypes = computed(() => parsedFileDataColumnMapping.value.parseFileAllTransactionTypes(props.parsedFileData)); const parsedFileValidMappedTransactionTypes = computed>(() => parsedFileDataColumnMapping.value.parseFileValidMappedTransactionTypes(props.parsedFileData)); -const parsedFileAutoDetectedTimeFormat = computed(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedTimeFormat(props.parsedFileData)); +const parsedFileAutoDetectedTimeFormat = computed(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedTimeFormat(props.parsedFileData, longDateFormatOrder.value, shortDateFormatOrder.value)); const parsedFileAutoDetectedTimezoneFormat = computed(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedTimezoneFormat(props.parsedFileData)); const parsedFileAutoDetectedAmountFormat = computed(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedAmountFormat(props.parsedFileData));