一、Date和Calendar的使用
(1)SimpleDateFormat 类
要讲Date
和Calendar
的使用的话,首先不得不提的就是SimpleDateFormat
类。
SimpleDateFormat
是与语言环境有关的方式来格式化和解析日期的具体类,它允许进行format
格式化(日期→文本)、parse
解析(文本→日期)和规范化。SimpleDateFormat
使得可以选择任何用户定义的日期/时间格式的模式。
(2)Date类
初始化
Date date= new Date(); // 分配对象并初始话
Date date= new Date(long date) // 从1970年1月1日00:00:00 GMT)以来的指定毫秒数。
getTime()
new Date().getTime();
// 1585738754693 是从1970年开始的毫秒数
时间比较
使用before()
、equals()
、after()
方法进行时间比较。
date1.before(date2)
date1.equals(date2)
date1.after(date2)
字符串和Date类型转换
使用SimpleDateFormat
的format()
和parse()
方法进行字符串和Date类型转换。
public void test1() throws Exception{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = new Date();
String strDate = sdf.format(date); // Date -> String
String strDate1 = "2020-04-01 08:35:53";
Date date1 = sdf.parse(strDate1); // String -> Date
System.out.println(strDate); // 2020-04-01 06:59:14
System.out.println(date1); // Wed Apr 01 08:35:53 CST 2020
}
已经被弃用的方法
在Date
类中,getYear()
、getMonth()
、getDate()
、getHours()
等方法都已经被弃用。
因为Date
类是以历元为标准的,所以new Date().getYear()
的值是 120 然而今年是 2020年 。new Date().getMonth()
的值是4然而这个月是4月份。
所以当年的设计很奇葩,就有了Calender
类来提供对时间的操作。
(3)Calendar类
获取Calendar实例
Calendar calendar = Calendar.getInstance();
Date和Calendar转换
通过getTime()
和setTime()
实现Date
和Calendar
类型转换
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date()); // Date -> Calendar
calendar.getTime(); // Calendar -> Date
获取年月日时分秒
通过get()
方法,可以获取该实例的年月日时分秒等信息
Calendar calendar = Calendar.getInstance();
calendar.get(Calendar.YEAR); // 年份
calendar.get(Calendar.MONTH); // 月份(比实际月份少1)
calendar.get(Calendar.DAY_OF_MONTH); // 日
calendar.get(Calendar.HOUR_OF_DAY); // 时
calendar.get(Calendar.MINUTE); // 分
需要注意的是月份按0—11总共十二个月,不是从1开始的
操作年月日时分秒
Calendar类中提供了add()
方法,可以直接对某部分进行操作。例如:
Calendar c = Calendar.getInstance();
c.add(Calendar.DAY_OF_MONTH, +1); // 日期+1
二、Groovy中TimeCategory
在做表达式解析的时候,学习到groovy.time.*
包,主要包含持续时间类和对日期时间常用操作类。
包中主要包含如下类:
各个类之间的关系如下图所示:
其中,Duration系列类中都是存放着一个持续时间。然后通过TimeCategory类中的方法实现对时间操作。
TimeCategory.minus(new Date(),TimeCategory.getDays(1)); // 日期减1天,返回Date类型
分析一下TimeCategory类的源码:
public class TimeCategory {
public static Date plus(final Date date, final BaseDuration duration) {
return duration.plus(date);
}
// 其实就是使用Calendar类进行时间操作
public static Date minus(final Date date, final BaseDuration duration) {
final Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.YEAR, -duration.getYears());
cal.add(Calendar.MONTH, -duration.getMonths());
cal.add(Calendar.DAY_OF_YEAR, -duration.getDays());
cal.add(Calendar.HOUR_OF_DAY, -duration.getHours());
cal.add(Calendar.MINUTE, -duration.getMinutes());
cal.add(Calendar.SECOND, -duration.getSeconds());
cal.add(Calendar.MILLISECOND, -duration.getMillis());
return cal.getTime();
}
// ......省略
}
三、为什么推荐使用LocalDateTime而不是Date
1、Date如果不格式化,可读性差
Wed Apr 01 16:35:53 CST 2020
2、存在线程安全问题
使用SimpleDateForma
t对时间进行格式化,但是SimpleDateFormat
不是线程安全的。
(1)format()
方法不是线程安全的
SimpleDateFormat
的format
方法最终调用代码:
protected Calendar calendar; // calendar是一个共享变量
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date); // 这里存在线程安全问题
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
calendar
是共享变量,并且这个共享变量没有做线程安全控制。
当多个线程同时使用相同的SimpleDateFormat
对象【如用static修饰的SimpleDateFormat】调用format()
方法时,多个线程会同时调用calendar.setTime()
方法,可能一个线程刚设置好time值另外的一个线程马上把设置的time值给修改了导致返回的格式化时间可能是错误的。在多并发情况下使用SimpleDateFormat
需格外注意。
(2)parse()
方法不是线程安全的
parse()
方法实际调用alb.establish(calendar).getTime()
方法来解析。
alb.establish(calendar)
方法里主要完成了:
- 1. 重置日期对象cal的属性值
- 2. 使用calb中中属性设置cal
- 3. 返回设置好的cal对象
但是这三步不是原子操作,所以会存在线程安全问题。
public Date parse(String text, ParsePosition pos)
{
// ......省略
pos.index = start;
Date parsedDate;
try {
// 这里存在线程安全问题
parsedDate = calb.establish(calendar).getTime();
if (ambiguousYear[0]) {
if (parsedDate.before(defaultCenturyStart)) {
parsedDate = calb.addYear(100).establish(calendar).getTime();
}
}
}
catch (IllegalArgumentException e) {
pos.errorIndex = start;
pos.index = oldStart;
return null;
}
return parsedDate;
}
(3)多线程并发如何保证线程安全
- 避免线程之间共享一个
SimpleDateFormat
对象,每个线程使用时都创建一次SimpleDateFormat
对象 => 创建和销毁对象的开销大- 对使用
format
和parse
方法的地方进行加锁 => 线程阻塞性能差- 使用
ThreadLocal
保证每个线程最多只创建一次SimpleDateFormat
对象 => 较好的方法
3、Date对时间处理比较麻烦
比如想获得某年、某月、某日,比如想得到n天后的时间,如果使用Date处理的话太难了。
因为月份是从0开始的,从Calendar
中获取的月份需要加一才能表示当前月份。
参考文章1
四、Java8的日期API
由于在 JDK8 发布以前日期类库中存在的诸多诟病,所以在 JDK8 中增加了新的日期处理类库,新版类库不仅丰富了日期、时间抽象,而且在 API 设计上也更易理解和使用。
用了Java8的日期API之后,都不想再碰原来的Date啦哈哈哈!
JDK8日期类包含如下 package:
java.time:基于ISO_8601日历系统实现的日期时间库
java.time.chrono:全球日历系统扩展库,可以自行扩展
java.time.format:日期时间格式,包含大量预定义的格式,可以自行扩展
java.time.zone:时区信息库
java.time.temporal:日期时间调整辅助库
1、基本概念
首先理解一下基本概念
(1)时刻:
所有计算机系统内部都用一个整数表示时刻,这个整数是距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数,可以理解时刻就是绝对时间,它与时区无关,不同时区对同一时刻的解读,即年月日时分秒是不一样的;
(2)时区:
同一时刻,世界上各个地区的时间可能是不一样的,具体时间与时区有关,一共有24个时区,英国格林尼治是0时区,北京是东八区,也就是说格林尼治凌晨1点,北京是早上9点;
(3)年历:
我们都知道,中国有公历和农历之分,公历和农历都是年历,不同的年历,一年有多少月,每月有多少天,甚至一天有多少小时,这些可能都是不一样的,我们主要讨论公历。
2、常用的类
Instant
:表示时刻,不直接对应年月日信息,需要通过时区转换
LocalDateTime
: 表示与时区无关的日期和时间信息,不直接对应时刻,需要通过时区转换
LocalDate
:表示与时区无关的日期,与LocalDateTime相比,只有日期信息,没有时间信息
LocalTime
:表示与时区无关的时间,与LocalDateTime相比,只有时间信息,没有日期信息
ZonedDateTime
: 表示特定时区的日期和时间
ZoneId/ZoneOffset
:表示时区
YearMonth
:只包含年月
MonthDay
:只包含月日
Year
:只包含年
DayOfWeek
:这是一个枚举类,定义了周日到周六的枚举值
Month
:这也是一个枚举类,定义了一月到十二月的枚举值。
DateTimeFormatter
:格式化器,默认提供多种格式化方式。线程安全的
3、常用操作
(1)获取当前时间
通过静态方法now()
,获取当前时间
LocalDateTime.now();
(2)解析和格式化
通过静态方法parse()
或of()
,得到时间对象
LocalDateTime.parse("2020-03-31T02:02:02")
LocalDateTime.of(2020,3,31,20,20);
(3)比较时间
通过isBefore()
、isAfter()
、isEqual()
方法,进行时间比较
localDateTime1.isBefore(localDateTime2);
(4)获取某对象的年、月、日、星期几、时、分、秒
通过get***()
方法,进行获取年月日时分秒的指定部分
localDateTime1.getYear();
(5)增加、减少某对象的年、月、日、星期几、时、分、秒
通过plus***()
或minus***()
方法,进行时间增减
localDateTime1.plusYears(long);
LocalDate
、LocalTime
、LocalDateTime
、Instant
为不可变对象,修改这些对象对象会返回一个副本
(6)设置某对象的年、月、日、星期几、时、分、秒
通过with***()
方法,直接设置指定部分
localDateTime1.withYear(int);
整理方法如下:
本来在自己word上整理的,复制过来也太费劲啦。索性就直接截图吧。。。。。
五、Date和LocalDateTime、LocalDate、LocalTime转换
1、在java8中,Date类中新增了两个方法
新增方法from(Instant instant)
和toInstant()
// 使用Instant创建Date
public static Date from(Instant instant) {
try {
return new Date(instant.toEpochMilli());
} catch (ArithmeticException ex) {
throw new IllegalArgumentException(ex);
}
}
// Date获取Instant
public Instant toInstant() {
return Instant.ofEpochMilli(getTime());
}
这两个新增的方法也是Date
和LocalDateTime
、LocalDate
、LocalTime
能够进行转换的基础。
转换思路:
a、旧转新:
Date.toInstant() 获取 Instant,使用 Instant 创建 LocalDateTime,然后从
LocalDateTime 获取 LocalDate 和 LocalTime。
b、新转旧:
将 LocalDate 和 LocalTime转为 LocalDateTime,然后从 LocalDateTime 获取 Instant,使用 Date.from(Instant) 创建Date。
2、类型转换
注意:转换过程中,均采用系统默认时区 ZoneId.systemDefault();
import java.time.*;
import java.util.Date;
/**
* @author Caocs
* @date 2020/4/1
* 转换过程中,均采用系统默认时区 ZoneId.systemDefault();
*/
public class RuleExecutorTest6 {
/**
* Date -> LocalDateTime
*/
public static LocalDateTime dateToLocalDateTime(Date date) {
Instant instant = date.toInstant();
ZoneId zone = ZoneId.systemDefault();
return LocalDateTime.ofInstant(instant, zone);
}
/**
* Date -> LocalDate
*/
public static LocalDate dateToLocalDate(Date date) {
LocalDateTime localDateTime = dateToLocalDateTime(date);
return localDateTime.toLocalDate();
}
/**
* Date -> LocalTime
*/
public static LocalTime dateToLocalTime(Date date) {
LocalDateTime localDateTime = dateToLocalDateTime(date);
return localDateTime.toLocalTime();
}
/**
* LocalDateTime -> Date
*/
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
return Date.from(instant);
}
/**
* LocalDate -> Date
* 时间部分全部为0
*/
public static Date localDateToDate(LocalDate localDate) {
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
return Date.from(instant);
}
/**
* LocalTime -> Date
* 日期部分使用系统当前日期
*/
public static Date localTimeToDate(LocalTime localTime) {
LocalDate localDate = LocalDate.now();
ZoneId zone = ZoneId.systemDefault();
Instant instant = LocalDateTime.of(localDate, localTime).atZone(zone).toInstant();
return Date.from(instant);
}
public static void main(String[] args) {
System.out.println(dateToLocalDateTime(new Date())); // 2020-04-01T15:54:52.784
System.out.println(dateToLocalDate(new Date())); // 2020-04-01
System.out.println(dateToLocalTime(new Date())); // 15:54:52.850
System.out.println(localDateTimeToDate(LocalDateTime.now())); // Wed Apr 01 15:54:52 CST 2020
System.out.println(localDateToDate(LocalDate.now())); // Wed Apr 01 00:00:00 CST 2020
System.out.println(localTimeToDate(LocalTime.now())); // Wed Apr 01 15:54:52 CST 2020
}
}