背景

在实际的项目中,时间是非常常见的一种应用,因此一般为时间格式化创建一个工具类,用于将项目中的时间进行格式化,目前使用的是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需要注意的问题是有:

  1. 如果带有毫秒,且格式为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 ,可以在秒和毫秒直接添加空格等符号,
  1. 格式化年月日时分秒使用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
  1. 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对时间的操作比较友好,但是目前暂时没有进行体验,后面有时候可以深度体验一下。