[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等
一些坑:
set(int,int,int,int,int,int)方法
方法不能設置毫秒值,所以當用getInstance後即使用設置相同的值,最後毫秒值也是不一致的。所以如果有需要,將MILLISECOND清零。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。
三、擴展及思考
- 用SimpleDateFormat格式化的時候不要用12小時制即hh,因為很容易導致上午下午不分,比如“2017-01-01 00:00:00“可能就變顯示成”2017-01-01 12:00:00”
- ::符號
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
四、參考及擴展資料
10.gmt
未完成待續..