Java——日期与时间详解
目录
- 日期与时间
- 1、概述与相关概念
- 1.1、UTC
- 1.2、GMT
- 1.3、时区
- 1.4、夏时令(DST)
- 1.5、UNIX时间戳(timestamp)
- 1.6、CST
- 1.7、ISO 8601
- 2、旧API
- 2.1、Date
- 2.1.1、构造方法
- 2.1.2、比较方法
- 2.1.3、其它方法
- 2.1.4、常见问题和注意事项
- 2.2、TimeZone
- 2.2.1、创建
- 2.2.2、常用方法
- 2.3、Calendar
- 2.3.1、创建
- 2.3.2、常用方法
- 2.3.3、转换时区
- 2.3.4、注意事项
- 2.4、DateFormat
- 2.4.1、创建
- 2.4.2、格式化(format)
- 2.4.3、解析(parse)
- 2.4.4、并发安全
- 2.4.4.1、问题复现
- 2.4.4.1、问题原因
- 2.4.4.1、解决方案
- 3、新API
- 3.1、Instant
- 3.1.1、创建
- 3.1.2、获取参数
- 3.1.3、比较
- 3.1.4、运算
- 3.2、Duration
- 3.2.1、创建
- 3.2.2、解析
- 3.2.3、计算
- 3.2.4、转换/取值
- 3.3、Period
- 3.3.1、创建
- 3.3.2、解析
- 3.3.3、计算
- 3.3.4、转换/取值
- 3.4、LocalDate
- 3.4.1、创建
- 3.4.2、计算
- 3.4.3、常用方法
- 3.5、LocalTime
- 3.5.1、创建
- 3.5.2、计算
- 3.5.3、常用方法
- 3.6、LocalDateTime
- 3.6.1、创建
- 3.6.2、计算
- 3.6.3、常用方法
- 3.7、DateTimeFormatter
- 3.7.1、预定义的格式器
- 3.7.2、locale相关的格式器
- 3.7.3、自定义的格式器
- 3.8、ZoneId
- 3.8.1、ZoneRegion
- 3.8.2、ZoneOffset
- 3.9、ZonedDateTime
- 3.9.1、创建
- 3.9.2、计算
- 3.9.3、常用方法
- 3.9.4、夏时令问题
- 3.10、TemporalAdjusters(日期调整器)
- 3.10.1、静态方法
- 3.10.2、自定义调整器
- 4、新/旧API转换
日期与时间
1、概述与相关概念
Java中的日期与时间API主要分为两个部分:
- 旧API:定义在
java.util
包中,包括Date
、Calendar
、TimeZone
等 - 新API:java8引入,定义在
java.time
里,包括LocalDateTime
、ZonedDateTime
、ZoneId
等
1.1、UTC
世界协调时(Universal Time Coordinated的缩写),又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。
UTC 是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成。
UTC时间格式为:YYYY-MM-DDThh
。例如,ssZ
2014-11-11T12:00:00Z
(为北京时间2014年11月11日20点0分0秒)
1.2、GMT
GMT(Greenwich Mean Time), 格林威治平时(也称格林威治时间)。
1884年10月在美国华盛顿召开了一个国际子午线会议,该会议将格林威治子午线设定为本初子午线,并将格林威治平时 (GMT, Greenwich Mean Time) 作为世界时间标准(UT, Universal Time)。由此也确定了全球24小时自然时区的划分,所有时区都以和 GMT 之间的偏移量做为参考。
1972年之前,格林威治时间(GMT)一直是世界时间的标准。1972年之后,GMT 不再是一个时间标准了。
目前UTC与GMT 相差为0.9秒,故二者可以基本视为一致。
我们一般认为GMT和UTC是一样的,都与英国伦敦的本地时相同。
1.3、时区
从格林威治本初子午线起,经度每向东或者向西间隔15°,就划分一个时区,在这个区域内,大家使用同样的标准时间。
但实际上,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。另外:由于目前,国际上并没有一个批准各国更改时区的机构。一些国家会由于特定原因改变自己的时区。
全球共分为24个标准时区,相邻时区的时间相差一个小时。
在不同地区,同一个时区往往会有很多个不同的时区名称,因为名称中通常会包含该国该地区的地理信息。在夏令时期间,当地的时区名称及字母缩写会有所变化(通常会包含“daylight”或“summer”字样)。
例如美国东部标准时间叫:EST,Estern Standard Time;而东部夏令时间叫:EDT,Estern Daylight Time。
1.4、夏时令(DST)
DST(Daylight Saving Time),夏令时又称夏季时间,或者夏时制。
它是为节约能源而人为规定地方时间的制度。一般在天亮早的夏季人为将时间提前一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。
全球约40%的国家在夏季使用夏令时,其他国家则全年只使用标准时间。标准时间在有的国家也因此被相应地称为冬季时间。
在施行夏令时的国家,一年里面有一天只有23小时(夏令时开始那一天),有一天有25小时(夏令时结束那一天),其他时间每天都是24小时。
1.5、UNIX时间戳(timestamp)
Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
UNIX时间戳的 0 按照 ISO 8601 规范为 :1970-01-01T00:00:00Z
一个小时表示为UNIX时间戳格式为:3600秒;一天表示为UNIX时间戳为86400秒,闰秒不计算。
1.6、CST
这个代号缩写,并不是一个统一标准,目前,可以同时代表如下 4 个不同版本的时区概念(要根据上下文语义加以区分):
- China Standard Time 中国标准时区 (UTC+8)
- Cuba Standard Time 古巴标准时区 (UTC-4)
- Central Standard Time (USA) 美国中央时区 (UTC-6)
- Central Standard Time (Australia) 澳大利亚中央时区(UTC+9)
1.7、ISO 8601
国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。
日期表示法:
年由4位数组成,以公历公元1年为0001
年,以公元前1年为0000
年,公元前2年为-0001
年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
日历日期表示法:
年为4位数,月为2位数,月中的日为2位数,例如2004年5月3日可写成2004-05-03
或20040503
。
顺序日期表示法
可以将一年内的天数直接表示,平年365天,闰年366天。如2004年5月3日可以表示为2004-125
或2004125
日历星期和日表示法编辑:
可以用2位数表示年内第几个日历星期,再加上一位数表示日历星期内第几天,但日历星期前要加上一个大写字母W,如2004年5月3日可写成2004-W19-1
或2004W191
。但2005-W011是从2005年1月3日开始的,前几天属于上年的第53个日历星期。每个日历星期从星期一开始,星期日为第7天。
第一个日历星期有以下四种等效说法:
- 本年度第一个星期四所在的星期;
- 1月4日所在的星期;
- 本年度第一个至少有4天在同一星期内的星期;
- 星期一在去年12月29日至今年1月4日以内的星期;
推理可得,如果1月1日是星期一、星期二、星期三或者星期四,它所在的星期就是第一个日历星期;如果1月1日是星期五、星期六或者星期日,它所在的星期就是上一年第52或者53个日历星期;12月28日总是在一年最后一个日历星期。
时间表示法:
小时、分和秒都用2位数表示,对UTC时间最后加一个大写字母Z,其他时区用实际时间加时差表示。如UTC时间下午2点30分5秒表示为14:30:05Z
或143005Z
,当时的北京时间表示为22:30:05+08:00
或223005+0800
,也可以简化成223005+08
。
日期和时间的组合表示法:
合并表示时,要在时间前面加一大写字母T,如要表示北京时间2004年5月3日下午5点30分8秒,可以写成2004-05-03T17:30:08+08:00
或20040503T173008+08
。
时间段表示法:
如果要表示某一作为一段时间的时间期间,前面加一大写字母P,但时间段后都要加上相应的代表时间的大写字母。如在一年三个月五天六小时七分三十秒内,可以写成P1Y3M5DT6H7M30S
。
重复时间表示法:
前面加上一大写字母R,如要从2004年5月6日北京时间下午1点起重复半年零5天3小时,要重复3次,可以表示为R3/20040506T130000+08/P0Y6M5DT3H0M0S
。
2、旧API
2.1、Date
Date类封装了日期、时间和时区,表示特定的时刻,精确度为毫秒。
public class Date
implements java.io.Serializable, Cloneable, Comparable<Date>
根据Datel类的创建时的声明可知Date类是可序列化的,可克隆的,和可以比较的,有固定的比较顺序。
在JDK 1.1之前, Date类还有两个附加功能。 它允许将日期解释为年,月,日,小时,分钟和秒值。 它还允许格式化和解析日期字符串。 不幸的是,这些功能的API不适合国际化。 从JDK 1.1开始, Calendar类应该用于在日期和时间字段之间进行转换,而DateFormat类应该用于格式化和解析日期字符串。 不推荐使用Date中的相应方法。
2.1.1、构造方法
//空参,默认为当前时刻,当前系统时区
Date date1 = new Date();
//long类型,自1970年1月1日00:00:00 GMT以来的毫秒
Date date2 = new Date(System.currentTimeMillis());
2.1.2、比较方法
public boolean before(Date when)
:测试此日期是否在指定日期之前。public boolean after(Date when)
:测试此日期是否在指定日期之后。public int compareTo(Date anotherDate)
:比较两个Date对象的顺序。public boolean equals(Object obj)
:比较两个Date对象的内容即时间毫秒数是否相等。Date date = new Date();
//before
boolean isBefore = date.before(new Date(1l));
System.out.println(isBefore);//false//after
boolean isAfter = date.after(new Date(1l));
System.out.println(isAfter);//true//compareTo
int isCompare = date.compareTo(new Date());
System.out.println(isCompare);//0//equals
boolean isEquals = date.equals(new Date());
System.out.println(isEquals);//true
2.1.3、其它方法
public String toString()
:Date对象的默认格式为 “星期几 月 日 时:分:秒 地区 年份
”,CST 意思是 China Standard Time 中国标准时间,也就是北京时间,东八区。public long getTime()
:返回自此 Date对象表示的1970年1月1日00:00:00 GMT以来的毫秒数.。public void setTime(long time)
:将此 Date对象设置为表示格林威治标准时间1970年1月1日00:00:00之后的 time毫秒的时间点。public Object clone()
:方法返回此对象的副本。public static Date from(Instant instant)
:从 Instant对象获得 Date的实例。 Instant使用的精度为纳秒,而Date使用的精度为毫秒。 转换将截断任何多余的精度信息,就好像以纳秒为单位的整数除以一百万。 Instant可以在未来的时间线上存储积分,并且远大于Date 。 在这种情况下,此方法将引发异常 IllegalArgumentException - 如果瞬间太大而无法表示为 Date。Date date = new Date();
//toString()
System.out.println(date.toString());//Sun Nov 05 15:16:52 CST 2023
//getTime
long time = date.getTime();
System.out.println(time);//1699168882769
//setTime
date.setTime(System.currentTimeMillis());
//clone
Date cloneDate = (Date) date.clone();
System.out.println(cloneDate);//Sun Nov 05 15:21:22 CST 2023
//from
Date d = Date.from(Instant.now());
System.out.println(d);//Sun Nov 05 15:30:26 CST 2023
2.1.4、常见问题和注意事项
- 不能转换时区,除了toGMTString()可以按GMT+0:00输出外,Date总是以当前计算机系统的默认时区为基础进行输出。
- 很难对日期和时间进行加减,计算两个日期相差多少天,计算某个月第一个星期一的日期等。
- 不是线程安全的,如果在多线程环境中使用,需要采取相应的同步措施。
- 月份从0开始,即0表示一月,11表示十二月。
- 不是不可变类,可以通过setTime()这个方法进行赋值,随时被修改。
- Date名字存在误导,严格来讲它表达的是一个时刻的概念,类似于jdk8中的Instant。
2.2、TimeZone
TimeZone用来表示时区偏移量。
abstract public class TimeZone implements Serializable, Cloneable
抽象类,可被序列化和克隆,SimpleTimeZone是其直接子类。
2.2.1、创建
public static Calendar getInstance()
:使用默认时区和语言环境获得一个日历
//当前主机的默认时区
TimeZone tzDefault = TimeZone.getDefault();
//GMT+9:00时区
TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00");
//纽约时区
TimeZone tzNY = TimeZone.getTimeZone("America/New_York");
System.out.println(tzDefault.getID()); // Asia/Shanghai
System.out.println(tzGMT9.getID()); // GMT+09:00
System.out.println(tzNY.getID()); // America/New_York
2.2.2、常用方法
获取TimeZone的基本信息:
getId()
getDisplayName()
getOffset()
TimeZone timeZone = TimeZone.getDefault();
//获取TimeZone对象的时区ID
String id = timeZone.getID();
System.out.println(id);//Asia/Shanghai
//获取TimeZone对象的名称
String name = timeZone.getDisplayName();
System.out.println(name);//中国标准时间
//返回该时区和世界标准时间UTC的时差
int offset = timeZone.getOffset(System.currentTimeMillis());
System.out.println(offset);//28800000public static synchronized String[] getAvailableIDs():获取受支持的所有可用的时区ID
String[] ids = TimeZone.getAvailableIDs();
System.out.println(ids.length);//627
2.3、Calendar
Calendar可以用于获取并设置年、月、日、时、分、秒,它和Date比,主要多了一个可以做简单的日期和时间运算的功能。
Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。
- BuddhistCalendar:佛教日历
- JapaneseImperialCalendar:日本黄历
- GregorianCalendar:公历
2.3.1、创建
Calendar只有一种方式获取,即Calendar.getInstance(),而且一获取到就是当前时间。如果我们想给它设置成特定的一个日期和时间,就必须先清除所有字段:
- public static Calendar getInstance()
- public static Calendar getInstance(TimeZone zone)
- public static Calendar getInstance(Locale aLocale)
public static Calendar getInstance(TimeZone zone,
Locale aLocale)//使用默认的时区和语言创建
Calendar calendar = Calendar.getInstance();
//清除所有字段
calendar.clear();
//设置year
calendar.set(Calendar.YEAR, 2021);
//打印 2021-01-01 00:00:00
System.out.println(new SimpleDateFormat(“yyyy-MM-dd HHss”).format(calendar.getTime()));
2.3.2、常用方法
- public int get(int field):返回给定日历字段的值。
- public void set(int field, int value):将给定的日历字段设置为给定值。
- public abstract void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
- public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象。
Calendar类中提供很多成员常量,代表给定的日历字段:
字段值 | 含义 |
---|---|
YEAR | 年 |
MONTH | 月(从0开始,可以+1使用) |
WEEK_OF_YEAR | 年中的第几周(从1开始) |
WEEK_OF_MONTH | 月中的第几周(从1开始) |
DATE | 等价于DAY_OF_MONTH(从1开始) |
DAY_OF_MONTH | 月中的天(几号) |
DAY_OF_YEAR | 年中的第几天(从1开始) |
DAY_OF_WEEK | 周中的天(周几,周日为1,可以-1使用) |
DAY_OF_WEEK_IN_MONTH | 周中的天(1-7号为第一周,2-8号为第二周…) |
AP_PM | 上午/下午 |
HOUR | 时(12小时制) |
HOUR_OF_DAY | 时(24小时制) |
MINUTE | 分 |
SECOND | 秒 |
MILLISECOND | 毫秒 |
ZONE_OFFSET | 时区偏移 |
DST_OFFSET | 夏时令偏移,毫秒 |
get/set方法:
Calendar calendar = Calendar.getInstance();
//获取年
System.out.println(calendar.get(Calendar.YEAR));//2023
//获取月
System.out.println(calendar.get(Calendar.MONTH) + 1);//11
//获取日
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));//5
//设置年
calendar.set(Calendar.YEAR, 1);
System.out.println(calendar.get(Calendar.YEAR));//1
add方法:
add方法可以对指定日历字段的值进行加减操作,如果第二个参数为正数则加上偏移量,如果为负数则减去偏移量。
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1;
int day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(year + "-" + month + "-" + day);//2023-11-5
//加5天
calendar.add(Calendar.DAY_OF_MONTH, 5);
//减2个月
calendar.add(Calendar.MONTH, -2);
year = calendar.get(Calendar.YEAR);
month = calendar.get(Calendar.MONTH) + 1;
day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(year + "-" + month + "-" + day);//2023-9-10
getTime方法:
Calendar中的getTime方法并不是获取毫秒时刻,而是拿到对应的Date对象。
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
System.out.println(date);//Sun Nov 05 18:08:51 CST 2023
2.3.3、转换时区
将北京时间2020-01-01 20:15:00按纽约时间打印:
// 当前时间:
Calendar c = Calendar.getInstance();
// 清除所有:
c.clear();
// 设置为北京时区:
c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
// 设置年月日时分秒:
c.set(2020,0,1,20,15,0);
// 显示时间:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2020-01-01 20:15:00
System.out.println(sdf.format(c.getTime()));
// 修改 sdf 的时区信息
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
// 2020-01-01 07:15:00
System.out.println(sdf.format(c.getTime()));
2.3.4、注意事项
- 西方星期的开始为周日(1)周一(2),中国开始为周一,因此可以-1使用。
- 在Calendar类中,月份的表示是以0-11代表1-12月(可以+1使用)。
- 日期是有大小关系的,时间靠后,时间越大。
2.4、DateFormat
java.text.DateFormat 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。
- 格式化:按照指定的格式,从Date对象转换为String对象。(format)
- 解析:按照指定的格式,从String对象转换为Date对象。(parse)
2.4.1、创建
由于DateFormat为抽象类,不能直接使用,所以需要常用的子类java.text.SimpleDateFormat。这个类需要一个模式(格式)来指定格式化或解析的标准。构造方法为:
//参数pattern是一个字符串,代表日期时间的自定义格式
public SimpleDateFormat(String pattern)
格式规则:
标识字符 | 含义 |
---|---|
y | 年 |
M | 月 |
d | 日 |
H | 时 |
m | 分 |
s | 秒 |
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
2.4.2、格式化(format)
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String str = format.format(date);
System.out.println(str);//2023-11-05 18:50:56
2.4.3、解析(parse)
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日");
String str = "2021年9月11日";
Date date = format.parse(str);
System.out.println(date);//Sat Sep 11 00:00:00 CST 2021
2.4.4、并发安全
2.4.4.1、问题复现
public class Main {
//定义一个全局的SimpleDateFormat
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//线程池
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), new ThreadPoolExecutor.AbortPolicy());
//定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
//定义一个线程安全的HashSet
Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(() -> {
//时间增加
calendar.add(Calendar.DATE, finalI);
//通过simpleDateFormat把时间转换成字符串
String dateString = simpleDateFormat.format(calendar.getTime());
//把字符串放入Set中
dates.add(dateString);
//countDown
countDownLatch.countDown();
});
}
//阻塞,直到countDown数量为0
countDownLatch.await();
//输出去重后的时间个数
System.out.println(dates.size());//60
}
}
循环一百次,每次循环的时候都在当前时间基础上增加一个天数(这个天数随着循环次数而变化),然后把所有日期放入一个线程安全的、带有去重功能的Set中,然后输出Set中元素个数。
正常情况下,以上代码输出结果应该是100。但是实际执行结果是一个小于100的数字,说明格式化后有重复数据。
2.4.4.1、问题原因
根本原因就是因为SimpleDateFormat作为一个非线程安全的类,被当做了共享变量在多个线程中进行使用,这就出现了线程安全问题。
SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。这其实就是问题的关键。
由于我们在声明SimpleDateFormat的时候,使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。
假设线程1刚刚执行完calendar.setTime把时间设置成2018-11-11,还没等执行完,线程2又执行了calendar.setTime把时间改成了2018-12-12。这时候线程1继续往下执行,拿到的calendar.getTime得到的时间就是线程2改过之后的。
除了format方法以外,SimpleDateFormat的parse方法也有同样的问题。
所以,不要把SimpleDateFormat作为一个共享变量使用。
2.4.4.1、解决方案
使用局部变量:
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(() -> {
// SimpleDateFormat声明成局部变量
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH
ss");
//时间增加
calendar.add(Calendar.DATE, finalI);
//通过simpleDateFormat把时间转换成字符串
String dateString = simpleDateFormat.format(calendar.getTime());
//把字符串放入Set中
dates.add(dateString);
//countDown
countDownLatch.countDown();
});
}
加同步锁:
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(() -> {
//加锁
synchronized (simpleDateFormat) {
//时间增加
calendar.add(Calendar.DATE, finalI);
//通过simpleDateFormat把时间转换成字符串
String dateString = simpleDateFormat.format(calendar.getTime());
//把字符串放入Set中
dates.add(dateString);
//countDown
countDownLatch.countDown();
}
});
}
使用ThreadLocal:
/**
使用ThreadLocal定义一个全局的SimpleDateFormat
*/
private static ThreadLocalsimpleDateFormatThreadLocal = new ThreadLocal () { @Override
protected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH
ss");
}
};
//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());使用DateTimeFormatter:
//解析日期
String dateStr= “2016年10月25日”;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy年MM月dd日”);
LocalDate date= LocalDate.parse(dateStr, formatter);//日期转换为字符串
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern(“yyyy年MM月dd日 hh:mm a”);
String nowStr = now .format(format);
System.out.println(nowStr);
3、新API
3.1、Instant
在Java中,Instant表示时间线上的某个点。这个时间点存在标准的UTC时间,注意这个时间并不是指北京时间或东京时间而是指世界时间。
被称为“新纪元”的时间线原点被设置为穿过伦敦格林尼治皇家天文台的本初子午线所处时区的1970年1月1日的午夜。这与UNX/POSX时间中使用的惯例相同。
从该原点开始,时间按照每天864O0秒向前或向回度量,精确到纳秒。Instant的值往回可追溯10亿年(Instant.MIN)。最大的值Instant.MAX是公元1000000000年的12月31日。
注意: Instant为不可变类
// 获取当前时间 2023-11-05T11:54:29.765Z(比当地时间相差8个小时)
System.out.println(Instant.now());
// 获取系统默认时间戳 2023-11-05T19:54:29.828+08:00[Asia/Shanghai]
System.out.println(Instant.now().atZone(ZoneId.systemDefault()));
在Instant中采用两个字段表示时间戳:
/**
* 该字段表示Instant时间距离原点1970-01-01T00:00:00Z的时间(单位秒)
*/
private final long seconds;
/**
* 该字段表示Instant当前时间的纳秒数这个值不会超过999,999,999,因为1秒=1000_000_000纳秒
*/
private final int nanos;
3.1.1、创建
//获取当前时间点
Instant instant1 = Instant.now();
System.out.println(instant1);//2023-11-05T12:00:32.069Z
//字符串转Instant
Instant instant2 = Instant.parse("2022-09-26T03:46:24.373Z");
System.out.println(instant2);//2022-09-26T03:46:24.373Z
//构建秒级Instant对象,从时间1970-01-01T00:00:00Z开始计算(距离原点5000秒)
Instant instant3 = Instant.ofEpochSecond(5000l);
System.out.println(instant3);//1970-01-01T01:23:20Z
// 构建毫秒级Instant对象,同样从时间1970-01-01T00:00:00Z开始计算(距离原点5000毫秒)
Instant instant4 = Instant.ofEpochMilli(5000);
System.out.println(instant4);//1970-01-01T00:00:05Z
// 构建纳秒级Instant对象,同样从时间1970-01-01T00:00:00Z开始计算
// 参数:epochSecond(秒),nanoAdjustment(纳秒)
Instant instant5 = Instant.ofEpochSecond(5, 1111);
System.out.println(instant5);//1970-01-01T00:00:05.000001111Z
3.1.2、获取参数
Instant instant = Instant.now();
System.out.println(instant);//2023-11-05T12:17:41.012Z
//秒
System.out.println(instant.getEpochSecond());//1699186661
//毫秒
System.out.println(instant.toEpochMilli());//1699186661012
//纳秒
System.out.println(instant.getNano());//12000000
3.1.3、比较
Instant instant1 = Instant.parse("2022-09-26T07:04:19.110Z");
Instant instant2 = Instant.parse("2022-09-26T07:04:19.110Z");
Instant instant3 = Instant.parse("2022-08-26T07:04:19.110Z");
// 相等为0
System.out.println(instant1.compareTo(instant2));
// instant1大于instant3 为1
System.out.println(instant1.compareTo(instant3));
// instant1小于instant3 为-1
System.out.println(instant3.compareTo(instant1));
// true
System.out.println(instant1.isAfter(instant3));
// false
System.out.println(instant1.isBefore(instant3));
3.1.4、运算
- Instant plus(TemporalAmount ammountToAdd):增加时间
- Instant minus(TemporalAmount amountToSubtract):减少时间
Instant(plus|minus)(Nanos|Millis|Seconds)(long number):产生一个时刻,该时刻与当前时刻距离给定数量的纳秒、微秒或秒
Instant instant = Instant.now();
System.out.println(instant);//2023-11-05T12:36:46.762Z
//加1秒
Instant plus1 = instant.plus(Duration.of(1l, ChronoUnit.SECONDS));
System.out.println(plus1);//2023-11-05T12:36:47.762Z//减1毫秒
Instant minus1 = instant.minus(Duration.of(1l, ChronoUnit.MILLIS));
System.out.println(minus1);//2023-11-05T12:36:46.761Z//加2秒
Instant plus2 = instant.plus(2l, ChronoUnit.SECONDS);
System.out.println(plus2);//2023-11-05T12:36:48.762Z//减2毫秒
Instant minus2 = instant.minus(2l,ChronoUnit.MILLIS);
System.out.println(minus2);//2023-11-05T12:36:46.760Z
3.2、Duration
Duration类通过秒和纳秒相结合来描述一个时间间隔量,最高精度是纳秒。时间量可以为正也可以为负,比如1天(86400秒0纳秒)、-1天(-86400秒0纳秒)、1年(31556952秒0纳秒)、1毫秒(0秒1000000纳秒)等。
注意:Duration是不可变的
3.2.1、创建
//通过指定值和单位创建
Duration d1 = Duration.of(2l, ChronoUnit.SECONDS);
//通过时间间隔量创建
Duration d2 = Duration.from(Period.ofDays(1));
//通过两个时间点创建
Instant instant1 = Instant.now();
Instant instant2 = instant1.plus(1l, ChronoUnit.SECONDS);
Duration d3 = Duration.between(instant1, instant2);
//以天为单位指定
Duration d4 = Duration.ofDays(1l);
//以小时为单元指定
Duration d5 = Duration.ofHours(1l);
//以分钟为单位指定
Duration d6 = Duration.ofMinutes(1L);
//以秒为单位指定
Duration d7 = Duration.ofSeconds(1l);
//以毫秒为单位指定
Duration d8 = Duration.ofMillis(1l);
//以纳秒为单位指定
Duration d9 = Duration.ofNanos(1l);
3.2.2、解析
通过解析字符串创建Duration对象。
使用方法:
Duration fromChar1 = Duration.parse("P1DT1H10M10.5S");
Duration fromChar2 = Duration.parse("PT10M");
格式说明:
采用ISO-8601时间格式。格式为:
PnYnMnDTnHnMnS
n
为个数- “
P
”, “D
”, “H
”, “M
” 和 “S
“可以是大写或者小写(建议大写) - 可以用“
-
”表示负数 P
:开始标记Y
:年M
:月D
:天T
:日期和时间的分割标记H
:小时M
:分钟S
:秒“PT20.345S” — parses as “20.345 seconds”
“PT15M” — parses as “15 minutes” (where a minute is 60 seconds)
“PT10H” — parses as “10 hours” (where an hour is 3600 seconds)
“P2D” — parses as “2 days” (where a day is 24 hours or 86400 seconds)
“P2DT3H4M” — parses as “2 days, 3 hours and 4 minutes”
“P-6H3M” — parses as “-6 hours and +3 minutes”
“-P6H3M” — parses as “-6 hours and -3 minutes”
“-P-6H+3M” — parses as “+6 hours and -3 minutes”
3.2.3、计算
- Duration minus(Duration duration)
- Duration minus(long amountToSubtract, TemporalUnit unit)
- Duration minusX():X表示days, hours, millis, minutes, nanos 或 seconds
- Duration plus(Duration duration)
- Duration plus(long amountToAdd, TemporalUnit unit)
Duration plusX():X表示days, hours, millis, minutes, nanos 或 seconds
Duration duration = Duration.ofHours(2);
Duration newDuration = duration.plusSeconds(33);Duration duration = Duration.ofHours(2);
Duration newDuration = duration.plus(33, ChronoUnit.SECONDS);
3.2.4、转换/取值
可以用toX来转换为其他单位,支持:toDays, toHours, toMinutes, toMillis, toNanos
Duration duration = Duration.ofHours(2);
duration.toDays(); // 0
duration.toHours(); // 2
duration.toMinutes(); // 120
duration.toMillis(); // 7200000
duration.toNanos(); // 7200000000000
可以用getX来获得指定位置的值,因为Duration是由秒和纳秒组成,所以只能获得秒和纳秒:
Duration duration = Duration.ofHours(2);
duration.getSeconds(); //7200
duration.getNano(); //
3.3、Period
Period类通过年、月、日相结合来描述一个时间间隔量,最高精度是天。时间量可以为正也可以为负,例如2年(2年0个月0天)、3个月(0年3个月0天)、4天(0年0月4天)等。
注意:Period是不可变的
3.3.1、创建
//通过分别指定年、月、日来创建
Period p1 = Period.of(1, 1, 1);
//通过时间间隔量创建
Period d2 = Period.from(Period.ofDays(2));
//通过两个本地时间创建
Period d3 =Period.between(LocalDate.of(2021, 1, 1), LocalDate.of(2022, 1, 1));
//以年为单位指定
Period d4 = Period.ofYears(2);
//以月为单位指定
Period d5 = Period.ofMonths(1);
//以天为单位指定
Period d6 = Period.ofDays(4);
//以周为单位指定
Period d7 = Period.ofWeeks(3);
3.3.2、解析
String str = "P4Y2M";
Period period = Period.parse(str);
"P2Y" -- Period.ofYears(2)
"P3M" -- Period.ofMonths(3)
"P4W" -- Period.ofWeeks(4)
"P5D" -- Period.ofDays(5)
"P1Y2M3D" -- Period.of(1, 2, 3)
"P1Y2M3W4D" -- Period.of(1, 2, 25)
"P-1Y2M" -- Period.of(-1, 2, 0)
"-P1Y2M" -- Period.of(-1, -2, 0)
3.3.3、计算
加/减:
- Period plus(TemporalAmount amountToAdd)
- Period plusX():X可以是Years、Months和Days
- Period minus(TemporalAmount amountToSubtract)
- Period minusX():X可以是Years、Months和Days
乘: 年、月、日每部分都乘
Period multipliedBy(int scalar)
Period period = Period.ofMonths(2);
period.plusDays(2);
period.minusYears(0);period.multipliedBy(2);
Temporal addTo(Temporal temporal) :向指定的时间对象增加此时间间隔量
Temporal subtractFrom(Temporal temporal):向指定的时间对象减少此时间间隔量
Period period = Period.ofDays(2);
LocalDate localDate1 = (LocalDate) period.addTo(LocalDate.of(2000, 2, 1));
System.out.println(localDate1);//2000-02-03LocalDate localDate2 = (LocalDate) period.subtractFrom(LocalDate.of(2000, 2, 1));
System.out.println(localDate2);//2000-01-30
3.3.4、转换/取值
Period period = Period.ofYears(2);
System.out.println(period.toTotalMonths());//24
System.out.println(period.getDays());//0
System.out.println(period.getMonths());//0
System.out.println(period.getYears());//2
3.4、LocalDate
LocalDate表示本地日期,与时区信息没有任何关联。
不可变类,线程安全
3.4.1、创建
//指定年月日创建
LocalDate localDate1 = LocalDate.of(2000, 1, 1);
//默认严格按照ISO 8601规定的日期格式打印
System.out.println(localDate1);//2000-01-01
//从一个本地时间中创建
LocalDate localDate2 = LocalDate.from(LocalDate.of(2000, 1, 1));
System.out.println(localDate2);//2000-01-01
//默认创建当前日期
LocalDate localDate3 = LocalDate.now();
System.out.println(localDate3);//2023-11-05
//指定距离1970.1.1日的天数创建
LocalDate localDate4 = LocalDate.ofEpochDay(1);
System.out.println(localDate4);//1970-01-02
//解析字符串创建,格式为ISO 8601
LocalDate localDate5 = LocalDate.parse("2030-03-11");
System.out.println(localDate5);//2030-03-11
3.4.2、计算
- LocalDate(plus|minus)(Days|Weeks|Months|Years)(long number):产生一个LocalDate,该对象是通过在当前对象上加上或减去给定数量的时间单位获得的
- LocalDate plus(TemporalAmount amountToAdd)
LocalDate minus(TemporalAmount amountToSubtract)
LocalDate localDate = LocalDate.now();
System.out.println(localDate);//2023-11-05//加2天
LocalDate plus1 = localDate.plus(2, ChronoUnit.DAYS);
System.out.println(plus1);//2023-11-07//减2个月
LocalDate munus1 = localDate.minusMonths(2);
System.out.println(munus1);//2023-09-05
注意:LocalDate 新增或减少API可能糊i创建出并不存在的日期。例如,在1月31日上加上1个月不应该产生2月31日。这些方法并不会抛出异常,而是会返回该月有效的最后一天。
3.4.3、常用方法
- LocalDate withDayOfMonth(int DayOfMonth)
- LocalDate withDayOfYear(int dayOfYear)
- LocalDate withMonth(int month)
- LocalDate withYear(int year):返回一个新的LocalDate,将月份日期、年日期、月或年修改为定值。
- int getDayOfMonth():获取月份日期(1到31之间)
- int getDayOfYear():获取年日期
- DayOfWeek getDayOfWeek():获取星期日期,返回某个DayOfWeek枚举值。
- Month getMonth()
- **int getMonthValue():**获取用Month枚举值表示的月份,或者用1到12之间的数字表示的月份。
- int getYear()
- Period until(ChronoLocalDate endDateExclusive):获取直到给定终止日期的period。LocalDate和date类针对非公历实现了ChronoLocalDate接口。
- boolean isBefore(ChronoLocalDate other)
- boolean isAfter(ChronoLocalDate other):如果该日期在给定日期之前或之后,则返回true。
- boolean idLeapYear():如果当前是闰年,则返回true。即,该年份能够被4整除,但是不能被100整除,或者能够被400整除。该算法应该可以应用于所有已经过去的年份,尽管在历史上它并不准确(闰年是在公元前46年发明出来的,而涉及整除100和400的规则是在1582年的公历改革中引入的。这场改革经历了300年才被广泛接受)。
- Stream
dateUntil(LocalDate endExclsive) - Stream
dateUntil(LocalDate endExclusive, Period step) :产生一个日期流,从当前的LocalDate对象直至参数endExcludice指定的日期,其中步长尺寸为1,或者是给定的period。
3.5、LocalTime
LocalTime表示当日的时刻。
不可变类,线程安全
3.5.1、创建
//默认创建当前时间
LocalTime localTime1 = LocalTime.now();
//默认严格按照ISO 8601规定的时间格式打印
System.out.println(localTime1);//23:03:46.030
//从另一个LocalTime中创建
LocalTime localTime2 = LocalTime.from(LocalTime.of(2, 2));
System.out.println(localTime2);//02:02
//指定小时、分钟
LocalTime localTime3 = LocalTime.of(2, 2);
System.out.println(localTime3);//02:02
//指定小时、分钟、秒
LocalTime localTime4 = LocalTime.of(2, 2, 2);
System.out.println(localTime4);//02:02:02
//指定小时、分钟、秒、毫秒
LocalTime localTime5 = LocalTime.of(2, 2, 2, 2);
System.out.println(localTime5);//02:02:02.000000002
//使用纳秒数创建
LocalTime localTime6 = LocalTime.ofNanoOfDay(2);
System.out.println(localTime6);//00:00:00.000000002
//使用秒数创建
LocalTime localTime7 = LocalTime.ofSecondOfDay(2);
System.out.println(localTime7);//00:00:02
//解析字符串创建,格式为ISO 8601
LocalTime localTime8 = LocalTime.parse("04:11:23.3243242");
System.out.println(localTime8);//04:11:23.324324200
3.5.2、计算
- LocalTime (plus|minus)(Hours|Minutes|Seconds|Nanos)(long number)
- LocalTime plus(TemporalAmount amountToAdd)
LocalTime minus(TemporalAmount amountToSubtract)
LocalTime localTime = LocalTime.now();
System.out.println(localTime);//23:11:09.077
//加2分钟
LocalTime plus = localTime.plusMinutes(2);
System.out.println(plus);//23:13:09.077
//减3小时
LocalTime minus = localTime.minus(3, ChronoUnit.HOURS);
System.out.println(minus);//20:11:09.077
3.5.3、常用方法
- LocalTime with(Hour|Minute|Second|Nano)(int value):返回一个新的LocalTime,将小时,分钟、秒或纳秒修改为给定值。
- int getHour():获取小时(0-23)
- int getMinute():获取分钟(0-59)
- int getSecond():获取秒(0-59)
- int getNano():获取纳秒(0-999,999,999)
- Int toSecondOfDay()
- long toNanoOfDay():产生自午夜到当前LocalTime的秒或纳秒 数
- boolean isBefore(LocalTime other)
- boolean isAfter(LocalTime other):如果当期日期在给定日期之前或之后,则返回true。
3.6、LocalDateTime
LocalDate与LocalTime的集合体。
不可变类,线程安全
3.6.1、创建
//默认为当前时间
LocalDateTime localDateTime1 = LocalDateTime.now();
//默认严格按照ISO 8601规定的日期和时间格式打印
System.out.println(localDateTime1);//2023-11-05T23:29:52.599
//通过另一个LocalDateTime创建
LocalDateTime localDateTime2 = LocalDateTime.from(LocalDateTime.of(LocalDate.now(), LocalTime.now()));
System.out.println(localDateTime2);//2023-11-05T23:29:52.599
//通过LocalDate和LocalTime创建
LocalDateTime localDateTime3 = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println(localDateTime3);//2023-11-05T23:29:52.600
//通过指定各部分参数创建
LocalDateTime localDateTime4 = LocalDateTime.of(2000, 1, 1, 1, 1, 1, 1);
System.out.println(localDateTime4);//2000-01-01T01:01:01.000000001
//通过自1970-01-01T00:00:00Z的秒数、纳秒数和ZoneOffset创建
LocalDateTime localDateTime5 = LocalDateTime.ofEpochSecond(1, 1, ZoneOffset.UTC);
System.out.println(localDateTime5);//1970-01-01T00:00:01.000000001
//通过Instant和ZoneId创建
LocalDateTime localDateTime6 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
System.out.println(localDateTime6);//2023-11-05T23:29:52.600
3.6.2、计算
参考LocalDate和LocalTime的计算API。
3.6.3、常用方法
对日期和时间进行调整则使用withXxx()
方法,例如:withHour(15)
会把10:11:12
变为15:11:12
:
- 调整年:withYear()
- 调整月:withMonth()
- 调整日:withDayOfMonth()
- 调整时:withHour()
- 调整分:withMinute()
- 调整秒:withSecond()
判断两个LocalDateTime的先后:
LocalDate d_2020_7_1 = LocalDate.of(2020, 7, 1);
LocalDate d_2020_8_1 = LocalDate.of(2020, 8, 1);
boolean b1 = d_2020_7_1.isBefore(d_2020_8_1);//true
boolean b2 = d_2020_7_1.isAfter(d_2020_8_1);//false
boolean b3 = d_2020_7_1.isEqual(d_2020_8_1);//false
LocalDateTime 转 LocalDate, LocalTime:
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间
3.7、DateTimeFormatter
DateTimeFormatter类提供了三种用于打印日期/时间值的格式器:
- 预定义的格式器
- locale相关的格式器
- 带有定制模式的格式器
3.7.1、预定义的格式器
LocalDateTime和ZonedDateTime均可格式化。
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);//2023-11-05T23:44:49.804
String str = DateTimeFormatter.ISO_LOCAL_DATE.format(localDateTime);
System.out.println(str);//2023-11-05
TemporalAccessor parse = DateTimeFormatter.ISO_LOCAL_DATE.parse(str);
LocalDate localDate = LocalDate.from(parse);
System.out.println(localDate);//2023-11-05
3.7.2、locale相关的格式器
只能格式化ZonedDateTime。
下面三种静态方法都可以创建这种格式器:
- ofLocalizedDate
- ofLocalizedTime
ofLocalizedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL);
String str = formatter.format(zonedDateTime);
System.out.println(str);//2023年11月6日 星期一 上午12时08分34秒 CST
TemporalAccessor parse = formatter.parse(str);
ZonedDateTime zonedDateTime1 = ZonedDateTime.from(parse);
System.out.println(zonedDateTime1);//2023-11-06T00:08:34-06:00[America/Chicago]
这些方法使用了默认的locale。为了切换到不同的locale,可以直接使用withLocale
方法:
ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL);
String str = formatter.withLocale(Locale.ENGLISH).format(zonedDateTime);
System.out.println(str);//Monday, November 6, 2023 12:14:18 AM CST
TemporalAccessor parse = formatter.withLocale(Locale.ENGLISH).parse(str);
ZonedDateTime zonedDateTime1 = ZonedDateTime.from(parse);
System.out.println(zonedDateTime1);//2023-11-06T00:14:18-06:00[America/Chicago]
3.7.3、自定义的格式器
常用的日期/时间的格式化符号:
// 自定义输出格式:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String s1 = dtf.format(LocalDateTime.now());//2022/01/12 08:48:10
// 用自定义格式解析:
LocalDateTime dt = LocalDateTime.parse("2020/01/02 15:16:17", dtf);
System.out.println(dt);//2020-01-02T15:16:17
ZonedDateTime zdt = ZonedDateTime.now();
//默认地区
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm");
System.out.println(dtf1.format(zdt));//2022 一月 12 星期三 15:23
//中国地区
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
System.out.println(dtf2.format(zdt));//2022 一月 12 星期三 15:23
//美国地区
DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.US);
System.out.println(dtf3.format(zdt));//2022 Jan 12 Wed 15:23
3.8、ZoneId
ZoneId用来表示一个时区,目前可用的时区有近600。它是一个抽象类,有两个子类:
- ZoneOffset:通过时差创建的
- ZoneRegion:通过区域创建的
为什么要定义ZoneRegion和ZoneOffset两个类来表示时区呢?这是因为人们可以通过两种方式来定义时区。
//所有可用的ZoneId
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set.size());//599
//通过时区编码获取
ZoneId zoneId1 = ZoneId.of("Europe/Berlin");
ZoneId zoneId2 = ZoneId.of("Brazil/East");
System.out.println(zoneId1.getRules());//ZoneRules[currentStandardOffset=+01:00]
System.out.println(zoneId2.getRules());//ZoneRules[currentStandardOffset=-03:00]
//获取系统默认
System.out.println(ZoneId.systemDefault());//Asia/Shanghai
3.8.1、ZoneRegion
通过城市或地区来定义时区。世界上有很多城市和地区,这些城市或地区都处在某个特定的时区当中,因此如果指定了一个城市或地区,就能确定它所在的时区。ZoneRegion就是代表城市或地区的类,当一个ZoneRegion类对象被创建后,ZoneRegion类对象所代表的那个城市或地区的时区也会被确定,所以说一个ZoneRegion类对象就代表一个时区。
ZoneId zid1 = ZoneId.systemDefault();
ZoneId zid2 = ZoneId.of("Europe/Paris");
System.out.println("zid1的类型:"+zid1.getClass().getName());
System.out.println("zid1的值:"+zid1);
System.out.println("zid1的时区信息:"+zid1.getRules());
System.out.println("zid2的类型:"+zid2.getClass().getName());
System.out.println("zid2的值:"+zid2);
System.out.println("zid2的时区信息:"+zid2.getRules());
zid1的类型:java.time.ZoneRegion
zid1的值:Asia/Shanghai
zid1的时区信息:ZoneRules[currentStandardOffset=+08:00]
zid2的类型:java.time.ZoneRegion
zid2的值:Europe/Paris
zid2的时区信息:ZoneRules[currentStandardOffset=+01:00]
3.8.2、ZoneOffset
通过指定时差来创建创建时区。全球共分为24个时区,人们把英国格林尼治时间天文台所在的那个时区称为“零时区”或“中时区”,其他各个时区与零时区都有时差。因此只需要指出与零时区的时差,也能确定一个时区。ZoneOffset就是代表时差的类,但必须说明:ZoneOffset类对象所代表的时差并不是任意两个地区的时差,而是特指与零时区的时差。ZoneOffset类对象中包含了与零时区的时差数值,所以ZoneOffset类对象也能代表一个时区。
ZoneId zid1 = ZoneId.of("+08:00");
ZoneId zid2 = ZoneId.of("+8");
ZoneId zid3 = ZoneId.of("+080000");
ZoneId zid4 = ZoneId.of("+08:30:00");
ZoneId zid5 = ZoneId.of("UTC+08:00:00");
ZoneId zid6 = ZoneId.of("GMT+08:00:00");
System.out.println("zid5的时区信息:"+zid5.getRules());
System.out.println("zid6的时区信息:"+zid6.getRules());
zid5的时区信息:ZoneRules[currentStandardOffset=+08:00]
zid6的时区信息:ZoneRules[currentStandardOffset=+08:00]
3.9、ZonedDateTime
ZonedDateTime类是Java 8中日期时间功能里,用于表示带时区的日期与时间信息的类。
注意:ZonedDateTime为不可变类,线程安全
3.9.1、创建
//默认当前系统时区、时间
ZonedDateTime z1 = ZonedDateTime.now();
System.out.println(z1);
//指定时区的当前时间
ZonedDateTime z2 = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println(z2);
//从另一个ZonedDateTime
ZonedDateTime z3 = ZonedDateTime.from(ZonedDateTime.now());
System.out.println(z3);
//通过本地时间和时区创建
ZonedDateTime z4 = ZonedDateTime.of(LocalDateTime.now(), ZoneId.systemDefault());
ZonedDateTime z5 = ZonedDateTime.of(LocalDate.now(), LocalTime.now(), ZoneId.systemDefault());
System.out.println(z4);
System.out.println(z5);
//指定各部分参数和时区创建
ZonedDateTime z6 = ZonedDateTime.of(2000, 1, 1, 1, 1, 1, 1, ZoneId.systemDefault());
System.out.println(z6);
//通过Instant和时区创建
ZonedDateTime z7 = ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
System.out.println(z7);
//解析字符串创建
ZonedDateTime z8 = ZonedDateTime.parse("2030-03-11T01:59:33.417+08:00");
System.out.println(z8);
System.out.println(z8.getZone());
3.9.2、计算
- ZonedDateTime (plus|minus) (Days|Weeks|Months|Years|Hours|Minutes|Seconds|Nanos) (long number)
- ZonedDateTime plus(TemporalAmount amountToAdd)
ZonedDateTime minus(TemporalAmount amountToSubtract)
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);//2023-11-06T02:16:05.692+08:00[Asia/Shanghai]//加1小时
ZonedDateTime plus = zonedDateTime.plusHours(2l);
System.out.println(plus);//2023-11-06T04:16:05.692+08:00[Asia/Shanghai]//减2天
ZonedDateTime minus = zonedDateTime.minus(2l, ChronoUnit.DAYS);
System.out.println(minus);//2023-11-04T02:16:05.692+08:00[Asia/Shanghai]
3.9.3、常用方法
- ZonedDateTime with(DayOfMonth|Month|Year|Hour|Minute|Second|Nano)(int value):返回一个新的ZonedDateTIme,用给定的值替换给定的时间单位
- ZonedDateTime withZoneSameInstant(ZoneId zone)
- ZonedDateTime withZoneSameLocal(ZoneId zone):返回一个新的ZonedDateTime,位于给定的时区。
- int getDayOfMonth():获取月份日期(1-31)
- int getDayOfYear():获取年份(1-366)
- DayOfWeek getDayOfWeek():获取星期日期,返回枚举值。
- Month getMonth():获取月份枚举值
- int getMonthValue():获取月份日期(1-12)
- int getYear():获取年份(-999,999,999到999,999,999)
- int getHour():获取分钟(0-59)
- int getSecond():获取秒(0-59)
- int getNano():获取纳秒
- public ZoneOffset getOffset():获取与UTC的时间差距,会随着夏时令变化。
- LocalDate toLocalDate()
- LocalTime toLocalTime()
- LocalDateTime toLocalDateTime()
- Instant toInstant():生成本地日期、时间、或日期时间或时刻。
- boolean isBefore(CHronoZonedDateTime other):该时间在给定时间之前,则返回true
- boolean isAgter(ChronoZonedDateTime other):该时间在给定时间之后,则返回true
3.9.4、夏时令问题
当夏令时开始时,时钟要向前拨快一小时。当你构建的时间对象正好落入了这跳过去的一个小时内时,会发生什么?
例如,在2013年,中欧地区在3月31日2:00切换到夏令时,如果你试图构建的时间是不存在的3月31日2:30,那么你实际上得到的是3:30。
ZonedDateTime skipped = ZonedDateTime.of(
LocalDate.of(2013, 3, 31),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
);
System.out.println(skipped);//2013-03-31T03:30+02:00[Europe/Berlin]
反过来,当夏令时结束时,时钟要向回拨慢一小时,这样同一个本地时间就会有出现两次。当你构建位于这个时间段内的时间对象时,就会得到这两个时刻中较早的一个:
ZonedDateTime ambiguous = ZonedDateTime.of(
LocalDate.of(2013, 10, 27),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
);
System.out.println(ambiguous);//2013-10-27T02:30+02:00[Europe/Berlin]
ZonedDateTime anHourLater = ambiguous.plusHours(1);
System.out.println(anHourLater);//2013-10-27T02:30+01:00[Europe/Berlin]
3.10、TemporalAdjusters(日期调整器)
对于日程安排应用来说,经常需要计算诸如“每个月的第一个星期二”这样的日期。
TemporalAdjusters类提供了大量用于常见调整的静态方法。
你可以将调整方法的结果传递给wth方法。例如,某个月的第一个星期二可以像下面这样计算:
LocalDate firstTuesday = LocalDate.of(2000, 10, 1)
.with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
System.out.println(firstTuesday);//2000-10-03
3.10.1、静态方法
- static TemporalAdjuster next(DayOfWeek dayOfWeek):向后调整为给定的星期日期
- static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek):向后或原地调整为给定的星期日期
- static TemporalAdjuster previous(DayOfWeek dayOfWeek):向前调整为给定的星期日期
- static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek):向前或原地调整为给定的星期日期
- static TemporalAdjuster dayOfWeekInMonth(int n, DayOfWeek dayOfWeek):将日期调整为月份中的第n个给定的星期日期
- static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek):将日期调整为月份中的最后一个给定的星期日期
- static TemporalAdjuster firstDayOfMonth()
- static TemporalAdjuster firstDayOfNextMonth()
- static TemporalAdjuster firstDayOfYear()
- static TemporalAdjuster firstDayOfNextYear()
- static TemporalAdjuster lastDayOfMonth()
- static TemporalAdjuster lastDayOfYear():用于将日期调整为月份或年份中的给递归的日期
3.10.2、自定义调整器
实现TemporalAdjuster接口:
//下一个工作日
TemporalAdjuster NEXT_WORKDAY = new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate result = (LocalDate)temporal;
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
}
};
LocalDate localDate = LocalDate.now();
LocalDate next = localDate.with(NEXT_WORKDAY);
System.out.println(next);
4、新/旧API转换
类 | 新->旧 | 旧->新 |
---|---|---|
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.Date | Date.valueOf(localDate) | date.toLocalDate() |
LocalTime <->java.sql.Time | Time.valueOf(localTIme) | time.toLocalTime() |
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() |
还没有评论,来说两句吧...