背景
在实际的项目中,时间是非常常见的一种应用,因此一般为时间格式化创建一个工具类,用于将项目中的时间进行格式化,目前使用的是SimpleDateFormat
,但是在阿里编码规范检查中有一项检查,此项检查中不推荐使用SimpleDateFormat
,因此本文对此项检查进行分析,用于设计出一个比较好用的时间格式化工具类。
检查条目描述如下:
SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。 说明:如果是JDK8的应用,可以使用instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
格式化当前时间
- 使用SimpleDateFormat 进行格式当前时间代码如下
public static String formatCurrent(String format){
// 注意 如果仅是格式化年月日时分秒都是使用数字来表示, Locale不影响
// 如果 如果需要进行国际化,则Locale 会受影响
SimpleDateFormat df = new SimpleDateFormat(format, Locale.US);
return df.format(new Date());
}
- 使用DateTimeFormatter进行格式化代码如下
public static String formatCurrent(String format){
LocalDateTime dt = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
return formatter.format(dt);
}
小结
从格式化当前的功能来看,代码量差不多,易用性也差不多,同时 DateTimeFormatter 是线程安全的,确实带来了好处。
国际化问题
以月份为例子,其他例如星期,上午下午,等在国际化的时候都会受到影响
// 调用 DateUtils.formatCurrent("yyyy-MMMM-dd HH:mm:ss:SSS"); 输出:2020-六月-30 19:02:00:794
// 调用 DateUtils.formatCurrent("yyyy-MMM-dd HH:mm:ss:SSS"); 输出:2020-6月-30 19:02:00:794
public static String formatCurrent(String format){
// 注意第二个参数
SimpleDateFormat df = new SimpleDateFormat(format, Locale.CHINA);
return df.format(new Date());
}
// 调用 DateUtils.formatCurrent("yyyy-MMMM-dd HH:mm:ss:SSS"); 输出:2020-June-30 19:02:00:794
public static String formatCurrent(String format){
// 注意第二个参数
SimpleDateFormat df = new SimpleDateFormat(format, Locale.US);
return df.format(new Date());
}
格式化时间转换
- 使用SimpleDateFormat 进行格式化时间进行转换,代码如下
public static String transformTime(String time, String oldFormat, String newFormat) {
try {
return new SimpleDateFormat(newFormat).format(new SimpleDateFormat(oldFormat).parse(time));
} catch (Exception e) {
e.printStackTrace();
}
return time;
}
- 使用DateTimeFormatter进行格式化时间进行转换,代码如下
public static String transformTime(String timeStr,String oldFormat,String newFormat) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(oldFormat);
LocalDateTime dt = LocalDateTime.parse(timeStr, formatter);
return DateTimeFormatter.ofPattern(newFormat).format(dt);
}
使用DateTimeFormatter需要注意的问题是有:
- 如果带有毫秒,且格式为
yyyyMMddHHmmssSSS
,会抛出异常,调用代码如下。bug 描述。在java9 应该已经解决这个问题,但是目前android 还不支持java9,在android 上面还存在这个问题。
String newRet1 = NewDateUtils.transformTime("20200630163030123","yyyyMMddHHmmssSSS","yyyy-MM-dd HH:mm:ss:SSS");
/**
* 抛出异常 java.time.format.DateTimeParseException: Text '20200630163030123' could not be parsed at index 0
**/
// 解决1
public static String transformTimeMil(String timeStr,String oldFormat,String newFormat) {
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
// 解析date+time
.appendPattern("yyyyMMddHHmmss")
// 解析毫秒数
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
LocalDateTime dt = LocalDateTime.parse(timeStr, formatter);
return DateTimeFormatter.ofPattern(newFormat).format(dt);
}
// 解决方式2 ,可以在秒和毫秒直接添加空格等符号,
- 格式化年月日时分秒使用LocalDateTime,格式化年月日,使用LocalDate,格式化时间(时分秒)使用LocalTime。不能统一使用LocalDateTime进行格式化
代码如下
// 格式化代码
public static String transformTime(String timeStr,String oldFormat,String newFormat) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(oldFormat);
LocalDateTime dt = LocalDateTime.parse(timeStr, formatter);
return DateTimeFormatter.ofPattern(newFormat).format(dt);
}
// 调用代码
String newRet1 = NewDateUtils.transformTime("20200630", "yyyyMMdd","yyyy-MM-dd");
// 抛出异常
// java.time.format.DateTimeParseException:
// 解决 需要将LocalDateTime 替换为LocalDate,如果是只有时间也要替换为 LocalTime
- LocalDate 无法只格式化月和日,代码如下
// 格式化代码
public static String transformDate(String timeStr,String oldFormat,String newFormat) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(oldFormat);
LocalDate dt = LocalDate.parse(timeStr, formatter);
return DateTimeFormatter.ofPattern(newFormat).format(dt);
}
// 调用
String newRet1 = NewDateUtils.transformDate("0630", "MMdd","MM-dd");
String newRet1 = NewDateUtils.transformDate("202006", "yyyyMM","yyyy-MM");
// 抛出异常 java.time.format.DateTimeParseException:
小结
从格式化时间转换这个功能来说,DateTimeFormatter 的设计并不完美,或者因为本人知识有限,使用方式不对,造成的问题。从格式化时间转换这个功能点来说,DateTimeFormatter 在android 上面尚不具备在项目中编写成工具类的条件。
判断时间是否有效
- 使用SimpleDateFormat 判断时间是否有效
public static boolean isValidDate(String str, String pattern) {
SimpleDateFormat format = new SimpleDateFormat(pattern);
try {
format.setLenient(false);
format.parse(str);
}catch (ParseException e) {
e.printStackTrace();
return false;
}
return true;
}
- 使用 DateTimeFormatter 来判断时间的有效性。
// 调用,注意这种模式下面,使用u代表年
ret = NewDateUtils.isValidDate("20200630","uuuuMMdd");
public static boolean isValidDate(String str, String pattern){
try {
DateTimeFormatter.ofPattern(pattern)
.withResolverStyle(ResolverStyle.STRICT)
.parse(str);
} catch (RuntimeException e) {
e.printStackTrace();
return false;
}
return true;
}
总结
当然,时间格式的使用还有判断时间的大小,计算出距离某个时间前或者后的时间等等。从第二点(格式化时间转换)来看,目前DateTimeFormatter 还不具备足够大的优势,替换SimpleDateFormat。SimpleDateFormat 面临的问题就是线程安全的问题,定义为工具类,且SimpleDateFormat 为局部变量,可以解决线程安全的问题,但是导致的问就是SimpleDateFormat为局部变量,创建了大量的实例,消耗了空间。同时了解到有一个开源库Joda-Time
对时间的操作比较友好,但是目前暂时没有进行体验,后面有时候可以深度体验一下。