Java - 时间类的进化历程

2020/09/12

本文系统地介绍下Java中的时间类,先来看下概况:

Java 1.0Date开始,介绍了它存在的问题,然后在Java 1.1中引入了Calendar

但是它并没有完全解决Date存在的问题,如月份仍从0开始计算,可变类导致的线程安全问题等。

接着,Joda-Time 应运而生,直至Java 8提供了java.time包,接下来让我们看下这个包内的主要类。

Date

我们先来看下Date主要存在哪些问题?

第一,年份从1900年开始,月份从0开始。

Date date1 = new Date(120, 5, 10);

// Wed Jun 10 00:00:00 CST 2020

第二,构造时间可能会存在自动调整。

Date date2 = new Date(120, 5, 31);
Date date3 = new Date(120, 6, 1);
System.out.println(date2);
System.out.println(date3);
System.out.println(date2.equals(date3));

// Wed Jul 01 00:00:00 CST 2020
// Wed Jul 01 00:00:00 CST 2020
// true

第三,由于Date类是可变的,所以存在线程安全问题。

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    es.submit(() -> {
        try {
            System.out.println(sdf.parse("15081947"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    });
}
es.shutdown();

// Thu Aug 16 00:00:00 CST 1509
// Thu Aug 16 00:00:00 CST 1509
// Thu Aug 16 00:00:00 CST 1509
// Sat Apr 16 00:00:00 CST 1137
// Sat Apr 16 00:00:00 CST 1137
// Thu Aug 16 00:00:00 CST 1509
// Thu Aug 16 00:00:00 CST 1509

同时,Date还有如下缺点:

  • 无法支持国际化。
  • 操作复杂。

Calendar

Calendar解决了Date月份从1900开始的问题,还是其他的问题并未解决。

来看下它的常见用法:

Calendar calendar = Calendar.getInstance();
calendar.set(2020, Calendar.SEPTEMBER, 12, 15, 56, 00);

int year        = calendar.get(Calendar.YEAR);
int month       = calendar.get(Calendar.MONTH);
int dayOfMonth  = calendar.get(Calendar.DAY_OF_MONTH);
int dayOfWeek   = calendar.get(Calendar.DAY_OF_WEEK);
int weekOfYear  = calendar.get(Calendar.WEEK_OF_YEAR);
int weekOfMonth = calendar.get(Calendar.WEEK_OF_MONTH);

int hour        = calendar.get(Calendar.HOUR);
int hourOfDay   = calendar.get(Calendar.HOUR_OF_DAY);
int minute      = calendar.get(Calendar.MINUTE);
int second      = calendar.get(Calendar.SECOND);
int millisecond = calendar.get(Calendar.MILLISECOND);

System.out.println("year        = " + year);
System.out.println("month       = " + month);
System.out.println("dayOfMonth  = " + dayOfMonth);
System.out.println("dayOfWeek   = " + dayOfWeek);
System.out.println("weekOfYear  = " + weekOfYear);
System.out.println("weekOfMonth = " + weekOfMonth);
System.out.println("hour        = " + hour);
System.out.println("hourOfDay   = " + hourOfDay);
System.out.println("minute      = " + minute);
System.out.println("second      = " + second);
System.out.println("millisecond = " + millisecond);

// year        = 2020
// month       = 8
// dayOfMonth  = 12
// dayOfWeek   = 7
// weekOfYear  = 37
// weekOfMonth = 2
// hour        = 3
// hourOfDay   = 15
// minute      = 56
// second      = 0
// millisecond = 385

Java8

我们可以使用LocalDateLocalTimeLocalDateTime处理本地时间,

也可以使用ZonedIdZonedDateTineZoneOffset处理不同时区的时间,

还可以方便地对时间进行各种操作、计算时间之间的间隔ChronoUnit、Period、Duration

TemporalAdjusters还提供一系列方法便于我们求年、月中的指定日期,

同时,我们还可以通过格式化和解析处理时间和字符串,

最后,Java8还保证了向后兼容性,通过Instant来实现DateCalendarJava8时间类之间的转换。

Local

下面通过构建实例、基本信息和常用方法三个维度来梳理下LocalDateLocalTimeLocalDateTime

LocalDate

public class LocalDateTest {

    public static void main(String[] args) {

        /** 1. How to create a LocalDate instance? */
        System.out.println("-----------------------------------------");
        LocalDate ld1 = LocalDate.now();
        System.out.println("ld1 = " + ld1);

        LocalDate ld2 = LocalDate.of(2020, 9, 12);
        System.out.println("ld2 = " + ld2);

        LocalDate ld3 = LocalDate.parse("2020-09-12");
        System.out.println("ld3 = " + ld3);
        System.out.println("-----------------------------------------");


        /** 2. Basic Information. */
        LocalDate date      = LocalDate.of(2020, 9, 11);
        int year            = date.getYear();
        Month month         = date.getMonth();
        int dayOfMonth      = date.getDayOfMonth();
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        int dayOfYear       = date.getDayOfYear();
        int len             = date.lengthOfMonth();
        boolean leapYear    = date.isLeapYear();
        System.out.println("year       = " + year);       // 2020
        System.out.println("month      = " + month);      // SEPTEMBER
        System.out.println("dayOfMonth = " + dayOfMonth); // 11
        System.out.println("dayOfWeek  = " + dayOfWeek);  // FRIDAY
        System.out.println("dayOfYear  = " + dayOfYear);  // 255
        System.out.println("len        = " + len);        // 30
        System.out.println("leapYear   = " + leapYear);   // true
        System.out.println("-----------------------------------------");


        /** 3. Utility Methods. */
        LocalDate ld = LocalDate.now();
        LocalDate tomorrow   = ld.plusDays(1);
        LocalDate yesterday  = ld.minusDays(1);
        LocalDate lastMonth1 = ld.minusMonths(1);
        LocalDate lastMonth2 = ld.minus(1, ChronoUnit.MONTHS);

        LocalDate ld4  = LocalDate.parse("2020-09-12");
        LocalDate ld5  = LocalDate.parse("2020-10-01");
        boolean before = ld4.isBefore(ld5);
        boolean after  = ld4.isAfter(ld5);

        System.out.println("tomorrow   = " + tomorrow);
        System.out.println("yesterday  = " + yesterday);
        System.out.println("lastMonth1 = " + lastMonth1);
        System.out.println("lastMonth2 = " + lastMonth2);
        System.out.println("before     = " + before);
        System.out.println("after      = " + after);
        System.out.println("-----------------------------------------");
    }
}

// -----------------------------------------
// ld1 = 2020-09-12
// ld2 = 2020-09-12
// ld3 = 2020-09-12
// -----------------------------------------
// year       = 2020
// month      = SEPTEMBER
// dayOfMonth = 11
// dayOfWeek  = FRIDAY
// dayOfYear  = 255
// len        = 30
// leapYear   = true
// -----------------------------------------
// tomorrow   = 2020-09-13
// yesterday  = 2020-09-11
// lastMonth1 = 2020-08-12
// lastMonth2 = 2020-08-12
// before     = true
// after      = false
// -----------------------------------------

LocalTime

public class LocalTimeTest {

    public static void main(String[] args) {

        /**
         * 1. How to create a LocalTime instance?
         */
        LocalTime lt1 = LocalTime.now();
        LocalTime lt2 = LocalTime.of(11, 22, 0);
        LocalTime lt3 = LocalTime.parse("11:22:00");
        System.out.println("-----------------------------------------");
        System.out.println("lt1    = " + lt1);
        System.out.println("lt2    = " + lt2);
        System.out.println("lt3    = " + lt3);

        DateTimeFormatter localTimeFormatter = DateTimeFormatter.ISO_LOCAL_TIME;
        String lt1Str = lt1.truncatedTo(ChronoUnit.SECONDS).format(localTimeFormatter);
        System.out.println("lt1Str = " + lt1Str);
        System.out.println("-----------------------------------------");

        /**
         * 2. Basic Information.
         */
        LocalTime now = LocalTime.now();
        int hour   = now.getHour();
        int minute = now.getMinute();
        int second = now.getSecond();
        int nano   = now.getNano();
        System.out.println("now    = " + now);
        System.out.println("hour   = " + hour);
        System.out.println("minute = " + minute);
        System.out.println("second = " + second);
        System.out.println("nano   = " + nano);
        System.out.println("-----------------------------------------");

        /**
         * 3. Utility Methods.
         */
        LocalTime lt = LocalTime.now();
        LocalTime addTwoHours = lt.plusHours(2);
        LocalTime minus30Mins = lt.minus(30, ChronoUnit.MINUTES);
        LocalTime withHour3   = lt.withHour(3);
        System.out.println("now         = " + lt);
        System.out.println("addTwoHours = " + addTwoHours);
        System.out.println("minus30Mins = " + minus30Mins);
        System.out.println("withHour3   = " + withHour3);
        System.out.println("-----------------------------------------");
    }
}

// -----------------------------------------
// lt1    = 18:32:42.948844200
// lt2    = 11:22
// lt3    = 11:22
// lt1Str = 18:32:42
// -----------------------------------------
// now    = 18:32:42.982458100
// hour   = 18
// minute = 32
// second = 42
// nano   = 982458100
// -----------------------------------------
// now         = 18:32:42.988980100
// addTwoHours = 20:32:42.988980100
// minus30Mins = 18:02:42.988980100
// withHour3   = 03:32:42.988980100
// -----------------------------------------

LocalDateTime

public class LocalDateTimeTest {

    public static void main(String[] args) {

        /**
         * 1. How to create a LocalDateTime instance.
         */
        LocalDateTime ldt1 = LocalDateTime.now();
        LocalDateTime ldt2 = LocalDateTime.of(2020, 9, 12, 14, 45, 20);
        LocalDateTime ldt3 = LocalDateTime.parse("2020-09-12T14:45:20");
        System.out.println("-----------------------------------------");
        System.out.println("ldt1 = " + ldt1);
        System.out.println("ldt2 = " + ldt2);
        System.out.println("ldt3 = " + ldt3);
        System.out.println("-----------------------------------------");

        /**
         * 2. Basic Information.
         */
        LocalDateTime datetime = LocalDateTime.now();
        int year            = datetime.getYear();
        Month month         = datetime.getMonth();
        int monthValue      = datetime.getMonthValue();
        int dayOfYear       = datetime.getDayOfYear();
        int dayOfMonth      = datetime.getDayOfMonth();
        DayOfWeek dayOfWeek = datetime.getDayOfWeek();
        int hour            = datetime.getHour();
        int minute          = datetime.getMinute();
        int second          = datetime.getSecond();
        int nano            = datetime.getNano();

        System.out.println("year       = " + year);
        System.out.println("month      = " + month);
        System.out.println("monthValue = " + monthValue);
        System.out.println("dayOfYear  = " + dayOfYear);
        System.out.println("dayOfMonth = " + dayOfMonth);
        System.out.println("dayOfWeek  = " + dayOfWeek);
        System.out.println("hour       = " + hour);
        System.out.println("minute     = " + minute);
        System.out.println("second     = " + second);
        System.out.println("nano       = " + nano);
        System.out.println("-----------------------------------------");

        /**
         * 3. Utility Methods.
         */
        LocalDateTime today = LocalDateTime.now();
        LocalDateTime tomorrow  = today.plusDays(1);
        LocalDateTime yesterday = today.minus(1, ChronoUnit.DAYS);
        LocalDateTime lastMonth = today.minusMonths(1);
        boolean before = today.isBefore(lastMonth);
        boolean after  = today.isAfter(lastMonth);

        System.out.println("today     = " + today);
        System.out.println("tomorrow  = " + tomorrow);
        System.out.println("yesterday = " + yesterday);
        System.out.println("lastMonth = " + lastMonth);
        System.out.println("before    = " + before);
        System.out.println("after     = " + after);
        System.out.println("-----------------------------------------");

    }
}

// -----------------------------------------
// ldt1 = 2020-09-12T18:33:44.990003600
// ldt2 = 2020-09-12T14:45:20
// ldt3 = 2020-09-12T14:45:20
// -----------------------------------------
// year       = 2020
// month      = SEPTEMBER
// monthValue = 9
// dayOfYear  = 256
// dayOfMonth = 12
// dayOfWeek  = SATURDAY
// hour       = 18
// minute     = 33
// second     = 45
// nano       = 12078800
// -----------------------------------------
// today     = 2020-09-12T18:33:45.019102500
// tomorrow  = 2020-09-13T18:33:45.019102500
// yesterday = 2020-09-11T18:33:45.019102500
// lastMonth = 2020-08-12T18:33:45.019102500
// before    = false
// after     = true
// -----------------------------------------

Zone

首先,我们来看下支持哪些时区?

Set<String> zonedIds = ZoneId.getAvailableZoneIds();
System.out.println("size() = " + zonedIds.size());
zonedIds.forEach(System.out::println);

// size = 599
// America/Los_Angeles
// Europe/London
// Europe/Paris
// Asia/Shanghai
// ...

再来看下如何创建一个ZonedDateTime实例?

ZonedDateTime shDt    = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime lsDT    = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
ZonedDateTime parisDT = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println("shDt    = " + shDt);
System.out.println("lsDT    = " + lsDT);
System.out.println("parisDT = " + parisDT);

// shDt    = 2020-09-12T15:06:46.609112900+08:00[Asia/Shanghai]
// lsDT    = 2020-09-12T00:06:46.610617900-07:00[America/Los_Angeles]
// parisDT = 2020-09-12T09:06:46.612123200+02:00[Europe/Paris]

Instant

Instant通常表示一个时间戳,需要注意两点:

第一,它通常表示的是UTC时间。

第二,对于DateCalendar的兼容性就是通过它实现的。

来看下常见用法:

public class InstantTest {

    public static void main(String[] args) {

        Instant now = Instant.now();
        int nano = now.getNano();
        long epochSecond = now.getEpochSecond();
        long milliseconds = now.toEpochMilli();

        System.out.println("now          = " + now);
        System.out.println("nano         = " + nano);
        System.out.println("epochSecond  = " + epochSecond);
        System.out.println("milliseconds = " + milliseconds);
    }
}

// now          = 2020-09-12T10:44:52.080563900Z
// nano         = 80563900
// epochSecond  = 1599907492
// milliseconds = 1599907492080

Intervals

如何计算两个时间的间隔?

我们可以使用PeriodDuration类,前者通常表示间隔的年、月、日,后者通常表示间隔的小时、分钟、秒等。

但是我们还可以通过ChronoUnit并指定单位:

LocalDateTime born = LocalDateTime.of(1949, 10, 1, 10, 00, 00);
LocalDateTime now  = LocalDateTime.now();

long years   = ChronoUnit.YEARS.between(born, now);
long months  = ChronoUnit.MONTHS.between(born, now);
long weeks   = ChronoUnit.WEEKS.between(born, now);
long days    = ChronoUnit.DAYS.between(born, now);
long hours   = ChronoUnit.HOURS.between(born, now);
long minutes = ChronoUnit.MINUTES.between(born, now);
long seconds = ChronoUnit.SECONDS.between(born, now);

System.out.println("years   = " + years);
System.out.println("months  = " + months);
System.out.println("weeks   = " + weeks);
System.out.println("days    = " + days);
System.out.println("hours   = " + hours);
System.out.println("minutes = " + minutes);
System.out.println("seconds = " + seconds);

// years   = 70
// months  = 851
// weeks   = 3702
// days    = 25914
// hours   = 621944
// minutes = 37316687
// seconds = 2239001254

TemporalAdjusters

如何计算当月的最后一天?这个月的最后一个星期六?

此时,就轮到TemporalAdjusters大展身手了。

public class TemporalAdjustersTest {

    public static void main(String[] args) {

        LocalDate date = LocalDate.now();
        LocalDate firstDayOfMonth     = date.with(TemporalAdjusters.firstDayOfMonth());
        LocalDate firstDayOfYear      = date.with(TemporalAdjusters.firstDayOfYear());
        LocalDate firstDayOfNextMonth = date.with(TemporalAdjusters.firstDayOfNextMonth());
        LocalDate firstDayOfNextYear  = date.with(TemporalAdjusters.firstDayOfNextYear());
        LocalDate firstFriday         = date.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY));
        LocalDate lastDayOfMonth      = date.with(TemporalAdjusters.lastDayOfMonth());
        LocalDate lastDayOfYear       = date.with(TemporalAdjusters.lastDayOfYear());
        LocalDate lastSaturday        = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.SATURDAY));

        System.out.println("firstDayOfMonth     = " + firstDayOfMonth);
        System.out.println("firstDayOfYear      = " + firstDayOfYear);
        System.out.println("firstDayOfNextMonth = " + firstDayOfNextMonth);
        System.out.println("firstDayOfNextYear  = " + firstDayOfNextYear);
        System.out.println("firstFriday         = " + firstFriday);
        System.out.println("lastDayOfMonth      = " + lastDayOfMonth);
        System.out.println("lastDayOfYear       = " + lastDayOfYear);
        System.out.println("lastSaturday        = " + lastSaturday);
    }
}

