512 lines
21 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String, Object> 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<String, Object> getAgeByBirthDate(Date birthDate) {
Map<String, Object> result = new HashMap<>(2);
if (birthDate == null) {
result.put("nldw", "");
result.put("nl", null);
return result;
}
Map<String, Object> 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<String, Object> getAgeByBirthDate(String birthStr) {
if (birthStr == null || birthStr.trim().isEmpty()) {
Map<String, Object> 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<String, Object> getAgeUnitByBirthDate(Date birthDate) {
Map<String, Object> 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<String, Object> getAgeUnitByBirthDate(String birthStr) {
if (birthStr == null || birthStr.trim().isEmpty()) {
Map<String, Object> 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<String, Object> 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<String, Object> 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<String,String> getAgeFromBirthDay(String birthday) {
Map<String, Object> map0 =getAgeUnitByBirthDate(birthday);
Map<String, String> 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<String,String> getAgeFromBirthDay(Date birthday) {
Map<String, Object> map0 =getAgeUnitByBirthDate(birthday);
Map<String, String> 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);
}
}