java时间日期总结

java时间日期总结

[TOC]

Java早期的时间API

Date

Date既能处理时间,又能处理日期,虽然如此,但是在很多方面天生缺陷,比如在jdk8之前对java的日期类的吐槽主要在这几个方面:

  • Java的日期/时间类的定义不一致,在java.util和java.sql的包中都有日期类,格式化和解析的类在java.text包里面。
  • java.util.Date同时包含日期和时间,但是java.sql.Date只包含日期。
  • 所有的日期类都是可变的,因此都不是线程安全的,最大的问题之一。
  • 日期类不提供国际化,不支持时区设置,因此引入了java.util.Calendar和java.util.TimeZone,但存在上面的问题。

date中非常多的方法都已经废弃,标记成了@Deprecated
现在Date的定位是在时间轴上表示唯一一个时刻,它代表一个绝对的时间,也就是说不管在什么地方,在什么时区,当前时间的Date都是一样的,都是从1970年1月1日0点0分GMT时间起,到目前这一刻的毫秒数。
目前还在用的方法有如下几个:

  • public long getTime() :返回内部存储的毫秒数
  • public void setTime(long time):重新设置内存的毫秒数
  • public boolean before(Date when):比较给定的时刻是否早于当前 Date 实例
  • public boolean after(Date when):比较给定的时刻是否晚于当前 Date 实例
  • public Instant toInstant(): jdk8新增加的方法,转成java8引入的Instant类型
  • public static Date from(Instant instant): jdk8新增加的方法,从Instant转成Date
Date date = new java.util.Date();
System.out.println(date);   //Tue Mar 05 11:04:34 CST 2019
System.out.println(date.getTime());   //1551755808311
System.out.println(date.toGMTString()); //5 Mar 2019 03:23:05 GMT 这个也可以转成GMT时间,不过toGMTString已经废了

Date date1 = new java.sql.Date(119,2,5);    //对应的年份 1900+119,月份2+1
System.out.println(date1);  //2019-03-05 

Date date2 = new java.sql.Date(System.currentTimeMillis());
System.out.println(date2);  //2019-03-05  java.sql.Date 只包含日期

Instant instant = date.toInstant();
System.out.println(instant);    //2019-03-05T03:21:36.191Z  GMT时间,比北京时间慢8小时

Date date3 = Date.from(instant);    //Tue Mar 05 11:27:31 CST 2019

Calendar

Calendar 用来表示日历,是对绝对时间Date的一种描述,我们看Date对象的时候肯定不会去看它的毫秒数,而是看某年某月某日某时某分某秒这种形式,日历随着地区和时区的不同而不同,比如美国日期标准格式月/日/年,中国标准是年/月/日,再比如同一个Date,在中国东八区自然比日本东九区慢一个小时。

Calendar可以对日期进行加减操作,国际化,设置时区等,它是一个抽象类,不能实例化,所以提供了几个工厂方法获取对象。

public static Calendar getInstance(){    
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

public static Calendar getInstance(TimeZone zone) {    
    return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}

public static Calendar getInstance(Locale aLocale){   
    return createCalendar(TimeZone.getDefault(), aLocale);
}

public static Calendar getInstance(TimeZone zone,Locale aLocale){    
    return createCalendar(zone, aLocale);
}

createCalendar方法需要提供两个参数,一个是时区,一个是语言,没有的话使用默认的设置,因为对于不同的国家,对于时刻的年月日的格式是不一样的

Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());     //Tue Mar 05 13:29:41 CST 2019 calendar.getTime() 返回一个Date对象
calendar.add(Calendar.YEAR,1);
System.out.println(calendar.getTime());     //Tue Mar 05 13:29:41 CST 2020 calendar.getTime() 返回一个Date对象

