十九、日期和时间
日期和时间
本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记
5.1 Date类
Date
是JDK 1.0
中java.util
包下提供类,Date
表示时刻,内部主要是一个long
类型的值,表示特定的瞬间,可以精确到毫秒,如下所示:
private transient long fastTime;
Date
有两个构造方法:
public Date(long date) {
fastTime = date;
}
public Date() {
this(System.currentTimeMillis());
}
第一个构造方法是根据传入的毫秒数进行初始化;第二个构造方法是默认构造方法,它根据System.currentTimeMillis()
的返回值进行初始化。System.currentTimeMillis()
是一个常用的方法,它返回当前时刻距离纪元时的毫秒数。Date
中的大部分方法都已经过时了,其中没有过时的主要方法有下面这些:
public long getTime() // 返回毫秒数
public boolean equals(Object obj) // 主要就是比较内部的毫秒数是否相同
public int compareTo(Date anotherDate) // 与其他Date进行比较,如果当前Date的毫秒数小于参数中的返回-1,相同返回0,否则返回1
public boolean before(Date when) // 判断是否在给定日期之前
public boolean after(Date when) // 判断是否在给定日期之后
public int hashCode() // 哈希值算法与Long类似
5.2 TimeZone
TimeZone
表示时区,它是一个抽象类,有静态方法用于获取其实例。获取当前的默认时区,代码为:
@Test
public void testTimeZone(){
assertTrue("Asia/Shanghai".equals( TimeZone.getDefault().getID()));
}
获取默认时区,并输出其ID
,每个人的电脑可能根据实际情况不同,并不一定都是Asia/Shanghai
。
默认时区是在哪里设置的呢?可以更改吗?
更改时区可以使用TimeZone.setDefault
和JVM
参数指定。
使用TimeZone.setDefault
设置默认时区代码如下:
@Test
public void testTimeZoneSetDefault(){
TimeZone.setDefault(TimeZone.getTimeZone("GMT+08:00"));
assertTrue("GMT+08:00".equals( TimeZone.getDefault().getID()));
}
使用JVM
参数修改如下所示:
java -Duser.timezone=Asia/Shanghai
TimeZone
也有静态方法,可以获得任意给定时区的实例。比如,获取美国东部时区:
TimeZone tz = TimeZone.getTimeZone("US/Eastern");
ID
除了可以是名称外,还可以是GMT
形式表示的时区,如:
TimeZone tz = TimeZone.getTimeZone("GMT+08:00");
5.3 Locale
Locale
表示国家(或地区)和语言,它有两个主要参数:一个是国家(或地区);另一个是语言,每个参数都有一个代码,不过国家(或地区)并不是必需的。比如,中国内地的代码是CN
,中国台湾地区的代码是TW
,美国的代码是US
,中文语言的代码是zh
,英文语言的代码是en
。Locale
类中定义了一些静态变量,表示常见的Locale
,比如:
Locale.US
:表示美国英语。Locale.ENGLISH
:表示所有英语。Locale.TAIWAN
:表示中国台湾地区所用的中文。Locale.CHINESE
:表示所有中文。Locale.SIMPLIFIED_CHINESE
:表示中国内地所用的中文。
与TimeZone
类似,Locale
也有静态方法获取默认值,如:
@Test
public void testLocale(){
Locale locale = Locale.getDefault();
assertTrue("zh_CN".equals(locale.toString()));
}
5.4 Calendar
java.util.Calendar
类是日期和时间操作中的主要类,它表示与TimeZone
和Locale
相关的日历信息,可以进行各种相关的运算。我们先来看下它的内部组成,与Date
类似,Calendar
内部也有一个表示时刻的毫秒数,定义为:
protected long time;
除此之外,Calendar
内部还有一个数组,表示日历中各个字段的值,定义为:
protected int fields[];
这个数组的长度为17
,保存一个日期中各个字段的值,都有哪些字段呢?Calendar
类中定义了一些静态变量,表示这些字段,主要有:
Calendar.YEAR
:表示年。Calendar.MONTH
:表示月, 1 1 1月是 0 0 0,Calendar
同样定义了表示各个月份的静态变量,如Calendar.JULY
表示 7 7 7月。Calendar.DAY_OF_MONTH
:表示日,每月的第一天是 1 1 1。Calendar.HOUR_OF_DAY
:表示小时,为 0 ~ 23 0~23 0~23。Calendar.MINUTE
:表示分钟,为 0 ~ 59 0~59 0~59。Calendar.SECOND
:表示秒,为 0 ~ 59 0~59 0~59。Calendar.MILLISECOND
:表示毫秒,为 0 ~ 999 0~999 0~999。Calendar.DAY_OF_WEEK
:表示星期几,周日是 1 1 1,周一是 2 2 2,周六是 7 7 7,Calenar
同样定义了表示各个星期的静态变量,如Calendar.SUNDAY
表示周日。
Calendar
是抽象类,不能直接创建对象,它提供了多个静态方法,可以获取Calendar
实例,比如:
public static Calendar getInstance()
{
Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
public static Calendar getInstance(TimeZone zone,
Locale aLocale)
{
return createCalendar(zone, aLocale);
}
最终调用的方法都是需要TimeZone
和Locale
的,如果没有,则会使用上面介绍的默认值。getInstance
方法会根据TimeZone
和Locale
创建对应的Calendar
子类对象,在中文系统中,子类一般是表示公历的GregorianCalendar
。getInstance
方法封装了Calendar
对象创建的细节。TimeZone
和Locale
不同,具体的子类可能不同,但都是Calendar
。
来看代码,输出当前时间的各种信息:
@Test
public void testCalendar() {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(1709045743587L);
assertTrue("year: 2024 month: 1 day: 27 hour: 22 minute: 55 second: 43 millisecond: 587 day_of_week: 3"
.equals("year: " + calendar.get(Calendar.YEAR) + " month: " + calendar.get(Calendar.MONTH) + " day: "
+ calendar.get(Calendar.DAY_OF_MONTH) + " hour: " + calendar.get(Calendar.HOUR_OF_DAY)
+ " minute: "
+ calendar.get(Calendar.MINUTE) + " second: " + calendar.get(Calendar.SECOND) + " millisecond: "
+ calendar.get(Calendar.MILLISECOND) + " day_of_week: " + calendar.get(Calendar.DAY_OF_WEEK)));
}
Calendar
内部,会将表示时刻的毫秒数,按照TimeZone
和Locale
对应的年历,计算各个日历字段的值,存放在fields
数组中,Calendar.get
方法获取的就是fields
数组中对应字段的值。调用函数:setTimeInMillis(long millis)
和setTime(Date date)
,Calendar
支持根据Date
或毫秒数设置时间,也支持根据年月日等日历字段设置时间,比如:
public final void set(int year, int month, int date)
public final void set(int year, int month, int date, int hourOfDay, int minute, int second)
public void set(int field, int value)
除了直接设置,Calendar
支持根据字段增加和减少时间:
public void add(int field, int amount)
amount
为正数表示增加,负数表示减少。比如,如果想设置Calendar
为第二天的下午
2
2
2点
15
15
15,代码可以为:
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 14);
calendar.set(Calendar.MINUTE, 15);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Calendar
的这些方法中一个比较方便和强大的地方在于,它能够自动调整相关的字段。比如,我们知道
2
2
2月最多有
29
29
29天,如果当前时间为
1
1
1月
30
30
30号,对Calendar.MONTH
字段加
1
1
1,即增加一月,Calendar
不是简单的只对月字段加
1
1
1,那样日期是
2
2
2月
30
30
30号,是无效的,Calendar
会自动调整为
2
2
2月最后一天,即
2
2
2月
28
28
28日或
29
29
29日。再如,设置的值可以超出其字段最大范围,Calendar
会自动更新其他字段,如:
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR_OF_DAY, 48);
calendar.add(Calendar.MINUTE, -120);
相当于增加了 46 46 46小时。
内部,根据字段设置或修改时间时,Calendar
会更新fields
数组对应字段的值,但一般不会立即更新其他相关字段或内部的毫秒数的值,不过在获取时间或字段值的时候, Calendar
会重新计算并更新相关字段。
简单总结下,Calenar
做了一项非常烦琐的工作,根据TimeZone
和Locale
,在绝对时间毫秒数和日历字段之间自动进行转换,且对不同日历字段的修改进行自动同步更新。除了add
方法,Calendar
还有一个类似的方法:
calendar.roll(Calendar.MINUTE, 3);
与add
方法的区别是,roll
方法不影响时间范围更大的字段值。比如:
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 13);
calendar.set(Calendar.MINUTE, 59);
calendar.add(Calendar.MINUTE, 3);
calendar
首先设置为13:59
,然后分钟字段加
3
3
3,执行后的calendar
时间为14:02
。如果add
改为roll
,即:
calendar.roll(Calendar.MINUTE, 3);
则执行后的calendar
时间会变为13:02
,在分钟字段上执行roll
方法不会改变小时的值。Calendar
可以方便地转换为Date
或毫秒数,方法是:
public final Date getTime()
public long getTimeInMillis()
与Date
类似,Calendar
之间也可以进行比较,也实现了Comparable
接口,相关方法有:
public boolean equals(Object obj)
public int compareTo(Calendar anotherCalendar)
public boolean after(Object when)
public boolean before(Object when)
5.5 DateFormat
DateFormat
类主要在Date
和字符串表示之间进行相互转换,它有两个主要的方法:
public final String format(Date date)
public Date parse(String source)
format
将Date
转换为字符串,parse
将字符串转换为Date
。Date
的字符串表示与TimeZone
和Locale
都是相关的,除此之外,还与两个格式化风格有关,一个是日期的格式化风格,另一个是时间的格式化风格。DateFormat
定义了4个静态变量,表示
4
4
4种风格:SHORT
、MEDIUM
、LONG
和FULL
;还定义了一个静态变量DEFAULT
,表示默认风格,值为MEDIUM
,不同风格输出的信息详细程度不同。
与Calendar
类似,DateFormat
也是抽象类,也用工厂方法创建对象,提供了多个静态方法创建DateFormat
对象,有三类方法:
public static final DateFormat getDateInstance();
public static final DateFormat getDateTimeInstance();
public static final DateFormat getTimeInstance();
getDateTimeInstance
方法既处理日期也处理时间,getDateInstance
方法只处理日期,getTimeInstance
方法只处理时间。看下面的代码:
@Test
public void testDateFormat() {
Calendar calendar = Calendar.getInstance();
// 2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
assertTrue("2016年8月15日 14:15:20".equals(DateFormat.getDateTimeInstance().format(calendar.getTime())));
assertTrue("2016年8月15日".equals(DateFormat.getDateInstance().format(calendar.getTime())));
assertTrue("14:15:20".equals(DateFormat.getTimeInstance().format(calendar.getTime())));
}
每类工厂方法都有两个重载的方法,接受日期和时间风格以及Locale
作为参数:
public static final DateFormat getDateTimeInstance(int dateStyle, int timeStyle);
public static final DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
比如,看下面的代码:
@Test
public void testDataFormatStyleLocale() {
Calendar calendar = Calendar.getInstance();
// 2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
assertTrue("2016年8月15日 CST 14:15:20".equals(DateFormat
.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.CHINESE).format(calendar.getTime())));
}
DateFormat
的工厂方法里,我们没看到TimeZone
参数,不过,DateFormat
提供了一个setter
方法,可以设置TimeZone
:
public void setTimeZone(TimeZone zone)
{
calendar.setTimeZone(zone);
}
DateFormat
虽然比较方便,但如果我们要对字符串格式有更精确的控制,则应该使用SimpleDateFormat
这个类。
5.6 SimpleDateFormat
SimpleDateFormat
是DateFormat
的子类,相比DateFormat
,它的一个主要不同是,它可以接受一个自定义的模式(pattern
)作为参数,这个模式规定了Date
的字符串形式。
@Test
public void testSimpleDateFormatFormat() {
Calendar calendar = Calendar.getInstance();
// 2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy年MM月dd日 E HH时mm分ss秒");
assertTrue("2016年08月15日 周一 14时15分20秒".equals(sdf.format(calendar.getTime())));
}
SimpleDateFormat
有个构造方法,可以接受一个pattern
作为参数,上面例子pattern
是:yyyy年MM月dd日 E HH时mm分ss秒
。
pattern
中含义如下表格所示:
Letter | Date or Time Component | Presentation | Examples |
---|---|---|---|
G | Era designator | Text | AD |
y | Year | Year | 1996 ; 96 |
Y | Week year | Year | 2009 ; 09 |
M | Month in year (context sensitive) | Month | July ; Jul ; 07 |
L | Month in year (standalone form) | Month | July ; Jul ; 07 |
w | Week in year | Number | 27 |
W | Week in month | Number | 2 |
D | Day in year | Number | 189 |
d | Day in month | Number | 10 |
F | Day of week in month | Number | 2 |
E | Day name in week | Text | Tuesday ; Tue |
u | Day number of week (1 = Monday, …, 7 = Sunday) | Number | 1 |
a | Am/pm marker | Text | PM |
H | Hour in day (0-23) | Number | 0 |
k | Hour in day (1-24) | Number | 24 |
K | Hour in am/pm (0-11) | Number | 0 |
h | Hour in am/pm (1-12) | Number | 12 |
m | Minute in hour | Number | 30 |
s | Second in minute | Number | 55 |
S | Millisecond | Number | 978 |
z | Time zone | General time zone | Pacific Standard Time ; PST ; GMT-08:00 |
Z | Time zone | RFC 822 time zone | -0800 |
X | Time zone | ISO 8601 time zone | -08 ; -0800 ; -08:00 |
常见例子如下所示:
Date and Time Pattern | Result |
---|---|
"yyyy.MM.dd G 'at' HH:mm:ss z" | 2001.07.04 AD at 12:08:56 PDT |
"EEE, MMM d, ''yy" | Wed, Jul 4, '01 |
"h:mm a" | 12:08 PM |
"hh 'o''clock' a, zzzz" | 12 o'clock PM, Pacific Daylight Time |
"K:mm a, z" | 0:08 PM, PDT |
"yyyyy.MMMMM.dd GGG hh:mm aaa" | 02001.July.04 AD 12:08 PM |
"EEE, d MMM yyyy HH:mm:ss Z" | Wed, 4 Jul 2001 12:08:56 -0700 |
"yyMMddHHmmssZ" | 010704120856-0700 |
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" | 2001-07-04T12:08:56.235-0700 |
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX" | 2001-07-04T12:08:56.235-07:00 |
"YYYY-'W'ww-u" | 2001-W27-3 |
除了将Date
转换为字符串,SimpleDateFormat
也可以方便地将字符串转化为Date
,如下所示代码:
@Test
public void testSimpleDateFormatParse() throws ParseException {
String str = "2016-08-15 14:15:20.456";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {
Date date = sdf.parse(str);
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年M月d h:m:s.S a");
System.out.println(sdf2.format(date));
assertTrue("2016年8月15 2:15:20.456 下午".equals(sdf2.format(date)));
} catch (ParseException e) {
throw e;
}
}
代码将字符串解析为了一个Date
对象,然后使用另外一个格式进行了输出,这里SSS
表示三位的毫秒数。需要注意的是,parse
会抛出一个受检异常,异常类型为ParseException
,调用者必须进行处理。
5.7 局限性
Date
表示时刻,与年月日无关,Calendar
表示日历,与时区和Locale
相关,可进行各种运算,是日期时间操作的主要类,DateFormat/SimpleDateFormat
在Date
和字符串之间进行相互转换。这些API
存在着一些局限性,如下:
- 可变性:像日期和时间这样的类应该是不可变的,某一个日期时间对象都只能代表某一个特定的瞬间。
- 偏移性:
Date
类中的年份是从 1900 1900 1900开始的,月份都是从 0 0 0开始的,这不符合常规编程习惯。 - 格式化:用于日期格式化及解析的
SimpleDateFormat
只对Date
类有用,Calendar
类则不行。 - 线程不安全性。
- 不能处理闰秒:由于地球自转的不均匀性和长期变慢性(主要由潮汐摩擦引起的),所以在世界时(民用时)和原子时之间相差超过到 ± 0.9 ±0.9 ±0.9秒时,人们就把协调世界时向前拨 1 1 1秒(负闰秒,最后一分钟为 59 59 59秒)或向后拨 1 1 1秒(正闰秒,最后一分钟为 61 61 61秒)。目前,全球已经进行了 27 27 27次闰秒,均为正闰秒。最近一次闰秒是北京时间 2017 2017 2017年 1 1 1月 1 1 1日 7 7 7时 59 59 59分 59 59 59秒。
5.8 Java 8的日期和时间API
Java 8
中引入的java.time
纠正了过去的缺陷,引入的类主要有:
Instant
:表示时刻,不直接对应年月日信息,需要通过时区转换;LocalDateTime
:表示与时区无关的日期和时间,不直接对应时刻,需要通过时区转换;ZoneId/ZoneOffset
:表示时区;LocalDate
:表示与时区无关的日期,与LocalDateTime
相比,只有日期,没有时间信息;LocalTime
:表示与时区无关的时间,与LocalDateTime
相比,只有时间,没有日期信息;ZonedDateTime
:表示特定时区的日期和时间;Duration
:持续时间;
5.9 Instant
Instant
表示时刻,获取当前时刻,代码为:
Instant now = Instant.now();
可以根据Epoch Time
(纪元时)创建Instant
。比如,另一种获取当前时刻的代码可以为:
Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
我们知道,Date
也表示时刻,Instant
和Date
可以通过纪元时相互转换,代码为:
@Test
public void testInstantDate(){
Instant instant = Instant.ofEpochMilli(1709045743587L);
// Instant 转为 Date
Date d = Date.from(instant);
assertTrue("Tue Feb 27 22:55:43 CST 2024".equals(d.toString()));
// Date 转为 Instant
Instant i = d.toInstant();
assertTrue("2024-02-27T14:55:43.587Z".equals(i.toString()));
}
5.10 LocalDateTime
LocalDateTime
表示与时区无关的日期和时间,获取系统默认时区的当前日期和时间,代码为:
LocalDateTime ldt = LocalDateTime.now();
还可以直接用年月日等信息构建LocalDateTime
。比如,表示
2017
2017
2017年
7
7
7月
11
11
11日
20
20
20点
45
45
45分
5
5
5秒,代码可以为:
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
LocalDateTime
有很多方法,可以获取年月日时分秒等日历信息,比如:
public int getYear()
public int getMonthValue()
public int getDayOfMonth()
public int getHour()
public int getMinute()
public int getSecond()
public DayOfWeek getDayOfWeek()
5.11 LocalDate/LocalTime
可以认为LocalDateTime
由两部分组成,一部分是日期LocalDate
,另一部分是时间LocalTime
。它们的用法也很直观,比如:
//表示2017年7月11日
LocalDate ld = LocalDate.of(2017, 7, 11);
//当前时刻按系统默认时区解读的日期
LocalDate now = LocalDate.now();
//表示21点10分34秒
LocalTime lt = LocalTime.of(21, 10, 34);
//当前时刻按系统默认时区解读的时间
LocalTime time = LocalTime.now();
LocalDateTime
由LocalDate
和LocalTime
构成,LocalDate
加上时间可以构成LocalDateTime
,LocalTime
加上日期可以构成LocalDateTime
,比如:
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
LocalDate ld = ldt.toLocalDate(); //2017-07-11
LocalTime lt = ldt.toLocalTime(); // 20:45:05
//LocalDate加上时间,结果为2017-07-11 21:18:39
LocalDateTime ldt2 = ld.atTime(21, 18, 39);
//LocalTime加上日期,结果为2016-03-24 20:45:05
LocalDateTime ldt3 = lt.atDate(LocalDate.of(2016, 3, 24));
5.12 ZoneId/ZoneOffset
LocalDateTime
不能直接转为时刻Instant
,转换需要一个参数ZoneOffset
,ZoneOffset
表示相对于格林尼治的时区差,北京是
+
08
:
00
+08:00
+08:00。比如,转换一个LocalDateTime
为北京的时刻,方法为:
public static Instant toBeijingInstant(LocalDateTime ldt) {
return ldt.toInstant(ZoneOffset.of("+08:00"));
}
给定一个时刻,使用不同时区解读,日历信息是不同的,Instant
有方法根据时区返回一个ZonedDateTime
:
public ZonedDateTime atZone(ZoneId zone)
默认时区是ZoneId.systemDefault()
,可以这样构建ZoneId
:
// 北京时区
ZoneId bjZone = ZoneId.of("GMT+08:00")
ZoneOffset
是ZoneId
的子类,可以根据时区差构造。
5.13 ZonedDateTime
ZonedDateTime
表示特定时区的日期和时间,获取系统默认时区的当前日期和时间,代码为:
ZonedDateTime zdt = ZonedDateTime.now();
LocalDateTime.now
也是获取默认时区的当前日期和时间,有什么区别呢?LocalDateTime
内部不会记录时区信息,只会单纯记录年月日时分秒等信息,而ZonedDateTime
除了记录日历信息,还会记录时区,它的其他大部分构建方法都需要显式传递时区,比如:
// 根据 Instant 和时区构建 ZonedDateTime
public static ZonedDateTime ofInstant(Instant instant, ZoneId zone)
// 根据 LocalDate、LocalTime 和 ZoneId 构造
public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone)
ZonedDateTime
可以直接转换为Instant
,比如:
ZonedDateTime ldt = ZonedDateTime.now();
Instant now = ldt.toInstant();
5.14 格式化
Java 8
中,主要的格式化类是java.time.format.DateTimeFormatter
,它是线程安全的,看个例子:[插图]输出为:[插图]将字符串转化为日期和时间对象,可以使用对应类的parse方法,比如:
@Test
public void testDateTimeFormatterFormat(){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.of(2016,8,18,14,20,45);
assertTrue("2016-08-18 14:20:45".equals(formatter.format(ldt)));
}
将字符串转化为日期和时间对象,可以使用对应类的parse
方法,比如:
@Test
public void testDateTimeFormatterParse(){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = "2016-08-18 14:20:45";
LocalDateTime ldt = LocalDateTime.parse(str, formatter);
assertTrue("2016-08-18T14:20:45".equals(ldt.toString()));
}
5.15 设置和修改时间
修改时期和时间有两种方式,一种是直接设置绝对值,另一种是在现有值的基础上进行相对增减操作,Java 8
的大部分类都支持这两种方式。另外,Java 8
的大部分类都是不可变类,修改操作是通过创建并返回新对象来实现的,原对象本身不会变。我们来看一些例子。
调整时间为下午 3 3 3点 20 20 20分,代码为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.withHour(15).withMinute(20).withSecond(0).withNano(0);
还可以为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.toLocalDate().atTime(15, 20);
3 3 3小时 5 5 5分钟后,示例代码为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusHours(3).plusMinutes(5);
LocalDateTime
有很多plusXXX
和minusXXX
方法,分别用于相对增加和减少时间。
今天 0 0 0点,可以为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.with(ChronoField.MILLI_OF_DAY, 0);
ChronoField
是一个枚举,里面定义了很多表示日历的字段,MILLI_OF_DAY
表示在一天中的毫秒数,值从
0
0
0到
(
24
∗
60
∗
60
∗
1000
)
−
1
(24 * 60 * 60 * 1000)-1
(24∗60∗60∗1000)−1。还可以为:
LocalDateTime ldt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
LocalTime.MIN
表示
00
:
00
00:00
00:00。也可以为:
LocalDateTime ldt = LocalDate.now().atTime(0, 0);
下周二上午 10 10 10点整,可以为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2).with(ChronoField.MILLI_OF_DAY, 0).withHour(10);
上面下周二指定是下周,如果是下一个周二呢?这与当前是周几有关,如果当前是周一,则下一个周二就是明天,而其他情况则是下周,代码可以为:
LocalDate ld = LocalDate.now();
if(! ld.getDayOfWeek().equals(DayOfWeek.MONDAY)){
ld = ld.plusWeeks(1);
}
LocalDateTime ldt = ld.with(ChronoField.DAY_OF_WEEK, 2).atTime(10, 0);
针对这种复杂一点的调整,Java 8
有一个专门的接口TemporalAdjuster
,这是一个函数式接口,定义为:
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
Temporal
是一个接口,表示日期或时间对象,Instant
、LocalDateTime
和LocalDate
等都实现了它,这个接口就是对日期或时间进行调整,还有一个专门的类TemporalAdjusters
,里面提供了很多TemporalAdjuster
的实现。比如,针对下一个周几的调整,方法是:
public static TemporalAdjuster next(DayOfWeek dayOfWeek)
针对上面的例子,代码可以为:
LocalDate ld = LocalDate.now();
LocalDateTime ldt = ld.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).atTime(10, 0);
TemporalAdjusters
中还有很多方法,部分方法如下:
public static TemporalAdjuster firstDayOfMonth()
public static TemporalAdjuster lastDayOfMonth()
public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)
public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)
public static TemporalAdjuster previous(DayOfWeek dayOfWeek)
public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)
一些例子如下:
@Test
public void testTemporalAdjusters() {
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
// 下周二上午 10 点整
LocalDateTime nextThu = ldt.with(TemporalAdjusters.next(
DayOfWeek.TUESDAY)).toLocalDate().atTime(10, 0);
assertTrue("2017-07-18T10:00".equals(nextThu.toString()));
// 本月最后一天最后一刻
LocalDateTime thisMonthLastDayLastTime = ldt.with(TemporalAdjusters.lastDayOfMonth()).toLocalDate()
.atTime(LocalTime.MAX);
assertTrue("2017-07-31T23:59:59.999999999".equals(thisMonthLastDayLastTime.toString()));
long maxDayOfMonth = ldt.range(
ChronoField.DAY_OF_MONTH).getMaximum();
LocalDateTime thisMonthLastDayLastTime2 = ldt.withDayOfMonth((int) maxDayOfMonth).toLocalDate()
.atTime(LocalTime.MAX);
assertTrue("2017-07-31T23:59:59.999999999".equals(thisMonthLastDayLastTime2.toString()));
// 下个月第一个周一的下午5点整
LocalDateTime nextMonthFirstMondaySeventeen = ldt.plusMonths(1)
.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)).toLocalDate().atTime(17, 0);
assertTrue("2017-08-07T17:00".equals(nextMonthFirstMondaySeventeen.toString()));
}
5.16 时间段的计算
Java 8
中表示时间段的类主要有两个:Period
和Duration
。Period
表示日期之间的差,用年月日表示,不能表示时间;Duration
表示时间差,用时分秒等表示,也可以用天表示,一天严格等于
24
24
24小时,不能用年月表示。下面看一些例子。计算两个日期之间的差,看个Period
的例子:
@Test
public void testPeriod() {
LocalDate ld1 = LocalDate.of(2016, 3, 24);
LocalDate ld2 = LocalDate.of(2017, 7, 12);
Period period = Period.between(ld1, ld2);
assertTrue("1年3月18天".equals(period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"));
}
根据生日计算年龄,示例代码可以为:
LocalDate born = LocalDate.of(1990,06,20);
int year = Period.between(born, LocalDate.now()).getYears();
计算迟到分钟数,假定早上 9 9 9点是上班时间,过了 9 9 9点算迟到,迟到要统计迟到的分钟数,怎么计算呢?看代码:
@Test
public void testDuration() {
long lateMinutes = Duration.between(LocalTime.of(9, 0), LocalDateTime.of(2017, 7, 11, 20, 45, 5)).toMinutes();
assertTrue(705 == lateMinutes);
}
5.17 与 Date/Calendar 对象的转换
Java 8
的日期和时间API
没有提供与老的Date/Calendar
相互转换的方法,但在实际中,我们可能是需要的。前面介绍了Date
可以与Instant
通过毫秒数相互转换,对于其他类型,也可以通过毫秒数/Instant
相互转换。比如,将LocalDateTime
按默认时区转换为Date
,代码可以为:
public static Date toDate(LocalDateTime ldt){
return new Date(ldt.atZone(ZoneId.systemDefault())
.toInstant().toEpochMilli());
}
将ZonedDateTime
转换为Calendar
,代码可以为:
public static Calendar toCalendar(ZonedDateTime zdt) {
TimeZone tz = TimeZone.getTimeZone(zdt.getZone());
Calendar calendar = Calendar.getInstance(tz);
calendar.setTimeInMillis(zdt.toInstant().toEpochMilli());
return calendar;
}
Calendar
保持了ZonedDateTime
的时区信息。
将Date
按默认时区转换为LocalDateTime
,代码可以为:
public static LocalDateTime toLocalDateTime(Date date) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),
ZoneId.systemDefault());
}
将Calendar
转换为ZonedDateTime
,代码可以为:
public static ZonedDateTime toZonedDateTime(Calendar calendar) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(calendar.getTimeInMillis()),
calendar.getTimeZone().toZoneId());
return zdt;
}
原文地址:https://blog.csdn.net/qq_31654025/article/details/136358997
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!