目录
3、LocalDateTime 获取年月日时分秒,相当于 LocalDate + LocalTime
4、Instant 获取秒数,用于表示一个时间戳(精确到纳秒)
5、Duration : 用于计算两个“时间”间隔 ,Period : 用于计算两个“日期”间隔
6、DateTimeFormatter : 解析和格式化日期或时间
7、ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期
一、旧的时间和日期
- Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且都不是线程安全的。
- Date如果不格式化,打印出的日期可读性差。
Sat Oct 12 17:12:32 CST 2019
使用 SimpleDateFormat 对时间进行格式化,但 SimpleDateFormat 是线程不安全的,SimpleDateFormat 的 format 方法源码如下:
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 需注意。例如如下代码:
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return sdf.parse("20191012");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> lists = new ArrayList<>();
for (int i = 0; i < 10; i++) {
lists.add(pool.submit(task));
}
for (Future<Date> future : lists) {
System.out.println(future.get());
}
pool.shutdown();
}
Caused by: java.lang.NumberFormatException: multiple points
解决线程安全问题的办法:
public static void main(String[] args) throws Exception {
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return DateFormatThreadLocal.convert("20191012");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> lists = new ArrayList<>();
for (int i = 0; i < 10; i++) {
lists.add(pool.submit(task));
}
for (Future<Date> future : lists) {
System.out.println(future.get());
}
pool.shutdown();
}
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
protected DateFormat initialValue(){
return new SimpleDateFormat("yyyyMMdd");
}
};
public static final Date convert(String source) throws ParseException{
return df.get().parse(source);
}
二、Java 8 新时间和日期API
Java 8的日期和时间类包含 LocalDate、LocalTime、Instant、Duration 以及 Period,这些类都包含在 java.time 包中,Java 8 新的时间API的使用方式,包括创建、格式化、解析、计算、修改,下面我们看下如何去使用。
1、LocalDate 只获取年月日
// 创建 LocalDate
// 获取当前年月日
LocalDate ld = LocalDate.now();
System.out.println(ld);
//构造指定的年月日
LocalDate ld1 = LocalDate.of(2019, 10, 12);
System.out.println(ld1);
// 获取年、月、日、星期几
int year = ld.getYear();
int year1 = ld.get(ChronoField.YEAR);
System.out.println(year + "," + year1);
// 获取月
Month month = ld.getMonth();
int month1 = ld.get(ChronoField.MONTH_OF_YEAR);
System.out.println(month + "," + month1);
// 获取日
int day = ld.getDayOfMonth();
int day1 = ld.get(ChronoField.DAY_OF_MONTH);
System.out.println(day + "," + day1);
// 获取星期几
DayOfWeek dayOfWeek = ld.getDayOfWeek();
int dayOfWeek1 = ld.get(ChronoField.DAY_OF_WEEK);
System.out.println(dayOfWeek + "," + dayOfWeek1);
运行结果:
2、LocalTime 只会获取时分秒
//创建 LocalTime
LocalTime localTime = LocalTime.of(12, 14, 14);
LocalTime localTime1 = LocalTime.now();
System.out.println(localTime + "," + localTime1);
//获取小时
int hour = localTime.getHour();
int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
System.out.println(hour + "," + hour1);
//获取分
int minute = localTime.getMinute();
int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
System.out.println(minute + "," + minute1);
//获取秒
int second = localTime.getMinute();
int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);
System.out.println(second + "," + second1);
运行结果:
3、LocalDateTime 获取年月日时分秒,相当于 LocalDate + LocalTime
// 创建 LocalDateTime
LocalDateTime ldt = LocalDateTime.now();
LocalDate ld = LocalDate.now();
LocalTime lt = LocalTime.now();
LocalDateTime ldt1 = LocalDateTime.of(2019, Month.OCTOBER, 10, 14, 46, 56);
LocalDateTime ldt2 = LocalDateTime.of(ld, lt);
LocalDateTime ldt3 = ld.atTime(lt);
LocalDateTime ldt4 = lt.atDate(ld);
// 获取LocalDate
LocalDate localDate2 = ldt.toLocalDate();
// 获取LocalTime
LocalTime localTime2 = ldt.toLocalTime();
System.out.println(ldt);
System.out.println(ldt1);
System.out.println(ldt2);
System.out.println(ldt3);
System.out.println(ldt4);
System.out.println(localDate2);
System.out.println(localTime2);
运行结果:
4、Instant 获取秒数,用于表示一个时间戳(精确到纳秒)
如果只是为了获取秒数或者毫秒数,可以使用System.currentTimeMillis()。
// 创建Instant对象。默认使用UTC时区
Instant instant = Instant.now();
System.out.println(instant);
// 获取秒数
long currentSecond = instant.getEpochSecond();
System.out.println(currentSecond);
// 获取毫秒数
long currentMilli = instant.toEpochMilli();
System.out.println(currentMilli);
运行结果:
5、Duration : 用于计算两个“时间”间隔 ,Period : 用于计算两个“日期”间隔
Instant ins1 = Instant.now();
System.out.println("--------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant ins2 = Instant.now();
System.out.println("所耗费时间为:" + Duration.between(ins1, ins2).toMillis());
System.out.println("----------------------------------");
LocalDate ld1 = LocalDate.now();
LocalDate ld2 = LocalDate.of(2011, 1, 1);
Period pe = Period.between(ld2, ld1);
System.out.println(pe.getYears());
System.out.println(pe.getMonths());
System.out.println(pe.getDays());
运行结果:
修改 LocalDate、LocalTime、LocalDateTime、Instant。
LocalDate、LocalTime、LocalDateTime、Instant 为不可变对象,修改这些对象对象会返回一个副本。
增加、减少年数、月数、天数等,以LocalDateTime为例:
LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 12, 14, 32, 0);
// 增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
// 减少一个月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);
// 通过with修改某些值
// 修改年为2020
localDateTime = localDateTime.withYear(2020);
localDateTime = localDateTime.with(ChronoField.YEAR, 2020);
// 时间计算
// 获取该年的第一天
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.with(firstDayOfYear());
6、DateTimeFormatter : 解析和格式化日期或时间
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);
System.out.println(strDate);
运行结果:
7、ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期
LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(ldt);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
System.out.println(zdt);
运行结果:
8、TemporalAdjuster : 时间校正器
public void test() {
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
LocalDateTime ldt2 = ldt.withDayOfMonth(10);
System.out.println(ldt2);
LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(ldt3);
//自定义:下一个工作日
LocalDateTime ldt5 = ldt.with((l) -> {
LocalDateTime ldt4 = (LocalDateTime) l;
DayOfWeek dow = ldt4.getDayOfWeek();
if (dow.equals(DayOfWeek.FRIDAY)) {
return ldt4.plusDays(3);
} else if (dow.equals(DayOfWeek.SATURDAY)) {
return ldt4.plusDays(2);
} else {
return ldt4.plusDays(1);
}
});
System.out.println(ldt5);
}
运行结果:
TemporalAdjusters 包含许多静态方法,可以直接调用,以下列举一些:
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 返回同一个月中每周的第几天 |
firstDayOfMonth | 返回当月的第一天 |
firstDayOfNextMonth | 返回下月的第一天 |
firstDayOfNextYear | 返回下一年的第一天 |
firstDayOfYear | 返回本年的第一天 |
firstInMonth | 返回同一个月中第一个星期几 |
lastDayOfMonth | 返回当月的最后一天 |
lastDayOfNextMonth | 返回下月的最后一天 |
lastDayOfNextYear | 返回下一年的最后一天 |
lastDayOfYear | 返回本年的最后一天 |
lastInMonth | 返回同一个月中最后一个星期几 |
next / previous | 返回后一个/前一个给定的星期几 |
nextOrSame / previousOrSame | 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回 |