绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
Java 日期处理易踩的十个坑
2023-02-16 15:48:06


以下文章来源于公众号捡田螺的小男孩 ,作者捡田螺的小男孩

前言

整理了 Java 日期处理的十个坑,希望对大家有帮助。

1. 用 Calendar 设置时间的坑


反例:
Calendar c = Calendar.getInstance();c.set(Calendar.HOUR10);System.out.println(c.getTime());

运行结果:


Thu Mar 26 22:28:05 GMT+08:00 2020


解析:


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


正例:
Calendar c = Calendar.getInstance();c.set(Calendar.HOUR_OF_DAY10);


2. Java 日期格式化 YYYY 的坑


反例:
Calendar calendar = Calendar.getInstance();calendar.set(2019, Calendar.DECEMBER, 31);Date testDate = calendar.getTime();SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");System.out.println("2019-12-31YYYY-MM-dd 格式后 " + dtf.format(testDate));

运行结果:


2019-12-31 转 YYYY-MM-dd 格式后 2020-12-31


解析:


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


正例:

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


3. Java日期格式化hh的坑。


反例:


String str = "2020-03-18 12:00";SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd hh:mm");Date newDate = dtf.parse(str);System.out.println(newDate);


运行结果:

Wed Mar 18 00:00:00 GMT+08:00 2020

解析:

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


正例:

String str = "2020-03-18 12:00";SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd HH:mm");Date newDate = dtf.parse(str);System.out.println(newDate);


4. Calendar 获取的月份比实际数字少 1 即 (0-11)


反例:

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


运行结果:


当前2月份


解析:


The first month of the year in the Gregorian and Julian calendarsis JANUARY which is 0;


也就是1月对应的是下标 0,依次类推。因此获取正确月份需要加 1。


正例:


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

5. Java 日期格式化 DD 的坑


反例:


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


运行结果:


2019-12-31 转 yyyy-MM-DD 格式后 2019-12-365


解析:

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

正例:

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

6. SimleDateFormat的format 初始化问题


反例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");System.out.println(sdf.format(20200323));


运行结果:


1970-01-01


解析:


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


正例:

Calendar calendar = Calendar.getInstance();calendar.set(2020Calendar.MARCH23);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");System.out.println(sdf.format(calendar.getTime()));


7. 日期本地化问题


反例:


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 at java.time.format.DateTimeFormatter.parseResolved(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);

8. SimpleDateFormat 解析的时间精度问题


反例:


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


运行结果:

Exception in thread "main" java.text.ParseExceptionUnparseable date"2020-03"at java.text.DateFormat.parse(DateFormat.java:366) at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)

解析:


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


正例:


SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");String time = "2020-03";System.out.println(sdf.parse(time));


9. SimpleDateFormat 的线性安全问题


反例:

import java.util.regex.Matcher;import java.util.regex.Pattern;
public class RegexMatches { public static void main(String args[]) {
// String to be scanned to find the pattern. String line = "This order was placed for QT3000! OK?"; String pattern = "(.*)(\\d+)(.*)";
// Create a Pattern object Pattern r = Pattern.compile(pattern);
// Now create matcher object. Matcher m = r.matcher(line); if (m.find()) { System.out.println("Found value: " + m.group()); System.out.println("Found value: " + m.group(1)); System.out.println("Found value: " + m.group(2)); } else { System.out.println("NO MATCH"); } }}


运行结果:

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$(SimpleDateFormatTest.java:19) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.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$(SimpleDateFormatTest.java:19) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.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();                }            });        }    }}


10. Java日期的夏令时问题


反例:

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(sdf.parse("1986-05-04 00:30:00"));


运行结果:


Sun May 04 01:30:00 CDT 1986


解析:


先了解一下夏令时:


夏令时,表示为了节约能源,人为规定时间的意思。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。

1986年4月,中国中央有关部门发出“在范围内实行夏时制的通知”,具体作法是:每年从四月中旬个星期日的凌晨2时整(北京时间),将时钟拨快一小时。(1992年起,夏令时暂停实行。)

夏时令这几个时间可以注意一下哈,1986-05-04, 1987-04-12, 1988-04-10, 1989-04-16, 1990-04-15, 1991-04-14.


结合 Demo 代码,中国在 1986-05-04 当天还在使用夏令时,时间被拨快了 1 个小时。所以 0 点 30 分打印成了 1 点 30 分。如果要打印正确的时间,可以考虑修改时区为东 8 区。


正例:

TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(sdf.parse("1986-05-04 00:30:00"));



分享好友

分享这个小栈给你的朋友们,一起进步吧。

Java小专栏
创建时间:2019-08-08 21:32:06
Java专栏专注技术分享
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • lihong
    栈主

小栈成员

查看更多
  • 栈栈
  • 杨三百
  • at_1
  • gaokeke123
戳我,来吐槽~