Java-小技巧-004-jdk时间Date,jdk8时间,joda,calendar,获取当前时间前一周、前一月、前一年的时间

一、常用的Date【不推荐】

1.1、基础用法

示例

    @Test
    public void testDate() throws Exception {
        Date date=new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr="2019-07-23 08:17:01";
        //Date→ String
        String format = sdf.format(date);
        System.out.println(format);
        //String →  Date
        Date date1 = sdf.parse(dateStr);
        System.out.println(date1);
//        2019-07-23 08:25:03
//        Tue Jul 23 08:17:01 CST 2019
    }

1.2、线程不安全示例

    @Test
    public void testParse() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<String> dateStrList = Arrays.asList(
                "2018-04-01 10:00:01",
                "2018-04-02 11:00:02",
                "2018-04-03 12:00:03",
                "2018-04-04 13:00:04",
                "2018-04-05 14:00:05"
        );
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        for (String str : dateStrList) {
            executorService.execute(() -> {
                try {
                    simpleDateFormat.parse(str);
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

输出

java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)

线程不安全说明:  

SimpleDateFormat继承了DateFormat,公用了一个变量
public abstract class DateFormat extends Format {

    protected Calendar calendar;

在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法,如下:

parsedDate = calb.establish(calendar).getTime();
    Calendar establish(Calendar cal) {
        boolean weekDate = isSet(WEEK_YEAR)
                            && field[WEEK_YEAR] > field[YEAR];
        if (weekDate && !cal.isWeekDateSupported()) {
            // Use YEAR instead
            if (!isSet(YEAR)) {
                set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
            }
            weekDate = false;
        }

        cal.clear();

可知SimpleDateFormat维护的用于format和parse方法计算日期-时间的calendar被清空了,如果此时线程A将calendar清空且没有设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题!

1.3、解决线程安全的方案

1.3.1、加synchronized锁

以synchronized同步SimpleDateFormat对象。

    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static synchronized Date parse(String s) throws ParseException {
        return sdf.parse(s);
    }

串行执行,在大批量调用效率很低,不推荐

高并发时,使用该对象会出现阻塞,当前使用者使用时,其他使用者等待,尽管结果是对的,但是并发成了排队,实际上并没有解决问题,还会对性能以及效率造成影响。

1.3.2、每次调用创建一个对象

public Date parse(String s) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(s);
    }

缺点,耗内存,GC压力增大,每次创建对象销毁,分配和销毁内存空间,整理内存,GC。不推荐

SimpleDateFormat中使用了Calendar对象,由于该对象相当重,在高并发的情况下会大量的new SimpleDateFormat以及销毁SimpleDateFormat,极其耗费资源。

1.3.3、每个线程创建一个

static ThreadLocal<SimpleDateFormat> localSdf= new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }

推荐使用ThreadLocal,每个线程一个对象,提高format对象利用率

注意:使用ThreadLocal的Thread均持有ThreadLocal自定义的map对象,key是ThreadLocal对象的弱引用,随ThreadLocal的GC或者内存不足就会GC,value是我们创建的对象是强引用,可能一直存在。因此ThreadLocal调用结束后,需要显示调用remove()方法

使用ThreadLocal时,如果执行原子任务的过程是每一个线程执行一个任务,那么这样的声明基本和每次使用前创建实例对象是没区别的;如果使用的是多线程加任务队列,举个例子,tomcat有m个处理线程,外部有n个待处理任务请求,那么当执行n个任务时,其实只会创建m个SimpleDateFormat实例,对于单一的处理线程,执行任务是有序的,所以对于当前线程而言,不存在并发。

二、java8 localdate【推荐】

LocalDateTime,Date,String 互转

    @Test
    // 01. java.util.Date --> java.time.LocalDateTime
    public void UDateToLocalDateTime() {
        java.util.Date date = new java.util.Date();
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
    }

    @Test
    // 02. java.util.Date --> java.time.LocalDate
    public void UDateToLocalDate() {
        java.util.Date date = new java.util.Date();
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
        LocalDate localDate = localDateTime.toLocalDate();
    }
    @Test
    // 03. java.util.Date --> java.time.LocalTime
    public void UDateToLocalTime() {
        java.util.Date date = new java.util.Date();
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
        LocalTime localTime = localDateTime.toLocalTime();
    }

    @Test
    // 04. java.time.LocalDateTime --> java.util.Date
    public void LocalDateTimeToUdate() {
        LocalDateTime localDateTime = LocalDateTime.now();
        ZoneId zone = ZoneId.systemDefault();
        Instant instant = localDateTime.atZone(zone).toInstant();
        java.util.Date date = Date.from(instant);
    }


    @Test
    // 05. java.time.LocalDate --> java.util.Date
    public void LocalDateToUdate() {
        LocalDate localDate = LocalDate.now();
        ZoneId zone = ZoneId.systemDefault();
        Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
        java.util.Date date = Date.from(instant);
    }

    @Test
    // 06. java.time.LocalTime --> java.util.Date
    public void LocalTimeToUdate() {
        LocalTime localTime = LocalTime.now();
        LocalDate localDate = LocalDate.now();
        LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
        ZoneId zone = ZoneId.systemDefault();
        Instant instant = localDateTime.atZone(zone).toInstant();
        java.util.Date date = Date.from(instant);
    }

    @Test
    // 07. java.time.LocalDateTime --> String
    public void localDateTimeToString() {
        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String format = localDateTime.format(formatter);
        System.out.println(format);
    }


    @Test
    // 08. String - - > java.time.LocalDateTime
    public void stringTolocalDateTime() {
        String dateStr="2019-07-23 09:49:19";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
        System.out.println(dateTime);
    }

时间加减

LocalDateTime localDateTime3 = LocalDateTime.now();
LocalDate.now();
LocalTime.now();
localDateTime3.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
LocalDateTime localDateTime4 = localDateTime3.minus(23,ChronoUnit.MONTHS);
localDateTime4.atZone(ZoneId.systemDefault());
localDateTime4 = localDateTime4.withHour(3);
localDateTime4 = localDateTime4.withYear(2016);
localDateTime4 = localDateTime4.with(ChronoField.MONTH_OF_YEAR,3);

间隔计算

使用Duration进行 day,hour,minute,second等的计算
使用Period进行Year,Month的计算

Duration duration = Duration.between(localDateTime,localDateTime4);
duration.toDays();
duration.toHours();
duration.toMinutes();
Period period2 = Period.between(localDateTime.toLocalDate(),localDateTime4.toLocalDate());
period2.getYears();
period2.getMonths();
period2.toTotalMonths(); 

更多介绍、推荐使用java8 localdate等 线程安全 支持较好  地址

三、joda

3.1、问题背景

3.1.1、使用jdk1.8 推荐使用二中方式

3.1.2、在没有jdk1.8的time类库时

1》使用SampleDateFormat存在问题

2》Apache的 DateFormatUtils 与 FastDateFormat

  使用org.apache.commons.lang.time.FastDateFormat 与 org.apache.commons.lang.time.DateFormatUtils。

  存在的问题:

    apache保证是线程安全的,并且更高效。但是DateFormatUtils与FastDateFormat这两个类中只有format()方法,所有的format方法只接受long,Date,Calendar类型的输入,转换成时间串,目前不存在parse()方法,可由时间字符串转换为时间对象。

3.2、Joda-Time

  使用Joda-Time类库。

存在的问题:暂无

1、使用maven包

<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.9.9</version>
</dependency>

2、使用

Joda-Time — 面向 Java 应用程序的日期/时间库的替代选择,Joda-Time 令时间和日期值变得易于管理、操作和理解。事实上,易于使用是 Joda 的主要设计目标。其他目标包括可扩展性、完整的特性集以及对多种日历系统的支持。并且 Joda 与 JDK 是百分之百可互操作的,因此您无需替换所有 Java 代码,只需要替换执行日期/时间计算的那部分代码。

 1.创建一个用时间表示的某个随意的时刻 — 比如,2015年12月21日0时0分

DateTime dt = new DateTime(2015, 12, 21, 0, 0, 0, 333);// 年,月,日,时,分,秒,毫秒 

2.格式化时间输出

DateTime dateTime = new DateTime(2015, 12, 21, 0, 0, 0, 333);
System.out.println(dateTime.toString("yyyy/MM/dd HH:mm:ss EE"));

3.解析文本格式时间

DateTimeFormatter format = DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss");  
DateTime dateTime = DateTime.parse("2015-12-21 23:22:45", format); 
System.out.println(dateTime.toString("yyyy/MM/dd HH:mm:ss EE"));

4.在某个日期上加上90天并输出结果

DateTime dateTime = new DateTime(2016, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(90).toString("E MM/dd/yyyy HH:mm:ss.SSS");

注意:plus后原值没变,返回一个新的Datetime

5.到新年还有多少天 

public Days daysToNewYear(LocalDate fromDate) {
  LocalDate newYear = fromDate.plusYears(1).withDayOfYear(1);
  return Days.daysBetween(fromDate, newYear);
}

6.与JDK日期对象的转换

DateTime dt = new DateTime();  

//转换成java.util.Date对象  
Date d1 = new Date(dt.getMillis());  
Date d2 = dt.toDate(); 

7.时区

//默认设置为日本时间  
DateTimeZone.setDefault(DateTimeZone.forID("Asia/Tokyo"));  
DateTime dt1 = new DateTime();  
System.out.println(dt1.toString("yyyy-MM-dd HH:mm:ss"));

//伦敦时间  
DateTime dt2 = new DateTime(DateTimeZone.forID("Europe/London"));
System.out.println(dt2.toString("yyyy-MM-dd HH:mm:ss"));

8.计算间隔和区间

DateTime begin = new DateTime("2015-02-01");  
DateTime end = new DateTime("2016-05-01");  

//计算区间毫秒数  
Duration d = new Duration(begin, end);  
long millis = d.getMillis();  

//计算区间天数  
Period p = new Period(begin, end, PeriodType.days());  
int days = p.getDays();  

//计算特定日期是否在该区间内  
Interval interval = new Interval(begin, end);  
boolean contained = interval.contains(new DateTime("2015-03-01")); 

9.日期比较

DateTime d1 = new DateTime("2015-10-01"); 
DateTime d2 = new DateTime("2016-02-01"); 

//和系统时间比  
boolean b1 = d1.isAfterNow();  
boolean b2 = d1.isBeforeNow();  
boolean b3 = d1.isEqualNow();  

//和其他日期比  
boolean f1 = d1.isAfter(d2);  
boolean f2 = d1.isBefore(d2);  
boolean f3 = d1.isEqual(d2);  

资料:

Joda-Time 简介(中文)https://www.ibm.com/developerworks/cn/java/j-jodatime.html
Joda-Time 文档(英文)http://joda-time.sourceforge.net/

3、传统方式

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar c = Calendar.getInstance();
         
        //过去七天
        c.setTime(new Date());
        c.add(Calendar.DATE, - 7);
        Date d = c.getTime();
        String day = format.format(d);
        System.out.println("过去七天:"+day);
         
        //过去一月
        c.setTime(new Date());
        c.add(Calendar.MONTH, -1);
        Date m = c.getTime();
        String mon = format.format(m);
        System.out.println("过去一个月:"+mon);
         
        //过去三个月
        c.setTime(new Date());
        c.add(Calendar.MONTH, -3);
        Date m3 = c.getTime();
        String mon3 = format.format(m3);
        System.out.println("过去三个月:"+mon3);
         
        //过去一年
        c.setTime(new Date());
        c.add(Calendar.YEAR, -1);
        Date y = c.getTime();
        String year = format.format(y);
        System.out.println("过去一年:"+year);

 

posted @ 2017-06-01 09:04  bjlhx15  阅读(1322)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