【实用工具类】NullSafeUtils:一站式解决Java空值安全与通用操作(附完整源码)
在Java开发中,空指针异常(NPE)是最常见的运行时异常之一,同时日期处理、字符串操作、数学运算等通用功能的分散实现也会导致代码冗余、维护成本高。今天给大家分享一款一站式的工具类 NullSafeUtils,它以空值安全为核心设计思想,覆盖空值判断、字符串/集合处理、数学运算、日期时间操作、IP获取等高频场景,帮你告别重复造轮子,提升开发效率。
一、工具类核心设计思想
在开始讲解功能前,先明确NullSafeUtils的核心设计原则,理解这些能更好地掌握工具类的使用逻辑:
- 空值安全优先 :所有方法均做了
null判断,从根源避免NPE; - 语义化命名 :方法名直观易懂(如
isEmpty、getStartOfMonth),见名知意; - 兼容新老API :同时支持老的
Date和Java 8+的LocalDateTime/Instant时间API; - 异常友好处理 :通过
SupplierWithException函数式接口优雅捕获取值异常,返回null而非抛出异常; - 默认值兜底:提供丰富的默认值处理方法,避免空值传递导致的业务异常。
二、基础功能篇:空值判断与默认值处理
这是工具类最基础也最常用的功能,先掌握这部分,能解决80%的空值问题。
2.1 核心空值判断方法
工具类提供了针对「对象、字符串、集合」的空值判断方法,语义清晰,无需重复写null == xxx:
| 方法 | 功能说明 | 使用示例 |
|---|---|---|
isNull(Object obj) |
判断对象是否为null | NullSafeUtils.isNull(user) // true/false |
isNotNull(Object obj) |
判断对象是否非null | NullSafeUtils.isNotNull(user) // 与isNull相反 |
isBlank(String str) |
判断字符串是否为null/空/仅空格 | NullSafeUtils.isBlank(" ") // true |
isNotBlank(String str) |
判断字符串是否非空(非null/非空/非仅空格) | NullSafeUtils.isNotBlank("test") // true |
isEmpty(Collection<T> coll) |
判断集合是否为null/空 | NullSafeUtils.isEmpty(new ArrayList<>()) // true |
isSingleElement(Collection<T> coll) |
判断集合是否仅包含一个元素 | NullSafeUtils.isSingleElement(Arrays.asList(1)) // true |
2.2 默认值兜底方法
当值为null或空时,自动返回默认值,避免空值传递:
java
// 通用对象默认值
String name = NullSafeUtils.requireNonNullElse(null, "未知用户"); // 输出:未知用户
Integer age = NullSafeUtils.getOrDefault(null, 18); // 输出:18
// 字符串默认值(自动去空格)
String desc = NullSafeUtils.getStringOrDefault(" ", "无描述"); // 输出:无描述
// 集合默认值
List<String> list = NullSafeUtils.getListOrDefault(null, Arrays.asList("a", "b")); // 输出:[a,b]
2.3 安全取值(捕获异常)
通过SupplierWithException函数式接口,安全执行可能抛出异常的取值逻辑,异常时返回null:
java
// 正常取值
Integer num1 = NullSafeUtils.getValue(() -> Integer.parseInt("123")); // 输出:123
// 异常时返回null(避免NumberFormatException)
Integer num2 = NullSafeUtils.getValue(() -> Integer.parseInt("abc")); // 输出:null
三、通用操作篇:字符串与文件/集合处理
日常开发中字符串、文件名称、集合的高频操作,工具类都做了空值安全封装。
3.1 字符串操作
(1)基础处理
java
// 去空格(null返回null)
String trimStr = NullSafeUtils.trim(" hello "); // 输出:hello
// 指定位置插入内容(自动修正边界)
String insertStr = NullSafeUtils.insert("abc", "123", 1); // 输出:a123bc
// 拼接多个字符串(过滤null,空格分隔)
String concatStr = NullSafeUtils.concat("hello", null, "world"); // 输出:hello world
(2)集合字符串拼接
java
// List拼接(分隔符为-)
List<String> list = Arrays.asList("a", "b", "c");
String joinList = NullSafeUtils.join(list, "-"); // 输出:a-b-c
// Set拼接(分隔符为,)
Set<String> set = new HashSet<>(Arrays.asList("x", "y"));
String joinSet = NullSafeUtils.join(set, ","); // 输出:x,y
3.2 文件名处理
文件上传/下载场景高频使用,安全获取文件名前缀/后缀:
java
// 剔除文件后缀
String fileName = NullSafeUtils.removeFileSuffix("report.xlsx"); // 输出:report
// 获取文件后缀(空值返回空字符串)
String suffix = NullSafeUtils.fileSuffix("data.tar.gz"); // 输出:gz
String emptySuffix = NullSafeUtils.fileSuffix("无后缀文件"); // 输出:""
四、进阶功能篇:空值安全的数学运算
针对数值类型(Integer/Long/BigDecimal)的运算,解决「null值参与运算」和「BigDecimal精度丢失」问题,尤其适用于财务、统计场景。
4.1 基础加减乘除(空值视为0)
java
// Integer运算(null→0)
Integer add = NullSafeUtils.add(10, null); // 输出:10
Integer sub = NullSafeUtils.subtract(20, 5); // 输出:15
Integer mul = NullSafeUtils.multiply(3, 4); // 输出:12
Integer div = NullSafeUtils.divide(10, 0); // 注意:除数为0返回0,输出:0
// BigDecimal运算(高精度,null→0)
BigDecimal bdAdd = NullSafeUtils.add(new BigDecimal("100.5"), new BigDecimal("200.3")); // 300.8
BigDecimal bdDiv = NullSafeUtils.divide(new BigDecimal("10"), new BigDecimal("3"), 2); // 3.33(四舍五入)
4.2 批量求和
java
// Integer集合求和(过滤null)
List<Integer> intList = Arrays.asList(1, 2, null, 3);
Integer sumInt = NullSafeUtils.sumInt(intList); // 输出:6
// BigDecimal集合求和
List<BigDecimal> bdList = Arrays.asList(new BigDecimal("1.1"), new BigDecimal("2.2"));
BigDecimal sumBd = NullSafeUtils.sumBigDecimal(bdList); // 输出:3.3
4.3 BigDecimal高精度处理
解决BigDecimal精度丢失、舍入模式问题:
java
// 保留2位小数(四舍五入,null→0.00)
BigDecimal scale1 = NullSafeUtils.scale(new BigDecimal("2.345")); // 输出:2.35
// 强制保留小数(仅补零,超出抛异常)
BigDecimal scale2 = NullSafeUtils.forceScaleWithZero(new BigDecimal("2.3"), 2); // 输出:2.30
// 转换为整数(向上取整)
BigDecimal integer = NullSafeUtils.toInteger(new BigDecimal("2.1")); // 输出:3
五、高级功能篇:日期时间处理(重点)
日期时间是开发中的痛点,工具类覆盖了「格式化、时间边界、日期计算、周期判断」等全场景,且兼容Date和Java 8时间API。
5.1 基础格式化
java
// 格式化Date为yyyy-MM-dd(null返回null)
Date now = new Date();
String dateStr = NullSafeUtils.formatDate(now); // 输出:2025-12-10
// 格式化为yyyy-MM-dd HH:mm:ss
String dateTimeStr = NullSafeUtils.formatDateTime(now); // 输出:2025-12-10 15:30:00
5.2 日期加减
java
// 加1天(null返回null)
Date nextDay = NullSafeUtils.addDay(now, 1); // 输出:2025-12-11
// 减1天
Date lastDay = NullSafeUtils.subDay(now, 1); // 输出:2025-12-09
5.3 时间边界获取(核心高频)
工具类提供了「当天/昨天/本周/本月/上月/去年」等时间的开始/结束边界,适用于报表、统计场景:
| 方法 | 功能说明 | 使用示例 |
|---|---|---|
getStartOfDay(null) |
获取当前天00:00:00的Date | 统计今日数据的开始时间 |
getEndOfDay(null) |
获取当前天23:59:59的Date | 统计今日数据的结束时间 |
getStartOfMonth(null) |
获取当月1号00:00:00的Date | 统计本月数据的开始时间 |
getEndOfMonth(null) |
获取当月最后一天23:59:59的Date | 统计本月数据的结束时间 |
getStartOfLastMonth(null) |
获取上月1号00:00:00的Date | 统计上月数据的开始时间 |
getStartOfSameMonthLastYear(null) |
获取去年同月1号00:00:00的Date | 同比去年同期数据 |
示例代码:
java
// 获取当月开始时间
Date startOfMonth = NullSafeUtils.getStartOfMonth(null); // 2025-12-01 00:00:00
// 获取去年上月最后一天结束时间
Date endOfLastYearLastMonth = NullSafeUtils.getEndOfSameMonthLastYear(null); // 2024-11-30 23:59:59.999
5.4 日期计算
java
// 计算两个日期的天数差(有符号)
Date start = NullSafeUtils.getStartOfMonth(null);
Date end = new Date();
long days = NullSafeUtils.calculateDaysBetween(start, end); // 输出:9(假设当前是12月10日)
// 获取当月总天数
int daysOfMonth = NullSafeUtils.getDaysOfCurrentMonth(null); // 输出:31(12月)
// 判断两个日期是否同月
boolean sameMonth = NullSafeUtils.isSameMonth(start, end); // 输出:true
5.5 时间维度判断
java
// 获取星期几(1=周一,7=周日)
int weekDay = NullSafeUtils.getWeekDay(null); // 输出:3(假设当前是周三)
// 判断是否为上午
boolean isMorning = NullSafeUtils.isMorning(null); // 输出:false(假设当前是15点)
六、特殊功能篇:获取客户端真实IP
在Web开发中,获取客户端真实IP是常见需求(如日志、风控),工具类处理了代理、反向代理场景的IP获取:
java
// 在Controller中获取真实IP
@RequestMapping("/getIp")
public String getIp(HttpServletRequest request) {
String realIp = NullSafeUtils.getRealClientIp(request);
return "真实IP:" + realIp;
}
实现逻辑:
- 优先获取
X-Real-IP(代理直接设置的真实IP); - 其次获取
X-Forwarded-For(处理多代理场景,取第一个非unknown的IP); - 最后获取
request.getRemoteAddr()(无代理时的IP); - 转换IPv6本地回环地址为
127.0.0.1。
七、最佳实践与注意事项
7.1 接入建议
- 将工具类放入项目的
util包下,全局复用; - 结合Lombok等工具,进一步简化代码;
- 对于分布式项目,可封装为common模块,供多服务依赖。
7.2 注意事项
- SimpleDateFormat线程安全问题 :工具类中定义的
yyyyMMdd/yyyyMMddHHmmss是静态变量,非线程安全!建议在方法内创建SimpleDateFormat实例,或使用ThreadLocal封装; - BigDecimal舍入模式 :财务场景建议明确指定舍入模式(如
RoundingMode.HALF_UP),避免精度问题; - IP获取的安全性 :需配置代理服务器过滤伪造的
X-Forwarded-For头,防止IP伪造; - 日期边界的纳秒处理 :部分方法(如
getEndOfNextMonth)处理了纳秒,需注意与数据库时间类型的兼容。
7.3 扩展建议
可根据业务需求扩展:
- 增加Map的空值安全操作(如
getMapValueOrDefault); - 扩展更多时间周期(如季度、半年)的边界获取;
- 增加JSON解析的空值安全方法。
八、完整源码
java
import org.apache.commons.lang3.time.DateUtils;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;
// 空值安全工具类
public class NullSafeUtils {
/**
* 带异常的Supplier函数式接口(用于getValue方法)
*
* @param <T> 返回值类型
*/
@FunctionalInterface
public interface SupplierWithException<T> {
/**
* 获取值,允许抛出异常
*
* @return 结果值
* @throws Exception 执行异常
*/
T get() throws Exception;
}
/**
* 安全获取值(捕获所有异常,异常时返回null)
*
* @param supplier 带异常的取值逻辑
* @param <T> 返回值类型
* @return 取值结果或null
* @example
* 输入:supplier=() -> Integer.parseInt("123") → 输出:123
* 输入:supplier=() -> Integer.parseInt("abc") → 输出:null(捕获NumberFormatException)
*/
public static <T> T getValue(SupplierWithException<T> supplier) {
try {
return supplier.get();
} catch (Exception e) {
return null;
}
}
/**
* 获取非空对象,若原对象为null则返回默认值
*
* @param obj 原对象,可为null
* @param defaultObj 默认值,可为null
* @param <T> 对象类型
* @return 原对象(非空)或默认值
* @example 输入:obj="test", defaultObj="default" → 输出:"test"
* 输入:obj=null, defaultObj="default" → 输出:"default"
*/
public static <T> T requireNonNullElse(T obj, T defaultObj) {
if (obj != null) {
return obj;
} else {
return defaultObj;
}
}
/**
* 安全获取集合大小(null返回0)
*
* @param collection 集合,可为null
* @param <T> 集合元素类型
* @return 集合大小或0
* @example
* 输入:collection=[1,2,3] → 输出:3
* 输入:collection=null → 输出:0
*/
public static <T> int size(Collection<T> collection) {
return collection == null ? 0 : collection.size();
}
// region 文件名称处理
/**
* 剔除文件名的后缀名
*
* @param fileName 文件名,可为null/空字符串(直接返回)
* @return 剔除后缀名的文件名
* @example 输入:fileName="a.txt" → 输出:"a"
*/
public static String removeFileSuffix(String fileName) {
// 处理空值或空字符串
if (fileName == null || fileName.isEmpty()) {
return fileName;
}
// 查找最后一个点的位置(兼容多级扩展名如.tar.gz)
int lastDotIndex = fileName.lastIndexOf('.');
// 处理无后缀或以点结尾的情况(如"no_extension"或"invalid.")
if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
return fileName;
}
// 截取最后一个点之前的内容作为基础名称
return fileName.substring(0, lastDotIndex);
}
/**
* 文件名获取后缀名
*
* @param fileName 文件名 例如:"a.txt"
* @return "txt"
*/
public static String fileSuffix(String fileName) {
// 处理空指针和空字符串
if (fileName == null || fileName.isEmpty()) {
return "";
}
// 查找最后一个点的位置
int lastDotIndex = fileName.lastIndexOf('.');
// 处理无后缀名或以点结尾的情况
if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
return "";
}
// 截取最后一个点之后的字符串作为后缀名
return fileName.substring(lastDotIndex + 1);
}
// endregion
// region IP地址
/**
* 获取客户端真实IP
*
* @param request HttpServletRequest对象,不可为null
* @return
*/
public static String getRealClientIp(HttpServletRequest request) {
// 1. 优先检查X-Real-IP(通常由代理直接设置为真实客户端IP,无多层代理信息)
String ip = request.getHeader("X-Real-IP");
if (isUnknown(ip)) {
// 2. 检查X-Forwarded-For(需确保代理已过滤伪造头)
ip = request.getHeader("X-Forwarded-For");
}
if (isUnknown(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (isUnknown(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
// 3. 无代理时获取远程地址
if (isUnknown(ip)) {
ip = request.getRemoteAddr();
}
// 4. 处理X-Forwarded-For多IP场景(取第一个非unknown的IP)
if (ip != null && ip.contains(",")) {
String[] ips = ip.split(",");
for (String segment : ips) {
String trimmedIp = segment.trim();
if (!isUnknown(trimmedIp)) {
ip = trimmedIp;
break;
}
}
}
// 5. 处理本地回环地址(IPv6转IPv4)
if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
ip = "127.0.0.1";
}
return ip;
}
// 工具方法:判断IP是否为unknown或空
private static boolean isUnknown(String ip) {
return ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip);
}
// endregion
// region 字符串处理
/**
* 在指定字符串的指定索引位置插入内容(空值安全)
*
* @param str 原字符串,可为null(返回null)
* @param content 插入的内容,可为null(返回原字符串)
* @param index 插入位置,自动修正边界:<0则取0,>str.length()则取str.length()
* @return 插入后的字符串
* @example 输入:str="abc", content="123", index=1 → 输出:"a123bc"
* 输入:str="abc", content=null, index=1 → 输出:"abc"
* 输入:str="abc", content="123", index=-1 → 输出:"123abc"
*/
public static String insert(String str, String content, int index) {
if (str == null) {
return null;
}
if (content == null) {
return str;
}
// 检查索引边界,保持在 [0, str.length()] 范围内
if (index < 0) {
index = 0;
} else if (index > str.length()) {
index = str.length();
}
// 使用 StringBuilder 提高效率并插入内容
StringBuilder sb = new StringBuilder(str);
sb.insert(index, content);
// 返回插入后的字符串
return sb.toString();
}
/**
* 拼接多个字符串(过滤null值,用空格分隔)
*
* @param strArray 字符串数组,可为null/包含null元素
* @return 拼接后的字符串(空数组/全null返回空字符串)
* @example 输入:strArray={"hello", null, "world", ""} → 输出:"hello world "
* 输入:strArray=null → 输出:""
*/
public static String concat(String... strArray) {
return Arrays.stream(strArray).filter(Objects::nonNull).collect(Collectors.joining(" "));
}
/**
* 字符串去空格(空值安全,null返回null)
*
* @param str 原字符串,可为null
* @return 去空格后的字符串或null
* @example 输入:str=" test " → 输出:"test"
* 输入:str=null → 输出:null
* 输入:str=" " → 输出:""
*/
public static String trim(String str) {
if (isNotEmpty(str)) {
return str.trim();
}
return str;
}
/**
* 获取非空字符串(空/空白字符串返回null,否则去空格)
*
* @param str 原字符串,可为null
* @return 非空字符串或null
* @example 输入:str=" test " → 输出:"test"
* 输入:str=" " → 输出:null
* 输入:str=null → 输出:null
*/
public static String getNotEmpty(String str) {
if (isEmpty(str)) {
return null;
}
return trim(str);
}
/**
* 获取字符串(空/空白字符串返回默认值,否则去空格)
*
* @param str 原字符串,可为null
* @param defaultStr 默认值,可为null
* @return 去空格后的字符串或默认值
* @example 输入:str=" test ", defaultStr="default" → 输出:"test"
* 输入:str=" ", defaultStr="default" → 输出:"default"
*/
public static String get(String str, String defaultStr) {
if (isEmpty(str)) {
return defaultStr;
}
return trim(str);
}
/**
* 获取字符串(空/空白字符串返回空字符串,否则去空格)
*
* @param str 原字符串,可为null
* @return 去空格后的字符串或空字符串
* @example 输入:str=" test " → 输出:"test"
* 输入:str=null → 输出:""
*/
public static String get(String str) {
if (isEmpty(str)) {
return "";
}
return trim(str);
}
/**
* 拼接Set集合中的字符串(空值安全)
*
* @param arrays 字符串Set,可为null/空(返回null)
* @param s 分隔符,不可为null(建议非空)
* @return 拼接后的字符串或null
* @example 输入:arrays={"a","b","c"}, s="," → 输出:"a,b,c"
* 输入:arrays=null, s="," → 输出:null
*/
public static String join(Set<String> arrays, String s) {
if (isNotEmpty(arrays)) {
return String.join(s, arrays);
}
return null;
}
/**
* 拼接List集合中的字符串(空值安全)
*
* @param arrays 字符串List,可为null/空(返回null)
* @param s 分隔符,不可为null(建议非空)
* @return 拼接后的字符串或null
* @example 输入:arrays=["a","b","c"], s="-" → 输出:"a-b-c"
* 输入:arrays=new ArrayList<>(), s="-" → 输出:null
*/
public static String join(List<String> arrays, String s) {
if (isNotEmpty(arrays)) {
return String.join(s, arrays);
}
return null;
}
// endregion
// region 基础判断
public static boolean isNull(Object obj) {
return obj == null;
}
public static boolean isNotNull(Object obj) {
return !isNull(obj);
}
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* 判断集合是否为空
*/
public static <T> boolean isEmpty(Collection<T> collection) {
return collection == null || collection.isEmpty();
}
public static <T> boolean isNotEmpty(Collection<T> collection) {
return !isEmpty(collection);
}
/**
* 判断字符串是否为空
*
* @param str 字符串
* @return true/false
*/
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
/**
* 比较两个BigDecimal值是否相等(空值安全,null则返回false)
*
* @param a 第一个BigDecimal值,可为null
* @param b 第二个BigDecimal值,可为null
* @return 相等返回true,否则false
* @example 输入:a=new BigDecimal("1.0"), b=new BigDecimal("1") → 输出:true
* 输入:a=new BigDecimal("2"), b=null → 输出:false
*/
public static boolean equalsValue(BigDecimal a, BigDecimal b) {
if (a == null || b == null) {
return false;
}
return a.compareTo(b) == 0;
}
/**
* 比较两个对象值是否相等(空值安全,null则返回false)
*
* @param a 第个对象值,可为null
* @param b 第二个对象值,可为null
* @return 相等返回true,否则false
* @example 输入:a=1.0, b=1 → 输出:true
* 输入:a=2, b=null → 输出:false
*/
public static boolean equalsValue(Object a, Object b) {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (!(a instanceof Comparable) || !(b instanceof Comparable)) {
throw new IllegalArgumentException("参数异常");
}
BigDecimal numA = toBigDecimal((Comparable<?>) a);
BigDecimal numB = toBigDecimal((Comparable<?>) b);
return numA.stripTrailingZeros().compareTo(numB.stripTrailingZeros()) == 0;
}
private static BigDecimal toBigDecimal(Comparable<?> value) {
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
try {
return new BigDecimal(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("无法转换为BigDecimal: " + value, e);
}
}
/**
* 判断集合是否包含且仅包含一个元素
*
* @param collection 集合,可为null
* @param <T> 集合元素类型
* @return 仅一个元素返回true,否则false
* @example 输入:collection=[1] → 输出:true
* 输入:collection=[1,2] → 输出:false
* 输入:collection=null → 输出:false
*/
public static <T> boolean isSingleElement(Collection<T> collection) {
return collection != null && collection.size() == 1;
}
// endregion
// region 默认值处理
/**
* 获取对象,若为null则返回默认值
*
* @param value 原对象,可为null
* @param defaultValue 默认值,可为null
* @param <T> 对象类型
* @return 原对象或默认值
* @example 输入:value=123, defaultValue=456 → 输出:123
* 输入:value=null, defaultValue=456 → 输出:456
*/
public static <T> T getOrDefault(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
/**
* 获取字符串,若为空则返回默认值(空定义:null/空字符串/仅空格)
*
* @param value 原字符串,可为null
* @param defaultValue 默认值,可为null
* @return 非空字符串或默认值
* @example 输入:value=" test ", defaultValue="default" → 输出:" test "
* 输入:value=" ", defaultValue="default" → 输出:"default"
*/
public static String getStringOrDefault(String value, String defaultValue) {
return isNotEmpty(value) ? value : defaultValue;
}
/**
* 获取List集合,若为空则返回默认集合(空定义:null/空集合)
*
* @param list 原List,可为null
* @param defaultList 默认List,可为null
* @param <T> 集合元素类型
* @return 非空List或默认List
* @example 输入:list=[1,2], defaultList=[3,4] → 输出:[1,2]
* 输入:list=null, defaultList=[3,4] → 输出:[3,4]
*/
public static <T> List<T> getListOrDefault(List<T> list, List<T> defaultList) {
return list != null && !list.isEmpty() ? list : defaultList;
}
// endregion
// region 数学运算
private static int toSafeInt(Integer value) {
return value != null ? value : 0;
}
private static long toSafeLong(Long value) {
return value != null ? value : 0L;
}
private static BigDecimal toSafeDecimal(BigDecimal value) {
return value != null ? value : BigDecimal.ZERO;
}
// 加法
public static Integer add(Integer a, Integer b) {
return toSafeInt(a) + toSafeInt(b);
}
public static Long add(Long a, Long b) {
return toSafeLong(a) + toSafeLong(b);
}
public static BigDecimal add(BigDecimal a, BigDecimal b) {
return toSafeDecimal(a).add(toSafeDecimal(b));
}
public static BigDecimal addAll(BigDecimal... values) {
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal val : values) {
sum = sum.add(toSafeDecimal(val));
}
return sum;
}
public static Integer addAll(Integer... values) {
if (values == null) {
return null;
}
return Arrays.stream(values).filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
}
// 减法
public static Integer subtract(Integer a, Integer b) {
return toSafeInt(a) - toSafeInt(b);
}
public static Long subtract(Long a, Long b) {
return toSafeLong(a) - toSafeLong(b);
}
public static BigDecimal subtract(BigDecimal a, BigDecimal b) {
return toSafeDecimal(a).subtract(toSafeDecimal(b));
}
// 乘法
public static Integer multiply(Integer a, Integer b) {
return toSafeInt(a) * toSafeInt(b);
}
public static Long multiply(Long a, Long b) {
return toSafeLong(a) * toSafeLong(b);
}
public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
return toSafeDecimal(a).multiply(toSafeDecimal(b));
}
// 除法
public static Integer divide(Integer a, Integer b) {
int divisor = toSafeInt(b);
return divisor == 0 ? 0 : toSafeInt(a) / divisor;
}
public static Long divide(Long a, Long b) {
long divisor = toSafeLong(b);
return divisor == 0 ? 0L : toSafeLong(a) / divisor;
}
public static BigDecimal divideToBigDecimal(Integer a, Integer b) {
return divideToBigDecimal(a, b, 2);
}
public static BigDecimal divideToBigDecimal(Integer a, Integer b, int scale) {
BigDecimal dividend = BigDecimal.valueOf(toSafeInt(a));
BigDecimal divisor = BigDecimal.valueOf(toSafeInt(b));
return divide(dividend, divisor, scale, RoundingMode.HALF_UP);
}
public static BigDecimal divideToBigDecimal(Long a, Long b) {
return divideToBigDecimal(a, b, 2);
}
public static BigDecimal divideToBigDecimal(Long a, Long b, int scale) {
BigDecimal dividend = BigDecimal.valueOf(toSafeLong(a));
BigDecimal divisor = BigDecimal.valueOf(toSafeLong(b));
return divide(dividend, divisor, scale, RoundingMode.HALF_UP);
}
public static BigDecimal divide(BigDecimal a, BigDecimal b, int scale, RoundingMode roundingMode) {
if (scale < 0) {
throw new IllegalArgumentException("小数位数scale不能为负数");
}
BigDecimal divisor = toSafeDecimal(b);
return BigDecimal.ZERO.equals(divisor)
? BigDecimal.ZERO
: toSafeDecimal(a).divide(divisor, scale, roundingMode);
}
public static BigDecimal divide(BigDecimal a, BigDecimal b, int scale) {
return divide(a, b, scale, RoundingMode.HALF_UP);
}
public static BigDecimal divide(BigDecimal a, BigDecimal b) {
return divide(a, b, 2, RoundingMode.HALF_UP);
}
public static Integer sumInt(Collection<Integer> numbers) {
return (numbers == null || numbers.isEmpty())
? 0
: numbers.stream()
.filter(Objects::nonNull) // 过滤null元素
.mapToInt(Integer::intValue) // 转换为int流(避免自动装箱开销)
.sum(); // 原生int求和,性能更优
}
public static Long sumLong(Collection<Long> numbers) {
return (numbers == null || numbers.isEmpty())
? 0L
: numbers.stream()
.filter(Objects::nonNull) // 过滤null元素
.mapToLong(Long::longValue) // 转换为int流(避免自动装箱开销)
.sum(); // 原生long求和,性能更优
}
public static BigDecimal sumBigDecimal(Collection<BigDecimal> numbers) {
return (numbers == null || numbers.isEmpty())
? BigDecimal.ZERO
: numbers.stream()
.filter(Objects::nonNull) // 过滤null元素
.reduce(BigDecimal.ZERO, BigDecimal::add); // 从0开始累加
}
/**
* 设置BigDecimal小数位数(指定舍入模式,空值安全)
*
* @param value 原数,可为null(视为0)
* @param scale 目标小数位数(≥0)
* @param roundingMode 舍入模式,不可为null
* @return 格式化后的BigDecimal
* @throws IllegalArgumentException scale为负数或roundingMode为null时抛出
* @example 输入:value=2.345, scale=2, roundingMode=RoundingMode.HALF_UP → 输出:2.35
* 输入:value=null, scale=2, roundingMode=RoundingMode.HALF_UP → 输出:0.00
*/
public static BigDecimal scale(BigDecimal value, int scale, RoundingMode roundingMode) {
if (scale < 0) {
throw new IllegalArgumentException("目标小数位数(scale)不能为负数: " + scale);
}
if (roundingMode == null) {
throw new IllegalArgumentException("舍入模式(roundingMode)不能为null");
}
// 处理null值,转为0后再处理精度
BigDecimal safeValue = value != null ? value : BigDecimal.ZERO;
return safeValue.setScale(scale, roundingMode);
}
/**
* 设置BigDecimal小数位数(默认四舍五入,空值安全)
*
* @param value 原数,可为null(视为0)
* @param scale 目标小数位数(≥0)
* @return 格式化后的BigDecimal
* @throws IllegalArgumentException scale为负数时抛出
* @example 输入:value=2.345, scale=2 → 输出:2.35
*/
public static BigDecimal scale(BigDecimal value, int scale) {
return scale(value, scale, RoundingMode.HALF_UP);
}
/**
* 设置BigDecimal小数位数(默认保留2位,四舍五入,空值安全)
*
* @param value 原数,可为null(视为0)
* @return 格式化后的BigDecimal
* @example 输入:value=2.345 → 输出:2.35
* 输入:value=null → 输出:0.00
*/
public static BigDecimal scale(BigDecimal value) {
return scale(value, 2, RoundingMode.HALF_UP);
}
/**
* 保留BigDecimal为整数(四舍五入,空值安全)
*
* @param value 原数,可为null(视为0)
* @return 整数BigDecimal
* @example 输入:value=2.6 → 输出:3
* 输入:value=2.3 → 输出:2
*/
public static BigDecimal toIntegerScale(BigDecimal value) {
return scale(value, 0);
}
/**
* 转换BigDecimal为double(保留2位小数,向上取整,空值安全)
*
* @param value 原数,可为null(视为0)
* @return 格式化后的double值
* @example 输入:value=2.341 → 输出:2.35
*/
public static double toDoubleScale(BigDecimal value) {
BigDecimal scale = scale(value, 2, RoundingMode.CEILING);
return scale.doubleValue();
}
/**
* 转换BigDecimal为整数(向上取整,空值安全)
*
* @param value 原数,可为null(视为0)
* @return 整数BigDecimal
* @example 输入:value=2.1 → 输出:3
*/
public static BigDecimal toInteger(BigDecimal value) {
return scale(value, 0, RoundingMode.CEILING);
}
/**
* 强制设置BigDecimal小数位数(不允许舍入,仅补零,空值安全)
* <p>若原数小数位数超过scale,抛ArithmeticException</p>
*
* @param value 原数,可为null(视为0)
* @param scale 目标小数位数(≥0)
* @return 格式化后的BigDecimal
* @throws IllegalArgumentException scale为负数时抛出
* @throws ArithmeticException 原数小数位数超过scale时抛出
* @example 输入:value=2.3, scale=2 → 输出:2.30
* 输入:value=2.345, scale=2 → 抛ArithmeticException
*/
public static BigDecimal forceScaleWithZero(BigDecimal value, int scale) {
return scale(value, scale, RoundingMode.UNNECESSARY);
}
// endregion
// region 时间日期
public static SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyy-MM-dd");
public static SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 格式化日期为yyyy-MM-dd(空值安全,null返回null)
*
* @param date 日期,可为null
* @return 格式化后的字符串或null
* @example 输入:date=2025-12-10 → 输出:"2025-12-10"
* 输入:date=null → 输出:null
*/
public static String formatDate(Date date) {
if (date == null) {
return null;
}
return yyyyMMdd.format(date);
}
/**
* 格式化日期时间为yyyy-MM-dd HH:mm:ss(空值安全,null返回null)
*
* @param date 日期,可为null
* @return 格式化后的字符串或null
* @example 输入:date=2025-12-10 14:30:20 → 输出:"2025-12-10 14:30:20"
*/
public static String formatDateTime(Date date) {
if (date == null) {
return null;
}
return yyyyMMddHHmmss.format(date);
}
// region 日期加减
/**
* 日期加指定天数(空值安全,null返回null)
*
* @param date 原日期,可为null
* @param addDay 要加的天数(可负数,等效减天数)
* @return 加天数后的日期或null
* @example 输入:date=2025-12-10, addDay=1 → 输出:2025-12-11
* 输入:date=2025-12-10, addDay=-1 → 输出:2025-12-09
*/
public static Date addDay(Date date, int addDay) {
return DateUtils.addDays(date, addDay);
}
/**
* 日期减指定天数(空值安全,null返回null)
*
* @param date 原日期,可为null
* @param addDay 要减的天数(正数)
* @return 减天数后的日期或null
* @example 输入:date=2025-12-10, addDay=1 → 输出:2025-12-09
*/
public static Date subDay(Date date, int addDay) {
return DateUtils.addDays(date, -addDay);
}
// endregion
// region 昨天
/**
* 获取指定时间戳前一天的00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 前一天0点的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳(1760106620000) → 输出:2025-12-09 00:00:00的时间戳(1759996800000)
* 输入:timestamp=null → 输出:当前时间前一天0点的时间戳
*/
public static Long getStartOfYesterday_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusDays(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳前一天的00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 前一天0点的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-09 00:00:00
* 输入:timestamp=null → 输出:当前时间前一天0点的Date对象
*/
public static Date getStartOfYesterday(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusDays(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳前一天的23:59:59时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 前一天23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-09 23:59:59的时间戳(1760083199000)
*/
public static Long getEndOfYesterday_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusDays(1)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳前一天的23:59:59 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 前一天23点59分59秒的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-09 23:59:59
*/
public static Date getEndOfYesterday(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusDays(1)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 当天
/**
* 获取指定时间戳当天的00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当天0点的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-10 00:00:00的时间戳(1760083200000)
*/
public static Long getStartOfDay_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳当天的00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当天0点的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-10 00:00:00
*/
public static Date getStartOfDay(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳当天的23:59:59时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当天23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-10 23:59:59的时间戳(1760169599000)
*/
public static Long getEndOfDay_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳当天的23:59:59 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当天23点59分59秒的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-10 23:59:59
*/
public static Date getEndOfDay(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 本周
/**
* 获取指定时间戳所在周的周一00:00:00时间戳(毫秒)
* <p>周定义:周一至周日为一周</p>
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 本周一0点的时间戳
* @example 输入:timestamp=2025-12-10(周三)的时间戳 → 输出:2025-12-08(周一)00:00:00的时间戳
* 输入:timestamp=2025-12-08(周一)的时间戳 → 输出:2025-12-08 00:00:00的时间戳
*/
public static Long getStartOfWeek_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(DayOfWeek.MONDAY)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
public static Date getStartOfWeek(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(DayOfWeek.MONDAY)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳所在周的周日23:59:59时间戳(毫秒)
* <p>周定义:周一至周日为一周</p>
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 本周日23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10(周三)的时间戳 → 输出:2025-12-14(周日)23:59:59的时间戳
* 输入:timestamp=2025-12-14(周日)的时间戳 → 输出:2025-12-14 23:59:59的时间戳
*/
public static Long getEndOfWeek_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(DayOfWeek.SUNDAY)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
public static Date getEndOfWeek(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(DayOfWeek.SUNDAY)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 本月
/**
* 获取指定时间戳所在月的1号00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当月1号0点的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-01 00:00:00的时间戳(1759238400000)
*/
public static Long getStartOfMonth_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在月的1号00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当月1号0点的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2025-12-01 00:00:00
*/
public static Date getStartOfMonth(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳所在月的最后一天23:59:59时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当月最后一天23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-12-31 23:59:59的时间戳(1761916799000)
* 输入:timestamp=2025-02-10的时间戳 → 输出:2025-02-28 23:59:59的时间戳
*/
public static Long getEndOfMonth_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.lastDayOfMonth())
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在月的最后一天23:59:59 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当月最后一天23点59分59秒的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-12-31 23:59:59
*/
public static Date getEndOfMonth(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.lastDayOfMonth())
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 上月
/**
* 获取指定时间戳所在月的上月1号00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 上月1号0点的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-11-01 00:00:00的时间戳(1756732800000)
*/
public static Long getStartOfLastMonth_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.minusMonths(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在月的上月1号00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 上月1号0点的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-11-01 00:00:00
*/
public static Date getStartOfLastMonth(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return
Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.minusMonths(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳所在月的上月最后一天23:59:59时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 上月最后一天23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-11-30 23:59:59的时间戳(1759238399000)
*/
public static Long getLastMonthEndOfMonth_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.lastDayOfMonth())
.minusMonths(1)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在月的上月最后一天23:59:59 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 上月最后一天23点59分59秒的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-11-30 23:59:59
*/
public static Date getLastMonthEndOfMonth(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.lastDayOfMonth())
.minusMonths(1)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 下月
/**
* 获取指定时间戳所在月的下月1号00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 下月1号0点的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2026-01-01 00:00:00的时间戳(1761916800000)
*/
public static Long getStartOfNextMonth_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.plusMonths(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在月的下月1号00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 下月1号0点的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2026-01-01 00:00:00
*/
public static Date getStartOfNextMonth(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return
Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.plusMonths(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳所在月的下月最后一天23:59:59.999时间戳(毫秒)
* <p>逻辑:当月第一天+2个月-1天 = 下月最后一天</p>
*
* @param timestamp 指定的时间戳(毫秒),可为null(默认当前时间)
* @return 下月最后一天23点59分59秒999纳秒的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2026-01-31 23:59:59.999的时间戳
* 输入:timestamp=2025-01-10的时间戳 → 输出:2025-02-28 23:59:59.999的时间戳
*/
public static Long getEndOfNextMonth_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.plusMonths(2) // 下个月的第一天再加上一个月
.minusDays(1) // 减一天,即下个月的最后一天
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(999_999_999)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在月的下月最后一天23:59:59.999 Date对象
*
* @param timestamp 指定的时间戳(毫秒),可为null(默认当前时间)
* @return 下月最后一天23点59分59秒999纳秒的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2026-01-31 23:59:59.999
*/
public static Date getEndOfNextMonth(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.plusMonths(2) // 下个月的第一天再加上一个月
.minusDays(1) // 减一天,即下个月的最后一天
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(999_999_999)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 当年
/**
* 获取指定时间戳所在年的1月1日00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当年第一天0点的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-01-01 00:00:00的时间戳(1735689600000)
*/
public static Long getStartOfYear_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfYear()) // 调整为年的第一天
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在年的1月1日00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当年第一天0点的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-01-01 00:00:00
*/
public static Date getStartOfYear(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return
Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfYear()) // 调整为年的第一天
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳所在年的12月31日23:59:59时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当年最后一天23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-12-31 23:59:59的时间戳(1761916799000)
*/
public static Long getEndOfYear_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.lastDayOfYear()) // 调整为年的最后一天
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳所在年的12月31日23:59:59 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当年最后一天23点59分59秒的Date对象
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2025-12-31 23:59:59
*/
public static Date getEndOfYear(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.lastDayOfYear()) // 调整为年的最后一天
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 去年
/**
* 获取指定时间戳「去年同日」的00:00:00时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年同日0点的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2024-12-10 00:00:00的时间戳
*/
public static Long getStartOfLastYear_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusYears(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳「去年同日」的00:00:00 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年同日0点的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2024-12-10 00:00:00
*/
public static Date getStartOfLastYear(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return
Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusYears(1)
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳「去年同日」的23:59:59时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年同日23点59分59秒的时间戳
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2024-12-10 23:59:59的时间戳
*/
public static Long getEndOfLastYear_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusYears(1)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
}
/**
* 获取指定时间戳「去年同日」的23:59:59 Date对象
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年同日23点59分59秒的Date对象
* @example 输入:timestamp=2025-12-10 14:30:20的时间戳 → 输出:2024-12-10 23:59:59
*/
public static Date getEndOfLastYear(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
return Date.from(
LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).minusYears(1)
.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(0)
.atZone(ZoneId.systemDefault())
.toInstant()
);
}
// endregion
// region 去年上月
/**
* 获取指定时间戳「去年上月」的1号00:00:00时间戳(毫秒)
* <p>逻辑:先减1年,再减1个月,取当月第一天0点</p>
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年上月1号0点的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2024-11-01 00:00:00的时间戳
*/
public static Long getStartOfSameMonthLastYear_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
// 保存原来的时分秒和毫秒
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int millisecond = calendar.get(Calendar.MILLISECOND);
// 设置为去年
calendar.add(Calendar.YEAR, -1);
// 设置为上个月
calendar.add(Calendar.MONTH, -1);
// 设置为本月的第一天
calendar.set(Calendar.DAY_OF_MONTH, 1);
// 设置为当天的开始时间(00:00:00)
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTimeInMillis();
}
/**
* 获取指定时间戳「去年上月」的1号00:00:00时间戳(毫秒)
* <p>逻辑:先减1年,再减1个月,取当月第一天0点</p>
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年上月1号0点的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2024-11-01 00:00:00的时间戳
*/
public static Date getStartOfSameMonthLastYear(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
// 保存原来的时分秒和毫秒
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int millisecond = calendar.get(Calendar.MILLISECOND);
// 设置为去年
calendar.add(Calendar.YEAR, -1);
// 设置为上个月
calendar.add(Calendar.MONTH, -1);
// 设置为本月的第一天
calendar.set(Calendar.DAY_OF_MONTH, 1);
// 设置为当天的开始时间(00:00:00)
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return new Date(calendar.getTimeInMillis());
}
/**
* 获取指定时间戳「上月同期」的00:00:00 Date对象(兼容月末天数不一致)
* <p>例如:当前是3月31日,上月(2月)没有31日,则取2月最后一天</p>
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 上月同期0点的Date对象
* @example 输入:timestamp=2025-03-31的时间戳 → 输出:2025-02-28 00:00:00
* 输入:timestamp=2025-03-15的时间戳 → 输出:2025-02-15 00:00:00
*/
public static Date getStartOfLastMonthCompare(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
// 首先获取上个月的第一天
LocalDateTime lastMonthFirstDay = LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).with(TemporalAdjusters.firstDayOfMonth())
.minusMonths(1);
// 然后调整到指定日期对应的天数,但不能超过上个月的总天数
LocalDateTime targetDate = lastMonthFirstDay.with(TemporalAdjusters.lastDayOfMonth());
int lastMonthMaxDay = targetDate.getDayOfMonth();
int currentDay = LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
).getDayOfMonth();
LocalDateTime result = lastMonthFirstDay.withDayOfMonth(Math.min(currentDay, lastMonthMaxDay))
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
return Date.from(
result.atZone(ZoneId.systemDefault())
.toInstant()
);
}
/**
* 获取指定时间戳「去年上月」的最后一天23:59:59.999时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年上月最后一天23点59分59秒999毫秒的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2024-11-30 23:59:59.999的时间戳
*/
public static Long getEndOfSameMonthLastYear_Timestamp(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
// 当前时间 的 去年 上个月 时间
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
// 设置为去年
calendar.add(Calendar.YEAR, -1);
// 上个月
calendar.add(Calendar.MONTH, -1);
// 获取上月最后一天
int lastDayOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
calendar.set(Calendar.DAY_OF_MONTH, lastDayOfMonth);
// 设置为当天的结束时间(23:59:59.999)
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTimeInMillis();
}
/**
* 获取指定时间戳「去年上月」的最后一天23:59:59.999时间戳(毫秒)
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 去年上月最后一天23点59分59秒999毫秒的时间戳
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:2024-11-30 23:59:59.999的时间戳
*/
public static Date getEndOfSameMonthLastYear(Long timestamp) {
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
// 当前时间 的 去年 上个月 时间
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
// 设置为去年
calendar.add(Calendar.YEAR, -1);
// 上个月
calendar.add(Calendar.MONTH, -1);
// 获取上月最后一天
int lastDayOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
calendar.set(Calendar.DAY_OF_MONTH, lastDayOfMonth);
// 设置为当天的结束时间(23:59:59.999)
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return new Date(calendar.getTimeInMillis());
}
// endregion
// region 计算天数
/**
* 获取指定时间戳对应日期「去年同月」的总天数
* <p>逻辑:先获取去年同月第一天的时间戳,再计算该月的天数</p>
*
* @param time 时间戳(毫秒),不可为null(long类型无null,默认当前时间需手动处理)
* @return 去年同月的总天数
* @example 输入:time=2025-12-10的时间戳(1760106620000) → 输出:31(2024年12月天数)
* 输入:time=2025-02-10的时间戳 → 输出:29(2024年2月是闰年,天数29)
*/
public static int getDaysOfSameMonthLastYear(long time) {
Long temp = NullSafeUtils.getStartOfSameMonthLastYear_Timestamp(time);
return getDaysOfLastMonth(temp);
}
/**
* 获取指定时间戳所在月的上月总天数
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 上月的总天数
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:30(2025年11月天数)
* 输入:timestamp=2025-03-10的时间戳 → 输出:28(2025年2月天数)
*/
public static int getDaysOfLastMonth(Long timestamp) {
// 如果传入的时间戳为 null,则使用当前时间
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
// 转换为 LocalDateTime
LocalDateTime dateTime = LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
);
// 调整为上个月的第一天,并返回该月的天数
return dateTime.with(TemporalAdjusters.firstDayOfMonth())
.minusMonths(1)
.toLocalDate()
.lengthOfMonth();
}
/**
* 获取指定时间戳所在月的当月总天数
*
* @param timestamp 时间戳(毫秒),可为null(默认当前时间)
* @return 当前月的总天数
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:31(2025年12月天数)
* 输入:timestamp=2025-02-10的时间戳 → 输出:28(2025年2月天数)
*/
public static int getDaysOfCurrentMonth(Long timestamp) {
// 如果传入的时间戳为 null,则使用当前时间
if (timestamp == null) {
timestamp = System.currentTimeMillis();
}
// 转换为 LocalDateTime
LocalDateTime dateTime = LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.systemDefault()
);
// 调整为当前月的第一天,并返回该月的天数
return dateTime.with(TemporalAdjusters.firstDayOfMonth())
.toLocalDate()
.lengthOfMonth();
}
/**
* 获取两个日期之间的所有月份(包含开始和结束月份,返回每月1号0点)
*
* @param startDate 开始日期,不可为null
* @param endDate 结束日期,不可为null
* @return 每月1号的Date列表(按时间升序)
* @throws IllegalArgumentException startDate/endDate为null时抛出
* @example 输入:startDate=2025-01-15, endDate=2025-03-20 → 输出:[2025-01-01, 2025-02-01, 2025-03-01]
*/
public static List<Date> getMonthRange(Date startDate, Date endDate) {
List<Date> monthList = new ArrayList<>();
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
cal.set(Calendar.DAY_OF_MONTH, 1); // 开始月份的第一天
Calendar endCal = Calendar.getInstance();
endCal.setTime(endDate);
endCal.set(Calendar.DAY_OF_MONTH, 1); // 结束月份的第一天
while (!cal.after(endCal)) {
monthList.add(cal.getTime());
cal.add(Calendar.MONTH, 1); // 下一个月
}
return monthList;
}
/**
* 获取指定时间戳所在月的总天数(long类型重载)
*
* @param timestamp 时间戳(毫秒),不可为null(long类型无null)
* @return 该月的总天数
* @example 输入:timestamp=2025-12-10的时间戳 → 输出:31
* 输入:timestamp=2025-02-10的时间戳 → 输出:28
*/
public static int getDaysOfMonth(long timestamp) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(timestamp);
return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
}
/**
* 计算两个Date对象之间相差的天数(绝对值,非负数)
*
* @param start 开始日期,不可为null
* @param end 结束日期,不可为null
* @return 两个日期的天数差(非负数)
* @throws IllegalArgumentException start/end为null时抛出
* @example 输入:start=2025-12-01, end=2025-12-10 → 输出:9
* 输入:start=2025-12-10, end=2025-12-01 → 输出:9
*/
public static long absCalculateDaysBetween(Date start, Date end) {
if (start == null || end == null) {
throw new IllegalArgumentException("Start and end dates must not be null");
}
// 将Date转换为LocalDateTime
LocalDateTime startDateTime = LocalDateTime.ofInstant(start.toInstant(), ZoneId.systemDefault());
LocalDateTime endDateTime = LocalDateTime.ofInstant(end.toInstant(), ZoneId.systemDefault());
// 计算两个日期之间的天数
return Math.abs(ChronoUnit.DAYS.between(startDateTime.toLocalDate(), endDateTime.toLocalDate()));
}
/**
* 计算两个Date对象之间相差的天数(有符号,end - start)
*
* @param start 开始日期,不可为null
* @param end 结束日期,不可为null
* @return 天数差(end在start之后为正,之前为负)
* @throws IllegalArgumentException start/end为null时抛出
* @example 输入:start=2025-12-01, end=2025-12-10 → 输出:9
* 输入:start=2025-12-10, end=2025-12-01 → 输出:-9
*/
public static long calculateDaysBetween(Date start, Date end) {
if (start == null || end == null) {
throw new IllegalArgumentException("Start and end dates must not be null");
}
// 将Date转换为LocalDateTime
LocalDateTime startDateTime = LocalDateTime.ofInstant(start.toInstant(), ZoneId.systemDefault());
LocalDateTime endDateTime = LocalDateTime.ofInstant(end.toInstant(), ZoneId.systemDefault());
// 计算两个日期之间的天数
return ChronoUnit.DAYS.between(startDateTime.toLocalDate(), endDateTime.toLocalDate());
}
/**
* 计算两个时间戳之间相差的天数(绝对值,非负数)
*
* @param startTimestamp 开始时间戳(毫秒),不可为null
* @param endTimestamp 结束时间戳(毫秒),不可为null
* @return 天数差(非负数)
* @throws IllegalArgumentException startTimestamp/endTimestamp为null时抛出
* @example 输入:startTimestamp=2025-12-01的时间戳, endTimestamp=2025-12-10的时间戳 → 输出:9
*/
public static long absCalculateDaysBetween(Long startTimestamp, Long endTimestamp) {
if (startTimestamp == null || endTimestamp == null) {
throw new IllegalArgumentException("Start and end timestamps must not be null");
}
// 将时间戳转换为LocalDateTime
LocalDateTime startDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTimestamp), ZoneId.systemDefault());
LocalDateTime endDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(endTimestamp), ZoneId.systemDefault());
// 计算两个日期之间的天数
return Math.abs(ChronoUnit.DAYS.between(startDateTime.toLocalDate(), endDateTime.toLocalDate()));
}
/**
* 计算两个时间戳之间相差的天数(有符号,end - start)
*
* @param startTimestamp 开始时间戳(毫秒),不可为null
* @param endTimestamp 结束时间戳(毫秒),不可为null
* @return 天数差
* @throws IllegalArgumentException startTimestamp/endTimestamp为null时抛出
* @example 输入:startTimestamp=2025-12-01的时间戳, endTimestamp=2025-12-10的时间戳 → 输出:-9
*/
public static long calculateDaysBetween(Long startTimestamp, Long endTimestamp) {
if (startTimestamp == null || endTimestamp == null) {
throw new IllegalArgumentException("Start and end timestamps must not be null");
}
// 将时间戳转换为LocalDateTime
LocalDateTime startDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTimestamp), ZoneId.systemDefault());
LocalDateTime endDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(endTimestamp), ZoneId.systemDefault());
// 计算两个日期之间的天数
return ChronoUnit.DAYS.between(startDateTime.toLocalDate(), endDateTime.toLocalDate());
}
/**
* 计算两个LocalDate对象之间相差的天数(绝对值,非负数)
*
* @param start 开始日期,不可为null
* @param end 结束日期,不可为null
* @return 天数差(非负数)
* @throws IllegalArgumentException start/end为null时抛出
* @example 输入:start=LocalDate.of(2025,12,1), end=LocalDate.of(2025,12,10) → 输出:9
*/
public static long absCalculateDaysBetween(LocalDate start, LocalDate end) {
if (start == null || end == null) {
throw new IllegalArgumentException("Start and end dates must not be null");
}
// 计算两个日期之间的天数
return Math.abs(ChronoUnit.DAYS.between(start, end));
}
/**
* 计算两个LocalDate对象之间相差的天数(有符号,end - start)
*
* @param start 开始日期,不可为null
* @param end 结束日期,不可为null
* @return 天数差
* @throws IllegalArgumentException start/end为null时抛出
* @example 输入:start=LocalDate.of(2025,12,1), end=LocalDate.of(2025,12,10) → 输出:-9
*/
public static long calculateDaysBetween(LocalDate start, LocalDate end) {
if (start == null || end == null) {
throw new IllegalArgumentException("Start and end dates must not be null");
}
// 计算两个日期之间的天数
return ChronoUnit.DAYS.between(start, end);
}
// endregion
// region 获取星期几 & 上午 下午
/**
* 获取指定时间戳对应的星期几(数字)
* <p>返回值:1=周一,2=周二,...,7=周日</p>
*
* @param timeMillis 时间戳(毫秒),可为null(默认当前时间)
* @return 星期几(1-7)
* @example 输入:timeMillis=2025-12-10(周三)的时间戳 → 输出:3
* 输入:timeMillis=2025-12-14(周日)的时间戳 → 输出:7
* 输入:timeMillis=null → 输出:当前时间对应的星期数
*/
public static int getWeekDay(Long timeMillis) {
long millis = timeMillis == null ? System.currentTimeMillis() : timeMillis;
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
return dayOfWeek.getValue();
}
/**
* 判断指定时间戳是否为上午(00:00:00 - 11:59:59)
*
* @param timeMillis 时间戳(毫秒),可为null(默认当前时间)
* @return 是上午返回true,否则false
* @example 输入:timeMillis=2025-12-10 10:30:00的时间戳 → 输出:true
* 输入:timeMillis=2025-12-10 12:00:00的时间戳 → 输出:false
*/
public static boolean isMorning(Long timeMillis) {
long millis = timeMillis == null ? System.currentTimeMillis() : timeMillis;
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
return localDateTime.getHour() < 12;
}
/**
* 判断指定时间戳是否为下午(12:00:00 - 23:59:59)
* <p>逻辑:取isMorning的反值</p>
*
* @param timeMillis 时间戳(毫秒),可为null(默认当前时间)
* @return 是下午返回true,否则false
* @example 输入:timeMillis=2025-12-10 14:30:00的时间戳 → 输出:true
* 输入:timeMillis=2025-12-10 11:59:59的时间戳 → 输出:false
*/
public static boolean isAfternoon(Long timeMillis) {
return !isMorning(timeMillis);
}
// endregion
// region 时间日期判断:同月
/**
* 判断两个日期是否在同一个月份(年+月都相同)
*
* @param startTime 开始时间,不可为null
* @param endTime 结束时间,不可为null
* @return 同月返回true,否则false
* @throws IllegalArgumentException startTime/endTime为null时抛出
* @example 输入:startTime=2025-12-01, endTime=2025-12-31 → 输出:true
* 输入:startTime=2025-12-31, endTime=2026-01-01 → 输出:false
*/
public static boolean isSameMonth(Date startTime, Date endTime) {
LocalDate start = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate end = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
return start.getYear() == end.getYear() &&
start.getMonth() == end.getMonth();
}
// endregion
// endregion
}