基于jdk8 LocalDate系列API的全新实用时间工具类

基于jdk8 LocalDate系列API的实用时间工具类, 已经经过多个项目的考验与完善, 包含个人心得体会

欢迎转载,转载请注明网址:https://blog.csdn.net/qq_41910280

版本说明
2020-07-02 初次提交 (实际创作于2019年)
2020-08-22 更新:
  1.增加灵活的时间格式
  2.增加了周几的判断
  3.增加了获取年、月的开始或结束时间
2020-09-10 更新:
  1.优化了周几的判断
2020-11-13 更新:
  1.优化获得年月日的开始/结束时间, 注意: 本次更新极个别方法不兼容历史版本, 但十分容易迁移
  2.其他细节优化
2020-11-30 更新:
  1.细节优化
2021-11-12 更新:
  1.细节优化
  2.增加获取星期的开始/结束

简介
  本文提供一个全面的基于jdk8的时间工具类。Talk is cheap, let’s Getting Started.

1. 直接上代码

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjusters;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import static java.time.format.ResolverStyle.STRICT;

/**
 * <h3>时间工具类</h3>
 * <p>
 * <ul>
 * <li>u means year(year-proleptic) which can be negative<br/>
 * <li>y means year-of-era which con't be negative
 * </ul>
 *  example:<br/>
 *  1 in "u" equal to 1 AD in "y G"<br/>
 *  0 in "u" equal to 1 BC in "y G"<br/>
 *  -1 in "u" equal to 2 BC in "y G"
 * <p>
 *
 * @author zhouyou
 * @date 2019/10/28 18:18
 * @email zhouyouchn@126.com
 * @blog https://blog.csdn.net/qq_41910280
 */
public class TimeUtils {
    static {
        /**
         * 中国曾实行过夏令时(1986-1991) Asia/Shanghai会处理夏令时 而GMT+8不会
         * 设置JVM时区 避免docker等采用UTC
         */
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
    }

    /**
     * maybe we can set DEFAULT_ZONE_ID = ZoneOffset.ofHours(8);
     * or we can set DEFAULT_ZONE_ID = ZoneId.of("Asia/Shanghai");
     */
    public static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();
    /**
     * 东八区 并不能完全代表北京时间
     *
     * @see #DEFAULT_ZONE_ID
     * @see #getDefaultZoneOffset(LocalDateTime)
     * @see #getDefaultZoneOffset(Instant)
     */
    @Deprecated
    public static final ZoneOffset DEFAULT_ZONE_OFFSET = ZoneOffset.ofHours(8);

    public static final String DAY_BEGIN_TIME_SUFFIX = " 00:00:00";
    public static final String DAY_END_TIME_SUFFIX = " 23:59:59";

    /**
     * format=uuuu-MM-dd HH:mm:ss.SSS || parse月日时分秒毫秒可选
     */
    public static final DateTimeFormatter ISO_ELASTIC_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern("uuuu[-MM[-dd]][ [HH][:mm][:ss][.SSS]]")
            .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
            .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
            .toFormatter().withResolverStyle(STRICT).withZone(DEFAULT_ZONE_ID);

    public static final DateTimeFormatter ISO_DATE_TIMESTAMP = ISO_ELASTIC_DATE_TIME_FORMATTER;

    public static final DateTimeFormatter ISO_DATE_TIME = buildDateTimeFormatter("uuuu-MM-dd HH:mm:ss");

    public static final DateTimeFormatter ISO_DATE_MINUTE = buildDateTimeFormatter("uuuu-MM-dd HH:mm");

    public static final DateTimeFormatter ISO_DATE = buildDateTimeFormatter("uuuu-MM-dd");

    public static final DateTimeFormatter ISO_MONTH = buildDateTimeFormatter("uuuu-MM");

    public static final DateTimeFormatter ISO_TIME = buildDateTimeFormatter("HH:mm:ss");

    public static final DateTimeFormatter ISO_MONTHDAY = buildDateTimeFormatter("MM-dd");

    public static final DateTimeFormatter ISO_DATE_WITHOUT_HYPHEN = buildDateTimeFormatter("uuuuMMdd");

    public static final DateTimeFormatter ISO_DATETIME_WITHOUT_HYPHEN = buildDateTimeFormatter("uuuuMMddHHmmss");

