一、时间线
在历史上,基本的时间单元‘秒’来自于地球围绕其轴心的自转。地球完成一次自转需要24小时或者86400秒。
Java日期和时间API规范要求Java使用如下时标:
- 每天都有86400秒
- 每天正午与官方时间准确匹配
- 其他时间也要以一种精确定义的方式与其紧密匹配
1.在Java中,一个Instant对象表示时间轴上的一个点(绝对时间),原点(元年)被规定为1970年1月1日的午夜。从原点开始,时间按照每天86400秒进行计算。向前向后分别以纳秒为单位。Instant的最小值(Instant.MIN)可以回退到10亿年前之久,最大值(Instant.MAX)表示1000000000年的12月31日
- Instant.now():返回当前的瞬时点
- Duration.between(start,end):计算两个瞬时点之间的时间距离
--------------------------------------------------------------------------------------------------------------------
Instant start=Instant.now();
Thread.sleep(1000);
Instant end=Instant.now();
Duration timeElapsed=Duration.between(start,end);
long millis = timeElapsed.toMillis();
--------------------------------------------------------------------------------------------------------------------
2.一个Duration对象表示两个瞬时点之间的时间量,可以通过调用toNanos、toMillis、toSeconds、toMinutes、toHours或者toDays方法,得到以各种时间单位来表示的Duration对象
Duration对象在内部存储上需要多个long值,秒钟的值由一个long值保存,而纳秒的值由另一个int保存,大概300年的纳秒值才会导致long值溢出
3.时间Instant和Duration的数学操作
方法 | 描述 |
plus,minus | 对当前Instant或者Duration添加或减少一段时间 |
plusNanos,plusMillis, plusSeconds,plusMinutes, plusHours,plusDays | 根据指定的时间单位,对当前Instant或者Duration添加一段时间 |
minusNanos,minusMillis, minusSeconds,minusMinutes, minusHours,minusDays | 根据指定的时间单位,对当前Instant或者Duration减少一段时间 |
multipliedBy,dividedBy | 返回当前Duration与指定long值相乘或相除得到的一段时间,注意 这只能用于Duration不能用于Instant |
isZero,isNegativ | 检查Duration是否为0或负数 |
例子:检查某个算法是否比另一个算法至少快十倍
--------------------------------------------------------------------------------------------------------------------
Duration timeElapsed2=Duration.between(start2,end2);
boolean overTenTimesFaster=
timeElapsed.multipliedBy(10).minus(timeElapsed2).isNegative();
--------------------------------------------------------------------------------------------------------------------
4.Instant和Duration类都是不可变的,他们的所有方法,例如multipliedBy或者minus都会返回一个新的实例
二、本地日期
在新的JavaAPI中,有两种人类时间本地日期/时间和带时区的时间,例如
“June 14,1903”、“July 16,1969,09:32:00 EDT”,有许多计算是不需要时区的,并且在某些情况下,考虑时区可能会导致错误的结果,API设计者们不推荐使用带时区的时间,生日、假期、会议时间等,最好都用本地日期或事件来表示,除非真的希望表示绝对的瞬时点
1.LocalDate是一个带有年份、月份、当月天数的日期,要创建一个LocalDate可以使用静态方法now或of:
--------------------------------------------------------------------------------------------------------------------
LocalDate today=LocalDate.now();
LocalDate alonzosBirthday=LocalDate.of(1903,6,14);
alonzosBirthday=LocalDate.of(1903,Month.JUNE,14);
--------------------------------------------------------------------------------------------------------------------
2.与Date不同,LocalDate使用与正常生活中一样的数字来表示月份,即1表示1月份,除此之外还可以使用Month枚举
3.LocalDate常用方法
方法 | 描述 |
now,of | 静态方法,根据当前时间或指定的年、月、日来创建一个LocalDate对象 |
plusDays,plusWeeks, plusMonths,plusYears | 向当前LocalDate对象添加几天、几周、几个月或者几年 |
minusDays,minusWeeks, minusMonths,minusYears | 从当前LocalDate对象中减去几天、几周、几个月或者几年 |
plus,minus | 添加或减少一个Duration或Period |
withDayOfMonth withDayOfYear withMonth,withYear | 将月份天数、年份天数、月份、年份修改为指定的值,并返回一个新的 LocalDate对象 |
getDayOfMonth | 获得月份天数(1~31) |
getDayOfYear | 获得年份天数(1~366) |
getDayWeek | 获得星期几(返回一个DayOfWeek的枚举值) |
getMonth,getMonthValue | 获得月份,或者为一个Month枚举的值,或者是1~12之间的一个数字 |
getYear | 获得年份,-999999999-999999999之间 |
Until | 获得两个日期之间的Period对象,或者指定ChronoUnits数字 |
isBefore,isAfter | 比较两个LocalDate |
isLeapYear | 如果当年为闰年,即当年的年份可以被4或400整除,但不能被100整除, 则返回true,该算法适用于以前的所有年份,即使从历史角度看并不准确 |
例子1
--------------------------------------------------------------------------------------------------------------------
LocalDate programmersDay=LocalDate.of(2014,1,1).plusDays(255);
--------------------------------------------------------------------------------------------------------------------
例子2
--------------------------------------------------------------------------------------------------------------------
independenceDay.until(christmas); //返回5个月21天
independenceDay.until(christmas,ChronoUnit.DAYS); //返回174天
--------------------------------------------------------------------------------------------------------------------
例子3
--------------------------------------------------------------------------------------------------------------------
LocalDate.of(1900,1,1).getDayOfWeek().getValue()
--------------------------------------------------------------------------------------------------------------------
4.两个瞬时点之间的距离时一个持续时间Duration,而对于本地日期,对应的对象就是时段Period,表示一段逝去的年、月或日。可以通过birthday.plus(Period.ofYears(1))或者birthday.plusYears(1)来获取下一年的生日日期,但如果时闰年,birthday.plus(Duration.ofDays(365))会导致错误的结果
5.DayOfWeek枚举提供了一些方便的方法plus和minus,用来计算模为7的星期几,例如:DayOfWeek.SATURDAY.plus(3)会返回DayOfWeek.TUESDAY
6.除了LocalDate,Java8中还提供了MonthDay、YearMonth和Year类来描述部分日期,例如MonthDay可以用来表示12月25日
三、日期校正器
1.编写代码时候,经常计算例如"每月的第一个周二"这样的日期。TemporalAdjusters类提供了许多静态方法来进行常用的校正。可以将一个校正方法的结果传递给with方法,与往常一样,with方法会返回一个新的LocalDate对象,而不会修改原有的对象
--------------------------------------------------------------------------------------------------------------------
LocalDate firstTuesday=LocalDate.of(year,month,1)
.with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
--------------------------------------------------------------------------------------------------------------------
2.TemporalAdjusters类中的日期校正方法
方法 | 描述 |
next(weekday),previous(weekday) | 指定工作日的前一天或后一天 |
nextOrSame(weekday) previousOrSame(weekday) | 从指定的日期开始,指定工作日的前一天或 后一天 |
dayOfWeekInMonth(n,weekday) | 某月中的第n个工作日(weekday所指定) |
lastInMonth(weekday) | 某月中的最后一个工作日(weekday所指定) |
firstDayOfMonth(),firstDayOfNextMonth() firstDayOfNextYear(),lastDayOfMonth() lastDayOfPreviousMonth(),lastDayOfYear() |
3.可以通过TemporalAdjuster接口来实现自己的校正器
--------------------------------------------------------------------------------------------------------------------
TemporalAdjuster NEXT_WORKDAY=w->{
LocalDate result=(LocalDate)w;
do{
result=result.plusDays(1);
}while(result.getDayOfWeek().getValue()>=6);
return result;
}
LocalDate backToWork=today.with(NEXT_WORKDAY);
--------------------------------------------------------------------------------------------------------------------
在本例中,lambda表达式的参数类型为Temporal,必须被强制转换成LocalDate对象,我们可以通过ofDateAdjuster方法以及一个UnaryOperator<LocalDate>的lambda表达式来避免强制转换
--------------------------------------------------------------------------------------------------------------------
TemporalAdjuster NEXT_WORKDAY=TemporalAdjusters.ofDateAdjuster(w->{
LocalDate result=w;
do{
result=result.plusDays(1);
}while(result.getDayOfWeek().getValue()>=6);
return result;
});
--------------------------------------------------------------------------------------------------------------------
四、本地时间
1.LocalTime表示一天中的某个时间,例如15:30:00,其实例可以通过now或者of方法来创建
--------------------------------------------------------------------------------------------------------------------
LocalTime rightNow=LocalTime.now();
LocalTime bedTime=LocalTime.of(22,30);
--------------------------------------------------------------------------------------------------------------------
2.LocalTime方法
方法 | 描述 |
now,of | 静态方法,根据当前时间或者指定时间创建实例 |
plusHours,plusMinutes plusSeconds,plusNanos | 向当前LocalTime对象添加几个小时、几分钟、几秒或者 几微秒 |
minusHours,minusMinutes minusSeconds,minusNanos | 从当前LocalTime对象中减去几个小时、几分钟、几秒或 者几微妙 |
plus,minus | 添加或减少一个Duration |
withHour,withMinute withSecond,withNano | 将小时、分钟、秒钟、微秒修改为指定的值,并返回一个新的 LocalTime对象 |
getHour,getMinute getSecond,getNano | 获得当前LocalTime的小时、分钟、秒钟及微秒值 |
toSecondOfDay,toNanoOfDay | 返回午夜时间与当前LocalTime之间相隔的秒钟数或微秒 数 |
isBefore,isAfter | 比较两个LocalTime |
3.LocalTime本身并不关心是AM还是PM,而是交给格式化程序来做这件事
4.LocalDatetime类表示一个日期和时间,该类适合用来存储确定时区中的某个时间点,例如某一次具体的课程或者活动安排。但是如果需要进行跨夏令时的时间计算,或者需要处理的用户处于不同的时区中,应该使用ZonedDateTime类
五、带时区的时间
1.因特网编号管理局(IANA)维护着一份全球所有已知时区的数据库,每年会更新几次,这些更新主要处理夏令时规则的改变,Java就使用了IANA数据库。每个时区都有一个ID,例如America/New_York或者Europe/Berlin,想获取素有可用的时区,可以调用
ZoneId.getAvailableIds
2.更具指定的时区ID,静态方法ZoneId.of(id)会返回一个ZoneId对象,可以通过local.atZone(zoneId)将一个LocalDateTime对象转换成一个ZonedDateTime对象,或者通过调用静态方法ZonedDateTime.of(year,month,day,hour,minute,second,nano,zoneId)
来创建一个ZonedDateTime对象
--------------------------------------------------------------------------------------------------------------------
ZoneDateTime apollo11launch=
ZonedDateTime.of(1969,7,16,9,32,0,0,ZoneId.of("America/New_York"));
--------------------------------------------------------------------------------------------------------------------
3.可以调用apollo11launch.toInstant()来获得对应的Instant对象,也可以通过调用
instant.atZone(zonedId)将一个Instant对象转换成一个指定时区的ZonedDateTime对象
4.ZonedDateTime的方法
方法 | 描述 |
now,of,ofInstant | 静态方法,根据当前时间或指定的年、月、 日、分、秒、微秒(或者一个LocalDate、 LocalTime对象)和ZonedId,或者一个 Instant对象和ZoneId创建实例 |
plusDays,plusWeeks plusMonths,plusYears plusHours,plusMinutes plusSeconds,plusNanos | 向当前ZonedDateTime对象添加相应单位 的时间 |
minusDays,minusWeeks minusMonths,minusYears minusHours,minusMinutes minusSecond,minusNanos | 从当前ZonedDateTime对象中减去相应单位 的时间 |
plus,minus | 添加或减少一个Duration或者Period |
withDayOfMonth,withDayOfYear withMonth,withYear,withHour withMinute,withSecond,withNano | 将相应单位的时间改为指定的值,并返回 一个新的ZonedDateTime对象 |
withZoneSameInstant withZoneSameLocal | 返回指定时区中的一个新ZonedDateTime对 象,它可以表示相同的瞬时点或者本地时间 |
getDayOfMonth | 获得月份天数 |
getDayOfYear | 获得年份天数 |
getDayOfWeek | 获得星期几(返回一个DayOfWeek的枚举值) |
getMonth,getMonthValue | 获得月份,或者为一个Month枚举的值,或 者为1~12之间的数字 |
getYear | 获得年份 |
getHour,getMinute getSecond,getNano | 获得ZonedDatetime对象的小时、分钟、秒钟 或者微秒数 |
getOffset | 获得与UTC之间的时差,返回一个 ZoneOffset对象,时差在-12:00到+14:00 之间,会根据夏令时发生改变 |
toLocaldate,toLocalTime toInstant | 返回本地日期、本地时间、瞬时点 |
isBefore,isAfter | 比较两个ZonedDateTime |
注:UTC表示"世界协调时",是格林威治皇家天文台的时间
5.当调整一个跨夏令时的日期时,不要添加7天的Duration对象,而应该使用Period类,否则无法处理夏令时
--------------------------------------------------------------------------------------------------------------------
ZonedDateTime nextMeeting=meeting.plus(Duration.ofDays(7)); //不能处理夏令时
ZonedDateTime nextMeeting=meeting.plus(Period.ofDays(7));
--------------------------------------------------------------------------------------------------------------------
六、格式化和解析
DateTimeFormatter类提供了三种格式化方法来打印日期/时间:
- 预定义的标准格式
- 语言环境相关的格式
- 自定义的格式
标准格式主要用于机器可读的时间戳如:1969-07-16-05:00,语言相关的格式是为了让人能够读懂日期和时间,这里只简单描述语言相关的格式。
DateTimeFormatter类用来代替DateFormat,如果出于向后兼容的考虑,需要一个DateFormat的实例,可以通过调用DateTimeFormatter实例的toFormat()方法来维护
1.Java8对日期和时间各提供了4种风格,SHORT、MEDIUM、LONG和FULL
风格 | 日期 | 时间 |
SHORT | 7/16/69 | 9:32 AM |
MEDIUM | Jul 16,1969 | 9:32:00 AM |
LONG | July 16,1969 | 9:32:00 AM EDT |
FULL | Wednesday,July 16,1969 | 9:32:00 AM EDT |
可以通过静态方法ofLocalizedDate、ofLocalizedTime和ofLocalizedDateTime来创建这类格式
--------------------------------------------------------------------------------------------------------------------
DateTimeFormatter formatter=
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
String formatted=formatter.format(apollo11launch);
--------------------------------------------------------------------------------------------------------------------
如果要更改其他的语言环境,只需要使用withLocale
--------------------------------------------------------------------------------------------------------------------
formatted=formatter.withLocale(Locale.FRENCH).formar(apollo11launch);
--------------------------------------------------------------------------------------------------------------------
2.可以通过ofPattern()方法来自定义日期的格式
--------------------------------------------------------------------------------------------------------------------
formatted=DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");
//Wed 1969-07-16 09:32
--------------------------------------------------------------------------------------------------------------------
七、与遗留代码互操作
Instant <===>java.util.Date
Date.from(instant)
date.toInstant()
ZonedDateTime<===>java.util.GregorianCalendar
GregorianCalendar.from(zonedDateTime)
cal.toZonedDateTime()
Instant<===>java.sql.Timestamp
Timestamp.from(instant)
timestamp.toInstant()
LocalDateTime<===>java.sql.Timestamp
Timestamp.valueOf(localDateTime)
timeStamp.toLocalDateTime()
LocalDate<===>java.sql.Time
Date.valueOf(localDate)
date.toLocalDate()
LocalTime<===>java.sql.Time
Time.valueOf(localTime)
date.toLocalDate()
DateTimeFormatter<===>java.text.DateFormat
formatter.toFormat()
java.util.TimeZone<===>ZoneId
Timezone.getTimeZone(id)
timeZone.toZoneId()
java.nio.file.attribute.FileTime<===>Instant
FileTime.from(instant)
fileTime.toInstant()