java日期詳解

来源:http://www.cnblogs.com/daochang/archive/2017/12/17/8052951.html
-Advertisement-
Play Games

[TOC] 一、簡介 java中的日期處理一直是個問題,沒有很好的方式去處理,所以才有第三方框架的位置比如joda。 文章主要對java日期處理的詳解,用1.8可以不用joda。 1. 相關概念 首先我們對一些基本的概念做一些介紹,其中可以將GMT和UTC表示時刻大小等同。 1.1 UT時間 UT反 ...


[TOC]

一、簡介

java中的日期處理一直是個問題,沒有很好的方式去處理,所以才有第三方框架的位置比如joda。
文章主要對java日期處理的詳解,用1.8可以不用joda。

1. 相關概念

首先我們對一些基本的概念做一些介紹,其中可以將GMT和UTC表示時刻大小等同。

1.1 UT時間
UT反應了地球自轉的平均速度。是通過觀測星星來測量的。
具體可以看參考1.

1.2 UTC
UTC是用原子鐘時間做參考,但保持和UT1在0.9秒內的時間,也就是說定時調整。現在電腦一般用的網路時間協議NTP(Network Time Protocol)是用於互聯網中時間同步的標準互聯網協議。NTP的用途是把電腦的時間同步到某些時間標準。目前採用的時間標準是世界協調時UTC(Universal Time Coordinated)。如果電腦不聯網即使再精確也是不准的,因為UTC會進行調整,而且一般走的時間也是不精確的。附不能上網的電腦如何同步時間資料可以看參考2.

1.3 GMT

Today, GMT is used as the UK’s civil time, or UTC. GMT has been referred to as “UT1", which directly corresponds to the rotation of the Earth, and is subject to that rotation’s slight irregularities. It is the difference between UT1 and UTC that is kept > below 0.9s by the application of leap seconds.

簡單點理解就是GMT是完全符合地球自轉的時間,也被稱為UT1。UTC時間是原子鐘時間,當UTC時間比GMT時間相0.9秒的時候,UTC會做調整與GMT一致,也就是說UTC時間和GMT的時間差不會大於0.9秒。

1.4 ISO 8601
一種時間交換的國際格式。
有些介面調用表示UTC/GMT時間的時候用"yyyy-MM-dd'T'HH:mm:ss'Z'"格式顯示。
帶毫秒格式"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"。
joda中實現如下

// Alternate ISO 8601 format without fractional seconds
private static final String ALTERNATIVE_ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";private static DateFormat getAlternativeIso8601DateFormat() {
        SimpleDateFormat df = new SimpleDateFormat(ALTERNATIVE_ISO8601_DATE_FORMAT, Locale.US);
        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
        return df;
}

1.5 RFC 822
STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES
其中ARPA網路其實就是互聯網的前身。
有些地方會用RFC 822里的時間格式,格式如下

 date-time = [ day "," ] date time ; dd mm yy
                                                     ; hh:mm:ss zzz
//第二個相當於現在格式
"EEE, dd MMM yyyy HH:mm:ss z"

阿裡oss裡面有些頭設置採用該格式。
joda中實現如下

// RFC 822 Date Format
private static final String RFC822_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
private static DateFormat getRfc822DateFormat() {
        SimpleDateFormat rfc822DateFormat =
                new SimpleDateFormat(RFC822_DATE_FORMAT, Locale.US);
        rfc822DateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
        return rfc822DateFormat;
}

在4,5中創建SimpleDateFormat的Locale.US可以決定格式字元串某些字元的代替用哪個語言,比如EEE等

SimpleDateFormat df1=new SimpleDateFormat("GGGG yyyy/MMMM/dd HH:mm:ss EEE aaa zzzz",Locale.CHINA);
SimpleDateFormat df2=new SimpleDateFormat("GGGG yyyy/MMMM/dd HH:mm:ss EEE aaa zzzz",Locale.US);
//公元 2016/三月/27 23:32:10 星期日 下午 中國標準時間
//AD 2016/March/27 23:32:10 Sun PM China Standard Time

