Passed
Push — master ( 0829fd...abe994 )
by ma
02:07
created

Calendar::solar()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 18
rs 9.4888
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($date->format('w')); // 0 ~ 6 修正 星期七 为 星期日
0 ignored issues
show
Bug introduced by
$date->format('w') of type string is incompatible with the type double|integer expected by parameter $num of abs(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
        $week = abs(/** @scrutinizer ignore-type */ $date->format('w')); // 0 ~ 6 修正 星期七 为 星期日
Loading history...
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', str_split($this->solarTerms[$year - 1900], 5));
0 ignored issues
show
Bug introduced by
It seems like str_split($this->solarTerms[$year - 1900], 5) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

437
        $solarTermsOfYear = array_map('hexdec', /** @scrutinizer ignore-type */ str_split($this->solarTerms[$year - 1900], 5));
Loading history...
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 = $this->dateDiff($date, '1900-01-31')->days;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dateDiff($date, '1900-01-31')->days can also be of type boolean. However, the property $days is declared as type false|integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $daysOfYear does not seem to be defined for all execution paths leading up to this point.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $daysOfMonth does not seem to be defined for all execution paths leading up to this point.
Loading history...
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)); // 返回当月「节气」为几日开始
0 ignored issues
show
Bug introduced by
$year of type string is incompatible with the type integer expected by parameter $year of tinymeng\tools\Calendar::getTerm(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

659
        $firstNode = $this->getTerm(/** @scrutinizer ignore-type */ $year, ($month * 2 - 1)); // 返回当月「节气」为几日开始
Loading history...
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) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (2100 == $year && 12 == ... == $month && $day < 31, Probably Intended Meaning: 2100 == $year && 12 == $...== $month && $day < 31)
Loading history...
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
Unused Code introduced by
The call to tinymeng\tools\Calendar::leapDays() has too many arguments starting with $month. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

747
            /** @scrutinizer ignore-call */ 
748
            $maxDays = $this->leapDays($year, $month);

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.

Loading history...
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
     *
860
     * @param array $lunar1
861
     * @param array $lunar2
862
     * @param bool  $absolute
863
     *
864
     * @return int
865
     */
866
    public function diffInMonths($lunar1, $lunar2, $absolute = true)
867
    {
868
        $solar1 =
869
            $this->lunar2solar($lunar1['lunar_year'], $lunar1['lunar_month'], $lunar1['lunar_day'], $lunar1['is_leap']);
870
        $date1 = $this->makeDate("{$solar1['solar_year']}-{$solar1['solar_month']}-{$solar1['solar_day']}");
871
872
        $solar2 =
873
            $this->lunar2solar($lunar2['lunar_year'], $lunar2['lunar_month'], $lunar2['lunar_day'], $lunar2['is_leap']);
874
        $date2 = $this->makeDate("{$solar2['solar_year']}-{$solar2['solar_month']}-{$solar2['solar_day']}");
875
876
        if ($date1 < $date2) {
877
            $lessLunar = $lunar1;
878
            $greaterLunar = $lunar2;
879
            $changed = false;
880
        } else {
881
            $lessLunar = $lunar2;
882
            $greaterLunar = $lunar1;
883
            $changed = true;
884
        }
885
886
        $diff = 0;
887
888
        if ($lessLunar['lunar_year'] == $greaterLunar['lunar_year']) {
889
            $leapMonth = $this->leapMonth($lessLunar['lunar_year']);
890
            $lessLunarAdjustFactor =
891
                $lessLunar['is_leap'] || (0 < $leapMonth && $leapMonth < $lessLunar['lunar_month']) ? 1 : 0;
892
            $greaterLunarAdjustFactor =
893
                $greaterLunar['is_leap'] || (0 < $leapMonth && $leapMonth < $greaterLunar['lunar_month']) ? 1 : 0;
894
            $diff =
895
                $greaterLunar['lunar_month'] + $greaterLunarAdjustFactor - $lessLunar['lunar_month'] - $lessLunarAdjustFactor;
896
        } else {
897
            $lessLunarLeapMonth = $this->leapMonth($lessLunar['lunar_year']);
898
            $greaterLunarLeapMonth = $this->leapMonth($greaterLunar['lunar_year']);
899
900
            $lessLunarAdjustFactor =
901
                (!$lessLunar['is_leap'] && $lessLunarLeapMonth == $lessLunar['lunar_month']) || $lessLunarLeapMonth > $lessLunar['lunar_month'] ? 1 : 0;
902
            $diff += 12 + $lessLunarAdjustFactor - $lessLunar['lunar_month'];
903
            for ($i = $lessLunar['lunar_year'] + 1; $i < $greaterLunar['lunar_year']; ++$i) {
904
                $diff += $this->monthsOfYear($i);
905
            }
906
            $greaterLunarAdjustFactor =
907
                $greaterLunar['is_leap'] || (0 < $greaterLunarLeapMonth && $greaterLunarLeapMonth < $greaterLunar['lunar_month']) ? 1 : 0;
908
            $diff += $greaterLunarAdjustFactor + $greaterLunar['lunar_month'];
909
        }
910
911
        $diff -= $greaterLunar['lunar_day'] >= $lessLunar['lunar_day'] ? 0 : 1;
912
913
        return $absolute ? $diff : ($changed ? -1 * $diff : $diff);
914
    }
