1、概述:
Java日期的API一直为世人所诟病,简陋的API以及非线程安全等问题,使得开发非常不便,知道后来Java 8的推出才改善这一问题。Java8除了加入了Lambda表达式和Stream操作等重大特性,另外还针对日期时间的操作,在Joda-Time等优秀工具包的基础上引入了一套全新的API,使得操作时间日期非常方便。
在许多编程语言中,一般都采用Unix时间体系来表示时间,这个时间是绝对公立的,它和时区没有任何关系。Unix时间使用自1970-01-01T00:00:00Z(Z即为0时间的标志)至今的毫秒差表示时间的数值,并且移除期间的“闰秒”(例如 1998-12-31T23:59:59:60Z)。Unix时间体系中,每天固定86400秒。
在Java中获取当前时间的方法是System.currentTimeMillis(),表示的是当前时刻至1970-01-01 00:00:00.000的毫秒差值
Java还提供了一个更加精确的时间:System.nanoTime(),获取一个时间精确到纳秒,但是它是由JVM提供的一个时间,主要用来精确衡量两个时间段之间的时间,如计算一段代码的执行时间:
long startTime = System.nanoTime();
long endTime = System.nanoTime();
endTime - startTime ;
2、时区
- GMT:格林尼治时间,格林尼治标准时间的正午是指太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。
- UTC:统一协调时间,其以源自时秒长为基础,在时刻上尽量接近格林尼治标准时间,标准UTC时间格式yyyy-MM-yy'T'HH:mm:ss.SSSXXX
由于地球每天的自传是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用,UTC是最主要的世界时间标准。
时区作为地理概念,表示“遵守统一时间标准的一个地区”。整个地球分为24个时区,东部和西部各12个,每个时区都有自己的本地时间,时区的基准点是伦敦(基准UTC),往东,会领先UTC,往西会落后UTC。如北京属东八区(UTC+08:00),那么我们的是加你会领先基准--也就是我们的早上九点时,敦伦是早上1点。
3、旧的时间API
旧的Java日期API也就是Java8之前的时间API之烂是公认的,一方面在于它设计分工不明确,往往一个类既能和处理日期又能处理时间,很混乱;另一方面,在某些年月日期的数值映射存储违反人们的正常认知,如0对应月份一月,11对应月份十二月等。
比如现在我要输出 2018 年的 1 月 1 日:
Date date = new Date(2018,1,1);
System.out.println(date);
看看输入的结果是否和你预期的一样:
Fri Feb 01 00:00:00 CST 3918
年份是 3918 年,这是啥?原来这里存在一个起始年份 1900,实际年份是要在你的年份参数上加上个起始年份。
然后旧的日期 API 中 Date 有 java.util.Date 和 java.sql.Date,两者的关系是 java.sql.Date 继承 java.util.Date。为了把前者转成后者,需要使用如下代码:
Date date = new Date();
java.sql.Date d = new java.sql.Date(date.getTime());
java.sql.Date 不支持 Date 参数的构造器,只好传入 long 类型的时间,接着尝试把当前小时数取出来:
System.out.println(d.getHours());
结果悲剧了:
Exception in thread "main" java.lang.IllegalArgumentException
at java.sql.Date.getHours(Date.java:177)
java.sql.Date 是 SQL 中的单纯的日期类型,不会有时分秒,这就是为什么把 java.sql.Date 通过 JDBC 插入数据库,会发现时分秒都没有了。因此如果你同时需要日期和时间,你应该使用 Timestamp,它也是 java.util.Date 的子类,Timestamp 则包含时间戳的完整信息。
在旧的日期 API 中,Data 这个类扮演着诸多角色,它提供了操作时刻的方法,提供了操作年月日日期的方法,甚至还有关时区的方法。可以说日期时间相关操作它都可以管。但是管的东西多,自然就不能面面俱到。现在的 Date 类中大部分方法都已废弃,被标记为 @Deprecated
。
现在一般使用旧的 API 时,Date 只负责存储一个绝对时间,并对 Calender 和 DateFormat 提供操作接口。Calendar 可以获取 Date 中的特定信息,比如这个时间是该年的第几个星期,此外,还可以进行日期时间的增减以及时间先后的比较。SimpleDateFormat 主要做一些格式化的输入输出。
这样承载日期时间信息、日期之间的转换,不同日期格式的显示,以及分析日期字符串这三项职责明确分开了。
总的来说,Date、Calendar 和 DateFormat 已经能够处理一般的时间日期问题了。但是不可避免的是,它们依然很繁琐,不好用并且这些日期类都是可变且线程不安全的。
4、使用Joda-Time
从上述可以看出 Java 8 之前的时间 API 确实用着不方便,我们再来看下面的代码:
Calendar calendar = Calendar.getInstance();
calendar.set(2018, Calendar.JANUARY, 1, 11, 11, 11);
System.out.println(calendar.getTime());
使用 Calendar 的静态方法 getInstance 可以获取 Calendar 的一个当前时刻的实例,而 getInstance 方法并未提供一个指定年月日和时分秒的重载方法。每次要指定特定的日期时间,必须先获取一个表示当前时间的 Calendar 实例,再去设值。
输出结果:
Sun Jan 01 11:11:11 CST 2018
注意前面说过给 Date 设置年份需要减去 1900。与 Date 不一样,Calendar 年份的设置并不需要减去 1900(月份的定义和 Date 还是一样),这种不一致绝对让有着强迫症的程序员们不适!
出现问题,就有人站出来解决问题。由于上述种种问题,社区就出现了一些第三方的日期处理框架,例如 Joda-Time、DATA4J 等开源项目,而 Joda-Time 是最为广泛使用的。
使用 Joda-Time 操作日期时间十分方便,获取 Calendar 对象和设置时间完全可以合成一步完成:
DateTime dateTime = new DateTime(2018, 1, 1, 11, 11, 11, 0);
DateTime 是 Joda-Time 中主要的日期时间操作类,注意这里一月份可以传 1 来表示了。
再如,如果要给上述时间增加 3 天再按格式输出的话,使用 Joda 也是十分便捷:
dateTime.plusDays(3).toString("yyyy/MM/dd HH:mm:ss");
//减去3天
dateTime.minusDays(3).toString("yyyy/MM/dd HH:mm:ss");
Joda-Time 分别提供了有关天、周、月和年的增减(plus/minus 打头的)方法。
再举个例子。现在假设我希望输出这样一个日期:
距离 2018 年 1 月 1 日 45 天之后的某天在下一个月的当前周的最后一天的日期。
坦白说,使用旧的 API 实现很繁琐且容易出错:
Calendar calendar = Calendar.getInstance();
calendar.set(2018, Calendar.JANUARY, 1);
calendar.add(Calendar.DAY_OF_MONTH, 45);
calendar.add(Calendar.MONTH, 1);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
// 获得入参日期相对于本周最后一天的偏移量(在国外,星期日是一周的第一天)
// 若入参日期是周日,直接就是本周最后一天
int nextMondayOffset = dayOfWeek == 0 ? 0: 8 -dayOfWeek;
// 增加到本周最后一天
calendar.add(Calendar.DAY_OF_MONTH, nextMondayOffset);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
System.out.println(simpleDateFormat.format(calendar.getTime()));
看看如何使用 Joda-Time 处理:
DateTime dateTime = new DateTime(2018, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek()
.withMaximumValue().toString("yyyy/MM/dd HH:mm:ss.SSS"));
使用 Joda-Time 计算两日期相差的天数:
//joda-time
LocalDate start=new LocalDate(2018, 11,14);
LocalDate end=new LocalDate(2018, 11, 15);
int days = Days.daysBetween(start, end).getDays();
//旧API处理方式
Calendar start = Calendar.getInstance();
start.set(2018, Calendar.NOVEMBER, 14);
Calendar end = Calendar.getInstance();
end.set(2018, Calendar.NOVEMBER, 15);
long startTim = start.getTimeInMillis();
long endTim = end.getTimeInMillis();
long diff = endTim-startTim;
int days=(int) (diff/1000 / 3600 / 24);
使用旧的 API 处理看起来很 low,处理也很麻烦,而使用 Joda-Time 就显得非常优雅。
通过上面的例子,相信你应该感受到了 Joda-Time 的强大,Joda-Time 提供了一组 Java 类包用于处理包括 ISO8601 标准在内的 date 和 time,令时间和日期值变得易于管理、操作和理解。Joda-Time 的主要设计目标就易于使用,而且其支持多种日历系统。
同时 Joda 与 JDK 是百分之百可互操作的,因此对于旧的日期 API 操作,只需要替换执行日期时间计算的那部分即可。
Date date = dateTime.toDate();
Calendar calendar = dateTime.toCalendar(Locale.CHINESE);
//通过jdk时间对象构造
Date date = new Date();
DateTime dateTime = new DateTime(date);
Calendar calendar = Calendar.getInstance();
dateTime = new DateTime(calendar);
另外,开发中经常用到时间与字符串直接地转换,Joda-Time 也可以非常方便地实现。
public static String convertStringUTC(Date javaDate)
{
DateTime dateTime = new DateTime(javaDate,DateTimeZone.UTC);
return dateTime.toString();
}
public static Date convertDateFromUTC(String date)
{
DateTime dateTime = new DateTime().parse(date, DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
return dateTime.toDate();
}
5、Java 8 时间日期API
自从 Java 8 发布以后,古老的 java.util.Date 不再是我们 Java 里操作日期时间的唯一选择。Java 8 借鉴第三方开源库 Joda-Time 的优秀设计,重新设计了一套新的日期时间 API,这套新的日期 API 是 JSR-310 规范的实现,相比之前,可以说好用百倍。
每位 Java 开发人员都至少应该了解这套新的 API 中的几个主要核心类:
- LocalDate:表示不带时间的日期
- LocalTime:表示不带日期的时间
- LocalDateTime:日期和时间类
- ZoneId:时区
- ZonedDateTime:一个带时区的完整时间
- Instant:Unix 时间,它代表的是时间戳,比如
2018-01-14T02:20:13.592Z
- Clock:获取某个时区下当前的瞬时时间,日期或者时间
- Duration:表示一个绝对的精确跨度,使用毫秒为单位
- Period:这个类表示与 Duration 相同的概念,但是以人们比较熟悉的单位表示,比如年、月、周
- DateTimeFormatter:格式化输出
- TemporalAdjusters:获得指定日期时间等,如当月的第一天、今年的最后一天等
在几乎所有的类中,方法都被明确定义用以完成相同的行为。例如,获取当前实例我们可以使用 now() 方法,在所有的类中都定义了 format() 和 parse() 方法。一旦你使用了其中某个类的方法,对于使用其他类也是十分容易上手。
5.1、下面看看这些类具体如何使用。
LocalDate、LocalTime、LocalDateTime
LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); //等价于 today.plusWeeks(1)
LocalDate date = LocalDate.of(2018,11,16);
LocalTime time = LocalTime.of(12,23,20);
LocalDateTime dateTime = LocalDateTime.of(date,time);
System.out.println(dateTime);
//LocalDate结合LocalTime成一个LocalDateTime
LocalDateTime dateTime2 = date.atTime(time);
System.out.println(dateTime2); //2018-11-16T12:23:20
格式化与解析时间对象 DateTimeFormatter
格式器用于解析日期字符串和格式化日期输出,创建格式器最简单的方法是通过 DateTimeFormatter 的静态工厂方法以及常量。创建格式器一般有如下三种方式:
- 常用 ISO 格式常量,如 ISO_LOCAL_DATE
- 字母模式,如 ofPattern("yyyy/MM/dd")
- 本地化样式,如 ofLocalizedDate(FormatStyle.MEDIUM)
和旧的 java.util.DateFormat 相比较,所有的 DateTimeFormatter 实例都是线程安全的。
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDateTime = localDateTime.format(formatter);
System.out.println(formatDateTime);
//DateTimeFormatter提供了一些默认的格式化器,DateTimeFormatter.ISO_LOCAL_DATE_TIME 格式 yyyy-MM-ddTHH:mm:ss.SSS
String dateTime2 = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println(dateTime2);
LocalDate localDate = LocalDate.parse("2018/11/11",DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println(localDate); //2018-11-11
Duration 与 Period
Duration 表示一个时间段,Duration 包含两部分:seconds 表示秒,nanos 表示纳秒,它们的组合表达了时间长度。
因为 Duration 表示时间段,所以 Duration 类中不包含 now() 静态方法。注意,Duration 不包含毫秒这个属性。
LocalDateTime from = LocalDateTime.of(2018, Month.JANUARY, 5, 10, 7, 0); // 2018-01-05 10:07:00
LocalDateTime to = LocalDateTime.of(2018, Month.FEBRUARY, 5, 10, 7, 0); // 2018-02-05 10:07:00
Duration duration = Duration.between(from, to); // 表示从 2018-01-05 10:07:00 到 2018-02-05 10:07:00 这段时间
Duration d = Duration.ofSeconds(6000);
System.out.println("6000秒相当于" + d.toMinutes() + "分");
System.out.println("6000秒相当于" + d.toHours() + "小时");
System.out.println("6000秒相当于" + d.toDays() + "天");
Period 在概念上和 Duration 类似,区别在于 Period 是以年月日来衡量一个时间段。Duration 用于计算两个时间间隔,Period 用于计算两个日期间隔,所以 between() 方法只能接收 LocalDate 类型的参数。
LocalDate start = LocalDate.of(2018, Month.JANUARY, 1);
LocalDate end = LocalDate.of(2020, Month.NOVEMBER, 11);
System.out.println("相隔月数:"+Period.between(start, end).getMonths());
System.out.println("相隔天数:"+Period.between(start, end).getDays());
这里我们观察下输出,会发现结果根本就不是我们想要的。
其实这里需要注意一点:Period 得到的是差值的绝对值(对应年月日直接计算数学上的差值),而并不表示真正的区间距离。
那么我如何计算两个时间的区间距离呢?API 提供了简便的方法:
long distanceMonth = start.until(end, ChronoUnit.MONTHS);
long distanceDay= start.until(end, ChronoUnit.DAYS);
System.out.println(distanceMonth);
System.out.println(distanceDay);
Instant 与 Clock
Instant 表示时间线上的一点(与 Date 类似),而不需要任何上下文信息,例如时区。概念上讲,它只是简单地表示自 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC)开始的秒数。
Instant 由两部分组成,一是从原点开始到指定时间点的秒数 s, 二是距离该秒数 s 的纳秒数。它以 Unix 时间戳的形式存储日期时间,不提供处理人类意义上的时间单位(年月日等)。
//第一个参数是秒,第二个是纳秒参数,纳秒的存储范围是0至999,999,999
//2s之后的在加上100万纳秒(1s)
Instant instant = Instant.ofEpochSecond(2,1000000000);
System.out.println(instant3); //1970-01-01T00:00:03Z
Instant instant1 = Instant.now();
System.out.println(instant); //1970-01-01T00:00:00Z
Instant instant2 = Instant.parse("2018-11-11T10:12:35.342Z");
System.out.println(instant2); //2018-11-11T10:12:35.342Z
// 在instant3的基础上添加5小时4分钟
Instant instant3 = instant2.plus(Duration.ofHours(5).plusMinutes(4));
System.out.println(instant3); //2018-11-11T15:16:35.342Z
//java.util.Date与Instant可相互转换
Instant timestamp = new Date().toInstant();
Date.from(Instant.now());
Clock 是时钟系统,用于查找当前时刻。你可以用它来获取某个时区下当前的日期或者时间。可以用 Clock 来替代旧的 System.currentTimeInMillis() 与 TimeZone.getDefault() 方法。
//系统默认时间
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.instant().toString());
//世界协调时UTC
Clock clock = Clock.systemUTC();
//通过Clock获取当前时刻
System.out.println("当前时刻为:" + clock.instant());
//获取clock对应的毫秒数,与System.currentTimeMillis()输出相同
System.out.println(clock.millis());
System.out.println(System.currentTimeMillis());
//在clock基础上增加6000秒,返回新的Clock
Clock clock2 = Clock.offset(clock, Duration.ofSeconds(6000));
//纽约时间
Clock clock = Clock.system(ZoneId.of("America/New_York"));
System.out.println("Current DateTime with NewYork clock: " + LocalDateTime.now(clock));
System.out.println(clock.millis());
ZoneId 和 ZonedDateTime
Java 8 不仅将日期和时间进行了分离,同时还有时区。Java 使用 ZoneId 来标识不同的时区。
时区的常见情况,是从基准 UTC 开始的一个固定偏移。ZoneId 的子类 ZoneOffset,代表了这种从伦敦格林威治零度子午线开始的时间偏移,也就是时差;而 ZonedDateTime 代表的是带时区的时间。ZonedDateTime 类似于 Java 8 以前的 GregorianCalendar 类,你可以通过本地时间或时间点来创建 ZoneDateTime。
// 所有可用的zoneid
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
//所有合法的“区域/城市”字符串
zoneIds.forEach(System.out::println);
ZoneId china = ZoneId.of("Asia/Shanghai");
ZonedDateTime dateAndTimeInChina = ZonedDateTime.of(LocalDateTime.now(), china);
System.out.println("特定时区下的日期和时间 : " + dateAndTimeInChina);
ZoneId america = ZoneId.of("America/New_York");
System.out.println(ZonedDateTime.now(america));
//GregorianCalendar与ZonedDateTime相互转换
ZonedDateTime zonedDateTime = new GregorianCalendar().toZonedDateTime();
GregorianCalendar.from(zonedDateTime);
使用 TemporalAdjuster 类灵活操纵日期
前面看到的所有日期操作都是相对比较直接的。有的时候,你需要进行一些更加灵活复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,就需要时间修改器 TemporalAdjuster,可以更加灵活地处理日期。TemporalAdjusters 工具提供了一些通用的功能,并且你还可以新增你自己的功能。
// TemporalAdjuster是一个函数式接口
TemporalAdjuster firstDayOfMonth = (temporal) -> temporal.with(DAY_OF_MONTH, 1);
System.out.println(localDate.plusMonths(1).with(firstDayOfMonth));
// 等价于下面语句
System.out.println(localDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth()));
// 计算下一个工作日的日期
TemporalAdjuster nextWorkDay = TemporalAdjusters.ofDateAdjuster(
tdate->{
DayOfWeek work = tdate.getDayOfWeek();
int addDays=0;
if (work.equals(DayOfWeek.FRIDAY)) {
addDays=3;
}else if(work.equals(DayOfWeek.SATURDAY)){
addDays=2;
}else {
addDays=1;
}
return tdate.plusDays(addDays);
}
);
LocalDate localDate1 = LocalDate.now().with(nextWorkDay);
System.out.println(localDate1);
java.util.Date 与 LocalDate、LocalTime、LocalDateTime 转换
有时候对于老的遗留项目我们需要将 java.util.Date 转换为新的日期 API。将 Date 转换为 LocalDate、LocalTime、LocalDateTime 可以借助于 ZonedDateTime 和 Instant。
Date date = new Date();
System.out.println("current date: " + date);
// Date -> LocalDateTime
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("localDateTime by Instant: " + localDateTime);
// Date -> LocalDate
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
System.out.println("localDate by Instant: " + localDate);
// Date -> LocalTime
LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
System.out.println("localTime by Instant: " + localTime);
//Date -> LocalDateTime 另一种方式
localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("localDateTime by ofInstant: " + localDateTime);
//Calendar --> Instant
Calendar.getInstance().toInstant();
Java 8 在 Date 类中引入了 2 个方法,from 和 toInstant,我们可以借助 from 方法来实现 LocalDateTime 到 Date 的转换。
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime: " + localDateTime);
// LocalDateTime -> Date
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDateTime -> current date: " + date);
// LocalDate -> Date
LocalDate localDate = LocalDate.now();
date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDate -> current date: " + date);
新的时间与日期 API 中很重要的一点是,它定义清楚了基本的时间与日期的概念,比方说日期、时间、瞬时时间、持续时间、时区及时间段。它们都是基于 ISO8601 日历系统,它是世界民用历法,也就是我们所说的公历。
新的 API 区分各种日期时间概念并且各个概念使用相似的方法定义模式,这种相似性非常有利于 API 的学习。总结一下一般的方法或者方法前缀:
- of:静态工厂方法,用于创建实例
- now:静态工厂方法,用当前时间创建实例
- parse:静态工厂方法,从字符串解析得到对象实例
- get:获取时间日期对象的部分状态。
- is:检查某些东西的是否是 true,例如比较时间前后
- with:返回一个部分状态改变了的时间日期对象拷贝
- plus:返回一个时间增加了的、时间日期对象拷贝
- minus:返回一个时间减少了的、时间日期对象拷贝
- to:转换到另一个类型
- at:把这个对象与另一个对象组合起来,例如 date.atTime(time)
- format:提供格式化时间日期对象的能力
最后再次声明,Java 8 中新的时间与日期 API 中的所有类都是不可变且线程安全的,任何修改操作都会返回一个新的实例,而之前 java.util.Date、Calendar 以及 SimpleDateFormat 这些关键的类都不是线程安全的。
6、时间日期 API 中的设计模式
- 工厂模式:now()、of() 等工厂方法直接生成日期或者日期时间。
- 策略模式:LocalDate/LocalTime/LocalDateTime/ZonedDateTime,针对日期、时间、日期和时间、带时区的日期时间,使用具体的时间日期类处理。策略模式在设计一整套东西时,对开发者特别友好。
前面也提到,所有新的日期时间 API 类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分。一旦你使用了其中某个类的方法,那么非常容易上手其他类的使用。
- 构建者模式:Java 8 开始在 Calendar 中加入了构建者类,可以按如下方式生成新的 Calendar 对象。
Calendar cal = new Calendar.Builder().setCalendarType("iso8601")
.setWeekDate(2018, 1,MONDAY).build();
这里设计模式与标准的教科书式的设计模式可能有所区别,所以我们在使用设计模式时也应灵活处理,不是一成不变的。
7、使用示例
例一
SimpleDateFormat 在多线程下的异常:
public class TimeFormatTest {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
// 请求总数
public static int clientTotal = 100;
// 同时并发执行的线程数
public static int threadTotal = 10;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
for (int i = 0; i< clientTotal; i ++) {
executorService.execute(()->{
try {
semaphore.acquire();
try {
simpleDateFormat.parse("20180208");
} catch (ParseException e) {
e.printStackTrace();
}
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
执行后会产生如下异常:
java.lang.NumberFormatException: multiple points
在 Java 8 之前可以使用线程本地变量解决这个问题,然后在 Java 8 中不存在这个问题:
public class TimeFormatTest {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
// 请求总数
public static int clientTotal = 100;
// 同时并发执行的线程数
public static int threadTotal = 10;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
LocalDate date = LocalDate.parse("20180208", DateTimeFormatter.ofPattern("yyyyMMdd"));
System.out.println(date);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
例二
有时候业务中我们需要处理时间日期的重复性,例如某人每年的某一天都是他的生日。MonthDay 这个类由月日组合,不包含年信息,也就是说你可以用它来代表每年重复出现的一些日子。当然也有一些别的组合,比如说 YearMonth 类。它和新的时间日期库中的其它类一样也都是不可变且线程安全的。
// 是否是生日
MonthDay monthDay = MonthDay.parse("--05-18");
MonthDay monthDay1 = MonthDay.from(LocalDate.now());
System.out.println(monthDay.getMonthValue());
if (monthDay.equals(monthDay1))
{
System.out.println("今天是生日");
}else {
System.out.println("今天不是生日");
}
//判断是否闰年
YearMonth yearMonth = YearMonth.from(LocalDate.now());
yearMonth.isLeapYear();
//信用卡过期,只有年月信息
YearMonth creditCardExpiry = YearMonth.of(2018, Month.FEBRUARY);
System.out.printf("信用卡过期时间:", creditCardExpiry);
例三
特定日期计算:
LocalDate localDate = LocalDate.now();
//计算下一个月的第一天的日期
localDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
LocalDateTime dateTime = LocalDateTime.now();
//计算一年前的第二个月的最后一天的日期
dateTime = dateTime.minusYears(1) // 一年前
.withMonth(2) // 设置为2月
.with(TemporalAdjusters.lastDayOfMonth()); // 一个月中的最后一天
//当月最后一个满足是星期四的日期
LocalDate date = localDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
//当前日期的前一个星期天,如果当天就是星期天
LocalDate date11 = localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));