Java8之 LocalDate,LocalDateTime和处理时间、日期工具类一网打尽

1、java 支持日期格式化

import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;public static final String DATE_TIME_FORMATTER_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DATE_FORMATTER_PATTERN = "yyyy-MM-dd";
public static final String TIME_FORMATTER_PATTERN = "HH:mm:ss";
public static final SimpleDateFormat S_DATE_TIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static final SimpleDateFormat S_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
public static final SimpleDateFormat S_TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

2、获取今天日期
Java 8 中的 LocalDate 用于表示当天日期。和 java.util.Date 不同,它只有日期,不包含时间。

LocalDate now = LocalDate.now();

控制台结果输出:

2022-12-25

3、获取年、月、日信息
LocalDate 类提供了获取年、月、日的快捷方法,其实例还包含很多其它的日期属性。通过调用这些方法就可以很方便的得到需要的日期信息,不用像以前一样需要依赖 java.util.Calendar 类了。

LocalDate now = LocalDate.now();
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();
System.out.printf("year = %d, month = %d, day = %d", year, month, day);

控制台结果输出:

year = 2022, month = 12, day = 25
4、设置特定日期
在第二个例子中,我们通过静态工厂方法 now() 非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法LocalDate.of() 创建任意日期, 该方法需要传入年、月、日做参数,返回对应的 LocalDate 实例。

LocalDate date = LocalDate.of(2022, 12, 24);
System.out.println(date);

控制台结果输出:

2022-12-24
5、判断两个日期是否相等
LocalDate 重载了 equal 方法:

LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2022, 12, 25);
LocalDate dateAgo= LocalDate.of(2022, 12, 24);
if (date.equals(now)) {System.out.println("是同一天");
}
if (dateAgo.equals(now)) {System.out.println("不是同一天");
}

6、检查像信用卡还款这种周期性事件
Java 中另一个日期时间的处理就是检查类似每月信用卡还款日期、老婆生日、房贷缴款日或保险缴费日这些周期性事件。如果你在信用卡部门工作,那么一定会有一个模块用来在客户每个还款日期向客户发送提示还款信息。

Java 中如何检查这些节日或其它周期性事件呢?答案就是 MonthDay 类。这个类组合了月份和日,去掉了年,这意味着你可以用它判断每年都会发生事件。和这个类相似的还有一个 YearMonth 类。这些类也都是不可变并且线程安全的值类型。下面我们通过 MonthDay 来检查周期性事件:

LocalDate now = LocalDate.now();
MonthDay repaymentDate= MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(now);
if (currentMonthDay.equals(dateOfRepayment )) {System.out.println("today is repayment day");
} else {System.out.println("Sorry, today is not repayment day");
}

7、获取当前时间
LocalTime 类提供了获取时、分、秒的快捷方法,可以调用静态工厂方法 now() 来获取当前时间。默认的格式是 hh:mm:ss:nnn。

LocalTime localTime = LocalTime.now();
System.out.println(localTime);

控制台输出:

13:35:56.155
8、在现有的时间上增加小时
增加小时、分、秒来计算将来的时间很常见。Java 8日期和时间类型是:不可变类型和线程安全.

通过plusHours() 方法实现增加小时功能。温馨提示:这些方法返回一个全新的 LocalTime 实例,由于其不可变性,返回后一定要用变量赋值。

LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalTime localTime1 = localTime.plusHours(2);//增加2小时
System.out.println(localTime1);

9、计算一周后的日期
LocalDate 日期不包含时间信息,它的 plus()方法用来增加天、周、月,ChronoUnit 类声明了这些时间单位。由于 LocalDate 也是不变类型,返回后一定要用变量赋值。

LocalDate now = LocalDate.now();
LocalDate plusDate = now.plus(1, ChronoUnit.WEEKS);
System.out.println(now);
System.out.println(plusDate);

10、计算一年前或一年后的日期
利用 LocalDate.minus() 方法计算一年前的日期。

LocalDate now = LocalDate.now();
LocalDate minusDate = now.minus(1, ChronoUnit.YEARS);
LocalDate plusDate1 = now.plus(1, ChronoUnit.YEARS);
System.out.println(minusDate);
System.out.println(plusDate1);

11、使用 Java 8 的 Clock 时钟类
Java 8 增加了一个 Clock 时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。以前用到System.currentTimeInMillis() 和 TimeZone.getDefault() 的地方都可用 Clock 替换。

