# normal

返回:time

# 日期的操作和格式化

back

# 增加和减少日期

LocalDate date = LocalDate.of(2017, 1, 5);          // 2017-01-05

LocalDate date1 = date.withYear(2016);              // 修改为 2016-01-05
LocalDate date2 = date.withMonth(2);                // 修改为 2017-02-05
LocalDate date3 = date.withDayOfMonth(1);           // 修改为 2017-01-01

LocalDate date4 = date.plusYears(1);                // 增加一年 2018-01-05
LocalDate date5 = date.minusMonths(2);              // 减少两个月 2016-11-05
LocalDate date6 = date.plus(5, ChronoUnit.DAYS);    // 增加5天 2017-01-10
1
2
3
4
5
6
7
8
9

TemporalAdjusters

上面例子中对于日期的操作比较简单,但是有些时候我们要面临更复杂的时间操作,比如将时间调到下一个工作日,或者是下个月的最后一天,这时候我们可以使用with()方法的另一个重载方法,它接收一个TemporalAdjuster参数,可以使我们更加灵活的调整日期:

LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY));      // 返回下一个距离当前时间最近的星期日
LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY));   // 返回本月最后一个星期六
1
2

要使上面的代码正确编译,你需要使用静态导入TemporalAdjusters对象:

import static java.time.temporal.TemporalAdjusters.*;
1
方法名 描述
dayOfWeekInMonth 返回同一个月中每周的第几天
firstDayOfMonth 返回当月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一个月中第一个星期几
lastDayOfMonth 返回当月的最后一天
lastDayOfNextMonth 返回下月的最后一天
lastDayOfNextYear 返回下一年的最后一天
lastDayOfYear 返回本年的最后一天
lastInMonth 返回同一个月中最后一个星期几
next / previous 返回后一个/前一个给定的星期几
nextOrSame / previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回
@Test
public void testWeek(){
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println(localDateTime.getDayOfWeek());
    System.out.println(localDateTime.getDayOfMonth());
    System.out.println(localDateTime.withDayOfMonth(7));
    System.out.println(localDateTime.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)));
    System.out.println(localDateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)));
}
// MONDAY
// 6
// 2020-04-07T15:30:17.031
// 2020-04-06T15:30:17.031
// 2020-04-06T15:30:17.031
1
2
3
4
5
6
7
8
9
10
11
12
13
14

自定义的TemporalAdjuster

如果上面表格中列出的方法不能满足你的需求,你还可以创建自定义的TemporalAdjuster接口的实现,TemporalAdjuster也是一个函数式接口,所以我们可以使用Lambda表达式:

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}
1
2
3
4

比如给定一个日期,计算该日期的下一个工作日(不包括星期六和星期天):

LocalDate date = LocalDate.of(2017, 1, 5);
date.with(temporal -> {
    // 当前日期
    DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));

    // 正常情况下,每次增加一天
    int dayToAdd = 1;

    // 如果是星期五,增加三天
    if (dayOfWeek == DayOfWeek.FRIDAY) {
        dayToAdd = 3;
    }

    // 如果是星期六,增加两天
    if (dayOfWeek == DayOfWeek.SATURDAY) {
        dayToAdd = 2;
    }

    return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 格式化日期

新的日期API中提供了一个DateTimeFormatter类用于处理日期格式化操作,它被包含在java.time.format包中,Java 8的日期类有一个format()方法用于将日期格式化为字符串,该方法接收一个DateTimeFormatter类型参数:

LocalDateTime dateTime = LocalDateTime.now();
String strDate1 = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);    // 20170105
String strDate2 = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE);    // 2017-01-05
String strDate3 = dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME);    // 14:20:16.998
String strDate4 = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));   // 2017-01-05
String strDate5 = dateTime.format(DateTimeFormatter.ofPattern("今天是:YYYY年 MMMM DD日 E", Locale.CHINESE)); // 今天是:2017年 一月 05日 星期四
1
2
3
4
5
6

同样,日期类也支持将一个字符串解析成一个日期对象,例如:

String strDate6 = "2017-01-05";
String strDate7 = "2017-01-05 12:30:05";