1.6 gregorian Calendar, julian Calendar
這是兩種曆法,我們一般用的通用的gregorian Calendar
擴展可以看參考內容

二、 相關類型的分析比較

1. jdk1.8之前

主要的類有記錄時間戳的Date,時間和日期進行轉換的Calendar,用來格式化和解析時間字元串的DateFormat

1.1 java.util.Date

使用前要註意時間表示的規則。

In all methods of class Date that accept or return
year, month, date, hours, minutes, and seconds values, the
following representations are used:


  • A year y is represented by the integer
    y - 1900.
  • A month is represented by an integer from 0 to 11; 0 is January,
    1 is February, and so forth; thus 11 is December.
  • A date (day of month) is represented by an integer from 1 to 31
    in the usual manner.
  • An hour is represented by an integer from 0 to 23. Thus, the hour
    from midnight to 1 a.m. is hour 0, and the hour from noon to 1
    p.m. is hour 12.
  • A minute is represented by an integer from 0 to 59 in the usual manner.
  • A second is represented by an integer from 0 to 61; the values 60 and
    61 occur only for leap seconds and even then only in Java
    implementations that actually track leap seconds correctly. Because
    of the manner in which leap seconds are currently introduced, it is
    extremely unlikely that two leap seconds will occur in the same
    minute, but this specification follows the date and time conventions
    for ISO C.

還有這個類有很多過期方法不推薦使用,很多已經被Calendar代替。

1.1.1 構造方法

註釋中說The class Date represents a specific instant in time, with millisecond precision.
也就是說這個類代表某個時刻的毫秒值,既然是毫秒值也就說需要有一個參考值。
當我們創建一個Date的時候獲取的是哪一個毫秒值?

public Date() {
        this(System.currentTimeMillis());
 }
 public Date(long date) {
      fastTime = date;
}

System.currentTimeMillis()是本地方法,註釋為the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.但是註釋中也說這個可能會因為操作系統的時間而不准。有些操作系統不一定是用毫秒錶示的。這個時間都是用的UTC時間,不和時區有關的,這個無關的意思是同一時刻每個時區下獲得的值應該是一致的,可以簡單用程式驗證一下獲取的時間表達內容。

long time = System.currentTimeMillis();
System.out.println(time=(time/1000));
System.out.println("秒:"+ time%60);
System.out.println(time=(time/60));
System.out.println("分鐘:"+time%60);
System.out.println(time=(time/60));
System.out.println("小時:"+time%24);

源碼解析可以看參考的相關內容
可以理解成和UTC的1970年1月1日零點的差值。而fastTime就是Date類保存這個時刻的變數。

1.1.2 成員變數
Date對象列印出來是本地時間,而構造方法是沒有時區體現的。那麼哪裡體現了時區呢?
下麵是Date的成員變數

1.gcal
獲取的是以下的對象。其中並沒有自定義欄位。可以說只是一個gregorian(西曆)時間工廠獲取CalendarDate的子類。

2.jcal
儒略歷相關的對象。
在以下方法中用到

private static final BaseCalendar getCalendarSystem(BaseCalendar.Date cdate) {
        if (jcal == null) {
            return gcal;
        }
        if (cdate.getEra() != null) {
            return jcal;
        }
        return gcal;
    }
    synchronized private static final BaseCalendar getJulianCalendar() {
        if (jcal == null) {
            jcal = (BaseCalendar) CalendarSystem.forName("julian");
        }
        return jcal;
    }

當時間戳在以下情況下用儒略歷,並且,在用到的時候會自動設置儒略歷,所以在clone的時候也沒有這個參數。所以這個可以忽略。

 private static final BaseCalendar getCalendarSystem(int year) {
        if (year >= 1582) {
            return gcal;
        }
        return getJulianCalendar();
    }
    private static final BaseCalendar getCalendarSystem(long utc) {
        // Quickly check if the time stamp given by `utc' is the Epoch
        // or later. If it's before 1970, we convert the cutover to
        // local time to compare.
        if (utc >= 0
            || utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER
                        - TimeZone.getDefaultRef().getOffset(utc)) {
            return gcal;
        }
        return getJulianCalendar();
    }