Clock clock = Clock.systemUTC();
Clock clock1 = Clock.systemDefaultZone();
System.out.println(clock);
System.out.println(clock1);

12、如何用 Java 判断日期是早于还是晚于另一个日期
LocalDate 类有两类方法 isBefore() 和 isAfter() 用于比较日期。调用 isBefore() 方法时,如果给定日期小于当前日期则返回 true。

LocalDate tomorrow = LocalDate.of(2018,6,20);
if(tomorrow.isAfter(now)){System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = now.minus(1, ChronoUnit.DAYS);
if(yesterday.isBefore(now)){System.out.println("Yesterday is day before today");
}

13、如何表示信用卡到期这类固定日期
YearMonth 是另一个组合类,用于表示信用卡到期日、FD 到期日、期货期权到期日等。还可以用这个类得到 当月共有多少天,YearMonth 实例的 lengthOfMonth() 方法可以返回当月的天数,在判断 2 月有 28 天还是 29 天时非常有用。

YearMonth currentYearMonth = YearMonth.now();
System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());YearMonth mortgageDueDate = YearMonth.of(2022, Month.JANUARY);
System.out.printf("Your Mortgage due date on %s %n", mortgageDueDate);

14、在 Java 8 中判断闰年
LocalDate 类有一个很实用的方法 isLeapYear() 判断该实例是否是一个闰年。

15、计算两个日期之间的天数和月数
常见日期操作是计算两个日期之间的天数、周数或月数。在 Java 8 中可以用 java.time.Period 类来做计算。下面这个例子中,我们计算了当天和将来某一天之间的月数。

LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2023, Month.MARCH, 20);
Period period = Period.between(now, date);
System.out.println("离下个时间还有" + period.getMonths() + " 个月");

控制台输出:

离下个时间还有2 个月
16、包含时差信息的日期和时间
在 Java 8 中,ZoneOffset 类用来表示时区,举例来说印度与 GMT 或 UTC 标准时区相差 +05:30,可以通过ZoneOffset.of() 静态方法来获取对应的时区。一旦得到了时差就可以通过传入 LocalDateTime 和 ZoneOffset 来创建一个 OffSetDateTime 对象。

LocalDateTime datetime = LocalDateTime.now();
ZoneOffset offset = ZoneOffset.of("+05:30");
OffsetDateTime date = OffsetDateTime.of(datetime, offset);
System.out.println("Date and Time with timezone offset in Java : " + date);

17、在 Java 8 中获取当前的时间戳
Instant 类有一个静态工厂方法 now() 会返回当前的时间戳

Instant timestamp = Instant.now();
System.out.println(timestamp);

18、Java 8 自定义格式化工具解析日期
通过调用 DateTimeFormatter 的 ofPattern() 静态方法并传入任意格式返回其实例,格式中的字符和以前代表的一样,M 代表月,m 代表分。如果格式不规范会抛出 DateTimeParseException 异常,不过如果只是把 M 写成 m 这种逻辑错误是不会抛异常的。

19、在 Java 8 中如何把日期转换成字符串
下面的例子将返回一个代表日期的格式化字符串。和前面类似,还是需要创建 DateTimeFormatter 实例并传入格式,但这回调用的是 format() 方法,而非 parse() 方法。这个方法会把传入的日期转化成指定格式的字符串。

LocalDateTime arrivalDate  = LocalDateTime.now();
try {DateTimeFormatter format = DateTimeFormatter.ofPattern("MMMdd yyyy  hh:mm a");String landing = arrivalDate.format(format);System.out.printf("Arriving at :  %s %n", landing);
}catch (DateTimeException ex) {System.out.printf("%s can't be formatted!%n", arrivalDate);ex.printStackTrace();
}

