日期时间

Locale

在计算机中,通常使用 Locale 表示一个国家或地区的日期、时间、数字、货币等格式。Locale由语言_国家的字母缩写构成,例如,

  • zh_CN 表示中文+中国,
  • en_US表示英文+美国。语言使用小写,国家使用大写。

对于日期来说,不同的Locale,例如,中国和美国的表示方式如下:

  • zh_CN:2016-11-30
  • en_US:11/30/2016

计算机用Locale在日期、时间、货币和字符串之间进行转换。一个电商网站会根据用户所在的 Local e对用户显示如下:

 

中国用户

美国用户

购买价格

12000.00

12,000.00

购买日期

2016-11-30

11/30/2016

DateCalendarSimpleDateFormat 和 TimeZone均是 JKD 8 以前的 API,确实不好用,请尽可能放弃他们

JDK 8 该用什么

jdk 8 开始,java.time 包提供了新的一系列日期时间的 API,主要是:

  • 本地日期和时间:LocalDateTimeLocalDateLocalTime
  • 带时区的日期和时间:ZonedDateTime
  • 时刻:Instant
  • 时区:ZoneIdZoneOffset
  • 时间间隔:Duration
  • 适用于新 API 的 格式化类型:DateTimeFormatter

LocalDateTime

LocalDate d = LocalDate.now(); // 当前日期
LocalTime t = LocalTime.now(); // 当前时间
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间

// 自定义格式化:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now()));
// 用自定义格式解析:
LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf);
System.out.println(dt2);

LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);
// 加5天减3小时:
LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
System.out.println(dt2); // 2019-10-31T17:30:59
// 减1月:
LocalDateTime dt3 = dt2.minusMonths(1);
System.out.println(dt3); // 2019-09-30T17:30:59

LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);
// 日期变为31日:
LocalDateTime dt2 = dt.withDayOfMonth(31);
System.out.println(dt2); // 2019-10-31T20:30:59
// 月份变为9:
LocalDateTime dt3 = dt2.withMonth(9); // 把 2019-10-31 的月份调整为9时,日期也自动变为30。
System.out.println(dt3); // 2019-09-30T20:30:59 

// 本月第一天0:00时刻:
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
System.out.println(firstDay);

// 本月最后1天:
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay);

// 下月第1天:
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(nextMonthFirstDay);

// 本月第1个工作日:
LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println(firstWeekday);

// 判断时间前后
LocalDate.now().isBefore(LocalDate.of(2019, 11, 19));
LocalTime.now().isAfter(LocalTime.parse("08:15:00"));

Duration 和 Period

// LocalDateTime 之间的时间差
LocalDateTime start = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
LocalDateTime end = LocalDateTime.of(2020, 1, 9, 19, 25, 30);
Duration d = Duration.between(start, end);
System.out.println(d); // PT1235H10M30S

// LocalDate 之间的时间差
Period p = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9));
System.out.println(p); // P1M21D

// 直接创建
Duration d1 = Duration.ofHours(10); // 10 hours
Duration d2 = Duration.parse(“P1DT2H3M”); // 1 day, 2 hours, 3 minutes

ZonedDateTime

// 可以简单地把 ZonedDateTime 理解成 LocalDateTime 加 ZoneId。ZoneId 是 java.time 引入的新的时区类
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC); // ZoneOffset 有 UTC、MAX、MIN 三个 ZoneId 常量
// 2019-09-15T20:58:18.786182+08:00[Asia/Shanghai]
// 2019-09-15T08:58:18.788860-04:00[America/New_York]

// 转换到 LocalDateTime,丢弃时区信息
LocalDateTime ldt = zdt.toLocalDateTime();

// 时区转换
// 以中国时区获取当前时间:
ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 转换为纽约时间:
ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));
// 注意时区转换包含了夏令时的计算,这才是时间跨时区转换的正确打开方式,涉及到时区时,千万不要自己计算时差,否则难以正确处理夏令时。

DateTimeFormatter

// DateTimeFormatter 线程安全,不可变;
// SimpleDateFormat 非线程安全,使用的时候,只能在方法内部创建新的局部变量。而 DateTimeFormatter 可以只创建一个实例,到处引用。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm”);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“E, yyyy-MMMM-dd HH:mm”, Locale.US);

ZonedDateTime zdt = ZonedDateTime.now();
var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm’Z’”); // 固定字符可用单引号’表示
formatter.format(zdt);
// 2019-09-15T23:16 GMT+08:00

var zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
zhFormatter.format(zdt);
// 2019 9月 15 周日 23:16

var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
usFormatter.format(zdt);
// Sun, September/15/2019 23:16

Instant

// 用 Instant 代替毫秒数,表示时间戳
now.getEpochSecond(); // 秒
now.toEpochMilli(); // 毫秒

public final class Instant implements … {
    private final long seconds; // 秒数
    private final int nanos; // 纳秒数
}

// 以指定时间戳创建Instant:
Instant ins = Instant.ofEpochSecond(1568568760);
ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault()); // 加上时区就变成 ZonedDateTime, 注意这个 systemDefault 时区

旧API转新API

// Date -> Instant:
Instant ins1 = new Date().toInstant();

// Calendar -> Instant -> ZonedDateTime:
Instant ins2 = Calendar.getInstance().toInstant();
ZonedDateTime zdt = ins2.atZone(calendar.getTimeZone().toZoneId());