3.fastTime
保存了一個時間戳表示時刻。最重要的參數。創建Date就是對這個值的賦值。

4.cdate
保存了時間相關內容,包括時區,語言等

    public static final int FIELD_UNDEFINED = -2147483648;
    public static final long TIME_UNDEFINED = -9223372036854775808L;
    private Era era;
    private int year;
    private int month;
    private int dayOfMonth;
    private int dayOfWeek;
    private boolean leapYear;
    private int hours;
    private int minutes;
    private int seconds;
    private int millis;
    private long fraction;
    private boolean normalized;
    private TimeZone zoneinfo;
    private int zoneOffset;
    private int daylightSaving;
    private boolean forceStandardTime;
    private Locale locale;

5.defalutCenturyStart
這個值可以忽略,在過期方法中用到。

@Deprecated
    public static long parse(String s) {
   ... ...
            // Parse 2-digit years within the correct default century.
            if (year < 100) {
                synchronized (Date.class) {
                    if (defaultCenturyStart == 0) {
                        defaultCenturyStart = gcal.getCalendarDate().getYear() - 80;
                    }
                }
                year += (defaultCenturyStart / 100) * 100;
                if (year < defaultCenturyStart) year += 100;
            }
            ... ...
    }

6.serialVersionUID
驗證版本一致性的UID

7.wtb
保存toString格式化用到的值

8.ttb
保存toString 格式化用到的值

1.1.3 主要方法

主要是比較方法和設置方法。由於項目遺留和數據層的原因限制這個類用到還是比較多。

1.2 java.util.Calendar

其實主要也是其中保存的毫秒值time欄位

下麵是我們常用的方法,用了預設的時區和區域語言

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

國內環境預設GregorianCalendar,但是TH-th用的BuddhistCalendar等

一些坑:

  1. set(int,int,int,int,int,int)方法
    方法不能設置毫秒值,所以當用getInstance後即使用設置相同的值,最後毫秒值也是不一致的。所以如果有需要,將MILLISECOND清零。

  2. set,add,get,roll
    set方法不會馬上計算時間,指是修改了對應的成員變數,只有get()、getTime()、getTimeInMillis()、add() 或 roll()的時候才會做調整

        //2000-8-31
        Calendar cal1 = Calendar.getInstance();
        cal1.set(2000, 7, 31, 0, 0 , 0);
        //應該是 2000-9-31,也就是 2000-10-1
        cal1.set(Calendar.MONTH, Calendar.SEPTEMBER);
        //如果 Calendar 轉化到 2000-10-1,那麼現在的結果就該是 2000-10-30
        cal1.set(Calendar.DAY_OF_MONTH, 30);
        //輸出的是2000-9-30,說明 Calendar 不是馬上就刷新其內部的記錄
        System.out.println(cal1.getTime());

    也就是說多次設置的時候如果中間有需要調整的時間,但是實際是不會做調整的。所以儘量將無法確定的設置之後不要再進行其他調整,防止最後實際值與正常值不准。

add方法會馬上做時間修改
roll與add類似,但是roll不會修改更大的欄位的值。

1.3 java.text.SimpleDateFormat

創建設置pattern字元串,可以表示的格式如下

日期格式是不同步的。建議為每個線程創建獨立的格式實例。如果多個線程同時訪問一個格式,則它必須是外部同步的。
SimpleDateFormat 是線程不安全的類,其父類維護了一個Calendar,調用相關方法有可能會修改Calendar。一般不要定義為static變數,如果定義為 static,必須加鎖,或者使用 DateUtils 工具類。 正例:註意線程安全,使用 DateUtils。org.apache.commons.lang.time.DateUtils,也推薦如下處理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { 
        @Override 
        protected DateFormat initialValue() { 
                return new SimpleDateFormat("yyyy-MM-dd"); 
        }
 };

