package com.czlis.interfaceCommon.utils; import cn.hutool.core.date.DateUtil; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 功能完整性 * 覆盖 “出生日期→年龄” 全场景:支持字符串(如 “25 岁”)、整数(如 25)、小数(如 25.5)、Map(如{nl:25, nldw:"岁"})多种返回格式。 * 支持 “年龄→出生日期” 估算:适配数值 + 单位、Map、字符串(如 “5 岁”)多种入参,满足不同调用需求。 * 单位兼容性 * 所有定义的单位(岁、月、个月、天、时、小时)均能被正确解析和处理,无匹配遗漏问题。 * 单位代码(1/2/3/4)与单位名称(岁 / 月 / 天 / 时)通过 getUnitCode/getUnitName 双向转换,逻辑闭环。 * 边界处理 * 出生日期在未来时返回合理默认值(0 岁),避免负数或异常。 * 未过当年生日时年龄减 1、不足 1 天按小时计算等细节逻辑正确。 * 精度控制 * 小数年龄计算用 BigDecimal 控制 4 位小数,无浮点数精度误差。 * 日期解析支持多种格式(yyyy-MM-dd 等),失败时抛明确异常。 * 所有方法调用链路完整(如 getAge→getAgeUnitByBirthDate→getAge),无逻辑断裂。 * 异常提示文案与实际参数(如 nl、nldw)一致,便于调试。 * Calendar 局部使用(非线程安全但局部变量无风险),适配低版本 Java。 */ public class AgeUtils { // 常量:1岁 = 365.25天 = 365.25×24小时 private static final double HOURS_PER_YEAR = 365.25 * 24; // 8766.0 // 常量:时间单位(支持岁、月、天) private static final String UNIT_YEAR = "岁"; private static final String UNIT_MONTH = "月"; private static final String UNIT_MONTH0 = "个月"; private static final String UNIT_DAY = "天"; private static final String UNIT_HOUR= "时"; private static final String UNIT_HOUR0= "小时"; private static final String CODE_YEAR = "1"; private static final String CODE_MONTH = "2"; private static final String CODE_DAY = "3"; private static final String CODE_HOUR= "4"; /** * 根据出生日期字符串(支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss)计算年龄 * @param birthDate 出生日期(Date类型)如 "2000-03-15") * @return 年龄(返回带单位的字符串,如 "25岁"、"3月"、"15天"、"8时") */ public static String getAge(Date birthDate) { if (birthDate == null) { return ""; } Map ageMap = getAgeUnitByBirthDate(birthDate); String unit = (String) ageMap.get("nldw"); Object value = ageMap.get("nl"); return (value == null || unit == null) ? "" : value + unit; } /** * 根据出生日期计算精确年龄 * @param birthStr 出生日期字符串(支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss) * @return 年龄(返回带单位的字符串,如 "25岁"、"3月"、"15天"、"8时") */ public static String getAge(String birthStr) { if (birthStr == null || birthStr.trim().isEmpty()) { return ""; } // 尝试解析日期字符串(复用之前的逻辑) Date birthDate = parseBirthDate(birthStr); if (birthDate == null) { throw new IllegalArgumentException("出生日期格式错误,支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss"); } return getAge(birthDate); } /** * 根据出生日期计算精确年龄,返回Map格式 * @return Map包含两个键: * - "nldw":年龄单位(1、2、3、4) * - "nl":对应的数值(如24、3、15、8) */ public static Map getAgeByBirthDate(Date birthDate) { Map result = new HashMap<>(2); if (birthDate == null) { result.put("nldw", ""); result.put("nl", null); return result; } Map ageMap = getAgeUnitByBirthDate(birthDate); String unit = (String) ageMap.get("nldw"); ageMap.put("nldw", getUnitCode(unit)); return ageMap; } /** * 根据出生日期计算精确年龄,返回Map格式 * @return Map包含两个键: * - "nldw":年龄单位(1、2、3、4) * - "nl":对应的数值(如24、3、15、8) */ public static Map getAgeByBirthDate(String birthStr) { if (birthStr == null || birthStr.trim().isEmpty()) { Map emptyResult = new HashMap<>(2); emptyResult.put("nldw", ""); emptyResult.put("nl", null); return emptyResult; } // 尝试解析日期字符串(复用之前的逻辑) Date birthDate = parseBirthDate(birthStr); if (birthDate == null) { throw new IllegalArgumentException("出生日期格式错误,支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss"); } return getAgeByBirthDate(birthDate); } /** * 根据出生日期(Date)计算年龄(按小时折算,精确到4位小数) * @return 带4位小数的年龄(如1.5=1岁6个月,0.25=3个月) */ public static double getDecimalAge(Date birthDate) { if (birthDate == null) { return 0.0000; } // 1. 计算当前时间与出生日期的毫秒差 long currentTimeMillis = System.currentTimeMillis(); long birthTimeMillis = birthDate.getTime(); long diffMillis = currentTimeMillis - birthTimeMillis; // 2. 出生日期在未来:返回0.0000 if (diffMillis <= 0) { return 0.0000; } // 3. 转换为总小时数(精确到毫秒级) double totalHours = diffMillis / (1000.0 * 60 * 60); // 除以毫秒→秒→分→小时 // 4. 计算年龄(总小时数 ÷ 一年的小时数) double age = totalHours / HOURS_PER_YEAR; // 5. 精确到4位小数(四舍五入) return new BigDecimal(age) .setScale(4, RoundingMode.HALF_UP) .doubleValue(); } /** * 根据出生日期(字符串)计算年龄(按小时折算,精确到4位小数) * @return 带4位小数的年龄(如1.5=1岁6个月,0.25=3个月) */ public static double getDecimalAge(String birthStr) { Date birthDate = parseBirthDate(birthStr); // 复用之前的日期解析方法 if (birthDate == null) { throw new IllegalArgumentException("出生日期格式错误,支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss"); } return getDecimalAge(birthDate); } /** * 根据出生日期(Date类型)计算年龄 * @param birthDate 出生日期(不为null) * @return 年龄(如 25) */ public static int getIntAge(Date birthDate) { if (birthDate == null) { throw new IllegalArgumentException("出生日期不能为空"); } // 1. 获取当前日期和出生日期的日历对象 Calendar currentCal = Calendar.getInstance(); // 当前日期 Calendar birthCal = Calendar.getInstance(); // 出生日期 birthCal.setTime(birthDate); // 2. 计算年份差 int currentYear = currentCal.get(Calendar.YEAR); int birthYear = birthCal.get(Calendar.YEAR); int age = currentYear - birthYear; // 3. 处理未过当年生日的情况(年龄减1) // 比较月份:当前月份 < 出生月份 → 未过生日 int currentMonth = currentCal.get(Calendar.MONTH); int birthMonth = birthCal.get(Calendar.MONTH); if (currentMonth < birthMonth) { age--; } // 月份相同,比较日期:当前日期 < 出生日期 → 未过生日 else if (currentMonth == birthMonth) { int currentDay = currentCal.get(Calendar.DAY_OF_MONTH); int birthDay = birthCal.get(Calendar.DAY_OF_MONTH); if (currentDay < birthDay) { age--; } } // 4. 确保年龄不为负数(出生日期在未来的情况) return Math.max(age, 0); } /** * 根据出生日期字符串计算年龄 * @param birthStr 出生日期字符串(支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss) * @return 年龄(如 25) */ public static int getIntAge(String birthStr) { if (birthStr == null || birthStr.trim().isEmpty()) { throw new IllegalArgumentException("出生日期字符串不能为空"); } Date birthDate = parseBirthDate(birthStr); if (birthDate == null) { throw new IllegalArgumentException("出生日期格式错误,支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss"); } return getIntAge(birthDate); } /** * 核心算法:根据出生日期计算精确年龄,返回Map格式 * @return Map包含两个键: * - "nldw":年龄单位(岁、月、天、小时) * - "nl":对应的数值(如24、3、15、8) */ public static Map getAgeUnitByBirthDate(Date birthDate) { Map result = new HashMap<>(2); if (birthDate == null) { result.put("nldw", ""); result.put("nl", null); return result; } Calendar currentCal = Calendar.getInstance(); Calendar birthCal = Calendar.getInstance(); birthCal.setTime(birthDate); // 1. 计算总毫秒差 long diffMillis = currentCal.getTimeInMillis() - birthCal.getTimeInMillis(); if (diffMillis <= 0) { result.put("nldw", UNIT_YEAR); result.put("nl", 0); return result; } // 2. 按不同时间单位计算 long totalHours = diffMillis / (1000 * 60 * 60); // 总小时数 if (totalHours < 24) { // 不足1天:返回小时 result.put("nldw", UNIT_HOUR); result.put("nl", (int) totalHours); return result; } long totalDays = totalHours / 24; // 总天数 if (totalDays < 30) { // 不足1月:返回天数 result.put("nldw", UNIT_DAY); result.put("nl", (int) totalDays); return result; } // 计算月数差(考虑月份天数不同) int currentYear = currentCal.get(Calendar.YEAR); int currentMonth = currentCal.get(Calendar.MONTH); int currentDay = currentCal.get(Calendar.DAY_OF_MONTH); int birthYear = birthCal.get(Calendar.YEAR); int birthMonth = birthCal.get(Calendar.MONTH); int birthDay = birthCal.get(Calendar.DAY_OF_MONTH); // 计算总月数 int months = (currentYear - birthYear) * 12 + (currentMonth - birthMonth); // 如果当前日期 < 出生日期,月数减1(未满月) if (currentDay < birthDay) { months--; } if (months < 12) { // 不足1岁:返回月数 result.put("nldw", UNIT_MONTH); result.put("nl", months); return result; } // 计算年数(复用之前的逻辑) int years = currentYear - birthYear; if (currentMonth < birthMonth || (currentMonth == birthMonth && currentDay < birthDay)) { years--; } result.put("nldw", UNIT_YEAR); result.put("nl", years); return result; } /** * 根据出生日期计算精确年龄,返回Map格式 * @return Map包含两个键: * - "nldw":年龄单位(岁、月、天、小时) * - "nl":对应的数值(如24、3、15、8) */ public static Map getAgeUnitByBirthDate(String birthStr) { if (birthStr == null || birthStr.trim().isEmpty()) { Map emptyResult = new HashMap<>(2); emptyResult.put("nldw", ""); emptyResult.put("nl", null); return emptyResult; } // 尝试解析日期字符串(复用之前的逻辑) Date birthDate = parseBirthDate(birthStr); if (birthDate == null) { throw new IllegalArgumentException("出生日期格式错误,支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss"); } return getAgeUnitByBirthDate(birthDate); } /** * 根据年龄和年龄单位估算出生日期(入参格式与getAgeMapByBirthDate返回值一致) * @param value 年龄Map(格式:{value: 5, unit: "岁"} 或 {value: 3, unit: "个月"}) * @param unit * @return 估算的出生日期 */ public static Date getBirthDateByAge(int value,String unit) { // 3. 获取当前时间(基于Calendar) Calendar calendar = Calendar.getInstance(); // 当前时间 // 4. 根据单位推算出生日期 switch (unit) { case CODE_YEAR: case UNIT_YEAR: // 岁:>1岁按当年1月1日,≤1岁按实际月数 if (value > 1) { calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) - value); calendar.set(Calendar.MONTH, Calendar.JANUARY); // 1月(0表示1月) calendar.set(Calendar.DAY_OF_MONTH, 1); // 1日 calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); } else { // ≤1岁:按月份推算(1岁=12个月) calendar.add(Calendar.MONTH, -value * 12); } break; case CODE_MONTH: case UNIT_MONTH: case UNIT_MONTH0: // 个月:忽略小时,按整天推算 calendar.add(Calendar.MONTH, -value); // 重置时间为00:00:00 calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); break; case CODE_DAY: case UNIT_DAY: // 天:忽略小时,按整天推算 calendar.add(Calendar.DAY_OF_MONTH, -value); // 重置时间为00:00:00 calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); break; case CODE_HOUR: case UNIT_HOUR: case UNIT_HOUR0: // 小时:保留精确时间 calendar.add(Calendar.HOUR_OF_DAY, -value); break; default: throw new IllegalArgumentException("不支持的单位:" + unit); } // 5. 返回推算的出生日期 return calendar.getTime(); } /** * 单参数估算出生日期(支持"5岁"、"3个月"、"5小时"格式) */ /** * 根据年龄Map估算出生日期(入参格式与getAgeMapByBirthDate返回值一致) * @param ageMap 年龄Map(格式:{value: 5, unit: "岁"} 或 {value: 3, unit: "个月"}) * @return 估算的出生日期 */ public static Date getBirthDateByAgeMap(Map ageMap) { // 1. 校验入参格式(与getAgeMapByBirthDate返回格式一致) validateAgeMap(ageMap); // 2. 提取数值和单位 int value = (Integer) ageMap.get("nl"); String unit = (String) ageMap.get("nldw"); // 5. 返回推算的出生日期 return getBirthDateByAge(value,unit); } /** * 单参数估算出生日期(支持"5岁"、"3月"、"5时"格式) */ public static Date getBirthDateByAge(String ageStr) { // 解析年龄数值和单位(复用之前的逻辑) int value = 0; String unit = ""; try { int unitStartIndex = 0; while (unitStartIndex < ageStr.length() && Character.isDigit(ageStr.charAt(unitStartIndex))) { unitStartIndex++; } value = Integer.parseInt(ageStr.substring(0, unitStartIndex).trim()); unit = ageStr.substring(unitStartIndex).trim(); } catch (Exception e) { throw new IllegalArgumentException("年龄格式错误(示例:5岁、3个月、5小时),输入:" + ageStr); } // 调用核心估算方法 return getBirthDateByAge(value, unit); } /** * 校验年龄Map格式(确保与getAgeMapByBirthDate返回格式一致) */ private static void validateAgeMap(Map ageMap) { if (ageMap == null || ageMap.isEmpty()) { throw new IllegalArgumentException("年龄Map不能为空"); } // 校验必须包含"nl"和"nldw"键 if (!ageMap.containsKey("nl") || !ageMap.containsKey("nldw")) { throw new IllegalArgumentException("年龄Map必须包含'nl'和'nldw'键(格式:{nl: 5, nldw: '岁'})"); } // 校验value为整数 Object valueObj = ageMap.get("nl"); if (!(valueObj instanceof Integer)) { throw new IllegalArgumentException("'nl'必须是整数(格式:{nl: 5, nldw: '岁'})"); } // 校验unit为有效单位 Object unitObj = ageMap.get("nldw"); if (!(unitObj instanceof String) || !(UNIT_YEAR.equals(unitObj) || UNIT_MONTH.equals(unitObj) ||UNIT_MONTH0.equals(unitObj) || UNIT_HOUR0.equals(unitObj) || UNIT_DAY.equals(unitObj) || UNIT_HOUR.equals(unitObj))) { throw new IllegalArgumentException("'unit'必须是'岁'/'月'/'天'/'时'(格式:{nl: 5, nldw: '岁'})"); } } // 私有方法:解析日期字符串(复用逻辑) private static Date parseBirthDate(String birthStr) { if (birthStr == null || birthStr.trim().isEmpty()) { return null; } SimpleDateFormat[] formats = { new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("yyyy/MM/dd"), new SimpleDateFormat("yyyy/MM/dd HH:mm:ss") }; for (SimpleDateFormat format : formats) { try { return format.parse(birthStr); } catch (ParseException ignored) { // 尝试下一个格式 } } throw new IllegalArgumentException("出生日期格式错误,支持:yyyy-MM-dd、yyyy-MM-dd HH:mm:ss"); } // 私有方法:获取年龄单位代码 private static String getUnitCode(String unitName) { String unitCode=""; if(UNIT_YEAR.equals(unitName))unitCode=CODE_YEAR; else if (UNIT_MONTH.equals(unitName)) unitCode=CODE_MONTH; else if (UNIT_MONTH0.equals(unitName)) unitCode=CODE_MONTH; else if (UNIT_DAY.equals(unitName)) unitCode=CODE_DAY; else if (UNIT_HOUR.equals(unitName)) unitCode=CODE_HOUR; else if (UNIT_HOUR0.equals(unitName)) unitCode=CODE_HOUR; return unitCode; } // 私有方法:获取年龄单位代码 private static String getUnitName(String unitCode) { String unitName=""; if(CODE_YEAR.equals(unitCode))unitName=UNIT_YEAR; else if (CODE_MONTH.equals(unitCode)) unitName=UNIT_MONTH; else if (CODE_DAY.equals(unitCode)) unitName=UNIT_DAY; else if (CODE_HOUR.equals(unitCode)) unitName=UNIT_HOUR; return unitName; } /** * “获取年龄及单位,返回 {age: 数值,unit: 单位}” * @param birthday * @return */ public static Map getAgeFromBirthDay(String birthday) { Map map0 =getAgeUnitByBirthDate(birthday); Map map = new HashMap<>(); map.put("age", map0.get("nl") == null ? "" : String.valueOf(map0.get("nl"))); map.put("unit", map0.get("nldw") == null ? "" : map0.get("nldw").toString()); return map; } /** * “获取年龄及单位,返回 {age: 数值,unit: 单位}” * @param birthday * @return */ public static Map getAgeFromBirthDay(Date birthday) { Map map0 =getAgeUnitByBirthDate(birthday); Map map = new HashMap<>(); map.put("age", map0.get("nl") == null ? "" : String.valueOf(map0.get("nl"))); map.put("unit", map0.get("nldw") == null ? "" : map0.get("nldw").toString()); return map; } /** * 通过年龄和单位获取生日 * @param nl * @param nldw * @return */ public static Date getBirthDayFromAge(int nl, String nldw) { return getBirthDateByAge(nl, nldw); } }