如何精确表达java.util.Date的业务逻辑


本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。

 

 

在Java中最常用的日期时间操作类有四个:

java.util.Date
java.sql.Date
java.sql.Time
java.sql.Timestamp

 

为了精确表达业务逻辑,应尽量避免使用父类(java.util.Date)的方法。java.sql包下的三个子类中特有的valueOf()静态方法与toString()方法可以精确表达业务逻辑。

系统时间与本地时间

在中国地区,如果调用以下代码,我们会得到“1970-01-01 08:00:00”的结果

// 例1 java.util.Date obj1 = new java.util.Date(0L); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(dateFormat.format(obj1)); // 打印结果:1970-01-01 08:00:00

 

看了JDK API之后,您可能会有这样的疑问,为什么 new java.util.Date(0L) 得到的时间不是“1970-01-01 00:00:00”而是“1970-01-01 08:00:00”呢?难到JDK API写错了?
JDK API没有错,关键是创建java.util.Date对象所使用的毫秒数是计算机系统时间,该毫秒数指的是自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数,即格林尼治标准时间(GMT)的1970-01-01 00:00:00。但是当计算机以字符串方式展示给我们看的时候,这个时间已经成为本地时间了,例如中国地区使用GMT+8时区,所以我们看到的是“1970-01-01 08:00:00”的结果!
将上述代码修改一下,我们就可以得到一个更清晰的概念。如:

// 例2 java.util.Date obj1 = new java.util.Date(0L); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'GMT'Z"); System.out.println(dateFormat.format(obj1)); // 打印结果:1970-01-01 08:00:00 GMT+0800

 

上面的代码我们可以看到“1970-01-01 08:00:00 GMT+0800”的结果,证明我们所处的时区在GMT+8区。当我们改变时区时,相同的系统时间将显示不同的结果。如:

// 例3 java.util.Date obj1 = new java.util.Date(0L); TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00")); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'GMT'Z"); System.out.println(dateFormat.format(obj1)); // 打印结果:1970-01-01 00:00:00 GMT+0000

 

上面的代码我们可以看到“1970-01-01 00:00:00 GMT+0000”的结果,证明现在所处的时区在GMT+0区。 

总之,通过以上的描述,我们可以得出这样几个结论:

  1. 所有计算机的系统时间概念上是统一的;
  2. 相同的系统时间会根据不同的时区产生不同的本地时间;
  3. 当时区为GMT+0时,系统时间与本地时间相同。

精确的业务逻辑

在Java中最常用的日期时间操作类中java.util.Date是其它三个类的父类。因为有这样的继承关系,所有四个类均可以通过系统时间生成对象。例如: 

java.util.Date obj = new java.sql.Date(0L);

这种使用系统时间作为数据产生的日时对象在业务逻辑上可能存在问题。例如在时区GMT+0环境下运行如下代码:

// 例4 // 将时区设置为GMT+0 TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00")); // 使用传统构造方法生成日时对象 java.util.Date obj1 = new java.sql.Date(0L); java.util.Date obj2 = new java.sql.Date(3600000L); // 业务逻辑相同 System.out.println(obj1.toString()); // "1970-01-01" System.out.println(obj2.toString()); // "1970-01-01" // 语法比较不相同 System.out.println(obj1.equals(obj2)); // false System.out.println(obj1.compareTo(obj2)); // -1

 

上述代码产生的两个对象在业务逻辑上都表示格林尼治标准时间1970-01-01(00:00:00),但在语法上二者时间却相差1小时。出现这种现象的根本原因在于使用了不精确的系统时间来反映日时对象的业务逻辑。

所谓日时对象的业务逻辑,就是以本地时间作为标准的日时信息。例如描述格林尼治标准时间(GMT+0)1970-01-01 00:00:00的精确系统时间是0L毫秒;描述北京时间(GMT+8)1970-01-01 00:00:00的精确系统时间则是-28800000L毫秒。

除java.util.Date外,其它三个类均有属于自己的业务范围,例如:
java.sql.Date的有效成份包括年、月、日
java.sql.Time的有效成份包括时、分、秒
java.sql.Timestamp的有效成份包括年、月、日、时、分、秒、纳秒(毫微秒)
由于四种日时对象内部均使用细化到毫秒的系统时间作为标准数据,势必会造成使用不同的系统时间可以表达相同业务逻辑的现象。那么通过什么方式可以将一个不精确的系统时间转换成能够准确表达业务逻辑的精确系统时间呢?

 

1. 使用toString()来获得日时对象的业务逻辑
在继承自java.util.Date的三个子类中,传统的构造方法以及getTime()和setTime()两个方法均使用系统时间来实现,也就注定了它们在业务逻辑上的不精确性。如果已经使用了不精确的系统时间创建java.sql包中的日时对象,则toString()是唯一个可以获取精确业务逻辑的方法。例4的代码可以说明这一点。

 

2. 使用valueOf()构造精确的日时对象
为了能够从本质上使用精确系统时间来准确表达日时对象中的业务逻辑,除java.util.Date外,其它三个类均提供了第二个生成对象的方法,那就是valueOf()静态方法。例如:

java.util.Date obj1 = java.sql.Date.valueOf("2000-01-01");

通过传递一个准确表达日时信息的字符串,valueOf()将产生一个由精确系统时间构成的可以反映准确业务逻辑的日时对象。同样,这种精确的日时对象也可以在语法上进行精确的比较判断,例如:

// 例5 // 使用valueOf()生成日时对象 java.util.Date obj1 = java.sql.Date.valueOf("2000-01-01"); // 使用精确系统时间生成日时对象 java.util.Date obj2 = new java.sql.Date(obj1.getTime()); // 业务逻辑准确 System.out.println(obj1.toString()); // "2000-01-01" System.out.println(obj2.toString()); // "2000-01-01" // 语法比较准确 System.out.println(obj1.equals(obj2)); // true System.out.println(obj1.compareTo(obj2)); // 0

 

