index.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // @ts-nocheck
  2. /**
  3. * 日期验证配置选项
  4. */
  5. export type IsDateOptions = {
  6. /** 日期格式字符串,默认 'YYYY/MM/DD' */
  7. format ?: string;
  8. /** 允许的分隔符数组,默认 ['/', '-'] */
  9. delimiters ?: string[];
  10. /** 是否严格匹配格式,默认 false */
  11. strictMode ?: boolean;
  12. }
  13. /**
  14. * 验证日期格式字符串是否合法
  15. * @param format - 需要验证的格式字符串
  16. * @returns 是否合法格式
  17. */
  18. function isValidFormat(format : string) : boolean {
  19. return /(^(y{4}|y{2})[年./-](m{1,2})[月./-](d{1,2}(日)?)$)|(^(m{1,2})[./-](d{1,2})[./-]((y{4}|y{2})$))|(^(d{1,2})[./-](m{1,2})[./-]((y{4}|y{2})$))/i.test(format);
  20. }
  21. /**
  22. * 将日期部分和格式部分组合成键值对数组
  23. * @param date - 分割后的日期部分数组
  24. * @param format - 分割后的格式部分数组
  25. * @returns 组合后的二维数组
  26. */
  27. function zip(date : string[], format : string[]) : string[][] {
  28. const zippedArr : string[][] = [];
  29. const len = Math.max(date.length, format.length);
  30. for (let i = 0; i < len; i++) {
  31. const key = i < date.length ? date[i] : ''
  32. const value = i < format.length ? format[i] : ''
  33. zippedArr.push([key, value])
  34. }
  35. return zippedArr;
  36. }
  37. /** 验证日期对象 */
  38. function validateDateObject(date : Date, strictMode : boolean) : boolean {
  39. // #ifndef APP-ANDROID
  40. return !strictMode && Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
  41. // #endif
  42. // #ifdef APP-ANDROID
  43. return !strictMode && !isNaN(date.getTime())
  44. // #endif
  45. }
  46. function escapeRegExp(str: string): string {
  47. return str//.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  48. }
  49. function enhancedSplit(
  50. str: string,
  51. delimiters: string[]
  52. ): string[] {
  53. // 构建动态分隔符正则表达式
  54. const escapedDelimiters = delimiters.map(d => escapeRegExp(d));
  55. const pattern = new RegExp(
  56. `[${escapedDelimiters.join('')}]+`, // 匹配任意允许的分隔符
  57. 'g'
  58. );
  59. return str.split(pattern).filter(p => p != '');
  60. }
  61. /**
  62. * 验证输入是否为有效日期
  63. * @param input - 输入值,可以是字符串或 Date 对象
  64. * @param options - 配置选项,可以是字符串(简写格式)或配置对象
  65. * @returns 是否为有效日期
  66. *
  67. * @example
  68. * isDate('2023/12/31'); // true
  69. * isDate(new Date()); // true
  70. * isDate('02-29-2023', { strictMode: true }); // false(2023年不是闰年)
  71. */
  72. export function isDate(input : Date, options ?: IsDateOptions) : boolean;
  73. export function isDate(input : string, options ?: string | IsDateOptions) : boolean;
  74. export function isDate(input : string | Date, options : string | IsDateOptions | null = null) : boolean {
  75. // 处理参数重载:允许第二个参数直接传格式字符串
  76. // Date对象验证
  77. let format = 'YYYY/MM/DD'
  78. let delimiters = ['/', '-']
  79. let strictMode = false
  80. if (options != null) {
  81. if (typeof options == 'string') {
  82. format = options as string
  83. } else {
  84. format = (options as IsDateOptions).format ?? format
  85. delimiters = (options as IsDateOptions).delimiters ?? delimiters
  86. strictMode = (options as IsDateOptions).strictMode ?? strictMode
  87. }
  88. }
  89. if (input instanceof Date) {
  90. return validateDateObject(input, strictMode);
  91. }
  92. // 字符串类型验证
  93. if (!isValidFormat(format)) return false;
  94. // 严格模式长度检查
  95. if (strictMode && input.length != format.length) {
  96. return false;
  97. }
  98. // 获取格式中的分隔符
  99. const formatDelimiter = delimiters.find((d) : boolean => format.indexOf(d) != -1);
  100. // 获取实际使用的分隔符
  101. const dateDelimiter = strictMode
  102. ? formatDelimiter
  103. : delimiters.find((d) : boolean => input.indexOf(d) != -1);
  104. // 分割日期和格式
  105. const dateParts = strictMode ? enhancedSplit(input, delimiters) : input.split(dateDelimiter ?? '');
  106. const formatParts = strictMode ? enhancedSplit(format.toLowerCase(), delimiters) : format.toLowerCase().split(formatDelimiter ?? '');
  107. // 组合成键值对
  108. const dateAndFormat = zip(dateParts, formatParts);
  109. const dateObj = new Map<string, string>();
  110. // 解析日期组成部分
  111. for (const [dateWord, formatWord] of dateAndFormat) {
  112. if (dateWord == '' || formatWord == '' || dateWord.length != formatWord.length) {
  113. return false;
  114. }
  115. dateObj.set(formatWord.charAt(0), dateWord)
  116. }
  117. // 年份处理
  118. let fullYear = dateObj.get('y');
  119. if (fullYear == null) return false;
  120. // 检查年份前导负号
  121. if (fullYear.startsWith('-')) return false;
  122. // 两位年份转四位
  123. if (fullYear.length == 2) {
  124. const parsedYear = parseInt(fullYear, 10);
  125. if (isNaN(parsedYear)) {
  126. return false;
  127. }
  128. const currentYear = new Date().getFullYear();
  129. const century = currentYear - (currentYear % 100);
  130. fullYear = (parseInt(fullYear, 10) < (currentYear % 100))
  131. ? `${century + 100 + parseInt(fullYear, 10)}`
  132. : `${century + parseInt(fullYear, 10)}`;
  133. }
  134. // 月份补零
  135. const month = dateObj.get('m')?.padStart(2, '0') ?? '';
  136. // 日期补零
  137. const day = dateObj.get('d')?.padStart(2, '0') ?? '';
  138. const isoDate = `${fullYear}-${month}-${day}T00:00:00.000Z`;
  139. // return new Date(time).getDate() == parseInt(day);
  140. // 构造 ISO 日期字符串验证
  141. try {
  142. // #ifndef APP-ANDROID
  143. const date = new Date(isoDate);
  144. return date.getUTCDate() === parseInt(day, 10) &&
  145. (date.getUTCMonth() + 1) === parseInt(month, 10) &&
  146. date.getUTCFullYear() === parseInt(fullYear, 10);
  147. // #endif
  148. // #ifdef APP-ANDROID
  149. const date = new Date(isoDate);
  150. return date.getDate() == parseInt(day, 10) &&
  151. (date.getMonth() + 1) == parseInt(month, 10) &&
  152. date.getFullYear() == parseInt(fullYear, 10);
  153. // #endif
  154. } catch {
  155. return false;
  156. }
  157. }