youyichannel

志于道,据于德,依于仁,游于艺!

0%

Java中常用的时间类

java.util.Date

Date类从JDK1.0开始就已经提供了,封装了当前的日期和时间。月份和小时从0开始,月份的天数从1开始,年份从1900开始。

但是,Date类不能实现国际化,偏移量也不统一。

⚠️注意:该类中大部分方法都已经过时,并且是线程不安全的。

常用的方法:

  1. 构造方法java.util.Date#Date()java.util.Date#Date(long)
  2. java.util.Date#getTime,返回自1970起已经过的毫秒数
Date a = new Date(); // 当前时间
System.out.println(a); // Thu Jan 25 18:54:18 CST 2024

Date b = new Date(System.currentTimeMillis());
System.out.println(b); // Thu Jan 25 18:54:18 CST 2024

System.out.println(b.getTime()); // 1706180058499

java.time.Instant

替代 Date 类

Date 时间精确到 ms,Instant 时间精确到 ns。

LocalDateTime localDateTime = LocalDateTime.now(); // 2024-01-25T19:01:50.944
System.out.println(localDateTime);
ZoneId zoneId = ZoneId.systemDefault(); // 获取系统默认时区 Asia/Shanghai
System.out.println(zoneId);
ZonedDateTime zdt = localDateTime.atZone(zoneId); // 带有默认时区的时间 2024-01-25T19:01:50.944+08:00[Asia/Shanghai]
System.out.println(zdt);
System.out.println(zdt.toInstant()); // 2024-01-25T11:01:50.944Z 格林威治时间,与北京时间相差8小时
Date date = Date.from(zdt.toInstant());
System.out.println(date); // Thu Jan 25 19:01:50 CST 2024 Date类格式
System.out.println("-----------");
System.out.println(Instant.now()); // 2024-01-25T11:01:50.955Z 格林威治时间,与北京时间相差8小时
System.out.println(Instant.now().atZone(ZoneId.systemDefault())); // 2024-01-25T19:01:50.955+08:00[Asia/Shanghai]

java.util.Calendar

不推荐使用

// 当前时间
Calendar calendar = Calendar.getInstance(); // 获取对象实例
System.out.println(calendar);
int days = calendar.get(Calendar.DAY_OF_YEAR); // 25
System.out.println(days);
int weeks = calendar.get(Calendar.WEEK_OF_YEAR); // 4
System.out.println(weeks);
int day = calendar.get(Calendar.DAY_OF_WEEK) - 1; // day = 1 即周一,减一的原因是因为国外是周天-周一-周二-周三-周四-周五-周六
System.out.println(day); // 4

java.text.SimpleDateFormat

在使用Date对象时,我们会使用 SimpleDateFormat 进行格式化显示。用于格式化输出日期,线程不安全,所以需要在每个方法内部都定义一个局部变量。

SimpleDateFormat#format方法,底层会调用 calendar.setTime(date)

// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
// ...
}

该类在多线程时,会存在线程不安全问题。因为 SimpleDateFormat 被多个线程调用 format() 方法时,t1 线程进入方法,执行了calendar.setTime(date);,设置成功,此时 t2 线程也执行该方法,也执行 calendar.setTime(date);,这就会导致线程在切换时,t1 线程设置好的时间被修改了,导致 t1 返回的时间格式是错误的。

解决方案:

  1. 可以使用 ThreadLocal 使得线程和SimpleDateFormat实例对象绑定,解决线程不安全的问题;
  2. 使用 LocalDateTime 类替代 Date
// 1. Date 转 String
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
System.out.println(time); // 2024-01-25 19:30:18

// 2. String 转 Date
try {
String dateStr = "2024-01-25 20:00:00";
Date dateParse = sdf.parse(dateStr);
System.out.println(dateParse); // Thu Jan 25 20:00:00 CST 2024
} catch (ParseException e) {
e.printStackTrace();
}

// 3. 线程安全
class DateUtil {

private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

// ...
}

java.time.format.DateTimeFormatter

在使用 LocalDateTime 或者 ZonedLocalDateTime 时,需要进行格式化显示就需要使用到 DateTimeFormatter,用于替代SimpleDateFormat

SimpleDateFormat不同的是,DateTimeFormatter既是不变对象,还是线程安全的(因为变量都是使用final修饰的)。

// 格式化输出时间
DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt); // 2024-01-25T19:52:50.418
System.out.println(dft.format(ldt)); // 2024-01-25 19:52:50

// 日期转为字符串
String dateStr = "2024/01/25 20时00分01秒"; // 任意格式的日期时间字符串
DateTimeFormatter dft2 = DateTimeFormatter.ofPattern(
"yyyy/MM/dd HH时mm分ss秒"); // 根据需要解析的日期、时间字符串定义解析所用的格式器
LocalDateTime ldt2 = LocalDateTime.parse(dateStr, dft2); // 执行解析
System.out.println(ldt2); // 2024-01-25T20:00:01

java.time.LocalDate / LocalTime / LocalDateTime

代替 Date,线程安全

LocalDateTime localDateTime = LocalDateTime.now(); // 获取当前的年月日时分秒
System.out.println(localDateTime); // 2024-01-25T20:00:27.317
System.out.println(localDateTime.getYear()); // 2024

System.out.println(localDateTime.getMonth()); // JANUARY
System.out.println(localDateTime.getMonthValue()); // 1
System.out.println(localDateTime.getDayOfMonth()); // 25
System.out.println(localDateTime.getHour()); // 20
System.out.println(localDateTime.getMinute()); // 0
System.out.println(localDateTime.getSecond()); // 27

LocalDateTime now = LocalDateTime.now();
LocalDateTime nextTime = now
// 加上对应的时间
.plusYears(5)
.plusMonths(5)
.plusDays(5)
.plusHours(5)
.plusMinutes(5)
.plusSeconds(5);
System.out.println(nextTime); // 2029-07-01T01:05:32.318

// 减去对应的时间
LocalDateTime prevTime = now.minusYears(5);
System.out.println(prevTime); // 2019-01-25T20:00:27.318

两个时间作比较,第一个时间减去第二个时间,如果年份相同,比较月份,月份相同比较天数,以此类推。

计算时间差

LocalDateTime now = LocalDateTime.now();
LocalDateTime time = LocalDateTime.of(2024, 1, 1, 1, 1, 1);
Duration duration = Duration.between(time, now); // now - time
System.out.println(duration.toDays()); // 相差的天数 24
System.out.println(duration.toHours()); // 相差的小时数 595
System.out.println(duration.toMinutes()); // 相差的分钟数 35705
System.out.println(duration.toNanos()); // 相差的纳秒数 2142322577000000

实现:每周四的定时任务

通过 Executors.newScheduledThreadPool 实现

// 需求:每周四 18:00:00 执行任务
LocalDateTime now = LocalDateTime.now();
LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0)
.with(DayOfWeek.THURSDAY);

// 当前时间超过了本周四
if (now.isAfter(time)) {
time = time.plusWeeks(1); // 修改为下周四
}

Duration duration = Duration.between(now, time);
long initialDelay = duration.toMillis();
long period = 1000 * 60 * 60 * 24 * 7;
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.scheduleAtFixedRate(() -> {
System.out.println("executor task...");
}, initialDelay, period, TimeUnit.MICROSECONDS);

获取从 1970-01-01 00:00:00到现在的毫秒数

long currentTimeMillis = System.currentTimeMillis();
System.out.println(currentTimeMillis);
long currentTimeMills2 = Clock.systemDefaultZone().millis(); // JDK8 Clock 获取毫秒
System.out.println(currentTimeMills2);