1.4 java.sql.Date/Time/Timestamp

這幾個類都繼承了java.util.Date。
相當於將java.util.Date分開表示了。Date表示年月日等信息。Time表示時分秒等信息。Timestamp多維護了納秒,可以表示納秒。

如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。

2. jdk1.8的時間類

2.1 總覽

1.8增加了新的date-time包,遵循JSR310。核心代碼主要放在java.time包下。預設的日曆系統用的ISO-8601(基於格裡高利歷)。
java.time下主要內容包括:

  • java.time -主要包括,日期,時間,日期時間,時刻,期間,和時鐘相關的類。
  • java.time.chrono -其他非ISO標準的日曆系統可以用java.time.chrono,裡面已經定義了一部分年表,你也可以自定義。
  • java.time.format -格式化和解析日期時間的類
  • java.time.temporal -擴展API,主要是提供給寫框架和寫庫的人,允許日期時間相互操作,訪問,和調整。欄位和單位在這個包下定義。
  • java.time.zone -定義了時區,相對於時區的偏移量,時區規則等。

1.8日期時間api沒有併發問題,清晰容易使用。

該包的API提供了大量相關的方法,這些方法一般有一致的方法首碼:

  • of:靜態工廠方法。
  • parse:靜態工廠方法,關註於解析。
  • get:獲取某些東西的值。
  • is:檢查某些東西的是否是true。
  • with:不可變的setter等價物。
  • plus:加一些量到某個對象。
  • minus:從某個對象減去一些量。
  • to:轉換到另一個類型。
  • at:把這個對象與另一個對象組合起來,例如: date.atTime(time)。

2.2 相互轉化和Instant

可以看到老的時間日期類裡面都有了Instant的轉化。Instant可以說是新舊轉換的中轉站。Instant主要維護了秒和納秒欄位,可以表示納秒範圍。當然不支持的話會拋出異常。主要還是java.util.Date轉換成新的時間類。

2.3 Clock

提供了訪問當前時間的方法,也可以獲取當前Instant。Clock是持有時區或者時區偏移量的。如果只是獲取當前時間戳,推薦還是用System.currentTimeMillis()

2.4 ZoneId/ZoneOffset/ZoneRules

zone id 主要包括兩個方面,一個是相對於對於UTC/Greenwich的固定偏移量相當於一個大時區,另一個是時區內有特殊的相對於UTC/Greenwich偏移量的地區。通常固定偏移量部分可以用ZoneOffset表示,用normalized()判斷是否可以用ZoneOffset表示。判斷主要用到了時區規則ZoneRules。時區的真正規則定義在ZoneRules中,定義了什麼時候多少偏移量。使用這種方式是因為ID是固定不變的,但是規則是政府定義並且經常變動。
Time-zone IDs是三種類型

  • 'z'和以'+'/'-'開頭的id
  • 固定首碼和offset-style IDs,比如'GMT+2' or 'UTC+01:00',可識別的首碼有GMT,UTC,UT。可以標準化成ZoneOffset,通過normalized方法
  • 基於地區的IDs,需要包含2個或以上的特征,並且不以'UTC', 'GMT', 'UT' '+' 或者'-'開頭。通過配置實現,具體可以看ZoneRulesProvider,配置實現了通過ID找到具體的ZoneRules。

2.4 LocalDateTime/LocalTime/LocalDate/ZoneDateTime

LocalDateTIme/LocalTime/LocalDate都是沒有時區概念的。這句話並不是說不能根據時區獲取時間,而是因為這些類不持有表示時區的變數。而ZoneDateTime持有時區和偏移量變數。
這些類都可以對時間進行修改其實都是生成新對象。所以這裡的時間類都是天然支持多線程的。
這些時間類中都提供了獲取時間對象,修改時間獲取新的時間對象,格式化時間等。
註意點

  • LocaDateTime的atZone是調整本地時間的時區的。並不會改變時間。要使用其他時間需要獲取的LocalDateTime.now的時候的就要傳入時區變數。

