add /GlobalCustomFormat

This commit is contained in:
Looly 2021-06-26 01:47:16 +08:00
parent 85e9bc11c2
commit 613b6f3ac5
16 changed files with 270 additions and 41 deletions

View File

@ -3,7 +3,7 @@
-------------------------------------------------------------------------------------------------------------
# 5.7.3 (2021-06-25)
# 5.7.3 (2021-06-26)
### 🐣新特性
* 【core 】 增加Convert.toSet方法issue#I3XFG2@Gitee
@ -11,6 +11,8 @@
* 【core 】 新增JAXBUtilpr#346@Gitee
* 【poi 】 ExcelWriter新增setColumnStyleIfHasData和setRowStyleIfHasDatapr#347@Gitee
* 【json 】 用户自定义日期时间格式时,解析也读取此格式
* 【core 】 增加可自定义日期格式GlobalCustomFormat
* 【jwt 】 JWT修改默认有序并规定payload日期格式为秒数
### 🐞Bug修复
* 【json 】 修复XML转义字符的问题issue#I3XH09@Gitee

View File

@ -3,6 +3,7 @@ package cn.hutool.core.date;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.NumberChineseFormatter;
import cn.hutool.core.date.format.FastDateParser;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -654,6 +655,15 @@ public class CalendarUtil {
calendar.setLenient(lenient);
for (final String parsePattern : parsePatterns) {
if(GlobalCustomFormat.isCustomFormat(parsePattern)){
final Date parse = GlobalCustomFormat.parse(str, parsePattern);
if(null == parse){
continue;
}
calendar.setTime(parse);
return calendar;
}
final FastDateParser fdp = new FastDateParser(parsePattern, tz, lcl);
calendar.clear();
try {

View File

@ -3,6 +3,7 @@ package cn.hutool.core.date;
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.date.format.GlobalCustomFormat;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -251,7 +252,9 @@ public class DateTime extends Date {
* @see DatePattern
*/
public DateTime(CharSequence dateStr, String format) {
this(dateStr, DateUtil.newSimpleFormat(format));
this(GlobalCustomFormat.isCustomFormat(format)
? GlobalCustomFormat.parse(dateStr, format)
: parse(dateStr, DateUtil.newSimpleFormat(format)));
}
/**

View File

@ -5,6 +5,7 @@ import cn.hutool.core.comparator.CompareUtil;
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.date.format.GlobalCustomFormat;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.CharUtil;
@ -488,6 +489,11 @@ public class DateUtil extends CalendarUtil {
return null;
}
// 检查自定义格式
if(GlobalCustomFormat.isCustomFormat(format)){
return GlobalCustomFormat.format(date, format);
}
TimeZone timeZone = null;
if (date instanceof DateTime) {
timeZone = ((DateTime) date).getTimeZone();
@ -696,6 +702,10 @@ public class DateUtil extends CalendarUtil {
* @since 4.5.18
*/
public static DateTime parse(CharSequence dateStr, String format, Locale locale) {
if(GlobalCustomFormat.isCustomFormat(format)){
// 自定义格式化器忽略Locale
return new DateTime(GlobalCustomFormat.parse(dateStr, format));
}
return new DateTime(dateStr, DateUtil.newSimpleFormat(format, locale, null));
}

View File

@ -1,5 +1,6 @@
package cn.hutool.core.date;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
@ -139,7 +140,7 @@ public class LocalDateTimeUtil {
* 毫秒转{@link LocalDateTime}结果会产生时间偏移
*
* @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数
* @param timeZone 时区
* @param timeZone 时区
* @return {@link LocalDateTime}
*/
public static LocalDateTime of(long epochMilli, TimeZone timeZone) {
@ -174,8 +175,8 @@ public class LocalDateTimeUtil {
return null;
}
if(temporalAccessor instanceof LocalDate){
return ((LocalDate)temporalAccessor).atStartOfDay();
if (temporalAccessor instanceof LocalDate) {
return ((LocalDate) temporalAccessor).atStartOfDay();
}
return LocalDateTime.of(
@ -201,8 +202,8 @@ public class LocalDateTimeUtil {
return null;
}
if(temporalAccessor instanceof LocalDateTime){
return ((LocalDateTime)temporalAccessor).toLocalDate();
if (temporalAccessor instanceof LocalDateTime) {
return ((LocalDateTime) temporalAccessor).toLocalDate();
}
return LocalDate.of(
@ -215,11 +216,11 @@ public class LocalDateTimeUtil {
/**
* 解析日期时间字符串为{@link LocalDateTime}仅支持yyyy-MM-dd'T'HH:mm:ss格式例如2007-12-03T10:15:30
*
* @param text 日期时间字符串
* @param text 日期时间字符串
* @return {@link LocalDateTime}
*/
public static LocalDateTime parse(CharSequence text) {
return parse(text, (DateTimeFormatter)null);
return parse(text, (DateTimeFormatter) null);
}
/**
@ -252,23 +253,27 @@ public class LocalDateTimeUtil {
return null;
}
if(GlobalCustomFormat.isCustomFormat(format)){
return of(GlobalCustomFormat.parse(text, format));
}
DateTimeFormatter formatter = null;
if(StrUtil.isNotBlank(format)){
if (StrUtil.isNotBlank(format)) {
// 修复yyyyMMddHHmmssSSS格式不能解析的问题
// fix issue#1082
//see https://stackoverflow.com/questions/22588051/is-java-time-failing-to-parse-fraction-of-second
// jdk8 bug at: https://bugs.openjdk.java.net/browse/JDK-8031085
if(StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)){
if (StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)) {
final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN);
if(ReUtil.isMatch("[S]{1,2}", fraction)){
if (ReUtil.isMatch("[S]{1,2}", fraction)) {
//将yyyyMMddHHmmssSyyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式用0补
text += StrUtil.repeat('0', 3-fraction.length());
text += StrUtil.repeat('0', 3 - fraction.length());
}
formatter = new DateTimeFormatterBuilder()
.appendPattern(DatePattern.PURE_DATETIME_PATTERN)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
} else{
} else {
formatter = DateTimeFormatter.ofPattern(format);
}
}
@ -279,12 +284,12 @@ public class LocalDateTimeUtil {
/**
* 解析日期时间字符串为{@link LocalDate}仅支持yyyy-MM-dd'T'HH:mm:ss格式例如2007-12-03T10:15:30
*
* @param text 日期时间字符串
* @param text 日期时间字符串
* @return {@link LocalDate}
* @since 5.3.10
*/
public static LocalDate parseDate(CharSequence text) {
return parseDate(text, (DateTimeFormatter)null);
return parseDate(text, (DateTimeFormatter) null);
}
/**
@ -323,7 +328,7 @@ public class LocalDateTimeUtil {
/**
* 格式化日期时间为yyyy-MM-dd HH:mm:ss格式
*
* @param time {@link LocalDateTime}
* @param time {@link LocalDateTime}
* @return 格式化后的字符串
* @since 5.3.11
*/
@ -350,16 +355,13 @@ public class LocalDateTimeUtil {
* @return 格式化后的字符串
*/
public static String format(LocalDateTime time, String format) {
if (null == time) {
return null;
}
return format(time, DateTimeFormatter.ofPattern(format));
return TemporalAccessorUtil.format(time, format);
}
/**
* 格式化日期时间为yyyy-MM-dd格式
*
* @param date {@link LocalDate}
* @param date {@link LocalDate}
* @return 格式化后的字符串
* @since 5.3.11
*/
@ -478,8 +480,8 @@ public class LocalDateTimeUtil {
*
* @param temporalAccessor Date对象
* @return {@link Instant}对象
* @since 5.4.1
* @see TemporalAccessorUtil#toEpochMilli(TemporalAccessor)
* @since 5.4.1
*/
public static long toEpochMilli(TemporalAccessor temporalAccessor) {
return TemporalAccessorUtil.toEpochMilli(temporalAccessor);

View File

@ -1,5 +1,6 @@
package cn.hutool.core.date;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.util.StrUtil;
import java.time.Instant;
@ -83,6 +84,11 @@ public class TemporalAccessorUtil extends TemporalUtil{
return null;
}
// 检查自定义格式
if(GlobalCustomFormat.isCustomFormat(format)){
return GlobalCustomFormat.format(time, format);
}
final DateTimeFormatter formatter = StrUtil.isBlank(format)
? null : DateTimeFormatter.ofPattern(format);

View File

@ -0,0 +1,119 @@
package cn.hutool.core.date.format;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* 全局自定义格式<br>
* 用于定义用户指定的日期格式和输出日期的关系
*
* @author looly
* @since 5.7.2
*/
public class GlobalCustomFormat {
public static final String FORMAT_SECONDS = "#sss";
public static final String FORMAT_MILLISECONDS = "#SSS";
private static final Map<CharSequence, Function<Date, String>> formatterMap;
private static final Map<CharSequence, Function<CharSequence, Date>> parserMap;
static {
formatterMap = new ConcurrentHashMap<>();
parserMap = new ConcurrentHashMap<>();
// Hutool预设的几种自定义格式
putFormatter(FORMAT_SECONDS, (date) -> String.valueOf(Math.floorDiv(date.getTime(), 1000)));
putParser(FORMAT_SECONDS, (dateStr) -> DateUtil.date(Math.multiplyExact(Long.parseLong(dateStr.toString()), 1000)));
putFormatter(FORMAT_MILLISECONDS, (date) -> String.valueOf(date.getTime()));
putParser(FORMAT_MILLISECONDS, (dateStr) -> DateUtil.date(Long.parseLong(dateStr.toString())));
}
/**
* 加入日期格式化规则
*
* @param format 格式
* @param func 格式化函数
*/
public static void putFormatter(String format, Function<Date, String> func) {
Assert.notNull(format, "Format must be not null !");
Assert.notNull(func, "Function must be not null !");
formatterMap.put(format, func);
}
/**
* 加入日期解析规则
*
* @param format 格式
* @param func 解析函数
*/
public static void putParser(String format, Function<CharSequence, Date> func) {
Assert.notNull(format, "Format must be not null !");
Assert.notNull(func, "Function must be not null !");
parserMap.put(format, func);
}
/**
* 检查指定格式是否为自定义格式
*
* @param format 格式
* @return 是否为自定义格式
*/
public static boolean isCustomFormat(String format) {
return formatterMap.containsKey(format);
}
/**
* 使用自定义格式格式化日期
*
* @param date 日期
* @param format 自定义格式
* @return 格式化后的日期
*/
public static String format(Date date, CharSequence format) {
if (null != formatterMap) {
final Function<Date, String> func = formatterMap.get(format);
if (null != func) {
return func.apply(date);
}
}
return null;
}
/**
* 使用自定义格式格式化日期
*
* @param temporalAccessor 日期
* @param format 自定义格式
* @return 格式化后的日期
*/
public static String format(TemporalAccessor temporalAccessor, CharSequence format) {
return format(DateUtil.date(temporalAccessor), format);
}
/**
* 使用自定义格式解析日期
*
* @param dateStr 日期字符串
* @param format 自定义格式
* @return 格式化后的日期
*/
public static Date parse(CharSequence dateStr, String format) {
if (null != parserMap) {
final Function<CharSequence, Date> func = parserMap.get(format);
if (null != func) {
return func.apply(dateStr);
}
}
return null;
}
}

View File

@ -62,6 +62,7 @@ public class JAXBUtil {
/**
* xml转换成JavaBean
*
* @param <T> Bean类型
* @param xml XML字符串
* @param c Bean类型
* @return bean

View File

@ -72,6 +72,30 @@ public class DateUtilTest {
Assert.assertEquals("00:00:00", formatTime);
}
@Test
public void formatAndParseCustomTest() {
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
String format = DateUtil.format(date, "#sss");
Assert.assertEquals("1488297600", format);
final DateTime parse = DateUtil.parse(format, "#sss");
Assert.assertEquals(date, parse);
}
@Test
public void formatAndParseCustomTest2() {
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
String format = DateUtil.format(date, "#SSS");
Assert.assertEquals("1488297600000", format);
final DateTime parse = DateUtil.parse(format, "#SSS");
Assert.assertEquals(date, parse);
}
@Test
public void beginAndEndTest() {
String dateStr = "2017-03-01 00:33:23";
@ -883,6 +907,6 @@ public class DateUtilTest {
public void parseNotFitTest(){
//https://github.com/looly/hutool/issues/1332
// 在日期格式不匹配的时候测试是否正常报错
final DateTime parse = DateUtil.parse("2020-12-23", DatePattern.PURE_DATE_PATTERN);
DateUtil.parse("2020-12-23", DatePattern.PURE_DATE_PATTERN);
}
}

View File

@ -20,4 +20,15 @@ public class TemporalAccessorUtilTest {
final String format = TemporalAccessorUtil.format(LocalTime.MIN, DatePattern.NORM_DATETIME_PATTERN);
Assert.assertEquals(today + " 00:00:00", format);
}
@Test
public void formatCustomTest(){
final String today = TemporalAccessorUtil.format(
LocalDate.of(2021, 6, 26), "#sss");
Assert.assertEquals("1624636800", today);
final String today2 = TemporalAccessorUtil.format(
LocalDate.of(2021, 6, 26), "#SSS");
Assert.assertEquals("1624636800000", today2);
}
}

View File

@ -3,6 +3,7 @@ package cn.hutool.json;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
@ -167,10 +168,6 @@ final class InternalJSONUtil {
try {
if (StrUtil.containsAnyIgnoreCase(string, ".", "e")) {
// pr#192@GiteeDouble会出现小数精度丢失问题此处使用BigDecimal
//double d = Double.parseDouble(string);
//if (false == Double.isInfinite(d) && false == Double.isNaN(d)) {
// return d;
//}
return new BigDecimal(string);
} else {
final long myLong = Long.parseLong(string);
@ -275,6 +272,12 @@ final class InternalJSONUtil {
} else{
dateStr = DateUtil.format(Convert.toDate(dateObj), format);
}
if(GlobalCustomFormat.FORMAT_SECONDS.equals(format)
|| GlobalCustomFormat.FORMAT_MILLISECONDS.equals(format)){
// Hutool自定义的秒和毫秒表示默认不包装双引号
return dateStr;
}
//用户定义了日期格式
return JSONUtil.quote(dateStr);
}

View File

@ -113,21 +113,25 @@ public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K> {
@Override
default Date getDate(K key, Date defaultValue){
// 默认转换
final Object obj = getObj(key);
if (null == obj) {
return defaultValue;
}
if(obj instanceof Date){
return (Date) obj;
}
String format = OptionalBean.ofNullable(getConfig()).getBean(JSONConfig::getDateFormat).get();
if(StrUtil.isNotBlank(format)){
// 用户指定了日期格式获取日期属性时使用对应格式
final String str = getStr(key);
final String str = Convert.toStr(obj);
if(null == str){
return defaultValue;
}
return DateUtil.parse(str, format);
}
// 默认转换
final Object obj = getObj(key);
if (null == obj) {
return defaultValue;
}
return Convert.toDate(obj, defaultValue);
}

View File

@ -450,6 +450,27 @@ public class JSONObjectTest {
Assert.assertEquals(DateUtil.beginOfDay(date), parse.getDate("date"));
}
@Test
public void setCustomDateFormatTest(){
JSONConfig jsonConfig = JSONConfig.create();
jsonConfig.setDateFormat("#sss");
jsonConfig.setOrder(true);
Date date = DateUtil.parse("2020-06-05 11:16:11");
JSONObject json = new JSONObject(jsonConfig);
json.set("date", date);
json.set("bbb", "222");
json.set("aaa", "123");
String jsonStr = "{\"date\":1591326971,\"bbb\":\"222\",\"aaa\":\"123\"}";
Assert.assertEquals(jsonStr, json.toString());
// 解析测试
final JSONObject parse = JSONUtil.parseObj(jsonStr, jsonConfig);
Assert.assertEquals(date, parse.getDate("date"));
}
@Test
public void getTimestampTest(){
String timeStr = "1970-01-01 00:00:00";

View File

@ -3,6 +3,7 @@ package cn.hutool.jwt;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
@ -19,11 +20,9 @@ import java.util.Map;
public class Claims implements Serializable {
private static final long serialVersionUID = 1L;
private JSONObject claimJSON;
private final JSONConfig CONFIG = JSONConfig.create().setDateFormat("#sss").setOrder(true);
public Claims() {
this.claimJSON = new JSONObject();
}
private JSONObject claimJSON;
/**
* 增加Claims属性如果属性值为{@code null}则移除这个属性
@ -32,6 +31,7 @@ public class Claims implements Serializable {
* @param value 属性值
*/
protected void setClaim(String name, Object value) {
init();
Assert.notNull(name, "Name must be not null!");
if (value == null) {
claimJSON.remove(name);
@ -59,6 +59,7 @@ public class Claims implements Serializable {
* @return 属性
*/
public Object getClaim(String name) {
init();
return this.claimJSON.getObj(name);
}
@ -68,6 +69,7 @@ public class Claims implements Serializable {
* @return JSON字符串
*/
public JSONObject getClaimsJson() {
init();
return this.claimJSON;
}
@ -78,11 +80,18 @@ public class Claims implements Serializable {
* @param charset 编码
*/
public void parse(String tokenPart, Charset charset) {
this.claimJSON = JSONUtil.parseObj(Base64.decodeStr(tokenPart, charset));
this.claimJSON = JSONUtil.parseObj(Base64.decodeStr(tokenPart, charset), CONFIG);
}
@Override
public String toString() {
init();
return this.claimJSON.toString();
}
private void init(){
if(null == this.claimJSON){
this.claimJSON = new JSONObject(CONFIG);
}
}
}

View File

@ -1,5 +1,6 @@
package cn.hutool.jwt;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.jwt.signers.AlgorithmUtil;
import cn.hutool.jwt.signers.JWTSigner;
@ -113,6 +114,7 @@ public class JWTSignerTest {
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setExpiresAt(DateUtil.tomorrow())
.setSigner(signer);
String token = jwt.sign();

View File

@ -1,5 +1,6 @@
package cn.hutool.jwt;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.signers.JWTSignerUtil;
import org.junit.Assert;
@ -14,11 +15,12 @@ public class JWTTest {
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setPayload(RegisteredPayload.EXPIRES_AT, DateUtil.parse("2022-01-01"))
.setKey(key);
String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
"U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWUsImV4cCI6IjE2NDA5NjY0MDAifQ." +
"c9qo9Z9vdN2gulvOEReU9iEi0bqgyVqjaNKbP1DmTL4";
String token = jwt.sign();
Assert.assertEquals(token, rightToken);