20、Java 8 日期时间API 总结
提供了 javax.time.ZoneId 获取时区。
提供了 LocalDate 和 LocalTime 类。
Java 8 的所有日期和时间 API 都是不可变类并且线程安全,而现有的 Date 和 Calendar API 中的 java.util.Date 和SimpleDateFormat 是非线程安全的。
主包是 java.time, 包含了表示日期、时间、时间间隔的一些类。里面有两个子包 java.time.format 用于格式化, java.time.temporal 用于更底层的操作。
时区代表了地球上某个区域内普遍使用的标准时间。每个时区都有一个代号,格式通常由区域/城市构成(Asia/Tokyo),在加上与格林威治或 UTC 的时差。例如:东京的时差是 +09:00。
OffsetDateTime 类实际上组合了 LocalDateTime 类和 ZoneOffset 类。用来表示包含和格林威治或 UTC 时差的完整日期(年、月、日)和时间(时、分、秒、纳秒)信息。
DateTimeFormatter 类用来格式化和解析时间。与 SimpleDateFormat 不同,这个类不可变并且线程安全,需要时可以给静态常量赋值。 DateTimeFormatter 类提供了大量的内置格式化工具,同时也允许你自定义。在转换方面也提供了 parse() 将字符串解析成日期,如果解析出错会抛出 DateTimeParseException。DateTimeFormatter 类同时还有format() 用来格式化日期,如果出错会抛出 DateTimeException异常。
21、Java8 日期常规操作总结
LocalDateTime与字符串互转/Date互转/LocalDate互转/指定日期/时间比较

字符串互转

DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime time = LocalDateTime.now();
String localTime = df.format(time);
LocalDateTime ldt = LocalDateTime.parse("2022-12-30 10:12:05",df);
System.out.println("LocalDateTime转成String类型的时间:"+localTime);
System.out.println("String类型的时间转成LocalDateTime:"+ldt);
DateLocalDateTime
java.util.Date date = new java.util.Date();
Instant instant = date.toInstant();
ZoneId zone = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
LocalDateTimeDate
LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
java.util.Date date = Date.from(instant);
LocalDateTimeLocalDate互转
LocalDateTime now = LocalDateTime.now();
LocalDate localDate = now.toLocalDate();
LocalDateDate互转
LocalDate localDate = LocalDate.now();
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
java.util.Date date = Date.from(instant);
时间调整
LocalDateTime now = LocalDateTime.now();
//明天
LocalDateTime plusDays = now.plusDays(1);
//昨天
LocalDateTime plusDays2 = now.plusDays(-1);
//还有时分等同理
时间间隔
使用Duration进行 day,hour,minute,second等的计算
使用Period进行Year,Month的计算
Duration duration = Duration.between(localDateTime,localDateTime4);
duration.toDays();
duration.toHours();
duration.toMinutes();
Period period2 = Period.between(localDateTime.toLocalDate(),localDateTime4.toLocalDate());
period2.getYears();
period2.getMonths();
period2.toTotalMonths();
判断是否是今天或昨天
String time = "2022-12-30 11:20:45";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localTime = LocalDateTime.parse(time, dtf);
LocalDateTime startTime = LocalDate.now().atTime(0, 0, 0);
LocalDateTime endTime = LocalDate.now().atTime(23, 59, 59);
LocalDateTime startYesterday = LocalDate.now().plusDays(-1).atTime(0, 0, 0);
LocalDateTime endYesterday = LocalDate.now().plusDays(-1).atTime(23, 59, 59);//如果小于昨天的开始日期
if (localTime.isBefore(startYesterday)) {System.out.println("时间是过去");
}
//时间是昨天
if (localTime.isAfter(startYesterday) && localTime.isBefore(endYesterday)) {System.out.println("时间是昨天");
}
//如果大于今天的开始日期,小于今天的结束日期
if (localTime.isAfter(startTime) && localTime.isBefore(endTime)) {System.out.println("时间是今天");
}
//如果大于今天的结束日期
if (localTime.isAfter(endTime)) {System.out.println("时间是未来");
}
}

获取当天的开始和结束时间

private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {//获取当前时间LocalDateTime nowTime = LocalDateTime.now();//获取当前日期LocalDate nowDate = LocalDate.now();//设置零点LocalDateTime beginTime = LocalDateTime.of(nowDate,LocalTime.MIN);//将时间进行格式化String time1= beginTime.format(dtf);//设置当天的结束时间LocalDateTime endTime = LocalDateTime.of(nowDate,LocalTime.MAX);//将时间进行格式化String time2 =dtf.format(endTime);System.out.println("今天开始的时间beginTime:"+time1);System.out.println("今天结束的时间endTime:"+time2);
}

控制台输出: 今天开始的时间beginTime:2022-12-30 00:00:00
今天结束的时间endTime: 2022-12-30 23:59:59
本周开始、结束时间