915
916
    /**
917
     * 获取两个日期之间以日为单位的距离.
918
     *
919
     * @param array $lunar1
920
     * @param array $lunar2
921
     * @param bool  $absolute
922
     *
923
     * @return int
924
     */
925
    public function diffInDays($lunar1, $lunar2, $absolute = true)
926
    {
927
        $solar1 =
928
            $this->lunar2solar($lunar1['lunar_year'], $lunar1['lunar_month'], $lunar1['lunar_day'], $lunar1['is_leap']);
929
        $date1 = $this->makeDate("{$solar1['solar_year']}-{$solar1['solar_month']}-{$solar1['solar_day']}");
930
931
        $solar2 =
932
            $this->lunar2solar($lunar2['lunar_year'], $lunar2['lunar_month'], $lunar2['lunar_day'], $lunar2['is_leap']);
933
        $date2 = $this->makeDate("{$solar2['solar_year']}-{$solar2['solar_month']}-{$solar2['solar_day']}");
934
935
        return $date1->diff($date2, $absolute)->format('%r%a');
936
    }
937
938
    /**
939
     * 增加年数.
940
     *
941
     * @param array $lunar
942
     * @param int   $value
943
     * @param bool  $overFlow
944
     *
945
     * @return array
946
     */
947
    public function addYears($lunar, $value = 1, $overFlow = true)
948
    {
949
        $newYear = $lunar['lunar_year'] + $value;
950
        $newMonth = $lunar['lunar_month'];
951
        $newDay = $lunar['lunar_day'];
952
        $isLeap = $lunar['is_leap'];
953
        $needOverFlow = false;
954
955
        $leapMonth = $this->leapMonth($newYear);
956
        $isLeap = $isLeap && $newMonth == $leapMonth;
957
        $maxDays = $isLeap ? $this->leapDays($newYear) : $this->lunarDays($newYear, $newMonth);
958
959
        if ($newDay > $maxDays) {
960
            if ($overFlow) {
961
                $newDay = 1;
962
                $needOverFlow = true;
963
            } else {
964
                $newDay = $maxDays;
965
            }
966
        }
967
        $ret = $this->lunar($newYear, $newMonth, $newDay, $isLeap);
968
        if ($needOverFlow) {
969
            $ret = $this->addMonths($ret, 1, $overFlow);
970
        }
971
972
        return $ret;
973
    }
974
975
    /**
976
     * 减少年数.
977
     *
978
     * @param array $lunar
979
     * @param int   $value
980
     * @param bool  $overFlow
981
     *
982
     * @return array
983
     */
