Java 時間與日期處理

Java 時間與日期處理 從屬於筆者的現代 Java 開發系列文章,涉及到的引用資料聲明在 Java 學習與實踐資料索引中。java

Java 時間與日期處理

在 Java 8 以前,咱們最多見的時間與日期處理相關的類就是 Date、Calendar 以及 SimpleDateFormatter 等等。不過 java.util.Date 也是被詬病已久,它包含了日期、時間、毫秒數等衆多繁雜的信息,其內部利用午夜 12 點來區分日期,利用 1970-01-01 來計算時間;而且其月份從 0 開始計數,並且用於得到年、月、日等信息的接口也是太不直觀。除此以外,java.util.DateSimpleDateFormatter 都不是類型安全的,而 JSR-310 中的 LocalDate LocalTime 等則是不變類型,更加適合於併發編程。JSR 310 實際上有兩個日期概念。第一個是 Instant,它大體對應於 java.util.Date 類,由於它表明了一個肯定的時間點,即相對於標準 Java 紀元(1970年1月1日)的偏移量;但與 java.util.Date 類不一樣的是其精確到了納秒級別。另外一個則是 LocalDate、LocalTime 以及 LocalDateTime 這樣表明了通常時區概念、易於理解的對象。sql

Class / Type Description
Year Represents a year.
YearMonth A month within a specific year.
LocalDate A date without an explicitly specified time zone.
LocalTime A time without an explicitly specified time zone.
LocalDateTime A combination date and time without an explicitly specified time zone.

最新 JDBC 映射將把數據庫的日期類型和 Java 8 的新類型關聯起來:數據庫

SQL Java
date LocalDate
time LocalTime
timestamp LocalDateTime
datetime LocalDateTime

時間與日期基礎概念

標準時間

GMT 即「格林威治標準時間」( Greenwich Mean Time,簡稱 G.M.T. ),指位於英國倫敦郊區的皇家格林威治天文臺的標準時間,由於本初子午線被定義爲經過那裏的經線。然而因爲地球的不規則自轉,致使 GMT 時間有偏差,所以目前已不被看成標準時間使用。UTC 是最主要的世界時間標準,是通過平均太陽時(以格林威治時間 GMT 爲準)、地軸運動修正後的新時標以及以「秒」爲單位的國際原子時所綜合精算而成的時間。UTC 比 GMT 來得更加精準。其偏差值必須保持在 0.9 秒之內,若大於 0.9 秒則由位於巴黎的國際地球自轉事務中央局發佈閏秒,使 UTC 與地球自轉週期一致。不過平常使用中,GMT 與 UTC 的功能與精確度是沒有差異的。協調世界時區會使用 「Z」 來表示。而在航空上,全部使用的時間劃一規定是協調世界時。並且 Z 在無線電中應讀做 「Zulu」(可參見北約音標字母),協調世界時也會被稱爲 「Zulu time」。編程

TimeZone&UTC Offsets: 時區與偏移

人們常常會把時區與 UTC 偏移量搞混,UTC 偏移量表明瞭某個具體的時間值與 UTC 時間之間的差別,一般用 HH:mm 形式表述。而 TimeZone 則表示某個地理區域,某個 TimeZone 中每每會包含多個偏移量,而多個時區可能在一年的某些時間有相同的偏移量。譬如 America/Chicago, America/Denver, 以及 America/Belize 在一年中不一樣的時間都會包含 -06:00 這個偏移。數組

時間戳

Unix 時間戳表示當前時間到 1970 年 1 月 1 日 00:00:00 UTC 對應的秒數。注意,JavaScript 內的時間戳指的是當前時間到 1970 年 1 月 1 日 00:00:00 UTC 對應的毫秒數,和 Unix 時間戳不是一個概念,後者表示秒數,差了 1000 倍。安全

時間數字字符串格式

RFC2822

YYYY/MM/DD HH:MM:SS ± timezone(時區用4位數字表示)
// eg 1992/02/12 12:23:22+0800

ISO 8601

國際標準化組織的國際標準 ISO 8601 是日期和時間的表示方法,全稱爲《數據存儲和交換形式·信息交換·日期和時間的表示方法》。目前最新爲第三版 ISO8601:2004,初版爲 ISO8601:1988,第二版爲 ISO8601:2000。年由 4 位數組成,以公曆公元 1 年爲 0001 年,以公元前 1 年爲 0000 年,公元前 2 年爲 -0001 年,其餘以此類推。應用其餘紀年法要換算成公曆,但若是發送和接受信息的雙方有共同一致贊成的其餘紀年法,能夠自行應用。併發