//本周开始时间
TemporalAdjuster FIRST_OF_WEEK =TemporalAdjusters.ofDateAdjuster(localDate -> localDate.minusDays(localDate.getDayOfWeek().getValue()- DayOfWeek.MONDAY.getValue()));
String weekStart = df.format(inputDate.with(FIRST_OF_WEEK));//本周结束时间
TemporalAdjuster LAST_OF_WEEK =TemporalAdjusters.ofDateAdjuster(localDate -> localDate.plusDays(DayOfWeek.SUNDAY.getValue() - localDate.getDayOfWeek().getValue()));
String weekEnd = df.format(inputDate.with(LAST_OF_WEEK));
本月第一天、最后一天
//TODO 本月的第一天
String  monthStart = df.format(LocalDate.of(inputDate.getYear(),inputDate.getMonth(),1));
//TODO 本月的最后一天
String monthEnd = df.format(inputDate.with(TemporalAdjusters.lastDayOfMonth()));
LocalDateTime 生成指定时间段的随机时间/*** 取范围日期的随机日期时间,不含边界* @param startDay* @param endDay* @return*/
public static LocalDateTime randomLocalDateTime(int startDay, int endDay) {int plusMinus = 1;if (startDay < 0 && endDay > 0) {plusMinus = Math.random() > 0.5 ? 1 : -1;if (plusMinus > 0) {startDay = 0;} else {endDay = Math.abs(startDay);startDay = 0;}} else if (startDay < 0 && endDay < 0) {plusMinus = -1;//两个数交换startDay = startDay + endDay;endDay = startDay - endDay;startDay = startDay - endDay;//取绝对值startDay = Math.abs(startDay);endDay = Math.abs(endDay);}//指定时间LocalDate day = LocalDate.now().plusDays(plusMinus * RandomUtils.nextInt(startDay, endDay));int hour = RandomUtils.nextInt(1, 24);int minute = RandomUtils.nextInt(0, 60);int second = RandomUtils.nextInt(0, 60);LocalTime time = LocalTime.of(hour, minute, second);return LocalDateTime.of(day, time);
}
方法调用://生成指定时间段的随机LocalDateTime时间
randomLocalDateTime(-3, 3).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime ldt = randomLocalDateTime(-7, 1);
String odMapStartTime = ldt.format(dtf);
String odMapEndTime = ldt.plusMinutes(r.nextInt(5)).format(dtf);

22、Java 日期常规操作踩坑集
用Calendar设置时间的坑
反例:

Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR, 10);
System.out.println(c.getTime());

运行结果:

Thu Mar 30 22:28:05 GMT+08:00 2022
解析:

我们设置了10小时,但运行结果是22点,而不是10点。因为Calendar.HOUR默认是按12小时制处理的,需要使用Calendar.HOUR_OF_DAY,因为它才是按24小时处理的。

