Java提供了Date和Calendar用于处理日期、时间的类,但是Date无法实现国际化,且对不同属性使用了前后矛盾的偏移量,比如月份与小时都是从0开始,月份中的天数却是从1开始的,年又是从1900年开始。而Calendar则显得过于复杂。因此Java 8 提供了一套全新的日期时间库。
一、Date类
- 通常使用的是java.util包下的
- 构造方法: Date date = new Date();该构造器会在底层调用System.currentTimeMillis()获得long整数作为日期参数。即这个方法获得的时间是系统当前时间
- 常用方法:
- boolean before(Date when): date1.before(date2): 比较date1时间是否在date2之前
- boolean after(Date when);测试该日期是否在指定日期之后
- setTime(long time);设置Date对象的时间
- long getTime(); 返回该时间对应的long型整数
4.设置Date类时间格式:DateFormat类
该类是抽象类,需要子类才能使用,所以通常使用其子类:SimpleDateFormat类来处理Date类的格式
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH-mm-ss”);
String v = sdf.format(new Date())//将当前日期以指定格式返回,并转化成字符串
或者
Date d = sdf.parse("2019-10-11 11:57:00")
注意:在多线程下,SimpleDateFormate是非线程安全的。
二、Calendar类
Calendar类是一个抽象类,它用于表示日历。
- java.util包下
- 有构造方法,用protected修饰,通常访问不到。需要调用默认的getInstance()方法
- Calendar caledar = Calendar.getInstance(); //系统当前时间的对象
- void set(int year,int month,int date):设置Calendar对象的年、月、日三个字段的值。因为月份是从0开始的,所以要设置8月时,第二个参数应该用7;
- int year = calendar.get(Canlendar.YEAR);//其他同理,但是month这个参数是从0开始数的,所以当获取当前月份时,记得+1;
- 获取时区:calendar.getTimeZone();
- 获取明天的当前时间 :
//JDK1.8之前:
Calendar calendar = Calendar.getInstance();//获取系统当前时间
Calendar.add(Calendar.DATE,1);//对日期加1,add()方法第一个参数可以是YEAR、MONTH、DATE,第二个参数可正可负。正表示未来,负表示过去
System.out.println(calendar.getTime())
//JDK1.8之后:
//方法一:
LocalDateTime today = LocalDateTime.now();
LocalDateTime tomorrow = today.plusDays(1);//增加一天
System.out.println(tomorrow);
//方法二:
LocalDateTime tomorrow = today.minusDays(-1);//minus是减去一天,参数是负数时,则表示加一天。
除了add()方法外,Calendar类还提供了一个方法,实现同样的效果:void roll(int field,int amount)
二者的区别在于:
(1)add()的功能更加强大,当被修改的字段超出他允许的范围时,他会自动向前进位。
例: Calendar call = Calendar.getInstance();
call.set(2003,7,23,0,0,0);//2003-8-23
call.add(MONTH,6);//2003-8-23 -->2004-2-23
(2)如果下一级的字段也需要改变,该字段会在自动修正到变化最小的值
例: Calendar call = Calendar.getInstance();
call.set(2003,7,31,0,0,0);//2003-8-31
//因为进位之后2月没有31号,所以自动修正到29号
call.add(MONTH,6);//2003-8-31 -->2004-2-29
(3) roll()则不会自动增大
三、Java 8中的时间操作
Java 8 中新增了三个类:
LocalDateTime:既包含时间也包含日期,不可变且线程安全
LocalDate: 只包含日期,不包含时间,不可变且线程安全
LocalTime: 只包含时间,不包含日期,不可变且线程安全
1、获取时间
LocalDate localDate = LocalDate.now();//获取当前日期
LocalTime localTime = LocalTime.mow();//获取当前时间
LocalDateTime ldt = LocalDateTime.now();//获取时间和日期
2、获取时间戳
时间戳是指格林威治时间1970-01-01 00:00:00(北京时间 1970-01-01 08:00:00)起到现在的总秒数
long milli = Instant.now().toEpochMilli();//获取当前时间戳,精确到毫秒
long second = Instant.now.getEpoSecond();//获取当前时间戳,精确到秒
3、时间格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String timeFormat = dateTimeFormatter.format(LocalDateTime.now());
//LocalDateTime:该类是布带时区的日期、时间
System.out.println(timeFormat);//输出 2019-10-11 12:37:50
4、时间转化
String timeStr = "2019-10-11 12:40:23";
LocalDateTime dateTime = LocalDateTime.parse(timeStr,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
相关面试题
1. 获取当前时间有几种方式?
答:获取当前时间常见的方式有以下三种:
new Date()
Calendar.getInstance().getTime()
LocalDateTime.now()
2. 如何获取昨天此刻的时间?
答:以下为获取昨天此刻时间的两种方式:
// 获取昨天此刻的时间(JDK 8 以前)
Calendar c = Calendar.getInstance();
c.add(Calendar.DATE,-1);
System.out.println(c.getTime());
// 获取昨天此刻的时间(JDK 8)
LocalDateTime todayTime = LocalDateTime.now();
System.out.println(todayTime.plusDays(-1));
3. 如何获取本月的最后一天?
答:以下为获取本月最后一天的两种方式:
// 获取本月的最后一天(JDK 8 以前)
Calendar ca = Calendar.getInstance();
ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
System.out.println(ca.getTime());
// 获取本月的最后一天(JDK 8)
LocalDate today = LocalDate.now();
System.out.println(today.with(TemporalAdjusters.lastDayOfMonth()));
4. 获取当前时间的时间戳有几种方式?
答:以下为获取当前时间戳的几种方式:
System.currentTimeMillis()
new Date().getTime()
Calendar.getInstance()
Instant.now().toEpochMilli()
LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()
其中,第四种和第五种方式是 JDK 8 才新加的。
5. 如何优雅地计算两个时间的相隔时间?
答:JDK 8 中可以使用 Duration 类来优雅地计算两个时间的相隔时间,代码如下:
LocalDateTime dt1 = LocalDateTime.now();
LocalDateTime dt2 = dt1.plusSeconds(60);
Duration duration = Duration.between(dt1, dt2);
System.out.println(duration.getSeconds()); // output:60
6. 如何优雅地计算两个日期的相隔日期?
答:JDK 8 中可以使用 Period 类来优雅地计算两个日期的相隔日期,代码如下:
LocalDate d1 = LocalDate.now();
LocalDate d2 = d1.plusDays(2);
Period period = Period.between(d1, d2);
System.out.println(period.getDays()); //output:2
7. SimpleDateFormat 是线程安全的吗?为什么?
答:SimpleDateFormat 是非线程安全的。因为查看 SimpleDateFormat 的源码可以得知,所有的格式化和解析,都需要通过一个中间对象进行转换,这个中间对象就是 Calendar,这样的话就造成非线程安全。试想一下当我们有多个线程操作同一个 Calendar 的时候后来的线程会覆盖先来线程的数据,那最后其实返回的是后来线程的数据,因此 SimpleDateFormat 就成为了非线程的了。
8. 怎么保证 SimpleDateFormat 的线程安全?
答:保证 SimpleDateFormat 线程安全的方式如下:
使用 Synchronized,在需要时间格式化的操作使用 Synchronized 关键字进行包装,保证线程堵塞格式化;
手动加锁,把需要格式化时间的代码,写到加锁部分,相对 Synchronized 来说,编码效率更低,性能略好,代码风险较大(风险在于不要忘记在操作的最后,手动释放锁);
使用 JDK 8 的 DateTimeFormatter 替代 SimpleDateFormat。
9. JDK 8 中新增的时间类都有哪些优点?
答:JDK 8 中的优点具体有以下几个优点,如下:
线程安全性
使用的便利性(如获取当前时间戳的便利性、增减日期的便利性等)
编写代码更简单优雅,如当前时间的格式化:LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
10. 如何比较两个时间(Date)的大小?
答:时间比较有以下三种方式:
获取两个时间的时间戳,得到两个 long 类型的变量,两个变量相减,通过结果的正负值来判断大小;
通过 Date 自带的 before()、after()、equals() 等方法比较,代码示例 date1.before(date2);
通过 compareTo() 方法比较,代码示例:date1.compareTo(date2),返回值 -1 表示前一个时间比后一个时间小,0 表示两个时间相等,1 表示前一个时间大于后一个时间。
总结
JDK 8 之前使用 java.util.Date 和 java.util.Calendar 来操作时间,它们有两个很明显的缺点,第一,非线程安全;第二,API 调用不方便。JDK 8 新增了几个时间操作类 java.time 包下的 LocalDateTime、LocalDate、LocalTime、Duration(计算相隔时间)、Period(计算相隔日期)和 DateTimeFormatter,提供了多线程下的线程安全和易用性,让我们可以更好的操作时间。