危机四伏的2020已经悄然而过
不知大家是否还记得
年初的微博上长久挂着#重启2020#的热搜
在最艰难的日子里
普通人要少看手机少看新闻
才能缓解内心的压抑和惶恐
这一年,一些人没有扛过去
但更多人,终究走下来了
此时此刻我们已经跨年结束
来到了全新的2021年……
感慨不说了
咳咳,想知道跨年可还安稳?
"YYYY-MM-dd"有没有给你送来新年的彩蛋?
事故模拟
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJ6pieKA-1609596215025)(http://qiniu.hughpro.com/20210102205020.png)]
看到上面程序运行的结果,大家肯定会有疑惑,为毛不一样呀(内心小声嘀咕:“不应该啊,之前没出现过问题的”)
事故分析
既然有疑惑,那我们就翻翻Java Api找找看,随便找个在线文档,找到SimpleDateFormat
里面就有关于日期格式的说明,我帮大家找好了:Java Api在线链接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZS7QXt7-1609596215029)(http://qiniu.hughpro.com/20210102210732.png)]
在Api里面y
的释义是year
,很好理解就是年的意思,那Y
的释义是Week year
,代表的是本周所在年份,而只要本周跨年,那么本周都是算到下一年的,所以就出现了上面的事故现场。
在外国一周是从周日开始到周六结束,其实这个问题在2020年12月27日就已经发生了,上面的测试代码,改成27,一样是会变成2021年12月27日。
在项目中,应该尽量避免每次转换日期都手动进行编写格式的操作,这边总会不可避免的产生这样的问题,统一定义一个DateUtil
类,里面提供各种转换格式的方法以及所以常用的到的格式的常量定义,这样跨年的彩蛋就会更换啦,哈哈
public class DateUtil{
/**
* 日期格式,年份,例如:2004,2008
*/
public static final String DATE_FORMAT_YYYY = "yyyy";
/**
* 日期格式,年份和月份,例如:200707,200808
*/
public static final String DATE_FORMAT_YYYYMM = "yyyyMM";
/**
* 日期格式,年份和月份,例如:200707,2008-08
*/
public static final String DATE_FORMAT_YYYY_MM = "yyyy-MM";
/**
* 日期格式,年月日,例如:050630,080808
*/
public static final String DATE_FORMAT_YYMMDD = "yyMMdd";
/**
* 日期格式,年月日,用横杠分开,例如:06-12-25,08-08-08
*/
public static final String DATE_FORMAT_YY_MM_DD = "yy-MM-dd";
/**
* 日期格式,年月日,例如:20050630,20080808
*/
public static final String DATE_FORMAT_YYYYMMDD = "yyyyMMdd";
/**
* 日期格式,年月日,用横杠分开,例如:2006-12-25,2008-08-08
*/
public static final String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd";
/**
* 日期格式,年月日,例如:2016.10.05
*/
public static final String DATE_FORMAT_POINTYYYYMMDD = "yyyy.MM.dd";
/**
* 日期格式,年月日,例如:2016年10月05日
*/
public static final String DATE_TIME_FORMAT_YYYY年MM月DD日 = "yyyy年MM月dd日";
/**
* 日期格式,年月日时分,例如:200506301210,200808081210
*/
public static final String DATE_FORMAT_YYYYMMDDHHmm = "yyyyMMddHHmm";
/**
* 日期格式,年月日时分,例如:20001230 12:00,20080808 20:08
*/
public static final String DATE_TIME_FORMAT_YYYYMMDD_HH_MI = "yyyyMMdd HH:mm";
/**
* 日期格式,年月日时分,例如:2000-12-30 12:00,2008-08-08 20:08
*/
public static final String DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI = "yyyy-MM-dd HH:mm";
/**
* 日期格式,年月日时分秒,例如:20001230120000,20080808200808
*/
public static final String DATE_TIME_FORMAT_YYYYMMDDHHMISS = "yyyyMMddHHmmss";
/**
* 日期格式,年月日时分秒,年月日用横杠分开,时分秒用冒号分开
* 例如:2005-05-10 23:20:00,2008-08-08 20:08:08
*/
public static final String DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS = "yyyy-MM-dd HH:mm:ss";
/**
* 日期格式,年月日时分秒毫秒,例如:20001230120000123,20080808200808456
*/
public static final String DATE_TIME_FORMAT_YYYYMMDDHHMISSSSS = "yyyyMMddHHmmssSSS";
/**
* 日期格式,月日时分,例如:10-05 12:00
*/
public static final String DATE_FORMAT_MMDDHHMI = "MM-dd HH:mm";
}
补充
另外,SimpleDateFormat
是线程不安全的,不推荐使用了,当并发较高的时候,容易出现问题,那如何解决呢?
-
使用局部变量的方式
public class DateUtil{ public static String formatDate(Date date)throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static Date parse(String strDate) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(strDate); } }
在需要用到
SimpleDateFormat
的地方新建一个实例,不管什么时候,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加重了创建对象的负担。在一般情况下,这样其实对性能影响并不是很明显的。 -
加同步锁
public class DateUtil { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException { synchronized (sdf){ return sdf.format(date); } } public static Date parse(String strDate) throws ParseException{ synchronized (sdf){ return sdf.parse(strDate); } } }
这样,当一个线程正在调用的时候,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。
-
使用
ThreadLocal
public class DateUtil { private static ThreadLocal<DateFormat> sdfThreadLocal = new ThreadLocal<DateFormat>(){ @Override public SimpleDateFormat initialValue(){ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static String formatDate(Date date)throws ParseException { return sdfThreadLocal.get().format(date); } public static Date parse(String strDate) throws ParseException{ return sdfThreadLocal.get().parse(strDate); } }
使用
ThreadLocal
, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。 -
使用
DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); //字符串转日期 LocalDate localDate = LocalDate.parse("2021-01-01 21:50:50",formatter); //日期转字符串 LocalTime localTime = LocalTime.now(); String strLocalTime = localTime.format(formatter); //yyyyMMdd String strLocalTime1 = localTime.format(DateTimeFormatter.BASIC_ISO_DATE);
上面代码仅仅是示例,关于
DateTimeFormatter
还有好多便捷的用法,它本身提供了一些自带的格式,不需再自己编写。和SimpleDateFormat
不同的是,DateTimeFormatter
不但是不变对象,它还是线程安全的,使用的时候,DateTimeFormatter
可以只创建一个实例,到处引用。
预祝大家2021年一切顺顺利利,疫情早点结束!
有任何问题欢迎关注公众号私信我,一起探讨,一起学习