Tôi đồng ý với quan điểm của OP rằng điều này là phản trực giác và gây khó chịu, nhưng việc xác định ý +1 month
nghĩa của điều này trong các tình huống xảy ra là gì. Hãy xem xét các ví dụ sau:
Bạn bắt đầu với 2015-01-31 và muốn thêm một tháng 6 lần để có một chu kỳ lập lịch gửi bản tin email. Với những kỳ vọng ban đầu của OP, điều này sẽ trở lại:
- 2015-01-31
- 2015-02-28
- 2015-03-31
- 2015-04-30
- 2015-05-31
- 2015-06-30
Ngay lập tức, hãy lưu ý rằng chúng tôi dự kiến +1 month
có nghĩa là last day of month
hoặc, cách khác, thêm 1 tháng mỗi lần lặp lại nhưng luôn tham chiếu đến điểm bắt đầu. Thay vì hiểu điều này là "ngày cuối cùng của tháng", chúng tôi có thể đọc nó là "ngày 31 của tháng tới hoặc có sẵn cuối cùng trong tháng đó". Điều này có nghĩa là chúng tôi chuyển từ ngày 30 tháng 4 sang ngày 31 tháng 5 thay vì đến ngày 30 tháng 5. Lưu ý rằng điều này không phải vì nó là "ngày cuối cùng của tháng" mà vì chúng tôi muốn "có sẵn gần nhất với ngày bắt đầu của tháng."
Vì vậy, giả sử một trong những người dùng của chúng tôi đăng ký một bản tin khác để bắt đầu vào ngày 30 tháng 1 năm 2015. Ngày trực quan để làm +1 month
gì? Một diễn giải sẽ là "ngày 30 của tháng tiếp theo hoặc gần nhất có sẵn" sẽ trả về:
- 2015-01-30
- 2015-02-28
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
Điều này sẽ ổn trừ khi người dùng của chúng tôi nhận được cả hai bản tin trong cùng một ngày. Hãy giả sử rằng đây là vấn đề từ phía cung thay vì phía cầu Chúng tôi không lo lắng rằng người dùng sẽ khó chịu khi nhận được 2 bản tin trong cùng một ngày nhưng thay vào đó, máy chủ thư của chúng tôi không thể cung cấp băng thông để gửi gấp đôi nhiều bản tin. Với ý nghĩ đó, chúng tôi quay lại cách giải thích khác của "+1 tháng" là "gửi vào ngày thứ hai đến ngày cuối cùng của mỗi tháng" sẽ trả về:
- 2015-01-30
- 2015-02-27
- 2015-03-30
- 2015-04-29
- 2015-05-30
- 2015-06-29
Bây giờ chúng tôi đã tránh được bất kỳ sự trùng lặp nào với nhóm đầu tiên, nhưng chúng tôi cũng kết thúc với tháng 4 và ngày 29 tháng 6, điều này chắc chắn phù hợp với trực giác ban đầu của chúng tôi rằng +1 month
đơn giản là sẽ trở lại m/$d/Y
hoặc hấp dẫn và đơn giản m/30/Y
cho tất cả các tháng có thể. Vì vậy, bây giờ chúng ta hãy xem xét cách giải thích thứ ba về +1 month
việc sử dụng cả hai ngày:
Ngày 31 tháng 1
- 2015-01-31
- 2015-03-03
- 2015-03-31
- 2015-05-01
- 2015-05-31
- 2015-07-01
Ngày 30 tháng 1
- 2015-01-30
- 2015-03-02
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
Trên đây có một số vấn đề. Tháng Hai bị bỏ qua, có thể là một vấn đề cả về nguồn cung (giả sử nếu có sự phân bổ băng thông hàng tháng và tháng Hai trở nên lãng phí và tháng Ba tăng gấp đôi) và nhu cầu cuối cùng (người dùng cảm thấy bị lừa ngoài tháng Hai và nhận thấy tháng Ba thừa như cố gắng sửa chữa sai lầm). Mặt khác, lưu ý rằng hai ngày đặt:
- không bao giờ chồng chéo
- luôn vào cùng một ngày khi tháng đó có ngày (vì vậy, bộ ngày 30 tháng 1 trông khá sạch sẽ)
- tất cả đều trong vòng 3 ngày (1 ngày trong hầu hết các trường hợp) kể từ ngày có thể được coi là ngày "chính xác".
- đều cách người kế nhiệm và người tiền nhiệm ít nhất 28 ngày (một tháng âm lịch), do đó rất đồng đều.
Với hai nhóm cuối cùng, sẽ không khó để chỉ cần lùi lại một trong các ngày nếu nó nằm ngoài tháng tiếp theo thực tế (vì vậy hãy quay trở lại ngày 28 tháng 2 và ngày 30 tháng 4 trong bộ đầu tiên) và không bị mất ngủ vì chồng chéo và phân kỳ không thường xuyên từ mẫu "ngày cuối cùng của tháng" so với "ngày thứ hai đến ngày cuối cùng của tháng". Nhưng mong đợi thư viện lựa chọn giữa "đẹp nhất / tự nhiên nhất", "giải thích toán học của 02/31 và các lần tràn tháng khác", và "liên quan đến đầu tiên của tháng hoặc tháng trước" sẽ luôn kết thúc với sự mong đợi của ai đó không được đáp ứng và một số lịch trình cần phải điều chỉnh ngày "sai" để tránh vấn đề trong thế giới thực mà cách diễn giải "sai" giới thiệu.
Vì vậy, một lần nữa, mặc dù tôi cũng mong đợi +1 month
để trả lại một ngày thực sự là vào tháng sau, nó không đơn giản như trực giác và đưa ra các lựa chọn, đi với toán học hơn mong đợi của các nhà phát triển web có lẽ là lựa chọn an toàn.
Đây là một giải pháp thay thế vẫn còn rắc rối như bất kỳ giải pháp nào nhưng tôi nghĩ rằng có kết quả tốt:
foreach(range(0,5) as $count) {
$new_date = clone $date;
$new_date->modify("+$count month");
$expected_month = $count + 1;
$actual_month = $new_date->format("m");
if($expected_month != $actual_month) {
$new_date = clone $date;
$new_date->modify("+". ($count - 1) . " month");
$new_date->modify("+4 weeks");
}
echo "* " . nl2br($new_date->format("Y-m-d") . PHP_EOL);
}
Nó không phải là tối ưu nhưng logic cơ bản là: Nếu thêm 1 tháng dẫn đến một ngày khác với tháng tiếp theo dự kiến, hãy loại bỏ ngày đó và thay vào đó thêm 4 tuần. Dưới đây là kết quả của hai ngày thi:
Ngày 31 tháng 1
- 2015-01-31
- 2015-02-28
- 2015-03-31
- 2015-04-28
- 2015-05-31
- 2015-06-28
Ngày 30 tháng 1
- 2015-01-30
- 2015-02-27
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
(Mã của tôi là một mớ hỗn độn và sẽ không hoạt động trong một kịch bản kéo dài nhiều năm. Tôi hoan nghênh bất kỳ ai viết lại giải pháp với mã thanh lịch hơn miễn là tiền đề cơ bản được giữ nguyên, tức là nếu tháng +1 trả về một ngày thú vị, hãy sử dụng +4 tuần thay vào đó.)