# java时间处理

返回:java8 | 返回:专题

🐉 获取一些常见日期和时间 🐉 常见又易错点
public static String getDatePoor(Date endDate, Date nowDate) {

    long nd = 1000 - 24 - 60 - 60;
    long nh = 1000 - 60 - 60;
    long nm = 1000 - 60;
    // long ns = 1000;
    // 获得两个时间的毫秒时间差异
    long diff = endDate.getTime() - nowDate.getTime();
    // 计算差多少天
    long day = diff / nd;
    // 计算差多少小时
    long hour = diff % nd / nh;
    // 计算差多少分钟
    long min = diff % nd % nh / nm;
    // 计算差多少秒//输出结果
    // long sec = diff % nd % nh % nm / ns;
    return day + "天" + hour + "小时" + min + "分钟";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# java8时间

package com.chlm.mysession.common;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

/**
 - java8时间测试
 - <p>Instant 是用来表示时刻的,类似 Unix 的时间,表示从协调世界时1970年1月1日0时0分0秒起至现在的总秒数,也可以获取毫秒。</p>
 - <p>Date 最多可以表示毫秒级别的时刻,而 Instant 可以表示纳秒级别的时刻。</p>
 - <p>LocalDate 表示一个日期,只有年月日,没有时分秒</p>
 - <p>LocalTime</p>
 - <p>LocalDateTime 就是年月日时分秒了。</p>
 - @author huting
 */
@Slf4j
public class TimeTest {
    /**
     - 线程安全,所以可以static
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER_DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    /**
     - 12小时
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    /**
     - 24小时
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER_24 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     - 12小时制,带am/pm
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER_AP = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a");

//    private static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");

    /**
     - 通过ThreadLocal实现SimpleDateFormat的线程安全性
     */
    private static final ThreadLocal<SimpleDateFormat> FORMAT_THREAD_LOCAL =
            new InheritableThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    @Test
    public void testInstant(){
        Instant instant = Instant.now();
        //输出当前时刻距离 1970年1月1日0时0分0秒 的秒和毫秒
        log.info("现在的秒数1:[{}]",instant.getEpochSecond());
        log.info("现在的毫秒数1:[{}]",instant.toEpochMilli());

        LocalDateTime today = LocalDateTime.now();

        //Instant 和 LocalDateTime(默认系统时区) 的互相转换,注意时区问题
        Instant instantFromLocalDateTime = today.atZone(ZoneId.systemDefault()).toInstant();
        log.info("现在的秒数2:[{}]",instantFromLocalDateTime.getEpochSecond());
        log.info("现在的毫秒数2:[{}]",instantFromLocalDateTime.toEpochMilli());

        LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant,ZoneId.systemDefault());
        log.info("当前时间为2:[{}],时区为:[{}]",timeFromInstant.format(DATE_TIME_FORMATTER)
                ,ZoneId.systemDefault().toString());

        Instant instantOfMillisecond = Instant.ofEpochSecond(1000);
        log.info("时间:[{}]",instantOfMillisecond);
    }

    @Test
    public void testLocalDateTime(){
        LocalDateTime today = LocalDateTime.now();
        log.info("当前时间为1:[{}]",today.format(DATE_TIME_FORMATTER));

        LocalDateTime tomorrow = today.plusDays(1);
        log.info("明天此时时间为:[{}]",tomorrow.format(DATE_TIME_FORMATTER));

        LocalDateTime minusHours = tomorrow.minusHours(24);
        log.info("明天此时时间减去4小时后为:[{}]",minusHours.format(DATE_TIME_FORMATTER));

        log.info("tomorrow比today大,是真的吗?[{}]",tomorrow.isAfter(today));
        log.info("minusHours与today一样,是真的吗?[{}]",minusHours.isEqual(today));
    }

    /**
     - LocalDate 会根据系统中当前时刻和默认时区计算出年月日的信息
     */
    @Test
    public void testLocalDate(){
        LocalDate localDate = LocalDate.now();
        log.info("当前日期:[{}]",localDate);

        localDate = LocalDate.of(2022,11,3);
        log.info("修改后的日期:[{}]",localDate);

        //根据天数,推算月、日
        localDate = LocalDate.ofYearDay(2016,200);
        log.info("修改后的日期:[{}]",localDate);

        localDate = LocalDate.ofEpochDay(333);
        log.info("修改后的日期:[{}]",localDate);

        log.info("当前月份——[{}],星期——[{}]",localDate.getMonth().name()
                ,localDate.getDayOfWeek().name());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

# localdate与String互转

back | 返回:超级转换之时间日期

默认情况下,java日期是ISO8601格式,因此如果您有任何表示ISO8601格式的日期的字符串,那么您可以直接使用LocalDate.parse()或LocalDateTime.parse()方法

String armisticeDate = "2016-04-04";
LocalDate aLD = LocalDate.parse(armisticeDate);
System.out.println("Date: " + aLD);

String armisticeDateTime = "2016-04-04T11:50";
LocalDateTime aLDT = LocalDateTime.parse(armisticeDateTime);
System.out.println("Date/Time: " + aLDT);
1
2
3
4
5
6
7
    /**
     - localdate与String互转
     */
    @Test
    public void testLocalDate2String(){
        LocalDate localDate = LocalDate.now();
        log.info("LocalDate转成String:[{}]",localDate.format(DATE_TIME_FORMATTER_DAY));
        LocalDateTime localDateTime = LocalDateTime.now();
        log.info("LocalDate转成String:[{}]",localDateTime.format(DATE_TIME_FORMATTER));
        log.info("LocalDate转成String:[{}]",localDateTime.format(DATE_TIME_FORMATTER_24));
        log.info("LocalDate转成String(预定义):[{}]",localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        log.info("LocalDate转成String:[{}]",localDateTime.format(DATE_TIME_FORMATTER_AP));
        log.info(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))));
    }

    @Test
    public void testString2LocalDate(){
        String timeStr = "2019-06-28 13:13:13";
        LocalDate date = LocalDate.parse(timeStr,DATE_TIME_FORMATTER_24);
        LocalDateTime time = LocalDateTime.parse(timeStr,DATE_TIME_FORMATTER_24);
        log.info("String转LocalDate:[{}]",date);
        log.info("String转LocalDateTime:[{}]",time);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# sqlTime转LocalDateTime

back

    //import java.sql.Timestamp;转LocalDateTime
    @Test
    public void sqlTimeStamp2Local(){
        Timestamp timestamp = Timestamp.valueOf(LocalDateTime.now());
        log.info("TimeStamp为:[{}]",timestamp);
        LocalDateTime now = timestamp.toLocalDateTime();
        log.info("LocalDateTime为:[{}]",now);
    }
}
}
1
2
3
4
5
6
7
8
9
10

