• java的TimeUtils或者DateUtils的编写心得


    一、几种常见的日期和时间类介绍

       介绍时间工具类不可避免必须要去触碰几个常见的日期和时间类,所以就简单介绍一下。

    1、jdk1.8之前的日期时间类

    a、Date类

      我们可以通过new的方式生成一个Date对象,构造函数有参的和无参的,无参的是获取当前的系统的时间,Date这个类有不少过期的方法,而且Date是线程不安全的,所以当你需要考虑线程安全的情况时,Date其实使用起来有一定的局限。Date类中有fastTime成员变量,所以对于一个Date来说,存有时间戳的,这就给各种日期和时间对象的的转换提供了可能。

    b、Calendar类

      这是一个抽象类,其有多个子类补充了很多其他的功能。Calendar翻译过来就是日历,所以我们可以获取日期哪一年、哪一个月和哪一天,以及计算和获取某个月的第一天等等。Calendar也是线程不安全的,其中的set方法其实可以修改Calendar中的成员变量。

    c、SimpleDateFormat

      在java8之前我们一般习惯使用SimpleDateFormat这个类来进行时间转换成字符串的操作。但是这个类是线程不安全的,这就意味着我们在转换时候存在着转换风险,当然我们有解决的方法,一个是使用本地变量ThreadLocal存放SimpleDateFormat,如果使用Map来存放,其实在生成的时候还是有线程安全的问题,这里我们使用悲观锁来做一下限制,当然也可以使用线程安全的Map(Hashtable、synchronizedMap、ConcurrentHashMap)。

    下面使用synchronized加锁:

    /** 锁对象 */
        private static final Object lockObj = new Object();
    
        /** 存放不同的日期模板格式的sdf的Map */
        private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();
    
        /**
         * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
         * 
         * @param pattern
         * @return
         */
        private static SimpleDateFormat getSdf(final String pattern) {
            ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);
    
            // 生成的时候我们需要去考虑线程问题,Map并没有做线程处理,我们可以
            if (tl == null) {
                synchronized (lockObj) {
                    tl = sdfMap.get(pattern);
                    if (tl == null) {
                        // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
                        System.out.println("put new sdf of pattern " + pattern + " to map");
    
                        // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
                        tl = new ThreadLocal<SimpleDateFormat>() {
    
                            @Override
                            protected SimpleDateFormat initialValue() {
                                System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
                                return new SimpleDateFormat(pattern);
                            }
                        };
                        sdfMap.put(pattern, tl);
                    }
                }
            }
    
            return tl.get();
        }
    
        /**
         * ThreadLocal的原理是,获取一个静态变量的副本,这个其实是牺牲空间换取时间的案例
         * 
         * @param date
         * @param pattern
         * @return
         */
        public static String format(Date date, String pattern) {
            return getSdf(pattern).format(date);
        }
    
        public static Date parse(String dateStr, String pattern) throws ParseException {
            return getSdf(pattern).parse(dateStr);
        }

    当然我们也可以使用线程安全的map:

    private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new Hashtable<String, ThreadLocal<SimpleDateFormat>>();

    d、sql包中其实也有几个时间的类  java.sql.Date/Time/Timestamp

      首先这几个类继承自util包中的Date类,相当于将java.util.Date分开表示了。Date表示年月日等信息。Time表示时分秒等信息。Timestamp多维护了纳秒,可以表示纳秒。平时用的不是很多。也是线程不安全的类。

    2、java8以后的时间日期类 

    在java8以后新增加了date-time包

    a、Instant

       这个类在java8之前和之后的时间日期类中都提供了转换的方法,这样就能很明确的通过Instant这个中间变量实现,java8之前和之后的时间日期类的相互转换。但是我们需要注意的是,Instant主要维护的是秒和纳秒字段,可以表示纳秒范围,如果不符合转换条件,就会抛出异常。

    以Date类为例,看一下源码:

    public static Date from(Instant instant) {
            try {
                return new Date(instant.toEpochMilli());
            } catch (ArithmeticException ex) {
                throw new IllegalArgumentException(ex);
            }
        }
    
        /**
         * Converts this {@code Date} object to an {@code Instant}.
         * <p>
         * The conversion creates an {@code Instant} that represents the same
         * point on the time-line as this {@code Date}.
         *
         * @return an instant representing the same point on the time-line as
         *  this {@code Date} object
         * @since 1.8
         */
        public Instant toInstant() {
            return Instant.ofEpochMilli(getTime());
        }

    b、Clock

       有获取当前时间的方法,也可以获取当前Instant,Clock是有时区或者说时区偏移量的。Clock是一个抽象类,其内部有几个子类继承自Clock。

    几个抽象方法:

    public abstract ZoneId getZone();
    public abstract Clock withZone(ZoneId zone);
    public long millis() {
        return instant().toEpochMilli();
    }
    public abstract Instant instant();

    c、ZoneId/ZoneOffset/ZoneRules

       ZoneId和ZoneOffset都是用来代表时区的偏移量的,一般ZoneOffset表示固定偏移量,ZoneOffset 表示与UTC时区偏移的固定区域(即UTC时间为标准),不跟踪由夏令时导致的区域偏移的更改;ZoneId 表示可变区偏移,表示区域偏移及其用于更改区域偏移的规则夏令时。这里举一个简单的例子,美国东部时间,我们可以使用zoneId来表示,应为美国使用的是冬令时和夏令时的时候时间是有区别的,和中国的时差会有一个小时的差别。而ZoneRules 跟踪区域偏移如何变化,时区的真正规则定义在ZoneRules中,定义了什么时候多少偏移量。

    常用的几个:

    //美东时间
        public static final String TIMEZONE_EST_NAME = "US/Eastern";
        public static final ZoneId TIMEZONE_EST = ZoneId.of(TIMEZONE_EST_NAME);
    //北京时间
        public static final String TIMEZONE_GMT8_NAME = "GMT+8";
        public static final ZoneId TIMEZONE_GMT8 = ZoneId.of(TIMEZONE_GMT8_NAME); 
        public static final ZoneOffset BEIJING_ZONE_OFFSET =ZoneOffset.of("+08:00");
    public static final ZoneOffset STATISTIC_ZONE_OFFSET =ZoneOffset.of("+03:00"); private static final ZoneId NEW_YORK_ZONE_ID = ZoneId.of("America/New_York"); private static final ZoneId SHANGHAI_ZONE_ID = ZoneId.of("Asia/Shanghai");

    d、LocalDateTime/LocalTime/LocalDate/ZoneDateTime

    LocalDateTime/LocalTime/LocalDate都没有时区的概念,其中LocalDate主要是对日期的操作,LocalTime主要是对时间的操作,LocalDateTime则是日期和时间都会涉及:

    jshell> LocalDate.now()
    $46 ==> 2018-07-07
    
    jshell> LocalDate.of(2018, 3, 30)
    $47 ==> 2018-03-30
    
    jshell> LocalTime.now()
    $48 ==> 00:32:06.883656
    
    jshell> LocalTime.of(12,43,12,33333);
    $49 ==> 12:43:12.000033333
    
    jshell> LocalDateTime.now()
    $50 ==> 2018-07-07T00:32:30.335562400
    
    jshell> LocalDateTime.of(2018, 12, 30, 12,33)
    $51 ==> 2018-12-30T12:33
    
    jshell> LocalDateTime.of(LocalDate.now(), LocalTime.now())
    $52 ==> 2018-07-07T00:40:38.198318200

    而ZoneDateTime会带有时区和偏移量的,可以看看他的成员变量:

     /**
         * The local date-time.
         */
        private final LocalDateTime dateTime;
        /**
         * The offset from UTC/Greenwich.
         */
        private final ZoneOffset offset;
        /**
         * The time-zone.
         */
        private final ZoneId zone;

    可以看出这是集合了时间时区和偏移量的新的时间类。

    二、常用的TimeUtils或者DateUtils的编写

    import java.text.ParseException;
    import java.time.*;
    import java.time.format.DateTimeFormatter;
    import java.time.temporal.ChronoUnit;
    import java.util.Date;
    import java.util.Hashtable;
    import java.util.Map;
    
    /**
     * Created by hehuaichun on 2018/10/22.
     */
    public class TimeUtils {
        /**
         * 考虑港股和美股 采用GMT-1时区来确定报表日 即T日的报表包含北京时间T日9时至T+1日9时的数据
         */
        public static final ZoneId TIMEZONE_GMT_1 = ZoneId.of("GMT-1");
        public static final String TIMEZONE_EST_NAME = "US/Eastern";
        public static final ZoneId TIMEZONE_EST = ZoneId.of(TIMEZONE_EST_NAME);
        public static final String TIMEZONE_GMT8_NAME = "GMT+8";
        public static final ZoneId TIMEZONE_GMT8 = ZoneId.of(TIMEZONE_GMT8_NAME);
    
        /**
         * 常用时间转换格式
         */
        public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
        public static final String DATE_NO_GAP_FORMAT = "yyyyMMdd";
        public static final String DATE_GAP_FORMAT = "yyyy-MM-dd";
        public static final String TIME_HH_MM_FORMAT = "HHmm";
        public static final Map<String, DateTimeFormatter> DATE_TIME_FORMAT_MAP = new Hashtable<String, DateTimeFormatter>() {
            {
                put(TIME_FORMAT, DateTimeFormatter.ofPattern(TIME_FORMAT));
                put(DATE_NO_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_NO_GAP_FORMAT));
                put(DATE_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_GAP_FORMAT));
                put(TIME_HH_MM_FORMAT, DateTimeFormatter.ofPattern(TIME_HH_MM_FORMAT));
            }
        };
    
        /**
         * 根据format的格式获取相应的DateTimeFormatter对象
         *
         * @param format 时间转换格式字符串
         * @return
         */
        public static DateTimeFormatter getDateTimeFormatter(String format) {
            if (DATE_TIME_FORMAT_MAP.containsKey(format)) {
                return DATE_TIME_FORMAT_MAP.get(format);
            } else {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
                DATE_TIME_FORMAT_MAP.put(format, formatter);
                return formatter;
            }
        }
    
        /**
         * 获取当前日期的开始时间
         *
         * @param zoneId 时间偏移量
         * @return
         */
        public static LocalDateTime todayStart(ZoneId zoneId) {
            return startOfDay(0, zoneId);
        }
    
        /**
         * 获取当前的ZoneDateTime
         *
         * @param zoneId 时区偏移量
         * @return
         */
        public static ZonedDateTime now(ZoneId zoneId) {
            return ZonedDateTime.now(zoneId);
        }
    
        /**
         * 获取当前日期的开始时间ZonedDateTime
         *
         * @param date   日期
         * @param zoneId 时区偏移量
         * @return
         */
        public static ZonedDateTime localDateToZoneDateTime(LocalDate date, ZoneId zoneId) {
            return date.atStartOfDay(zoneId);
        }
    
        /**
         * 获取当前日期的开始时间
         *
         * @param dateTime
         * @return
         */
        public static LocalDateTime startOfDay(ZonedDateTime dateTime) {
            return dateTime.truncatedTo(ChronoUnit.DAYS).toLocalDateTime();
        }
    
        /**
         * 获取今天后的指定天数的开始时间
         *
         * @param plusDays 当前多少天后
         * @param zoneId   时区偏移量
         * @return
         */
        public static LocalDateTime startOfDay(int plusDays, ZoneId zoneId) {
            return startOfDay(now(zoneId).plusDays(plusDays));
        }
    
        /**
         * 获取指定日期的后几个工作日的时间LocalDate
         *
         * @param date 指定日期
         * @param days 工作日数
         * @return
         */
        public static LocalDate plusWeekdays(LocalDate date, int days) {
            if (days == 0) {
                return date;
            }
            if (Math.abs(days) > 50) {
                throw new IllegalArgumentException("days must be less than 50");
            }
            int i = 0;
            int delta = days > 0 ? 1 : -1;
            while (i < Math.abs(days)) {
                date = date.plusDays(delta);
                DayOfWeek dayOfWeek = date.getDayOfWeek();
                if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
                    i += 1;
                }
            }
            return date;
        }
    
        /**
         * 获取指定日期的后几个工作日的时间ZoneDateTime
         *
         * @param date
         * @param days
         * @return
         */
        public static ZonedDateTime plusWeekdays(ZonedDateTime date, int days) {
            return plusWeekdays(date.toLocalDate(), days).atStartOfDay(date.getZone());
        }
    
        /**
         * 获取当前月份的第一天的时间ZoneDateTime
         *
         * @param zoneId
         * @return
         */
        public static ZonedDateTime firstDayOfMonth(ZoneId zoneId) {
            return now(zoneId).withDayOfMonth(1);
        }
    
    
        /**
         * 将Date转成指定时区的Date
         *
         * @param date
         * @return
         */
        public static Date dateToDate(Date date, ZoneId zoneId) {
            LocalDateTime dt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
            return toDate(ZonedDateTime.of(dt, zoneId));
        }
    
        /**
         * 将LocalDate转成Date
         *
         * @param date
         * @return
         */
        public static Date toDate(LocalDate date) {
            return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
        }
    
        /**
         * ZonedDateTime 转换成Date
         *
         * @param dateTime
         * @return
         */
        public static Date toDate(ZonedDateTime dateTime) {
            return Date.from(dateTime.toInstant());
        }
    
        /**
         * String 转换成 Date
         *
         * @param date
         * @param format
         * @return
         * @throws ParseException
         */
        public static Date stringToDate(String date, String format, ZoneId zoneId) throws ParseException {
            DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
            Instant instant = Instant.from(formatter.parse(date));
            return Date.from(instant);
        }
    
        /**
         * 将Date转成相应的时区的localDate
         *
         * @param date
         * @param zoneId
         * @return
         */
        public static LocalDate toLocalDate(Date date, ZoneId zoneId) {
            return date.toInstant().atZone(zoneId).toLocalDate();
        }
    
        /**
         * 将Instant转成指定时区偏移量的localDate
         *
         * @param instant
         * @param zoneId
         * @return
         */
        public static LocalDate toLocalDate(Instant instant, ZoneId zoneId) {
            return instant.atZone(zoneId).toLocalDate();
        }
    
        /**
         * 将Instant转换成指定时区偏移量的localDateTime
         * @param instant
         * @param zoneId
         * @return
         */
        public static LocalDateTime toLocalDateTime(Instant instant, ZoneId zoneId){
            return instant.atZone(zoneId).toLocalDateTime();
        }
    
        /**
         * 将Instant转成系统默认时区偏移量的LocalDateTime
         * @param instant
         * @return
         */
        public static LocalDateTime toLocalDateTime(Instant instant){
            return toLocalDateTime(instant, ZoneId.systemDefault());
        }
    
        /**
         * 将ZoneDateTime 转成 指定时区偏移量的LocalDateTime
         * @param zonedDateTime  时间
         * @param zoneId         指定时区偏移量
         * @return
         */
        public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId){
            return zonedDateTime.toInstant().atZone(zoneId).toLocalDateTime();
        }
    
        /**
         *将ZoneDateTime 转成 LocalDateTime
         * @param zonedDateTime
         * @return
         */
        public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime){
           return zonedDateTime.toLocalDateTime();
        }
    
        /**
         * String 转成 ZoneDateTime
         * 需要类似 yyyy-MM-dd HH:mm:ss 需要日期和时间信息完整信息
         *
         * @param date
         * @param format
         * @param zoneId
         * @return
         */
        public static ZonedDateTime stringToZoneDateTime(String date, String format, ZoneId zoneId) {
            DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
            return ZonedDateTime.parse(date, formatter);
        }
    
    
        /**
         * 将时间戳long转成ZonedDateTime
         *
         * @param timeStamp
         * @param zoneId
         * @return
         */
        public static ZonedDateTime longToZoneDateTime(long timeStamp, ZoneId zoneId) {
            return ZonedDateTime.from(Instant.ofEpochMilli(timeStamp).atZone(zoneId));
        }
    
        /**
         * 两个时区的zoneDateTime相互转换
         *
         * @param zonedDateTime 需要转换的如期
         * @param zoneId        转换成的ZoneDateTime的时区偏移量
         * @return
         */
        public static ZonedDateTime zonedDateTimeToZoneDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId) {
            return ZonedDateTime.ofInstant(zonedDateTime.toInstant(), zoneId);
        }
    
        /**
         * Date 转成 指定时区偏移量的ZoneDateTime
         * @param date
         * @param zoneId
         * @return
         */
        public static ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId){
            return date.toInstant().atZone(zoneId);
        }
    
        /**
         * LocaldateTime 转成 指定时区偏移量的ZonedDateTime
         * @param localDateTime  本地时间
         * @param zoneId         转成ZonedDateTime的时区偏移量
         * @return
         */
        public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId){
            return localDateTime.atZone(zoneId);
        }
    
        /**
         * Date装换成String
         *
         * @param date   时间
         * @param format 转化格式
         * @return
         */
        public static String dateToString(Date date, String format, ZoneId zoneId) {
            DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
            return formatter.format(date.toInstant());
        }
    
        /**
         * ZoneDateTime 转换成 String
         *
         * @param dateTime
         * @param zoneId   localDateTime所属时区
         * @return
         */
        public static String zoneDateTimeToString(ZonedDateTime dateTime, String format, ZoneId zoneId) {
            DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
            return dateTime.format(formatter);
        }
    
        /**
         * LocalDateTime 转成 String
         *
         * @param localDateTime
         * @param format
         * @return
         */
        public static String localDateTimeToString(LocalDateTime localDateTime, String format){
            DateTimeFormatter formatter = getDateTimeFormatter(format);
            return localDateTime.format(formatter);
        }
    
        /**
         * 将ZonedDateTime转成时间戳long
         *
         * @parm zonedDateTime
         * @return
         */
        public static long zoneDateTimeToLong(ZonedDateTime zonedDateTime) {
            return zonedDateTime.toInstant().toEpochMilli();
        }
    
        /**
         * 将LocalDateTime转成时间戳long
         * @param localDateTime
         * @param zoneId
         * @return
         */
        public static long toLong(LocalDateTime localDateTime, ZoneId zoneId){
           return zoneDateTimeToLong(localDateTime.atZone(zoneId));
        }
    
    
    }

    三、编写的总结

    1、首先我们尽量使用线程安全的时间类,也就是说尽量使用java8以后的几种时间类。从源码可以看出,java8之后的几个时间类是用final修饰的,线程安全

    2、我们要利用好静态变量,也就是不仅在使用的全局变量的时候需要线程安全的问题,还需要考虑时间空间的代价。其实SimpleDateTimeFormat和DateTImeFormatter的生成会消耗不少资源。

    3、Date类中含有时间戳,Instant有两个成员变量seconds和nanos变量,这样Instant就作为一个中间量,作用很大,不仅提供了java8之前的时间类和java8之后时间类的转换,还提供了其他很多有用的方法。

    4、ZonedDateTime含有时区偏移变量,其他的如Date、LocalDateTime、LocalDate、LocalTime、Instant都不含有时区偏移变量,但是Date想要转换成java8之后的时间类或者使用DateTimeFormatter转换时,需要提供ZoneId或者ZoneOffset,类似这种 date.toInstant().atZone(zoneId)。整体思路实现Date -> Instant -> ZonedDateTime -> 其他时间类。

    5、ZonedDateTime是有日期和时间的,我们在DateTimeFormatter的时候需要注意, ZonedDateTime转成字符串非常的灵活,但是字符串转成ZonedDateTime的时候需要提供类似yyyy-MM-dd HH:mm:ss这种格式。

  • 相关阅读:
    Android4.0 以后不允许在主线程进行网络连接
    关于升级linux下apache
    事物复制中大项目(Large Article)出问题如何快速修复
    国企银行面试总结
    Git命令详解(一)-个人使用
    函数体的规模要小,尽量控制在 50 行代码之内
    函数的功能要单一,不要设计多用途的函数
    在函数体的“出口处”,对 return 语句的正确性和效率进行检查
    在函数体的“入口处”,对参数的有效性进行检查
    有些场合用“引用传递”替换“值传 递”可以提高效率
  • 原文地址:https://www.cnblogs.com/xiaotiaosi/p/9806920.html
Copyright © 2020-2023  润新知