文章目录
1. 简介
Java中的时间相关库真是一言难尽,以至于被逼出了一个joda库,Java时间处理库一直被吐槽当然也是有原因的。
java.util.Date、java.util.Calendar不好用,但是也勉强够用了。
Java8的时候,也许是KPI要求,他们终于决定对时间库做点重构,哦,不,已经不叫重构了,叫重写。
功能是完善了,但是对像我这样的API调用爱好者来说却太过复杂,不信你看,下面的类和接口你知道几个:
- LocalTime
- LocalDate
- LocalDateTime
- OffsetDateTime
- ZonedDateTime
- TemporalAccessor
- Temporal
- ChronoField
- TemporalAdjusters
- TemporalAdjuster
- TemporalUnit
- ChronoUnit
- Year
- YearMonth
- ZoneOffset
- ZoneId
- Instant
- Period
- ChronoPeriod
- Clock
- TemporalAmount
- DateTimeFormatter
当然,不了解上面这些类和接口也并不影响我们调用API,但是多了解一点总是没错的。
当然,这里我们也不会介绍全部的类和接口,我们会简单的说一下它们的逻辑,重点介绍一点常用且使用的类,如下面这些类。
- LocalDate
- LocalDateTime
- DateTimeFormatter
- ZoneId
- Instant
- Duration
- Period
- TemporalAdjusters
2. LocalDate
2.1 创建
LocalDate的构造方法是私有的,创建只能通过静态工程方法,主要是of和parse。
@Test
public void createLocalDate(){
LocalDate.now();//今天
LocalDate.of(2021, 1, 1);
LocalDate.of(2021, Month.JANUARY, 1);
LocalDate.parse("2021-01-01");
}
2.2 计算
新的日期库最大的特点是日期计算真的非常方便,基本可以执行任何你需要的。
@Test
public void calcLocalDate(){
LocalDate now = LocalDate.now();
now.plusDays(7);//7天之后
now.plusYears(1);//1年之后
now.minusMonths(1);//1月之前
now.minusWeeks(1);//1周之前
}
2.3 比较
@Test
public void compareLocalDate(){
LocalDate now = LocalDate.now();
LocalDate first = LocalDate.of(2021, 1, 1);
System.out.println(now.isBefore(first));//now日期是不是早于first
System.out.println(now.isAfter(first));//now日期是不是晚于first
System.out.println(now.isEqual(first));//now和first是不是同一天
}
2.4 其他
@Test
public void otherLocalDate(){
LocalDate now = LocalDate.now();
now.toEpochDay();//当前日期时间戳对应的天
now.withYear(2021);//指定年份
now.until(LocalDate.now().plusDays(36));//计算2个日期的时间区间
now.atStartOfDay();//一天的开始时间
now.format(DateTimeFormatter.ISO_DATE_TIME);//格式化时间
}
3. LocalDateTime
LocalDateTime与LocalDate的使用方法基本一致,只是多了比LocalDate多了时间部分,他们之前的方法基本可以相互套用。
@Test
public void chronoFieldTest(){
LocalDateTime localDateTime = LocalDateTime.now();
//当前的分钟
System.out.println(localDateTime.getLong(ChronoField.MINUTE_OF_HOUR));
//当前是一天的第多少个小时
System.out.println(localDateTime.getLong(ChronoField.HOUR_OF_DAY));
//当前是一个月中的第多少天
System.out.println(localDateTime.getLong(ChronoField.DAY_OF_MONTH));
//当前是一年中的第多少天
System.out.println(localDateTime.getLong(ChronoField.DAY_OF_YEAR));
//当前是这个月的第多少周,不是按完整的周计算
System.out.println(localDateTime.getLong(ChronoField.ALIGNED_WEEK_OF_MONTH));
//时间戳对应的天 19412
System.out.println(localDateTime.getLong(ChronoField.EPOCH_DAY));
}
@Test
public void chronoUnitTest(){
LocalDateTime localDateTime = LocalDateTime.now();
localDateTime.plus(1,ChronoUnit.YEARS);//一年之后
localDateTime.plus(1,ChronoUnit.HOURS);//一小时之后
localDateTime.plus(1,ChronoUnit.HALF_DAYS);//半天之后
localDateTime.plus(1,ChronoUnit.MONTHS);//一月之后
}
4. DateTimeFormatter
DateTimeFormatter很简单,也很复杂,基本就是使用ofPattern方法指定格式化时间的格式就可以了。
@Test
public void datetimeFormatter(){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDateTime date = LocalDateTime.now();
System.out.println(formatter.format(date));
System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(date));
System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(date));
System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date));
System.out.println(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).format(date));
System.out.println(DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(date));
}
5. ZoneId与ZoneOffset
ZoneId是一个抽象类,主要是为了处理时区问题的,ZoneOffset是ZoneId的实现类。
其实主要就涉及本地时间和时间戳的一个转换问题。
@Test
public void zoneId(){
long now = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(now);
ZoneId zoneId = ZoneId.of("Asia/Shanghai");//东八区北京时间
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId);
System.out.println(now);
System.out.println(localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());
System.out.println(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
System.out.println(localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli());
}
对于上面的代码,如果你能解释为什么使用ZoneOffset.UTC,比正确的时区换算出来的毫秒要大,你就理解了Java时区这个问题。
如果对于时区还不太了解,可以看一下后面GTM、UTC与时间戳的内容。
6. Instant
@Test
public void instant() throws InterruptedException {
//1毫秒
Instant instant = Instant.ofEpochMilli(1);
System.out.println(instant.toEpochMilli());
//1秒再过100万纳秒
instant = Instant.ofEpochSecond(1,1_000_000_000);
System.out.println(instant.toEpochMilli());
Instant start = Instant.now();
TimeUnit.SECONDS.sleep(2);
Instant end = Instant.now();
Duration duration = Duration.between(start,end);
System.out.println(duration.toMillis());
}
7. Duration与Period
Duration和Period都有时间区间、时间段的意味,但是Period更强调周期,例如一年、一个月、一周等。
思考这样一个问题:女朋友把每一年的第99天定为爱情纪念日,2019年的第99天是2019-04-09,那么明年的爱情纪念日应该用下面哪一种方式计算:
- Duration.ofDays(365)
- Period.ofYears(1)
如果,女朋友今天心情不好,想要问一下距离下一次爱情纪念日还有多少天?又应该用哪一种方式计算?
用下面的代码测试一下吧,我只能帮你到这里了。
@Test
public void test(){
LocalDate localDate = LocalDate.parse("2019-04-09");
System.out.println(localDate.getLong(ChronoField.DAY_OF_YEAR));
LocalDate durationLocalDate = localDate.plusDays(365);
System.out.println(durationLocalDate.format(DateTimeFormatter.ISO_DATE));
System.out.println(durationLocalDate.getLong(ChronoField.DAY_OF_YEAR));
LocalDate periodLocalDate = localDate.plus(Period.ofYears(1));
System.out.println(periodLocalDate.format(DateTimeFormatter.ISO_DATE));
System.out.println(periodLocalDate.getLong(ChronoField.DAY_OF_YEAR));
}
另外LocalDateTime使用Duration,LocalDate使用Period。
@Test
public void duration(){
LocalDateTime now = LocalDateTime.now();
LocalDateTime localDateTime = now.plusDays(1);
Duration duration = Duration.between(now, localDateTime);
System.out.println(duration.toDays());
Period period = Period.between(now.toLocalDate(), localDateTime.toLocalDate());
System.out.println(period.getDays());
}
@Test
public void duration(){
LocalDateTime now = LocalDateTime.now();
LocalDateTime localDateTime = now.plusYears(1);
Duration duration = Duration.between(now, localDateTime);
System.out.println(duration.toMillis());
System.out.println(Duration.ofDays(1).toHours());
}
8. TemporalAdjuster与TemporalAdjusters
如果你要日程安排类的相关功能,TemporalAdjuster绝对是一个好的选择。
一看TemporalAdjusters,我们就能猜到应该是TemporalAdjuster的工具类,它提供了下面这些方法:
方法 | 说明 |
---|---|
lastDayOfYear | 今年的最后一天 |
firstDayOfYear | 当年的第一天 |
lastDayOfMonth | 下月的最后一天 |
next(DayOfWeek) | 下一个星期X |
firstDayOfMonth | 当月的第1天 |
lastDayOfNextYear | 明年的最后一天 |
lastDayOfNextMonth | 下月的最后一天 |
firstDayOfNextYear | 明年的第一天 |
firstDayOfNextMonth | 下个月的第1天 |
previous(DayOfWeek) | 前一个星期X |
nextOrSame(DayOfWeek) | 下一个星期X,如果当前就是星期X,那就是当前日期 |
lastInMonth(DayOfWeek) | 当月最后一个星期X |
firstInMonth(DayOfWeek) | 当月第一个星期X |
previousOrSame(DayOfWeek) | 前一个星期X,如果当前就是星期X,那就是当前日期 |
dayOfWeekInMonth(int,DayOfWeek) | 当月第Y个星期X,例如,母亲节,5月的第2个星期天 |
到底怎么使用呢?
看一些小例子就清楚了:
@Test
public void temporalAdjusters(){
LocalDate now = LocalDate.now();
LocalDate tmp;
//下一个星期五
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.FRIDAY);
tmp = now.with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//下一个星期五,如果当前日期是星期五,那么就是当前日期
temporalAdjuster = TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY);
tmp = now.with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
temporalAdjuster = TemporalAdjusters.lastDayOfMonth();
tmp = now.with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//当月月的最后一个周五
temporalAdjuster = TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY);
tmp = now.with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//当月月第一个周六
temporalAdjuster = TemporalAdjusters.firstInMonth(DayOfWeek.SATURDAY);
tmp = now.with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//母亲节:5月第2个星期天
temporalAdjuster = TemporalAdjusters.dayOfWeekInMonth(2,DayOfWeek.SUNDAY);
tmp = now.withMonth(5).with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//父亲节:6月第3个星期天
temporalAdjuster = TemporalAdjusters.dayOfWeekInMonth(3,DayOfWeek.SUNDAY);
tmp = now.withMonth(6).with(temporalAdjuster);
System.out.println(tmp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
9. GTM、UTC与时间戳
9.1 GMT
GMT(Greenwish Mean Time): 格林威治标准时间,指位于伦敦郊区的皇家格林尼治天文台的标准时间。
为啥使用这个时间做标准时间呢?
因为本初子午线被定义在通过那里的经线。
为啥本初子午线被定义在通过那里的经线呢?
你的问题太多了,电影中知道太多的角色都没有什么好结局。
格林威治标准时间的正午是指当太阳横穿格林尼治子午线时,也就是在格林尼治上空最高点的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能和实际的太阳时相差16分钟。地球每天的自转是有些不规则的,而且正在缓慢减速。
所以,格林尼治时间已经不再被作为标准时间使用。现在的标准时间是协调世界时(UTC),它由原子钟提供。
9.2 UTC
UTC(Coordinated Universal Time):世界统一时间,世界标准时间、国际协调时间
虽然GMT不够精确,但是我们通常让我GMT与UTC是等价的。
9.3 时间戳
时间戳是指格林威治时间(GMT)自:1970-01-01 00:00:00至当前所经过的秒(10位)或者毫秒(13位)数。
所以,从时间戳的定义我们就知道了时间戳和时区没有任何关系,不存在本地时间转化问题,因此,在存储时间戳的时候最好不要做加减换算,使用本地时间的时候才需要。
时间戳虽然不需要加减换算,但是时间戳到本地时间却需要,因为从时间戳定义我们知道,它关联了本地时间GMT。
所以,时间戳转本地时间是需要时区的,大致的流程是:
- 时间戳 -> GMT时间
- GMT时间 + 时区偏移 -> 本地时间,例如,我们在东八区,所以我们的时间就是GMT+8
当然我们计算的时候通常是先执行时间戳加减,然后再转换为时间,结果上是没有问题的,逻辑上容易让人误解。