阅读 104

4hutool源码分析:DateUtil(时间工具类)-格式化时间

源码分析目的

知其然,知其所以然

项目引用

此博文的依据:hutool-5.6.5版本源码

        <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>5.6.5</version> </dependency> 复制代码

方法名称:DateUtil.formatLocalDateTime(java.time.LocalDateTime)

方法描述

格式化日期时间<br> 格式 yyyy-MM-dd HH:mm:ss

源码分析一

 /**  * 格式化日期时间<br>  * 格式 yyyy-MM-dd HH:mm:ss  *  * @param localDateTime 被格式化的日期  * @return 格式化后的字符串  */ public static String formatLocalDateTime(LocalDateTime localDateTime) { return LocalDateTimeUtil.formatNormal(localDateTime); } 复制代码

首先formatLocalDateTime方法的入参是LocalDateTime(Java8支持的日期时间类,是线程安全的)

然后调用LocalDateTimeUtil.formatNormal(localDateTime)

//LocalDateTimeUtil /**  * 格式化日期时间为yyyy-MM-dd HH:mm:ss格式  *  * @param time      {@link LocalDateTime}  * @return 格式化后的字符串  * @since 5.3.11  */ public static String formatNormal(LocalDateTime time) { return format(time, DatePattern.NORM_DATETIME_FORMATTER); } /**  * 格式化日期时间为指定格式  *  * @param time      {@link LocalDateTime}  * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}  * @return 格式化后的字符串  */ public static String format(LocalDateTime time, DateTimeFormatter formatter) { return TemporalAccessorUtil.format(time, formatter); } 复制代码

跟代码,发现DatePattern.NORM_DATETIME_FORMATTER的日期时间格式为:

/**  * 标准日期时间格式,精确到秒:yyyy-MM-dd HH:mm:ss  */ public static final String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; 复制代码

然后会调用format(LocalDateTime time, DateTimeFormatter formatter),DateTimeFormatter 这个也是(Java8支持的日期格式化器类,是线程安全的)。

hutool这里做了很好的示范,使用DateTimeFormatter替换了SimpleDateFormat(线程不安全的)。

为什么SimpleDateFormat是线程不安全的,请看万字博文教你搞懂java源码的日期和时间相关用法

然后我们继续往下深挖TemporalAccessorUtil.format(time, formatter)