正例:

Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 10);
Java日期格式化YYYY的坑
反例:Calendar calendar = Calendar.getInstance();
calendar.set(2022, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2022-12-31 转 YYYY-MM-dd 格式后 " + dtf.format(testDate));
运行结果:2022-12-31YYYY-MM-dd 格式后 2023-12-31

解析:

为什么明明是2022年12月31号,就转了一下格式,就变成了2023年12月31号了?因为YYYY是基于周来计算年的,它指向当天所在周属于的年份,一周从周日开始算起,周六结束,只要本周跨年,那么这一周就算下一年的了。正确姿势是使用yyyy格式。

正例:

Calendar calendar = Calendar.getInstance();
calendar.set(2022, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("2022-12-31 转 yyyy-MM-dd 格式后 " + dtf.format(testDate));
Java日期格式化hh的坑
反例:String str = "2022-12-30 12:00";
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd hh:mm");
Date newDate = dtf.parse(str);
System.out.println(newDate);
运行结果:Wed Mar 30 00:00:00 GMT+08:00 2022

解析:

设置的时间是12点,为什么运行结果是0点呢?因为hh是12制的日期格式,当时间为12点,会处理为0点。正确姿势是使用HH,它才是24小时制。

Calendar获取的月份比实际数字少1即(0-11)
反例:

//获取当前月,当前是12月
Calendar calendar = Calendar.getInstance();
System.out.println("当前"+calendar.get(Calendar.MONTH)+"月份");
运行结果:当前11月份

解析:

The first month of the year in the Gregorian and Julian calendars
is JANUARY which is 0;
也就是1月对应的是下标 0,依次类推。因此获取正确月份需要加 1.

正例:

//获取当前月,当前是12月
Calendar calendar = Calendar.getInstance();
System.out.println("当前"+(calendar.get(Calendar.MONTH)+1)+"月份");

Java日期格式化DD的坑
反例:

Calendar calendar = Calendar.getInstance();
calendar.set(2022, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-DD");
System.out.println("2022-12-31 转 yyyy-MM-DD 格式后 " + dtf.format(testDate));

运行结果:

2022-12-31 转 yyyy-MM-DD 格式后 2022-12-365
解析:

DD和dd表示的不一样,DD表示的是一年中的第几天,而dd表示的是一月中的第几天,所以应该用的是dd。

SimleDateFormat的format初始化问题
反例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.format(20221231));
运行结果:1970-01-01

解析:

用format格式化日期是,要输入的是一个Date类型的日期,而不是一个整型或者字符串。

日期本地化问题
反例:

String dateStr = "Wed Mar 18 10:00:00 2020";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy");
LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
System.out.println(dateTime);

运行结果:

Exception in thread “main” java.time.format.DateTimeParseException: Text ‘Wed Mar 18 10:00:00 2020’ could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)
解析:

DateTimeFormatter 这个类默认进行本地化设置,如果默认是中文,解析英文字符串就会报异常。可以传入一个本地化参数(Locale.US)解决这个问题

正例:

String dateStr = "Wed Mar 18 10:00:00 2020";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy",Locale.US);
LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
System.out.println(dateTime);

SimpleDateFormat 解析的时间精度问题
反例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String time = "2022-12";
System.out.println(sdf.parse(time));

运行结果:

Exception in thread "main" java.text.ParseException: Unparseable date: "2022-12"at java.text.DateFormat.parse(DateFormat.java:366)at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)

解析:

SimpleDateFormat 可以解析长于/等于它定义的时间精度,但是不能解析小于它定义的时间精度。

SimpleDateFormat 的线性安全问题
反例:

public class SimpleDateFormatTest {private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));while (true) {threadPoolExecutor.execute(() -> {String dateString = sdf.format(new Date());try {Date parseDate = sdf.parse(dateString);String dateString2 = sdf.format(parseDate);System.out.println(dateString.equals(dateString2));} catch (ParseException e) {e.printStackTrace();}});}}

运行结果:

