java设计描述时间的类_java的TimeUtils或者DateUtils的编写心得

一、几种常见的日期和时间类介绍

介绍时间工具类不可避免必须要去触碰几个常见的日期和时间类,所以就简单介绍一下。

1、jdk1.8之前的日期时间类

a、Date类

我们可以通过new的方式生成一个Date对象,构造函数有参的和无参的,无参的是获取当前的系统的时间,Date这个类有不少过期的方法,而且Date是线程不安全的,所以当你需要考虑线程安全的情况时,Date其实使用起来有一定的局限。Date类中有fastTime成员变量,所以对于一个Date来说,存有时间戳的,这就给各种日期和时间对象的的转换提供了可能。

b、Calendar类

这是一个抽象类,其有多个子类补充了很多其他的功能。Calendar翻译过来就是日历,所以我们可以获取日期哪一年、哪一个月和哪一天,以及计算和获取某个月的第一天等等。Calendar也是线程不安全的,其中的set方法其实可以修改Calendar中的成员变量。

c、SimpleDateFormat

在java8之前我们一般习惯使用SimpleDateFormat这个类来进行时间转换成字符串的操作。但是这个类是线程不安全的,这就意味着我们在转换时候存在着转换风险,当然我们有解决的方法,一个是使用本地变量ThreadLocal存放SimpleDateFormat,如果使用Map来存放,其实在生成的时候还是有线程安全的问题,这里我们使用悲观锁来做一下限制,当然也可以使用线程安全的Map(Hashtable、synchronizedMap、ConcurrentHashMap)。

下面使用synchronized加锁:

/**锁对象*/

private static final Object lockObj = newObject();/**存放不同的日期模板格式的sdf的Map*/

private static Map> sdfMap = new HashMap>();/*** 返回一个ThreadLocal的sdf,每个线程只会new一次sdf

*

*@parampattern

*@return

*/

private static SimpleDateFormat getSdf(finalString pattern) {

ThreadLocal tl =sdfMap.get(pattern);//生成的时候我们需要去考虑线程问题,Map并没有做线程处理,我们可以

if (tl == null) {synchronized(lockObj) {

tl=sdfMap.get(pattern);if (tl == null) {//只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map

System.out.println("put new sdf of pattern " + pattern + " to map");//这里是关键,使用ThreadLocal替代原来直接new SimpleDateFormat

tl = new ThreadLocal() {

@OverrideprotectedSimpleDateFormat initialValue() {

System.out.println("thread: " + Thread.currentThread() + " init pattern: " +pattern);return newSimpleDateFormat(pattern);

}

};

sdfMap.put(pattern, tl);

}

}

}returntl.get();

}/*** ThreadLocal的原理是,获取一个静态变量的副本,这个其实是牺牲空间换取时间的案例

*

*@paramdate

*@parampattern

*@return

*/

public staticString format(Date date, String pattern) {returngetSdf(pattern).format(date);

}public static Date parse(String dateStr, String pattern) throwsParseException {returngetSdf(pattern).parse(dateStr);

}

当然我们也可以使用线程安全的map:

private static Map> sdfMap = new Hashtable>();

d、sql包中其实也有几个时间的类  java.sql.Date/Time/Timestamp

首先这几个类继承自util包中的Date类,相当于将java.util.Date分开表示了。Date表示年月日等信息。Time表示时分秒等信息。Timestamp多维护了纳秒,可以表示纳秒。平时用的不是很多。也是线程不安全的类。

2、java8以后的时间日期类

在java8以后新增加了date-time包

a、Instant

这个类在java8之前和之后的时间日期类中都提供了转换的方法,这样就能很明确的通过Instant这个中间变量实现,java8之前和之后的时间日期类的相互转换。但是我们需要注意的是,Instant主要维护的是秒和纳秒字段,可以表示纳秒范围,如果不符合转换条件,就会抛出异常。

以Date类为例,看一下源码:

public staticDate from(Instant instant) {try{return newDate(instant.toEpochMilli());

}catch(ArithmeticException ex) {throw newIllegalArgumentException(ex);

}

}/*** Converts this {@codeDate} object to an {@codeInstant}.

*

* The conversion creates an {@codeInstant} that represents the same

* point on the time-line as this {@codeDate}.

*

*@returnan instant representing the same point on the time-line as

* this {@codeDate} object

*@since1.8*/

publicInstant toInstant() {returnInstant.ofEpochMilli(getTime());

}

b、Clock

有获取当前时间的方法,也可以获取当前Instant,Clock是有时区或者说时区偏移量的。Clock是一个抽象类,其内部有几个子类继承自Clock。

几个抽象方法:

public abstractZoneId getZone();public abstractClock withZone(ZoneId zone);public longmillis() {returninstant().toEpochMilli();

}public abstract Instant instant();

c、ZoneId/ZoneOffset/ZoneRules

