关于Java中的日期你应该知道的事

原创
01/18 19:09
阅读数 290

1. 简介

Java中的时间相关库真是一言难尽,以至于被逼出了一个joda库,Java时间处理库一直被吐槽当然也是有原因的。

java.util.Date、java.util.Calendar不好用,但是也勉强够用了。

Java8的时候,也许是KPI要求,他们终于决定对时间库做点重构,哦,不,已经不叫重构了,叫重写。

功能是完善了,但是对像我这样的API调用爱好者来说却太过复杂,不信你看,下面的类和接口你知道几个:

  1. LocalTime
  2. LocalDate
  3. LocalDateTime
  4. OffsetDateTime
  5. ZonedDateTime
  6. TemporalAccessor
  7. Temporal
  8. ChronoField
  9. TemporalAdjusters
  10. TemporalAdjuster
  11. TemporalUnit
  12. ChronoUnit
  13. Year
  14. YearMonth
  15. ZoneOffset
  16. ZoneId
  17. Instant
  18. Period
  19. ChronoPeriod
  20. Clock
  21. TemporalAmount
  22. DateTimeFormatter

当然,不了解上面这些类和接口也并不影响我们调用API,但是多了解一点总是没错的。

当然,这里我们也不会介绍全部的类和接口,我们会简单的说一下它们的逻辑,重点介绍一点常用且使用的类,如下面这些类。

  1. LocalDate
  2. LocalDateTime
  3. DateTimeFormatter
  4. ZoneId
  5. Instant
  6. Duration
  7. Period
  8. 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));//当前是这个月的第多少周,不是按完整的周计算
    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,那么明年的爱情纪念日应该用下面哪一种方式计算:

  1. Duration.ofDays(365)
  2. 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 当年的第一天
1astDayOLMonth 下月的最后一天
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。

所以,时间戳转本地时间是需要时区的,大致的流程是:

  1. 时间戳 -> GMT时间
  2. GMT时间 + 时区偏移 -> 本地时间,例如,我们在东八区,所以我们的时间就是GMT+8

当然我们计算的时候通常是先执行时间戳加减,然后再转换为时间,结果上是没有问题的,逻辑上容易让人误解。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部