Phân tích một chuỗi với ngày và thời gian vào một thời điểm cụ thể (Java gọi đó là " Instant
") khá phức tạp. Java đã giải quyết điều này trong một số lần lặp. Cái mới nhất, java.time
và java.time.chrono
, đáp ứng hầu hết tất cả các nhu cầu (ngoại trừ Time Dilation :)).
Tuy nhiên, sự phức tạp đó mang lại rất nhiều nhầm lẫn.
Chìa khóa để hiểu phân tích ngày là:
Tại sao Java có nhiều cách để phân tích một ngày
- Có một số hệ thống để đo thời gian. Ví dụ, lịch Nhật Bản lịch sử được bắt nguồn từ khoảng thời gian trị vì của hoàng đế hoặc triều đại tương ứng. Sau đó, có ví dụ như dấu thời gian UNIX. May mắn thay, toàn bộ thế giới (kinh doanh) quản lý để sử dụng như nhau.
- Trong lịch sử, các hệ thống đã được chuyển từ / sang, vì nhiều lý do . Ví dụ: từ lịch Julian đến lịch Gregorian năm 1582. Vì vậy, ngày 'tây' trước đó cần phải được đối xử khác nhau.
- Và tất nhiên sự thay đổi đã không xảy ra ngay lập tức. Bởi vì lịch xuất phát từ các trụ sở của một số tôn giáo và các khu vực khác của châu Âu tin vào các chế độ ăn kiêng khác, ví dụ, Đức đã không chuyển sang năm 1700.
... và tại sao LocalDateTime
, ZonedDateTime
et al. quá phức tạp
Có múi giờ . Múi giờ về cơ bản là một "dải" * [1] trên bề mặt Trái đất có chính quyền tuân theo các quy tắc tương tự khi nào nó có thời gian bù. Điều này bao gồm các quy tắc thời gian mùa hè.
Các múi giờ thay đổi theo thời gian cho các khu vực khác nhau, chủ yếu dựa trên người chinh phục ai. Và quy tắc của một múi giờ cũng thay đổi theo thời gian .
Có thời gian bù đắp. Điều đó không giống với múi giờ, bởi vì múi giờ có thể là "Prague", nhưng điều đó có thời gian bù vào mùa hè và thời gian bù vào mùa đông.
Nếu bạn nhận được dấu thời gian với múi giờ, độ lệch có thể thay đổi, tùy thuộc vào phần nào của năm. Trong giờ nhuận, dấu thời gian có thể có nghĩa là 2 thời điểm khác nhau, vì vậy không có thông tin bổ sung, không thể tin cậy được chuyển đổi.
Lưu ý: Theo dấu thời gian, tôi có nghĩa là "một chuỗi chứa ngày và / hoặc thời gian, tùy ý có múi giờ và / hoặc thời gian bù."
Một số múi giờ có thể chia sẻ cùng thời gian bù cho các khoảng thời gian nhất định. Chẳng hạn, múi giờ GMT / UTC giống như múi giờ "London" khi thời gian bù mùa hè không có hiệu lực.
Để làm cho nó phức tạp hơn một chút (nhưng điều đó không quá quan trọng đối với trường hợp sử dụng của bạn):
- Các nhà khoa học quan sát động lực của Trái đất, thay đổi theo thời gian; dựa vào đó, họ thêm giây vào cuối năm riêng lẻ. (Vì vậy,
2040-12-31 24:00:00
có thể là thời gian ngày hợp lệ.) Điều này cần cập nhật thường xuyên của siêu dữ liệu mà các hệ thống sử dụng để có quyền chuyển đổi ngày. Ví dụ: trên Linux, bạn nhận được các bản cập nhật thường xuyên cho các gói Java bao gồm các dữ liệu mới này.
Các bản cập nhật không phải lúc nào cũng giữ hành vi trước đó cho cả dấu thời gian lịch sử và tương lai. Vì vậy, có thể xảy ra việc phân tích hai dấu thời gian xung quanh thay đổi của múi giờ so với chúng có thể cho kết quả khác nhau khi chạy trên các phiên bản phần mềm khác nhau. Điều đó cũng áp dụng để so sánh giữa múi giờ bị ảnh hưởng và múi giờ khác.
Nếu điều này gây ra lỗi trong phần mềm của bạn, hãy xem xét sử dụng một số dấu thời gian không có các quy tắc phức tạp như dấu thời gian UNIX .
Vì 7, cho những ngày trong tương lai, chúng tôi không thể chuyển đổi ngày chính xác một cách chắc chắn. Vì vậy, ví dụ, phân tích cú pháp hiện tại 8524-02-17 12:00:00
có thể tắt một vài giây từ phân tích cú pháp trong tương lai.
API của JDK cho điều này phát triển với nhu cầu hiện đại
- Các bản phát hành Java đầu tiên có
java.util.Date
cách tiếp cận hơi ngây thơ, giả sử rằng chỉ có năm, tháng, ngày và thời gian. Điều này nhanh chóng không đủ.
- Ngoài ra, nhu cầu của cơ sở dữ liệu là khác nhau, do đó khá sớm,
java.sql.Date
đã được giới thiệu, với những hạn chế riêng của nó.
- Vì không bao gồm các lịch và múi giờ khác nhau,
Calendar
API đã được giới thiệu.
- Điều này vẫn không bao gồm sự phức tạp của các múi giờ. Tuy nhiên, sự pha trộn của các API trên thực sự là một vấn đề khó khăn. Vì vậy, khi các nhà phát triển Java bắt đầu làm việc trên các ứng dụng web toàn cầu, các thư viện nhắm vào hầu hết các trường hợp sử dụng, như JodaTime, đã nhanh chóng trở nên phổ biến. JodaTime là tiêu chuẩn thực tế trong khoảng một thập kỷ.
- Nhưng JDK không tích hợp với JodaTime, vì vậy làm việc với nó hơi cồng kềnh. Vì vậy, sau một cuộc thảo luận rất dài về cách tiếp cận vấn đề, JSR-310 được tạo ra chủ yếu dựa trên JodaTime .
Làm thế nào để đối phó với nó trong Java java.time
Xác định loại để phân tích dấu thời gian
Khi bạn đang sử dụng một chuỗi dấu thời gian, bạn cần biết nó chứa thông tin gì. Đây là điểm quan trọng. Nếu bạn không có quyền này, bạn sẽ có một ngoại lệ khó hiểu như "Không thể tạo tức thì" hoặc "Vùng bị thiếu" hoặc "id vùng không xác định", v.v.
Nó có chứa ngày và thời gian không?
Nó có bù thời gian không?
Một thời gian bù là +hh:mm
một phần. Đôi khi, +00:00
có thể được thay thế bằng Z
'Thời gian Zulu', UTC
như Giờ phối hợp quốc tế, hoặc GMT
là Giờ trung bình của Greenwich. Những cái này cũng đặt múi giờ.
Đối với những dấu thời gian này, bạn sử dụng OffsetDateTime
.
Nó có múi giờ không?
Đối với những dấu thời gian này, bạn sử dụng ZonedDateTime
.
Vùng được chỉ định bởi
- tên ("Prague", "Giờ chuẩn Thái Bình Dương", "PST") hoặc
- "ID khu vực" ("America / Los_Angele", "Châu Âu / Luân Đôn"), được đại diện bởi java.time.ZoneId .
Danh sách các múi giờ được biên soạn bởi "cơ sở dữ liệu TZ" , được hỗ trợ bởi ICAAN.
Theo ZoneId
javadoc, id của khu vực bằng cách nào đó cũng có thể được chỉ định là Z
và bù. Tôi không chắc làm thế nào bản đồ này đến khu vực thực. Nếu dấu thời gian, chỉ có TZ, rơi vào một giờ nhuận thay đổi thời gian, thì đó là mơ hồ, và việc giải thích là chủ đề ResolverStyle
, xem bên dưới.
Nếu nó không có , thì bối cảnh bị thiếu được giả định hoặc bỏ qua. Và người tiêu dùng phải quyết định. Vì vậy, nó cần được phân tích cú pháp LocalDateTime
và chuyển đổi thành OffsetDateTime
bằng cách thêm thông tin còn thiếu:
- Bạn có thể cho rằng đó là thời gian UTC. Thêm độ lệch UTC là 0 giờ.
- Bạn có thể cho rằng đó là thời điểm diễn ra việc chuyển đổi. Chuyển đổi nó bằng cách thêm múi giờ của hệ thống.
- Bạn có thể bỏ qua và chỉ sử dụng nó như là. Điều đó rất hữu ích, ví dụ để so sánh hoặc trừ hai lần (xem
Duration
) hoặc khi bạn không biết và điều đó không thực sự quan trọng (ví dụ: lịch trình xe buýt địa phương).
Thông tin bán thời gian
- Dựa trên những gì các dấu thời gian chứa, bạn có thể mất
LocalDate
, LocalTime
,OffsetTime
, MonthDay
, Year
, hoặc YearMonth
ra khỏi nó.
Nếu bạn có thông tin đầy đủ, bạn có thể nhận được một java.time.Instant
. Điều này cũng được sử dụng nội bộ để chuyển đổi giữa OffsetDateTime
và ZonedDateTime
.
Tìm hiểu làm thế nào để phân tích nó
Có một tài liệu mở rộng DateTimeFormatter
mà cả hai có thể phân tích chuỗi dấu thời gian và định dạng thành chuỗi.
Các s được tạo trướcDateTimeFormatter
sẽ bao gồm tất cả các định dạng dấu thời gian chuẩn. Ví dụ, ISO_INSTANT
có thể phân tích cú pháp 2011-12-03T10:15:30.123457Z
.
Nếu bạn có một số định dạng đặc biệt, thì bạn có thể tạo DateTimeFormatter của riêng bạn (cũng là một trình phân tích cú pháp).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Tôi khuyên bạn nên xem mã nguồn của DateTimeFormatter
và lấy cảm hứng về cách xây dựng một mã nguồn bằng cách sử dụngDateTimeFormatterBuilder
. Trong khi bạn ở đó, cũng có một cái nhìn ResolverStyle
để kiểm soát xem trình phân tích cú pháp là LENIENT, SMART hay STRICT cho các định dạng và thông tin mơ hồ.
TemporalAccessor
Bây giờ, lỗi thường xuyên là đi vào sự phức tạp của TemporalAccessor
. Điều này xuất phát từ cách các nhà phát triển đã được sử dụng để làm việc với SimpleDateFormatter.parse(String)
. Phải, DateTimeFormatter.parse("...")
cho bạn TemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Nhưng, được trang bị kiến thức từ phần trước, bạn có thể thuận tiện phân tích thành loại bạn cần:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
Bạn thực sự không cần phải DateTimeFormatter
một trong hai. Các loại bạn muốn phân tích cú pháp có các parse(String)
phương thức.
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Về TemporalAccessor
, bạn có thể sử dụng nó nếu bạn có một ý tưởng mơ hồ về thông tin nào có trong chuỗi và muốn quyết định khi chạy.
Tôi hy vọng tôi làm sáng tỏ tâm hồn của bạn :)
Lưu ý: Có một backport của java.time
Java 6 và 7: ThreeTen-Backport . Đối với Android, nó có ThreeTenABP .
[1] Không chỉ là chúng không phải là sọc, mà còn có một số thái cực kỳ lạ. Chẳng hạn, một số đảo Thái Bình Dương lân cận có múi giờ +14: 00 và -11: 00. Điều đó có nghĩa là, trong khi ở một hòn đảo, có ngày 1 tháng 3 ngày 3 tháng 5, ở một hòn đảo khác không xa, vẫn là ngày 30 tháng 4 ngày 12 tháng 4 (nếu tôi đếm đúng :))
ZonedDateTime
chứ không phải là mộtLocalDateTime
. Tên là phản trực giác; cácLocal
phương tiện bất kỳ địa phương nói chung chứ không phải là một múi giờ cụ thể. Như vậy, mộtLocalDateTime
đối tượng không bị ràng buộc với dòng thời gian. Để có ý nghĩa, để có được một thời điểm xác định trên dòng thời gian, bạn phải áp dụng múi giờ.