984
    public function subYears($lunar, $value = 1, $overFlow = true)
985
    {
986
        return $this->addYears($lunar, -1 * $value, $overFlow);
987
    }
988
989
    /**
990
     * 增加月数.
991
     *
992
     * @param array $lunar
993
     * @param int   $value
994
     * @param bool  $overFlow
995
     *
996
     * @return array
997
     */
998
    public function addMonths($lunar, $value = 1, $overFlow = true)
999
    {
1000
        if (0 > $value) {
1001
            return $this->subMonths($lunar, -1 * $value, $overFlow);
1002
        } else {
1003
            $newYear = $lunar['lunar_year'];
1004
            $newMonth = $lunar['lunar_month'];
1005
            $newDay = $lunar['lunar_day'];
1006
            $isLeap = $lunar['is_leap'];
1007
1008
            while (0 < $value) {
1009
                $leapMonth = $this->leapMonth($newYear);
1010
                if (0 < $leapMonth) {
1011
                    $currentIsLeap = $isLeap;
1012
                    $isLeap = $newMonth + $value == $leapMonth + ($isLeap ? 0 : 1);
1013
1014
                    if ((!$currentIsLeap && $leapMonth == $newMonth) || ($newMonth < $leapMonth && $newMonth + $value > $leapMonth)) {
1015
                        --$value;
1016
                    }
1017
                } else {
1018
                    $isLeap = false;
1019
                }
1020
1021
                if (13 > $newMonth + $value) {
1022
                    $newMonth += $value;
1023
                    $value = 0;
1024
                } else {
1025
                    $value = $value + $newMonth - 13;
1026
                    ++$newYear;
1027
                    $newMonth = 1;
1028
                }
1029
1030
                if (0 == $value) {
1031
                    $maxDays = $isLeap ? $this->leapDays($newYear) : $this->lunarDays($newYear, $newMonth);
1032
                    if ($newDay > $maxDays) {
1033
                        if ($overFlow) {
1034
                            $newDay = 1;
1035
                            ++$value;
1036
                        } else {
1037
                            $newDay = $maxDays;
1038
                        }
1039
                    }
1040
                }
1041
            }
1042
1043
            return $this->lunar($newYear, $newMonth, $newDay, $isLeap);
1044
        }
1045
    }
1046
1047
    /**
1048
     * 减少月数.
1049
     *
1050
     * @param array $lunar
1051
     * @param int   $value
1052
     * @param bool  $overFlow
1053
     *
1054
     * @return array
1055
     */
1056
    public function subMonths($lunar, $value = 1, $overFlow = true)
1057
    {
1058
        if (0 > $value) {
1059
            return $this->addMonths($lunar, -1 * $value, $overFlow);
1060
        } else {
1061
            $newYear = $lunar['lunar_year'];
1062
            $newMonth = $lunar['lunar_month'];
1063
            $newDay = $lunar['lunar_day'];
1064
            $isLeap = $lunar['is_leap'];
1065
            $needOverFlow = false;
1066
1067
            while (0 < $value) {
1068
                $leapMonth = $this->leapMonth($newYear);
1069
1070
                if (0 < $leapMonth) {
1071
                    $isLeap = $newMonth - $value == $leapMonth;
1072
1073
                    if ($newMonth >= $leapMonth && $newMonth - $value < $leapMonth) {
1074
                        --$value;
1075
                    }
1076
                } else {
1077
                    $isLeap = false;
1078
                }
1079
1080
                if ($newMonth > $value) {
1081
                    $newMonth -= $value;
1082
                    $value = 0;
1083
                } else {
1084
                    $value = $value - $newMonth;
1085
                    --$newYear;
1086
                    $newMonth = 12;
1087
                }
1088
1089
                if (0 == $value) {
1090
                    $maxDays = $isLeap ? $this->leapDays($newYear) : $this->lunarDays($newYear, $newMonth);
1091
                    if ($newDay > $maxDays) {
1092
                        $newDay = $maxDays;
1093
                        $needOverFlow = $overFlow;
1094
                    }
1095
                }
1096
            }
1097
1098
            $ret = $this->lunar($newYear, $newMonth, $newDay, $isLeap);
1099
            if ($needOverFlow) {
1100
                $ret = $this->addDays($ret, 1);
1101
            }
1102
1103
            return $ret;
1104
        }
1105
    }