YYYY-MM-DDThh:mm:ss ± timezone(時區用HH:MM表示)
1997-07-16T08:20:30Z
// 「Z」表示UTC標準時區,即"00:00",因此這裏表示零時區的`1997年7月16日08時20分30秒`
//轉換成位於東八區的北京時間則爲`1997年7月17日16時20分30秒`
1997-07-16T19:20:30+01:00
// 表示東一區的1997年7月16日19時20秒30分,轉換成UTC標準時間的話是1997-07-16T18:20:30Z

時間戳

在 Java 8 以前,咱們使用 java.sql.Timestamp 來表示時間戳對象,能夠經過如下方式建立與獲取對象:函數

// 利用系統標準時間建立
Timestamp timestamp = new Timestamp(System.currentTimeMillis());

// 從 Date 對象中建立
new Timestamp((new Date()).getTime());

// 獲取自 1970-01-01 00:00:00 GMT 以來的毫秒數
timestamp.getTime();

在 Java 8 中,便可以使用 java.time.Instant 來表示自從 1970-01-01T00:00:00Z 以後通過的標準時間:學習

// 基於靜態函數建立
Instant instant = Instant.now();

// 基於 Date 或者毫秒數轉換
Instant someInstant = someDate.toInstant();
Instant someInstant = Instant.ofEpochMilli(someDate.getTime());

// 基於 TimeStamp 轉換
Instant instant = timestamp.toInstant();

// 從 LocalDate 轉化而來
LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC)

// 從 LocalDateTime 轉化而來
ldt.atZone(ZoneId.systemDefault()).toInstant();

// 獲取毫秒
long timeStampMillis = instant.toEpochMilli();

// 獲取秒
long timeStampSeconds = instant.getEpochSecond();

Clock 方便咱們去讀取當前的日期與時間。Clock 能夠根據不一樣的時區來進行建立,而且能夠做爲System.currentTimeMillis()的替代。這種指向時間軸的對象便是Instant類。Instants 能夠被用於建立java.util.Date對象。ui

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date

Date

// 默認建立
Date date0 = new Date();

// 從 TimeStamp 中建立
Date date1 = new Date(time);

// 基於 Instant 建立
Date date = Date.from(instant);

// 從格式化字符串中獲取
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
java.util.Date dt=sdf.parse("2005-2-19");

// 從 LocalDateTime 中轉化而來
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

基於 Date 的日期比較經常使用如下方式:

  • 使用 getTime() 方法獲取兩個日期(自1970年1月1日經歷的毫秒數值),而後比較這兩個值。

  • 使用方法 before(),after() 和 equals()。例如,一個月的12號比18號早,則 new Date(99, 2, 12).before(new Date (99, 2, 18)) 返回true。

  • 使用 compareTo() 方法,它是由 Comparable 接口定義的,Date 類實現了這個接口。

Calendar

Date 用於記錄某一個含日期的、精確到毫秒的時間。重點在表明一剎那的時間自己。 Calendar 用於將某一日期放到曆法中的互動——時間和年、月、日、星期、上午、下午、夏令時等這些曆法規定互相做用關係和互動。咱們能夠經過 Calendar 內置的構造器來建立實例:

Calendar.Builder builder =new Calendar.Builder();
Calendar calendar1 = builder.build();
Date date = calendar.getTime();

在 Calendar 中咱們則可以得到較爲直觀的年月日信息:

// 2017,再也不是 2017 - 1900 = 117
int year =calendar.get(Calendar.YEAR);

int month=calendar.get(Calendar.MONTH)+1;

int day =calendar.get(Calendar.DAY_OF_MONTH);

int hour =calendar.get(Calendar.HOUR_OF_DAY);

int minute =calendar.get(Calendar.MINUTE);

int seconds =calendar.get(Calendar.SECOND);

除此以外,Calendar 還提供了一系列 set 方法來容許咱們動態設置時間,還可使用 add 等方法進行日期的加減。

SimpleDateFormat

SimpleDateFormat 用來進行簡單的數據格式化轉化操做:

Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");

LocalDateTime

LocalDate

// 取當前日期:
LocalDate today = LocalDate.now();

// 根據年月日取日期,12月就是12:
LocalDate crischristmas = LocalDate.of(2017, 5, 15); 