/**  * 格式化日期时间为指定格式  *  * @param time      {@link TemporalAccessor}  * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}  * @return 格式化后的字符串  * @since 5.3.10  */ public static String format(TemporalAccessor time, DateTimeFormatter formatter) {    if (null == time) {       return null;    }    if(null == formatter){       formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;    }    try {       return formatter.format(time);    } catch (UnsupportedTemporalTypeException e){       if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){          // 用户传入LocalDate,但是要求格式化带有时间部分,转换为LocalDateTime重试          return formatter.format(((LocalDate) time).atStartOfDay());       }else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){          // 用户传入LocalTime,但是要求格式化带有日期部分,转换为LocalDateTime重试          return formatter.format(((LocalTime) time).atDate(LocalDate.now()));       }       throw e;    } } 复制代码

最前面加了两个入参判空处理,time为null时,返回null;formatter为null时,给格式默认值,eg:2011-12-03T10:15:30

image-20210725113946673

然后执行formatter.format(time)相当于是DateTimeFormatter.format(LocalDateTime)。这样就格式化成功了。

值得一说的是**TemporalAccessorUtil.format(TemporalAccessor time, DateTimeFormatter formatter)**里面有加异常处理机制

 try {       return formatter.format(time);    } catch (UnsupportedTemporalTypeException e){       if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){          // 用户传入LocalDate,但是要求格式化带有时间部分,转换为LocalDateTime重试          return formatter.format(((LocalDate) time).atStartOfDay());       }else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){          // 用户传入LocalTime,但是要求格式化带有日期部分,转换为LocalDateTime重试          return formatter.format(((LocalTime) time).atDate(LocalDate.now()));       }       throw e;    } 复制代码

因为入参TemporalAccessor time的实现类常用的有如下几个(java8提供的):

  • LocalDateTime

  • LocalDate

  • LocalTime

在进行日期时间转化时,日期时间和要转化的格式化字符串要对应上,不然会抛出异常,所以做了如上的补救措施。

方法名称:DateUtil.format(java.time.LocalDateTime, java.lang.String)

方法描述

根据特定格式格式化日期

源码分析一

/**  * 根据特定格式格式化日期  *  * @param localDateTime 被格式化的日期  * @param format        日期格式,常用格式见: {@link DatePattern}  * @return 格式化后的字符串  */ public static String format(LocalDateTime localDateTime, String format) {    return LocalDateTimeUtil.format(localDateTime, format); } 复制代码

首先:hutool提供了常用的日期时间格式

/**  * 日期格式化类,提供常用的日期格式化对象  *  */ public class DatePattern { ... } 复制代码

image-20210725115905355

然后:调用LocalDateTimeUtil.format(localDateTime, format)

/**  * 格式化日期时间为指定格式  *  * @param time   {@link LocalDateTime}  * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS  * @return 格式化后的字符串  */ public static String format(LocalDateTime time, String format) {    if (null == time) {       return null;    }    return format(time, DateTimeFormatter.ofPattern(format)); } 复制代码

源码**format(time, DateTimeFormatter.ofPattern(format))**可以拆解成两部分:

  • DateTimeFormatter.ofPattern(format)

  • format(LocalDateTime time, DateTimeFormatter formatter)

第一部分:**DateTimeFormatter.ofPattern(format)**是把字符串日期时间格式转化为日期时间格式化对象DateTimeFormatter ;

注意DateTimeFormatter.ofPattern(format)的用法是有的(代码详解-->万字博文教你搞懂java源码的日期和时间相关用法):

  1. 在正常配置按照标准格式的字符串日期,是能够正常转换的。如果月,日,时,分,秒在不足两位的情况需要补0,否则的话会转换失败,抛出异常。

  2. YYYY和DD谨慎使用

第二部分,format(LocalDateTime time, DateTimeFormatter formatter)上面有介绍了,这里就不水字了。

方法名称:DateUtil.format(java.util.Date, java.text.DateFormat)

方法描述

根据特定格式格式化日期

源码分析一

/**  * 根据特定格式格式化日期  *  * @param date   被格式化的日期  * @param format 日期格式,常用格式见: {@link DatePattern}  * @return 格式化后的字符串  */ public static String format(Date date, String format) {    if (null == date || StrUtil.isBlank(format)) {       return null;    }    TimeZone timeZone = null;    if (date instanceof DateTime) {       timeZone = ((DateTime) date).getTimeZone();    }    return format(date, newSimpleFormat(format, null, timeZone)); } 复制代码

从代码中**format(Date date, String format)**方法提供了两个入参,一个是Date 类型的 被格式化的日期和要日期格式的字符串。这是为了兼容java8之前的旧日期时间API提供的方法

方法内首先对两个参数加了判空处理。

然后判断时间是否是hutool的DateTime对象,如果是,则获取时区TimeZone

接着调用format(date, newSimpleFormat(format, null, timeZone)),可拆解成两部分:

  • newSimpleFormat(format, null, timeZone),获取SimpleDateFormat对象(注:此方法是非线程安全的)

  • format(Date date, DateFormat format) 根据特定格式格式化日期

首先:**newSimpleFormat(format, null, timeZone)**代码详解:

/**  * 创建{@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; } 复制代码

如果**format(Date date, String format)输入的是Date对象的时间,那format(date, newSimpleFormat(format, null, timeZone))**具象化后,是这样的:format(date, newSimpleFormat(format, null, null))

//获取当前的语言环境   locale = Locale.getDefault(Locale.Category.FORMAT); 复制代码

然后new了一个SimpleDateFormat对象。并设置了时区和设置了setLenient,这个方法的含义是是否严格解析日期。setLenient设置为false时,就是严格解析日期:会严格按照日期时间格式,java不会帮忙计算,直接抛出异常。

image-20210725122714337

然后**format(Date date, DateFormat format) **代码分析:

/**  * 根据特定格式格式化日期  *  * @param date   被格式化的日期  * @param format {@link SimpleDateFormat}  * @return 格式化后的字符串  */ public static String format(Date date, DateFormat format) {    if (null == format || null == date) {       return null;    }    return format.format(date); } 复制代码

对两个入参进行了判空处理。然后调用SimpleDateFormat.format(date),这是java8之前就有提供的方法。

方法名称:DateUtil.format(java.util.Date, java.time.format.DateTimeFormatter)(方法有问题,已反馈,官方已修正)

方法描述

根据特定格式格式化日期

源码分析一

/**  * 根据特定格式格式化日期  *  * @param date   被格式化的日期  * @param format {@link DateTimeFormatter}  * @return 格式化后的字符串  * @since 5.0.0  */ public static String format(Date date, DateTimeFormatter format) {    if (null == format || null == date) {       return null;    }    return format.format(date.toInstant()); } 复制代码

首先对两个入参做了判空处理。

然后,执行了format.format(date.toInstant()),代码可拆解成两部分:

  • date.toInstant():返回Instant对象

  • DateTimeFormatter.format(Instant):java8提供的格式化日期时间的方法

代码**DateTimeFormatter.format(Instant)**是怎么处理的呢?

    public String format(TemporalAccessor temporal) {         StringBuilder buf = new StringBuilder(32);         formatTo(temporal, buf);         return buf.toString();     } 复制代码

首先new了个StringBuilder对象,用来拼接字符串;

然后调用**formatTo(temporal, buf)**方法

public void formatTo(TemporalAccessor temporal, Appendable appendable) {     Objects.requireNonNull(temporal, "temporal");     Objects.requireNonNull(appendable, "appendable");     try {         DateTimePrintContext context = new DateTimePrintContext(temporal, this);         if (appendable instanceof StringBuilder) {             printerParser.format(context, (StringBuilder) appendable);         } else {             // buffer output to avoid writing to appendable in case of error             StringBuilder buf = new StringBuilder(32);             printerParser.format(context, buf);             appendable.append(buf);         }     } catch (IOException ex) {         throw new DateTimeException(ex.getMessage(), ex);     } } 复制代码

**formatTo(temporal, buf)**方法也是先判断两个入参空处理。

然后,Instant对象被封装在一个新new的DateTimePrintContext对象

运行demo有问题,进行排查

 //根据特定格式格式化日期 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String dateStr = DateUtil.format(new Date(),dtf); System.out.println(dateStr); 复制代码

image-20210725195348793

到这里已经是jdk的源码了DateTimeFormatter.format

image-20210725195424950

image-20210725195522610

image-20210725195636339

从上面可知,会调用 NumberPrinterParser.format() NumberPrinterParser是在DateTimeFormatterBuilder类中的。

image-20210725195947802

到这一步会报错

image-20210725200153850

为什么会报错呢,我们来看下context.getValue(field)发生了什么:

image-20210725200349650

从上面代码可行,temporal实际上是Instant对象,Instant.getLong只支持四种字段类型。。

NANO_OF_SECOND MICRO_OF_SECOND MILLI_OF_SECOND INSTANT_SECONDS 复制代码

image-20210725200551164

如果不是上面这几种字段类型,则抛出异常

DateUtil.format当遇到DateTimeFormatter会将Date对象首先转换为Instant,因为缺少时区,导致报错。

建议改法

/**  * 根据特定格式格式化日期  *  * @param date   被格式化的日期  * @param format  * @return 格式化后的字符串  * @since 5.0.0  */ public static String format(Date date, DateTimeFormatter format) {    if (null == format || null == date) {       return null;    }    Instant instant = date.toInstant();       ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());    LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();    return format.format(localDateTime); } 复制代码

先把date类型转化为LocalDateTime类型,然后再进行DateTimeFormatter.format(LocalDateTime)的格式化

测试demo

//根据特定格式格式化日期 String str = "2021-07-25 20:11:25"; DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:dd"); Date date = DateUtil.parse(str); String dateStr = DateUtil.format(date,dtf); System.out.println(dateStr); Assert.assertEquals(str, dateStr); 复制代码

image-20210725201444728

官方改法

修订版本 #5.7.5

/**  * 根据特定格式格式化日期  *  * @param date   被格式化的日期  * @param format {@link SimpleDateFormat} {@link DatePattern#NORM_DATETIME_FORMATTER}  * @return 格式化后的字符串  * @since 5.0.0  */ public static String format(Date date, DateTimeFormatter format) {    if (null == format || null == date) {       return null;    }    // java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra    // 出现以上报错时,表示Instant时间戳没有时区信息,赋予默认时区    return TemporalAccessorUtil.format(date.toInstant(), format); } 复制代码

更换了新的调用方法TemporalAccessorUtil.format(date.toInstant(), format),date.toInstant()返回Instant对象,则变成了TemporalAccessorUtil.format(Instant, format)

//TemporalAccessorUtil /**  * 格式化日期时间为指定格式  *  * @param time      {@link TemporalAccessor}  * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}  * @return 格式化后的字符串  * @since 5.3.10  */ public static String format(TemporalAccessor time, DateTimeFormatter formatter) { if (null == time) { return null; } if(null == formatter){ formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; } try { return formatter.format(time); } catch (UnsupportedTemporalTypeException e){ if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){ // 用户传入LocalDate,但是要求格式化带有时间部分,转换为LocalDateTime重试 return formatter.format(((LocalDate) time).atStartOfDay()); }else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){ // 用户传入LocalTime,但是要求格式化带有日期部分,转换为LocalDateTime重试 return formatter.format(((LocalTime) time).atDate(LocalDate.now())); } else if(time instanceof Instant){ // 时间戳没有时区信息,赋予默认时区 return formatter.format(((Instant) time).atZone(ZoneId.systemDefault())); } throw e; } } 复制代码

对比了下跟5.6.5版本的差异,新增了当time是Instant时,给一个默认的时区

else if(time instanceof Instant){    // 时间戳没有时区信息,赋予默认时区    return formatter.format(((Instant) time).atZone(ZoneId.systemDefault())); } 复制代码

image-2021072651290

方法名称:DateUtil.formatDateTime(java.util.Date)

方法描述

格式化日期时间<br> 格式 yyyy-MM-dd HH:mm:ss

源码分析一

/**  * 格式化日期时间<br>  * 格式 yyyy-MM-dd HH:mm:ss  *  * @param date 被格式化的日期  * @return 格式化后的日期  */ public static String formatDateTime(Date date) {    if (null == date) {       return null;    }    return DatePattern.NORM_DATETIME_FORMAT.format(date); } 复制代码

首先好习惯,先对入参进行判空处理

然后调用DatePattern.NORM_DATETIME_FORMAT.format(date)返回FastDateFormat对象

针对只支持java8之前的程序,可以使用FastDateFormat线程安全替换SimpleDateFormat线程不安全--》源码分析

 FastDateFormat 是一个线程安全的实现 来源 Apache Commons Lang 3.5 复制代码

DatePattern.NORM_DATETIME_FORMAT--> /**  * 标准日期时间格式,精确到秒 {@link FastDateFormat}:yyyy-MM-dd HH:mm:ss  */ public static final FastDateFormat NORM_DATETIME_FORMAT = FastDateFormat.getInstance(NORM_DATETIME_PATTERN); 复制代码

则转化为了FastDateFormat.format(date)

//FastDateFormat @Override public String format(final Date date) {    return printer.format(date); } //FastDatePrinter  @Override public String format(final Date date) {     final Calendar c = Calendar.getInstance(timeZone, locale);     c.setTime(date);     return applyRulesToString(c); } 复制代码

先把date转化为Calendar,方便获取日期和时间

private String applyRulesToString(final Calendar c) {    return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString(); } private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) { try { for (final Rule rule : this.rules) { rule.appendTo(buf, calendar); } } catch (final IOException e) { throw new DateException(e); } return buf; } 复制代码

核心的代码是这块

for (final Rule rule : this.rules) { rule.appendTo(buf, calendar); } 复制代码

测试demo

String dateStr = "2017-03-01"; Date date = DateUtil.parse(dateStr); String formatDateTime = DateUtil.formatDateTime(date); Assert.assertEquals("2017-03-01 00:00:00", formatDateTime); 复制代码

断点跟进代码:

image-20210725211322291

往下跟代码

image-20210725203801875

//FastDatePrinter private static class PaddedNumberField implements NumberRule {     ...        @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(mField)); } /**  * {@inheritDoc}  */ @Override public final void appendTo(final Appendable buffer, final int value) throws IOException { appendFullDigits(buffer, value, mSize); }               ...} 复制代码

已经取到年份了:2017

image-20210725204215108

往下跟代码:

image-20210725211459199

//FastDatePrinter private static class CharacterLiteral implements Rule { ... @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { buffer.append(mValue); } ... } 复制代码

就是把'-'字符串直接拼接上去。

下一个获取月份:

image-20210725211534912

//FastDatePrinter private static class TwoDigitMonthField implements NumberRule {     ...         /**  * {@inheritDoc}  */ @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(Calendar.MONTH) + 1); } /**  * {@inheritDoc}  */ @Override public final void appendTo(final Appendable buffer, final int value) throws IOException { appendDigits(buffer, value); }     ... } 复制代码

获取了月份:3

image-20210725205900990

然后下一个:把'-'字符串直接拼接上去。

image-20210725211609400

继续往下跟

image-20210725211704501

//FastDatePrinter private static class TwoDigitNumberField implements NumberRule {     ...          @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(mField)); } /**  * {@inheritDoc}  */ @Override public final void appendTo(final Appendable buffer, final int value) throws IOException { if (value < 100) { appendDigits(buffer, value); } else { appendFullDigits(buffer, value, 2); } }     ... } 复制代码

image-20210725210353207

这样获取到的日期:2017-03-01

然后加了一个空格字符串

image-20210725211811625

时分秒的调用,都是调用一样的FastDatePrinter#TwoDigitNumberField。

//FastDatePrinter private static class TwoDigitNumberField implements NumberRule {     ...          @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(mField)); } /**  * {@inheritDoc}  */ @Override public final void appendTo(final Appendable buffer, final int value) throws IOException { if (value < 100) { appendDigits(buffer, value); } else { appendFullDigits(buffer, value, 2); } }     ... } 复制代码

image-20210725212005035

这样就得到了"2017-03-01 00:00:00"

方法名称:DateUtil.formatDate(java.util.Date)

方法描述

格式化日期部分(不包括时间)<br> 格式 yyyy-MM-dd

源码分析一

/**  * 格式化日期部分(不包括时间)<br>  * 格式 yyyy-MM-dd  *  * @param date 被格式化的日期  * @return 格式化后的字符串  */ public static String formatDate(Date date) {    if (null == date) {       return null;    }    return DatePattern.NORM_DATE_FORMAT.format(date); } 复制代码

首先好习惯,先对入参进行判空处理

然后调用DatePattern.NORM_DATE_FORMAT.format(date)返回FastDateFormat对象

针对只支持java8之前的程序,可以使用FastDateFormat线程安全替换SimpleDateFormat线程不安全--》源码分析

 FastDateFormat 是一个线程安全的实现 来源 Apache Commons Lang 3.5 复制代码

 /**  * 标准日期格式 {@link FastDateFormat}:yyyy-MM-dd  */ public static final FastDateFormat NORM_DATE_FORMAT = FastDateFormat.getInstance(NORM_DATE_PATTERN); 复制代码

则转化为了FastDateFormat.format(date),源码分析看上面

方法名称:DateUtil.formatTime(java.util.Date)

方法描述

格式化时间<br> 格式 HH:mm:ss

源码分析一

/**  * 格式化时间<br>  * 格式 HH:mm:ss  *  * @param date 被格式化的日期  * @return 格式化后的字符串  * @since 3.0.1  */ public static String formatTime(Date date) {    if (null == date) {       return null;    }    return DatePattern.NORM_TIME_FORMAT.format(date); } 复制代码

首先好习惯,先对入参进行判空处理

然后调用DatePattern.NORM_TIME_FORMAT.format(date)返回FastDateFormat对象

针对只支持java8之前的程序,可以使用FastDateFormat线程安全替换SimpleDateFormat线程不安全--》源码分析

 FastDateFormat 是一个线程安全的实现 来源 Apache Commons Lang 3.5 复制代码

 /**  * 标准时间格式 {@link FastDateFormat}:HH:mm:ss  */ public static final FastDateFormat NORM_TIME_FORMAT = FastDateFormat.getInstance(NORM_TIME_PATTERN); 复制代码

则转化为了FastDateFormat.format(date),源码分析看上面

方法名称:DateUtil.formatHttpDate(java.util.Date)

方法描述

格式化为Http的标准日期格式<br> 标准日期格式遵循RFC 1123规范,格式类似于:Fri, 31 Dec 1999 23:59:59 GMT

源码分析一

/**  * 格式化为Http的标准日期格式<br>  * 标准日期格式遵循RFC 1123规范,格式类似于:Fri, 31 Dec 1999 23:59:59 GMT  *  * @param date 被格式化的日期  * @return HTTP标准形式日期字符串  */ public static String formatHttpDate(Date date) {    if (null == date) {       return null;    }    return DatePattern.HTTP_DATETIME_FORMAT.format(date); } 复制代码

首先好习惯,先对入参进行判空处理

然后调用DatePattern.HTTP_DATETIME_FORMAT.format(date)返回FastDateFormat对象

针对只支持java8之前的程序,可以使用FastDateFormat线程安全替换SimpleDateFormat线程不安全--》源码分析

 FastDateFormat 是一个线程安全的实现 来源 Apache Commons Lang 3.5 复制代码

 /**  * HTTP头中日期时间格式 {@link FastDateFormat}:EEE, dd MMM yyyy HH:mm:ss z  */ public static final FastDateFormat HTTP_DATETIME_FORMAT = FastDateFormat.getInstance(HTTP_DATETIME_PATTERN, TimeZone.getTimeZone("GMT"), Locale.US); 复制代码

则转化为了FastDateFormat.format(date),源码分析看上面

方法名称:DateUtil.formatChineseDate(java.util.Date, boolean, boolean)

方法描述

格式化为中文日期格式,如果isUppercase为false,则返回类似:2018年10月24日,否则返回二〇一八年十月二十四日

源码分析一

/**  * 格式化为中文日期格式,如果isUppercase为false,则返回类似:2018年10月24日,否则返回二〇一八年十月二十四日  *  * @param date        被格式化的日期  * @param isUppercase 是否采用大写形式  * @param withTime    是否包含时间部分  * @return 中文日期字符串  * @since 5.3.9  */ public static String formatChineseDate(Date date, boolean isUppercase, boolean withTime) {    if (null == date) {       return null;    }    if (false == isUppercase) {       return (withTime ? DatePattern.CHINESE_DATE_TIME_FORMAT : DatePattern.CHINESE_DATE_FORMAT).format(date);    }    return CalendarUtil.formatChineseDate(CalendarUtil.calendar(date), withTime); } 复制代码

首先好习惯,先对入参进行判空处理

当不采用大写形式时

执行**(withTime ? DatePattern.CHINESE_DATE_TIME_FORMAT : DatePattern.CHINESE_DATE_FORMAT).format(date)**

这里用了一个多目运算,包含时间部分时,调用DatePattern.CHINESE_DATE_TIME_FORMAT .format(date), 不包含时间部分时,调用DatePattern.CHINESE_DATE_FORMAT.format(date)

/**  * 标准日期格式 {@link FastDateFormat}:yyyy年MM月dd日HH时mm分ss秒  */ public static final FastDateFormat CHINESE_DATE_TIME_FORMAT = FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN); /**  * 标准日期格式 {@link FastDateFormat}:yyyy年MM月dd日  */ public static final FastDateFormat CHINESE_DATE_FORMAT = FastDateFormat.getInstance(CHINESE_DATE_PATTERN); 复制代码

由上面源码可知,两个都是返回FastDateFormat对象。

针对只支持java8之前的程序,可以使用FastDateFormat线程安全替换SimpleDateFormat线程不安全--》源码分析

 FastDateFormat 是一个线程安全的实现 来源 Apache Commons Lang 3.5 复制代码

 /**  * HTTP头中日期时间格式 {@link FastDateFormat}:EEE, dd MMM yyyy HH:mm:ss z  */ public static final FastDateFormat HTTP_DATETIME_FORMAT = FastDateFormat.getInstance(HTTP_DATETIME_PATTERN, TimeZone.getTimeZone("GMT"), Locale.US); 复制代码

则转化为了FastDateFormat.format(date),源码分析看上面

当采用大写形式时

执行CalendarUtil.formatChineseDate(CalendarUtil.calendar(date), withTime);

可以拆解成两部分:

  • CalendarUtil.calendar(date):把date对象转换为Calendar对象

  • CalendarUtil.formatChineseDate(Calendar calendar, boolean withTime):将指定Calendar时间格式化为纯中文形式,例如

     2018-02-24 12:13:14转换为 二〇一八年二月二十四日(withTime为false) 2018-02-24 12:13:14 转换为 二〇一八年二月二十四日一十二时一十三分一十四秒(withTime为true) 复制代码

CalendarUtil.calendar(date)源码分析:

/**  * 转换为Calendar对象  *  * @param date 日期对象  * @return Calendar对象  */ public static Calendar calendar(Date date) {    if (date instanceof DateTime) {       return ((DateTime) date).toCalendar();    } else {       return calendar(date.getTime());    } } 复制代码

如果是hutool提供的DateTime类型,可以直接转化获取。

如果是date类型,则通过*cal.setTimeInMillis转化为Calendar对象。

/**  * 转换为Calendar对象  *  * @param millis 时间戳  * @return Calendar对象  */ public static Calendar calendar(long millis) {    final Calendar cal = Calendar.getInstance();    cal.setTimeInMillis(millis);    return cal; } 复制代码

有同学会发现,这个跟我们常用的Calendar.setTime(Date date),不一样,看了下源码

//Calendar public final void setTime(Date date) {     setTimeInMillis(date.getTime()); } 复制代码

本质上是一样的。。setTime方法里也是会调用setTimeInMillis方法。

CalendarUtil.formatChineseDate(Calendar calendar, boolean withTime)源码分析:

/**  * 将指定Calendar时间格式化为纯中文形式,比如:  *  * <pre>  *     2018-02-24 12:13:14转换为 二〇一八年二月二十四日(withTime为false)  *     2018-02-24 12:13:14 转换为 二〇一八年二月二十四日一十二时一十三分一十四秒(withTime为true)  * </pre>  *  * @param calendar {@link Calendar}  * @param withTime 是否包含时间部分  * @return 格式化后的字符串  * @since 5.3.9  */ public static String formatChineseDate(Calendar calendar, boolean withTime) {    final StringBuilder result = StrUtil.builder();    // 年    String year = String.valueOf(calendar.get(Calendar.YEAR));    final int length = year.length();    for (int i = 0; i < length; i++) {       result.append(NumberChineseFormatter.numberCharToChinese(year.charAt(i), false));    }    result.append('年');    // 月    int month = calendar.get(Calendar.MONTH) + 1;    result.append(NumberChineseFormatter.format(month, false));    result.append('月');    // 日    int day = calendar.get(Calendar.DAY_OF_MONTH);    result.append(NumberChineseFormatter.format(day, false));    result.append('日');    if (withTime) {       // 时       int hour = calendar.get(Calendar.HOUR_OF_DAY);       result.append(NumberChineseFormatter.format(hour, false));       result.append('时');       // 分       int minute = calendar.get(Calendar.MINUTE);       result.append(NumberChineseFormatter.format(minute, false));       result.append('分');       // 秒       int second = calendar.get(Calendar.SECOND);       result.append(NumberChineseFormatter.format(second, false));       result.append('秒');    }    return result.toString().replace('零', '〇'); } 复制代码

由源码可知,是按年月日时分秒,每个字段进行转化。


作者:小虚竹and掘金
链接:https://juejin.cn/post/7028414550709895205


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