ZoneId和ZoneOffset都是用来代表时区的偏移量的,一般ZoneOffset表示固定偏移量,ZoneOffset表示与UTC时区偏移的固定区域(即UTC时间为标准),不跟踪由夏令时导致的区域偏移的更改;ZoneId表示可变区偏移,表示区域偏移及其用于更改区域偏移的规则夏令时。这里举一个简单的例子,美国东部时间,我们可以使用zoneId来表示,应为美国使用的是冬令时和夏令时的时候时间是有区别的,和中国的时差会有一个小时的差别。而ZoneRules跟踪区域偏移如何变化,时区的真正规则定义在ZoneRules中,定义了什么时候多少偏移量。

常用的几个:

//美东时间

public static final String TIMEZONE_EST_NAME = "US/Eastern";public static final ZoneId TIMEZONE_EST =ZoneId.of(TIMEZONE_EST_NAME);//北京时间

public static final String TIMEZONE_GMT8_NAME = "GMT+8";public static final ZoneId TIMEZONE_GMT8 =ZoneId.of(TIMEZONE_GMT8_NAME);public static final ZoneOffset BEIJING_ZONE_OFFSET =ZoneOffset.of("+08:00");public static final ZoneOffset STATISTIC_ZONE_OFFSET =ZoneOffset.of("+03:00");private static final ZoneId NEW_YORK_ZONE_ID = ZoneId.of("America/New_York");private static final ZoneId SHANGHAI_ZONE_ID = ZoneId.of("Asia/Shanghai");

d、LocalDateTime/LocalTime/LocalDate/ZoneDateTime

LocalDateTime/LocalTime/LocalDate都没有时区的概念,其中LocalDate主要是对日期的操作,LocalTime主要是对时间的操作,LocalDateTime则是日期和时间都会涉及:

jshell>LocalDate.now()

$46 ==> 2018-07-07jshell> LocalDate.of(2018, 3, 30)

$47 ==> 2018-03-30jshell>LocalTime.now()

$48 ==> 00:32:06.883656jshell> LocalTime.of(12,43,12,33333);

$49 ==> 12:43:12.000033333jshell>LocalDateTime.now()

$50 ==> 2018-07-07T00:32:30.335562400jshell> LocalDateTime.of(2018, 12, 30, 12,33)

$51 ==> 2018-12-30T12:33jshell> LocalDateTime.of(LocalDate.now(), LocalTime.now())

$52 ==> 2018-07-07T00:40:38.198318200

而ZoneDateTime会带有时区和偏移量的,可以看看他的成员变量:

/*** The local date-time.*/

private finalLocalDateTime dateTime;/*** The offset from UTC/Greenwich.*/

private finalZoneOffset offset;/*** The time-zone.*/

private final ZoneId zone;

可以看出这是集合了时间时区和偏移量的新的时间类。

二、常用的TimeUtils或者DateUtils的编写

importjava.text.ParseException;import java.time.*;importjava.time.format.DateTimeFormatter;importjava.time.temporal.ChronoUnit;importjava.util.Date;importjava.util.Hashtable;importjava.util.Map;/*** Created by hehuaichun on 2018/10/22.*/

public classTimeUtils {/*** 考虑港股和美股 采用GMT-1时区来确定报表日 即T日的报表包含北京时间T日9时至T+1日9时的数据*/

public static final ZoneId TIMEZONE_GMT_1 = ZoneId.of("GMT-1");public static final String TIMEZONE_EST_NAME = "US/Eastern";public static final ZoneId TIMEZONE_EST =ZoneId.of(TIMEZONE_EST_NAME);public static final String TIMEZONE_GMT8_NAME = "GMT+8";public static final ZoneId TIMEZONE_GMT8 =ZoneId.of(TIMEZONE_GMT8_NAME);/*** 常用时间转换格式*/

public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String DATE_NO_GAP_FORMAT = "yyyyMMdd";public static final String DATE_GAP_FORMAT = "yyyy-MM-dd";public static final String TIME_HH_MM_FORMAT = "HHmm";public static final Map DATE_TIME_FORMAT_MAP = new Hashtable() {

{

put(TIME_FORMAT, DateTimeFormatter.ofPattern(TIME_FORMAT));

put(DATE_NO_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_NO_GAP_FORMAT));

put(DATE_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_GAP_FORMAT));

put(TIME_HH_MM_FORMAT, DateTimeFormatter.ofPattern(TIME_HH_MM_FORMAT));

}

};/*** 根据format的格式获取相应的DateTimeFormatter对象

*

*@paramformat 时间转换格式字符串

*@return

*/