// 根據指定格式字符串取
LocalDate endOfFeb = LocalDate.parse("2017-05-15"); // 嚴格按照ISO yyyy-MM-dd驗證,02寫成2都不行,固然也有一個重載方法容許本身定義格式
LocalDate.parse("2014-02-29"); // 無效日期沒法經過:DateTimeParseException: Invalid date

// 經過自定義時間字符串格式獲取
DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

// 獲取其餘時區下日期
LocalDate localDate = LocalDate.now(ZoneId.of("GMT+02:30"));

// 從 LocalDateTime 中獲取實例
LocalDateTime localDateTime = LocalDateTime.now();
LocalDate localDate = localDateTime.toLocalDate();

日期操做

// 取本月第1天
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2014-12-01

// 取本月第2天
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2014-12-02

// 取本月最後一天,不再用計算是28,29,30仍是31
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2014-12-31

// 取下一天
LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 變成了2015-01-01

// 取2015年1月第一個週一
LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2015-01-05

LocalTime

// 獲取其餘時區下時間
LocalTime localTime = LocalTime.now(ZoneId.of("GMT+02:30"));

// 從 LocalDateTime 中獲取實例
LocalDateTime localDateTime = LocalDateTime.now();
LocalTime localTime = localDateTime.toLocalTime();


- 12:00
- 12:01:02
- 12:01:02.345

LocalDateTime

// 經過時間戳建立
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(1450073569l), TimeZone.getDefault().toZoneId());

// 經過 Date 對象建立
Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

// 經過解析時間字符串建立
DateTimeFormatter formatter =
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13
  • 獲取年、月、日等信息

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439
  • 時間格式化展現

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

時間操做

localDateTime.plusDays(1);
localDateTime.minusHours(2);

時區轉換

Timezones 以 ZoneId 來區分。能夠經過靜態構造方法很容易的建立,Timezones 定義了 Instants 與 Local Dates 之間的轉化關係:

System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());
ZoneId losAngeles = ZoneId.of("America/Los_Angeles");
ZoneId berlin = ZoneId.of("Europe/Berlin");

// 2014-02-20 12:00
LocalDateTime dateTime = LocalDateTime.of(2014, 02, 20, 12, 0);

// 2014-02-20 12:00, Europe/Berlin (+01:00)
ZonedDateTime berlinDateTime = ZonedDateTime.of(dateTime, berlin);

// 2014-02-20 03:00, America/Los_Angeles (-08:00)
ZonedDateTime losAngelesDateTime = berlinDateTime.withZoneSameInstant(losAngeles);

int offsetInSeconds = losAngelesDateTime.getOffset().getTotalSeconds(); // -28800

// a collection of all available zones
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();

// using offsets
LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20, 3, 30);
ZoneOffset offset = ZoneOffset.of("+05:00");

// 2013-07-20 03:30 +05:00
OffsetDateTime plusFive = OffsetDateTime.of(date, offset);

// 2013-07-19 20:30 -02:00
OffsetDateTime minusTwo = plusFive.withOffsetSameInstant(ZoneOffset.ofHours(-2));

時差

Period 類以年月日來表示日期差,而 Duration 以秒與毫秒來表示時間差;Duration 適用於處理 Instant 與機器時間。

// periods

LocalDate firstDate = LocalDate.of(2010, 5, 17); // 2010-05-17
LocalDate secondDate = LocalDate.of(2015, 3, 7); // 2015-03-07
Period period = Period.between(firstDate, secondDate);

int days = period.getDays(); // 18
int months = period.getMonths(); // 9
int years = period.getYears(); // 4
boolean isNegative = period.isNegative(); // false

Period twoMonthsAndFiveDays = Period.ofMonths(2).plusDays(5);
LocalDate sixthOfJanuary = LocalDate.of(2014, 1, 6);

// add two months and five days to 2014-01-06, result is 2014-03-11
LocalDate eleventhOfMarch = sixthOfJanuary.plus(twoMonthsAndFiveDays);


// durations

Instant firstInstant= Instant.ofEpochSecond( 1294881180 ); // 2011-01-13 01:13
Instant secondInstant = Instant.ofEpochSecond(1294708260); // 2011-01-11 01:11

Duration between = Duration.between(firstInstant, secondInstant);

// negative because firstInstant is after secondInstant (-172920)
long seconds = between.getSeconds();

// get absolute result in minutes (2882)
long absoluteResult = between.abs().toMinutes();

// two hours in seconds (7200)
long twoHoursInSeconds = Duration.ofHours(2).getSeconds();
相關文章
相關標籤/搜索