|
@@ 677-766 (lines=90) @@
|
| 674 |
|
$this->pinField(DateDefinitions::DAY_OF_MONTH); |
| 675 |
|
return; |
| 676 |
|
|
| 677 |
|
case DateDefinitions::WEEK_OF_MONTH: |
| 678 |
|
{ |
| 679 |
|
// This is tricky, because during the roll we may have to shift |
| 680 |
|
// to a different day of the week. For example: |
| 681 |
|
|
| 682 |
|
// s m t w r f s |
| 683 |
|
// 1 2 3 4 5 |
| 684 |
|
// 6 7 8 9 10 11 12 |
| 685 |
|
|
| 686 |
|
// When rolling from the 6th or 7th back one week, we go to the |
| 687 |
|
// 1st (assuming that the first partial week counts). The same |
| 688 |
|
// thing happens at the end of the month. |
| 689 |
|
|
| 690 |
|
// The other tricky thing is that we have to figure out whether |
| 691 |
|
// the first partial week actually counts or not, based on the |
| 692 |
|
// minimal first days in the week. And we have to use the |
| 693 |
|
// correct first day of the week to delineate the week |
| 694 |
|
// boundaries. |
| 695 |
|
|
| 696 |
|
// Here's our algorithm. First, we find the real boundaries of |
| 697 |
|
// the month. Then we discard the first partial week if it |
| 698 |
|
// doesn't count in this locale. Then we fill in the ends with |
| 699 |
|
// phantom days, so that the first partial week and the last |
| 700 |
|
// partial week are full weeks. We then have a nice square |
| 701 |
|
// block of weeks. We do the usual rolling within this block, |
| 702 |
|
// as is done elsewhere in this method. If we wind up on one of |
| 703 |
|
// the phantom days that we added, we recognize this and pin to |
| 704 |
|
// the first or the last day of the month. Easy, eh? |
| 705 |
|
|
| 706 |
|
// Normalize the DAY_OF_WEEK so that 0 is the first day of the week |
| 707 |
|
// in this locale. We have dow in 0..6. |
| 708 |
|
$dow = $this->internalGet(DateDefinitions::DAY_OF_WEEK) - $this->getFirstDayOfWeek(); |
| 709 |
|
if ($dow < 0) { |
| 710 |
|
$dow += 7; |
| 711 |
|
} |
| 712 |
|
|
| 713 |
|
// Find the day of the week (normalized for locale) for the first |
| 714 |
|
// of the month. |
| 715 |
|
$fdm = ($dow - $this->internalGet(DateDefinitions::DAY_OF_MONTH) + 1) % 7; |
| 716 |
|
if ($fdm < 0) { |
| 717 |
|
$fdm += 7; |
| 718 |
|
} |
| 719 |
|
|
| 720 |
|
// Get the first day of the first full week of the month, |
| 721 |
|
// including phantom days, if any. Figure out if the first week |
| 722 |
|
// counts or not; if it counts, then fill in phantom days. If |
| 723 |
|
// not, advance to the first real full week (skip the partial week). |
| 724 |
|
if ((7 - $fdm) < $this->getMinimalDaysInFirstWeek()) { |
| 725 |
|
$start = 8 - $fdm; // Skip the first partial week |
| 726 |
|
} else { |
| 727 |
|
$start = 1 - $fdm; // This may be zero or negative |
| 728 |
|
} |
| 729 |
|
|
| 730 |
|
// Get the day of the week (normalized for locale) for the last |
| 731 |
|
// day of the month. |
| 732 |
|
$monthLen = $this->getActualMaximum(DateDefinitions::DAY_OF_MONTH); |
| 733 |
|
$ldm = ($monthLen - $this->internalGet(DateDefinitions::DAY_OF_MONTH) + $dow) % 7; |
| 734 |
|
// We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here. |
| 735 |
|
|
| 736 |
|
// Get the limit day for the blocked-off rectangular month; that |
| 737 |
|
// is, the day which is one past the last day of the month, |
| 738 |
|
// after the month has already been filled in with phantom days |
| 739 |
|
// to fill out the last week. This day has a normalized DOW of 0. |
| 740 |
|
$limit = $monthLen + 7 - $ldm; |
| 741 |
|
|
| 742 |
|
// Now roll between start and (limit - 1). |
| 743 |
|
$gap = $limit - $start; |
| 744 |
|
$day_of_month = ($this->internalGet(DateDefinitions::DAY_OF_MONTH) + $amount * 7 - $start) % $gap; |
| 745 |
|
if ($day_of_month < 0) { |
| 746 |
|
$day_of_month += $gap; |
| 747 |
|
} |
| 748 |
|
$day_of_month += $start; |
| 749 |
|
|
| 750 |
|
// Finally, pin to the real start and end of the month. |
| 751 |
|
if ($day_of_month < 1) { |
| 752 |
|
$day_of_month = 1; |
| 753 |
|
} |
| 754 |
|
if ($day_of_month > $monthLen) { |
| 755 |
|
$day_of_month = $monthLen; |
| 756 |
|
} |
| 757 |
|
|
| 758 |
|
// Set the DAY_OF_MONTH. We rely on the fact that this field |
| 759 |
|
// takes precedence over everything else (since all other fields |
| 760 |
|
// are also set at this point). If this fact changes (if the |
| 761 |
|
// disambiguation algorithm changes) then we will have to unset |
| 762 |
|
// the appropriate fields here so that DAY_OF_MONTH is attended |
| 763 |
|
// to. |
| 764 |
|
$this->set(DateDefinitions::DAY_OF_MONTH, $day_of_month); |
| 765 |
|
return; |
| 766 |
|
} |
| 767 |
|
case DateDefinitions::WEEK_OF_YEAR: |
| 768 |
|
{ |
| 769 |
|
// This follows the outline of WEEK_OF_MONTH, except it applies |
|
@@ 767-832 (lines=66) @@
|
| 764 |
|
$this->set(DateDefinitions::DAY_OF_MONTH, $day_of_month); |
| 765 |
|
return; |
| 766 |
|
} |
| 767 |
|
case DateDefinitions::WEEK_OF_YEAR: |
| 768 |
|
{ |
| 769 |
|
// This follows the outline of WEEK_OF_MONTH, except it applies |
| 770 |
|
// to the whole year. Please see the comment for WEEK_OF_MONTH |
| 771 |
|
// for general notes. |
| 772 |
|
|
| 773 |
|
// Normalize the DAY_OF_WEEK so that 0 is the first day of the week |
| 774 |
|
// in this locale. We have dow in 0..6. |
| 775 |
|
$dow = $this->internalGet(DateDefinitions::DAY_OF_WEEK) - $this->getFirstDayOfWeek(); |
| 776 |
|
if ($dow < 0) { |
| 777 |
|
$dow += 7; |
| 778 |
|
} |
| 779 |
|
|
| 780 |
|
// Find the day of the week (normalized for locale) for the first |
| 781 |
|
// of the year. |
| 782 |
|
$fdy = ($dow - $this->internalGet(DateDefinitions::DAY_OF_YEAR) + 1) % 7; |
| 783 |
|
if ($fdy < 0) { |
| 784 |
|
$fdy += 7; |
| 785 |
|
} |
| 786 |
|
|
| 787 |
|
// Get the first day of the first full week of the year, |
| 788 |
|
// including phantom days, if any. Figure out if the first week |
| 789 |
|
// counts or not; if it counts, then fill in phantom days. If |
| 790 |
|
// not, advance to the first real full week (skip the partial week). |
| 791 |
|
if ((7 - $fdy) < $this->getMinimalDaysInFirstWeek()) { |
| 792 |
|
$start = 8 - $fdy; // Skip the first partial week |
| 793 |
|
} else { |
| 794 |
|
$start = 1 - $fdy; // This may be zero or negative |
| 795 |
|
} |
| 796 |
|
|
| 797 |
|
// Get the day of the week (normalized for locale) for the last |
| 798 |
|
// day of the year. |
| 799 |
|
$yearLen = $this->getActualMaximum(DateDefinitions::DAY_OF_YEAR); |
| 800 |
|
$ldy = ($yearLen - $this->internalGet(DateDefinitions::DAY_OF_YEAR) + $dow) % 7; |
| 801 |
|
// We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here. |
| 802 |
|
|
| 803 |
|
// Get the limit day for the blocked-off rectangular year; that |
| 804 |
|
// is, the day which is one past the last day of the year, |
| 805 |
|
// after the year has already been filled in with phantom days |
| 806 |
|
// to fill out the last week. This day has a normalized DOW of 0. |
| 807 |
|
$limit = $yearLen + 7 - $ldy; |
| 808 |
|
|
| 809 |
|
// Now roll between start and (limit - 1). |
| 810 |
|
$gap = $limit - $start; |
| 811 |
|
$day_of_year = ($this->internalGet(DateDefinitions::DAY_OF_YEAR) + $amount * 7 - $start) % $gap; |
| 812 |
|
if ($day_of_year < 0) { |
| 813 |
|
$day_of_year += $gap; |
| 814 |
|
} |
| 815 |
|
$day_of_year += $start; |
| 816 |
|
|
| 817 |
|
// Finally, pin to the real start and end of the month. |
| 818 |
|
if ($day_of_year < 1) { |
| 819 |
|
$day_of_year = 1; |
| 820 |
|
} |
| 821 |
|
if ($day_of_year > $yearLen) { |
| 822 |
|
$day_of_year = $yearLen; |
| 823 |
|
} |
| 824 |
|
|
| 825 |
|
// Make sure that the year and day of year are attended to by |
| 826 |
|
// clearing other fields which would normally take precedence. |
| 827 |
|
// If the disambiguation algorithm is changed, this section will |
| 828 |
|
// have to be updated as well. |
| 829 |
|
$this->set(DateDefinitions::DAY_OF_YEAR, $day_of_year); |
| 830 |
|
$this->clear(DateDefinitions::MONTH); |
| 831 |
|
return; |
| 832 |
|
} |
| 833 |
|
case DateDefinitions::DAY_OF_YEAR: |
| 834 |
|
{ |
| 835 |
|
// Roll the day of year using millis. Compute the millis for |