通过对valueOf()和toString()的了解,我们自然会想到一种转换方式,可以将不精确的系统时间转换成可以准确表达业务逻辑的精确系统时间。如下面的工具类:

public class DateTimeUtils { /** * 取得可以精确表达业务逻辑的日时对象 * * @param obj * 不精确的日时对象 * @return 精确的日时对象 */ public static java.sql.Date getLocalDate(java.util.Date obj) { if (obj == null) return null; java.sql.Date tmp = null; if (java.sql.Date.class.equals(obj.getClass())) { // 如果原始日时对象的类型是java.sql.Date // 则转换过程分两步: // 第一步,取得java.sql.Date对象的精确业务逻辑值 String tmpString = obj.toString(); // 第二步,生成能够精确反映业务逻辑的日时对象 tmp = java.sql.Date.valueOf(tmpString); } else { // 如果原始日时对象的类型不是java.sql.Date // 则转换过程分三步: // 第一步,生成一个不能精确表达业务逻辑的java.sql.Date对象 obj = new java.sql.Date(obj.getTime()); // 第二步和第三步与处理java.sql.Date类型的原始日时对象相同 // 第二步,取得java.sql.Date对象的精确业务逻辑值 String tmpString = obj.toString(); // 第三步,生成能够精确反映业务逻辑的日时对象 tmp = java.sql.Date.valueOf(tmpString); } return tmp; } }

 

上述方法可以简化为:

public static java.sql.Date getLocalDate(java.util.Date obj) { if (obj == null) return null; java.sql.Date tmp = null; if (java.sql.Date.class.equals(obj.getClass())) { tmp = java.sql.Date.valueOf(obj.toString()); } else { tmp = getLocalDate(new java.sql.Date(obj.getTime())); } return tmp; }

 

根据相同道理,也可以得到转换java.sql.Time与java.sql.Timestamp日时对象的方法。最后的版本如下:

public class DateTimeUtils { /** * 取得可以精确表达业务逻辑的日期对象<br> * * 如果原始日时对象的类型是java.sql.Date <br> * 则转换过程分两步:<br> * 第一步,取得java.sql.Date对象的精确业务逻辑值 <br> * 第二步,生成能够精确反映业务逻辑的日时对象<br> * * 如果原始日时对象的类型不是java.sql.Date<br> * 则转换过程分三步:<br> * 第一步,生成一个不能精确表达业务逻辑的java.sql.Date对象<br> * 第二步和第三步与处理java.sql.Date类型的原始日时对象相同 * * @param obj * 不精确的日期对象 * @return 精确的日时对象 */ public static java.sql.Date getLocalDate(java.util.Date obj) { if (obj == null) return null; java.sql.Date tmp = null; if (java.sql.Date.class.equals(obj.getClass())) { tmp = java.sql.Date.valueOf(obj.toString()); } else { tmp = getLocalDate(new java.sql.Date(obj.getTime())); } return tmp; } public static java.sql.Time getLocalTime(java.util.Date obj) { if (obj == null) return null; java.sql.Time tmp = null; if (java.sql.Time.class.equals(obj.getClass())) { tmp = java.sql.Time.valueOf(obj.toString()); } else { tmp = getLocalTime(new java.sql.Time(obj.getTime())); } return tmp; } public static java.sql.Timestamp getLocalTimestamp(java.util.Date obj) { if (obj == null) return null; java.sql.Timestamp tmp = null; if (java.sql.Timestamp.class.equals(obj.getClass())) { tmp = java.sql.Timestamp.valueOf(obj.toString()); } else { tmp = getLocalTimestamp(new java.sql.Timestamp(obj.getTime())); } return tmp; } }

 

我们可以这样使用:
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8:00")); // 使用三种方法产生的日时对象 java.util.Date obj1 = java.sql.Date.valueOf("1970-01-01"); java.util.Date obj2 = new java.util.Date(0L); java.util.Date obj3 = new java.sql.Date(0L); // 通过相同的方式转换 obj1 = DateTimeUtils.getLocalDate(obj1); obj2 = DateTimeUtils.getLocalDate(obj2); obj3 = DateTimeUtils.getLocalDate(obj3); // 得到了相同的精确业务逻辑 System.out.println(obj1.getTime());// -28800000 System.out.println(obj2.getTime());// -28800000 System.out.println(obj3.getTime());// -28800000 // 业务逻辑相同 System.out.println(obj1.toString()); // 1970-01-01 System.out.println(obj2.toString()); // 1970-01-01 // 语法比较准确 System.out.println(obj1.equals(obj2)); // true System.out.println(obj1.compareTo(obj2)); // 0

 

也可以在不同类型的日时对象之间转换,如:

TimeZone.setDefault(TimeZone.getTimeZone("GMT+8:00")); java.util.Date obj1 = java.sql.Timestamp.valueOf("2000-01-01 01:00:00"); // 将java.sql.Timestamp转换成java.sql.Time // 日期信息变成 1970-01-01 // 时间信息保留 java.util.Date obj2 = DateTimeUtils.getLocalTime(obj1); System.out.println(obj2.toString());// 01:00:00 System.out.println(obj2.getTime());// -25200000 // 将java.sql.Time转换成java.sql.Date // 日期信息保留 // 时间信息变成 00:00:00 java.util.Date obj3 = DateTimeUtils.getLocalDate(obj2); System.out.println(obj3.toString());// 1970-01-01 System.out.println(obj3.getTime());// -28800000

 

你可能感兴趣的:(java,jdk,api,String,null,Class)