Java时间类型详解


Java 中提供了一系列关于时间操作的类,同时在 JDK 8 中引入了新的包 java.time 提供新的日期操作类,以解决旧版本中线程安全与时区等问题所带来的麻烦之处。

下面即针对上述中的时间类进行展示介绍

一、Date类

JDK 8 之前包含两类 Date 类型,即 java.util.Datejava.sql.Date ,下面将分别进行介绍。

1. 初始化

(1) java.util.Date

java.util.Date 类提供包含空构造器,允许通过 new Date() 获取当前系统时间。

public void demo1(){
    java.util.Date date = new java.util.Date();
    System.out.println("java.util.Date: " + date);
}
(2) java.sql.Date

java.sql.Date 继承于 java.util.Date ,不包含空构造器,无法通过 new Date() 创建对象,在通过构造函数创建时必须传值。

同时 java.sql.date 的时间格式是 yyyy-MM-dd ,没有时间精度,强制获取时分秒则会抛异常。

public void demo2() {
    java.sql.Date date = new java.sql.Date(System.currentTimeMillis());
    System.out.println("java.util.Date: " + date);
}

2. 系统时间

在使用 java.sql.Date 创建时间对象时需要传入时间戳值, JDK 提供两类常用的系统时间获取: currentTimeMillis()nanoTime() ,二者都是获取当前系统时间,区别仅在时间精度。

public void timestampDemo() {
    // 返回系统时间,精确度为毫秒
    long millTime = System.currentTimeMillis();
    System.out.println("millTime:" + millTime);

    // 返回系统时间,精确度为毫微秒
    long nanoTime = System.nanoTime();
    System.out.println("nanoTime:" + nanoTime);
}

3. 格式化

若需要对时间进行格式化 JDK 提供了 SimpleDateFormatDateFormat 两种方式,根据自身喜好选择。

(1) DateFormat

DateFormat 根据不同的时间类型提供多种格式化方案,示例如下:

public void dateFormat() {
    Date date = new Date();
    // SHORT 风格的日期时间格式化: 22-10-10 下午3:12
    DateFormat df1 = DateFormat.getInstance();
    System.out.println(df1.format(date));

    // 日期格式化,使用默认语言环境和默认风格: 2022-10-10
    DateFormat df2 = DateFormat.getDateInstance();
    System.out.println(df2.format(date));

    // 日期时间格式化,使用默认语言环境和默认风格: 2022-10-10 15:12:04
    DateFormat df3 = DateFormat.getDateTimeInstance();
    System.out.println(df3.format(date));
}
(2) SimpleDateFormat

SimpleDateFormat 则提供自定义的时间格式化模板,通入传入时间格式串实现时间格式化。

需要注意 SimpleDateFormat 为线程不安全类,切勿在多线程情景下使用,其使用示例如下:

public void dateFormat() {
    Date date = new Date();
    // 格式化日期
    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
    String dateStr1 = sdf1.format(date);
    System.out.println("SimpleDateFormat 1:" + dateStr1);

    // 格式化日期和时间
    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
    String dateStr2 = sdf2.format(date);
    System.out.println("SimpleDateFormat 2:" + dateStr2);
}

4. 时间比较

当需要比较两个时间节点先后顺序可以通过自带的 after()before() 方法判断,同时也可以转为时间戳进而比较先后顺序。

public void compareDemo() throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date1 = sdf.parse("2022-09-13 10:30:00");
    Date date2 = sdf.parse("2022-09-13 10:00:00");
    System.out.println("date1 is after date2: " + date1.after(date2));
    System.out.println("date1 is before date2: " + date1.before(date2));

    int i = date1.compareTo(date2);
    String result = i < 0 ? "before" : (i == 0 ? "same" : "after");
    System.out.println("date1 compare date2: " + result);

    long interval = date1.getTime() - date2.getTime();
    System.out.println("(date1~date2) interval: " + interval / 1000 + "s");
    System.out.println("(date1~date2) interval: " + interval / (60 * 1000) + "m");
    System.out.println("(date1~date2) interval: " + interval / (60 * 60 * 1000) + "h");
}

二、时间戳

Java 针对时间戳共提供了两种初始化方式,下面分别进行介绍。

1. from()

通过 from() 可以将指定时间实例转为时间戳。

public void TimestampDemo(){
    Timestamp timestamp_2 = Timestamp.from(new Date().toInstant());
    System.out.println(timestamp_2);
}

2. valueOf()

通过 valueOf() 可以将指定时间字符串转化为时间戳格式。

public void TimestampDemo(){
    Timestamp timestamp_1 = Timestamp.valueOf("1970-01-01 00:00:001");
    System.out.println(timestamp_1);
}

三、日历类

Calendar 顾名思义日历,通过该类可以对时间进行更进一步的操作。

1. 初始化

Calendar 初始化十分简单,通过 getInstance() 方法即可,若在初始化后需要更改值则通过 get()set() 方法即可。

