Phân tích kỳ vọng của bạn (bằng mã giả)
startDate.plus(Period.between(startDate, endDate)) == endDate
chúng ta phải thảo luận về một số chủ đề:
- Làm thế nào để xử lý các đơn vị riêng biệt như tháng hoặc ngày?
- việc thêm thời lượng (hay "thời gian") được định nghĩa như thế nào?
- Làm thế nào để xác định khoảng cách thời gian (thời lượng) giữa hai ngày?
- phép trừ của một khoảng thời gian (hoặc "thời gian") được định nghĩa như thế nào?
Trước tiên hãy nhìn vào các đơn vị. Ngày không có vấn đề gì vì chúng là đơn vị lịch nhỏ nhất có thể và mỗi ngày theo lịch khác nhau bởi bất kỳ ngày nào khác trong số nguyên đầy đủ của ngày. Vì vậy, chúng tôi luôn có mã giả bằng nhau nếu dương hoặc âm:
startDate.plus(ChronoUnit.Days.between(startDate, endDate)) == endDate
Tuy nhiên, các tháng rất khó khăn vì lịch gregorian định nghĩa các tháng theo lịch với các độ dài khác nhau. Vì vậy, tình huống có thể phát sinh là việc thêm bất kỳ số nguyên tháng nào vào một ngày có thể gây ra một ngày không hợp lệ:
[2019-08-31] + P1M = [2019-09-31]
Quyết định java.time
giảm ngày kết thúc thành hợp lệ - ở đây [2019-09-30] - là hợp lý và tương ứng với mong đợi của hầu hết người dùng vì ngày cuối cùng vẫn giữ nguyên tháng tính toán. Tuy nhiên, phần bổ sung này bao gồm cả hiệu chỉnh cuối tháng KHÔNG thể đảo ngược , hãy xem thao tác hoàn nguyên được gọi là phép trừ:
[2019-09-30] - P1M = [2019-08-30]
Kết quả cũng hợp lý vì a) quy tắc cơ bản của việc thêm tháng là giữ cho ngày tháng càng nhiều càng tốt và b) [2019-08-30] + P1M = [2019-09-30].
Việc thêm một khoảng thời gian (thời gian) chính xác là gì?
Trong java.time
, a Period
là một thành phần của các vật phẩm bao gồm năm, tháng và ngày với bất kỳ số lượng một phần nguyên nào. Vì vậy, việc thêm một Period
có thể được giải quyết để thêm số tiền một phần vào ngày bắt đầu. Vì năm luôn có thể chuyển đổi thành 12 bội số của tháng, trước tiên chúng ta có thể kết hợp năm và tháng và sau đó thêm tổng số trong một bước để tránh các tác dụng phụ lạ trong năm nhuận. Những ngày có thể được thêm vào trong bước cuối cùng. Một thiết kế hợp lý như được thực hiện trong java.time
.
Làm thế nào để xác định quyền Period
giữa hai ngày?
Trước tiên chúng ta hãy thảo luận về trường hợp khi thời lượng là dương, có nghĩa là ngày bắt đầu trước ngày kết thúc. Sau đó, chúng ta luôn có thể xác định thời lượng bằng cách trước tiên xác định sự khác biệt theo tháng và sau đó là ngày. Thứ tự này rất quan trọng để đạt được thành phần tháng vì nếu không, mọi khoảng thời gian giữa hai ngày sẽ chỉ bao gồm các ngày. Sử dụng ngày mẫu của bạn:
[2019-09-30] + P1M1D = [2019-10-31]
Về mặt kỹ thuật, ngày bắt đầu trước tiên được chuyển tiếp bằng chênh lệch được tính theo tháng giữa ngày bắt đầu và ngày kết thúc. Sau đó, ngày delta là chênh lệch giữa ngày bắt đầu di chuyển và ngày kết thúc được thêm vào ngày bắt đầu di chuyển. Bằng cách này, chúng ta có thể tính thời lượng là P1M1D trong ví dụ. Cho đến nay rất hợp lý.
Làm thế nào để trừ một khoảng thời gian?
Điểm thú vị nhất trong ví dụ bổ sung trước đó là, tình cờ KHÔNG có sự điều chỉnh vào cuối tháng. Tuy nhiên java.time
không thực hiện phép trừ ngược. Đầu tiên nó trừ đi các tháng và sau đó là các ngày:
[2019-10-31] - P1M1D = [2019-09-29]
java.time
Thay vào đó, nếu đã cố gắng đảo ngược các bước trong phần bổ sung trước đó thì sự lựa chọn tự nhiên sẽ là đầu tiên trừ đi các ngày và sau đó là các tháng . Với thứ tự thay đổi này, chúng tôi sẽ nhận được [2019-09-30]. Thứ tự thay đổi trong phép trừ sẽ giúp miễn là không có sự điều chỉnh cuối tháng trong bước bổ sung tương ứng. Điều này đặc biệt đúng nếu ngày trong tháng của bất kỳ ngày bắt đầu hoặc ngày kết thúc nào không lớn hơn 28 (độ dài tháng tối thiểu có thể). Thật không may java.time
đã xác định một thiết kế khác cho phép trừ Period
dẫn đến kết quả ít nhất quán hơn.
Là sự bổ sung của một thời lượng có thể đảo ngược trong phép trừ?
Trước tiên, chúng ta phải hiểu rằng thứ tự thay đổi được đề xuất trong phép trừ thời lượng từ một ngày theo lịch nhất định không đảm bảo tính thuận nghịch của phép cộng. Ví dụ về bộ đếm có hiệu chỉnh cuối tháng bổ sung:
[2011-03-31] + P3M1D = [2011-06-30] + P1D = [2011-07-01] (ok)
[2011-07-01] - P3M1D = [2011-06-30] - P3M = [2011-03-30] :-(
Thay đổi thứ tự không phải là xấu vì nó mang lại kết quả phù hợp hơn. Nhưng làm thế nào để chữa những thiếu sót còn lại? Cách duy nhất còn lại là thay đổi cách tính thời lượng. Thay vì sử dụng P3M1D, chúng ta có thể thấy rằng thời lượng P2M31D sẽ hoạt động theo cả hai hướng:
[2011-03-31] + P2M31D = [2011-05-31] + P31D = [2011-07-01] (ok)
[2011-07-01] - P2M31D = [2011-05-31] - P2M = [2011-03-31] (ok)
Vì vậy, ý tưởng là để thay đổi chuẩn hóa của thời lượng tính toán. Điều này có thể được thực hiện bằng cách xem xét việc bổ sung đồng bằng tháng được tính toán có thể đảo ngược trong bước trừ - tức là tránh sự cần thiết phải điều chỉnh vào cuối tháng. java.time
không may không cung cấp một giải pháp như vậy. Nó không phải là một lỗi, nhưng có thể được coi là một giới hạn thiết kế.
Lựa chọn thay thế?
Tôi đã tăng cường thư viện thời gian Time4J của mình bằng các số liệu có thể đảo ngược, triển khai các ý tưởng được đưa ra ở trên. Xem ví dụ sau:
PlainDate d1 = PlainDate.of(2011, 3, 31);
PlainDate d2 = PlainDate.of(2011, 7, 1);
TimeMetric<CalendarUnit, Duration<CalendarUnit>> metric =
Duration.inYearsMonthsDays().reversible();
Duration<CalendarUnit> duration =
metric.between(d1, d2); // P2M31D
Duration<CalendarUnit> invDur =
metric.between(d2, d1); // -P2M31D
assertThat(d1.plus(duration), is(d2)); // first invariance
assertThat(invDur, is(duration.inverse())); // second invariance
assertThat(d2.minus(duration), is(d1)); // third invariance
date.plus(period).minus(period)
) kết quả không phải lúc nào cũng giống nhau. Câu hỏi này là nhiều hơn vềPeriod.between
bất biến của chức năng.