Exception in thread “pool-1-thread-49” java.lang.NumberFormatException: For input string: “5151.”
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.example.demo.SimpleDateFormatTest.lambda$main 0 ( S i m p l e D a t e F o r m a t T e s t . j a v a : 19 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r ( T h r e a d P o o l E x e c u t o r . j a v a : 1149 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r 0(SimpleDateFormatTest.java:19) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor 0(SimpleDateFormatTest.java:19)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)atjava.util.concurrent.ThreadPoolExecutorWorker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread “pool-1-thread-47” java.lang.NumberFormatException: For input string: “5151.”
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.example.demo.SimpleDateFormatTest.lambda$main 0 ( S i m p l e D a t e F o r m a t T e s t . j a v a : 19 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r ( T h r e a d P o o l E x e c u t o r . j a v a : 1149 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r 0(SimpleDateFormatTest.java:19) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor 0(SimpleDateFormatTest.java:19)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)atjava.util.concurrent.ThreadPoolExecutorWorker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
解析: 全局变量的SimpleDateFormat,在并发情况下,存在安全性问题。

SimpleDateFormat继承了 DateFormat
DateFormat类中维护了一个全局的Calendar变量
sdf.parse(dateStr)和sdf.format(date),都是由Calendar引用来储存的。
如果SimpleDateFormat是static全局共享的,Calendar引用也会被共享。
又因为Calendar内部并没有线程安全机制,所以全局共享的SimpleDateFormat不是线性安全的。
解决SimpleDateFormat线性不安全问题,有三种方式:

将SimpleDateFormat定义为局部变量
使用ThreadLocal。
方法加同步锁synchronized。
正例:

public class SimpleDateFormatTest {private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();public static DateFormat getDateFormat() {DateFormat df = threadLocal.get();if(df == null){df = new SimpleDateFormat(DATE_FORMAT);threadLocal.set(df);}return df;}public static String formatDate(Date date) throws ParseException {return getDateFormat().format(date);}public static Date parse(String strDate) throws ParseException {return getDateFormat().parse(strDate);}public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));while (true) {threadPoolExecutor.execute(() -> {try {String dateString = formatDate(new Date());Date parseDate = parse(dateString);String dateString2 = formatDate(parseDate);System.out.println(dateString.equals(dateString2));} catch (ParseException e) {e.printStackTrace();}});}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/729989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试官问:生成订单30分钟未支付,则自动取消,该怎么实现

今天给大家上一盘硬菜&#xff0c;并且是支付中非常重要的一个技术解决方案&#xff0c;有这块业务的同学注意自己试一把了哈&#xff01; 在开发中&#xff0c;往往会遇到一些关于延时任务的需求。例如 生成订单30分钟未支付&#xff0c;则自动取消 生成订单60秒后,给用户发短…

论文翻译:一种基于强化学习的车辆队列控制策略,用于减少交通振荡中的能量消耗

A Reinforcement Learning-Based Vehicle Platoon Control Strategy for Reducing Energy Consumption in Traffic Oscillations 一种基于强化学习的车辆队列控制策略&#xff0c;用于减少交通振荡中的能量消耗 文章目录 A Reinforcement Learning-Based Vehicle Platoon Cont…

QT中的信号和槽

信号和槽概述 在 Qt 中&#xff0c;用户和控件的每次交互过程称为⼀个事件。比如 “用户点击按钮” 是⼀个事件&#xff0c;“用户关闭窗口” 也是⼀个事件。每个事件都会发出⼀个信号&#xff0c;例如用户点击按钮会发出 “按钮被点击” 的信号&#xff0c;用户关闭窗口会发出…

突破编程_前端_JS编程实例(自适应表格列宽)

1 开发目标 针对如下的表格组件&#xff1a; 根据表格的各个列字符串宽度动态调整表格列宽&#xff1a; 2 详细需求 本组件目标是提供一个自动调整 HTML 表格列宽的解决方案&#xff0c;通过 JS 实现动态计算并调整表格每列的宽度&#xff0c;以使得表格能够自适应容器宽度&a…

微信作为私域营销的载体有哪些优势?

私域流量的本质就是&#xff1a; 降低我的获客成本&#xff0c;提高我产品服务的复购率&#xff0c;增加我和用户之间的粘性&#xff0c;挖掘用户的终身价值。 私域流量的优势&#xff1a; 1、更精准&#xff1b; 2、节约成本&#xff0c;减少广告成本&#xff1b; 3、有利于品…

High-Resolution Image Synthesis with Latent Diffusion Models

一、简介 标题:High-Resolution Image Synthesis with Latent Diffusion Models(https://arxiv.org/pdf/2112.10752.pdf;GitHub - CompVis/latent-diffusion: High-Resolution Image Synthesis with Latent Diffusion Models) 期刊:CVPR 时间:2022 作者:Robin Romba…

mongo多数据源动态配置

mongo的配置信息默认使用配置文件的spring.data.mongodb前缀的信息进行配置&#xff0c;但是我想另外配个其他前缀的信息如logging.data.mongodb&#xff0c;区分默认的mongo数据源。这个就需要建个mongo配置类&#xff0c;以logging.data.mongodb前缀去动态配置mongo数据源。 …

LCR 179. 查找总价格为目标值的两个商品 - 力扣

1. 题目 购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况&#xff0c;返回任一结果即可。 2. 示例 3. 分析 我们首先想到暴力解法&#xff0c;这道题目的暴力还是比较简单的&#xff0c;列举每个数的情况即可…

C/C++的内存管理与初阶模板

引言 我们在学习C的时候&#xff0c;会经常在堆上申请空间&#xff0c;所以这个时候就体现了内存管理遍历。 图下是我们常见的计算机的内存划分&#xff1a; 我也在图下对部分变量存在的位置&#xff0c;及时标注。(如果有任何问题可以联系博主修改&#xff0c;感谢大家。) 那…

智慧油气场站:油气行业实现数字化转型的关键一步

智慧油气场站&#xff1a;油气行业实现数字化转型的关键一步 在现代社会&#xff0c;能源供应是国家经济发展和人民生活的重要保障。而油气场站作为能源的重要供应和储存基地&#xff0c;扮演着至关重要的角色。此外&#xff0c;油气场站还可以为石油和天然气的生产提供支持。…

Docker安装主从数据库

我自己的主数据库名字 user_muster 密码是123456 从数据库 就是slave2 名字是root 密码是123456 首先开启docker后直接执行命令 docker run -d \ -p 3307:3306 \ -v /xk857/mysql/master/conf:/etc/mysql/conf.d \ -v /xk857/mysql/master/data:/var/lib/mysql \ -e MYSQL_…

云计算与大数据课程笔记(二)之Google云计算框架辅助笔记(上)

Chunk & Block 在GFS中&#xff0c;Chunk默认大小是64MB。作者在参加云计算相关竞赛时发现有题目说Hadoop的Block默认是64MB&#xff0c;这和作者之前学的128MB不太一样&#xff0c;故进行以下整理&#xff1a; 在Hadoop分布式文件系统&#xff08;HDFS&#xff09;中&…

Threejs用下个点方法实现模型沿着轨道行驶

上一文中实现了用模型所在点的切线方式确定模型的朝向&#xff0c;这个章节是对上个章节的补充&#xff0c;用一种更简单的方式实现小车沿着轨道方向移动&#xff0c;如上文前半部分内容&#xff0c;需要创建场景&#xff0c;轨道&#xff0c;加载车的模型&#xff0c;一切就绪…

mac 配置.bash_profile不生效问题

1、问题描述 mac系统中配置了环境变量只能在当前终端生效&#xff0c;切换了终端就无效了&#xff0c;查了下问题所在。mac系统会预装一个终极shell - zsh&#xff0c;环境变量读取在 .zshrc 文件下。 2、解决方案 1、切换终端到bash 切换终端到bash chsh -s /bin/bash 切换终端…

【随笔】程序员的金三银四求职宝典,每个人都有最合适自己的求职宝典

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读文章&#xff01; 此篇是【话题达人】系列文章&#xff0c;这一次的话题是《程序员的金三银四求职宝典》 目录 背景能力流程图求职宝典就业数据人数曲线图增长率柱状图 其他建议文章推荐 背景 随着春天的脚步渐近&#xff0…

【JavaEE初阶】 关于JVM垃圾回收

文章目录 &#x1f343;前言&#x1f38b;死亡对象的判断算法&#x1f6a9;引用计数算法&#x1f6a9;可达性分析算法 &#x1f333;垃圾回收算法&#x1f6a9;标记-清除算法&#x1f6a9;复制算法&#x1f6a9;标记-整理算法&#x1f6a9;分代算法&#x1f388;哪些对象会进入…

Redis与 Memcache区别

Redis与 Memcache区别 1 , Redis 和 Memcache 都是将数据存放在内存中&#xff0c;都是内存数据库。不过 Memcache 还可用于缓存 其他东西&#xff0c;例如图片、视频等等。 2 , Memcache 仅支持key-value结构的数据类型&#xff0c;Redis不仅仅支持简单的key-value类型的数据&…

STM32 | Proteus 8.6安装步骤(图文并茂)

01 Proteus 8.6 简介 Proteus 8.6 是一款功能强大的电子设计自动化软件&#xff0c;广泛用于电路设计、仿真和PCB布局。它为电子工程师和学生提供了一个全面的工具集&#xff0c;用于设计和验证各种电路和电子设备。Proteus 8.6 包括了以下几个主要特性&#xff1a; 1. 电路设…

低代码与微服务:重塑软件开发的未来

在软件开发的浩瀚宇宙中&#xff0c;新的技术和理念不断涌现&#xff0c;为开发者提供了更为广阔的创新空间。其中&#xff0c;“低代码”与“微服务”无疑是两颗璀璨的明星&#xff0c;它们正在改变着传统的软件开发模式&#xff0c;为开发者带来了更高效、更灵活的开发体验。…

Java实战:Spring Boot+Netty+Websocket实现后台向前端推送信息

本文将详细介绍如何使用Spring Boot集成Netty和Websocket&#xff0c;实现后台向前端推送信息的功能。我们将深入探讨Netty和Websocket的原理&#xff0c;以及如何利用Spring Boot简化Netty的集成和配置。 1. 引言 在当今的互联网应用中&#xff0c;实时通信变得越来越重要。…