From b8e4a131d884e06436ee1c6844af3a0fca2b9040 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 24 Dec 2020 00:35:30 +0800 Subject: [PATCH] add methods --- CHANGELOG.md | 5 +- .../core/convert/impl/NumberConverter.java | 37 +++--- .../java/cn/hutool/core/date/DateTime.java | 18 +-- .../java/cn/hutool/core/date/DateUtil.java | 57 +++++++-- .../hutool/core/date/format/FormatCache.java | 10 +- .../java/cn/hutool/core/util/NumberUtil.java | 117 ++++++++++++------ .../core/convert/NumberConverterTest.java | 22 ++++ .../cn/hutool/core/date/DateUtilTest.java | 7 ++ .../cn/hutool/core/util/NumberUtilTest.java | 88 +++++++++---- 9 files changed, 250 insertions(+), 111 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/convert/NumberConverterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b6dfb104c..67d9a174b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.5.5 (2020-12-23) +# 5.5.5 (2020-12-24) ### 新特性 * 【core 】 URLUtil.normalize新增重载(pr#233@Gitee) @@ -11,11 +11,14 @@ * 【db 】 RedisDS实现序列化接口(pr#1323@Github) * 【poi 】 StyleUtil增加getFormat方法(pr#235@Gitee) * 【poi 】 增加ExcelDateUtil更多日期格式支持(issue#1316@Github) +* 【core 】 NumberUtil.toBigDecimal支持各类数字格式,如1,234.56等(issue#1334@Github) +* 【core 】 NumberUtil增加parseXXX方法(issue#1334@Github) ### Bug修复 * 【core 】 FileUtil.isSub相对路径判断问题(pr#1315@Github) * 【core 】 TreeUtil增加空判定(issue#I2ACCW@Gitee) * 【db 】 解决Hive获取表名失败问题(issue#I2AGLU@Gitee) +* 【core 】 修复DateUtil.parse未使用严格模式导致结果不正常的问题(issue#1332@Github) ------------------------------------------------------------------------------------------------------------- # 5.5.4 (2020-12-16) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 7d41bbb4c..e1b2f0c03 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -21,16 +21,16 @@ import java.util.function.Function; * 数字转换器
* 支持类型为:
* * * @author Looly @@ -80,7 +80,11 @@ public class NumberConverter extends AbstractConverter { return BooleanUtil.toByteObj((Boolean) value); } final String valueStr = toStrFunc.apply(value); - return StrUtil.isBlank(valueStr) ? null : Byte.valueOf(valueStr); + try{ + return StrUtil.isBlank(valueStr) ? null : Byte.valueOf(valueStr); + } catch (NumberFormatException e){ + return NumberUtil.parseNumber(valueStr).byteValue(); + } } else if (Short.class == targetType) { if (value instanceof Number) { return ((Number) value).shortValue(); @@ -88,7 +92,11 @@ public class NumberConverter extends AbstractConverter { return BooleanUtil.toShortObj((Boolean) value); } final String valueStr = toStrFunc.apply((value)); - return StrUtil.isBlank(valueStr) ? null : Short.valueOf(valueStr); + try{ + return StrUtil.isBlank(valueStr) ? null : Short.valueOf(valueStr); + } catch (NumberFormatException e){ + return NumberUtil.parseNumber(valueStr).shortValue(); + } } else if (Integer.class == targetType) { if (value instanceof Number) { return ((Number) value).intValue(); @@ -146,8 +154,7 @@ public class NumberConverter extends AbstractConverter { return BooleanUtil.toFloatObj((Boolean) value); } final String valueStr = toStrFunc.apply((value)); - return StrUtil.isBlank(valueStr) ? null : Float.valueOf(valueStr); - + return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseFloat(valueStr); } else if (Double.class == targetType) { if (value instanceof Number) { return ((Number) value).doubleValue(); @@ -155,7 +162,7 @@ public class NumberConverter extends AbstractConverter { return BooleanUtil.toDoubleObj((Boolean) value); } final String valueStr = toStrFunc.apply((value)); - return StrUtil.isBlank(valueStr) ? null : Double.valueOf(valueStr); + return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseDouble(valueStr); } else if (DoubleAdder.class == targetType) { //jdk8 新增 final Number number = convert(value, Long.class, toStrFunc); diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index be693e020..fb27f592d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -219,7 +219,7 @@ public class DateTime extends Date { * @see DatePattern */ public DateTime(CharSequence dateStr, String format) { - this(dateStr, new SimpleDateFormat(format)); + this(dateStr, DateUtil.newSimpleFormat(format)); } /** @@ -895,9 +895,7 @@ public class DateTime extends Date { */ public String toString(TimeZone timeZone) { if (null != timeZone) { - final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN); - simpleDateFormat.setTimeZone(timeZone); - return toString(simpleDateFormat); + return toString(DateUtil.newSimpleFormat(DatePattern.NORM_DATETIME_PATTERN, null, timeZone)); } return toString(DatePattern.NORM_DATETIME_FORMAT); } @@ -910,9 +908,7 @@ public class DateTime extends Date { */ public String toDateStr() { if (null != this.timeZone) { - final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_DATE_PATTERN); - simpleDateFormat.setTimeZone(this.timeZone); - return toString(simpleDateFormat); + return toString(DateUtil.newSimpleFormat(DatePattern.NORM_DATE_PATTERN, null, timeZone)); } return toString(DatePattern.NORM_DATE_FORMAT); } @@ -925,9 +921,7 @@ public class DateTime extends Date { */ public String toTimeStr() { if (null != this.timeZone) { - final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_TIME_PATTERN); - simpleDateFormat.setTimeZone(this.timeZone); - return toString(simpleDateFormat); + return toString(DateUtil.newSimpleFormat(DatePattern.NORM_TIME_PATTERN, null, timeZone)); } return toString(DatePattern.NORM_TIME_FORMAT); } @@ -940,9 +934,7 @@ public class DateTime extends Date { */ public String toString(String format) { if (null != this.timeZone) { - final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); - simpleDateFormat.setTimeZone(this.timeZone); - return toString(simpleDateFormat); + return toString(DateUtil.newSimpleFormat(format, null, timeZone)); } return toString(FastDateFormat.getInstance(format)); } 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 c08f46b7d..c4efd201f 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 @@ -14,14 +14,18 @@ import cn.hutool.core.util.StrUtil; import java.text.DateFormat; import java.text.SimpleDateFormat; - import java.time.Instant; import java.time.LocalDateTime; import java.time.Year; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; - -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -506,14 +510,11 @@ public class DateUtil extends CalendarUtil { return null; } - final SimpleDateFormat sdf = new SimpleDateFormat(format); + TimeZone timeZone = null; if (date instanceof DateTime) { - final TimeZone timeZone = ((DateTime) date).getTimeZone(); - if (null != timeZone) { - sdf.setTimeZone(timeZone); - } + timeZone = ((DateTime) date).getTimeZone(); } - return format(date, sdf); + return format(date, newSimpleFormat(format, null, timeZone)); } /** @@ -717,7 +718,7 @@ public class DateUtil extends CalendarUtil { * @since 4.5.18 */ public static DateTime parse(CharSequence dateStr, String format, Locale locale) { - return new DateTime(dateStr, new SimpleDateFormat(format, locale)); + return new DateTime(dateStr, DateUtil.newSimpleFormat(format, locale, null)); } /** @@ -1917,7 +1918,7 @@ public class DateUtil extends CalendarUtil { /** * 获得指定月份的总天数 * - * @param month 年份 + * @param month 年份 * @param isLeapYear 是否闰年 * @return 天 * @since 5.4.2 @@ -1926,6 +1927,40 @@ public class DateUtil extends CalendarUtil { return java.time.Month.of(month).length(isLeapYear); } + /** + * 创建{@link SimpleDateFormat},注意此对象非线程安全!
+ * 此对象默认为严格格式模式,即parse时如果格式不正确会报错。 + * + * @param pattern 表达式 + * @return {@link SimpleDateFormat} + * @since 5.5.5 + */ + public static SimpleDateFormat newSimpleFormat(String pattern) { + return newSimpleFormat(pattern, null, null); + } + + /** + * 创建{@link SimpleDateFormat},注意此对象非线程安全!
+ * 此对象默认为严格格式模式,即parse时如果格式不正确会报错。 + * + * @param pattern 表达式 + * @param locale {@link Locale},{@code null}表示默认 + * @param timeZone {@link TimeZone},{@code null}表示默认 + * @return {@link SimpleDateFormat} + * @since 5.5.5 + */ + public static SimpleDateFormat newSimpleFormat(String pattern, Locale locale, TimeZone timeZone) { + if (null == locale) { + locale = Locale.getDefault(Locale.Category.FORMAT); + } + final SimpleDateFormat format = new SimpleDateFormat(pattern, locale); + if (null != timeZone) { + format.setTimeZone(timeZone); + } + format.setLenient(false); + return format; + } + // ------------------------------------------------------------------------ Private method start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/date/format/FormatCache.java b/hutool-core/src/main/java/cn/hutool/core/date/format/FormatCache.java index a3491393d..a9f7d5390 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/format/FormatCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/format/FormatCache.java @@ -1,5 +1,8 @@ package cn.hutool.core.date.format; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Tuple; + import java.text.DateFormat; import java.text.Format; import java.text.SimpleDateFormat; @@ -8,9 +11,6 @@ import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Tuple; - /** * 日期格式化器缓存
* Thanks to Apache Commons Lang 3.5 @@ -43,7 +43,7 @@ abstract class FormatCache { * @param timeZone 时区,默认当前时区 * @param locale 地区,默认使用当前地区 * @return 格式化器 - * @throws IllegalArgumentException pattern 无效或null + * @throws IllegalArgumentException pattern 无效或{@code null} */ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { Assert.notBlank(pattern, "pattern must not be blank") ; @@ -74,7 +74,7 @@ abstract class FormatCache { * @param timeZone 时区,默认当前时区 * @param locale 地区,默认使用当前地区 * @return 格式化器 - * @throws IllegalArgumentException pattern 无效或null + * @throws IllegalArgumentException pattern 无效或{@code null} */ abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index aa8896bf1..11f6e94e9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -2044,6 +2044,11 @@ public class NumberUtil { * @since 4.0.9 */ public static BigDecimal toBigDecimal(String number) { + try{ + number = parseNumber(number).toString(); + } catch (Exception ignore){ + // 忽略解析错误 + } return StrUtil.isBlank(number) ? BigDecimal.ZERO : new BigDecimal(number); } @@ -2287,7 +2292,7 @@ public class NumberUtil { * *
 	 * 1、0x开头的视为16进制数字
-	 * 2、0开头的视为8进制数字
+	 * 2、0开头的忽略开头的0
 	 * 3、其它情况按照10进制转换
 	 * 4、空串返回0
 	 * 5、.123形式返回0(按照小于0的小数对待)
@@ -2304,18 +2309,16 @@ public class NumberUtil {
 			return 0;
 		}
 
-		// 对于带小数转换为整数采取去掉小数的策略
-		number = StrUtil.subBefore(number, CharUtil.DOT, false);
-		if (StrUtil.isEmpty(number)) {
-			return 0;
-		}
-
 		if (StrUtil.startWithIgnoreCase(number, "0x")) {
 			// 0x04表示16进制数
 			return Integer.parseInt(number.substring(2), 16);
 		}
 
-		return Integer.parseInt(removeNumberFlag(number));
+		try{
+			return Integer.parseInt(number);
+		} catch (NumberFormatException e){
+			return parseNumber(number).intValue();
+		}
 	}
 
 	/**
@@ -2323,9 +2326,11 @@ public class NumberUtil {
 	 *
 	 * 
 	 * 1、0x开头的视为16进制数字
-	 * 2、0开头的视为8进制数字
+	 * 2、0开头的忽略开头的0
 	 * 3、空串返回0
 	 * 4、其它情况按照10进制转换
+	 * 5、.123形式返回0(按照小于0的小数对待)
+	 * 6、123.56截取小数点之前的数字,忽略小数部分
 	 * 
* * @param number 数字,支持0x开头、0开头和普通十进制 @@ -2334,13 +2339,7 @@ public class NumberUtil { */ public static long parseLong(String number) { if (StrUtil.isBlank(number)) { - return 0; - } - - // 对于带小数转换为整数采取去掉小数的策略 - number = StrUtil.subBefore(number, CharUtil.DOT, false); - if (StrUtil.isEmpty(number)) { - return 0; + return 0L; } if (number.startsWith("0x")) { @@ -2348,7 +2347,63 @@ public class NumberUtil { return Long.parseLong(number.substring(2), 16); } - return Long.parseLong(removeNumberFlag(number)); + try{ + return Long.parseLong(number); + } catch (NumberFormatException e){ + return parseNumber(number).longValue(); + } + } + + /** + * 解析转换数字字符串为long型数字,规则如下: + * + *
+	 * 1、0开头的忽略开头的0
+	 * 2、空串返回0
+	 * 3、其它情况按照10进制转换
+	 * 4、.123形式返回0.123(按照小于0的小数对待)
+	 * 
+ * + * @param number 数字,支持0x开头、0开头和普通十进制 + * @return long + * @since 5.5.5 + */ + public static float parseFloat(String number) { + if (StrUtil.isBlank(number)) { + return 0f; + } + + try{ + return Float.parseFloat(number); + } catch (NumberFormatException e){ + return parseNumber(number).floatValue(); + } + } + + /** + * 解析转换数字字符串为long型数字,规则如下: + * + *
+	 * 1、0开头的忽略开头的0
+	 * 2、空串返回0
+	 * 3、其它情况按照10进制转换
+	 * 4、.123形式返回0.123(按照小于0的小数对待)
+	 * 
+ * + * @param number 数字,支持0x开头、0开头和普通十进制 + * @return long + * @since 5.5.5 + */ + public static double parseDouble(String number) { + if (StrUtil.isBlank(number)) { + return 0D; + } + + try{ + return Double.parseDouble(number); + } catch (NumberFormatException e){ + return parseNumber(number).doubleValue(); + } } /** @@ -2357,13 +2412,15 @@ public class NumberUtil { * @param numberStr Number字符串 * @return Number对象 * @since 4.1.15 + * @throws NumberFormatException 包装了{@link ParseException},当给定的数字字符串无法解析时抛出 */ - public static Number parseNumber(String numberStr) { - numberStr = removeNumberFlag(numberStr); + public static Number parseNumber(String numberStr) throws NumberFormatException{ try { return NumberFormat.getInstance().parse(numberStr); } catch (ParseException e) { - throw new UtilException(e); + final NumberFormatException nfe = new NumberFormatException(e.getMessage()); + nfe.initCause(e); + throw nfe; } } @@ -2509,25 +2566,5 @@ public class NumberUtil { return selectNum * mathNode(selectNum - 1); } } - - /** - * 去掉数字尾部的数字标识,例如12D,44.0F,22L中的最后一个字母 - * - * @param number 数字字符串 - * @return 去掉标识的字符串 - */ - private static String removeNumberFlag(String number) { - // 去掉千位分隔符 - if (StrUtil.contains(number, CharUtil.COMMA)) { - number = StrUtil.removeAll(number, CharUtil.COMMA); - } - // 去掉类型标识的结尾 - final int lastPos = number.length() - 1; - final char lastCharUpper = Character.toUpperCase(number.charAt(lastPos)); - if ('D' == lastCharUpper || 'L' == lastCharUpper || 'F' == lastCharUpper) { - number = StrUtil.subPre(number, lastPos); - } - return number; - } // ------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/NumberConverterTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/NumberConverterTest.java new file mode 100644 index 000000000..28632f2de --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/convert/NumberConverterTest.java @@ -0,0 +1,22 @@ +package cn.hutool.core.convert; + +import cn.hutool.core.convert.impl.NumberConverter; +import org.junit.Assert; +import org.junit.Test; + +public class NumberConverterTest { + + @Test + public void toDoubleTest(){ + final NumberConverter numberConverter = new NumberConverter(Double.class); + final Number convert = numberConverter.convert("1,234.55", null); + Assert.assertEquals(1234.55D, convert); + } + + @Test + public void toIntegerTest(){ + final NumberConverter numberConverter = new NumberConverter(Integer.class); + final Number convert = numberConverter.convert("1,234.55", null); + Assert.assertEquals(1234, convert); + } +} 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 0500d3827..f709f644d 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 @@ -831,4 +831,11 @@ public class DateUtilTest { final DateTime parse = DateUtil.parse(dt); Assert.assertEquals("2020-06-03 12:32:12", parse.toString()); } + + @Test(expected = DateException.class) + public void parseNotFitTest(){ + //https://github.com/looly/hutool/issues/1332 + // 在日期格式不匹配的时候,测试是否正常报错 + final DateTime parse = DateUtil.parse("2020-12-23", DatePattern.PURE_DATE_PATTERN); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java index cbd632a0f..a6546efcd 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java @@ -189,6 +189,12 @@ public class NumberUtilTest { BigDecimal bigDecimal = NumberUtil.toBigDecimal(a); Assert.assertEquals("3.14", bigDecimal.toString()); + + bigDecimal = NumberUtil.toBigDecimal("1,234.55"); + Assert.assertEquals("1234.55", bigDecimal.toString()); + + bigDecimal = NumberUtil.toBigDecimal("1,234.56D"); + Assert.assertEquals("1234.56", bigDecimal.toString()); } @Test @@ -205,21 +211,33 @@ public class NumberUtilTest { @Test public void parseIntTest() { - int v1 = NumberUtil.parseInt("0xFF"); - Assert.assertEquals(255, v1); - int v2 = NumberUtil.parseInt("010"); - Assert.assertEquals(10, v2); - int v3 = NumberUtil.parseInt("10"); - Assert.assertEquals(10, v3); - int v4 = NumberUtil.parseInt(" "); - Assert.assertEquals(0, v4); - int v5 = NumberUtil.parseInt("10F"); - Assert.assertEquals(10, v5); - int v6 = NumberUtil.parseInt("22.4D"); - Assert.assertEquals(22, v6); + int number = NumberUtil.parseInt("0xFF"); + Assert.assertEquals(255, number); - int v7 = NumberUtil.parseInt("0"); - Assert.assertEquals(0, v7); + // 0开头 + number = NumberUtil.parseInt("010"); + Assert.assertEquals(10, number); + + number = NumberUtil.parseInt("10"); + Assert.assertEquals(10, number); + + number = NumberUtil.parseInt(" "); + Assert.assertEquals(0, number); + + number = NumberUtil.parseInt("10F"); + Assert.assertEquals(10, number); + + number = NumberUtil.parseInt("22.4D"); + Assert.assertEquals(22, number); + + number = NumberUtil.parseInt("22.6D"); + Assert.assertEquals(22, number); + + number = NumberUtil.parseInt("0"); + Assert.assertEquals(0, number); + + number = NumberUtil.parseInt(".123"); + Assert.assertEquals(0, number); } @Test @@ -236,22 +254,40 @@ public class NumberUtilTest { // 千位分隔符去掉 int v1 = NumberUtil.parseNumber("1,482.00").intValue(); Assert.assertEquals(1482, v1); + + Number v2 = NumberUtil.parseNumber("1,482.00D"); + Assert.assertEquals(1482L, v2); } @Test public void parseLongTest() { - long v1 = NumberUtil.parseLong("0xFF"); - Assert.assertEquals(255L, v1); - long v2 = NumberUtil.parseLong("010"); - Assert.assertEquals(10L, v2); - long v3 = NumberUtil.parseLong("10"); - Assert.assertEquals(10L, v3); - long v4 = NumberUtil.parseLong(" "); - Assert.assertEquals(0L, v4); - long v5 = NumberUtil.parseLong("10F"); - Assert.assertEquals(10L, v5); - long v6 = NumberUtil.parseLong("22.4D"); - Assert.assertEquals(22L, v6); + long number = NumberUtil.parseLong("0xFF"); + Assert.assertEquals(255, number); + + // 0开头 + number = NumberUtil.parseLong("010"); + Assert.assertEquals(10, number); + + number = NumberUtil.parseLong("10"); + Assert.assertEquals(10, number); + + number = NumberUtil.parseLong(" "); + Assert.assertEquals(0, number); + + number = NumberUtil.parseLong("10F"); + Assert.assertEquals(10, number); + + number = NumberUtil.parseLong("22.4D"); + Assert.assertEquals(22, number); + + number = NumberUtil.parseLong("22.6D"); + Assert.assertEquals(22, number); + + number = NumberUtil.parseLong("0"); + Assert.assertEquals(0, number); + + number = NumberUtil.parseLong(".123"); + Assert.assertEquals(0, number); } @Test