2.5 DateTimeFormatter

時間對象進行格式化時間的需要用到格式化和解析日期和時間的時候需要用到DateTimeFormatter。

三、擴展及思考

  1. 用SimpleDateFormat格式化的時候不要用12小時制即hh,因為很容易導致上午下午不分,比如“2017-01-01 00:00:00“可能就變顯示成”2017-01-01 12:00:00”
  2. ::符號
    LocalDateTime的方法
    ```
    public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
    Objects.requireNonNull(formatter, "formatter");
    return formatter.parse(text, LocalDateTime::from);
    }
parse調用的方法是

public

LocalDateTime::from調用的方法是

public static LocalDateTime from(TemporalAccessor temporal) {
.... ...
}

其中temporal是LocalDateTime的介面
這裡其實大家都有一個疑問就是LocalDateTime::from到底代表什麼意思。

LocalDateTime::from
//與下列表示相同
x -> LocalDateTime.from(x)
//相當於
new TemporalQuery

四、參考及擴展資料

1.universal-time

2.系統-不能上網的電腦如何同步時間?

3.JVM源碼分析之System.currentTimeMillis及nanoTime原理詳解

4.Change From Julian to Gregorian Calendar

4.Java日期時間(Date/Time)(附Date.java源碼)

5.everything-about-java-8

6.深入理解Java 8 Lambda

7.Java Date-Time Packages官網資料

8.JSR 310: Date and Time API

9.STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES

10.gmt

未完成待續..


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • windows下執行 scrapy 的指定的時候出現錯誤, 最初出現錯誤 提示沒有pywin32 那麼就去安裝了一個pywin32 然後pip安裝 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pywin32 但是問題來了,當我安裝完對應版本的pywin32 ...
  • File類總結 File類概述 Java.io.File類 文件和目錄路徑名的抽象表示形式。 把電腦中的文件和文件夾(目錄)封裝成了一個File對象,通過File對象中的方法可以操作文件和文件夾; 是一個與系統無關的類,任意的操作系統都可以使用這個類中的方法操作文件和文件夾 3個File類有關的單詞 ...
  • 1.C++標準庫和STL C++標準庫以header files形式呈現: (1)C++標準庫的header files不帶尾碼名(.h),例如#include (2)新式C header files 不帶尾碼名.h,例如#include (3)舊式C header files (帶有尾碼名.h)仍... ...
  • 首先,找齊Spring框架中IoC功能、aop功能、JdbcTemplate功能所需的jar包,當前13個Jar包 1、Spring壓縮包中的四個核心JAR包,實現IoC控制反轉的根據xml配置文件或註解生成對象 beans 、context、core 和expression 下載地址: https ...
  • Part VII. Spring Cloud Sleuth 46. Introduction Spring Cloud Sleuth為Spring Cloud實現了分散式的跟蹤解決方案 46.1 Terminology Spring Cloud Sleuth借用了Dapper的術語 Span: 基本 ...
  • 相信各位都在在網上買過東西吧?那麼今天我的主題就是寫個線上購物系統,是不可能的,哈哈(後期確實有這個項目),那麼購物都填寫過快遞地址吧?然後網上查個地址都有地址管理吧? 要求: 1.列印出省、市、縣等多級目錄 2.每一級可以返回到上一級,第一級不能再往上返回 3.可以隨時退出系統 分析: 1.簡單的 ...
  • 使用fmsb包繪製雷達圖 {r} library("fmsb") radarfig ...
  • 1.解釋器路徑 2.編碼 1.ascill 00000000 (8個位表示) 缺點:表示不了英文 2.unicode 0000000000000000+ (至少16位表示) 缺點:消耗記憶體,當表示位不需要16位以上,造成多餘記憶體消耗 Python3 無需關註 Python2 每個文件中只要出現中文, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...