public staticDateTimeFormatter getDateTimeFormatter(String format) {if(DATE_TIME_FORMAT_MAP.containsKey(format)) {returnDATE_TIME_FORMAT_MAP.get(format);

}else{

DateTimeFormatter formatter=DateTimeFormatter.ofPattern(format);

DATE_TIME_FORMAT_MAP.put(format, formatter);returnformatter;

}

}/*** 获取当前日期的开始时间

*

*@paramzoneId 时间偏移量

*@return

*/

public staticLocalDateTime todayStart(ZoneId zoneId) {return startOfDay(0, zoneId);

}/*** 获取当前的ZoneDateTime

*

*@paramzoneId 时区偏移量

*@return

*/

public staticZonedDateTime now(ZoneId zoneId) {returnZonedDateTime.now(zoneId);

}/*** 获取当前日期的开始时间ZonedDateTime

*

*@paramdate 日期

*@paramzoneId 时区偏移量

*@return

*/

public staticZonedDateTime localDateToZoneDateTime(LocalDate date, ZoneId zoneId) {returndate.atStartOfDay(zoneId);

}/*** 获取当前日期的开始时间

*

*@paramdateTime

*@return

*/

public staticLocalDateTime startOfDay(ZonedDateTime dateTime) {returndateTime.truncatedTo(ChronoUnit.DAYS).toLocalDateTime();

}/*** 获取今天后的指定天数的开始时间

*

*@paramplusDays 当前多少天后

*@paramzoneId 时区偏移量

*@return

*/

public static LocalDateTime startOfDay(intplusDays, ZoneId zoneId) {returnstartOfDay(now(zoneId).plusDays(plusDays));

}/*** 获取指定日期的后几个工作日的时间LocalDate

*

*@paramdate 指定日期

*@paramdays 工作日数

*@return

*/

public static LocalDate plusWeekdays(LocalDate date, intdays) {if (days == 0) {returndate;

}if (Math.abs(days) > 50) {throw new IllegalArgumentException("days must be less than 50");

}int i = 0;int delta = days > 0 ? 1 : -1;while (i

date=date.plusDays(delta);

DayOfWeek dayOfWeek=date.getDayOfWeek();if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek !=DayOfWeek.SUNDAY) {

i+= 1;

}

}returndate;

}/*** 获取指定日期的后几个工作日的时间ZoneDateTime

*

*@paramdate

*@paramdays

*@return

*/

public static ZonedDateTime plusWeekdays(ZonedDateTime date, intdays) {returnplusWeekdays(date.toLocalDate(), days).atStartOfDay(date.getZone());

}/*** 获取当前月份的第一天的时间ZoneDateTime

*

*@paramzoneId

*@return

*/

public staticZonedDateTime firstDayOfMonth(ZoneId zoneId) {return now(zoneId).withDayOfMonth(1);

}/*** 将Date转成指定时区的Date

*

*@paramdate

*@return

*/

public staticDate dateToDate(Date date, ZoneId zoneId) {

LocalDateTime dt=LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());returntoDate(ZonedDateTime.of(dt, zoneId));

}/*** 将LocalDate转成Date

*

*@paramdate

*@return

*/

public staticDate toDate(LocalDate date) {returnDate.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());

}/*** ZonedDateTime 转换成Date

*

*@paramdateTime

*@return

*/

public staticDate toDate(ZonedDateTime dateTime) {returnDate.from(dateTime.toInstant());

}/*** String 转换成 Date

*

*@paramdate

*@paramformat

*@return*@throwsParseException*/

public static Date stringToDate(String date, String format, ZoneId zoneId) throwsParseException {

DateTimeFormatter formatter=getDateTimeFormatter(format).withZone(zoneId);

Instant instant=Instant.from(formatter.parse(date));returnDate.from(instant);

}/*** 将Date转成相应的时区的localDate

*

*@paramdate

*@paramzoneId

*@return

*/

public staticLocalDate toLocalDate(Date date, ZoneId zoneId) {returndate.toInstant().atZone(zoneId).toLocalDate();

}/*** 将Instant转成指定时区偏移量的localDate

*

*@paraminstant

*@paramzoneId

*@return

*/

public staticLocalDate toLocalDate(Instant instant, ZoneId zoneId) {returninstant.atZone(zoneId).toLocalDate();

}/*** 将Instant转换成指定时区偏移量的localDateTime

*@paraminstant

*@paramzoneId

*@return

*/

public staticLocalDateTime toLocalDateTime(Instant instant, ZoneId zoneId){returninstant.atZone(zoneId).toLocalDateTime();

}/*** 将Instant转成系统默认时区偏移量的LocalDateTime

*@paraminstant

*@return

*/

public staticLocalDateTime toLocalDateTime(Instant instant){returntoLocalDateTime(instant, ZoneId.systemDefault());

}/*** 将ZoneDateTime 转成 指定时区偏移量的LocalDateTime

*@paramzonedDateTime 时间

*@paramzoneId 指定时区偏移量

*@return

*/

public staticLocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId){returnzonedDateTime.toInstant().atZone(zoneId).toLocalDateTime();

}/***将ZoneDateTime 转成 LocalDateTime

*@paramzonedDateTime

*@return

*/

public staticLocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime){returnzonedDateTime.toLocalDateTime();

}/*** String 转成 ZoneDateTime

* 需要类似 yyyy-MM-dd HH:mm:ss 需要日期和时间信息完整信息

*

*@paramdate

*@paramformat

*@paramzoneId

*@return

*/

public staticZonedDateTime stringToZoneDateTime(String date, String format, ZoneId zoneId) {

DateTimeFormatter formatter=getDateTimeFormatter(format).withZone(zoneId);returnZonedDateTime.parse(date, formatter);

}/*** 将时间戳long转成ZonedDateTime

*

*@paramtimeStamp

*@paramzoneId

*@return

*/

public static ZonedDateTime longToZoneDateTime(longtimeStamp, ZoneId zoneId) {returnZonedDateTime.from(Instant.ofEpochMilli(timeStamp).atZone(zoneId));

}/*** 两个时区的zoneDateTime相互转换

*

*@paramzonedDateTime 需要转换的如期

*@paramzoneId 转换成的ZoneDateTime的时区偏移量

*@return

*/

public staticZonedDateTime zonedDateTimeToZoneDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId) {returnZonedDateTime.ofInstant(zonedDateTime.toInstant(), zoneId);

}/*** Date 转成 指定时区偏移量的ZoneDateTime

*@paramdate

*@paramzoneId

*@return

*/

public staticZonedDateTime toZonedDateTime(Date date, ZoneId zoneId){returndate.toInstant().atZone(zoneId);

}/*** LocaldateTime 转成 指定时区偏移量的ZonedDateTime

*@paramlocalDateTime 本地时间

*@paramzoneId 转成ZonedDateTime的时区偏移量

*@return

*/

public staticZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId){returnlocalDateTime.atZone(zoneId);

}/*** Date装换成String

*

*@paramdate 时间

*@paramformat 转化格式

*@return

*/

public staticString dateToString(Date date, String format, ZoneId zoneId) {

DateTimeFormatter formatter=getDateTimeFormatter(format).withZone(zoneId);returnformatter.format(date.toInstant());

}/*** ZoneDateTime 转换成 String

*

*@paramdateTime

*@paramzoneId localDateTime所属时区

*@return

*/

public staticString zoneDateTimeToString(ZonedDateTime dateTime, String format, ZoneId zoneId) {

DateTimeFormatter formatter=getDateTimeFormatter(format).withZone(zoneId);returndateTime.format(formatter);

}/*** LocalDateTime 转成 String

*

*@paramlocalDateTime

*@paramformat

*@return

*/

public staticString localDateTimeToString(LocalDateTime localDateTime, String format){

DateTimeFormatter formatter=getDateTimeFormatter(format);returnlocalDateTime.format(formatter);

}/*** 将ZonedDateTime转成时间戳long

*

* @parm zonedDateTime

*@return

*/

public static longzoneDateTimeToLong(ZonedDateTime zonedDateTime) {returnzonedDateTime.toInstant().toEpochMilli();

}/*** 将LocalDateTime转成时间戳long

*@paramlocalDateTime

*@paramzoneId

*@return

*/

public static longtoLong(LocalDateTime localDateTime, ZoneId zoneId){returnzoneDateTimeToLong(localDateTime.atZone(zoneId));

}

}

三、编写的总结

1、首先我们尽量使用线程安全的时间类,也就是说尽量使用java8以后的几种时间类。从源码可以看出,java8之后的几个时间类是用final修饰的,线程安全。

2、我们要利用好静态变量,也就是不仅在使用的全局变量的时候需要线程安全的问题,还需要考虑时间空间的代价。其实SimpleDateTimeFormat和DateTImeFormatter的生成会消耗不少资源。

3、Date类中含有时间戳,Instant有两个成员变量seconds和nanos变量,这样Instant就作为一个中间量,作用很大,不仅提供了java8之前的时间类和java8之后时间类的转换,还提供了其他很多有用的方法。

4、ZonedDateTime含有时区偏移变量,其他的如Date、LocalDateTime、LocalDate、LocalTime、Instant都不含有时区偏移变量,但是Date想要转换成java8之后的时间类或者使用DateTimeFormatter转换时,需要提供ZoneId或者ZoneOffset,类似这种 date.toInstant().atZone(zoneId)。整体思路实现Date -> Instant -> ZonedDateTime -> 其他时间类。

5、ZonedDateTime是有日期和时间的,我们在DateTimeFormatter的时候需要注意, ZonedDateTime转成字符串非常的灵活,但是字符串转成ZonedDateTime的时候需要提供类似yyyy-MM-dd HH:mm:ss这种格式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值