# 获取所在季度开始与结束时间

/**
* 获取时间所在季度的开始或者结束时间
*
* @param localDateTime 时间,若为空,则直接获取当前时间
* @param isStart       true:开始时间,false:结束时间
* @return 时间
*/
public static LocalDateTime getStartOrEndDayOfQuarter(LocalDateTime localDateTime, boolean isStart) {
    LocalDateTime resultTime = LocalDateTime.now();
    localDateTime = localDateTime == null ? resultTime : localDateTime;
    Month month = localDateTime.getMonth();
    Month firstMonth = month.firstMonthOfQuarter();
    Month endMonth = Month.of(firstMonth.getValue() + 2);
    return isStart
            ? LocalDateTime.of(
                    LocalDate.of(localDateTime.getYear(), firstMonth, 1),
                    LocalTime.MIN)
            : LocalDateTime.of(
                    LocalDate.of(localDateTime.getYear(), endMonth, endMonth.length(localDateTime.toLocalDate().isLeapYear())),
                    LocalTime.MAX);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 获取所在周的开始与结束日期

/**
    * 获取传入时间或者当前时间所在周的周一或者周天
    * @param localDateTime 时间
    * @param isStart true:周一,false:周天
    * @return 时间
    */
public static LocalDateTime getStartOrEndOfWeek(LocalDateTime localDateTime, boolean isStart) {
    LocalDateTime resultTime = LocalDateTime.now();
    localDateTime = localDateTime == null ? resultTime : localDateTime;
    return localDateTime.with(temporal -> {
        int calNow = temporal.get(ChronoField.DAY_OF_WEEK);
        return isStart
                ? temporal.plus(1 - calNow, ChronoUnit.DAYS)
                : temporal.plus(7 - calNow, ChronoUnit.DAYS);
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 获取当天最小最大时间

back

LocalDateTime.now().with(LocalTime.MIN);
LocalDateTime.now().with(LocalTime.MAX);
1
2

# SimpleDateFormat非线程安全

back

SimpleDateFormat是非线程安全的,时间处理,基本所有项目上都是需要使用到的,往往很多初学者会把SimpleDateFormat定义为static类型,然后在进行时间转化的时候没有做加锁处理。如下:

public class Main {

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

    public static void main(String[] args) throws ParseException {
        System.out.println(SDFT.parse("2019-05-29 12:12:12"));
    }

}
1
2
3
4
5
6
7
8
9
@Test
    public void testSimpleDateFormat(){
        for (int i = 1; i < 31; i++) {
            int ii = i;
            new Thread(() -> {
                Date date = null;
                try {
                    String s = "2019-05-" + ii;
                    date = SDF.parse(s);
                    System.out.println("" + ii + ":" + date.getDate());
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
12:12
2:2
9:7
11:11
6:6
8:6
21:21
10:10
24:24
25:25
28:28
1
2
3
4
5
6
7
8
9
10
11

根据预期结果,两边应该相等,但是却出现了不相等的情况。原因:因为SimpleDateFormat定义为了共享的,所以其类里的属性calendar也是多个线程共享的,这就造成了线程安全问题。 解决方案:

  • 1.加锁处理
try {
    String s = "2019-05-" + ii;
    synchronized (TimeTest.class){
        date = SDF.parse(s);
    }
    System.out.println("" + ii + ":" + date.getDate());
} catch (ParseException e) {
    e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
  • 2.每次都创建SimpleDateFormat实例
  • 3.使用LocalThread
private static final ThreadLocal<SimpleDateFormat> FORMAT_THREAD_LOCAL =
            new InheritableThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

@Test
    public void testSimpleDateFormat(){
        for (int i = 1; i < 31; i++) {
            int ii = i;
            new Thread(() -> {
                Date date = null;
                try {
                    String s = "2019-05-" + ii;
//                    synchronized (TimeTest.class){
//                    date = SDF.parse(s);
//                    }
                    date = FORMAT_THREAD_LOCAL.get().parse(s);
                    System.out.println("" + ii + ":" + date.getDate());
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • 4.使用JDK1.8提供的DateTimeFormatter来处理时间
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Test
    public void testSimpleDateFormat(){
        for (int i = 1; i < 31; i++) {
            int ii = i;
            new Thread(() -> {
                LocalDate date = null;
                try {
                    String s = ii<10?("2019-05-0" + ii):("2019-05-" + ii);
//                    synchronized (TimeTest.class){
//                    date = SDF.parse(s);
//                    }
                    date = LocalDate.parse(s,DATE_TIME_FORMATTER);
                    System.out.println("" + ii + ":" + date.getDayOfMonth());
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# java8时间_new

back

新API基于ISO标准日历系统,java.time包下的所有类都是不可变类型而且线程安全。

编号 类的名称 描述
1 Instant 时间戳
2 Duration 持续时间,时间差
3 LocalDate 只包含日期,比如:2018-02-05
4 LocalTime 只包含时间,比如:23:12:10
5 LocalDateTime 包含日期和时间,比如:2018-02-05 23:14:21
6 Period 时间段
7 ZoneOffset 时区偏移量,比如:+8:00
8 ZonedDateTime 带时区的时间
9 Clock 时钟,比如获取目前美国纽约的时间
10 java.time.format.DateTimeFormatter 时间格式化

# Clock

Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数

@Test
    public void testClock(){
        Clock clock = Clock.systemUTC();
        log.info("毫秒:【{}】,",clock.millis());
        Clock defaultClock = Clock.systemDefaultZone();
        log.info("毫秒:【{}】,",defaultClock.millis());
    }
1
2
3
4
5
6
7

# 处理时区ZoneDateTime

# 检查闰年

back

# 两个日期之间的天数和月数

LocalDateTime cu = LocalDateTime.now();
LocalDateTime d1 = cu.minusDays(2L).with(LocalTime.MIN);
System.out.println(cu);
System.out.println(d1);
LocalDateTime d2 = cu.with(LocalTime.MIN);
System.out.println(cu);
System.out.println(d2);
// Duration主要获取时分秒的差
System.out.println(Duration.between(d1, d2).toDays());
// Period获取天、月、年、周的差
Period.between(LocalDate.of(2021,11,1), LocalDate.of(2023,1,1)).getMonths(); // 2
// ChronoUnit都可以
ChronoUnit.MONTHS.between(LocalDate.of(2021,11,1), LocalDate.of(2023,1,1)); // 14
System.out.println(ChronoUnit.DAYS.between(d1, d2));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 各种时间、字符串等之间的互相转换

back

  • LocalDate转Date
LocalDate nowLocalDate = LocalDate.now();
Date date = Date.from(localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant());
1
2
  • LocalDateTime转Date
LocalDateTime localDateTime = LocalDateTime.now();
Date date = Date.from(localDateTime.atZone(ZoneOffset.ofHours(8)).toInstant());
1
2
  • Date转LocalDateTime(LocalDate)
Date date = new Date();
LocalDateTime localDateTime = date.toInstant().atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
LocalDate localDate = date.toInstant().atZone(ZoneOffset.ofHours(8)).toLocalDate();
1
2
3
  • LocalDate转时间戳
LocalDate localDate = LocalDate.now();
long timestamp = localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
1
2
  • LocalDateTime转时间戳
LocalDateTime localDateTime = LocalDateTime.now();
long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
1
2
  • 时间戳转LocalDateTime(LocalDate)
long timestamp = System.currentTimeMillis();
LocalDate localDate = Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDate();
LocalDateTime localDateTime = Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
1
2
3
  • LocalDateTime与TimeStamp互转
LocalDateTime localDateTime = timeStamp.toLocalDateTime();

TimeStamp timeStamp = TimeStamp.valueOf(LocalDateTime.now());
1
2
3

# 日期相关工具类

back

# DateUtils DateFormatUtils

Date now = new Date();
// Date 加 1 天
Date addDays = DateUtils.addDays(now, 1);
// Date 加 33 分钟
Date addMinutes = DateUtils.addMinutes(now, 33);
// Date 减去 233 秒
Date addSeconds = DateUtils.addSeconds(now, -233);
// 判断是否 同一天
boolean sameDay = DateUtils.isSameDay(addDays, addMinutes);
// 过滤时分秒,若 now 为 2020-05-07 22:13:00 调用 truncate 方法以后
// 返回时间为 2020-05-07 00:00:00
Date truncate = DateUtils.truncate(now, Calendar.DATE);
1
2
3
4
5
6
7
8
9
10
11
12