LocalDate date = LocalDate.parse(strDate6, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDateTime dateTime1 = LocalDateTime.parse(strDate7, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
1
2
3
4
5

# 时区

Java 8中的时区操作被很大程度上简化了,新的时区类java.time.ZoneId是原有的java.util.TimeZone类的替代品。ZoneId对象可以通过ZoneId.of()方法创建,也可以通过ZoneId.systemDefault()获取系统默认时区

ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZoneId systemZoneId = ZoneId.systemDefault();
1
2

of()方法接收一个“区域/城市”的字符串作为参数,你可以通过getAvailableZoneIds()方法获取所有合法的“区域/城市”字符串:

Set<String> zoneIds = ZoneId.getAvailableZoneIds();
1

对于老的时区类TimeZone,Java 8也提供了转化方法

ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();
1

有了ZoneId,我们就可以将一个LocalDate、LocalTime或LocalDateTime对象转化为ZonedDateTime对象:

LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);
1
2

将zonedDateTime打印到控制台为:2017-01-05T15:26:56.147+08:00[Asia/Shanghai]
ZonedDateTime对象由两部分构成,LocalDateTimeZoneId,其中2017-01-05T15:26:56.147部分为LocalDateTime,+08:00[Asia/Shanghai]部分为ZoneId。

另一种表示时区的方式是使用ZoneOffset,它是以当前时间和**世界标准时间(UTC)/格林威治时间(GMT)**的偏差来计算,例如:

ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);
1
2
3

# 其他历法

Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。闰年的定义是:非世纪年,能被4整除;世纪年能被400整除。为了计算的一致性,公元1年的前一年被当做公元0年,以此类推。 此外Java 8还提供了4套其他历法(很奇怪为什么没有汉族人使用的农历),每套历法都包含一个日期类,分别是:

  • ThaiBuddhistDate:泰国佛教历
  • MinguoDate:中华民国历
  • JapaneseDate:日本历
  • HijrahDate:伊斯兰历

每个日期类都继承ChronoLocalDate类,所以可以在不知道具体历法的情况下也可以操作。不过这些历法一般不常用,除非是有某些特殊需求情况下才会使用。
这些不同的历法也可以用于向公历转换:

LocalDate date = LocalDate.now();
JapaneseDate jpDate = JapaneseDate.from(date);
1
2

由于它们都继承ChronoLocalDate类,所以在不知道具体历法情况下,可以通过ChronoLocalDate类操作日期:

Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE);
ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow();
1
2

我们在开发过程中应该尽量避免使用ChronoLocalDate,尽量用与历法无关的方式操作时间,因为不同的历法计算日期的方式不一样,比如开发者会在程序中做一些假设,假设一年中有12个月,如果是中国农历中包含了闰月,一年有可能是13个月,但开发者认为是12个月,多出来的一个月属于明年的。再比如假设年份是累加的,过了一年就在原来的年份上加一,但日本天皇在换代之后需要重新纪年,所以过了一年年份可能会从1开始计算。

# 计算一周后的时间

back

同样可以计算月,一年等之后的日期

LocalDate weekLater = today.plus(1, ChronoUnit.WEEKS);
        log.info("一周后日期:【{}】",weekLater);
1
2

# 检查生日这种周期性日期

back

@Test
    public void testBirth(){
        LocalDate today = LocalDate.now();
        LocalDate birthday = LocalDate.of(1988,9,2);
        MonthDay monthDay = MonthDay.of(birthday.getMonth(),birthday.getDayOfMonth());
        MonthDay currentMonthDay = MonthDay.from(today);
        log.info(monthDay.equals(currentMonthDay)?"生日到了":"还没到你的生日");
    }
1
2
3
4
5
6
7
8

# 获取今天的日期

back

LocalDate localDate = LocalDate.now();
1

# 获取年月日信息

back

LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
1
2
3
4

# 获取特定日期

back

LocalDate date = LocalDate.of(2018,2,6);
1

# 判断日期是否相等

back

@Test
public void testBeforeAfter(){
    LocalDateTime t1 = LocalDateTime.of(2019,11,11,12,24,24);
    LocalDateTime t21 = LocalDateTime.of(2019,11,11,12,24,24);
    LocalDateTime t22 = LocalDateTime.of(2019,11,11,12,24,55);
    LocalDateTime t3 = LocalDateTime.of(2019,11,11,12,24,55);

    System.out.println(t1.isAfter(t21));
    System.out.println(t1.isBefore(t21));

    System.out.println(t3.isAfter(t1));

    Range<LocalDateTime> timeRange = Range.closedOpen(t1,t3);
    System.out.println(timeRange.contains(t21));
    System.out.println(timeRange.contains(t22));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

输出

false
false
true
true
false
1
2
3
4
5

# 老一套的date

back

# date比较大小

拿到getTime进行比较

s1.getCreateTime().getTime() > s2.getCreateTime().getTime()
1