/// 日期工具:统一 `yyyy-MM-dd` 的格式化与解析,收敛各处重复的 padLeft 逻辑。 library; /// 格式化为 `yyyy-MM-dd`(提交后端的统一格式)。 String formatYmd(DateTime d) => '${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}'; /// 解析 `yyyy-MM-dd`(或更长的带时间字符串,取前 10 位)。无效返回 null。 DateTime? parseYmd(String? s) { if (s == null || s.isEmpty) return null; final t = s.length >= 10 ? s.substring(0, 10) : s; return DateTime.tryParse(t); } /// 组合年/月/日为 `yyyy-MM-dd`,自动把超出当月的「日」clamp 回当月最大值 /// (如 2 月选 31 → 28/29)。任一为空返回 null。 String? composeYmd(int? year, int? month, int? day) { if (year == null || month == null || day == null) return null; final maxDay = DateTime(year, month + 1, 0).day; // 下月第 0 天 = 当月最后一天 final d = day > maxDay ? maxDay : day; return formatYmd(DateTime(year, month, d)); } /// 归一用户键入的日期为 `yyyy-MM-dd`。支持「2024」「2024-5」「2024-5-12」 /// 「20240512」「2024/05/12」等;缺月/日补 01(老酒只知年份,填 2024 即 /// 2024-01-01),超出当月的日 clamp 回当月最大值。无有效年(1900–2200)返回 null。 String? normalizeYmd(String raw) { final s = raw.trim(); if (s.isEmpty) return null; int? y, mo, d; if (RegExp(r'^[0-9]+$').hasMatch(s)) { // 纯数字串:yyyymmdd / yyyymm / yyyy if (s.length >= 8) { y = int.tryParse(s.substring(0, 4)); mo = int.tryParse(s.substring(4, 6)); d = int.tryParse(s.substring(6, 8)); } else if (s.length == 6) { y = int.tryParse(s.substring(0, 4)); mo = int.tryParse(s.substring(4, 6)); } else { y = int.tryParse(s); } } else { final parts = s.split(RegExp(r'[^0-9]+')).where((e) => e.isNotEmpty).toList(); if (parts.isNotEmpty) y = int.tryParse(parts[0]); if (parts.length >= 2) mo = int.tryParse(parts[1]); if (parts.length >= 3) d = int.tryParse(parts[2]); } if (y == null || y < 1900 || y > 2200) return null; mo ??= 1; d ??= 1; if (mo < 1 || mo > 12) return null; final maxDay = DateTime(y, mo + 1, 0).day; if (d < 1) d = 1; if (d > maxDay) d = maxDay; return '${y.toString().padLeft(4, '0')}-' '${mo.toString().padLeft(2, '0')}-' '${d.toString().padLeft(2, '0')}'; }