Merge remote-tracking branch 'upstream/v5-dev' into v5-dev

This commit is contained in:
lzpeng723 2020-12-24 21:54:01 +08:00
commit d7afb728f0
19 changed files with 413 additions and 227 deletions

View File

@ -3,14 +3,22 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.5.5 (2020-12-17) # 5.5.5 (2020-12-24)
### 新特性 ### 新特性
* 【core 】 URLUtil.normalize新增重载pr#233@Gitee * 【core 】 URLUtil.normalize新增重载pr#233@Gitee
* 【core 】 PathUtil增加isSub和toAbsNormal方法 * 【core 】 PathUtil增加isSub和toAbsNormal方法
* 【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修复 ### Bug修复
* 【core 】 FileUtil.isSub相对路径判断问题pr#1315@Github * 【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) # 5.5.4 (2020-12-16)

View File

@ -21,16 +21,16 @@ import java.util.function.Function;
* 数字转换器<br> * 数字转换器<br>
* 支持类型为<br> * 支持类型为<br>
* <ul> * <ul>
* <li><code>java.lang.Byte</code></li> * <li>{@code java.lang.Byte}</li>
* <li><code>java.lang.Short</code></li> * <li>{@code java.lang.Short}</li>
* <li><code>java.lang.Integer</code></li> * <li>{@code java.lang.Integer}</li>
* <li><code>java.util.concurrent.atomic.AtomicInteger</code></li> * <li>{@code java.util.concurrent.atomic.AtomicInteger}</li>
* <li><code>java.lang.Long</code></li> * <li>{@code java.lang.Long}</li>
* <li><code>java.util.concurrent.atomic.AtomicLong</code></li> * <li>{@code java.util.concurrent.atomic.AtomicLong}</li>
* <li><code>java.lang.Float</code></li> * <li>{@code java.lang.Float}</li>
* <li><code>java.lang.Double</code></li> * <li>{@code java.lang.Double}</li>
* <li><code>java.math.BigDecimal</code></li> * <li>{@code java.math.BigDecimal}</li>
* <li><code>java.math.BigInteger</code></li> * <li>{@code java.math.BigInteger}</li>
* </ul> * </ul>
* *
* @author Looly * @author Looly
@ -80,7 +80,11 @@ public class NumberConverter extends AbstractConverter<Number> {
return BooleanUtil.toByteObj((Boolean) value); return BooleanUtil.toByteObj((Boolean) value);
} }
final String valueStr = toStrFunc.apply(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) { } else if (Short.class == targetType) {
if (value instanceof Number) { if (value instanceof Number) {
return ((Number) value).shortValue(); return ((Number) value).shortValue();
@ -88,7 +92,11 @@ public class NumberConverter extends AbstractConverter<Number> {
return BooleanUtil.toShortObj((Boolean) value); return BooleanUtil.toShortObj((Boolean) value);
} }
final String valueStr = toStrFunc.apply((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) { } else if (Integer.class == targetType) {
if (value instanceof Number) { if (value instanceof Number) {
return ((Number) value).intValue(); return ((Number) value).intValue();
@ -146,8 +154,7 @@ public class NumberConverter extends AbstractConverter<Number> {
return BooleanUtil.toFloatObj((Boolean) value); return BooleanUtil.toFloatObj((Boolean) value);
} }
final String valueStr = toStrFunc.apply((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) { } else if (Double.class == targetType) {
if (value instanceof Number) { if (value instanceof Number) {
return ((Number) value).doubleValue(); return ((Number) value).doubleValue();
@ -155,7 +162,7 @@ public class NumberConverter extends AbstractConverter<Number> {
return BooleanUtil.toDoubleObj((Boolean) value); return BooleanUtil.toDoubleObj((Boolean) value);
} }
final String valueStr = toStrFunc.apply((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) { } else if (DoubleAdder.class == targetType) {
//jdk8 新增 //jdk8 新增
final Number number = convert(value, Long.class, toStrFunc); final Number number = convert(value, Long.class, toStrFunc);

View File

@ -219,7 +219,7 @@ public class DateTime extends Date {
* @see DatePattern * @see DatePattern
*/ */
public DateTime(CharSequence dateStr, String format) { 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) { public String toString(TimeZone timeZone) {
if (null != timeZone) { if (null != timeZone) {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN); return toString(DateUtil.newSimpleFormat(DatePattern.NORM_DATETIME_PATTERN, null, timeZone));
simpleDateFormat.setTimeZone(timeZone);
return toString(simpleDateFormat);
} }
return toString(DatePattern.NORM_DATETIME_FORMAT); return toString(DatePattern.NORM_DATETIME_FORMAT);
} }
@ -910,9 +908,7 @@ public class DateTime extends Date {
*/ */
public String toDateStr() { public String toDateStr() {
if (null != this.timeZone) { if (null != this.timeZone) {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_DATE_PATTERN); return toString(DateUtil.newSimpleFormat(DatePattern.NORM_DATE_PATTERN, null, timeZone));
simpleDateFormat.setTimeZone(this.timeZone);
return toString(simpleDateFormat);
} }
return toString(DatePattern.NORM_DATE_FORMAT); return toString(DatePattern.NORM_DATE_FORMAT);
} }
@ -925,9 +921,7 @@ public class DateTime extends Date {
*/ */
public String toTimeStr() { public String toTimeStr() {
if (null != this.timeZone) { if (null != this.timeZone) {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_TIME_PATTERN); return toString(DateUtil.newSimpleFormat(DatePattern.NORM_TIME_PATTERN, null, timeZone));
simpleDateFormat.setTimeZone(this.timeZone);
return toString(simpleDateFormat);
} }
return toString(DatePattern.NORM_TIME_FORMAT); return toString(DatePattern.NORM_TIME_FORMAT);
} }
@ -940,9 +934,7 @@ public class DateTime extends Date {
*/ */
public String toString(String format) { public String toString(String format) {
if (null != this.timeZone) { if (null != this.timeZone) {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); return toString(DateUtil.newSimpleFormat(format, null, timeZone));
simpleDateFormat.setTimeZone(this.timeZone);
return toString(simpleDateFormat);
} }
return toString(FastDateFormat.getInstance(format)); return toString(FastDateFormat.getInstance(format));
} }

View File

@ -14,14 +14,18 @@ import cn.hutool.core.util.StrUtil;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.Year; import java.time.Year;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.*; 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.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -506,14 +510,11 @@ public class DateUtil extends CalendarUtil {
return null; return null;
} }
final SimpleDateFormat sdf = new SimpleDateFormat(format); TimeZone timeZone = null;
if (date instanceof DateTime) { if (date instanceof DateTime) {
final TimeZone timeZone = ((DateTime) date).getTimeZone(); timeZone = ((DateTime) date).getTimeZone();
if (null != timeZone) {
sdf.setTimeZone(timeZone);
}
} }
return format(date, sdf); return format(date, newSimpleFormat(format, null, timeZone));
} }
/** /**
@ -717,7 +718,7 @@ public class DateUtil extends CalendarUtil {
* @since 4.5.18 * @since 4.5.18
*/ */
public static DateTime parse(CharSequence dateStr, String format, Locale locale) { 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 是否闰年 * @param isLeapYear 是否闰年
* @return * @return
* @since 5.4.2 * @since 5.4.2
@ -1926,6 +1927,40 @@ public class DateUtil extends CalendarUtil {
return java.time.Month.of(month).length(isLeapYear); return java.time.Month.of(month).length(isLeapYear);
} }
/**
* 创建{@link SimpleDateFormat}注意此对象非线程安全<br>
* 此对象默认为严格格式模式即parse时如果格式不正确会报错
*
* @param pattern 表达式
* @return {@link SimpleDateFormat}
* @since 5.5.5
*/
public static SimpleDateFormat newSimpleFormat(String pattern) {
return newSimpleFormat(pattern, null, null);
}
/**
* 创建{@link SimpleDateFormat}注意此对象非线程安全<br>
* 此对象默认为严格格式模式即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 // ------------------------------------------------------------------------ Private method start
/** /**

View File

@ -1,5 +1,8 @@
package cn.hutool.core.date.format; 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.DateFormat;
import java.text.Format; import java.text.Format;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -8,9 +11,6 @@ import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Tuple;
/** /**
* 日期格式化器缓存<br> * 日期格式化器缓存<br>
* Thanks to Apache Commons Lang 3.5 * Thanks to Apache Commons Lang 3.5
@ -43,7 +43,7 @@ abstract class FormatCache<F extends Format> {
* @param timeZone 时区默认当前时区 * @param timeZone 时区默认当前时区
* @param locale 地区默认使用当前地区 * @param locale 地区默认使用当前地区
* @return 格式化器 * @return 格式化器
* @throws IllegalArgumentException pattern 无效或<code>null</code> * @throws IllegalArgumentException pattern 无效或{@code null}
*/ */
public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {
Assert.notBlank(pattern, "pattern must not be blank") ; Assert.notBlank(pattern, "pattern must not be blank") ;
@ -74,7 +74,7 @@ abstract class FormatCache<F extends Format> {
* @param timeZone 时区默认当前时区 * @param timeZone 时区默认当前时区
* @param locale 地区默认使用当前地区 * @param locale 地区默认使用当前地区
* @return 格式化器 * @return 格式化器
* @throws IllegalArgumentException pattern 无效或<code>null</code> * @throws IllegalArgumentException pattern 无效或{@code null}
*/ */
abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale);

View File

@ -74,7 +74,7 @@ public class TreeUtil {
List<Tree<E>> finalTreeList = CollUtil.newArrayList(); List<Tree<E>> finalTreeList = CollUtil.newArrayList();
for (Tree<E> node : treeList) { for (Tree<E> node : treeList) {
if (parentId.equals(node.getParentId())) { if (ObjectUtil.equals(parentId,node.getParentId())) {
finalTreeList.add(node); finalTreeList.add(node);
innerBuild(treeList, node, 0, treeNodeConfig.getDeep()); innerBuild(treeList, node, 0, treeNodeConfig.getDeep());
} }

View File

@ -2044,6 +2044,11 @@ public class NumberUtil {
* @since 4.0.9 * @since 4.0.9
*/ */
public static BigDecimal toBigDecimal(String number) { public static BigDecimal toBigDecimal(String number) {
try{
number = parseNumber(number).toString();
} catch (Exception ignore){
// 忽略解析错误
}
return StrUtil.isBlank(number) ? BigDecimal.ZERO : new BigDecimal(number); return StrUtil.isBlank(number) ? BigDecimal.ZERO : new BigDecimal(number);
} }
@ -2287,7 +2292,7 @@ public class NumberUtil {
* *
* <pre> * <pre>
* 10x开头的视为16进制数字 * 10x开头的视为16进制数字
* 20开头的视为8进制数字 * 20开头的忽略开头的0
* 3其它情况按照10进制转换 * 3其它情况按照10进制转换
* 4空串返回0 * 4空串返回0
* 5.123形式返回0按照小于0的小数对待 * 5.123形式返回0按照小于0的小数对待
@ -2304,18 +2309,16 @@ public class NumberUtil {
return 0; return 0;
} }
// 对于带小数转换为整数采取去掉小数的策略
number = StrUtil.subBefore(number, CharUtil.DOT, false);
if (StrUtil.isEmpty(number)) {
return 0;
}
if (StrUtil.startWithIgnoreCase(number, "0x")) { if (StrUtil.startWithIgnoreCase(number, "0x")) {
// 0x04表示16进制数 // 0x04表示16进制数
return Integer.parseInt(number.substring(2), 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 {
* *
* <pre> * <pre>
* 10x开头的视为16进制数字 * 10x开头的视为16进制数字
* 20开头的视为8进制数字 * 20开头的忽略开头的0
* 3空串返回0 * 3空串返回0
* 4其它情况按照10进制转换 * 4其它情况按照10进制转换
* 5.123形式返回0按照小于0的小数对待
* 6123.56截取小数点之前的数字忽略小数部分
* </pre> * </pre>
* *
* @param number 数字支持0x开头0开头和普通十进制 * @param number 数字支持0x开头0开头和普通十进制
@ -2334,13 +2339,7 @@ public class NumberUtil {
*/ */
public static long parseLong(String number) { public static long parseLong(String number) {
if (StrUtil.isBlank(number)) { if (StrUtil.isBlank(number)) {
return 0; return 0L;
}
// 对于带小数转换为整数采取去掉小数的策略
number = StrUtil.subBefore(number, CharUtil.DOT, false);
if (StrUtil.isEmpty(number)) {
return 0;
} }
if (number.startsWith("0x")) { if (number.startsWith("0x")) {
@ -2348,7 +2347,63 @@ public class NumberUtil {
return Long.parseLong(number.substring(2), 16); 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型数字规则如下
*
* <pre>
* 10开头的忽略开头的0
* 2空串返回0
* 3其它情况按照10进制转换
* 4.123形式返回0.123按照小于0的小数对待
* </pre>
*
* @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型数字规则如下
*
* <pre>
* 10开头的忽略开头的0
* 2空串返回0
* 3其它情况按照10进制转换
* 4.123形式返回0.123按照小于0的小数对待
* </pre>
*
* @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字符串 * @param numberStr Number字符串
* @return Number对象 * @return Number对象
* @since 4.1.15 * @since 4.1.15
* @throws NumberFormatException 包装了{@link ParseException}当给定的数字字符串无法解析时抛出
*/ */
public static Number parseNumber(String numberStr) { public static Number parseNumber(String numberStr) throws NumberFormatException{
numberStr = removeNumberFlag(numberStr);
try { try {
return NumberFormat.getInstance().parse(numberStr); return NumberFormat.getInstance().parse(numberStr);
} catch (ParseException e) { } 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); return selectNum * mathNode(selectNum - 1);
} }
} }
/**
* 去掉数字尾部的数字标识例如12D44.0F22L中的最后一个字母
*
* @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 // ------------------------------------------------------------------------------------------- Private method end
} }

View File

@ -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);
}
}

View File

@ -831,4 +831,11 @@ public class DateUtilTest {
final DateTime parse = DateUtil.parse(dt); final DateTime parse = DateUtil.parse(dt);
Assert.assertEquals("2020-06-03 12:32:12", parse.toString()); 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);
}
} }

View File

@ -189,6 +189,12 @@ public class NumberUtilTest {
BigDecimal bigDecimal = NumberUtil.toBigDecimal(a); BigDecimal bigDecimal = NumberUtil.toBigDecimal(a);
Assert.assertEquals("3.14", bigDecimal.toString()); 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 @Test
@ -205,21 +211,33 @@ public class NumberUtilTest {
@Test @Test
public void parseIntTest() { public void parseIntTest() {
int v1 = NumberUtil.parseInt("0xFF"); int number = NumberUtil.parseInt("0xFF");
Assert.assertEquals(255, v1); Assert.assertEquals(255, number);
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 v7 = NumberUtil.parseInt("0"); // 0开头
Assert.assertEquals(0, v7); 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 @Test
@ -236,22 +254,40 @@ public class NumberUtilTest {
// 千位分隔符去掉 // 千位分隔符去掉
int v1 = NumberUtil.parseNumber("1,482.00").intValue(); int v1 = NumberUtil.parseNumber("1,482.00").intValue();
Assert.assertEquals(1482, v1); Assert.assertEquals(1482, v1);
Number v2 = NumberUtil.parseNumber("1,482.00D");
Assert.assertEquals(1482L, v2);
} }
@Test @Test
public void parseLongTest() { public void parseLongTest() {
long v1 = NumberUtil.parseLong("0xFF"); long number = NumberUtil.parseLong("0xFF");
Assert.assertEquals(255L, v1); Assert.assertEquals(255, number);
long v2 = NumberUtil.parseLong("010");
Assert.assertEquals(10L, v2); // 0开头
long v3 = NumberUtil.parseLong("10"); number = NumberUtil.parseLong("010");
Assert.assertEquals(10L, v3); Assert.assertEquals(10, number);
long v4 = NumberUtil.parseLong(" ");
Assert.assertEquals(0L, v4); number = NumberUtil.parseLong("10");
long v5 = NumberUtil.parseLong("10F"); Assert.assertEquals(10, number);
Assert.assertEquals(10L, v5);
long v6 = NumberUtil.parseLong("22.4D"); number = NumberUtil.parseLong(" ");
Assert.assertEquals(22L, v6); 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 @Test

View File

@ -154,7 +154,12 @@ public class HandleHelper {
row.put(meta.getColumnLabel(i), getColumnValue(rs, i, type, null)); row.put(meta.getColumnLabel(i), getColumnValue(rs, i, type, null));
} }
if (withMetaInfo) { if (withMetaInfo) {
row.setTableName(meta.getTableName(1)); try {
row.setTableName(meta.getTableName(1));
} catch (SQLException ignore){
//issue#I2AGLU@Gitee
// Hive等NoSQL中无表的概念此处报错跳过
}
row.setFieldNames(row.keySet()); row.setFieldNames(row.keySet());
} }
return row; return row;

View File

@ -9,6 +9,7 @@ import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol;
import java.io.Closeable; import java.io.Closeable;
import java.io.Serializable;
/** /**
* Jedis数据源 * Jedis数据源
@ -16,7 +17,8 @@ import java.io.Closeable;
* @author looly * @author looly
* @since 3.2.3 * @since 3.2.3
*/ */
public class RedisDS implements Closeable{ public class RedisDS implements Closeable, Serializable {
private static final long serialVersionUID = -5605411972456177456L;
/** 默认配置文件 */ /** 默认配置文件 */
public final static String REDIS_CONFIG_PATH = "config/redis.setting"; public final static String REDIS_CONFIG_PATH = "config/redis.setting";
@ -29,7 +31,7 @@ public class RedisDS implements Closeable{
/** /**
* 创建RedisDS使用默认配置文件默认分组 * 创建RedisDS使用默认配置文件默认分组
* *
* @return {@link RedisDS} * @return RedisDS
*/ */
public static RedisDS create() { public static RedisDS create() {
return new RedisDS(); return new RedisDS();
@ -39,7 +41,7 @@ public class RedisDS implements Closeable{
* 创建RedisDS使用默认配置文件 * 创建RedisDS使用默认配置文件
* *
* @param group 配置文件中配置分组 * @param group 配置文件中配置分组
* @return {@link RedisDS} * @return RedisDS
*/ */
public static RedisDS create(String group) { public static RedisDS create(String group) {
return new RedisDS(group); return new RedisDS(group);
@ -50,7 +52,7 @@ public class RedisDS implements Closeable{
* *
* @param setting 配置文件 * @param setting 配置文件
* @param group 配置文件中配置分组 * @param group 配置文件中配置分组
* @return {@link RedisDS} * @return RedisDS
*/ */
public static RedisDS create(Setting setting, String group) { public static RedisDS create(Setting setting, String group) {
return new RedisDS(setting, group); return new RedisDS(setting, group);

View File

@ -19,7 +19,7 @@
<properties> <properties>
<!-- versions --> <!-- versions -->
<velocity.version>2.2</velocity.version> <velocity.version>2.2</velocity.version>
<beetl.version>3.2.4.RELEASE</beetl.version> <beetl.version>3.3.1.RELEASE</beetl.version>
<rythm.version>1.3.0</rythm.version> <rythm.version>1.3.0</rythm.version>
<freemarker.version>2.3.30</freemarker.version> <freemarker.version>2.3.30</freemarker.version>
<enjoy.version>4.9.03</enjoy.version> <enjoy.version>4.9.03</enjoy.version>

View File

@ -9,8 +9,6 @@ import cn.hutool.json.JSONUtil;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -142,26 +140,4 @@ public class HttpRequestTest {
HttpResponse execute = get.execute(); HttpResponse execute = get.execute();
Console.log(execute.body()); Console.log(execute.body());
} }
@Test
public void getByProxy(){
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
// 用户名密码, 若已添加白名单则不需要添加
final String ProxyUser = "t10757311156848";
final String ProxyPass = "ikm5uu44";
Authenticator.setDefault(new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(ProxyUser, ProxyPass.toCharArray());
}
});
final HttpResponse res = HttpRequest.get("https://httpbin.org/get")
.basicAuth(ProxyUser, ProxyPass)
.setHttpProxy("tps193.kdlapi.com", 15818).execute();
Console.log(res.body());
}
} }

View File

@ -0,0 +1,70 @@
package cn.hutool.poi.excel;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.ExcelNumberFormat;
/**
* Excel中日期判断读取处理等补充工具类
*
* @author looly
* @since 5.5.5
*/
public class ExcelDateUtil {
/**
* 某些特殊的自定义日期格式
*/
private static final int[] customFormats = new int[]{28, 30, 31, 32, 33, 55, 56, 57, 58};
public static boolean isDateFormat(Cell cell){
return isDateFormat(cell, null);
}
/**
* 判断是否日期格式
* @param cell 单元格
* @param cfEvaluator {@link ConditionalFormattingEvaluator}
* @return 是否日期格式
*/
public static boolean isDateFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator){
final ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator);
return isDateFormat(nf);
}
/**
* 判断是否日期格式
* @param numFmt {@link ExcelNumberFormat}
* @return 是否日期格式
*/
public static boolean isDateFormat(ExcelNumberFormat numFmt) {
return isDateFormat(numFmt.getIdx(), numFmt.getFormat());
}
/**
* 判断日期格式
*
* @param formatIndex 格式索引一般用于内建格式
* @param formatString 格式字符串
* @return 是否为日期格式
* @since 5.5.3
*/
public static boolean isDateFormat(int formatIndex, String formatString) {
// issue#1283@Github
if (ArrayUtil.contains(customFormats, formatIndex)) {
return true;
}
// 自定义格式判断
if (StrUtil.isNotEmpty(formatString) &&
StrUtil.containsAny(formatString, "", "星期", "aa")) {
// aa -> 周一
// aaa -> 星期一
return true;
}
return org.apache.poi.ss.usermodel.DateUtil.isADateFormat(formatIndex, formatString);
}
}

View File

@ -3,6 +3,7 @@ package cn.hutool.poi.excel.cell;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelDateUtil;
import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.StyleSet; import cn.hutool.poi.excel.StyleSet;
import cn.hutool.poi.excel.editors.TrimEditor; import cn.hutool.poi.excel.editors.TrimEditor;
@ -102,7 +103,7 @@ public class CellUtil {
if (null == cell) { if (null == cell) {
return null; return null;
} }
if(cell instanceof NullCell){ if (cell instanceof NullCell) {
return null == cellEditor ? null : cellEditor.edit(cell, null); return null == cellEditor ? null : cellEditor.edit(cell, null);
} }
if (null == cellType) { if (null == cellType) {
@ -111,7 +112,7 @@ public class CellUtil {
// 尝试获取合并单元格如果是合并单元格则重新获取单元格类型 // 尝试获取合并单元格如果是合并单元格则重新获取单元格类型
final Cell mergedCell = getMergedRegionCell(cell); final Cell mergedCell = getMergedRegionCell(cell);
if(mergedCell != cell){ if (mergedCell != cell) {
cell = mergedCell; cell = mergedCell;
cellType = cell.getCellTypeEnum(); cellType = cell.getCellTypeEnum();
} }
@ -235,7 +236,7 @@ public class CellUtil {
} }
/** /**
*获取单元格如果单元格不存在返回{@link NullCell} * 获取单元格如果单元格不存在返回{@link NullCell}
* *
* @param row Excel表的行 * @param row Excel表的行
* @param cellIndex 列号 * @param cellIndex 列号
@ -377,7 +378,7 @@ public class CellUtil {
* @since 5.1.5 * @since 5.1.5
*/ */
public static Cell getMergedRegionCell(Cell cell) { public static Cell getMergedRegionCell(Cell cell) {
if(null == cell){ if (null == cell) {
return null; return null;
} }
return ObjectUtil.defaultIfNull( return ObjectUtil.defaultIfNull(
@ -404,10 +405,10 @@ public class CellUtil {
/** /**
* 为特定单元格添加批注 * 为特定单元格添加批注
* *
* @param cell 单元格 * @param cell 单元格
* @param commentText 批注内容 * @param commentText 批注内容
* @param commentAuthor 作者 * @param commentAuthor 作者
* @param anchor 批注的位置大小等信息null表示使用默认 * @param anchor 批注的位置大小等信息null表示使用默认
* @since 5.4.8 * @since 5.4.8
*/ */
public static void setComment(Cell cell, String commentText, String commentAuthor, ClientAnchor anchor) { public static void setComment(Cell cell, String commentText, String commentAuthor, ClientAnchor anchor) {
@ -431,16 +432,16 @@ public class CellUtil {
// -------------------------------------------------------------------------------------------------------------- Private method start // -------------------------------------------------------------------------------------------------------------- Private method start
/** /**
* 获取合并单元格非合并单元格返回<code>null</code><br> * 获取合并单元格非合并单元格返回{@code null}<br>
* 传入的x,y坐标列行数可以是合并单元格范围内的任意一个单元格 * 传入的x,y坐标列行数可以是合并单元格范围内的任意一个单元格
* *
* @param sheet {@link Sheet} * @param sheet {@link Sheet}
* @param x 列号从0开始可以是合并单元格范围中的任意一列 * @param x 列号从0开始可以是合并单元格范围中的任意一列
* @param y 行号从0开始可以是合并单元格范围中的任意一行 * @param y 行号从0开始可以是合并单元格范围中的任意一行
* @return 合并单元格如果非合并单元格返回<code>null</code> * @return 合并单元格如果非合并单元格返回{@code null}
* @since 5.4.5 * @since 5.4.5
*/ */
private static Cell getCellIfMergedRegion(Sheet sheet, int x, int y){ private static Cell getCellIfMergedRegion(Sheet sheet, int x, int y) {
final int sheetMergeCount = sheet.getNumMergedRegions(); final int sheetMergeCount = sheet.getNumMergedRegions();
CellRangeAddress ca; CellRangeAddress ca;
for (int i = 0; i < sheetMergeCount; i++) { for (int i = 0; i < sheetMergeCount; i++) {
@ -463,9 +464,8 @@ public class CellUtil {
final CellStyle style = cell.getCellStyle(); final CellStyle style = cell.getCellStyle();
if (null != style) { if (null != style) {
final short formatIndex = style.getDataFormat();
// 判断是否为日期 // 判断是否为日期
if (isDateType(cell, formatIndex)) { if (ExcelDateUtil.isDateFormat(cell)) {
return DateUtil.date(cell.getDateCellValue());// 使用Hutool的DateTime包装 return DateUtil.date(cell.getDateCellValue());// 使用Hutool的DateTime包装
} }
@ -483,32 +483,5 @@ public class CellUtil {
// 某些Excel单元格值为double计算结果可能导致精度问题通过转换解决精度问题 // 某些Excel单元格值为double计算结果可能导致精度问题通过转换解决精度问题
return Double.parseDouble(NumberToTextConverter.toText(value)); return Double.parseDouble(NumberToTextConverter.toText(value));
} }
/**
* 是否为日期格式<br>
* 判断方式
*
* <pre>
* 1指定序号
* 2org.apache.poi.ss.usermodel.DateUtil.isADateFormat方法判定
* </pre>
*
* @param cell 单元格
* @param formatIndex 格式序号
* @return 是否为日期格式
*/
private static boolean isDateType(Cell cell, int formatIndex) {
// yyyy-MM-dd----- 14
// yyyy年m月d日---- 31
// yyyy年m月------- 57
// m月d日 ---------- 58
// HH:mm----------- 20
// h时mm分 -------- 32
if (formatIndex == 14 || formatIndex == 31 || formatIndex == 57 || formatIndex == 58 || formatIndex == 20 || formatIndex == 32) {
return true;
}
return org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell);
}
// -------------------------------------------------------------------------------------------------------------- Private method end // -------------------------------------------------------------------------------------------------------------- Private method end
} }

View File

@ -6,6 +6,7 @@ import cn.hutool.core.exceptions.DependencyException;
import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelDateUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler; import cn.hutool.poi.excel.sax.handler.RowHandler;
import cn.hutool.poi.exceptions.POIException; import cn.hutool.poi.exceptions.POIException;
import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
@ -209,16 +210,10 @@ public class ExcelSaxUtil {
* @param formatString 格式字符串 * @param formatString 格式字符串
* @return 是否为日期格式 * @return 是否为日期格式
* @since 5.5.3 * @since 5.5.3
* @see ExcelDateUtil#isDateFormat(int, String)
*/ */
public static boolean isDateFormat(int formatIndex, String formatString) { public static boolean isDateFormat(int formatIndex, String formatString) {
// https://blog.csdn.net/u014342130/article/details/50619503 return ExcelDateUtil.isDateFormat(formatIndex, formatString);
// issue#1283@Github
if (formatIndex == 28 || formatIndex == 31) {
// 28 -> m月d日
// 31 -> yyyy年m月d日
return true;
}
return org.apache.poi.ss.usermodel.DateUtil.isADateFormat(formatIndex, formatString);
} }
/** /**

View File

@ -1,8 +1,10 @@
package cn.hutool.poi.excel.style; package cn.hutool.poi.excel.style;
import cn.hutool.core.util.StrUtil;
import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.HorizontalAlignment;
@ -10,8 +12,6 @@ import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import cn.hutool.core.util.StrUtil;
/** /**
* Excel样式工具类 * Excel样式工具类
* *
@ -23,7 +23,7 @@ public class StyleUtil {
/** /**
* 克隆新的{@link CellStyle} * 克隆新的{@link CellStyle}
* *
* @param cell 单元格 * @param cell 单元格
* @param cellStyle 被复制的样式 * @param cellStyle 被复制的样式
* @return {@link CellStyle} * @return {@link CellStyle}
*/ */
@ -34,7 +34,7 @@ public class StyleUtil {
/** /**
* 克隆新的{@link CellStyle} * 克隆新的{@link CellStyle}
* *
* @param workbook 工作簿 * @param workbook 工作簿
* @param cellStyle 被复制的样式 * @param cellStyle 被复制的样式
* @return {@link CellStyle} * @return {@link CellStyle}
*/ */
@ -48,8 +48,8 @@ public class StyleUtil {
* 设置cell文本对齐样式 * 设置cell文本对齐样式
* *
* @param cellStyle {@link CellStyle} * @param cellStyle {@link CellStyle}
* @param halign 横向位置 * @param halign 横向位置
* @param valign 纵向位置 * @param valign 纵向位置
* @return {@link CellStyle} * @return {@link CellStyle}
*/ */
public static CellStyle setAlign(CellStyle cellStyle, HorizontalAlignment halign, VerticalAlignment valign) { public static CellStyle setAlign(CellStyle cellStyle, HorizontalAlignment halign, VerticalAlignment valign) {
@ -61,7 +61,7 @@ public class StyleUtil {
/** /**
* 设置cell的四个边框粗细和颜色 * 设置cell的四个边框粗细和颜色
* *
* @param cellStyle {@link CellStyle} * @param cellStyle {@link CellStyle}
* @param borderSize 边框粗细{@link BorderStyle}枚举 * @param borderSize 边框粗细{@link BorderStyle}枚举
* @param colorIndex 颜色的short值 * @param colorIndex 颜色的short值
* @return {@link CellStyle} * @return {@link CellStyle}
@ -85,8 +85,8 @@ public class StyleUtil {
/** /**
* 给cell设置颜色 * 给cell设置颜色
* *
* @param cellStyle {@link CellStyle} * @param cellStyle {@link CellStyle}
* @param color 背景颜色 * @param color 背景颜色
* @param fillPattern 填充方式 {@link FillPatternType}枚举 * @param fillPattern 填充方式 {@link FillPatternType}枚举
* @return {@link CellStyle} * @return {@link CellStyle}
*/ */
@ -97,8 +97,8 @@ public class StyleUtil {
/** /**
* 给cell设置颜色 * 给cell设置颜色
* *
* @param cellStyle {@link CellStyle} * @param cellStyle {@link CellStyle}
* @param color 背景颜色 * @param color 背景颜色
* @param fillPattern 填充方式 {@link FillPatternType}枚举 * @param fillPattern 填充方式 {@link FillPatternType}枚举
* @return {@link CellStyle} * @return {@link CellStyle}
*/ */
@ -112,7 +112,7 @@ public class StyleUtil {
* 创建字体 * 创建字体
* *
* @param workbook {@link Workbook} * @param workbook {@link Workbook}
* @param color 字体颜色 * @param color 字体颜色
* @param fontSize 字体大小 * @param fontSize 字体大小
* @param fontName 字体名称可以为null使用默认字体 * @param fontName 字体名称可以为null使用默认字体
* @return {@link Font} * @return {@link Font}
@ -125,20 +125,20 @@ public class StyleUtil {
/** /**
* 设置字体样式 * 设置字体样式
* *
* @param font 字体{@link Font} * @param font 字体{@link Font}
* @param color 字体颜色 * @param color 字体颜色
* @param fontSize 字体大小 * @param fontSize 字体大小
* @param fontName 字体名称可以为null使用默认字体 * @param fontName 字体名称可以为null使用默认字体
* @return {@link Font} * @return {@link Font}
*/ */
public static Font setFontStyle(Font font, short color, short fontSize, String fontName) { public static Font setFontStyle(Font font, short color, short fontSize, String fontName) {
if(color > 0) { if (color > 0) {
font.setColor(color); font.setColor(color);
} }
if(fontSize > 0) { if (fontSize > 0) {
font.setFontHeightInPoints(fontSize); font.setFontHeightInPoints(fontSize);
} }
if(StrUtil.isNotBlank(fontName)) { if (StrUtil.isNotBlank(fontName)) {
font.setFontName(fontName); font.setFontName(fontName);
} }
return font; return font;
@ -153,7 +153,7 @@ public class StyleUtil {
* @since 5.4.0 * @since 5.4.0
*/ */
public static CellStyle createCellStyle(Workbook workbook) { public static CellStyle createCellStyle(Workbook workbook) {
if(null == workbook){ if (null == workbook) {
return null; return null;
} }
return workbook.createCellStyle(); return workbook.createCellStyle();
@ -192,13 +192,26 @@ public class StyleUtil {
} }
/** /**
* 给定样式是否为null无样式或默认样式默认样式为<code>workbook.getCellStyleAt(0)</code> * 给定样式是否为null无样式或默认样式默认样式为{@code workbook.getCellStyleAt(0)}
*
* @param workbook 工作簿 * @param workbook 工作簿
* @param style 被检查的样式 * @param style 被检查的样式
* @return 是否为null无样式或默认样式 * @return 是否为null无样式或默认样式
* @since 4.6.3 * @since 4.6.3
*/ */
public static boolean isNullOrDefaultStyle(Workbook workbook, CellStyle style) { public static boolean isNullOrDefaultStyle(Workbook workbook, CellStyle style) {
return (null == style) || style.equals(workbook.getCellStyleAt(0)); return (null == style) || style.equals(workbook.getCellStyleAt(0));
} }
/**
* 创建数据格式并获取格式
*
* @param format 数据格式
* @return 数据格式
* @since 5.5.5
*/
public Short getFormat(Workbook workbook, String format) {
final DataFormat dataFormat = workbook.createDataFormat();
return dataFormat.getFormat(format);
}
} }

View File

@ -164,6 +164,14 @@ public class ExcelSaxReadTest {
Assert.assertEquals("2012-12-21 00:00:00", rows.get(4)); Assert.assertEquals("2012-12-21 00:00:00", rows.get(4));
} }
@Test
@Ignore
public void dateReadXlsxTest2() {
ExcelUtil.readBySax("d:/test/custom_date_format2.xlsx", 0,
(i, i1, list) -> Console.log(list)
);
}
@Test @Test
@Ignore @Ignore
public void readBlankTest() { public void readBlankTest() {