public void init() {
    // 获取当前时间实例
    Calendar calendar = Calendar.getInstance();
    System.out.println("Calendar: " + calendar);

    // 更新日期: set(filed, value)  get(filed)
    calendar.set(Calendar.YEAR, 1998);
    int year = calendar.get(Calendar.YEAR);
    System.out.println("Year: " + year);
}

2. 转化计算

(1) 时间转化

通过 add(Fiedd, n) 方法可以之前或之后的时间点。

public void demo() {
    Calendar calendar = Calendar.getInstance();
    // 当前时间后移
    calendar.add(Calendar.DATE, +1);
    // 当前时间前推
    calendar.add(Calendar.DATE, -1);

    // 将日期转为可读的 Date 类
    Date date = calendar.getTime();
    System.out.println(date);
}
(2) 时间计算

当然 Calendar 也可以与常见的 Date 类相互转化,通过 setTime()getTime() 即可。

public void convert() {
    Calendar calendar = Calendar.getInstance();
    // Date 类转 Calendar
    calendar.setTime(new Date());
    System.out.println("Set time: " + calendar);

    // Calendar 类转 Date
    Date date = calendar.getTime();
    System.out.println(date);
}

3. 工具类

(1) 第n天时间

根据传入的整数获取第前 n 天的日期。

public Date getNBefore(int n) {
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.DAY_OF_YEAR, calendar.get(Calendar.DAY_OF_YEAR) - n);
    return calendar.getTime();
}
(2) 星期始末

在开发中常需要获取指定日期的星期起始时间,即周一与周日的日期,通过 Calendar 即可轻松实现,下面简单介绍一下实现思路。

首先通过 get(Calendar.DAY_OF_WEEK) 获取传入的时间与周一和周日的偏移量,即分别距离二者多少天,通过得到的差值利用 add() 方法即可获取当周的起始时间,然后在根据是要获取上周起始还是下周起始将时间再次加减七天即可。

public String[] getWeekDate(String type, Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
    if (dayOfWeek <= 0) {
        dayOfWeek = 7;
    }
    
    // 获取当前与周一周日的时间偏移量
    int offset1 = 1 - dayOfWeek;
    int offset2 = 7 - dayOfWeek;
    String[] dateArr = new String[2];
    switch (type) {
        // 上周日期
        case "LAST":
            // 上周一: 先减到周一在减七天
            // 上周日: 先加到周末在减七天
            calendar.add(Calendar.DATE, offset1 - 7);
            dateArr[0] = sdf.format(calendar.getTime());
            calendar.add(Calendar.DATE, 7 + offset2 - 7);
            dateArr[1] = sdf.format(calendar.getTime());
            break;
        // 本周日期
        case "THIS":
            calendar.add(Calendar.DATE, offset1);
            dateArr[0] = sdf.format(calendar.getTime());
            calendar.add(Calendar.DATE, offset2);
            dateArr[1] = sdf.format(calendar.getTime());
            break;
        // 下周日期
        case "NEXT":
            // 下周一: 先减到周一再加七天
            // 下周日: 先加到周末在加七天
            calendar.add(Calendar.DATE, offset1 + 7);
            dateArr[0] = sdf.format(calendar.getTime());
            calendar.add(Calendar.DATE, -7 + offset2 + 7);
            dateArr[1] = sdf.format(calendar.getTime());
            break;
    }
    return dateArr;
}
(3) 月份始末

月份的起始时间与星期的获取方式类型,不再详细介绍。

private String[] getMonthDate(String type, Date nowDays) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(nowDays);
    if (type.equals("last")) {
        calendar.add(Calendar.MONTH, -1);
    } else {
        calendar.add(Calendar.MONTH, +1);
    }

    // 获取日期最小与最大值
    String[] dateArr = new String[2];
    int firstDay = calendar.getActualMinimum(Calendar.DAY_OF_MONTH);
    int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    calendar.set(Calendar.DAY_OF_MONTH, firstDay);
    dateArr[0] = sdf.format(calendar.getTime());
    calendar.set(Calendar.DAY_OF_MONTH, lastDay);
    dateArr[1] = sdf.format(calendar.getTime());
    return dateArr;
}

四、LocalDate

对于一个开发者而言, java.util.Datejava.sql.Date 两类重名显然是很糟糕的设计,因此在 JDK8 后引入了全新的日期类 LocalDate 提供更简易的时间日期操作。

1. 初始化

在新版本 java.time 包中一共引入了三类全新时间格式: LocalDateLocalTimeLocalDateTime 依次对应精度为: yyyy-MM-ddhh:mm:ss.SSSyyyy-MM-dd hh:mm:ss.SSS,从而解决了旧版中两类 Date 类型名称相同的尴尬情景。

三类时间类型都提供 now()of() 两种初始化方式,分别代表获取当前时间和根据输入转化时间,相应的初始化示例如下:

