1 | <?php |
||
2 | /* |
||
3 | * This file is part of the tinymeng/tools. |
||
4 | * (c) overtrue <[email protected]> |
||
5 | * This source file is subject to the MIT license that is bundled |
||
6 | * with this source code in the file LICENSE. |
||
7 | */ |
||
8 | namespace tinymeng\tools; |
||
9 | |||
10 | use DateTime; |
||
11 | use DateTimeZone; |
||
12 | use InvalidArgumentException; |
||
13 | |||
14 | /** |
||
15 | * Class Calendar. |
||
16 | * |
||
17 | * @author overtrue <[email protected]> |
||
18 | */ |
||
19 | class Calendar |
||
20 | { |
||
21 | /** |
||
22 | * 农历 1900-2100 的润大小信息. |
||
23 | * |
||
24 | * @var array |
||
25 | */ |
||
26 | protected $lunars = [ |
||
27 | 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909 |
||
28 | 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919 |
||
29 | 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929 |
||
30 | 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939 |
||
31 | 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949 |
||
32 | 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959 |
||
33 | 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969 |
||
34 | 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979 |
||
35 | 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989 |
||
36 | 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999 |
||
37 | 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009 |
||
38 | 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019 |
||
39 | 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029 |
||
40 | 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039 |
||
41 | 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049 |
||
42 | 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059 |
||
43 | 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069 |
||
44 | 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079 |
||
45 | 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089 |
||
46 | 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099 |
||
47 | 0x0d520, // 2100 |
||
48 | ]; |
||
49 | |||
50 | /** |
||
51 | * 公历每个月份的天数表. |
||
52 | * |
||
53 | * @var array |
||
54 | */ |
||
55 | protected $solarMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; |
||
56 | |||
57 | /** |
||
58 | * 天干地支之天干速查表. |
||
59 | * |
||
60 | * @var array |
||
61 | */ |
||
62 | protected $gan = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; |
||
63 | |||
64 | /** |
||
65 | * 天干地支之天干速查表 <=> 色彩. |
||
66 | * |
||
67 | * @var array |
||
68 | */ |
||
69 | protected $colors = ['青', '青', '红', '红', '黄', '黄', '白', '白', '黑', '黑']; |
||
70 | |||
71 | /** |
||
72 | * 天干地支之天干速查表 <=> 五行. |
||
73 | * |
||
74 | * @var array |
||
75 | */ |
||
76 | protected $wuXing = ['木', '木', '火', '火', '土', '土', '金', '金', '水', '水']; |
||
77 | |||
78 | /** |
||
79 | * 地支 <=> 五行. |
||
80 | * |
||
81 | * @var array |
||
82 | */ |
||
83 | protected $zhiWuxing = ['水', '土', '木', '木', '土', '火', '火', '土', '金', '金', '土', '水']; |
||
84 | |||
85 | /** |
||
86 | * 天干地支之地支速查表. |
||
87 | * |
||
88 | * @var array |
||
89 | */ |
||
90 | protected $zhi = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; |
||
91 | |||
92 | /** |
||
93 | * 天干地支之地支速查表 <=> 生肖. |
||
94 | * |
||
95 | * @var array |
||
96 | */ |
||
97 | protected $animals = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']; |
||
98 | |||
99 | /** |
||
100 | * 24节气速查表. |
||
101 | * |
||
102 | * @var array |
||
103 | */ |
||
104 | protected $solarTerm = [ |
||
105 | '小寒', '大寒', '立春', '雨水', '惊蛰', '春分', |
||
106 | '清明', '谷雨', '立夏', '小满', '芒种', '夏至', |
||
107 | '小暑', '大暑', '立秋', '处暑', '白露', '秋分', |
||
108 | '寒露', '霜降', '立冬', '小雪', '大雪', '冬至', |
||
109 | ]; |
||
110 | |||
111 | /** |
||
112 | * 1900-2100 各年的 24 节气日期速查表. |
||
113 | * |
||
114 | * @var array |
||
115 | */ |
||
116 | protected $solarTerms = [ |
||
117 | '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', |
||
118 | '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', |
||
119 | '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', |
||
120 | '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', |
||
121 | 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f', |
||
122 | '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa', |
||
123 | '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', |
||
124 | '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f', |
||
125 | '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', |
||
126 | '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', |
||
127 | '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', |
||
128 | '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', |
||
129 | '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', |
||
130 | '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', |
||
131 | '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722', |
||
132 | '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f', |
||
133 | '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', |
||
134 | '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', |
||
135 | '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722', |
||
136 | '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', |
||
137 | '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', |
||
138 | '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', |
||
139 | '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722', |
||
140 | '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', |
||
141 | '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', |
||
142 | '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', |
||
143 | '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', |
||
144 | '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', |
||
145 | '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', |
||
146 | '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', |
||
147 | '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', |
||
148 | '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', |
||
149 | '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', |
||
150 | '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', |
||
151 | '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', |
||
152 | '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa', |
||
153 | '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', |
||
154 | '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', |
||
155 | '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721', |
||
156 | '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', |
||
157 | '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', |
||
158 | '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', |
||
159 | '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd', |
||
160 | '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', |
||
161 | '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', |
||
162 | '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', |
||
163 | '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', |
||
164 | '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', |
||
165 | '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', |
||
166 | '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', |
||
167 | '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5', |
||
168 | '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722', |
||
169 | '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', |
||
170 | '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', |
||
171 | '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', |
||
172 | '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', |
||
173 | '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721', |
||
174 | '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd', |
||
175 | '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35', |
||
176 | '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', |
||
177 | '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721', |
||
178 | '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5', |
||
179 | '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35', |
||
180 | '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', |
||
181 | '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', |
||
182 | '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35', |
||
183 | '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', |
||
184 | ]; |
||
185 | |||
186 | /** |
||
187 | * 数字转中文速查表. |
||
188 | * |
||
189 | * @var array |
||
190 | */ |
||
191 | protected $weekdayAlias = ['日', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; |
||
192 | |||
193 | /** |
||
194 | * 日期转农历称呼速查表. |
||
195 | * |
||
196 | * @var array |
||
197 | */ |
||
198 | protected $dateAlias = ['初', '十', '廿', '卅']; |
||
199 | |||
200 | /** |
||
201 | * 月份转农历称呼速查表. |
||
202 | * |
||
203 | * @var array |
||
204 | */ |
||
205 | protected $monthAlias = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']; |
||
206 | |||
207 | /** |
||
208 | * 传入阳历年月日获得详细的公历、农历信息. |
||
209 | * |
||
210 | * @param int $year |
||
211 | * @param int $month |
||
212 | * @param int $day |
||
213 | * @param int $hour |
||
214 | * |
||
215 | * @return array |
||
216 | */ |
||
217 | public function solar($year, $month, $day, $hour = null) |
||
218 | { |
||
219 | $date = $this->makeDate("{$year}-{$month}-{$day}"); |
||
220 | $lunar = $this->solar2lunar($year, $month, $day, $hour); |
||
221 | $week = abs((int)$date->format('w')); // 0 ~ 6 修正 星期七 为 星期日 |
||
222 | |||
223 | return array_merge( |
||
224 | $lunar, |
||
225 | [ |
||
226 | 'gregorian_year' => (string) $year, |
||
227 | 'gregorian_month' => sprintf('%02d', $month), |
||
228 | 'gregorian_day' => sprintf('%02d', $day), |
||
229 | 'gregorian_hour' => !is_numeric($hour) || $hour < 0 || $hour > 23 ? null : sprintf('%02d', $hour), |
||
230 | 'week_no' => $week, // 在周日时将会传回 0 |
||
231 | 'week_name' => '星期'.$this->weekdayAlias[$week], |
||
232 | 'is_today' => 0 === $this->makeDate('now')->diff($date)->days, |
||
233 | 'constellation' => $this->toConstellation($month, $day), |
||
234 | 'is_same_year' => $lunar['lunar_year'] == $year ?: false, |
||
235 | ] |
||
236 | ); |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历信息. |
||
241 | * |
||
242 | * @param int $year lunar year |
||
243 | * @param int $month lunar month |
||
244 | * @param int $day lunar day |
||
245 | * @param bool $isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] |
||
246 | * @param int $hour birth hour.[0~23] |
||
247 | * |
||
248 | * @return array |
||
249 | */ |
||
250 | public function lunar($year, $month, $day, $isLeapMonth = false, $hour = null) |
||
251 | { |
||
252 | $solar = $this->lunar2solar($year, $month, $day, $isLeapMonth); |
||
253 | |||
254 | return $this->solar($solar['solar_year'], $solar['solar_month'], $solar['solar_day'], $hour); |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * 返回农历指定年的总天数. |
||
259 | * |
||
260 | * @param int $year |
||
261 | * |
||
262 | * @return int |
||
263 | */ |
||
264 | public function daysOfYear($year) |
||
265 | { |
||
266 | $sum = 348; |
||
267 | |||
268 | for ($i = 0x8000; $i > 0x8; $i >>= 1) { |
||
269 | $sum += ($this->lunars[$year - 1900] & $i) ? 1 : 0; |
||
270 | } |
||
271 | |||
272 | return $sum + $this->leapDays($year); |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * 返回农历指定年的总月数. |
||
277 | * |
||
278 | * @param int $year |
||
279 | * |
||
280 | * @return int |
||
281 | */ |
||
282 | public function monthsOfYear($year) |
||
283 | { |
||
284 | return 0 < $this->leapMonth($year) ? 13 : 12; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * 返回农历 y 年闰月是哪个月;若 y 年没有闰月 则返回0. |
||
289 | * |
||
290 | * @param int $year |
||
291 | * |
||
292 | * @return int |
||
293 | */ |
||
294 | public function leapMonth($year) |
||
295 | { |
||
296 | // 闰字编码 \u95f0 |
||
297 | return $this->lunars[$year - 1900] & 0xf; |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * 返回农历y年闰月的天数 若该年没有闰月则返回 0. |
||
302 | * |
||
303 | * @param int $year |
||
304 | * |
||
305 | * @return int |
||
306 | */ |
||
307 | public function leapDays($year) |
||
308 | { |
||
309 | if ($this->leapMonth($year)) { |
||
310 | return ($this->lunars[$year - 1900] & 0x10000) ? 30 : 29; |
||
311 | } |
||
312 | |||
313 | return 0; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * 返回农历 y 年 m 月(非闰月)的总天数,计算 m 为闰月时的天数请使用 leapDays 方法. |
||
318 | * |
||
319 | * @param int $year |
||
320 | * @param int $month |
||
321 | * |
||
322 | * @return int |
||
323 | */ |
||
324 | public function lunarDays($year, $month) |
||
325 | { |
||
326 | // 月份参数从 1 至 12,参数错误返回 -1 |
||
327 | if ($month > 12 || $month < 1) { |
||
328 | return -1; |
||
329 | } |
||
330 | |||
331 | return ($this->lunars[$year - 1900] & (0x10000 >> $month)) ? 30 : 29; |
||
332 | } |
||
333 | |||
334 | /** |
||
335 | * 返回公历 y 年 m 月的天数. |
||
336 | * |
||
337 | * @param int $year |
||
338 | * @param int $month |
||
339 | * |
||
340 | * @return int |
||
341 | */ |
||
342 | public function solarDays($year, $month) |
||
343 | { |
||
344 | // 若参数错误 返回-1 |
||
345 | if ($month > 12 || $month < 1) { |
||
346 | return -1; |
||
347 | } |
||
348 | |||
349 | $ms = $month - 1; |
||
350 | |||
351 | if (1 == $ms) { // 2 月份的闰平规律测算后确认返回 28 或 29 |
||
352 | return ((0 === $year % 4) && (0 !== $year % 100) || (0 === $year % 400)) ? 29 : 28; |
||
353 | } |
||
354 | |||
355 | return $this->solarMonth[$ms]; |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * 农历年份转换为干支纪年. |
||
360 | * |
||
361 | * @param int $lunarYear |
||
362 | * @param null|int $termIndex |
||
363 | * |
||
364 | * @return string |
||
365 | */ |
||
366 | public function ganZhiYear($lunarYear, $termIndex = null) |
||
367 | { |
||
368 | /** |
||
369 | * 据维基百科干支词条:『在西历新年后,华夏新年或干支历新年之前,则续用上一年之干支』 |
||
370 | * 所以干支年份应该不需要根据节气校正,为免影响现有系统,此处暂时保留原有逻辑 |
||
371 | * https://zh.wikipedia.org/wiki/%E5%B9%B2%E6%94%AF. |
||
372 | * |
||
373 | * 即使考虑节气,有的年份没有立春,有的年份有两个立春,此处逻辑仍不能处理该特殊情况 |
||
374 | */ |
||
375 | $adjust = null !== $termIndex && 3 > $termIndex ? 1 : 0; |
||
376 | |||
377 | $ganKey = ($lunarYear + $adjust - 4) % 10; |
||
378 | $zhiKey = ($lunarYear + $adjust - 4) % 12; |
||
379 | |||
380 | return $this->gan[$ganKey].$this->zhi[$zhiKey]; |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * 公历月、日判断所属星座. |
||
385 | * |
||
386 | * @param int $gregorianMonth |
||
387 | * @param int $gregorianDay |
||
388 | * |
||
389 | * @return string |
||
390 | */ |
||
391 | public function toConstellation($gregorianMonth, $gregorianDay) |
||
392 | { |
||
393 | $constellations = '魔羯水瓶双鱼白羊金牛双子巨蟹狮子处女天秤天蝎射手魔羯'; |
||
394 | $arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]; |
||
395 | |||
396 | return mb_substr( |
||
397 | $constellations, |
||
398 | $gregorianMonth * 2 - ($gregorianDay < $arr[$gregorianMonth - 1] ? 2 : 0), |
||
399 | 2, |
||
400 | 'UTF-8' |
||
401 | ); |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * 传入offset偏移量返回干支. |
||
406 | * |
||
407 | * @param int $offset 相对甲子的偏移量 |
||
408 | * |
||
409 | * @return string |
||
410 | */ |
||
411 | public function toGanZhi($offset) |
||
412 | { |
||
413 | return $this->gan[$offset % 10].$this->zhi[$offset % 12]; |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * 传入公历年获得该年第n个节气的公历日期 |
||
418 | * |
||
419 | * @param int $year 公历年(1900-2100); |
||
420 | * @param int $no 二十四节气中的第几个节气(1~24);从n=1(小寒)算起 |
||
421 | * |
||
422 | * @return int |
||
423 | * |
||
424 | * @example |
||
425 | * <pre> |
||
426 | * $_24 = $this->getTerm(1987,3) ;// _24 = 4; 意即 1987 年 2 月 4 日立春 |
||
427 | * </pre> |
||
428 | */ |
||
429 | public function getTerm($year, $no) |
||
430 | { |
||
431 | if ($year < 1900 || $year > 2100) { |
||
432 | return -1; |
||
433 | } |
||
434 | if ($no < 1 || $no > 24) { |
||
435 | return -1; |
||
436 | } |
||
437 | $solarTermsOfYear = array_map('hexdec', (array)str_split($this->solarTerms[$year - 1900], 5)); |
||
438 | $positions = [ |
||
439 | 0 => [0, 1], |
||
440 | 1 => [1, 2], |
||
441 | 2 => [3, 1], |
||
442 | 3 => [4, 2], |
||
443 | ]; |
||
444 | $group = intval(($no - 1) / 4); |
||
445 | list($offset, $length) = $positions[($no - 1) % 4]; |
||
446 | |||
447 | return substr($solarTermsOfYear[$group], $offset, $length); |
||
448 | } |
||
449 | |||
450 | public function toChinaYear($year) |
||
451 | { |
||
452 | if (!is_numeric($year)) { |
||
453 | throw new InvalidArgumentException("错误的年份:{$year}"); |
||
454 | } |
||
455 | $lunarYear = ''; |
||
456 | $year = (string) $year; |
||
457 | for ($i = 0, $l = strlen($year); $i < $l; ++$i) { |
||
458 | $lunarYear .= '0' !== $year[$i] ? $this->weekdayAlias[$year[$i]] : '零'; |
||
459 | } |
||
460 | |||
461 | return $lunarYear; |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * 传入农历数字月份返回汉语通俗表示法. |
||
466 | * |
||
467 | * @param int $month |
||
468 | * |
||
469 | * @return string |
||
470 | */ |
||
471 | public function toChinaMonth($month) |
||
472 | { |
||
473 | // 若参数错误 返回 -1 |
||
474 | if ($month > 12 || $month < 1) { |
||
475 | throw new InvalidArgumentException("错误的月份:{$month}"); |
||
476 | } |
||
477 | |||
478 | return $this->monthAlias[abs($month) - 1].'月'; |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * 传入农历日期数字返回汉字表示法. |
||
483 | * |
||
484 | * @param int $day |
||
485 | * |
||
486 | * @return string |
||
487 | */ |
||
488 | public function toChinaDay($day) |
||
489 | { |
||
490 | switch ($day) { |
||
491 | case 10: |
||
492 | return '初十'; |
||
493 | case 20: |
||
494 | return '二十'; |
||
495 | case 30: |
||
496 | return '三十'; |
||
497 | default: |
||
498 | return $this->dateAlias[intval($day / 10)].$this->weekdayAlias[$day % 10]; |
||
499 | } |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * 年份转生肖. |
||
504 | * |
||
505 | * 仅能大致转换, 精确划分生肖分界线是 “立春”. |
||
506 | * |
||
507 | * @param int $year |
||
508 | * @param null|int $termIndex |
||
509 | * |
||
510 | * @return string |
||
511 | */ |
||
512 | public function getAnimal($year, $termIndex = null) |
||
513 | { |
||
514 | // 认为此逻辑不需要,详情参见 ganZhiYear 相关注释 |
||
515 | $adjust = null !== $termIndex && 3 > $termIndex ? 1 : 0; |
||
516 | |||
517 | $animalIndex = ($year + $adjust - 4) % 12; |
||
518 | |||
519 | return $this->animals[$animalIndex]; |
||
520 | } |
||
521 | |||
522 | /** |
||
523 | * 干支转色彩. |
||
524 | * |
||
525 | * @param $ganZhi |
||
526 | * |
||
527 | * @return string |
||
528 | */ |
||
529 | protected function getColor($ganZhi) |
||
530 | { |
||
531 | if (!$ganZhi) { |
||
532 | return null; |
||
533 | } |
||
534 | |||
535 | $gan = substr($ganZhi, 0, 3); |
||
536 | |||
537 | if (!$gan) { |
||
538 | return null; |
||
539 | } |
||
540 | |||
541 | return $this->colors[array_search($gan, $this->gan)]; |
||
542 | } |
||
543 | |||
544 | /** |
||
545 | * 干支转五行. |
||
546 | * |
||
547 | * @param $ganZhi |
||
548 | * |
||
549 | * @return string |
||
550 | */ |
||
551 | protected function getWuXing($ganZhi) |
||
552 | { |
||
553 | if (!$ganZhi) { |
||
554 | return null; |
||
555 | } |
||
556 | |||
557 | $gan = substr($ganZhi, 0, 3); |
||
558 | $zhi = substr($ganZhi, 3); |
||
559 | |||
560 | if (!$gan || !$zhi) { |
||
561 | return null; |
||
562 | } |
||
563 | |||
564 | $wGan = $this->wuXing[array_search($gan, $this->gan)]; |
||
565 | $wZhi = $this->zhiWuxing[array_search($zhi, $this->zhi)]; |
||
566 | |||
567 | return $wGan.$wZhi; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * 阳历转阴历. |
||
572 | * |
||
573 | * @param int $year |
||
574 | * @param int $month |
||
575 | * @param int $day |
||
576 | * @param int $hour |
||
577 | * |
||
578 | * @return array |
||
579 | */ |
||
580 | public function solar2lunar($year, $month, $day, $hour = null) |
||
581 | { |
||
582 | if (23 == $hour) { |
||
583 | // 23点过后算子时,农历以子时为一天的起始 |
||
584 | $date = $this->makeDate("{$year}-{$month}-{$day} +1day"); |
||
585 | } else { |
||
586 | $date = $this->makeDate("{$year}-{$month}-{$day}"); |
||
587 | } |
||
588 | |||
589 | list($year, $month, $day) = explode('-', $date->format('Y-n-j')); |
||
590 | |||
591 | // 参数区间1900.1.31~2100.12.31 |
||
592 | if ($year < 1900 || $year > 2100) { |
||
593 | throw new InvalidArgumentException("不支持的年份:{$year}"); |
||
594 | } |
||
595 | |||
596 | // 年份限定、上限 |
||
597 | if (1900 == $year && 1 == $month && $day < 31) { |
||
598 | throw new InvalidArgumentException("不支持的日期:{$year}-{$month}-{$day}"); |
||
599 | } |
||
600 | |||
601 | $offset = (int)$this->dateDiff($date, '1900-01-31')->days; |
||
602 | |||
603 | for ($i = 1900; $i < 2101 && $offset > 0; ++$i) { |
||
604 | $daysOfYear = $this->daysOfYear($i); |
||
605 | $offset -= $daysOfYear; |
||
606 | } |
||
607 | |||
608 | if ($offset < 0) { |
||
609 | $offset += $daysOfYear; |
||
610 | --$i; |
||
611 | } |
||
612 | |||
613 | // 农历年 |
||
614 | $lunarYear = $i; |
||
615 | |||
616 | $leap = $this->leapMonth($i); // 闰哪个月 |
||
617 | $isLeap = false; |
||
618 | |||
619 | // 用当年的天数 offset,逐个减去每月(农历)的天数,求出当天是本月的第几天 |
||
620 | for ($i = 1; $i < 13 && $offset > 0; ++$i) { |
||
621 | // 闰月 |
||
622 | if ($leap > 0 && $i == ($leap + 1) && !$isLeap) { |
||
623 | --$i; |
||
624 | $isLeap = true; |
||
625 | $daysOfMonth = $this->leapDays($lunarYear); // 计算农历月天数 |
||
626 | } else { |
||
627 | $daysOfMonth = $this->lunarDays($lunarYear, $i); // 计算农历普通月天数 |
||
628 | } |
||
629 | |||
630 | // 解除闰月 |
||
631 | if (true === $isLeap && $i == ($leap + 1)) { |
||
632 | $isLeap = false; |
||
633 | } |
||
634 | |||
635 | $offset -= $daysOfMonth; |
||
636 | } |
||
637 | // offset为0时,并且刚才计算的月份是闰月,要校正 |
||
638 | if (0 === $offset && $leap > 0 && $i == $leap + 1) { |
||
639 | if ($isLeap) { |
||
640 | $isLeap = false; |
||
641 | } else { |
||
642 | $isLeap = true; |
||
643 | --$i; |
||
644 | } |
||
645 | } |
||
646 | |||
647 | if ($offset < 0) { |
||
648 | $offset += $daysOfMonth; |
||
649 | --$i; |
||
650 | } |
||
651 | |||
652 | // 农历月 |
||
653 | $lunarMonth = $i; |
||
654 | |||
655 | // 农历日 |
||
656 | $lunarDay = $offset + 1; |
||
657 | |||
658 | // 月柱 1900 年 1 月小寒以前为 丙子月(60进制12) |
||
659 | $firstNode = $this->getTerm($year, ($month * 2 - 1)); // 返回当月「节气」为几日开始 |
||
660 | $secondNode = $this->getTerm($year, ($month * 2)); // 返回当月「节气」为几日开始 |
||
661 | |||
662 | // 依据 12 节气修正干支月 |
||
663 | $ganZhiMonth = $this->toGanZhi(($year - 1900) * 12 + $month + 11); |
||
664 | |||
665 | if ($day >= $firstNode) { |
||
666 | $ganZhiMonth = $this->toGanZhi(($year - 1900) * 12 + $month + 12); |
||
667 | } |
||
668 | |||
669 | // 获取该天的节气 |
||
670 | $termIndex = null; |
||
671 | if ($firstNode == $day) { |
||
672 | $termIndex = $month * 2 - 2; |
||
673 | } |
||
674 | |||
675 | if ($secondNode == $day) { |
||
676 | $termIndex = $month * 2 - 1; |
||
677 | } |
||
678 | |||
679 | $term = null !== $termIndex ? $this->solarTerm[$termIndex] : null; |
||
680 | |||
681 | // 日柱 当月一日与 1900/1/1 相差天数 |
||
682 | $dayCyclical = $this->dateDiff("{$year}-{$month}-01", '1900-01-01')->days + 10; |
||
683 | $dayCyclical += $day - 1; |
||
684 | $ganZhiDay = $this->toGanZhi($dayCyclical); |
||
685 | |||
686 | // 时柱和时辰 |
||
687 | list($ganZhiHour, $lunarHour, $hour) = $this->ganZhiHour($hour, $dayCyclical); |
||
688 | |||
689 | $ganZhiYear = $this->ganZhiYear($lunarYear, $termIndex); |
||
690 | |||
691 | return [ |
||
692 | 'lunar_year' => (string) $lunarYear, |
||
693 | 'lunar_month' => sprintf('%02d', $lunarMonth), |
||
694 | 'lunar_day' => sprintf('%02d', $lunarDay), |
||
695 | 'lunar_hour' => $hour, |
||
696 | 'lunar_year_chinese' => $this->toChinaYear($lunarYear), |
||
697 | 'lunar_month_chinese' => ($isLeap ? '闰' : '').$this->toChinaMonth($lunarMonth), |
||
698 | 'lunar_day_chinese' => $this->toChinaDay($lunarDay), |
||
699 | 'lunar_hour_chinese' => $lunarHour, |
||
700 | 'ganzhi_year' => $ganZhiYear, |
||
701 | 'ganzhi_month' => $ganZhiMonth, |
||
702 | 'ganzhi_day' => $ganZhiDay, |
||
703 | 'ganzhi_hour' => $ganZhiHour, |
||
704 | 'wuxing_year' => $this->getWuXing($ganZhiYear), |
||
705 | 'wuxing_month' => $this->getWuXing($ganZhiMonth), |
||
706 | 'wuxing_day' => $this->getWuXing($ganZhiDay), |
||
707 | 'wuxing_hour' => $this->getWuXing($ganZhiHour), |
||
708 | 'color_year' => $this->getColor($ganZhiYear), |
||
709 | 'color_month' => $this->getColor($ganZhiMonth), |
||
710 | 'color_day' => $this->getColor($ganZhiDay), |
||
711 | 'color_hour' => $this->getColor($ganZhiHour), |
||
712 | 'animal' => $this->getAnimal($lunarYear, $termIndex), |
||
713 | 'term' => $term, |
||
714 | 'is_leap' => $isLeap, |
||
715 | ]; |
||
716 | } |
||
717 | |||
718 | /** |
||
719 | * 阴历转阳历. |
||
720 | * |
||
721 | * @param int $year |
||
722 | * @param int $month |
||
723 | * @param int $day |
||
724 | * @param bool $isLeapMonth |
||
725 | * |
||
726 | * @return array|int |
||
727 | */ |
||
728 | public function lunar2solar($year, $month, $day, $isLeapMonth = false) |
||
729 | { |
||
730 | // 参数区间 1900.1.3 1 ~2100.12.1 |
||
731 | $leapMonth = $this->leapMonth($year); |
||
732 | |||
733 | // 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 |
||
734 | if ($isLeapMonth && ($leapMonth != $month)) { |
||
735 | $isLeapMonth = false; |
||
736 | } |
||
737 | |||
738 | // 超出了最大极限值 |
||
739 | if (2100 == $year && 12 == $month && $day > 1 || 1900 == $year && 1 == $month && $day < 31) { |
||
740 | return -1; |
||
741 | } |
||
742 | |||
743 | $maxDays = $days = $this->lunarDays($year, $month); |
||
744 | |||
745 | // if month is leap, _day use leapDays method |
||
746 | if ($isLeapMonth) { |
||
747 | $maxDays = $this->leapDays($year, $month); |
||
0 ignored issues
–
show
|
|||
748 | } |
||
749 | |||
750 | // 参数合法性效验 |
||
751 | if ($year < 1900 || $year > 2100 || $day > $maxDays) { |
||
752 | throw new InvalidArgumentException('传入的参数不合法'); |
||
753 | } |
||
754 | |||
755 | // 计算农历的时间差 |
||
756 | $offset = 0; |
||
757 | |||
758 | for ($i = 1900; $i < $year; ++$i) { |
||
759 | $offset += $this->daysOfYear($i); |
||
760 | } |
||
761 | |||
762 | $isAdd = false; |
||
763 | for ($i = 1; $i < $month; ++$i) { |
||
764 | $leap = $this->leapMonth($year); |
||
765 | if (!$isAdd) {// 处理闰月 |
||
766 | if ($leap <= $i && $leap > 0) { |
||
767 | $offset += $this->leapDays($year); |
||
768 | $isAdd = true; |
||
769 | } |
||
770 | } |
||
771 | $offset += $this->lunarDays($year, $i); |
||
772 | } |
||
773 | |||
774 | // 转换闰月农历 需补充该年闰月的前一个月的时差 |
||
775 | if ($isLeapMonth) { |
||
776 | $offset += $days; |
||
777 | } |
||
778 | |||
779 | // 1900 年农历正月一日的公历时间为 1900 年 1 月 30 日 0 时 0 分 0 秒 (该时间也是本农历的最开始起始点) |
||
780 | // XXX: 部分 windows 机器不支持负时间戳,所以这里就写死了,哈哈哈哈... |
||
781 | $startTimestamp = -2206483200; |
||
782 | $date = date('Y-m-d', ($offset + $day) * 86400 + $startTimestamp); |
||
783 | |||
784 | list($solarYear, $solarMonth, $solarDay) = explode('-', $date); |
||
785 | |||
786 | return [ |
||
787 | 'solar_year' => $solarYear, |
||
788 | 'solar_month' => sprintf('%02d', $solarMonth), |
||
789 | 'solar_day' => sprintf('%02d', $solarDay), |
||
790 | ]; |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * 获取两个日期之间的距离. |
||
795 | * |
||
796 | * @param string|\DateTime $date1 |
||
797 | * @param string|\DateTime $date2 |
||
798 | * |
||
799 | * @return bool|\DateInterval |
||
800 | */ |
||
801 | public function dateDiff($date1, $date2) |
||
802 | { |
||
803 | if (!($date1 instanceof DateTime)) { |
||
804 | $date1 = $this->makeDate($date1); |
||
805 | } |
||
806 | |||
807 | if (!($date2 instanceof DateTime)) { |
||
808 | $date2 = $this->makeDate($date2); |
||
809 | } |
||
810 | |||
811 | return $date1->diff($date2); |
||
812 | } |
||
813 | |||
814 | /** |
||
815 | * 获取两个日期之间以年为单位的距离. |
||
816 | * |
||
817 | * @param array $lunar1 |
||
818 | * @param array $lunar2 |
||
819 | * @param bool $absolute |
||
820 | * |
||
821 | * @return int |
||
822 | */ |
||
823 | public function diffInYears($lunar1, $lunar2, $absolute = true) |
||
824 | { |
||
825 | $solar1 = |
||
826 | $this->lunar2solar($lunar1['lunar_year'], $lunar1['lunar_month'], $lunar1['lunar_day'], $lunar1['is_leap']); |
||
827 | $date1 = $this->makeDate("{$solar1['solar_year']}-{$solar1['solar_month']}-{$solar1['solar_day']}"); |
||
828 | |||
829 | $solar2 = |
||
830 | $this->lunar2solar($lunar2['lunar_year'], $lunar2['lunar_month'], $lunar2['lunar_day'], $lunar2['is_leap']); |
||
831 | $date2 = $this->makeDate("{$solar2['solar_year']}-{$solar2['solar_month']}-{$solar2['solar_day']}"); |
||
832 | |||
833 | if ($date1 < $date2) { |
||
834 | $lessLunar = $lunar1; |
||
835 | $greaterLunar = $lunar2; |
||
836 | $changed = false; |
||
837 | } else { |
||
838 | $lessLunar = $lunar2; |
||
839 | $greaterLunar = $lunar1; |
||
840 | $changed = true; |
||
841 | } |
||
842 | |||
843 | $monthAdjustFactor = $greaterLunar['lunar_day'] >= $lessLunar['lunar_day'] ? 0 : 1; |
||
844 | if ($greaterLunar['lunar_month'] == $lessLunar['lunar_month']) { |
||
845 | if ($greaterLunar['is_leap'] && !$lessLunar['is_leap']) { |
||
846 | $monthAdjustFactor = 0; |
||
847 | } elseif (!$greaterLunar['is_leap'] && $lessLunar['is_leap']) { |
||
848 | $monthAdjustFactor = 1; |
||
849 | } |
||
850 | } |
||
851 | $yearAdjustFactor = $greaterLunar['lunar_month'] - $monthAdjustFactor >= $lessLunar['lunar_month'] ? 0 : 1; |
||
852 | $diff = $greaterLunar['lunar_year'] - $yearAdjustFactor - $lessLunar['lunar_year']; |
||
853 | |||
854 | return $absolute ? $diff : ($changed ? -1 * $diff : $diff); |
||
855 | } |
||
856 | |||
857 | /** |
||
858 | * 获取两个日期之间以月为单位的距离. |
||
859 | * @param array $lunar1 |
||
860 | * @param array $lunar2 |
||
861 | * @param bool $absolute |
||
862 | * @return float|int|mixed |
||
863 | */ |
||
864 | public function diffInMonths($lunar1, $lunar2, $absolute = true) |
||
865 | { |
||
866 | $solar1 = |
||
867 | $this->lunar2solar($lunar1['lunar_year'], $lunar1['lunar_month'], $lunar1['lunar_day'], $lunar1['is_leap']); |
||
868 | $date1 = $this->makeDate("{$solar1['solar_year']}-{$solar1['solar_month']}-{$solar1['solar_day']}"); |
||
869 | |||
870 | $solar2 = |
||
871 | $this->lunar2solar($lunar2['lunar_year'], $lunar2['lunar_month'], $lunar2['lunar_day'], $lunar2['is_leap']); |
||
872 | $date2 = $this->makeDate("{$solar2['solar_year']}-{$solar2['solar_month']}-{$solar2['solar_day']}"); |
||
873 | |||
874 | if ($date1 < $date2) { |
||
875 | $lessLunar = $lunar1; |
||
876 | $greaterLunar = $lunar2; |
||
877 | $changed = false; |
||
878 | } else { |
||
879 | $lessLunar = $lunar2; |
||
880 | $greaterLunar = $lunar1; |
||
881 | $changed = true; |
||
882 | } |
||
883 | |||
884 | $diff = 0; |
||
885 | |||
886 | if ($lessLunar['lunar_year'] == $greaterLunar['lunar_year']) { |
||
887 | $leapMonth = $this->leapMonth($lessLunar['lunar_year']); |
||
888 | $lessLunarAdjustFactor = |
||
889 | $lessLunar['is_leap'] || (0 < $leapMonth && $leapMonth < $lessLunar['lunar_month']) ? 1 : 0; |
||
890 | $greaterLunarAdjustFactor = |
||
891 | $greaterLunar['is_leap'] || (0 < $leapMonth && $leapMonth < $greaterLunar['lunar_month']) ? 1 : 0; |
||
892 | $diff = |
||
893 | $greaterLunar['lunar_month'] + $greaterLunarAdjustFactor - $lessLunar['lunar_month'] - $lessLunarAdjustFactor; |
||
894 | } else { |
||
895 | $lessLunarLeapMonth = $this->leapMonth($lessLunar['lunar_year']); |
||
896 | $greaterLunarLeapMonth = $this->leapMonth($greaterLunar['lunar_year']); |
||
897 | |||
898 | $lessLunarAdjustFactor = |
||
899 | (!$lessLunar['is_leap'] && $lessLunarLeapMonth == $lessLunar['lunar_month']) || $lessLunarLeapMonth > $lessLunar['lunar_month'] ? 1 : 0; |
||
900 | $diff += 12 + $lessLunarAdjustFactor - $lessLunar['lunar_month']; |
||
901 | for ($i = $lessLunar['lunar_year'] + 1; $i < $greaterLunar['lunar_year']; ++$i) { |
||
902 | $diff += $this->monthsOfYear($i); |
||
903 | } |
||
904 | $greaterLunarAdjustFactor = |
||
905 | $greaterLunar['is_leap'] || (0 < $greaterLunarLeapMonth && $greaterLunarLeapMonth < $greaterLunar['lunar_month']) ? 1 : 0; |
||
906 | $diff += $greaterLunarAdjustFactor + $greaterLunar['lunar_month']; |
||
907 | } |
||
908 | |||
909 | $diff -= $greaterLunar['lunar_day'] >= $lessLunar['lunar_day'] ? 0 : 1; |
||
910 | |||
911 | return $absolute ? $diff : ($changed ? -1 * $diff : $diff); |
||
912 | } |
||
913 | |||
914 | /** |
||
915 | * 获取两个日期之间以日为单位的距离. |
||
916 | * |
||
917 | * @param array $lunar1 |
||
918 | * @param array $lunar2 |
||
919 | * @param bool $absolute |
||
920 | * |
||
921 | * @return int |
||
922 | */ |
||
923 | public function diffInDays($lunar1, $lunar2, $absolute = true) |
||
924 | { |
||
925 | $solar1 = |
||
926 | $this->lunar2solar($lunar1['lunar_year'], $lunar1['lunar_month'], $lunar1['lunar_day'], $lunar1['is_leap']); |
||
927 | $date1 = $this->makeDate("{$solar1['solar_year']}-{$solar1['solar_month']}-{$solar1['solar_day']}"); |
||
928 | |||
929 | $solar2 = |
||
930 | $this->lunar2solar($lunar2['lunar_year'], $lunar2['lunar_month'], $lunar2['lunar_day'], $lunar2['is_leap']); |
||
931 | $date2 = $this->makeDate("{$solar2['solar_year']}-{$solar2['solar_month']}-{$solar2['solar_day']}"); |
||
932 | |||
933 | return $date1->diff($date2, $absolute)->format('%r%a'); |
||
934 | } |
||
935 | |||
936 | /** |
||
937 | * 增加年数. |
||
938 | * |
||
939 | * @param array $lunar |
||
940 | * @param int $value |
||
941 | * @param bool $overFlow |
||
942 | * |
||
943 | * @return array |
||
944 | */ |
||
945 | public function addYears($lunar, $value = 1, $overFlow = true) |
||
946 | { |
||
947 | $newYear = $lunar['lunar_year'] + $value; |
||
948 | $newMonth = $lunar['lunar_month']; |
||
949 | $newDay = $lunar['lunar_day']; |
||
950 | $isLeap = $lunar['is_leap']; |
||
951 | $needOverFlow = false; |
||
952 | |||
953 | $leapMonth = $this->leapMonth($newYear); |
||
954 | $isLeap = $isLeap && $newMonth == $leapMonth; |
||
955 | $maxDays = $isLeap ? $this->leapDays($newYear) : $this->lunarDays($newYear, $newMonth); |
||
956 | |||
957 | if ($newDay > $maxDays) { |
||
958 | if ($overFlow) { |
||
959 | $newDay = 1; |
||
960 | $needOverFlow = true; |
||
961 | } else { |
||
962 | $newDay = $maxDays; |
||
963 | } |
||
964 | } |
||
965 | $ret = $this->lunar($newYear, $newMonth, $newDay, $isLeap); |
||
966 | if ($needOverFlow) { |
||
967 | $ret = $this->addMonths($ret, 1, $overFlow); |
||
968 | } |
||
969 | |||
970 | return $ret; |
||
971 | } |
||
972 | |||
973 | /** |
||
974 | * 减少年数. |
||
975 | * |
||
976 | * @param array $lunar |
||
977 | * @param int $value |
||
978 | * @param bool $overFlow |
||
979 | * |
||
980 | * @return array |
||
981 | */ |
||
982 | public function subYears($lunar, $value = 1, $overFlow = true) |
||
983 | { |
||
984 | return $this->addYears($lunar, -1 * $value, $overFlow); |
||
985 | } |
||
986 | |||
987 | /** |
||
988 | * 增加月数. |
||
989 | * |
||
990 | * @param array $lunar |
||
991 | * @param int $value |
||
992 | * @param bool $overFlow |
||
993 | * |
||
994 | * @return array |
||
995 | */ |
||
996 | public function addMonths($lunar, $value = 1, $overFlow = true) |
||
997 | { |
||
998 | if (0 > $value) { |
||
999 | return $this->subMonths($lunar, -1 * $value, $overFlow); |
||
1000 | } else { |
||
1001 | $newYear = $lunar['lunar_year']; |
||
1002 | $newMonth = $lunar['lunar_month']; |
||
1003 | $newDay = $lunar['lunar_day']; |
||
1004 | $isLeap = $lunar['is_leap']; |
||
1005 | |||
1006 | while (0 < $value) { |
||
1007 | $leapMonth = $this->leapMonth($newYear); |
||
1008 | if (0 < $leapMonth) { |
||
1009 | $currentIsLeap = $isLeap; |
||
1010 | $isLeap = $newMonth + $value == $leapMonth + ($isLeap ? 0 : 1); |
||
1011 | |||
1012 | if ((!$currentIsLeap && $leapMonth == $newMonth) || ($newMonth < $leapMonth && $newMonth + $value > $leapMonth)) { |
||
1013 | --$value; |
||
1014 | } |
||
1015 | } else { |
||
1016 | $isLeap = false; |
||
1017 | } |
||
1018 | |||
1019 | if (13 > $newMonth + $value) { |
||
1020 | $newMonth += $value; |
||
1021 | $value = 0; |
||
1022 | } else { |
||
1023 | $value = $value + $newMonth - 13; |
||
1024 | ++$newYear; |
||
1025 | $newMonth = 1; |
||
1026 | } |
||
1027 | |||
1028 | if (0 == $value) { |
||
1029 | $maxDays = $isLeap ? $this->leapDays($newYear) : $this->lunarDays($newYear, $newMonth); |
||
1030 | if ($newDay > $maxDays) { |
||
1031 | if ($overFlow) { |
||
1032 | $newDay = 1; |
||
1033 | ++$value; |
||
1034 | } else { |
||
1035 | $newDay = $maxDays; |
||
1036 | } |
||
1037 | } |
||
1038 | } |
||
1039 | } |
||
1040 | |||
1041 | return $this->lunar($newYear, $newMonth, $newDay, $isLeap); |
||
1042 | } |
||
1043 | } |
||
1044 | |||
1045 | /** |
||
1046 | * 减少月数. |
||
1047 | * |
||
1048 | * @param array $lunar |
||
1049 | * @param int $value |
||
1050 | * @param bool $overFlow |
||
1051 | * |
||
1052 | * @return array |
||
1053 | */ |
||
1054 | public function subMonths($lunar, $value = 1, $overFlow = true) |
||
1055 | { |
||
1056 | if (0 > $value) { |
||
1057 | return $this->addMonths($lunar, -1 * $value, $overFlow); |
||
1058 | } else { |
||
1059 | $newYear = $lunar['lunar_year']; |
||
1060 | $newMonth = $lunar['lunar_month']; |
||
1061 | $newDay = $lunar['lunar_day']; |
||
1062 | $isLeap = $lunar['is_leap']; |
||
1063 | $needOverFlow = false; |
||
1064 | |||
1065 | while (0 < $value) { |
||
1066 | $leapMonth = $this->leapMonth($newYear); |
||
1067 | |||
1068 | if (0 < $leapMonth) { |
||
1069 | $isLeap = $newMonth - $value == $leapMonth; |
||
1070 | |||
1071 | if ($newMonth >= $leapMonth && $newMonth - $value < $leapMonth) { |
||
1072 | --$value; |
||
1073 | } |
||
1074 | } else { |
||
1075 | $isLeap = false; |
||
1076 | } |
||
1077 | |||
1078 | if ($newMonth > $value) { |
||
1079 | $newMonth -= $value; |
||
1080 | $value = 0; |
||
1081 | } else { |
||
1082 | $value = $value - $newMonth; |
||
1083 | --$newYear; |
||
1084 | $newMonth = 12; |
||
1085 | } |
||
1086 | |||
1087 | if (0 == $value) { |
||
1088 | $maxDays = $isLeap ? $this->leapDays($newYear) : $this->lunarDays($newYear, $newMonth); |
||
1089 | if ($newDay > $maxDays) { |
||
1090 | $newDay = $maxDays; |
||
1091 | $needOverFlow = $overFlow; |
||
1092 | } |
||
1093 | } |
||
1094 | } |
||
1095 | |||
1096 | $ret = $this->lunar($newYear, $newMonth, $newDay, $isLeap); |
||
1097 | if ($needOverFlow) { |
||
1098 | $ret = $this->addDays($ret, 1); |
||
1099 | } |
||
1100 | |||
1101 | return $ret; |
||
1102 | } |
||
1103 | } |
||
1104 | |||
1105 | /** |
||
1106 | * 增加天数. |
||
1107 | * |
||
1108 | * @param array $lunar |
||
1109 | * @param int $value |
||
1110 | * |
||
1111 | * @return array |
||
1112 | */ |
||
1113 | public function addDays($lunar, $value = 1) |
||
1114 | { |
||
1115 | $solar = |
||
1116 | $this->lunar2solar($lunar['lunar_year'], $lunar['lunar_month'], $lunar['lunar_day'], $lunar['is_leap']); |
||
1117 | $date = $this->makeDate("{$solar['solar_year']}-{$solar['solar_month']}-{$solar['solar_day']}"); |
||
1118 | $date->modify($value.' day'); |
||
1119 | |||
1120 | return $this->solar2lunar((int)$date->format('Y'), (int)$date->format('m'), (int)$date->format('d')); |
||
1121 | } |
||
1122 | |||
1123 | /** |
||
1124 | * 减少天数. |
||
1125 | * |
||
1126 | * @param array $lunar |
||
1127 | * @param int $value |
||
1128 | * |
||
1129 | * @return array |
||
1130 | */ |
||
1131 | public function subDays($lunar, $value = 1) |
||
1132 | { |
||
1133 | return $this->addDays($lunar, -1 * $value); |
||
1134 | } |
||
1135 | |||
1136 | /** |
||
1137 | * 创建日期对象 |
||
1138 | * |
||
1139 | * @param string $string |
||
1140 | * @param string $timezone |
||
1141 | * |
||
1142 | * @return \DateTime |
||
1143 | */ |
||
1144 | protected function makeDate($string = 'now', $timezone = 'PRC') |
||
1145 | { |
||
1146 | return new DateTime($string, new DateTimeZone($timezone)); |
||
1147 | } |
||
1148 | |||
1149 | /** |
||
1150 | * 获取时柱. |
||
1151 | * |
||
1152 | * @param int $hour 0~23 小时格式 |
||
1153 | * @param int $ganZhiDay 干支日期 |
||
1154 | * |
||
1155 | * @return array |
||
1156 | * |
||
1157 | * @see https://baike.baidu.com/item/%E6%97%B6%E6%9F%B1/6274024 |
||
1158 | */ |
||
1159 | protected function ganZhiHour($hour, $ganZhiDay) |
||
1160 | { |
||
1161 | if (!is_numeric($hour) || $hour < 0 || $hour > 23) { |
||
1162 | return [null, null, null]; |
||
1163 | } |
||
1164 | |||
1165 | $zhiHour = intval(($hour + 1) / 2); |
||
1166 | $zhiHour = 12 === $zhiHour ? 0 : $zhiHour; |
||
1167 | |||
1168 | return [ |
||
1169 | $this->gan[($ganZhiDay % 10 % 5 * 2 + $zhiHour) % 10].$this->zhi[$zhiHour], |
||
1170 | $this->zhi[$zhiHour].'时', |
||
1171 | sprintf('%02d', $hour), |
||
1172 | ]; |
||
1173 | } |
||
1174 | } |
||
1175 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.