java.time
jdk引入新的时间体系:
- LocalDate只保留日期
- LocalTime只保留时间
- LocalDateTime同时保留时间和日期
- ZonedDateTime保留了时区、时间、日期
- Instant:是不带时区一个时时间点,与java.util.Date类似,但是精确到纳秒。
@Test
public void testJdk8Date() {
System.out.println(LocalDate.now());
System.out.println(LocalTime.now());
System.out.println(ZonedDateTime.now());
System.out.println(LocalDateTime.now());
System.out.println(Instant.now());
System.out.println(MinguoDate.now());
}
//2021-04-14
//11:54:14.660177900
//2021-04-14T11:54:14.661181200+08:00[Asia/Shanghai]
//2021-04-14T11:54:14.661181200
//2021-04-14T03:54:14.661181200Z
//Minguo ROC 110-04-14
从上面也可以看出Instant
是无时区的,代表UTC的时间戳,Z在时区里面表示UTC
LocalDate.now()
public static LocalDate now() {
return now(Clock.systemDefaultZone());
}
public static LocalDate now(Clock clock) {
Objects.requireNonNull(clock, "clock");
final Instant now = clock.instant(); // called once
return ofInstant(now, clock.getZone());
}
public static LocalDate ofInstant(Instant instant, ZoneId zone) {
Objects.requireNonNull(instant, "instant");
Objects.requireNonNull(zone, "zone");
ZoneRules rules = zone.getRules();
ZoneOffset offset = rules.getOffset(instant);
//计数utc和时区偏差后的总时间
long localSecond = instant.getEpochSecond() + offset.getTotalSeconds();
long localEpochDay = Math.floorDiv(localSecond, SECONDS_PER_DAY);
return ofEpochDay(localEpochDay);
}
//此时这里处理的已经是加上时区偏差的时间了
//如东八区: epochDay = utcSec + 8*60*60
public static LocalDate ofEpochDay(long epochDay) {
EPOCH_DAY.checkValidValue(epochDay);
long zeroDay = epochDay + DAYS_0000_TO_1970;
// find the march-based year
zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle
long adjust = 0;
if (zeroDay < 0) {
// adjust negative years to positive for calculation
long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
adjust = adjustCycles * 400;
zeroDay += -adjustCycles * DAYS_PER_CYCLE;
}
long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
if (doyEst < 0) {
// fix estimate
yearEst--;
doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
}
yearEst += adjust; // reset any negative year
int marchDoy0 = (int) doyEst;
// convert march-based values back to january-based
int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
int month = (marchMonth0 + 2) % 12 + 1;
int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
yearEst += marchMonth0 / 10;
// check year now we are certain it is correct
int year = YEAR.checkValidIntValue(yearEst);
return new LocalDate(year, month, dom);
}
private LocalDate(int year, int month, int dayOfMonth) {
this.year = year;
this.month = (short) month;
this.day = (short) dayOfMonth;
}
最后
LocalDate(int year, int month, int dayOfMonth)
完全和时区没有关系,只保存了所在时区应该显示的年月日,简单理解就是时间的字符串
LocalDateTime转时间戳
- ZoneId:表示时间对应的时区,常见的是:区域/ 城市,如
Asia/Shanghai
,Asia/Tokyo
- ZoneOffset:ZoneId的子类,使用时间偏移量表示
@Test
public void testZone() {
System.out.println(ZoneId.systemDefault());
System.out.println(ZoneId.of("Asia/Shanghai"));
System.out.println(ZoneId.of("Asia/Hong_Kong"));
System.out.println(ZoneOffset.UTC);
System.out.println(ZoneId.of("Z"));
System.out.println(ZoneId.of("+8"));
System.out.println(ZoneId.of("UTC+8"));
System.out.println(ZoneId.ofOffset("UTC",ZoneOffset.ofHours(8)));
//具体见方法注释
System.out.println(ZoneOffset.of("+08:30:20"));
}
//Asia/Shanghai
//Asia/Shanghai
//Asia/Hong_Kong
//Z
//Z
//+08:00
//UTC+08:00
//UTC+08:00
//2021-04-14T06:47:58.587
//2021-04-14T13:47:58.587
ZoneDateTime
ZoneDateTime在LocalDateTime的基础上增加了时区的概念,这样可以将同一个时间转换成不同时区的格式显示
@Test
public void testDateStr (){
LocalDateTime leaving = LocalDateTime.of(2020, 6, 29, 19, 30);
//这里也是2020/06/29 19:30:00,但是有了时区的概念,是上海时区的2020/06/29 19:30:00
//而上面的LocalDateTime可以理解为只是一个时间字符串
ZonedDateTime shanghaiTime = leaving.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(shanghaiTime);
//这里把上海时间加上半小时后,转化为了东京时间,东京时间与上海时间相差1个小时
//所以这里显示的字符串是2020-06-29T21:00+09:00[Asia/Tokyo]
ZonedDateTime tokyoTime = shanghaiTime.plusMinutes(30).withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyoTime);
}
理解LocalDateTime的无时区性
@Test
public void testLocalDtToIne (){
long defaultZoneDt = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long utcZoneDt = LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli();
System.out.println(defaultZoneDt);
System.out.println(utcZoneDt);
System.out.println(FgDateUtils.millsToDateTime(defaultZoneDt));
System.out.println(FgDateUtils.millsToDateTime(utcZoneDt));
}
1618383299634
1618412099635
2021-04-14 14:54:59
2021-04-14 22:54:59
实际当前时间是上海时间2021-04-14 14:54:59
LocalDateTime.now()
只是简单的字符串,在初始化的过程,已经将utc的时间搓,转换为当前时区的年月日,所以取名也有Local。
.atZone()
则是将前面得到的时间字符串赋予时区,即让程序理解2021-04-14 14:54:59是那个时区的时间,如果理解为UTC的时间,那么对应上海时间还要在加上8个小时,也就是2021-04-14 22:54:59
封装的工具类
/**
* @author froggengo@qq.com
* @date 2021/4/14 9:30.
*/
public class FgTimeUtils {
//时间搓转日期,mills为utc时间搓
public static LocalDateTime millsToDateTime(long mills) {
Instant instant = Instant.ofEpochMilli(mills);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
//时间搓转日期
public static LocalDateTime secToDateTime(int sec) {
Instant instant = Instant.ofEpochSecond(sec);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
/**
* 字符串转时间搓,时间格式
* yyyy-MM-dd HH:mm:ss
* yyyy-MM-dd
* yyyy-MM
*/
public static LocalDateTime strToDateTime(String str) {
Objects.requireNonNull(str);
String dateStr;
switch (str.length()) {
case 19:
dateStr = str;
break;
case 10:
dateStr = str + " 00:00:00";
break;
case 7:
dateStr = str + "-01 00:00:00";
break;
default:
throw new UnsupportedOperationException();
}
return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(FgDateUtils.YYYY_MM_DD_HH_MM_SS));
}
/**
* localDatetime转时间搓(utc),时间搓均指utc+0时间
*/
public static long dateTimeToMills(LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
/**
* 字符串转时间搓,时间格式
* yyyy-MM-dd HH:mm:ss
* yyyy-MM-dd
* yyyy-MM
*/
public static long strToMills(String str) {
Objects.requireNonNull(str);
return dateTimeToMills(strToDateTime(str));
}
/**
* 计算偏移量后当天时间0点ZoneDateTime
* LocalDate的另一个方法会转成
* LocalDate.now().atStartOfDay(ZoneId.systemDefault()
*/
public static LocalDateTime startOfDayByOffset(int offset) {
LocalDate now = LocalDate.now().plusDays(offset);
return LocalDateTime.of(now, LocalTime.MIN);
}
/**
* 计算偏移后当月第一天时间0点
*/
public static LocalDateTime startOfMonthByOffset(int offset) {
LocalDate now = LocalDate.now().plusMonths(offset);
return LocalDateTime.of(now.with(TemporalAdjusters.firstDayOfMonth()), LocalTime.MIN);
}
/**
* 将time包下的时间对象转成相应的字符串
*/
public static <T extends Temporal> String formatTime(T time) {
if (time instanceof LocalDateTime) {
return ((LocalDateTime) time).format(DateTimeFormatter.ofPattern(FgDateUtils.YYYY_MM_DD_HH_MM_SS));
} else if (time instanceof LocalDate) {
return ((LocalDate) time).format(DateTimeFormatter.ofPattern(FgDateUtils.YYYY_MM_DD));
} else if ((time instanceof LocalTime)) {
return ((LocalTime) time).format(DateTimeFormatter.ofPattern(FgDateUtils.HH_MM_SS));
} else if ((time instanceof ZonedDateTime)) {
return ((ZonedDateTime) time).format(DateTimeFormatter.ofPattern(FgDateUtils.YYYY_MM_DD_HH_MM_SS));
}
throw new UnsupportedOperationException("only support LocalDateTime、LocalDate、LocalTime、ZonedDateTime");
}
public static String formatDate(LocalDateTime dateTime) {
return dateTime.format(DateTimeFormatter.ofPattern(FgDateUtils.YYYY_MM_DD));
}
// 两个时间搓的时间差
public static String duration(long max, long min) {
Duration duration = Duration.ofMillis(max - min);
return duration.toDaysPart() + "天" + duration.toHoursPart() + "小时"
+ duration.toMinutesPart() + "分" + duration.toSecondsPart() + "秒";
}
}