// firstDayOfMonth     = 2020-09-01
// firstDayOfYear      = 2020-01-01
// firstDayOfNextMonth = 2020-10-01
// firstDayOfNextYear  = 2021-01-01
// firstFriday         = 2020-09-04
// lastDayOfMonth      = 2020-09-30
// lastDayOfYear       = 2020-12-31
// lastSaturday        = 2020-09-26

Format & Parse

再来看下如何进行格式化和解析。

public class FormatAndParseTest {

    public static void main(String[] args) {

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        System.out.println("-----------------------------------------");
        System.out.println("now         = " + now);
        String formattedDT = now.format(formatter);
        System.out.println("formattedDT = " + formattedDT);
        System.out.println("-----------------------------------------");

        String dtStr = "2020-10-01 08:30:30";
        LocalDateTime ldt = LocalDateTime.parse(dtStr, formatter);
        System.out.println("dtStr       = " + dtStr);
        System.out.println("ldt         = " + ldt);
        System.out.println("-----------------------------------------");
    }
}

// -----------------------------------------
// now         = 2020-09-12T18:50:37.969089500
// formattedDT = 2020-09-12 18:50:37
// -----------------------------------------
// dtStr       = 2020-10-01 08:30:30
// ldt         = 2020-10-01T08:30:30
// -----------------------------------------