Calendar calendar1 = Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.FRANCE);
System.out.println(calendar1.getTime());    //Tue Mar 05 13:29:41 CST 2019 
//可以发现上面两个calendar的gettime值是一样的,calendar1对象里面的时间是0时区当前的格林威治时间,这个时间和当前东八区的格林威治时间是一样的(就是那个毫秒值是相等的),而Date默认的toString方法(默认调用`calendar1.getTime().toString()`)是按照当前的默认时区转换日历的,所以看到的都是东八区的日期
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
//TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(calendar1.getTime());    // Tue Mar 05 06:05:12 GMT 2019 当设置默认时区是0时区的时候,这个时间就和之前差了8小时
//如果要获取当前时间直接用new Date就成了,和Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.FRANCE);一毛一样,只有需要把一个date表示成日历的时候才需要用Calendar和TimeZone

//源码 通过毫秒数构建了一个date对象
public final Date getTime() {    
    return new Date(getTimeInMillis());
}

DateFormat

DateFormat用于日期的格式转换,是一个抽象类,也需要通过工厂方法产生实例对象:

  • public final static DateFormat getTimeInstance() //只处理时间的转换
  • public final static DateFormat getDateInstance() //只处理日期的转换
  • public final static DateFormat getDateTimeInstance() //既可以处理时间,也可以处理日期
Date date = new Date();

DateFormat dateFormat = DateFormat.getDateInstance();
System.out.println(dateFormat.format(date));    //2019-3-5

DateFormat timeFormat = DateFormat.getTimeInstance();
System.out.println(timeFormat.format(date));    //14:24:28

DateFormat dateTimeFormat=DateFormat.getDateTimeInstance();
System.out.println(dateTimeFormat.format(date));    //2019-3-5 14:24:28
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); //设置时区
System.out.println(dateTimeFormat.format(date));    //2019-3-5 14:24:28

DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));  //2019-03-05 14:24:28
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); //设置时区
System.out.println(simpleDateFormat.format(date));  //2019-03-05 06:24:28

一般都是使用SimpleDateFormat自定义输出格式,SimpleDateFormat线程不安全,为什么不安全呢?
因为SimpleDateFormat内部有一个Calendar对象,用来存储和这个SimpleDateFormat相关的日期信息,simpleDateFormat.format(date)simpleDateFormat.parse(str_date),这些date都是Calendar来存储的,这会带来一个问题,如果SimpleDateFormat定义成了static,那么多个thread将会共享这个SimpleDateFormat,同时共享Calendar的引用,造成一个线程的数据被另一个线程覆盖或清除。

// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
                            FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);
    ...
}
Calendar establish(Calendar cal) {
    ...
    cal.clear();
    ...
}

解决办法

  1. 将SimpleDateFormat定义成局部变量,但是每调用一次都会创建一个对象,比较浪费
  2. 加同步锁synchronize,但是可能会在这个地方造成瓶颈,影响性能
  3. 使用joda-time,或者java8的时间类,很强大,特别推荐
  4. 使用ThreadLocal,每个线程有一个自己的SimpleDateFormat对象,因为每一个Thread,都是线性执行的,所以不会出现竞争Calendar的情况。
public class DateUtilTest {
    private static final Object lockObj = new Object();
    private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<>();


    private static SimpleDateFormat getSimpleDateFormat(final String pattren){
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattren);
        if (tl == null){
            synchronized (lockObj){
                tl = sdfMap.get(pattren);
                if (tl == null){
                    tl = ThreadLocal.withInitial(() -> new SimpleDateFormat(pattren));
                    sdfMap.put(pattren, tl);
                }
            }
        }
        return tl.get();
    }
}

Java8新引入的API

Instant,LocalDate,LocalTime,LocalDateTime都是不可变类,线程安全,放心使用

Instant

Instant是不可变类,用来代替Date,表示一个时间戳,一个绝对的时间,和时区,地区无关,可以表示纳秒级别的时间,Date只能到毫秒。

Instant instant = Instant.now();
System.out.println(instant);    //2019-03-05T08:02:09.391Z

Instant instant1 = Instant.now(Clock.systemDefaultZone());
System.out.println(instant1);   //2019-03-05T08:02:09.481Z

Instant instant2 = Instant.ofEpochSecond(60,1234);
System.out.println(instant2);   //1970-01-01T00:01:00.000001234Z

Instant instant3 = Instant.ofEpochMilli(1000);
System.out.println(instant3);   //1970-01-01T00:00:01Z