1106
1107
    /**
1108
     * 增加天数.
1109
     *
1110
     * @param array $lunar
1111
     * @param int   $value
1112
     *
1113
     * @return array
1114
     */
1115
    public function addDays($lunar, $value = 1)
1116
    {
1117
        $solar =
1118
            $this->lunar2solar($lunar['lunar_year'], $lunar['lunar_month'], $lunar['lunar_day'], $lunar['is_leap']);
1119
        $date = $this->makeDate("{$solar['solar_year']}-{$solar['solar_month']}-{$solar['solar_day']}");
1120
        $date->modify($value.' day');
1121
1122
        return $this->solar2lunar($date->format('Y'), $date->format('m'), $date->format('d'));
0 ignored issues
show
Bug introduced by
$date->format('d') of type string is incompatible with the type integer expected by parameter $day of tinymeng\tools\Calendar::solar2lunar(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1122
        return $this->solar2lunar($date->format('Y'), $date->format('m'), /** @scrutinizer ignore-type */ $date->format('d'));
Loading history...
Bug introduced by
$date->format('m') of type string is incompatible with the type integer expected by parameter $month of tinymeng\tools\Calendar::solar2lunar(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1122
        return $this->solar2lunar($date->format('Y'), /** @scrutinizer ignore-type */ $date->format('m'), $date->format('d'));
Loading history...
Bug introduced by
$date->format('Y') of type string is incompatible with the type integer expected by parameter $year of tinymeng\tools\Calendar::solar2lunar(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1122
        return $this->solar2lunar(/** @scrutinizer ignore-type */ $date->format('Y'), $date->format('m'), $date->format('d'));
Loading history...
1123
    }
1124
1125
    /**
1126
     * 减少天数.
1127
     *
1128
     * @param array $lunar
1129
     * @param int   $value
1130
     *
1131
     * @return array
1132
     */
1133
    public function subDays($lunar, $value = 1)
1134
    {
1135
        return $this->addDays($lunar, -1 * $value);
1136
    }
1137
1138
    /**
1139
     * 创建日期对象
1140
     *
1141
     * @param string $string
1142
     * @param string $timezone
1143
     *
1144
     * @return \DateTime
1145
     */
1146
    protected function makeDate($string = 'now', $timezone = 'PRC')
1147
    {
1148
        return new DateTime($string, new DateTimeZone($timezone));
1149
    }
1150
1151
    /**
1152
     * 获取时柱.
1153
     *
1154
     * @param int $hour      0~23 小时格式
1155
     * @param int $ganZhiDay 干支日期
1156
     *
1157
     * @return array
1158
     *
1159
     * @see https://baike.baidu.com/item/%E6%97%B6%E6%9F%B1/6274024
1160
     */
1161
    protected function ganZhiHour($hour, $ganZhiDay)
1162
    {
1163
        if (!is_numeric($hour) || $hour < 0 || $hour > 23) {
0 ignored issues
show
introduced by
The condition is_numeric($hour) is always true.
Loading history...
1164
            return [null, null, null];
1165
        }
1166
1167
        $zhiHour = intval(($hour + 1) / 2);
1168
        $zhiHour = 12 === $zhiHour ? 0 : $zhiHour;
1169
1170
        return [
1171
            $this->gan[($ganZhiDay % 10 % 5 * 2 + $zhiHour) % 10].$this->zhi[$zhiHour],
1172
            $this->zhi[$zhiHour].'时',
1173
            sprintf('%02d', $hour),
1174
        ];
1175
    }
1176
}
1177