public void demo1() {
    // yyyy-MM-dd
    LocalDate localDate = LocalDate.now();
    System.out.println("LocalDate: " + localDate);
    LocalDate localDate1 = LocalDate.of(2022, 9, 6);
    System.out.println("LocalDate1: " + localDate1);

    // hh:mm:ss.SSS
    LocalTime localTime = LocalTime.now();
    System.out.println("\nlocalTime: " + localTime);
    LocalTime localTime1 = LocalTime.of(23, 11, 6);
    System.out.println("localTime1: " + localTime1);

    // yyyy-MM-dd hh:mm:ss.SSS
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println("\nLocalDateTime: " + localDateTime);

    // yyyy-MM-dd hh:mm:ss.SSS region
    ZonedDateTime zonedDateTime = ZonedDateTime.now();
    System.out.println("\nWhole info: " + zonedDateTime);
    // time region
    System.out.println("Time zone: " + zonedDateTime.getZone());
}

2. 格式化

针对全新的时间类型同样提供了新的时间格式化器 DateTimeFormatter ,与 SimpleDateFormat 使用类似其通过 ofPattern() 构造方式指定时间格式。

DateTimeFormatter 同样提供 pase()format() 两种格式方式,分别将字符串转为时间类型与将时间类型格式化为字符串类型,示例如下:

public void demo3() {
    LocalDate localDate = LocalDate.now();
    // 定义格式化构造器,类似 SimpleDateFormat
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    // format():将 LocalDate 类型转字符串
    String date1 = formatter.format(localDate);
    System.out.println("Formatter time(format): " + date1);

    // parse():将 字符串转 LocalDate 类型
    LocalDate date2 = LocalDate.parse(localDate.toString(), formatter);
    System.out.println("Formatter time(parse): " + date2);
}

3. 类型转化

LocalDate 等时间类型也可与传统的 Date 类型相互转化,下面分别进行介绍。

注意下述示例中的 Date 类型完整包名为 java.util ,若选择 java.sql 下的 Date 其转化同通过 valueOf() 方法即可。

(1) Date <-> Time

java.util.Date 转化为 java.time.LocalDate 等类型。

public void convert() {
    Date date = new Date();
    LocalDate localDate = date.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalDate();
    System.out.println("localDate: " + localDate);

    LocalTime localTime = date.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalTime();
    System.out.println("localTime: " + localTime);
    
    LocalDateTime localDateTime = date.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalDateTime();
    System.out.println("localDateTime: " + localDateTime);
}
(2) Time <-> Date

java.time.LocalDate 等类型转化为 java.util.Date 类型。

public void convert1() {
    LocalDate localDate = LocalDate.now();
    Instant instant1 = localDate.atStartOfDay()
            .atZone(ZoneId.systemDefault())
            .toInstant();
    Date date1 = Date.from(instant1);
    System.out.println("date1: " + date1);

    LocalDateTime localDateTime = LocalDateTime.now();
    Instant instant3 = localDateTime.atZone(ZoneId.systemDefault())
            .toInstant();
    Date date3 = Date.from(instant3);
    System.out.println("date3: " + date3);
}

4. 时间计算

LocalDate 等系列时间类型除了涵盖了基础的 Date 功能,同时也能达到 Calendar 的部分效果。

(1) 单位获取

通过 getYear() 等方法获取指定精度单位值。

public void demo1() {
    LocalDate localDate = LocalDate.now();
    int year = localDate.getYear();
    int month = localDate.getMonth().getValue();
    int day = localDate.getDayOfMonth();
    System.out.println("Year: " + year);
    System.out.println("Month: " + month);
    System.out.println("Day: " + day);
}
(2) 指定时间

通过 withYear() 等方法修改指定精度单位值。

public void demo2() {
    // Change date
    LocalDate withYear = localDate.withYear(2021);
    LocalDate withMonth = localDate.withMonth(9);
    LocalDate withDay = localDate.withDayOfMonth(27);
    System.out.println("To 2021 Year: " + withYear);
    System.out.println("To Oct: " + withMonth);
    System.out.println("To 27: " + withDay);
}
(3) 时间前推

通过 minusYears() 等方法前推指定精度单位值。

public void demo3() {
    LocalDate beforeYear = localDate.minusYears(1);
    LocalDate beforeMonth = localDate.minusMonths(1);
    LocalDate beforeDay = localDate.minusDays(1);
    System.out.println("Before 1 Year: " + beforeYear);
    System.out.println("Before 1 Month: " + beforeMonth);
    System.out.println("Before 1 Day: " + beforeDay);
}
(4) 时间后移

通过 plusYears() 等方法后移指定精度单位值。

public void demo4() {
    LocalDate afterYear = localDate.plusYears(1);
    LocalDate afterMonth = localDate.plusMonths(1);
    LocalDate afterDay = localDate.plusDays(1);
    System.out.println("After 1 Year: " + afterYear);
    System.out.println("After 1 Month: " + afterMonth);
    System.out.println("After 1 Day: " + afterDay);
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录