diff --git a/CHANGELOG.md b/CHANGELOG.md index 743a0af62..2b39123a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * 【json 】 解析Object中对是否为bean单独判断,而不是直接解析 * 【core 】 SimHash锁改为StampedLock * 【core 】 Singleton改为SimpleCache实现 +* 【core 】 增加CalendarUtil,DateUtil相关方法全部迁移到此 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index e03c08ce5..19a5299b1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -39,831 +39,828 @@ import java.util.Set; * return EqualsBuilder.reflectionEquals(this, obj); * } * - * */ public class EqualsBuilder implements Builder { private static final long serialVersionUID = 1L; - /** - *

- * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. - *

- * - * @since 3.0 - */ - private static final ThreadLocal>> REGISTRY = new ThreadLocal<>(); + /** + *

+ * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

+ * + * @since 3.0 + */ + private static final ThreadLocal>> REGISTRY = new ThreadLocal<>(); - /** - *

- * Returns the registry of object pairs being traversed by the reflection - * methods in the current thread. - *

- * - * @return Set the registry of objects being traversed - * @since 3.0 - */ - static Set> getRegistry() { - return REGISTRY.get(); - } + /** + *

+ * Returns the registry of object pairs being traversed by the reflection + * methods in the current thread. + *

+ * + * @return Set the registry of objects being traversed + * @since 3.0 + */ + static Set> getRegistry() { + return REGISTRY.get(); + } - /** - *

- * Converters value pair into a register pair. - *

- * - * @param lhs this object - * @param rhs the other object - * - * @return the pair - */ - static Pair getRegisterPair(final Object lhs, final Object rhs) { - final IDKey left = new IDKey(lhs); - final IDKey right = new IDKey(rhs); - return new Pair<>(left, right); - } + /** + *

+ * Converters value pair into a register pair. + *

+ * + * @param lhs this object + * @param rhs the other object + * @return the pair + */ + static Pair getRegisterPair(final Object lhs, final Object rhs) { + final IDKey left = new IDKey(lhs); + final IDKey right = new IDKey(rhs); + return new Pair<>(left, right); + } - /** - *

- * Returns true if the registry contains the given object pair. - * Used by the reflection methods to avoid infinite loops. - * Objects might be swapped therefore a check is needed if the object pair - * is registered in given or swapped order. - *

- * - * @param lhs this object to lookup in registry - * @param rhs the other object to lookup on registry - * @return boolean true if the registry contains the given object. - * @since 3.0 - */ - static boolean isRegistered(final Object lhs, final Object rhs) { - final Set> registry = getRegistry(); - final Pair pair = getRegisterPair(lhs, rhs); - final Pair swappedPair = new Pair<>(pair.getKey(), pair.getValue()); + /** + *

+ * Returns true if the registry contains the given object pair. + * Used by the reflection methods to avoid infinite loops. + * Objects might be swapped therefore a check is needed if the object pair + * is registered in given or swapped order. + *

+ * + * @param lhs this object to lookup in registry + * @param rhs the other object to lookup on registry + * @return boolean true if the registry contains the given object. + * @since 3.0 + */ + static boolean isRegistered(final Object lhs, final Object rhs) { + final Set> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + final Pair swappedPair = new Pair<>(pair.getKey(), pair.getValue()); - return registry != null - && (registry.contains(pair) || registry.contains(swappedPair)); - } + return registry != null + && (registry.contains(pair) || registry.contains(swappedPair)); + } - /** - *

- * Registers the given object pair. - * Used by the reflection methods to avoid infinite loops. - *

- * - * @param lhs this object to register - * @param rhs the other object to register - */ - static void register(final Object lhs, final Object rhs) { - synchronized (EqualsBuilder.class) { - if (getRegistry() == null) { - REGISTRY.set(new HashSet<>()); - } - } + /** + *

+ * Registers the given object pair. + * Used by the reflection methods to avoid infinite loops. + *

+ * + * @param lhs this object to register + * @param rhs the other object to register + */ + static void register(final Object lhs, final Object rhs) { + synchronized (EqualsBuilder.class) { + if (getRegistry() == null) { + REGISTRY.set(new HashSet<>()); + } + } - final Set> registry = getRegistry(); - final Pair pair = getRegisterPair(lhs, rhs); - registry.add(pair); - } + final Set> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + registry.add(pair); + } - /** - *

- * Unregisters the given object pair. - *

- * - *

- * Used by the reflection methods to avoid infinite loops. - * - * @param lhs this object to unregister - * @param rhs the other object to unregister - * @since 3.0 - */ - static void unregister(final Object lhs, final Object rhs) { - Set> registry = getRegistry(); - if (registry != null) { - final Pair pair = getRegisterPair(lhs, rhs); - registry.remove(pair); - synchronized (EqualsBuilder.class) { - //read again - registry = getRegistry(); - if (registry != null && registry.isEmpty()) { - REGISTRY.remove(); - } - } - } - } + /** + *

+ * Unregisters the given object pair. + *

+ * + *

+ * Used by the reflection methods to avoid infinite loops. + * + * @param lhs this object to unregister + * @param rhs the other object to unregister + * @since 3.0 + */ + static void unregister(final Object lhs, final Object rhs) { + Set> registry = getRegistry(); + if (registry != null) { + final Pair pair = getRegisterPair(lhs, rhs); + registry.remove(pair); + synchronized (EqualsBuilder.class) { + //read again + registry = getRegistry(); + if (registry != null && registry.isEmpty()) { + REGISTRY.remove(); + } + } + } + } - /** 是否equals,此值随着构建会变更,默认true */ - private boolean isEquals = true; + /** + * 是否equals,此值随着构建会变更,默认true + */ + private boolean isEquals = true; - /** - * 构造,初始状态值为true - */ - public EqualsBuilder() { - // do nothing for now. - } + /** + * 构造,初始状态值为true + */ + public EqualsBuilder() { + // do nothing for now. + } - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - /** - *

反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

- * - * @param lhs 此对象 - * @param rhs 另一个对象 - * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { - return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); - } + /** + *

反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

+ * + * @param lhs 此对象 + * @param rhs 另一个对象 + * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 + * @return 两个对象是否equals,是返回true + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { + return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); + } - /** - *

反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

- * - * @param lhs 此对象 - * @param rhs 另一个对象 - * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { - return reflectionEquals(lhs, rhs, false, null, excludeFields); - } + /** + *

反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

+ * + * @param lhs 此对象 + * @param rhs 另一个对象 + * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 + * @return 两个对象是否equals,是返回true + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } - /** - *

This method uses reflection to determine if the two Objects - * are equal.

- * - *

It uses AccessibleObject.setAccessible to gain access to private - * fields. This means that it will throw a security exception if run under - * a security manager, if the permissions are not set up correctly. It is also - * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

- * - *

If the TestTransients parameter is set to true, transient - * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

- * - *

Static fields will not be tested. Superclass fields will be included.

- * - * @param lhs this object - * @param rhs the other object - * @param testTransients whether to include transient fields - * @return true if the two Objects have tested equals. - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { - return reflectionEquals(lhs, rhs, testTransients, null); - } + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

+ * + *

If the TestTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { + return reflectionEquals(lhs, rhs, testTransients, null); + } - /** - *

This method uses reflection to determine if the two Objects - * are equal.

- * - *

It uses AccessibleObject.setAccessible to gain access to private - * fields. This means that it will throw a security exception if run under - * a security manager, if the permissions are not set up correctly. It is also - * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

- * - *

If the testTransients parameter is set to true, transient - * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

- * - *

Static fields will not be included. Superclass fields will be appended - * up to and including the specified superclass. A null superclass is treated - * as java.lang.Object.

- * - * @param lhs this object - * @param rhs the other object - * @param testTransients whether to include transient fields - * @param reflectUpToClass the superclass to reflect up to (inclusive), - * may be null - * @param excludeFields array of field names to exclude from testing - * @return true if the two Objects have tested equals. - * @since 2.0 - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, - final String... excludeFields) { - if (lhs == rhs) { - return true; - } - if (lhs == null || rhs == null) { - return false; - } - // Find the leaf class since there may be transients in the leaf - // class or in classes between the leaf and root. - // If we are not testing transients or a subclass has no ivars, - // then a subclass can test equals to a superclass. - final Class lhsClass = lhs.getClass(); - final Class rhsClass = rhs.getClass(); - Class testClass; - if (lhsClass.isInstance(rhs)) { - testClass = lhsClass; - if (!rhsClass.isInstance(lhs)) { - // rhsClass is a subclass of lhsClass - testClass = rhsClass; - } - } else if (rhsClass.isInstance(lhs)) { - testClass = rhsClass; - if (!lhsClass.isInstance(rhs)) { - // lhsClass is a subclass of rhsClass - testClass = lhsClass; - } - } else { - // The two classes are not related. - return false; - } - final EqualsBuilder equalsBuilder = new EqualsBuilder(); - try { - if (testClass.isArray()) { - equalsBuilder.append(lhs, rhs); - } else { - reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); - while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { - testClass = testClass.getSuperclass(); - reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); - } - } - } catch (final IllegalArgumentException e) { - // In this case, we tried to test a subclass vs. a superclass and - // the subclass has ivars or the ivars are transient and - // we are testing transients. - // If a subclass has ivars that we are trying to test them, we get an - // exception and we know that the objects are not equal. - return false; - } - return equalsBuilder.isEquals(); - } + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

+ * + *

If the testTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be null + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + * @since 2.0 + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, + final String... excludeFields) { + if (lhs == rhs) { + return true; + } + if (lhs == null || rhs == null) { + return false; + } + // Find the leaf class since there may be transients in the leaf + // class or in classes between the leaf and root. + // If we are not testing transients or a subclass has no ivars, + // then a subclass can test equals to a superclass. + final Class lhsClass = lhs.getClass(); + final Class rhsClass = rhs.getClass(); + Class testClass; + if (lhsClass.isInstance(rhs)) { + testClass = lhsClass; + if (!rhsClass.isInstance(lhs)) { + // rhsClass is a subclass of lhsClass + testClass = rhsClass; + } + } else if (rhsClass.isInstance(lhs)) { + testClass = rhsClass; + if (!lhsClass.isInstance(rhs)) { + // lhsClass is a subclass of rhsClass + testClass = lhsClass; + } + } else { + // The two classes are not related. + return false; + } + final EqualsBuilder equalsBuilder = new EqualsBuilder(); + try { + if (testClass.isArray()) { + equalsBuilder.append(lhs, rhs); + } else { + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { + testClass = testClass.getSuperclass(); + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + } + } + } catch (final IllegalArgumentException e) { + // In this case, we tried to test a subclass vs. a superclass and + // the subclass has ivars or the ivars are transient and + // we are testing transients. + // If a subclass has ivars that we are trying to test them, we get an + // exception and we know that the objects are not equal. + return false; + } + return equalsBuilder.isEquals(); + } - /** - *

Appends the fields and values defined by the given object of the - * given Class.

- * - * @param lhs the left hand object - * @param rhs the right hand object - * @param clazz the class to append details of - * @param builder the builder to append to - * @param useTransients whether to test transient fields - * @param excludeFields array of field names to exclude from testing - */ - private static void reflectionAppend( - final Object lhs, - final Object rhs, - final Class clazz, - final EqualsBuilder builder, - final boolean useTransients, - final String[] excludeFields) { + /** + *

Appends the fields and values defined by the given object of the + * given Class.

+ * + * @param lhs the left hand object + * @param rhs the right hand object + * @param clazz the class to append details of + * @param builder the builder to append to + * @param useTransients whether to test transient fields + * @param excludeFields array of field names to exclude from testing + */ + private static void reflectionAppend( + final Object lhs, + final Object rhs, + final Class clazz, + final EqualsBuilder builder, + final boolean useTransients, + final String[] excludeFields) { - if (isRegistered(lhs, rhs)) { - return; - } + if (isRegistered(lhs, rhs)) { + return; + } - try { - register(lhs, rhs); - final Field[] fields = clazz.getDeclaredFields(); - AccessibleObject.setAccessible(fields, true); - for (int i = 0; i < fields.length && builder.isEquals; i++) { - final Field f = fields[i]; - if (false == ArrayUtil.contains(excludeFields, f.getName()) - && (f.getName().indexOf('$') == -1) - && (useTransients || !Modifier.isTransient(f.getModifiers())) - && (!Modifier.isStatic(f.getModifiers()))) { - try { - builder.append(f.get(lhs), f.get(rhs)); - } catch (final IllegalAccessException e) { - //this can't happen. Would get a Security exception instead - //throw a runtime exception in case the impossible happens. - throw new InternalError("Unexpected IllegalAccessException"); - } - } - } - } finally { - unregister(lhs, rhs); - } - } + try { + register(lhs, rhs); + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (int i = 0; i < fields.length && builder.isEquals; i++) { + final Field f = fields[i]; + if (false == ArrayUtil.contains(excludeFields, f.getName()) + && (f.getName().indexOf('$') == -1) + && (useTransients || !Modifier.isTransient(f.getModifiers())) + && (!Modifier.isStatic(f.getModifiers()))) { + try { + builder.append(f.get(lhs), f.get(rhs)); + } catch (final IllegalAccessException e) { + //this can't happen. Would get a Security exception instead + //throw a runtime exception in case the impossible happens. + throw new InternalError("Unexpected IllegalAccessException"); + } + } + } + } finally { + unregister(lhs, rhs); + } + } - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - /** - *

Adds the result of super.equals() to this builder.

- * - * @param superEquals the result of calling super.equals() - * @return EqualsBuilder - used to chain calls. - * @since 2.0 - */ - public EqualsBuilder appendSuper(final boolean superEquals) { - if (isEquals == false) { - return this; - } - isEquals = superEquals; - return this; - } + /** + *

Adds the result of super.equals() to this builder.

+ * + * @param superEquals the result of calling super.equals() + * @return EqualsBuilder - used to chain calls. + * @since 2.0 + */ + public EqualsBuilder appendSuper(final boolean superEquals) { + if (isEquals == false) { + return this; + } + isEquals = superEquals; + return this; + } - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - /** - *

Test if two Objects are equal using their - * equals method.

- * - * @param lhs the left hand object - * @param rhs the right hand object - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final Object lhs, final Object rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - final Class lhsClass = lhs.getClass(); - if (!lhsClass.isArray()) { - // The simple case, not an array, just test the element - isEquals = lhs.equals(rhs); - } else if (lhs.getClass() != rhs.getClass()) { - // Here when we compare different dimensions, for example: a boolean[][] to a boolean[] - this.setEquals(false); - } - // 'Switch' on type of array, to dispatch to the correct handler - // This handles multi dimensional arrays of the same depth - else if (lhs instanceof long[]) { - append((long[]) lhs, (long[]) rhs); - } else if (lhs instanceof int[]) { - append((int[]) lhs, (int[]) rhs); - } else if (lhs instanceof short[]) { - append((short[]) lhs, (short[]) rhs); - } else if (lhs instanceof char[]) { - append((char[]) lhs, (char[]) rhs); - } else if (lhs instanceof byte[]) { - append((byte[]) lhs, (byte[]) rhs); - } else if (lhs instanceof double[]) { - append((double[]) lhs, (double[]) rhs); - } else if (lhs instanceof float[]) { - append((float[]) lhs, (float[]) rhs); - } else if (lhs instanceof boolean[]) { - append((boolean[]) lhs, (boolean[]) rhs); - } else { - // Not an array of primitives - append((Object[]) lhs, (Object[]) rhs); - } - return this; - } + /** + *

Test if two Objects are equal using their + * equals method.

+ * + * @param lhs the left hand object + * @param rhs the right hand object + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final Object lhs, final Object rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + final Class lhsClass = lhs.getClass(); + if (!lhsClass.isArray()) { + // The simple case, not an array, just test the element + isEquals = lhs.equals(rhs); + } else if (lhs.getClass() != rhs.getClass()) { + // Here when we compare different dimensions, for example: a boolean[][] to a boolean[] + this.setEquals(false); + } + // 'Switch' on type of array, to dispatch to the correct handler + // This handles multi dimensional arrays of the same depth + else if (lhs instanceof long[]) { + append((long[]) lhs, (long[]) rhs); + } else if (lhs instanceof int[]) { + append((int[]) lhs, (int[]) rhs); + } else if (lhs instanceof short[]) { + append((short[]) lhs, (short[]) rhs); + } else if (lhs instanceof char[]) { + append((char[]) lhs, (char[]) rhs); + } else if (lhs instanceof byte[]) { + append((byte[]) lhs, (byte[]) rhs); + } else if (lhs instanceof double[]) { + append((double[]) lhs, (double[]) rhs); + } else if (lhs instanceof float[]) { + append((float[]) lhs, (float[]) rhs); + } else if (lhs instanceof boolean[]) { + append((boolean[]) lhs, (boolean[]) rhs); + } else { + // Not an array of primitives + append((Object[]) lhs, (Object[]) rhs); + } + return this; + } - /** - *

- * Test if two long s are equal. - *

- * - * @param lhs - * the left hand long - * @param rhs - * the right hand long - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final long lhs, final long rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

+ * Test if two long s are equal. + *

+ * + * @param lhs the left hand long + * @param rhs the right hand long + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final long lhs, final long rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

Test if two ints are equal.

- * - * @param lhs the left hand int - * @param rhs the right hand int - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final int lhs, final int rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

Test if two ints are equal.

+ * + * @param lhs the left hand int + * @param rhs the right hand int + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final int lhs, final int rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

Test if two shorts are equal.

- * - * @param lhs the left hand short - * @param rhs the right hand short - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final short lhs, final short rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

Test if two shorts are equal.

+ * + * @param lhs the left hand short + * @param rhs the right hand short + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final short lhs, final short rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

Test if two chars are equal.

- * - * @param lhs the left hand char - * @param rhs the right hand char - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final char lhs, final char rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

Test if two chars are equal.

+ * + * @param lhs the left hand char + * @param rhs the right hand char + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final char lhs, final char rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

Test if two bytes are equal.

- * - * @param lhs the left hand byte - * @param rhs the right hand byte - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final byte lhs, final byte rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

Test if two bytes are equal.

+ * + * @param lhs the left hand byte + * @param rhs the right hand byte + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final byte lhs, final byte rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

Test if two doubles are equal by testing that the - * pattern of bits returned by doubleToLong are equal.

- * - *

This handles NaNs, Infinities, and -0.0.

- * - *

It is compatible with the hash code generated by - * HashCodeBuilder.

- * - * @param lhs the left hand double - * @param rhs the right hand double - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final double lhs, final double rhs) { - if (isEquals == false) { - return this; - } - return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); - } + /** + *

Test if two doubles are equal by testing that the + * pattern of bits returned by doubleToLong are equal.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @param lhs the left hand double + * @param rhs the right hand double + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final double lhs, final double rhs) { + if (isEquals == false) { + return this; + } + return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); + } - /** - *

Test if two floats are equal byt testing that the - * pattern of bits returned by doubleToLong are equal.

- * - *

This handles NaNs, Infinities, and -0.0.

- * - *

It is compatible with the hash code generated by - * HashCodeBuilder.

- * - * @param lhs the left hand float - * @param rhs the right hand float - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final float lhs, final float rhs) { - if (isEquals == false) { - return this; - } - return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); - } + /** + *

Test if two floats are equal byt testing that the + * pattern of bits returned by doubleToLong are equal.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @param lhs the left hand float + * @param rhs the right hand float + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final float lhs, final float rhs) { + if (isEquals == false) { + return this; + } + return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); + } - /** - *

Test if two booleanss are equal.

- * - * @param lhs the left hand boolean - * @param rhs the right hand boolean - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final boolean lhs, final boolean rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

Test if two booleanss are equal.

+ * + * @param lhs the left hand boolean + * @param rhs the right hand boolean + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final boolean lhs, final boolean rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

Performs a deep comparison of two Object arrays.

- * - *

This also will be called for the top level of - * multi-dimensional, ragged, and multi-typed arrays.

- * - * @param lhs the left hand Object[] - * @param rhs the right hand Object[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Performs a deep comparison of two Object arrays.

+ * + *

This also will be called for the top level of + * multi-dimensional, ragged, and multi-typed arrays.

+ * + * @param lhs the left hand Object[] + * @param rhs the right hand Object[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of long. Length and all - * values are compared.

- * - *

The method {@link #append(long, long)} is used.

- * - * @param lhs the left hand long[] - * @param rhs the right hand long[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final long[] lhs, final long[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of long. Length and all + * values are compared.

+ * + *

The method {@link #append(long, long)} is used.

+ * + * @param lhs the left hand long[] + * @param rhs the right hand long[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final long[] lhs, final long[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of int. Length and all - * values are compared.

- * - *

The method {@link #append(int, int)} is used.

- * - * @param lhs the left hand int[] - * @param rhs the right hand int[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final int[] lhs, final int[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of int. Length and all + * values are compared.

+ * + *

The method {@link #append(int, int)} is used.

+ * + * @param lhs the left hand int[] + * @param rhs the right hand int[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final int[] lhs, final int[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of short. Length and all - * values are compared.

- * - *

The method {@link #append(short, short)} is used.

- * - * @param lhs the left hand short[] - * @param rhs the right hand short[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final short[] lhs, final short[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of short. Length and all + * values are compared.

+ * + *

The method {@link #append(short, short)} is used.

+ * + * @param lhs the left hand short[] + * @param rhs the right hand short[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final short[] lhs, final short[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of char. Length and all - * values are compared.

- * - *

The method {@link #append(char, char)} is used.

- * - * @param lhs the left hand char[] - * @param rhs the right hand char[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final char[] lhs, final char[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of char. Length and all + * values are compared.

+ * + *

The method {@link #append(char, char)} is used.

+ * + * @param lhs the left hand char[] + * @param rhs the right hand char[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final char[] lhs, final char[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of byte. Length and all - * values are compared.

- * - *

The method {@link #append(byte, byte)} is used.

- * - * @param lhs the left hand byte[] - * @param rhs the right hand byte[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of byte. Length and all + * values are compared.

+ * + *

The method {@link #append(byte, byte)} is used.

+ * + * @param lhs the left hand byte[] + * @param rhs the right hand byte[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of double. Length and all - * values are compared.

- * - *

The method {@link #append(double, double)} is used.

- * - * @param lhs the left hand double[] - * @param rhs the right hand double[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final double[] lhs, final double[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of double. Length and all + * values are compared.

+ * + *

The method {@link #append(double, double)} is used.

+ * + * @param lhs the left hand double[] + * @param rhs the right hand double[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final double[] lhs, final double[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of float. Length and all - * values are compared.

- * - *

The method {@link #append(float, float)} is used.

- * - * @param lhs the left hand float[] - * @param rhs the right hand float[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final float[] lhs, final float[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of float. Length and all + * values are compared.

+ * + *

The method {@link #append(float, float)} is used.

+ * + * @param lhs the left hand float[] + * @param rhs the right hand float[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final float[] lhs, final float[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Deep comparison of array of boolean. Length and all - * values are compared.

- * - *

The method {@link #append(boolean, boolean)} is used.

- * - * @param lhs the left hand boolean[] - * @param rhs the right hand boolean[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

Deep comparison of array of boolean. Length and all + * values are compared.

+ * + *

The method {@link #append(boolean, boolean)} is used.

+ * + * @param lhs the left hand boolean[] + * @param rhs the right hand boolean[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

Returns true if the fields that have been checked - * are all equal.

- * - * @return boolean - */ - public boolean isEquals() { - return this.isEquals; - } + /** + *

Returns true if the fields that have been checked + * are all equal.

+ * + * @return boolean + */ + public boolean isEquals() { + return this.isEquals; + } - /** - *

Returns true if the fields that have been checked - * are all equal.

- * - * @return true if all of the fields that have been checked - * are equal, false otherwise. - * - * @since 3.0 - */ - @Override - public Boolean build() { - return isEquals(); - } + /** + *

Returns true if the fields that have been checked + * are all equal.

+ * + * @return true if all of the fields that have been checked + * are equal, false otherwise. + * @since 3.0 + */ + @Override + public Boolean build() { + return isEquals(); + } - /** - * Sets the isEquals value. - * - * @param isEquals The value to set. - * @since 2.1 - */ - @SuppressWarnings("SameParameterValue") - protected void setEquals(boolean isEquals) { - this.isEquals = isEquals; - } + /** + * Sets the isEquals value. + * + * @param isEquals The value to set. + * @since 2.1 + */ + protected void setEquals(boolean isEquals) { + this.isEquals = isEquals; + } - /** - * Reset the EqualsBuilder so you can use the same object again - * @since 2.5 - */ - public void reset() { - this.isEquals = true; - } + /** + * Reset the EqualsBuilder so you can use the same object again + * + * @since 2.5 + */ + public void reset() { + this.isEquals = true; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java new file mode 100644 index 000000000..72baae414 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -0,0 +1,430 @@ +package cn.hutool.core.date; + +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.util.StrUtil; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedHashSet; + +/** + * 针对{@link Calendar} 对象封装工具类 + * + * @author looly + * @since 5.3.0 + */ +public class CalendarUtil { + + /** + * 创建Calendar对象,时间为默认时区的当前时间 + * + * @return Calendar对象 + * @since 4.6.6 + */ + public static Calendar calendar() { + return Calendar.getInstance(); + } + + /** + * 转换为Calendar对象 + * + * @param date 日期对象 + * @return Calendar对象 + */ + public static Calendar calendar(Date date) { + if (date instanceof DateTime) { + return ((DateTime) date).toCalendar(); + } else { + return calendar(date.getTime()); + } + } + + /** + * 转换为Calendar对象 + * + * @param millis 时间戳 + * @return Calendar对象 + */ + public static Calendar calendar(long millis) { + final Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(millis); + return cal; + } + + /** + * 是否为上午 + * + * @param calendar {@link Calendar} + * @return 是否为上午 + */ + public static boolean isAM(Calendar calendar) { + return Calendar.AM == calendar.get(Calendar.AM_PM); + } + + /** + * 是否为下午 + * + * @param calendar {@link Calendar} + * @return 是否为下午 + */ + public static boolean isPM(Calendar calendar) { + return Calendar.PM == calendar.get(Calendar.AM_PM); + } + + /** + * 修改日期为某个时间字段起始时间 + * + * @param calendar {@link Calendar} + * @param dateField 时间字段 + * @return 原{@link Calendar} + */ + public static Calendar truncate(Calendar calendar, DateField dateField) { + return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.TRUNCATE); + } + + /** + * 修改日期为某个时间字段四舍五入时间 + * + * @param calendar {@link Calendar} + * @param dateField 时间字段 + * @return 原{@link Calendar} + */ + public static Calendar round(Calendar calendar, DateField dateField) { + return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.ROUND); + } + + /** + * 修改日期为某个时间字段结束时间 + * + * @param calendar {@link Calendar} + * @param dateField 时间字段 + * @return 原{@link Calendar} + */ + public static Calendar ceiling(Calendar calendar, DateField dateField) { + return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.CEILING); + } + + /** + * 获取秒级别的开始时间,即忽略毫秒部分 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.6.2 + */ + public static Calendar beginOfSecond(Calendar calendar) { + return truncate(calendar, DateField.SECOND); + } + + /** + * 获取秒级别的结束时间,即毫秒设置为999 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.6.2 + */ + public static Calendar endOfSecond(Calendar calendar) { + return ceiling(calendar, DateField.SECOND); + } + + /** + * 获取某天的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfDay(Calendar calendar) { + return truncate(calendar, DateField.DAY_OF_MONTH); + } + + /** + * 获取某天的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfDay(Calendar calendar) { + return ceiling(calendar, DateField.DAY_OF_MONTH); + } + + /** + * 获取给定日期当前周的开始时间,周一定为一周的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfWeek(Calendar calendar) { + return beginOfWeek(calendar, true); + } + + /** + * 获取给定日期当前周的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天) + * @return {@link Calendar} + * @since 3.1.2 + */ + public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) { + calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY); + // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH + return truncate(calendar, DateField.WEEK_OF_MONTH); + } + + /** + * 获取某周的结束时间,周日定为一周的结束 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfWeek(Calendar calendar) { + return endOfWeek(calendar, true); + } + + /** + * 获取某周的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天) + * @return {@link Calendar} + */ + public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) { + calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY); + // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH + return ceiling(calendar, DateField.WEEK_OF_MONTH); + } + + /** + * 获取某月的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfMonth(Calendar calendar) { + return truncate(calendar, DateField.MONTH); + } + + /** + * 获取某月的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfMonth(Calendar calendar) { + return ceiling(calendar, DateField.MONTH); + } + + /** + * 获取某季度的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.1.0 + */ + public static Calendar beginOfQuarter(Calendar calendar) { + //noinspection MagicConstant + calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3); + calendar.set(Calendar.DAY_OF_MONTH, 1); + return beginOfDay(calendar); + } + + /** + * 获取某季度的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.1.0 + */ + public static Calendar endOfQuarter(Calendar calendar) { + //noinspection MagicConstant + calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2); + calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); + return endOfDay(calendar); + } + + /** + * 获取某年的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfYear(Calendar calendar) { + return truncate(calendar, DateField.YEAR); + } + + /** + * 获取某年的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfYear(Calendar calendar) { + return ceiling(calendar, DateField.YEAR); + } + + /** + * 比较两个日期是否为同一天 + * + * @param cal1 日期1 + * @param cal2 日期2 + * @return 是否为同一天 + */ + public static boolean isSameDay(Calendar cal1, Calendar cal2) { + if (cal1 == null || cal2 == null) { + throw new IllegalArgumentException("The date must not be null"); + } + return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && // + cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && // + cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); + } + + /** + * 获得指定日期区间内的年份和季节
+ * + * @param startDate 起始日期(包含) + * @param endDate 结束日期(包含) + * @return 季度列表 ,元素类似于 20132 + * @since 4.1.15 + */ + public static LinkedHashSet yearAndQuarter(long startDate, long endDate) { + LinkedHashSet quarters = new LinkedHashSet<>(); + final Calendar cal = calendar(startDate); + while (startDate <= endDate) { + // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环 + quarters.add(yearAndQuarter(cal)); + + cal.add(Calendar.MONTH, 3); + startDate = cal.getTimeInMillis(); + } + + return quarters; + } + + /** + * 获得指定日期年份和季节
+ * 格式:[20131]表示2013年第一季度 + * + * @param cal 日期 + */ + public static String yearAndQuarter(Calendar cal) { + return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); + } + + /** + * 获取指定日期字段的最小值,例如分钟的最小值是0 + * + * @param calendar {@link Calendar} + * @param dateField {@link DateField} + * @return 字段最小值 + * @see Calendar#getActualMinimum(int) + * @since 4.5.7 + */ + public static int getBeginValue(Calendar calendar, int dateField) { + if (Calendar.DAY_OF_WEEK == dateField) { + return calendar.getFirstDayOfWeek(); + } + return calendar.getActualMinimum(dateField); + } + + /** + * 获取指定日期字段的最大值,例如分钟的最大值是59 + * + * @param calendar {@link Calendar} + * @param dateField {@link DateField} + * @return 字段最大值 + * @see Calendar#getActualMaximum(int) + * @since 4.5.7 + */ + public static int getEndValue(Calendar calendar, int dateField) { + if (Calendar.DAY_OF_WEEK == dateField) { + return (calendar.getFirstDayOfWeek() + 6) % 7; + } + return calendar.getActualMaximum(dateField); + } + + /** + * Calendar{@link Instant}对象 + * + * @param calendar Date对象 + * @return {@link Instant}对象 + * @since 5.0.5 + */ + public static Instant toInstant(Calendar calendar) { + return null == calendar ? null : calendar.toInstant(); + } + + /** + * {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区 + * + * @param calendar {@link Calendar} + * @return {@link LocalDateTime} + * @since 5.0.5 + */ + public static LocalDateTime toLocalDateTime(Calendar calendar) { + return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId()); + } + + /** + * {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期 + * + * @param calendar1 日期1 + * @param calendar2 日期2 + * @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0 + * @since 4.6.2 + */ + public static int compare(Calendar calendar1, Calendar calendar2) { + return CompareUtil.compare(calendar1, calendar2); + } + + /** + * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 + * + * @param birthday 生日 + * @param dateToCompare 需要对比的日期 + * @return 年龄 + */ + public static int age(Calendar birthday, Calendar dateToCompare) { + return age(birthday.getTimeInMillis(), dateToCompare.getTimeInMillis()); + } + + /** + * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 + * + * @param birthday 生日 + * @param dateToCompare 需要对比的日期 + * @return 年龄 + */ + protected static int age(long birthday, long dateToCompare) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(dateToCompare); + + if (cal.before(birthday)) { + throw new IllegalArgumentException("Birthday is after dateToCompare!"); + } + + final int year = cal.get(Calendar.YEAR); + final int month = cal.get(Calendar.MONTH); + final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); + + cal.setTimeInMillis(birthday); + int age = year - cal.get(Calendar.YEAR); + + final int monthBirth = cal.get(Calendar.MONTH); + if (month == monthBirth) { + + final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); + final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); + if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { + // 如果生日在当月,但是未达到生日当天的日期,年龄减一 + age--; + } + } else if (month < monthBirth) { + // 如果当前月份未达到生日的月份,年龄计算减一 + age--; + } + + return age; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index f0de7226e..6bd5f0bfd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -3,10 +3,10 @@ package cn.hutool.core.date; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateModifier.ModifyType; import cn.hutool.core.date.format.DateParser; import cn.hutool.core.date.format.DatePrinter; import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.PatternPool; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ReUtil; @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; * * @author xiaoleilu */ -public class DateUtil { +public class DateUtil extends CalendarUtil { /** * java.util.Date EEE MMM zzz 缩写数组 @@ -129,42 +129,6 @@ public class DateUtil { return new DateTime(temporalAccessor); } - /** - * 创建Calendar对象,时间为默认时区的当前时间 - * - * @return Calendar对象 - * @since 4.6.6 - */ - public static Calendar calendar() { - return Calendar.getInstance(); - } - - /** - * 转换为Calendar对象 - * - * @param date 日期对象 - * @return Calendar对象 - */ - public static Calendar calendar(Date date) { - if (date instanceof DateTime) { - return ((DateTime) date).toCalendar(); - } else { - return calendar(date.getTime()); - } - } - - /** - * 转换为Calendar对象 - * - * @param millis 时间戳 - * @return Calendar对象 - */ - public static Calendar calendar(long millis) { - final Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(millis); - return cal; - } - /** * 当前时间的时间戳 * @@ -359,17 +323,6 @@ public class DateUtil { return DateTime.of(date).isAM(); } - /** - * 是否为上午 - * - * @param calendar {@link Calendar} - * @return 是否为上午 - * @since 4.5.7 - */ - public static boolean isAM(Calendar calendar) { - return Calendar.AM == calendar.get(Calendar.AM_PM); - } - /** * 是否为下午 * @@ -490,29 +443,6 @@ public class DateUtil { } return yearAndQuarter(startDate.getTime(), endDate.getTime()); } - - /** - * 获得指定日期区间内的年份和季节
- * - * @param startDate 起始日期(包含) - * @param endDate 结束日期(包含) - * @return 季度列表 ,元素类似于 20132 - * @since 4.1.15 - */ - public static LinkedHashSet yearAndQuarter(long startDate, long endDate) { - LinkedHashSet quarters = new LinkedHashSet<>(); - final Calendar cal = calendar(startDate); - while (startDate <= endDate) { - // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环 - quarters.add(yearAndQuarter(cal)); - - cal.add(Calendar.MONTH, 3); - startDate = cal.getTimeInMillis(); - } - - return quarters; - } - // ------------------------------------ Format start ---------------------------------------------- /** @@ -986,18 +916,6 @@ public class DateUtil { return new DateTime(truncate(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段起始时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar truncate(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.TRUNCATE); - } - /** * 修改日期为某个时间字段四舍五入时间 * @@ -1010,18 +928,6 @@ public class DateUtil { return new DateTime(round(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段四舍五入时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar round(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.ROUND); - } - /** * 修改日期为某个时间字段结束时间 * @@ -1034,18 +940,6 @@ public class DateUtil { return new DateTime(ceiling(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段结束时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar ceiling(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.CEILING); - } - /** * 获取秒级别的开始时间,即忽略毫秒部分 * @@ -1068,28 +962,6 @@ public class DateUtil { return new DateTime(endOfSecond(calendar(date))); } - /** - * 获取秒级别的开始时间,即忽略毫秒部分 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.6.2 - */ - public static Calendar beginOfSecond(Calendar calendar) { - return truncate(calendar, DateField.SECOND); - } - - /** - * 获取秒级别的结束时间,即毫秒设置为999 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.6.2 - */ - public static Calendar endOfSecond(Calendar calendar) { - return ceiling(calendar, DateField.SECOND); - } - /** * 获取某天的开始时间 * @@ -1110,26 +982,6 @@ public class DateUtil { return new DateTime(endOfDay(calendar(date))); } - /** - * 获取某天的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfDay(Calendar calendar) { - return truncate(calendar, DateField.DAY_OF_MONTH); - } - - /** - * 获取某天的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfDay(Calendar calendar) { - return ceiling(calendar, DateField.DAY_OF_MONTH); - } - /** * 获取某周的开始时间,周一定为一周的开始时间 * @@ -1150,54 +1002,6 @@ public class DateUtil { return new DateTime(endOfWeek(calendar(date))); } - /** - * 获取给定日期当前周的开始时间,周一定为一周的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfWeek(Calendar calendar) { - return beginOfWeek(calendar, true); - } - - /** - * 获取给定日期当前周的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天) - * @return {@link Calendar} - * @since 3.1.2 - */ - public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) { - calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY); - // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH - return truncate(calendar, DateField.WEEK_OF_MONTH); - } - - /** - * 获取某周的结束时间,周日定为一周的结束 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfWeek(Calendar calendar) { - return endOfWeek(calendar, true); - } - - /** - * 获取某周的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天) - * @return {@link Calendar} - * @since 3.1.2 - */ - public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) { - calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY); - // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH - return ceiling(calendar, DateField.WEEK_OF_MONTH); - } - /** * 获取某月的开始时间 * @@ -1218,26 +1022,6 @@ public class DateUtil { return new DateTime(endOfMonth(calendar(date))); } - /** - * 获取某月的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfMonth(Calendar calendar) { - return truncate(calendar, DateField.MONTH); - } - - /** - * 获取某月的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfMonth(Calendar calendar) { - return ceiling(calendar, DateField.MONTH); - } - /** * 获取某季度的开始时间 * @@ -1258,34 +1042,6 @@ public class DateUtil { return new DateTime(endOfQuarter(calendar(date))); } - /** - * 获取某季度的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.1.0 - */ - public static Calendar beginOfQuarter(Calendar calendar) { - //noinspection MagicConstant - calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3); - calendar.set(Calendar.DAY_OF_MONTH, 1); - return beginOfDay(calendar); - } - - /** - * 获取某季度的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.1.0 - */ - public static Calendar endOfQuarter(Calendar calendar) { - //noinspection MagicConstant - calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2); - calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); - return endOfDay(calendar); - } - /** * 获取某年的开始时间 * @@ -1305,27 +1061,6 @@ public class DateUtil { public static DateTime endOfYear(Date date) { return new DateTime(endOfYear(calendar(date))); } - - /** - * 获取某年的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfYear(Calendar calendar) { - return truncate(calendar, DateField.YEAR); - } - - /** - * 获取某年的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfYear(Calendar calendar) { - return ceiling(calendar, DateField.YEAR); - } - // --------------------------------------------------- Offset for now /** @@ -1554,9 +1289,9 @@ public class DateUtil { /** * 计算指定指定时间区间内的周数 * - * @param beginDate 开始时间 - * @param endDate 结束时间 - * @param isReset 是否重置时间为起始时间 + * @param beginDate 开始时间 + * @param endDate 结束时间 + * @param isReset 是否重置时间为起始时间 * @return 周数 */ public static long betweenWeek(Date beginDate, Date endDate, boolean isReset) { @@ -1686,23 +1421,6 @@ public class DateUtil { return isSameDay(calendar(date1), calendar(date2)); } - /** - * 比较两个日期是否为同一天 - * - * @param cal1 日期1 - * @param cal2 日期2 - * @return 是否为同一天 - * @since 4.1.13 - */ - public static boolean isSameDay(Calendar cal1, Calendar cal2) { - if (cal1 == null || cal2 == null) { - throw new IllegalArgumentException("The date must not be null"); - } - return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && // - cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && // - cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); - } - /** * 计时,常用于记录某段代码的执行时间,单位:纳秒 * @@ -1863,41 +1581,16 @@ public class DateUtil { /** * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 * - * @param birthDay 生日 + * @param birthday 生日 * @param dateToCompare 需要对比的日期 * @return 年龄 */ - public static int age(Date birthDay, Date dateToCompare) { - Calendar cal = Calendar.getInstance(); - cal.setTime(dateToCompare); - - if (cal.before(birthDay)) { - throw new IllegalArgumentException(StrUtil.format("Birthday is after date {}!", formatDate(dateToCompare))); + public static int age(Date birthday, Date dateToCompare) { + Assert.notNull(birthday, "Birthday can not be null !"); + if (null == dateToCompare) { + dateToCompare = date(); } - - final int year = cal.get(Calendar.YEAR); - final int month = cal.get(Calendar.MONTH); - final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); - final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); - - cal.setTime(birthDay); - int age = year - cal.get(Calendar.YEAR); - - final int monthBirth = cal.get(Calendar.MONTH); - if (month == monthBirth) { - - final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); - final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); - if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { - // 如果生日在当月,但是未达到生日当天的日期,年龄减一 - age--; - } - } else if (month < monthBirth) { - // 如果当前月份未达到生日的月份,年龄计算减一 - age--; - } - - return age; + return age(birthday.getTime(), dateToCompare.getTime()); } /** @@ -2042,38 +1735,6 @@ public class DateUtil { return Zodiac.getChineseZodiac(year); } - /** - * 获取指定日期字段的最小值,例如分钟的最小值是0 - * - * @param calendar {@link Calendar} - * @param dateField {@link DateField} - * @return 字段最小值 - * @see Calendar#getActualMinimum(int) - * @since 4.5.7 - */ - public static int getBeginValue(Calendar calendar, int dateField) { - if (Calendar.DAY_OF_WEEK == dateField) { - return calendar.getFirstDayOfWeek(); - } - return calendar.getActualMinimum(dateField); - } - - /** - * 获取指定日期字段的最大值,例如分钟的最大值是59 - * - * @param calendar {@link Calendar} - * @param dateField {@link DateField} - * @return 字段最大值 - * @see Calendar#getActualMaximum(int) - * @since 4.5.7 - */ - public static int getEndValue(Calendar calendar, int dateField) { - if (Calendar.DAY_OF_WEEK == dateField) { - return (calendar.getFirstDayOfWeek() + 6) % 7; - } - return calendar.getActualMaximum(dateField); - } - /** * {@code null}安全的日期比较,{@code null}对象排在末尾 * @@ -2086,18 +1747,6 @@ public class DateUtil { return CompareUtil.compare(date1, date2); } - /** - * {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期 - * - * @param calendar1 日期1 - * @param calendar2 日期2 - * @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0 - * @since 4.6.2 - */ - public static int compare(Calendar calendar1, Calendar calendar2) { - return CompareUtil.compare(calendar1, calendar2); - } - /** * 纳秒转毫秒 * @@ -2131,17 +1780,6 @@ public class DateUtil { return null == date ? null : date.toInstant(); } - /** - * Calendar{@link Instant}对象 - * - * @param calendar Date对象 - * @return {@link Instant}对象 - * @since 5.0.5 - */ - public static Instant toInstant(Calendar calendar) { - return null == calendar ? null : calendar.toInstant(); - } - /** * Date对象转换为{@link Instant}对象 * @@ -2189,21 +1827,10 @@ public class DateUtil { return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } - /** - * {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区 - * - * @param calendar {@link Calendar} - * @return {@link LocalDateTime} - * @since 5.0.5 - */ - public static LocalDateTime toLocalDateTime(Calendar calendar) { - return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId()); - } - /** * {@link Date} 转换为 {@link LocalDateTime},使用系统默认时区 * - * @param date {@link Calendar} + * @param date {@link Date} * @return {@link LocalDateTime} * @since 5.0.5 */ @@ -2214,16 +1841,6 @@ public class DateUtil { // ------------------------------------------------------------------------ Private method start - /** - * 获得指定日期年份和季节
- * 格式:[20131]表示2013年第一季度 - * - * @param cal 日期 - */ - private static String yearAndQuarter(Calendar cal) { - return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); - } - /** * 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期,空格后为时间:
* 将以下字符替换为"-" diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 3688a7f8b..39f9d550b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -329,28 +329,36 @@ public class DateUtilTest { Assert.assertEquals("20190321", ymd); } - @SuppressWarnings("ConstantConditions") @Test public void parseTest5() { // 测试时间解析 + //noinspection ConstantConditions String time = DateUtil.parse("22:12:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("22:12:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:12:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:12:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:2:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:02:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:2:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:02:01", time); + //noinspection ConstantConditions time = DateUtil.parse("22:2:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("22:02:01", time); + //noinspection ConstantConditions time = DateUtil.parse("2:22:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:22:01", time); // 测试两位时间解析 + //noinspection ConstantConditions time = DateUtil.parse("2:22").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:22:00", time); + //noinspection ConstantConditions time = DateUtil.parse("12:22").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("12:22:00", time); + //noinspection ConstantConditions time = DateUtil.parse("12:2").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("12:02:00", time); @@ -688,6 +696,14 @@ public class DateUtilTest { Assert.assertEquals(18, age); } + @Test(expected = IllegalArgumentException.class) + public void ageTest2(){ + String d1 = "2019-02-29"; + String d2 = "2018-02-28"; + final int age = DateUtil.age(DateUtil.parseDate(d1), DateUtil.parseDate(d2)); + Assert.assertEquals(18, age); + } + @Test public void isExpiredTest(){ DateTime startDate = DateUtil.parse("2019-12-01 17:02:30");