Date date1 = Date.from(instant3);
System.out.println(date1);  //Thu Jan 01 08:00:01 CST 1970

Date date = new Date();
Instant instant4 = date.toInstant();
System.out.println(instant4);   //2019-03-05T08:02:46.703Z

Instant.parse("1970-01-01T00:00:01Z");

LocalDate

LocalDate 是不可变类,线程安全,用来处理日期

LocalDate localDate = LocalDate.now();
System.out.println(localDate);  //2019-03-05
localDate.atStartOfDay();   //2019-03-05T00:00
localDate.getDayOfMonth();  //5
localDate.getDayOfYear();   //64
localDate.getDayOfWeek();   //TUESDAY
//获取到当前时区今天的开始时间并转成Instant,因为上海是东八区时间,所以当天开始时间是0点,转成Instant后是标准的GMT时间,所以看上去慢了8小时
System.out.println(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).getZone());  //Asia/Shanghai
System.out.println(localDate.atStartOfDay().atZone(ZoneId.systemDefault()));    //2019-03 -07T00:00+08:00[Asia/Shanghai]
System.out.println(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());    //2019-03-06T16:00:00Z

LocalDate localDate1 = LocalDate.of(2019, 3, 31);
System.out.println(localDate1); //2019-03-31
localDate1.plusYears(-1).plusMonths(-1).plusDays(-1);   //2018-02-27
localDate1.minusYears(-1).minusMonths(-1).minusDays(-1);    //2020-05-01
localDate1.isLeapYear();    //false
localDate1.lengthOfYear();  //365

//Date转LocalDate
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
LocalDate localDate2 = localDateTime.toLocalDate();

LocalDate localDate3 = LocalDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalDate()
//LocalDate转Date
Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())

LocalTime

不可变类,用来处理时间,提供 小时,分钟,秒,纳秒的处理,默认使用系统的默认时区处理时间

LocalTime localTime = LocalTime.now();
System.out.println(localTime);  //16:50:15.309

LocalTime localTime1 = LocalTime.of(12,34,56,789);
System.out.println(localTime1); //12:34:56.000000789

// Date转LocalTime
System.out.println(LocalDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalTime());   //16:50:15.309

//LocalTime转Date
Date.from(LocalTime.now().atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant())   //Tue Mar 05 16:56:09 CST 2019

LocalDateTime

不可变类,表示日期和时间,默认使用系统默认时区表示GMT时间

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);  //2019-03-05T16:59:48.227

LocalDateTime localDateTime1 = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println(localDateTime1); //2019-03-05T16:59:48.227

LocalDateTime localDateTime2 = LocalDateTime.now(ZoneId.of("GMT-5"));
System.out.println(localDateTime2); //2019-03-05T03:59:48.227

LocalDateTime localDateTime3 = LocalDateTime.ofInstant(Instant.now(),ZoneId.of("GMT"));
System.out.println(localDateTime3); //2019-03-05T09:01:44.650

//Date 转 LocalDateTime
LocalDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault())
//LocalDateTime 转  Date
Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant())

ZonedDateTime

不可变类,绑定了时区的LocalDateTime,内部包括一个LocalDateTime的实例,和时区信息ZoneId,时区偏移量ZoneOffset

ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);  //2019-03-05T17:08:00.309+08:00[Asia/Shanghai]
ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("GMT-8"));   //2019-03-05T01:37:57.640-08:00[GMT-08:00]
System.out.println(zonedDateTime1);

//获取所有时区名称
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String str : zoneIds){
    if (str.startsWith("Asia"))
        System.out.println(str);
}

DateTimeFormatter

不可变类,线程安全,用来格式化时间

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(dateTimeFormatter.format(localDateTime));    //2019-03-05 17:49:40


String str = "2019-03-05 17:48:14";
LocalDateTime localDateTime1 = LocalDateTime.parse(str, dateTimeFormatter);
System.out.println(localDateTime1); //2019-03-05T17:48:14

Period Duration

计算时间的差值

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("GMT-8"));
System.out.println(localDateTime1);

//时间的差值
Duration duration = Duration.between(localDateTime, localDateTime1);
System.out.println(duration.toHours());

