字母 | 释义 |
yyyy | 年 |
MM | 月 |
dd | 日 |
hh | 1~12小时制(1-12) |
HH | 24小时制(0-23) |
mm | 分 |
ss | 秒 |
S | 毫秒 |
E | 星期几 |
D | 一年中的第几天 |
F | 一月中的第几个星期(会把这个月总共过的天数除以7) |
w | 一年中的第几个星期 |
W | 一月中的第几星期(会根据实际情况来算) |
a | 上下午标识 |
k | 和HH差不多,表示一天24小时制(1-24)。 |
K | 和hh差不多,表示一天12小时制(0-11)。 |
z | 表示时区 |
上面的日期和时间模式 是按我们常用的年月日时分秒来放的,下面是专业的图,供参考。
定义了以下模式字母(所有其他字符 'A' 到 'Z' 和 'a' 到 'z' 都被保留)
/** * java.util.Date转String * 年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 * @param d * @return */ public static String DateToString(Date d) { //年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm:ss.SSS E zZ"); return sdf.format(d); }
/** * String转java.util.Date * 年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 * @param d * @return */ public static Date StringToDate(String str) { //年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm:ss.SSS E zZ"); Date date = null; try { date = sdf.parse(str); } catch (ParseException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } return date; }
/** * 输入一个人的身份证号码,输出出身年月日,格式为xx年xx月xx日并求年龄 * @param idCard 身份证号码 * @return 格式为xx年xx月xx日并求年龄 */ public static String getDateAndAgeByIdCard(String idCard) { String str = ""; try { // 第一种输出年月日 String b = idCard.substring(6, 14); // 日期对象 要与最开始的日期格式一致不能加年月日 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); // 将字符串转为日期对象 转为日期对象后就可以改变自己想要的格式了(就是sdf1定义) Date dt = sdf.parse(b); // 定义自己想要的日期格式,转化为日期对象后定义自己想要的格式 SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日"); String f = sdf1.format(dt);// 格式化日期对象 // 求年龄 Date now = new Date(); // getTime()是指自己距离1970年为止的毫秒数 // 转为 秒 /分/时/日/年 long age = (now.getTime() - dt.getTime()) / 1000 / 60 / 60 / 24 / 360; str = "出生日期: " + f + ",年龄: " + age + "岁"; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return str; }
由于 SimpleDateFormat 中的 format 方法在执行过程中,会使用一个成员变量 calendar 来保存时间。
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { this.calendar.setTime(date); boolean useDateFormatSymbols = this.useDateFormatSymbols(); int i = 0; ……
由于在声明 SimpleDateFormat 的时候,使用的是 static 定义的。那么这个 SimpleDateFormat 就是一个共享变量,SimpleDateFormat 中的 calendar 也就可以被多个线程访问到。
举个例子:假设一个线程 A 刚执行完 calendar.setTime 把时间设置成 2020-05-07,这个线程还没执行完,线程 B 又执行了 calendar.setTime 把时间改成了 2020-06-07。这时候线程 A 继续往下执行,拿到的 calendar.getTime 得到的时间就是线程 B 改过之后的。
除了 format 方法以外,SimpleDateFormat 的 parse 方法也有同样的问题。
所以,不要把 SimpleDateFormat 作为一个共享变量使用。
如果使用的是Java 8 之前的JDK,可以使 SimpleDateFormat 变成线程安全的,通过加锁的方式来解决:
/** * 线程安全的 SimpleDateFormat */ private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); /** * 获取 DateFormat * @param pattern 格式化 * @return DateFormat */ public static DateFormat getDateFormat(String pattern) { DateFormat df = threadLocal.get(); if(df == null){ df = new SimpleDateFormat(pattern); threadLocal.set(df); } return df; }
如果使用的是Java 8 + 的版本,那么完全可以抛弃这种线程不安全的时间格式化方法。可以使用 DateTimeFormatter 代替 SimpleDateFormat,这是一个线程安全的格式化工具类。
package com; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatSimple { /** * 线程安全的 SimpleDateFormat */ private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); /** * 获取 DateFormat * @param pattern 格式化 * @return DateFormat */ public static DateFormat getDateFormat(String pattern) { DateFormat df = threadLocal.get(); if(df == null){ df = new SimpleDateFormat(pattern); threadLocal.set(df); } return df; } /** * java.util.Date转String * 年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 * @param d * @return */ public static String DateToStringByThreadLocal(Date d) { return getDateFormat("yyyy-MM-dd a HH:mm:ss.SSS E zZ").format(d); } /** * java.util.Date转String * 年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 * @param d * @return */ public static String DateToString(Date d) { //年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm:ss.SSS E zZ"); return sdf.format(d); } /** * String转java.util.Date * 年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 * @param d * @return */ public static Date StringToDate(String str) { //年-月-日 上/下午标识 时:分:秒.毫秒 星期几 时区英文时区数字 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm:ss.SSS E zZ"); Date date = null; try { date = sdf.parse(str); } catch (ParseException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } return date; } /** * 输入一个人的身份证号码,输出出身年月日,格式为xx年xx月xx日并求年龄 * @param idCard 身份证号码 * @return 格式为xx年xx月xx日并求年龄 */ public static String getDateAndAgeByIdCard(String idCard) { String str = ""; try { // 第一种输出年月日 String b = idCard.substring(6, 14); // 日期对象 要与最开始的日期格式一致不能加年月日 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); // 将字符串转为日期对象 转为日期对象后就可以改变自己想要的格式了(就是sdf1定义) Date dt = sdf.parse(b); // 定义自己想要的日期格式,转化为日期对象后定义自己想要的格式 SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日"); String f = sdf1.format(dt);// 格式化日期对象 // 求年龄 Date now = new Date(); // getTime()是指自己距离1970年为止的毫秒数 // 转为 秒 /分/时/日/年 long age = (now.getTime() - dt.getTime()) / 1000 / 60 / 60 / 24 / 360; str = "出生日期: " + f + ",年龄: " + age + "岁"; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return str; } }