5hutool源码分析:DateUtil(时间工具类)-解析被格式化的时间

2,771 阅读4分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

❤️作者简介:大家好,我是小虚竹。Java领域优质创作者🏆,CSDN博客专家认证🏆,华为云享专家认证🏆

❤️技术活,该赏

❤️点赞 👍 收藏 ⭐再看,养成习惯

看本篇文章前,建议先对java源码的日期和时间有一定的了解,如果不了解的话,可以先看这篇文章:

万字博文教你搞懂java源码的日期和时间相关用法

关联文章:

hutool实战(带你掌握里面的各种工具)目录

5hutool实战:DateUtil-解析被格式化的时间


源码分析目的

知其然,知其所以然

项目引用

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

        <dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-core</artifactId>
			<version>5.6.5</version>
		</dependency>

方法名称:DateUtil.parseLocalDateTime(java.lang.CharSequence)

方法描述

构建LocalDateTime对象
格式:yyyy-MM-dd HH:mm:ss

源码分析一

	/**
	 * 构建LocalDateTime对象
	 *
	 * @param dateStr 时间字符串(带格式)
	 * @param format  使用{@link DatePattern}定义的格式
	 * @return LocalDateTime对象
	 */
	public static LocalDateTime parseLocalDateTime(CharSequence dateStr, String format) {
		return LocalDateTimeUtil.parse(dateStr, format);
	}

parseLocalDateTime(CharSequence dateStr, String format)方法的format要使用DatePattern定义的格式,保证能解析出来。

来看看**LocalDateTimeUtil.parse(dateStr, format)**源码是如何写的:

//LocalDateTimeUtil
/**
	 * 解析日期时间字符串为{@link LocalDateTime}
	 *
	 * @param text   日期时间字符串
	 * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS
	 * @return {@link LocalDateTime}
	 */
	public static LocalDateTime parse(CharSequence text, String format) {
		if (null == text) {
			return null;
		}

		DateTimeFormatter formatter = null;
		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)){
				final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN);
				if(ReUtil.isMatch("[S]{1,2}", fraction)){
					//将yyyyMMddHHmmssS、yyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式,用0补
					text += StrUtil.repeat('0', 3-fraction.length());
				}
				formatter = new DateTimeFormatterBuilder()
						.appendPattern(DatePattern.PURE_DATETIME_PATTERN)
						.appendValue(ChronoField.MILLI_OF_SECOND, 3)
						.toFormatter();
			} else{
				formatter = DateTimeFormatter.ofPattern(format);
			}
		}

		return parse(text, formatter);
	}

注释里写着修复了JDK8 的一个bug bugs.openjdk.java.net/browse/JDK-…

试试:

	@Test
	public void localDateTimeTest5() {
		String strDate = "20210805220359100";
		DateTimeFormatter formatter =DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
		System.out.println(formatter.parse(strDate));
	}

image-20210805911767

真的会报错。

官方有给出解决方案,但在java9版本修复。下放到8u版本里。

image-20210805224431816

所以hutool这边的写法就好理解了,这个是官方给出的解决方案:修复yyyyMMddHHmmssSSS格式不能解析的问题

//LocalDateTimeUtil
/**
	 * 解析日期时间字符串为{@link LocalDateTime}
	 *
	 * @param text   日期时间字符串
	 * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS
	 * @return {@link LocalDateTime}
	 */
	public static LocalDateTime parse(CharSequence text, String format) {
		
	  ...
				formatter = new DateTimeFormatterBuilder()
						.appendPattern(DatePattern.PURE_DATETIME_PATTERN)
						.appendValue(ChronoField.MILLI_OF_SECOND, 3)
						.toFormatter();
		...

		return parse(text, formatter);
	}

最后调用parse(text, formatter);

/**
	 * 解析日期时间字符串为{@link LocalDateTime},格式支持日期时间、日期、时间
	 *
	 * @param text      日期时间字符串
	 * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}
	 * @return {@link LocalDateTime}
	 */
	public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
		if (null == text) {
			return null;
		}
		if (null == formatter) {
			return LocalDateTime.parse(text);
		}

		return of(formatter.parse(text));
	}

首先好习惯,先判断入参是否为空处理。

LocalDateTime.parse(text)//返回LocalDateTime对象
DateTimeFormatter.parse(text)//返回TemporalAccessor对象

都是java8 新提供的API:

万字博文教你搞懂java源码的日期和时间相关用法

最后使用**of(TemporalAccessor)**转化为LocalDateTime时间对象

首先来看下TemporalAccessor

TemporalAccessor 的实现类包含

  • Instant
  • LocalDateTime
  • ZonedDateTime
  • OffsetDateTime
  • LocalDate
  • LocalTime
  • OffsetTime
public static LocalDateTime of(TemporalAccessor temporalAccessor) {
		if (null == temporalAccessor) {
			return null;
		}

		if(temporalAccessor instanceof LocalDate){
			return ((LocalDate)temporalAccessor).atStartOfDay();
		}

		return LocalDateTime.of(
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR),
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR),
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH),
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.HOUR_OF_DAY),
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.MINUTE_OF_HOUR),
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.SECOND_OF_MINUTE),
				TemporalAccessorUtil.get(temporalAccessor, ChronoField.NANO_OF_SECOND)
		);
	}

首先,由上面可知,LocalDate是temporalAccessor的实现类。((LocalDate)temporalAccessor).atStartOfDay()这个就可以变成LocalDate.atStartOfDay()

public LocalDateTime atStartOfDay() {
        return LocalDateTime.of(this, LocalTime.MIDNIGHT);
    }

LocalTime.MIDNIGHT:

 /**
     * The time of midnight at the start of the day, '00:00'.
     */
    public static final LocalTime MIDNIGHT;

LocalDateTime是由LocalDate和LocalTime组合成的。

 public static LocalDateTime of(LocalDate date, LocalTime time) {
        Objects.requireNonNull(date, "date");
        Objects.requireNonNull(time, "time");
        return new LocalDateTime(date, time);
    }

然后,TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR)这个是hutool的源码,我们来看看

	/**
	 * 安全获取时间的某个属性,属性不存在返回0
	 *
	 * @param temporalAccessor 需要获取的时间对象
	 * @param field            需要获取的属性
	 * @return 时间的值,如果无法获取则默认为 0
	 */
	public static int get(TemporalAccessor temporalAccessor, TemporalField field) {
		if (temporalAccessor.isSupported(field)) {
			return temporalAccessor.get(field);
		}

		return (int)field.range().getMinimum();
	}

判断temporalAccessor是否有支持指定的字段,如果有,直接返回指定字段对应的时间值。如果没有,则执行

(int)field.range().getMinimum(),获取字段对应的最小值。

		System.out.println(ChronoField.YEAR.range().getMinimum());
		System.out.println(ChronoField.MONTH_OF_YEAR.range().getMinimum());
		System.out.println(ChronoField.DAY_OF_MONTH.range().getMinimum());
		System.out.println(ChronoField.HOUR_OF_DAY.range().getMinimum());
		System.out.println(ChronoField.MINUTE_OF_HOUR.range().getMinimum());
		System.out.println(ChronoField.SECOND_OF_MINUTE.range().getMinimum());
		System.out.println(ChronoField.NANO_OF_SECOND.range().getMinimum());

image-20210805953564

year值初始化时设的最小值和最大值

image-20210805233338612