    private static final String[] WEEK_DAY_STRS = {"一", "二", "三", "四", "五", "六", "日"};

    public static DateTimeFormatter buildDateTimeFormatter(String pattern) {
        return DateTimeFormatter.ofPattern(pattern).withResolverStyle(STRICT).withZone(DEFAULT_ZONE_ID);
    }


    /**
     * <p>
     * <b>脱离时间谈ZoneOffset是错误的</b><br/>
     * see: 日光节约时间DST(夏令时)
     * example: 在一年中, 洛杉矶一段时间为"-8", 一段时间为"-7"
     * </p>
     *
     * @param localDateTime
     * @return
     */
    private static ZoneOffset getDefaultZoneOffset(LocalDateTime localDateTime) {
        return DEFAULT_ZONE_ID.getRules().getOffset(localDateTime);
    }

    /**
     * @param instant
     * @return
     * @see #getDefaultZoneOffset(LocalDateTime)
     */
    private static ZoneOffset getDefaultZoneOffset(Instant instant) {
        return DEFAULT_ZONE_ID.getRules().getOffset(instant);
    }

    /**
     * LocalDateTime 和 Date 的相互转换
     */
    public static Date toDate(LocalDateTime localDateTime) {
        return localDateTime == null ? null : Date.from(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());
    }

    public static LocalDateTime toLocalDateTime(Date date) {
        return date == null ? null : toInstant(date).atZone(DEFAULT_ZONE_ID).toLocalDateTime();
    }

    /**
     * LocalDate 和 Date 的相互转换
     */
    public static Date toDate(LocalDate localDate) {
        return localDate == null ? null : Date.from(localDate.atStartOfDay(DEFAULT_ZONE_ID).toInstant());
    }

    public static LocalDate toLocalDate(Date date) {
        return date == null ? null : toInstant(date).atZone(DEFAULT_ZONE_ID).toLocalDate();
    }

    /**
     * LocalDate 和 LocalDateTime 的相互转换
     */
    public static LocalDateTime toLocalDateTime(LocalDate localDate) {
        return localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
    }

    public static LocalDate toLocalDate(LocalDateTime localDateTime) {
        return localDateTime == null ? null : localDateTime.toLocalDate();
    }

