Java 程序读取Mysql数据库时间信息与真实时间相差 13、14 小时、SQLException: HOUR_OF_DAY: 2 -> 3
CST时区引起的异常:
Java 程序读取Mysql数据库时间信息,与真实时间相差 13、14 小时
java.sql.SQLException: HOUR_OF_DAY: 2 -> 3
原因:
Mysql 驱动:mysql-connector-java 升级到8版本后。将数据库时间解析到java时间,需要获取数据库的时区。
java如何数据库时区:
1、数据库连接中指明的时区,就用该时区,优先级最高。datasource.urljdbc:mysql://127.0.0.1:3306/yourDB?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8。
2、通过语句查询数据时区 select @@time_zone,如果返回SYSTEM,数据库没有配置时区,使用系统时区 select @@system_time_zone。
mysql> select @@time_zone;
+-------------+
| @@time_zone |
+-------------+
| SYSTEM |
+-------------+
mysql> select @@system_time_zone;
+--------------------+
| @@system_time_zone |
+--------------------+
| CST |
+--------------------+
如果java获取数据库的时区是CST,就会出现以上的问题。
CST是什么时区?
CST时间有四种解释:
美国中部时间 Central Standard Time (USA) UTC-06:00/ UTC-05:00
澳大利亚中部时间 Central Standard Time (Australia) UTC+09:30
中国标准时 China Standard Time UTC+08:00
古巴标准时 Cuba Standard Time UTC-04:00
java是美国人的,当然认为CST是美国中部时间。
夏令时:
由于美国有夏令时,CST非夏令时对应 UTC-06:00,夏令时对应 UTC-05:00 。
美国的夏令时,从每年3月第2个星期天凌晨开始,到每年11月第1个星期天凌晨结束。
以2020年为例:
夏令时开始时间调整前:2020年03月08日星期日 02:00:00,时间向前拨一小时.
调整后:2020年03月08日星期日 03:00:00
夏令时结束时间调整前:2020年11月01日星期日 02:00:00,时间往回拨一小时.
调整后:2020年11月01日星期日 01:00:00
这意味这:CST没有2020-03-08 02:00:00~2020-03-08 03:00:00 这个区间的时间。会有两个 2020-11-01 01:00:00~2020-11-01 02:00:00区间的时间。
例:
//相差14小时
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CST"), Locale.US);
calendar.setLenient(false);
//2020-03-08 01:02:00
calendar.set(2020, 2, 8, 1, 2, 0);
Date s = new Date(calendar.getTimeInMillis());
SimpleDateFormat f1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(f1.format(s));//2020-03-08 15:02:00
//2020-03-08 01:02:00 非夏令时间,于北京时间相差14小时。
例:
//相差13小时
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CST"), Locale.US);
calendar.setLenient(false);
//2020-03-08 03:02:00
calendar.set(2020, 2, 8, 3, 2, 0);
Date s = new Date(calendar.getTimeInMillis());
SimpleDateFormat f1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(f1.format(s));//2020-03-08 16:02:00
//2020-03-08 03:02:00 夏令时间,于北京时间相差13小时。
例:
//抛出 Exception: HOUR_OF_DAY: 2 -> 3
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CST"), Locale.US);
calendar.setLenient(false);//严格数据校验
//2020-03-08 02:02:00
calendar.set(2020, 2, 8, 2, 2, 0);
Date s = new Date(calendar.getTimeInMillis());//抛出异常:java.lang.IllegalArgumentException: HOUR_OF_DAY: 2 -> 3
SimpleDateFormat f1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(f1.format(s));
//2020-03-08 02:02:00 在CST 时区中是一个不存在的时间,因此出现了异常。
解决上面的问题3种方式:
1、修改数据时区。
2、降低mysql 驱动 mysql-connector-java的版本。
3、访问数据库连接上加上数据时区。这种方式最好。
mysql-connector-java 源码中是如何操作的
public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {
private NativeSession session = null;
protected ResultSetFactory nullStatementResultSetFactory;
public ConnectionImpl(HostInfo hostInfo) throws SQLException {
……
try {
……
//创建ResultSetFactory
this.nullStatementResultSetFactory = new ResultSetFactory(this, null);
//创建session
this.session = new NativeSession(hostInfo, this.propertySet);
……
} catch (CJException e1) {
throw SQLExceptionsMapping.translateException(e1, getExceptionInterceptor());
}
try {
// createNewIO方法 -> connectOneTryOnly方法 -> initializePropsFromServer方法
createNewIO(false);
} catch (SQLException ex) {
}
……
}
private void initializePropsFromServer() throws SQLException {
……
//配置session ,这里就有获取数据库时区的操作
this.session.getProtocol().initServerSession();
……
}
}
public class NativeProtocol extends AbstractProtocol<NativePacketPayload> implements Protocol<NativePacketPayload>, RuntimePropertyListener {
@Override
public void initServerSession() {
//配置时区
configureTimezone();
}
/**
* 配置时区
*/
public void configureTimezone() {
//获取数据库时区
String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone");
//获取数据库系统时区
if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone");
}
//获取访问数据连接中配置的时区
String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();
if (configuredTimeZoneOnServer != null) {
// user can override this with driver properties, so don't detect if that's the case
if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) {
try {
canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());
} catch (IllegalArgumentException iae) {
throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor());
}
}
}
if (canonicalTimezone != null && canonicalTimezone.length() > 0) {
this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone));
//
// The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this...
//
if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }),
getExceptionInterceptor());
}
}
this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone());
}
}
在创建Connect时同样也创建了ResultSetFactory ,该Factory会创建ResultSetImpl ,ResultSetImpl用于解析数据库返回的结果集。
public class ResultSetFactory implements ProtocolEntityFactory<ResultSetImpl, NativePacketPayload> {
private JdbcConnection conn;
private StatementImpl stmt;
public ResultSetFactory(JdbcConnection connection, StatementImpl creatorStmt) throws SQLException {
this.conn = connection;
this.stmt = creatorStmt;
}
/**
* 创建ResultSetImpl
*/
public ResultSetImpl createFromResultsetRows(int resultSetConcurrency, int resultSetType, ResultsetRows rows) throws SQLException {
ResultSetImpl rs;
StatementImpl st = this.stmt;
if (rows.getOwner() != null) {
st = ((ResultSetImpl) rows.getOwner()).getOwningStatement();
}
switch (resultSetConcurrency) {
case java.sql.ResultSet.CONCUR_UPDATABLE:
rs = new UpdatableResultSet(rows, this.conn, st);
break;
default:
// CONCUR_READ_ONLY
rs = new ResultSetImpl(rows, this.conn, st);
break;
}
return rs;
}
}
public class ResultSetImpl extends NativeResultset implements ResultSetInternalMethods, WarningListener {
protected volatile JdbcConnection connection;
protected NativeSession session = null;
public ResultSetImpl(ResultsetRows tuples, JdbcConnection conn, StatementImpl creatorStmt) throws SQLException {
this.connection = conn;
this.session = (NativeSession) conn.getSession();
……
this.floatValueFactory = new FloatValueFactory(pset);
this.doubleValueFactory = new DoubleValueFactory(pset);
this.bigDecimalValueFactory = new BigDecimalValueFactory(pset);
this.binaryStreamValueFactory = new BinaryStreamValueFactory(pset);
this.defaultDateValueFactory = new SqlDateValueFactory(pset, null, this.session.getServerSession().getDefaultTimeZone(), this);
this.defaultTimeValueFactory = new SqlTimeValueFactory(pset, null, this.session.getServerSession().getDefaultTimeZone(), this);
//这里会将session中的时区传入
this.defaultTimestampValueFactory = new SqlTimestampValueFactory(pset, null, this.session.getServerSession().getDefaultTimeZone());
this.defaultLocalDateValueFactory = new LocalDateValueFactory(pset, this);
this.defaultLocalTimeValueFactory = new LocalTimeValueFactory(pset, this);
……
}
}
public class SqlTimestampValueFactory extends AbstractDateTimeValueFactory<Timestamp> {
private Calendar cal;
public SqlTimestampValueFactory(PropertySet pset, Calendar calendar, TimeZone tz) {
super(pset);
if (calendar != null) {
this.cal = (Calendar) calendar.clone();
} else {
this.cal = Calendar.getInstance(tz, Locale.US);
this.cal.setLenient(false);
@Override
public Timestamp localCreateFromTimestamp(InternalTimestamp its) {
synchronized (this.cal) {
try {
this.cal.set(its.getYear(), its.getMonth() - 1, its.getDay(), its.getHours(), its.getMinutes(), its.getSeconds());
Timestamp ts = new Timestamp(this.cal.getTimeInMillis());
ts.setNanos(its.getNanos());
return ts;
} catch (IllegalArgumentException e) {
}
}
}
}
更多推荐
所有评论(0)