Backward Compatibility

最后,我们再来看下Java时间对于DateCalendar的兼容性。

public class CompatibilityTest {

    public static void main(String[] args) {

        Date date = new Date();
        Instant dateInstant = date.toInstant();
        LocalDateTime ldt1 = LocalDateTime.ofInstant(dateInstant, ZoneId.systemDefault());
        System.out.println("-----------------------------------------");
        System.out.println("date     = " + date);
        System.out.println("ldt1     = " + ldt1);
        System.out.println("-----------------------------------------");

        Calendar calendar = Calendar.getInstance();
        Instant calInstant = calendar.toInstant();
        LocalDateTime ldt2 = LocalDateTime.ofInstant(calInstant, ZoneId.of("Asia/Shanghai"));
        System.out.println("calendar = " + calendar.getTime());
        System.out.println("ldt2     = " + ldt2);
        System.out.println("-----------------------------------------");
    }
}

// -----------------------------------------
// date     = Sat Sep 12 18:51:19 CST 2020
// ldt1     = 2020-09-12T18:51:19.713
// -----------------------------------------
// calendar = Sat Sep 12 18:51:19 CST 2020
// ldt2     = 2020-09-12T18:51:19.757
// -----------------------------------------

Reference


一位喜欢提问、尝试的程序员

(转载本站文章请注明作者和出处 姚屹晨-yaoyichen

Post Directory