Compare commits

...

3 Commits

Author SHA1 Message Date
3f08b08910 docs: 修改 copyright 2025-03-23 13:50:03 +08:00
4c1ee87b4d feat: StringTools 新增工具方法
新增 StringTools#isBlank、StringTools#isEmpty、StringTools#isNotEmpty、StringTools#isURL、StringTools#isEmail
2025-03-23 13:49:23 +08:00
a9c8fec81a feat: DateTimeTools 新增 isFuture 和 isPast。 2025-03-23 13:27:37 +08:00
13 changed files with 427 additions and 17 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 the original author or authors.
* Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -722,6 +722,78 @@ public class DateTimeTools {
// #endregion - start & end
// ================================
// ================================
// #region - isFuture
// ================================
public static boolean isFuture(Date date) {
return date.after(new Date());
}
public static boolean isFuture(Calendar date) {
return date.after(Calendar.getInstance());
}
public static boolean isFuture(Instant instant) {
return instant.isAfter(Instant.now());
}
public static boolean isFuture(long timeMillis) {
return timeMillis > System.currentTimeMillis();
}
public static boolean isFuture(LocalDate date) {
return date.isAfter(LocalDate.now());
}
public static boolean isFuture(LocalDateTime dateTime) {
return dateTime.isAfter(LocalDateTime.now());
}
public static boolean isFuture(ZonedDateTime dateTime) {
return dateTime.isAfter(ZonedDateTime.now());
}
// ================================
// #endregion - isFuture
// ================================
// ================================
// #region - isPast
// ================================
public static boolean isPast(Date date) {
return date.before(new Date());
}
public static boolean isPast(Calendar date) {
return date.before(Calendar.getInstance());
}
public static boolean isPast(Instant instant) {
return instant.isBefore(Instant.now());
}
public static boolean isPast(long timeMillis) {
return timeMillis < System.currentTimeMillis();
}
public static boolean isPast(LocalDate date) {
return date.isBefore(LocalDate.now());
}
public static boolean isPast(LocalDateTime dateTime) {
return dateTime.isBefore(LocalDateTime.now());
}
public static boolean isPast(ZonedDateTime dateTime) {
return dateTime.isBefore(ZonedDateTime.now());
}
// ================================
// #endregion - isPast
// ================================
// ================================
// #region - others
// ================================

View File

@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 the original author or authors.
* Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 the original author or authors.
* Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,16 @@
package xyz.zhouxy.plusone.commons.util;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import javax.annotation.Nullable;
import com.google.common.annotations.Beta;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
/**
* StringTools
*
@ -45,6 +51,18 @@ public class StringTools {
return false;
}
public static boolean isBlank(@Nullable String cs) {
if (cs == null || cs.isEmpty()) {
return true;
}
for (int i = 0; i < cs.length(); i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
public static String repeat(String str, int times) {
return repeat(str, times, Integer.MAX_VALUE);
}
@ -54,6 +72,29 @@ public class StringTools {
return String.valueOf(ArrayTools.repeat(str.toCharArray(), times, maxLength));
}
public static boolean isNotEmpty(@Nullable final String cs) {
return cs != null && !cs.isEmpty();
}
public static boolean isEmpty(@Nullable final String cs) {
return cs == null || cs.isEmpty();
}
@Beta
public static boolean isEmail(@Nullable final String cs) {
return RegexTools.matches(cs, PatternConsts.EMAIL);
}
@Beta
public static boolean isURL(@Nullable final String cs) {
try {
new URL(cs);
} catch (MalformedURLException e) {
return false;
}
return true;
}
private StringTools() {
throw new IllegalStateException("Utility class");
}

View File

@ -435,32 +435,117 @@ class DateTimeToolsTests {
// ================================
// ================================
// #region - others
// #region - start & end
// ================================
@Test
void startDateOfYear() {
void testStartAndEndDateOfYear() {
assertEquals(LocalDate.of(2008, 1, 1), DateTimeTools.startDateOfYear(2008));
assertEquals(LocalDate.of(2008, 12, 31), DateTimeTools.endDateOfYear(2008));
}
@Test
void testStartOfNextDate() {
assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0),
DateTimeTools.startOfNextDate(LOCAL_DATE));
assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0).atZone(SYS_ZONE_ID),
DateTimeTools.startOfNextDate(LOCAL_DATE, SYS_ZONE_ID));
assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0).atZone(ZONE_ID),
DateTimeTools.startOfNextDate(LOCAL_DATE, ZONE_ID));
}
// ================================
// #endregion - start & end
// ================================
// ================================
// #region - isFuture & isPast
// ================================
@Test
void test_isFuture_And_isPast_WhenFuture() {
Date date = new Date(Instant.now().plusSeconds(10).toEpochMilli());
assertTrue(DateTimeTools.isFuture(date));
assertFalse(DateTimeTools.isPast(date));
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("+01:00"));
calendar.add(Calendar.SECOND, 10);
assertTrue(DateTimeTools.isFuture(calendar));
assertFalse(DateTimeTools.isPast(calendar));
Instant instant = Instant.now().plusSeconds(10);
assertTrue(DateTimeTools.isFuture(instant));
assertFalse(DateTimeTools.isPast(instant));
long timeMillis = Instant.now().plusSeconds(10).toEpochMilli();
assertTrue(DateTimeTools.isFuture(timeMillis));
assertFalse(DateTimeTools.isPast(timeMillis));
LocalDate localDate = LocalDate.now().plusDays(1);
assertTrue(DateTimeTools.isFuture(localDate));
assertFalse(DateTimeTools.isPast(localDate));
LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(10);
assertTrue(DateTimeTools.isFuture(localDateTime));
assertFalse(DateTimeTools.isPast(localDateTime));
ZonedDateTime zonedDateTime = Instant.now().plusSeconds(10).atZone(ZoneId.of("+01:00"));
assertTrue(DateTimeTools.isFuture(zonedDateTime));
assertFalse(DateTimeTools.isPast(zonedDateTime));
}
@Test
void test_isFuture_And_isPast_WhenPast() {
Date date = new Date(Instant.now().minusSeconds(10).toEpochMilli());
assertFalse(DateTimeTools.isFuture(date));
assertTrue(DateTimeTools.isPast(date));
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("+01:00"));
calendar.add(Calendar.SECOND, -10);
assertFalse(DateTimeTools.isFuture(calendar));
assertTrue(DateTimeTools.isPast(calendar));
Instant instant = Instant.now().minusSeconds(10);
assertFalse(DateTimeTools.isFuture(instant));
assertTrue(DateTimeTools.isPast(instant));
long timeMillis = Instant.now().minusSeconds(10).toEpochMilli();
assertFalse(DateTimeTools.isFuture(timeMillis));
assertTrue(DateTimeTools.isPast(timeMillis));
LocalDate localDate = LocalDate.now().minusDays(1);
assertFalse(DateTimeTools.isFuture(localDate));
assertTrue(DateTimeTools.isPast(localDate));
LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(10);
assertFalse(DateTimeTools.isFuture(localDateTime));
assertTrue(DateTimeTools.isPast(localDateTime));
ZonedDateTime zonedDateTime = Instant.now().minusSeconds(10).atZone(ZoneId.of("+01:00"));
assertFalse(DateTimeTools.isFuture(zonedDateTime));
assertTrue(DateTimeTools.isPast(zonedDateTime));
}
// ================================
// #endregion - isFuture & isPast
// ================================
// ================================
// #region - others
// ================================
@Test
void startDateTimeRange() {
Range<LocalDateTime> localDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE);
assertEquals(LOCAL_DATE.atStartOfDay(), localDateTimeRange.lowerEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
assertTrue(localDateTimeRange.contains(LOCAL_DATE.atStartOfDay()));
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint());
assertFalse(localDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay()));
Range<ZonedDateTime> zonedDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE, SYS_ZONE_ID);
assertEquals(LOCAL_DATE.atStartOfDay().atZone(SYS_ZONE_ID), zonedDateTimeRange.lowerEndpoint());
assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint());
assertTrue(zonedDateTimeRange.contains(LOCAL_DATE.atStartOfDay().atZone(SYS_ZONE_ID)));
assertEquals(ZonedDateTime.of(LocalDate.of(2024, 12, 30).atStartOfDay(), SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint());
assertEquals(ZonedDateTime.of(LocalDate.of(2024, 12, 30).atStartOfDay(), SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint());
assertFalse(zonedDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID)));
}
@ -480,6 +565,10 @@ class DateTimeToolsTests {
assertThrows(DateTimeException.class, () -> DateTimeTools.toYearString(Year.MIN_VALUE - 1));
assertThrows(DateTimeException.class, () -> DateTimeTools.toYearString(Year.MAX_VALUE + 1));
assertEquals("2024", DateTimeTools.toYearString(Year.of(2024)));
assertEquals("999999999", DateTimeTools.toYearString(Year.of(Year.MAX_VALUE)));
assertEquals("-999999999", DateTimeTools.toYearString(Year.of(Year.MIN_VALUE)));
assertEquals("01", DateTimeTools.toMonthStringMM(1));
assertEquals("02", DateTimeTools.toMonthStringMM(2));
assertEquals("3", DateTimeTools.toMonthStringM(3));

View File

@ -32,6 +32,10 @@ import org.junit.jupiter.api.Test;
public
class StringToolsTests {
// ================================
// #region - isNotBlank
// ================================
@Test
void isNotBlank_NullString_ReturnsFalse() {
assertFalse(StringTools.isNotBlank(null));
@ -52,6 +56,206 @@ class StringToolsTests {
assertTrue(StringTools.isNotBlank("Hello"));
}
// ================================
// #endregion - isNotBlank
// ================================
// ================================
// #region - isBlank
// ================================
@Test
void isBlank_NullString_ReturnsTrue() {
assertTrue(StringTools.isBlank(null));
}
@Test
void isBlank_EmptyString_ReturnsTrue() {
assertTrue(StringTools.isBlank(""));
}
@Test
void isBlank_WhitespaceString_ReturnsTrue() {
assertTrue(StringTools.isBlank(" "));
}
@Test
void isBlank_NonWhitespaceString_ReturnsFalse() {
assertFalse(StringTools.isBlank("Hello"));
}
// ================================
// #endregion - isBlank
// ================================
// ================================
// #region - isNotEmpty
// ================================
@Test
void isNotEmpty_NullString_ReturnsFalse() {
assertFalse(StringTools.isNotEmpty(null));
}
@Test
void isNotEmpty_EmptyString_ReturnsFalse() {
assertFalse(StringTools.isNotEmpty(""));
}
@Test
void isNotEmpty_WhitespaceString_ReturnsTrue() {
assertTrue(StringTools.isNotEmpty(" "));
}
@Test
void isNotEmpty_NonWhitespaceString_ReturnsTrue() {
assertTrue(StringTools.isNotEmpty("Hello"));
}
// ================================
// #endregion - isNotEmpty
// ================================
// ================================
// #region - isEmpty
// ================================
@Test
void isEmpty_NullString_ReturnsTrue() {
assertTrue(StringTools.isEmpty(null));
}
@Test
void isEmpty_EmptyString_ReturnsTrue() {
assertTrue(StringTools.isEmpty(""));
}
@Test
void isEmpty_WhitespaceString_ReturnsFalse() {
assertFalse(StringTools.isEmpty(" "));
}
@Test
void isEmpty_NonWhitespaceString_ReturnsFalse() {
assertFalse(StringTools.isEmpty("Hello"));
}
// ================================
// #endregion - isEmpty
// ================================
// ================================
// #region - EMAIL
// ================================
@Test
public void testValidEmails() {
assertTrue(StringTools.isEmail("test@example.com"));
assertTrue(StringTools.isEmail("user.name+tag+sorting@example.com"));
assertTrue(StringTools.isEmail("user@sub.example.com"));
assertTrue(StringTools.isEmail("user@123.123.123.123"));
}
@Test
public void testInvalidEmails() {
assertFalse(StringTools.isEmail(".username@example.com"));
assertFalse(StringTools.isEmail("@missingusername.com"));
assertFalse(StringTools.isEmail("plainaddress"));
assertFalse(StringTools.isEmail("username..username@example.com"));
assertFalse(StringTools.isEmail("username.@example.com"));
assertFalse(StringTools.isEmail("username@-example.com"));
assertFalse(StringTools.isEmail("username@-example.com"));
assertFalse(StringTools.isEmail("username@.com.com"));
assertFalse(StringTools.isEmail("username@.com.my"));
assertFalse(StringTools.isEmail("username@.com"));
assertFalse(StringTools.isEmail("username@com."));
assertFalse(StringTools.isEmail("username@com"));
assertFalse(StringTools.isEmail("username@example..com"));
assertFalse(StringTools.isEmail("username@example.com-"));
assertFalse(StringTools.isEmail("username@example.com."));
assertFalse(StringTools.isEmail("username@example"));
}
// ================================
// #endregion - EMAIL
// ================================
// ================================
// #region - isURL
// ================================
/**
* TC1: 验证标准HTTP协议URL
*/
@Test
void isURL_ValidHttpURL_ReturnsTrue() {
assertTrue(StringTools.isURL("http://example.com"));
}
/**
* TC2: 验证带路径参数的HTTPS复杂URL
*/
@Test
void isURL_ValidHttpsComplexURL_ReturnsTrue() {
assertTrue(StringTools.isURL("https://test.com:8080/api/v1?param=value#anchor"));
}
/**
* TC3: 验证FTP协议URL
*/
@Test
void isURL_ValidFtpURL_ReturnsTrue() {
assertTrue(StringTools.isURL("ftp://files.example.com/directory/"));
}
/**
* TC4: 验证非法协议处理
*/
@Test
void isURL_InvalidProtocol_ReturnsFalse() {
assertFalse(StringTools.isURL("httpx://invalid.com"));
}
/**
* TC5: 验证null输入处理
*/
@Test
void isURL_NullInput_ReturnsFalse() {
assertFalse(StringTools.isURL(null));
}
/**
* TC6: 验证空字符串处理
*/
@Test
void isURL_EmptyString_ReturnsFalse() {
assertFalse(StringTools.isURL(StringTools.EMPTY_STRING));
}
/**
* TC7: 验证缺失协议头处理
*/
@Test
void isURL_MissingProtocol_ReturnsFalse() {
assertFalse(StringTools.isURL("www.example.com/path"));
}
/**
* TC8: 验证未编码特殊字符处理
*/
@Test
void isURL_UnencodedSpecialChars_ReturnsTrue() {
assertTrue(StringTools.isURL("http://example.com/测试"));
}
// ================================
// #endregion - isURL
// ================================
// ================================
// #region - repeat
// ================================
@Test
void repeat_NullString_ThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
@ -90,6 +294,10 @@ class StringToolsTests {
assertEquals("", StringTools.repeat("Hello", 0));
}
// ================================
// #endregion - repeat
// ================================
@Test
void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
Constructor<?>[] constructors = StringTools.class.getDeclaredConstructors();