    /**
     * LocalTime 和 Date 的相互转换
     */
    public static Date toDate(LocalTime localTime) {
        return localTime == null ? null : Date.from(localTime.atDate(LocalDate.now()).atZone(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * 只保留h以下单位
     *
     * @param localTime
     * @return
     */
    public static Date toTime(LocalTime localTime) {
        return localTime == null ? null : Date.from(localTime.atDate(LocalDate.ofEpochDay(0L)).atZone(DEFAULT_ZONE_ID).toInstant());
    }

    public static LocalTime toLocalTime(Date date) {
        return date == null ? null : toInstant(date).atZone(DEFAULT_ZONE_ID).toLocalTime();
    }

    /**
     * 不直接使用toInstant防止java.sql.Time或java.sql.Date抛出异常
     * Instant instant;
     * if (date instanceof java.sql.Time || date instanceof java.sql.Date) {
     * Calendar calendar = Calendar.getInstance();
     * calendar.setTime(date);
     * instant = calendar.toInstant();
     * } else {
     * instant = date.toInstant();
     * }
     * return instant;
     *
     * @param date
     * @return
     */
    private static Instant toInstant(Date date) {
        return Instant.ofEpochMilli(date.getTime());
    }

    /**
     * @param date
     * @return
     * @see #defaultDateTimeFormatter(DateTimeFormatter)
     */
    public static String format(Date date) {
        return format(date, null);
    }

    public static String format(Date date, DateTimeFormatter formatter) {
        return format(toLocalDateTime(date), formatter);
    }

    public static String format(DateTimeFormatter formatter) {
        return format(LocalDateTime.now(), formatter);
    }

    /**
     * @param localDateTime
     * @see #defaultDateTimeFormatter(DateTimeFormatter)
     */
    public static String format(LocalDateTime localDateTime) {
        return format(localDateTime, null);
    }

    /**
     * @param localDateTime
     * @param formatter     默认{@link #ISO_DATE_TIME}
     * @return
     * @see #defaultDateTimeFormatter(DateTimeFormatter)
     */
    public static String format(LocalDateTime localDateTime, DateTimeFormatter formatter) {
        return defaultDateTimeFormatter(formatter).format(localDateTime);
    }

    /**
     * @param localDate
     * @return {@link #ISO_DATE}
     * @see #defaultDateFormatter(DateTimeFormatter)
     */
    public static String format(LocalDate localDate) {
        return format(localDate, null);
    }

    /**
     * @param localDate
     * @param formatter 默认{@link #ISO_DATE}
     * @return
     * @see #defaultDateFormatter(DateTimeFormatter)
     */
    public static String format(LocalDate localDate, DateTimeFormatter formatter) {
        return defaultDateFormatter(formatter).format(localDate);
    }

    /**
     * @param text {@link #ISO_ELASTIC_DATE_TIME_FORMATTER}
     * @return
     */
    public static LocalDateTime parse2LocalDateTime(String text) {
        return parse2LocalDateTime(text, null);
    }

    /**
     * @param text
     * @param formatter 默认{@link #ISO_ELASTIC_DATE_TIME_FORMATTER}
     * @return
     */
    public static LocalDateTime parse2LocalDateTime(String text, DateTimeFormatter formatter) {
        return LocalDateTime.parse(text, defaultDateTimeParser(formatter));
    }

    /**
     * @param text {@link #ISO_ELASTIC_DATE_TIME_FORMATTER}
     * @return
     */
    public static Date parse2Date(String text) {
        return toDate(parse2LocalDateTime(text));
    }

    /**
     * @param text
     * @param formatter 默认{@link #ISO_ELASTIC_DATE_TIME_FORMATTER}
     * @return
     */
    public static Date parse2Date(String text, DateTimeFormatter formatter) {
        return toDate(parse2LocalDateTime(text, formatter));
    }

    /**
     * @param text {@link #ISO_ELASTIC_DATE_TIME_FORMATTER}
     * @return
     */
    public static LocalDate parse2LocalDate(String text) {
        return parse2LocalDate(text, null);
    }

    /**
     * @param text
     * @param formatter 默认{@link #ISO_ELASTIC_DATE_TIME_FORMATTER}
     * @return
     */
    public static LocalDate parse2LocalDate(String text, DateTimeFormatter formatter) {
        return LocalDate.parse(text, defaultDateTimeParser(formatter));
    }

    /**
     * @param text MM-dd
     * @return
     */
    public static MonthDay parseMonthDay(String text) {
        return parseMonthDay(text, null);
    }

    /**
     * @param text MM-dd
     * @return
     */
    public static MonthDay parseMonthDay(String text, DateTimeFormatter formatter) {
        return MonthDay.parse(text, defaultMonthDayFormatter(formatter));
    }

    /**
     * other way 1: (存在夏令时问题)
     * return localDateTime.toInstant(DEFAULT_ZONE_OFFSET).toEpochMilli();
     * other way 2: (Timestamp效率低于Instant)
     * return Timestamp.valueOf(localDateTime).getTime();
     *
     * @param localDateTime
     * @return
     */
    public static long getTime(LocalDateTime localDateTime) {
        return localDateTime.atZone(DEFAULT_ZONE_ID).toInstant().toEpochMilli();
    }

    /**
     * 根据{@link #DEFAULT_ZONE_ID}进行时间戳解析
     *
     * @param timeMillis ms
     * @return
     */
    public static LocalDateTime parseTime(long timeMillis) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(timeMillis), DEFAULT_ZONE_ID);
    }

    /**
     * <b>固定偏移量</b>
     * 根据DEFAULT_ZONE_OFFSET进行时间戳解析
     *
     * @param timeMillis ms
     * @return
     * @see #parseTime(long)
     */
    @Deprecated
    public static LocalDateTime parseTimeFast(long timeMillis) {
        int nanoOfSecond = (int) Math.floorMod(timeMillis, 1000) * 1000_000;
        long epochSecond = Math.floorDiv(timeMillis, 1000);
        return LocalDateTime.ofEpochSecond(epochSecond, nanoOfSecond, DEFAULT_ZONE_OFFSET);
    }

    /**
     * <b>时间间隔</b>
     * <p>
     * 选择ChronoUnit是因为:<br/>
     * Duration和Period会计算各个时间差,但并不会整合,
     * 这意味着:如果相差2个月0天,获取的相差天数却是0
     * <p/>
     *
     * @param temporal1
     * @param temporal2
     * @param unit
     * @return temporal2-temporal1的unit时间差
     */
    public static long between(Temporal temporal1, Temporal temporal2, ChronoUnit unit) {
        return unit.between(temporal1, temporal2);
    }

    /**
     * @return <b>yyyy-MM-dd HH:mm:ss</b>
     */
    public static String now() {
        return now(null);
    }

    /**
     * @param formatter 默认yyyy-MM-dd HH:mm:ss
     * @return
     */
    public static String now(DateTimeFormatter formatter) {
        return format(formatter);
    }

    /**
     * @return {@link #ISO_DATE}
     */
    public static String nowDate() {
        return LocalDate.now().format(ISO_DATE);
    }

    /**
     * @return <b>yyyy-MM-dd</b>
     */
    @Deprecated
    public static String plusDate(long yearsToAdd, long monthsToAdd, long daysToAdd) {
        LocalDate now = LocalDate.now();
        if (yearsToAdd != 0) {
            now = now.plusYears(yearsToAdd);
        }
        if (monthsToAdd != 0) {
            now = now.plusMonths(monthsToAdd);
        }
        if (daysToAdd != 0) {
            now = now.plusDays(daysToAdd);
        }
        return now.format(ISO_DATE);
    }

    /**
     * @return <b>yyyy-MM-dd</b>
     */
    @Deprecated
    public static String plusDate(LocalDate localDate, long yearsToAdd, long monthsToAdd, long daysToAdd) {
        if (localDate == null) {
            localDate = LocalDate.now();
        }
        if (yearsToAdd != 0) {
            localDate = localDate.plusYears(yearsToAdd);
        }
        if (monthsToAdd != 0) {
            localDate = localDate.plusMonths(monthsToAdd);
        }
        if (daysToAdd != 0) {
            localDate = localDate.plusDays(daysToAdd);
        }
        return localDate.format(ISO_DATE);
    }

    /**
     * @return <b>yyyy-MM-dd</b>
     */
    @Deprecated
    public static String plusDate(Date date, long yearsToAdd, long monthsToAdd, long daysToAdd) {
        return plusDate(toLocalDate(date), yearsToAdd, monthsToAdd, daysToAdd);
    }

    /**
     * 获取当前时间是周几
     */
    public static String getDayOfWeek(Calendar calendar) {
        int i = calendar.get(Calendar.DAY_OF_WEEK);
        return WEEK_DAY_STRS[(i + 5) % 7];
    }

    /**
     * 获取当前时间是周几
     */
    public static String getDayOfWeek(LocalDate localDate) {
        int value = localDate.getDayOfWeek().getValue();
        return WEEK_DAY_STRS[value - 1];
    }

    /**
     * 获取当前时间是周几
     */
    public static String getDayOfWeek(Date date) {
        return getDayOfWeek(toLocalDate(date));
    }

    /**
     * @param text yyyy-MM-dd
     * @return
     */
    public static Date getDayBegin(String text) {
        return toDate(LocalDateTime.of(LocalDate.parse(text, ISO_DATE), LocalTime.MIN));
    }

    /**
     * @param text yyyy-MM-dd
     * @return
     */
    public static Date getDayEnd(String text) {
        return toDate(LocalDateTime.of(LocalDate.parse(text, ISO_DATE), LocalTime.MAX));
    }

    public static LocalDateTime getDayBegin(LocalDate localDate) {
        return LocalDateTime.of(localDate, LocalTime.MIN);
    }

    public static LocalDateTime getDayEnd(LocalDate localDate) {
        return LocalDateTime.of(localDate, LocalTime.MAX);
    }

    public static LocalDateTime getDayBegin(LocalDateTime localDateTime) {
        return getDayBegin(localDateTime.toLocalDate());
    }

    public static LocalDateTime getDayEnd(LocalDateTime localDateTime) {
        return getDayEnd(localDateTime.toLocalDate());
    }

    public static Date getDayBegin(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MIN));
    }

    public static Date getDayEnd(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MAX));
    }

    public static Date getYearBegin(Date date) {
        // 或.with(ChronoField.DAY_OF_YEAR, 1)
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MIN).with(TemporalAdjusters.firstDayOfYear()));
    }

    public static Date getYearEnd(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MAX).with(TemporalAdjusters.lastDayOfYear()));
    }

    public static LocalDateTime getYearBegin(LocalDateTime localDateTime) {
        return LocalDateTime.of(toLocalDate(localDateTime), LocalTime.MIN).with(TemporalAdjusters.firstDayOfYear());
    }

    public static LocalDateTime getYearEnd(LocalDateTime localDateTime) {
        return LocalDateTime.of(toLocalDate(localDateTime), LocalTime.MAX).with(TemporalAdjusters.lastDayOfYear());
    }

    public static LocalDate getYearBegin(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.firstDayOfYear());
    }

    public static LocalDate getYearEnd(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.lastDayOfYear());
    }

    public static Date getMonthBegin(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MIN).with(TemporalAdjusters.firstDayOfMonth()));
    }

    public static Date getMonthEnd(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MAX).with(TemporalAdjusters.lastDayOfMonth()));
    }

    public static LocalDateTime getMonthBegin(LocalDateTime localDateTime) {
        return LocalDateTime.of(toLocalDate(localDateTime), LocalTime.MIN).with(TemporalAdjusters.firstDayOfMonth());
    }

    public static LocalDateTime getMonthEnd(LocalDateTime localDateTime) {
        return LocalDateTime.of(toLocalDate(localDateTime), LocalTime.MAX).with(TemporalAdjusters.lastDayOfMonth());
    }

    public static LocalDate getMonthBegin(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.firstDayOfMonth());
    }

    public static LocalDate getMonthEnd(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.lastDayOfMonth());
    }

    public static Date getWeekBegin(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MIN).with(ChronoField.DAY_OF_WEEK, 1));
    }

    public static Date getWeekEnd(Date date) {
        return toDate(LocalDateTime.of(toLocalDate(date), LocalTime.MAX).with(ChronoField.DAY_OF_WEEK, 7));
    }

    public static LocalDateTime getWeekBegin(LocalDateTime localDateTime) {
        return LocalDateTime.of(toLocalDate(localDateTime), LocalTime.MIN).with(ChronoField.DAY_OF_WEEK, 1);
    }

    public static LocalDateTime getWeekEnd(LocalDateTime localDateTime) {
        return LocalDateTime.of(toLocalDate(localDateTime), LocalTime.MAX).with(ChronoField.DAY_OF_WEEK, 7);
    }

    public static LocalDate getWeekBegin(LocalDate localDate) {
        return localDate.with(ChronoField.DAY_OF_WEEK, 1);
    }

    public static LocalDate getWeekEnd(LocalDate localDate) {
        return localDate.with(ChronoField.DAY_OF_WEEK, 7);
    }

    /**
     * @param text yyyy-MM-dd
     * @return
     */
    @Deprecated
    public static Date[] getDayBeginAndEnd(String text) {
        LocalDate localDate = LocalDate.parse(text, ISO_DATE);
        return getDaysByLocalDate(LocalDateTime.of(localDate, LocalTime.MIN), LocalDateTime.of(localDate, LocalTime.MAX));
    }

    @Deprecated
    public static Date[] getDayBeginAndEnd(Date date) {
        LocalDate localDate = toLocalDate(date);
        return getDaysByLocalDate(LocalDateTime.of(localDate, LocalTime.MIN), LocalDateTime.of(localDate, LocalTime.MAX));
    }

    private static Date[] getDaysByLocalDate(LocalDateTime... localDateTimes) {
        return Arrays.stream(localDateTimes).map(TimeUtils::toDate).toArray(Date[]::new);
    }

    private static DateTimeFormatter defaultDateTimeParser(DateTimeFormatter formatter) {
        return formatter == null ? ISO_ELASTIC_DATE_TIME_FORMATTER : formatter;
    }

    private static DateTimeFormatter defaultDateTimeFormatter(DateTimeFormatter formatter) {
        return formatter == null ? ISO_DATE_TIME : formatter;
    }

    private static DateTimeFormatter defaultDateFormatter(DateTimeFormatter formatter) {
        return formatter == null ? ISO_DATE : formatter;
    }

    private static DateTimeFormatter defaultMonthDayFormatter(DateTimeFormatter formatter) {
        return formatter == null ? ISO_MONTHDAY : formatter;
    }

}
神奇的小尾巴:
本人邮箱:zhouyouchn@126.com zhoooooouyou@gmail.com
zhouyou@whut.edu.cn 欢迎交流,共同进步。
欢迎转载,转载请注明本网址。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值