//日期的差值
Period period = Period.between(localDateTime.toLocalDate(), localDateTime1.toLocalDate());
System.out.println(period.getDays());

Joda -Time

<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.1</version>
</dependency>

Joda设计出来是为了替代Date和Calendar这一套的,jdk8重写了java的日期库,引入了java.time包,Joda-Time的作者Stephen Colebourne和Oracle一起共同参与了这些API的设计和实现。
** 核心类**

  • Instant 表示一个时间轴上一个瞬时的时间,和时区无关
  • DateTime 替换JDK的Calendar类 处理时区用这个类
  • LocalDate 只包含日期部分
  • LocalTime 只包含时间部分
  • LocalDateTime 日期-时间
org.joda.time.Instant instant = new org.joda.time.Instant();
System.out.println(instant);    //2019-03-07T03:05:14.904Z
System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("Asia/Tokyo")).print(instant));   //2019-03-07 03:05:14

org.joda.time.LocalDate localDate = org.joda.time.LocalDate.now();
System.out.println(localDate);  //2019-03-07
System.out.println(localDate.toString("MM-dd-yyyy"));   //03-07-2019

org.joda.time.LocalTime localTime = org.joda.time.LocalTime.now();
System.out.println(localTime);  //11:07:06.920
System.out.println(localTime.toString("HH:mm:ss")); //11:07:06

org.joda.time.LocalDateTime localDateTime = org.joda.time.LocalDateTime.now();
System.out.println(localDateTime);  //2019-03-07T11:08:30.802
System.out.println(localDateTime.toString("yyyy-MM-dd HH:mm:ss"));  //2019-03-07 11:08:30

System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("Asia/Tokyo")).withLocale(Locale.JAPAN).print(localDate));   //2019-03-07 ��:��:��
System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("Asia/Tokyo")).print(localTime));   //����-��-�� 11:28:21
System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("GMT")).print(localDateTime));   //2019-03-07 11:28:21

DateTime dateTime = new DateTime();
System.out.println(dateTime);   //2019-03-07T11:13:16.440+08:00
DateTime dateTime1 = new DateTime(new Date());
System.out.println(dateTime1);  //2019-03-07T11:13:16.440+08:00
DateTime dateTime2 = new DateTime(System.currentTimeMillis());
System.out.println(dateTime2);  //2019-03-07T11:13:16.455+08:00
DateTime dateTime3 = new DateTime(2019,3,7,11,13,16,455);
System.out.println(dateTime3);  //2019-03-07T11:15:05.455+08:00
DateTime dateTime4 = new DateTime("2019-03-07T11:13:16.455+08:00");
System.out.println(dateTime4);  //2019-03-07T11:13:16.455+08:00

System.out.println(dateTime.dayOfWeek().getAsText(Locale.FRANCE));  //jeudi
System.out.println(dateTime.dayOfWeek().getAsText(Locale.KOREA));   //목요일

System.out.println(dateTime.dayOfMonth().roundCeilingCopy().toString("yyyy-MM-dd HH:mm:ss"));   //2019-03-08 00:00:00
System.out.println(dateTime.dayOfMonth().roundFloorCopy().toString("yyyy-MM-dd HH:mm:ss"));     //2019-03-07 00:00:00
System.out.println(dateTime.hourOfDay().roundCeilingCopy().toString("yyyy-MM-dd HH:mm:ss"));    //2019-03-07 12:00:00
System.out.println(dateTime.minuteOfHour().roundCeilingCopy().toString("yyyy-MM-dd HH:mm:ss")); //2019-03-07 11:23:00

System.out.println(dateTime.withZone(DateTimeZone.forID("Asia/Tokyo")).toString("yyyy-MM-dd HH:mm:ss"));    //2019-03-07 12:25:58

参考链接

https://segmentfault.com/q/1010000000178306/a-1020000000178984
https://juejin.im/post/5adb06cdf265da0b7b3579fb
https://blog.csdn.net/csdn_ds/article/details/72984646
https://juejin.im/post/5addc7a66fb9a07aa43bd2a0
https://www.jianshu.com/p/efdeda608780

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容