diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b286bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea/ +vendor/ +composer.lock +composer.phar +.phpunit.result.cache +.DS_Store +package.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0bf586 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Tyme [![License](https://img.shields.io/badge/license-MIT-4EB1BA.svg?style=flat-square)](https://github.com/6tail/tyme4php/blob/master/LICENSE) + +Tyme是一个非常强大的日历工具库,可以看作 [Lunar](https://6tail.cn/calendar/api.html "https://6tail.cn/calendar/api.html") 的升级版,拥有更优的设计和扩展性,支持公历和农历、星座、干支、生肖、节气、法定假日等。 + + +> 基于php8.1开发。 + +## composer + + composer require 6tail/tyme4php + + getLunarDay(); + +## 单文件版本 + +1. 下载本源代码,执行tools/build-standalone.php,可在tools目录下生成Tyme.php单文件。 +2. 可在 [Releases](https://github.com/6tail/tyme4php/releases) 中下载对应版本的Tyme.php单文件。 + + + getLunarDay(); + +## 文档 + +请移步至 [https://6tail.cn/tyme.html](https://6tail.cn/tyme.html "https://6tail.cn/tyme.html") + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=6tail/tyme4php&type=Date)](https://star-history.com/#6tail/tyme4php&Date) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..931d927 --- /dev/null +++ b/composer.json @@ -0,0 +1,36 @@ +{ + "name": "6tail/tyme4php", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "6tail", + "email": "6tail@6tail.cn", + "homepage": "https://6tail.cn" + } + ], + "minimum-stability": "stable", + "require": { + "php": ">=8.1", + "ext-bcmath": "*" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "autoload": { + "psr-4": { + "com\\tyme\\": "src/" + } + }, + "homepage": "https://github.com/6tail/tyme4php", + "keywords": [ + "公历", + "农历", + "星座", + "干支", + "生肖", + "节气", + "法定假日" + ], + "description": "a calendar library" +} diff --git a/src/AbstractCulture.php b/src/AbstractCulture.php new file mode 100644 index 0000000..cc0888f --- /dev/null +++ b/src/AbstractCulture.php @@ -0,0 +1,48 @@ +getName(); + } + + /** + * @param mixed $o 对象 + * @return bool true/false + */ + function equals(mixed $o): bool + { + return $o instanceof Culture && $this->__toString() == $o->__toString(); + } + + /** + * 转换为不超范围的索引 + * + * @param int|null $index 索引 + * @param string|null $name 名称 + * @param int|null $size 数量 + * @return int 索引,从0开始 + */ + protected function indexOf(int $index = null, string $name = null, int $size = null): int + { + if ($index !== null && $size !== null) { + $i = $index % $size; + if ($i < 0) { + $i += $size; + } + return $i; + } + throw new InvalidArgumentException(sprintf('invalid name: %s, size: %d', $name, $size)); + } +} diff --git a/src/AbstractCultureDay.php b/src/AbstractCultureDay.php new file mode 100644 index 0000000..507c666 --- /dev/null +++ b/src/AbstractCultureDay.php @@ -0,0 +1,53 @@ +culture = $culture; + $this->dayIndex = $dayIndex; + } + + /** + * 天索引 + * + * @return int 索引 + */ + function getDayIndex(): int + { + return $this->dayIndex; + } + + protected function getCulture(): Culture + { + return $this->culture; + } + + function __toString(): string + { + return sprintf('%s第%d天', $this->culture, $this->dayIndex + 1); + } + + function getName(): string + { + return $this->culture->getName(); + } +} diff --git a/src/AbstractTyme.php b/src/AbstractTyme.php new file mode 100644 index 0000000..abc4763 --- /dev/null +++ b/src/AbstractTyme.php @@ -0,0 +1,13 @@ +names = $names; + if ($index !== null) { + $this->index = $this->indexOf($index); + } else if ($name !== null) { + $this->index = $this->indexOf(null, $name); + } + } + + /** + * 名称 + * + * @return string 名称 + */ + function getName(): string + { + return $this->names[$this->index]; + } + + /** + * 索引 + * + * @return int 索引,从0开始 + */ + function getIndex(): int + { + return $this->index; + } + + /** + * 数量 + * + * @return int 数量 + */ + function getSize(): int + { + return count($this->names); + } + + protected function indexOf(int $index = null, string $name = null, int $size = null): int + { + if ($index !== null) { + if ($size === null) { + return parent::indexOf($index, null, $this->getSize()); + } else { + return parent::indexOf($index, null, $size); + } + } else if ($name !== null) { + // 传了name,则忽略size + for ($i = 0, $j = $this->getSize(); $i < $j; $i++) { + if ($this->names[$i] == $name) { + return $i; + } + } + throw new InvalidArgumentException(sprintf('illegal name: %d', $name)); + } + throw new InvalidArgumentException('need index or name'); + } + + /** + * 推移后的索引 + * + * @param int $n 推移步数 + * @return int 索引,从0开始 + */ + protected function nextIndex(int $n): int + { + return $this->indexOf($this->index + $n); + } + +} diff --git a/src/Tyme.php b/src/Tyme.php new file mode 100644 index 0000000..db19821 --- /dev/null +++ b/src/Tyme.php @@ -0,0 +1,19 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Beast.php b/src/culture/Beast.php new file mode 100644 index 0000000..fa1b5b8 --- /dev/null +++ b/src/culture/Beast.php @@ -0,0 +1,50 @@ +nextIndex($n)); + } + + /** + * 宫 + * + * @return Zone 宫 + */ + function getZone(): Zone + { + return Zone::fromIndex($this->index); + } +} diff --git a/src/culture/Constellation.php b/src/culture/Constellation.php new file mode 100644 index 0000000..63eaaf6 --- /dev/null +++ b/src/culture/Constellation.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Direction.php b/src/culture/Direction.php new file mode 100644 index 0000000..3432151 --- /dev/null +++ b/src/culture/Direction.php @@ -0,0 +1,53 @@ +nextIndex($n)); + } + + /** + * 九野 + * + * @return Land 九野 + */ + function getLand(): Land + { + return Land::fromIndex($this->index); + } +} diff --git a/src/culture/Duty.php b/src/culture/Duty.php new file mode 100644 index 0000000..44acb83 --- /dev/null +++ b/src/culture/Duty.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Element.php b/src/culture/Element.php new file mode 100644 index 0000000..761ef05 --- /dev/null +++ b/src/culture/Element.php @@ -0,0 +1,80 @@ +nextIndex($n)); + } + + /** + * 我生者(生) + * + * @return Element 五行 + */ + function getReinforce(): static + { + return $this->next(1); + } + + /** + * 我克者(克) + * + * @return Element 五行 + */ + function getRestrain(): static + { + return $this->next(2); + } + + /** + * 生我者(泄) + * + * @return Element 五行 + */ + function getReinforced(): static + { + return $this->next(-1); + } + + /** + * 克我者(耗) + * + * @return Element 五行 + */ + function getRestrained(): static + { + return $this->next(-2); + } +} diff --git a/src/culture/Land.php b/src/culture/Land.php new file mode 100644 index 0000000..051b055 --- /dev/null +++ b/src/culture/Land.php @@ -0,0 +1,50 @@ +nextIndex($n)); + } + + /** + * 方位 + * + * @return Direction 方位 + */ + function getDirection(): Direction + { + return Direction::fromIndex($this->index); + } +} diff --git a/src/culture/Luck.php b/src/culture/Luck.php new file mode 100644 index 0000000..13f5061 --- /dev/null +++ b/src/culture/Luck.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Phase.php b/src/culture/Phase.php new file mode 100644 index 0000000..73c8761 --- /dev/null +++ b/src/culture/Phase.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Sixty.php b/src/culture/Sixty.php new file mode 100644 index 0000000..2fccea6 --- /dev/null +++ b/src/culture/Sixty.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Sound.php b/src/culture/Sound.php new file mode 100644 index 0000000..cb249db --- /dev/null +++ b/src/culture/Sound.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Ten.php b/src/culture/Ten.php new file mode 100644 index 0000000..4b124ea --- /dev/null +++ b/src/culture/Ten.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Terrain.php b/src/culture/Terrain.php new file mode 100644 index 0000000..678662d --- /dev/null +++ b/src/culture/Terrain.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/Twenty.php b/src/culture/Twenty.php new file mode 100644 index 0000000..c8c5e49 --- /dev/null +++ b/src/culture/Twenty.php @@ -0,0 +1,49 @@ +nextIndex($n)); + } + + /** + * 元 + * @return Sixty 元 + */ + function getSixty(): Sixty + { + return Sixty::fromIndex(intdiv($this->index, 3)); + } +} diff --git a/src/culture/Week.php b/src/culture/Week.php new file mode 100644 index 0000000..a526d5f --- /dev/null +++ b/src/culture/Week.php @@ -0,0 +1,52 @@ +nextIndex($n)); + } + + + /** + * 七曜 + * + * @return SevenStar 七曜 + */ + function getSevenStar(): SevenStar + { + return SevenStar::fromIndex($this->index); + } +} diff --git a/src/culture/Zodiac.php b/src/culture/Zodiac.php new file mode 100644 index 0000000..d5606c1 --- /dev/null +++ b/src/culture/Zodiac.php @@ -0,0 +1,51 @@ +nextIndex($n)); + } + + /** + * 地支 + * + * @return EarthBranch 地支 + */ + function getEarthBranch(): EarthBranch + { + return EarthBranch::fromIndex($this->index); + } +} diff --git a/src/culture/Zone.php b/src/culture/Zone.php new file mode 100644 index 0000000..3fdbcd8 --- /dev/null +++ b/src/culture/Zone.php @@ -0,0 +1,60 @@ +nextIndex($n)); + } + + /** + * 方位 + * + * @return Direction 方位 + */ + function getDirection(): Direction + { + return Direction::fromName($this->getName()); + } + + /** + * 神兽 + * + * @return Beast 神兽 + */ + function getBeast(): Beast + { + return Beast::fromIndex($this->index); + } +} diff --git a/src/culture/dog/Dog.php b/src/culture/dog/Dog.php new file mode 100644 index 0000000..021fd97 --- /dev/null +++ b/src/culture/dog/Dog.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/dog/DogDay.php b/src/culture/dog/DogDay.php new file mode 100644 index 0000000..da55a9b --- /dev/null +++ b/src/culture/dog/DogDay.php @@ -0,0 +1,29 @@ +culture; + } +} diff --git a/src/culture/fetus/FetusDay.php b/src/culture/fetus/FetusDay.php new file mode 100644 index 0000000..aaf39c7 --- /dev/null +++ b/src/culture/fetus/FetusDay.php @@ -0,0 +1,120 @@ +getSixtyCycle(); + $this->fetusHeavenStem = new FetusHeavenStem($sixtyCycle->getHeavenStem()->getIndex() % 5); + $this->fetusEarthBranch = new FetusEarthBranch($sixtyCycle->getEarthBranch()->getIndex() % 6); + $index = [3, 3, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, -9, -9, -9, -9, -9, -5, -5, -1, -1, -1, -3, -7, -7, -7, -7, -5, 7, 7, 7, 7, 7, 7, 2, 2, 2, 2, 2, 3, 3, 3, 3][$sixtyCycle->getIndex()]; + $this->side = Side::fromCode($index < 0 ? 0 : 1); + $this->direction = Direction::fromIndex($index); + } + + static function fromLunarDay(LunarDay $lunarDay): static + { + return new static($lunarDay); + } + + function getName(): string + { + $s = $this->fetusHeavenStem->getName() . $this->fetusEarthBranch->getName(); + if ('门门' == $s) { + $s = '占大门'; + } else if ('碓磨碓' == $s) { + $s = '占碓磨'; + } else if ('房床床' == $s) { + $s = '占房床'; + } else if (str_starts_with($s, '门')) { + $s = '占' . $s; + } + + $s .= ' '; + + $directionName = $this->direction->getName(); + if (Side::IN == $this->side) { + $s .= '房'; + } + $s .= $this->side->getName(); + + if (Side::OUT == $this->side && str_contains('北南西东', $directionName)) { + $s .= '正'; + } + $s .= $directionName; + return $s; + } + + /** + * 内外 + * + * @return Side 侧 + */ + function getSide(): Side + { + return $this->side; + } + + /** + * 方位 + * + * @return Direction 方位 + */ + function getDirection(): Direction + { + return $this->direction; + } + + /** + * 天干六甲胎神 + * + * @return FetusHeavenStem 天干六甲胎神 + */ + function getFetusHeavenStem(): FetusHeavenStem + { + return $this->fetusHeavenStem; + } + + /** + * 地支六甲胎神 + * + * @return FetusEarthBranch 地支六甲胎神 + */ + function getFetusEarthBranch(): FetusEarthBranch + { + return $this->fetusEarthBranch; + } +} diff --git a/src/culture/fetus/FetusEarthBranch.php b/src/culture/fetus/FetusEarthBranch.php new file mode 100644 index 0000000..4a264ca --- /dev/null +++ b/src/culture/fetus/FetusEarthBranch.php @@ -0,0 +1,26 @@ +nextIndex($n)); + } +} diff --git a/src/culture/fetus/FetusHeavenStem.php b/src/culture/fetus/FetusHeavenStem.php new file mode 100644 index 0000000..de828a1 --- /dev/null +++ b/src/culture/fetus/FetusHeavenStem.php @@ -0,0 +1,26 @@ +nextIndex($n)); + } +} diff --git a/src/culture/fetus/FetusMonth.php b/src/culture/fetus/FetusMonth.php new file mode 100644 index 0000000..17cd0a0 --- /dev/null +++ b/src/culture/fetus/FetusMonth.php @@ -0,0 +1,43 @@ +isLeap() ? null : new static($lunarMonth->getMonth() - 1); + } + + function next(int $n): static + { + return self::fromIndex($this->nextIndex($n)); + } +} diff --git a/src/culture/nine/Nine.php b/src/culture/nine/Nine.php new file mode 100644 index 0000000..c1cc286 --- /dev/null +++ b/src/culture/nine/Nine.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/nine/NineDay.php b/src/culture/nine/NineDay.php new file mode 100644 index 0000000..a7387b8 --- /dev/null +++ b/src/culture/nine/NineDay.php @@ -0,0 +1,29 @@ +culture; + } +} diff --git a/src/culture/pengzu/PengZu.php b/src/culture/pengzu/PengZu.php new file mode 100644 index 0000000..5913b55 --- /dev/null +++ b/src/culture/pengzu/PengZu.php @@ -0,0 +1,67 @@ +pengZuHeavenStem = PengZuHeavenStem::fromIndex($sixtyCycle->getHeavenStem()->getIndex()); + $this->pengZuEarthBranch = PengZuEarthBranch::fromIndex($sixtyCycle->getEarthBranch()->getIndex()); + } + + /** + * 从干支初始化 + * + * @param SixtyCycle $sixtyCycle 干支 + * @return PengZu 彭祖百忌 + */ + static function fromSixtyCycle(SixtyCycle $sixtyCycle): static + { + return new static($sixtyCycle); + } + + function getName(): string + { + return sprintf('%s %s', $this->pengZuHeavenStem, $this->pengZuEarthBranch); + } + + /** + * 天干彭祖百忌 + * + * @return PengZuHeavenStem 天干彭祖百忌 + */ + function getPengZuHeavenStem(): PengZuHeavenStem + { + return $this->pengZuHeavenStem; + } + + /** + * 地支彭祖百忌 + * + * @return PengZuEarthBranch 地支彭祖百忌 + */ + function getPengZuEarthBranch(): PengZuEarthBranch + { + return $this->pengZuEarthBranch; + } +} diff --git a/src/culture/pengzu/PengZuEarthBranch.php b/src/culture/pengzu/PengZuEarthBranch.php new file mode 100644 index 0000000..4b201e2 --- /dev/null +++ b/src/culture/pengzu/PengZuEarthBranch.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/pengzu/PengZuHeavenStem.php b/src/culture/pengzu/PengZuHeavenStem.php new file mode 100644 index 0000000..8e1ce4e --- /dev/null +++ b/src/culture/pengzu/PengZuHeavenStem.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/phenology/Phenology.php b/src/culture/phenology/Phenology.php new file mode 100644 index 0000000..b89866a --- /dev/null +++ b/src/culture/phenology/Phenology.php @@ -0,0 +1,51 @@ +nextIndex($n)); + } + + /** + * 三候 + * + * @return ThreePhenology 三候 + */ + function getThreePhenology(): ThreePhenology + { + return ThreePhenology::fromIndex($this->index % 3); + } + +} diff --git a/src/culture/phenology/PhenologyDay.php b/src/culture/phenology/PhenologyDay.php new file mode 100644 index 0000000..8cf879f --- /dev/null +++ b/src/culture/phenology/PhenologyDay.php @@ -0,0 +1,29 @@ +culture; + } +} diff --git a/src/culture/phenology/ThreePhenology.php b/src/culture/phenology/ThreePhenology.php new file mode 100644 index 0000000..af4c1db --- /dev/null +++ b/src/culture/phenology/ThreePhenology.php @@ -0,0 +1,41 @@ +nextIndex($n)); + } + +} diff --git a/src/culture/star/nine/Dipper.php b/src/culture/star/nine/Dipper.php new file mode 100644 index 0000000..85e32d4 --- /dev/null +++ b/src/culture/star/nine/Dipper.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/star/nine/NineStar.php b/src/culture/star/nine/NineStar.php new file mode 100644 index 0000000..bd75632 --- /dev/null +++ b/src/culture/star/nine/NineStar.php @@ -0,0 +1,87 @@ +nextIndex($n)); + } + + /** + * 颜色 + * + * @return string 颜色 + */ + function getColor(): string + { + return ['白', '黒', '碧', '绿', '黄', '白', '赤', '白', '紫'][$this->index]; + } + + /** + * 五行 + * + * @return Element 五行 + */ + function getElement(): Element + { + return Element::fromIndex([4, 2, 0, 0, 2, 3, 3, 2, 1][$this->index]); + } + + /** + * 北斗九星 + * + * @return Dipper 北斗九星 + */ + function getDipper(): Dipper + { + return Dipper::fromIndex($this->index); + } + + /** + * 方位 + * + * @return Direction 方位 + */ + function getDirection(): Direction + { + return Direction::fromIndex($this->index); + } + + function __toString(): string + { + return sprintf('%s%s%s', $this->getName(), $this->getColor(), $this->getElement()); + } +} diff --git a/src/culture/star/seven/SevenStar.php b/src/culture/star/seven/SevenStar.php new file mode 100644 index 0000000..b0714ef --- /dev/null +++ b/src/culture/star/seven/SevenStar.php @@ -0,0 +1,51 @@ +nextIndex($n)); + } + + /** + * 星期 + * + * @return Week 星期 + */ + function getWeek(): Week + { + return Week::fromIndex($this->index); + } +} diff --git a/src/culture/star/ten/TenStar.php b/src/culture/star/ten/TenStar.php new file mode 100644 index 0000000..3ee5d3d --- /dev/null +++ b/src/culture/star/ten/TenStar.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/culture/star/twelve/Ecliptic.php b/src/culture/star/twelve/Ecliptic.php new file mode 100644 index 0000000..a028ed4 --- /dev/null +++ b/src/culture/star/twelve/Ecliptic.php @@ -0,0 +1,51 @@ +nextIndex($n)); + } + + /** + * 吉凶 + * + * @return Luck 吉凶 + */ + function getLuck(): Luck + { + return Luck::fromIndex($this->index); + } +} diff --git a/src/culture/star/twelve/TwelveStar.php b/src/culture/star/twelve/TwelveStar.php new file mode 100644 index 0000000..cb64b51 --- /dev/null +++ b/src/culture/star/twelve/TwelveStar.php @@ -0,0 +1,51 @@ +nextIndex($n)); + } + + /** + * 黄道黑道 + * + * @return Ecliptic 黄道黑道 + */ + function getEcliptic(): Ecliptic + { + return Ecliptic::fromIndex([0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1][$this->index]); + } + +} diff --git a/src/culture/star/twentyeight/TwentyEightStar.php b/src/culture/star/twentyeight/TwentyEightStar.php new file mode 100644 index 0000000..631099d --- /dev/null +++ b/src/culture/star/twentyeight/TwentyEightStar.php @@ -0,0 +1,96 @@ +nextIndex($n)); + } + + /** + * 七曜 + * + * @return SevenStar 七曜 + */ + function getSevenStar(): SevenStar + { + return SevenStar::fromIndex($this->index % 7 + 4); + } + + /** + * 九野 + * + * @return Land 九野 + */ + function getLand(): Land + { + return Land::fromIndex([4, 4, 4, 2, 2, 2, 7, 7, 7, 0, 0, 0, 0, 5, 5, 5, 6, 6, 6, 1, 1, 1, 8, 8, 8, 3, 3, 3][$this->index]); + } + + /** + * 宫 + * + * @return Zone 宫 + */ + function getZone(): Zone + { + return Zone::fromIndex(intdiv($this->index, 7)); + } + + /** + * 动物 + * + * @return Animal 动物 + */ + function getAnimal(): Animal + { + return Animal::fromIndex($this->index); + } + + /** + * 吉凶 + * + * @return Luck 吉凶 + */ + function getLuck(): Luck + { + return Luck::fromIndex([0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0][$this->index]); + } + +} diff --git a/src/eightchar/ChildLimit.php b/src/eightchar/ChildLimit.php new file mode 100644 index 0000000..e554b73 --- /dev/null +++ b/src/eightchar/ChildLimit.php @@ -0,0 +1,260 @@ +startTime = $birthTime; + $this->gender = $gender; + $this->eightChar = $birthTime->getLunarHour()->getEightChar(); + // 阳男阴女顺推,阴男阳女逆推 + $yang = YinYang::YANG == $this->eightChar->getYear()->getHeavenStem()->getYinYang(); + $man = Gender::MAN == $gender; + $forward = ($yang && $man) || (!$yang && !$man); + $term = $birthTime->getTerm(); + if (!$term->isJie()) { + $term = $term->next(-1); + } + $start = $forward ? $birthTime : $term->getJulianDay()->getSolarTime(); + $end = $forward ? $term->next(2)->getJulianDay()->getSolarTime() : $birthTime; + + $seconds = $end->subtract($start); + // 3天 = 1年,3天=60*60*24*3秒=259200秒 = 1年 + $year = intdiv($seconds, 259200); + $seconds %= 259200; + // 1天 = 4月,1天=60*60*24秒=86400秒 = 4月,85400秒/4=21600秒 = 1月 + $month = intdiv($seconds, 21600); + $seconds %= 21600; + // 1时 = 5天,1时=60*60秒=3600秒 = 5天,3600秒/5=720秒 = 1天 + $day = intdiv($seconds, 720); + $seconds %= 720; + // 1分 = 2时,60秒 = 2时,60秒/2=30秒 = 1时 + $hour = intdiv($seconds, 30); + $seconds %= 30; + // 1秒 = 2分,1秒/2=0.5秒 = 1分 + $minute = $seconds * 2; + + $this->forward = $forward; + $this->yearCount = $year; + $this->monthCount = $month; + $this->dayCount = $day; + $this->hourCount = $hour; + $this->minuteCount = $minute; + + $birthday = $birthTime->getDay(); + $birthMonth = $birthday->getMonth(); + + $d = $birthday->getDay() + $day; + $h = $birthTime->getHour() + $hour; + $mi = $birthTime->getMinute() + $minute; + $h += intdiv($mi, 60); + $mi %= 60; + $d += intdiv($h, 24); + $h %= 24; + + $sm = SolarMonth::fromYm($birthMonth->getYear()->getYear() + $year, $birthMonth->getMonth())->next($month); + + $dc = $sm->getDayCount(); + if ($d > $dc) { + $d -= $dc; + $sm = $sm->next(1); + } + $this->endTime = SolarTime::fromYmdHms($sm->getYear()->getYear(), $sm->getMonth(), $d, $h, $mi, $birthTime->getSecond()); + } + + /** + * 通过出生公历时刻初始化 + * + * @param SolarTime $birthTime 出生公历时刻 + * @param Gender $gender 性别 + * @return static 童限 + */ + static function fromSolarTime(SolarTime $birthTime, Gender $gender): static + { + return new static($birthTime, $gender); + } + + /** + * 八字 + * + * @return EightChar 八字 + */ + function getEightChar(): EightChar + { + return $this->eightChar; + } + + /** + * 性别 + * + * @return Gender 性别 + */ + function getGender(): Gender + { + return $this->gender; + } + + /** + * 是否顺推 + * + * @return bool true/false + */ + function isForward(): bool + { + return $this->forward; + } + + /** + * 年数 + * + * @return int 年数 + */ + function getYearCount(): int + { + return $this->yearCount; + } + + /** + * 月数 + * + * @return int 月数 + */ + function getMonthCount(): int + { + return $this->monthCount; + } + + /** + * 日数 + * + * @return int 日数 + */ + function getDayCount(): int + { + return $this->dayCount; + } + + /** + * 小时数 + * + * @return int 小时数 + */ + function getHourCount(): int + { + return $this->hourCount; + } + + /** + * 分钟数 + * + * @return int 分钟数 + */ + function getMinuteCount(): int + { + return $this->minuteCount; + } + + /** + * 开始(即出生)的公历时刻 + * + * @return SolarTime 公历时刻 + */ + function getStartTime(): SolarTime + { + return $this->startTime; + } + + /** + * 结束(即开始起运)的公历时刻 + * + * @return SolarTime 公历时刻 + */ + function getEndTime(): SolarTime + { + return $this->endTime; + } + + /** + * 大运 + * + * @return DecadeFortune 大运 + */ + function getStartDecadeFortune(): DecadeFortune + { + return DecadeFortune::fromChildLimit($this, 0); + } + + /** + * 小运 + * + * @return Fortune 小运 + */ + function getStartFortune(): Fortune + { + return Fortune::fromChildLimit($this, 0); + } + +} diff --git a/src/eightchar/DecadeFortune.php b/src/eightchar/DecadeFortune.php new file mode 100644 index 0000000..d47837c --- /dev/null +++ b/src/eightchar/DecadeFortune.php @@ -0,0 +1,109 @@ +childLimit = $childLimit; + $this->index = $index; + } + + static function fromChildLimit(ChildLimit $childLimit, int $index): static + { + return new static($childLimit, $index); + } + + /** + * 开始年龄 + * + * @return int 开始年龄 + */ + function getStartAge(): int + { + return $this->childLimit->getYearCount() + 1 + $this->index * 10; + } + + /** + * 结束年龄 + * + * @return int 结束年龄 + */ + function getEndAge(): int + { + return $this->getStartAge() + 9; + } + + /** + * 开始农历年 + * + * @return LunarYear 农历年 + */ + function getStartLunarYear(): LunarYear + { + return $this->childLimit->getEndTime()->getLunarHour()->getDay()->getMonth()->getYear()->next($this->index * 10); + } + + /** + * 结束农历年 + * + * @return LunarYear 农历年 + */ + function getEndLunarYear(): LunarYear + { + return $this->getStartLunarYear()->next(9); + } + + /** + * 干支 + * + * @return SixtyCycle 干支 + */ + function getSixtyCycle(): SixtyCycle + { + $n = $this->index + 1; + return $this->childLimit->getEightChar()->getMonth()->next($this->childLimit->isForward() ? $n : -$n); + } + + function getName(): string + { + return $this->getSixtyCycle()->getName(); + } + + function next(int $n): static + { + return self::fromChildLimit($this->childLimit, $this->index + $n); + } + + /** + * 开始小运 + * + * @return Fortune 小运 + */ + function getStartFortune(): Fortune + { + return Fortune::fromChildLimit($this->childLimit, $this->index * 10); + } + +} diff --git a/src/eightchar/EightChar.php b/src/eightchar/EightChar.php new file mode 100644 index 0000000..212d58a --- /dev/null +++ b/src/eightchar/EightChar.php @@ -0,0 +1,148 @@ +year = $year; + $this->month = $month; + $this->day = $day; + $this->hour = $hour; + } + + /** + * 年柱 + * + * @return SixtyCycle 年柱 + */ + function getYear(): SixtyCycle + { + return $this->year; + } + + /** + * 月柱 + * + * @return SixtyCycle 月柱 + */ + function getMonth(): SixtyCycle + { + return $this->month; + } + + /** + * 日柱 + * + * @return SixtyCycle 日柱 + */ + function getDay(): SixtyCycle + { + return $this->day; + } + + /** + * 时柱 + * + * @return SixtyCycle 时柱 + */ + function getHour(): SixtyCycle + { + return $this->hour; + } + + /** + * 胎元 + * + * @return SixtyCycle 胎元 + */ + function getFetalOrigin(): SixtyCycle + { + return SixtyCycle::fromName(sprintf('%s%s', $this->month->getHeavenStem()->next(1)->getName(), $this->month->getEarthBranch()->next(3)->getName())); + } + + /** + * 胎息 + * + * @return SixtyCycle 胎息 + */ + function getFetalBreath(): SixtyCycle + { + return SixtyCycle::fromName(sprintf('%s%s', $this->day->getHeavenStem()->next(5)->getName(), EarthBranch::fromIndex(13 - $this->day->getEarthBranch()->getIndex())->getName())); + } + + /** + * 命宫 + * + * @return SixtyCycle 命宫 + */ + function getOwnSign(): SixtyCycle + { + $offset = $this->month->getEarthBranch()->next(-1)->getIndex() + $this->hour->getEarthBranch()->next(-1)->getIndex(); + $offset = ($offset >= 14 ? 26 : 14) - $offset; + $offset -= 1; + return SixtyCycle::fromName(sprintf('%s%s', HeavenStem::fromIndex(($this->year->getHeavenStem()->getIndex() + 1) * 2 + $offset)->getName(), EarthBranch::fromIndex(2 + $offset)->getName())); + } + + /** + * 身宫 + * + * @return SixtyCycle 身宫 + */ + function getBodySign(): SixtyCycle + { + $offset = $this->month->getEarthBranch()->getIndex() + $this->hour->getEarthBranch()->getIndex(); + $offset %= 12; + $offset -= 1; + return SixtyCycle::fromName(sprintf('%s%s', HeavenStem::fromIndex(($this->year->getHeavenStem()->getIndex() + 1) * 2 + $offset)->getName(), EarthBranch::fromIndex(2 + $offset)->getName())); + } + + /** + * 建除十二值神 + * + * @return Duty 建除十二值神 + */ + function getDuty(): Duty + { + return Duty::fromIndex($this->day->getEarthBranch()->getIndex() - $this->month->getEarthBranch()->getIndex()); + } + + function getName(): string + { + return sprintf('%s %s %s %s', $this->year, $this->month, $this->day, $this->hour); + } + +} diff --git a/src/eightchar/Fortune.php b/src/eightchar/Fortune.php new file mode 100644 index 0000000..a72ed26 --- /dev/null +++ b/src/eightchar/Fortune.php @@ -0,0 +1,79 @@ +childLimit = $childLimit; + $this->index = $index; + } + + static function fromChildLimit(ChildLimit $childLimit, int $index): static + { + return new static($childLimit, $index); + } + + /** + * 年龄 + * + * @return int 年龄 + */ + function getAge(): int + { + return $this->childLimit->getYearCount() + 1 + $this->index; + } + + /** + * 农历年 + * + * @return LunarYear 农历年 + */ + function getLunarYear(): LunarYear + { + return $this->childLimit->getEndTime()->getLunarHour()->getDay()->getMonth()->getYear()->next($this->index); + } + + /** + * 干支 + * + * @return SixtyCycle 干支 + */ + function getSixtyCycle(): SixtyCycle + { + $n = $this->getAge(); + return $this->childLimit->getEightChar()->getHour()->next($this->childLimit->isForward() ? $n : -$n); + } + + function getName(): string + { + return $this->getSixtyCycle()->getName(); + } + + function next(int $n): static + { + return self::fromChildLimit($this->childLimit, $this->index + $n); + } + +} diff --git a/src/enums/FestivalType.php b/src/enums/FestivalType.php new file mode 100644 index 0000000..414e984 --- /dev/null +++ b/src/enums/FestivalType.php @@ -0,0 +1,55 @@ +value; + } + + function getName(): string + { + return match ($this) { + self::DAY => '日期', + self::TERM => '节气', + self::EVE => '除夕' + }; + } + + static function fromCode(int $code): FestivalType + { + return match (true) { + $code == 0 => self::DAY, + $code == 1 => self::TERM, + $code == 2 => self::EVE, + default => null + }; + } + + static function fromName(string $name): FestivalType + { + return match (true) { + $name == '日期' => self::DAY, + $name == '节气' => self::TERM, + $name == '除夕' => self::EVE, + default => null + }; + } + + function equals(Side $o): bool + { + return $this->value == $o->value; + } + +} diff --git a/src/enums/Gender.php b/src/enums/Gender.php new file mode 100644 index 0000000..ca3606f --- /dev/null +++ b/src/enums/Gender.php @@ -0,0 +1,51 @@ +value; + } + + function getName(): string + { + return match ($this) { + self::WOMAN => '女', + self::MAN => '男' + }; + } + + static function fromCode(int $code): Gender + { + return match (true) { + $code == 0 => self::WOMAN, + $code == 1 => self::MAN, + default => null + }; + } + + static function fromName(string $name): Gender + { + return match (true) { + $name == '女' => self::WOMAN, + $name == '男' => self::MAN, + default => null + }; + } + + function equals(Side $o): bool + { + return $this->value == $o->value; + } + +} diff --git a/src/enums/Side.php b/src/enums/Side.php new file mode 100644 index 0000000..5494902 --- /dev/null +++ b/src/enums/Side.php @@ -0,0 +1,51 @@ +value; + } + + function getName(): string + { + return match ($this) { + self::IN => '内', + self::OUT => '外' + }; + } + + static function fromCode(int $code): Side + { + return match (true) { + $code == 0 => self::IN, + $code == 1 => self::OUT, + default => null + }; + } + + static function fromName(string $name): Side + { + return match (true) { + $name == '内' => self::IN, + $name == '外' => self::OUT, + default => null + }; + } + + function equals(Side $o): bool + { + return $this->value == $o->value; + } + +} diff --git a/src/enums/YinYang.php b/src/enums/YinYang.php new file mode 100644 index 0000000..ce36634 --- /dev/null +++ b/src/enums/YinYang.php @@ -0,0 +1,51 @@ +value; + } + + function getName(): string + { + return match ($this) { + self::YIN => '阴', + self::YANG => '阳' + }; + } + + static function fromCode(int $code): YinYang + { + return match (true) { + $code == 1 => self::YANG, + $code == 0 => self::YIN, + default => null + }; + } + + static function fromName(string $name): YinYang + { + return match (true) { + $name == '阳' => self::YANG, + $name == '阴' => self::YIN, + default => null + }; + } + + function equals(YinYang $o): bool + { + return $this->value == $o->value; + } + +} diff --git a/src/festival/LunarFestival.php b/src/festival/LunarFestival.php new file mode 100644 index 0000000..13f5a1f --- /dev/null +++ b/src/festival/LunarFestival.php @@ -0,0 +1,164 @@ +type = $type; + $this->day = $day; + $this->solarTerm = $solarTerm; + $this->index = intval(substr($data, 1, 2)); + $this->name = static::$NAMES[$this->index]; + } + + static function fromIndex(int $year, int $index): ?static + { + if ($index < 0 || $index >= count(static::$NAMES)) { + throw new InvalidArgumentException(sprintf('illegal index: %d', $index)); + } + if (preg_match_all(sprintf('/@%02d\\d+/', $index), static::$DATA, $matches)) { + $data = $matches[0][0]; + $type = FestivalType::fromCode(ord(substr($data, 3, 1)) - 48); + switch ($type) { + case FestivalType::DAY: + return new static($type, LunarDay::fromYmd($year, intval(substr($data, 4, 2)), intval(substr($data, 6, 2))), null, $data); + case FestivalType::TERM: + $solarTerm = SolarTerm::fromIndex($year, intval(substr($data, 4, 2))); + return new static($type, $solarTerm->getJulianDay()->getSolarDay()->getLunarDay(), $solarTerm, $data); + case FestivalType::EVE: + return new static($type, LunarDay::fromYmd($year + 1, 1, 1)->next(-1), null, $data); + } + } + return null; + } + + static function fromYmd(int $year, int $month, int $day): ?static + { + if (preg_match_all(sprintf('/@\d{2}0%02d%02d/', $month, $day), static::$DATA, $matches)) { + return new static(FestivalType::DAY, LunarDay::fromYmd($year, $month, $day), null, $matches[0][0]); + } + if (preg_match_all('/@\\d{2}1\\d{2}/', static::$DATA, $matches)) { + $data = $matches[0][0]; + $solarTerm = SolarTerm::fromIndex($year, intval(substr($data, 4, 2))); + $lunarDay = $solarTerm->getJulianDay()->getSolarDay()->getLunarDay(); + $lunarMonth = $lunarDay->getMonth(); + if ($lunarMonth->getYear()->getYear() == $year && $lunarMonth->getMonth() == $month && $lunarDay->getDay() == $day) { + return new static(FestivalType::TERM, $lunarDay, $solarTerm, $data); + } + } + if (preg_match_all('/@\\d{2}2/', static::$DATA, $matches)) { + $lunarDay = LunarDay::fromYmd($year, $month, $day); + $nextDay = $lunarDay->next(1); + if ($nextDay->getMonth()->getMonth() == 1 && $nextDay->getDay() == 1) { + return new static(FestivalType::EVE, $lunarDay, null, $matches[0][0]); + } + } + return null; + } + + function next(int $n): static + { + $m = $this->day->getMonth(); + $year = $m->getYear()->getYear(); + if ($n == 0) { + return static::fromYmd($year, $m->getMonthWithLeap(), $this->day->getDay()); + } + $size = count(self::$NAMES); + $t = $this->index + $n; + $offset = $this->indexOf($t, null, $size); + if ($t < 0) { + $t -= $size; + } + return static::fromIndex($year + intdiv($t, $size), $offset); + } + + function __toString(): string + { + return sprintf('%s %s', $this->day, $this->name); + } + + /** + * 类型 + * @return FestivalType 节日类型 + */ + function getType(): FestivalType + { + return $this->type; + } + + /** + * @return LunarDay 农历日 + */ + function getDay(): LunarDay + { + return $this->day; + } + + /** + * 索引 + * + * @return int 索引 + */ + function getIndex(): int + { + return $this->index; + } + + function getName(): string + { + return $this->name; + } + + /** + * 节气,非节气返回null + * + * @return SolarTerm 节气 + */ + function getSolarTerm(): SolarTerm + { + return $this->solarTerm; + } +} diff --git a/src/festival/SolarFestival.php b/src/festival/SolarFestival.php new file mode 100644 index 0000000..518ca6a --- /dev/null +++ b/src/festival/SolarFestival.php @@ -0,0 +1,149 @@ +type = $type; + $this->day = $day; + $this->startYear = $startYear; + $this->index = intval(substr($data, 1, 2)); + $this->name = static::$NAMES[$this->index]; + } + + static function fromIndex(int $year, int $index): ?static + { + if ($index < 0 || $index >= count(static::$NAMES)) { + throw new InvalidArgumentException(sprintf('illegal index: %d', $index)); + } + if(preg_match_all(sprintf('/@%02d\\d+/', $index), static::$DATA, $matches)) { + $data = $matches[0][0]; + $type = FestivalType::fromCode(ord(substr($data, 3, 1)) - 48); + if ($type == FestivalType::DAY) { + $startYear = intval(substr($data, 8, 4)); + if ($year >= $startYear) { + return new static($type, SolarDay::fromYmd($year, intval(substr($data, 4, 2)), intval(substr($data, 6, 2))), $startYear, $data); + } + } + } + return null; + } + + static function fromYmd(int $year, int $month, int $day): ?static + { + if (preg_match_all(sprintf('/@\\d{2}0%02d%02d\\d+/', $month, $day), static::$DATA, $matches)) { + $data = $matches[0][0]; + $startYear = intval(substr($data, 8, 4)); + if ($year >= $startYear) { + return new static(FestivalType::DAY, SolarDay::fromYmd($year, $month, $day), $startYear, $data); + } + } + return null; + } + + function next(int $n): static + { + $m = $this->day->getMonth(); + $year = $m->getYear()->getYear(); + if ($n == 0) { + return static::fromYmd($year, $m->getMonth(), $this->day->getDay()); + } + $size = count(static::$NAMES); + $t = $this->index + $n; + $offset = $this->indexOf($t, null, $size); + if ($t < 0) { + $t -= $size; + } + return static::fromIndex($year + intdiv($t, $size), $offset); + } + + function __toString(): string + { + return sprintf('%s %s', $this->day, $this->name); + } + + /** + * 类型 + * @return FestivalType 节日类型 + */ + function getType(): FestivalType + { + return $this->type; + } + + /** + * 公历日 + * @return SolarDay 公历日 + */ + function getDay(): SolarDay + { + return $this->day; + } + + /** + * 索引 + * + * @return int 索引 + */ + function getIndex(): int + { + return $this->index; + } + + function getName(): string + { + return $this->name; + } + + /** + * 起始年 + * + * @return int 年 + */ + function getStartYear(): int + { + return $this->startYear; + } +} diff --git a/src/holiday/LegalHoliday.php b/src/holiday/LegalHoliday.php new file mode 100644 index 0000000..bd38f93 --- /dev/null +++ b/src/holiday/LegalHoliday.php @@ -0,0 +1,131 @@ +day = SolarDay::fromYmd($year, $month, $day); + $this->work = '0' == substr($data, 8, 1); + $this->name = static::$NAMES[ord(substr($data, 9, 1)) - 48]; + } + + static function fromYmd(int $year, int $month, int $day): ?static + { + if(preg_match_all(sprintf('/%04d%02d%02d[0-1][0-8][\\+|-]\\d{2}/', $year, $month, $day), static::$DATA, $matches)) { + return new static($year, $month, $day, $matches[0][0]); + } + return null; + } + + function next(int $n): ?static + { + $m = $this->day->getMonth(); + $year = $m->getYear()->getYear(); + $month = $m->getMonth(); + if ($n == 0) { + return static::fromYmd($year, $month, $this->day->getDay()); + } + $reg = '/%04d\\d{4}[0-1][0-8][\\+|-]\\d{2}/'; + $today = sprintf('%04d%02d%02d', $year, $month, $this->day->getDay()); + $index = -1; + $size = 0; + if (preg_match_all(sprintf($reg, $year), static::$DATA, $matches)) { + $size = count($matches[0]); + for ($i = 0; $i < $size; $i++) { + if (str_starts_with($matches[0][$i], $today)) { + $index = $i; + break; + } + } + } + if ($index == -1) { + return null; + } + $index += $n; + $y = $year; + $forward = $n > 0; + $add = $forward ? 1 : -1; + while ($forward ? ($index >= $size) : ($index < 0)) { + if ($forward) { + $index -= $size; + } + $y += $add; + $size = 0; + if(preg_match_all(sprintf($reg, $y), static::$DATA, $matches)) { + $size = count($matches[0]); + } + if ($size < 1) { + return null; + } + if (!$forward) { + $index += $size; + } + } + $d = $matches[0][$index]; + return new static(intval(substr($d, 0, 4)), intval(substr($d, 4, 2)), intval(substr($d, 6, 2)), $d); + } + + function __toString(): string + { + return sprintf('%s %s(%s)', $this->day, $this->name, $this->work ? '班' : '休'); + } + + function getDay(): SolarDay + { + return $this->day; + } + + function getName(): string + { + return $this->name; + } + + /** + * 是否上班 + * + * @return bool true/false + */ + function isWork(): bool + { + return $this->work; + } + + /** + * @param mixed $o 对象 + * @return bool true/false + */ + function equals(mixed $o): bool + { + return $o instanceof LegalHoliday && $this->__toString() == $o->__toString(); + } +} diff --git a/src/jd/JulianDay.php b/src/jd/JulianDay.php new file mode 100644 index 0000000..0c5f44d --- /dev/null +++ b/src/jd/JulianDay.php @@ -0,0 +1,186 @@ +day = $day; + } + + static function fromJulianDay($day): static + { + return new static($day); + } + + static function fromYmdHms(int $year, int $month, int $day, int $hour, int $minute, int $second): static + { + $d = $day + (($second / 60 + $minute) / 60 + $hour) / 24; + $n = 0; + $g = $year * 372 + $month * 31 + (int)$d >= 588829; + if ($month <= 2) { + $month += 12; + $year--; + } + if ($g) { + $n = intdiv($year, 100); + $n = 2 - $n + intdiv($n, 4); + } + return static::fromJulianDay((int)(365.25 * ($year + 4716)) + (int)(30.6001 * ($month + 1)) + $d + $n - 1524.5); + } + + /** + * 儒略日 + * + * @return float 儒略日 + */ + function getDay(): float + { + return $this->day; + } + + function getName(): string + { + return $this->day . ''; + } + + function next(int $n): static + { + return static::fromJulianDay($this->day + $n); + } + + /** + * 公历日 + * + * @return SolarDay 公历日 + */ + function getSolarDay(): SolarDay + { + $d = (int)($this->day + 0.5); + $f = $this->day + 0.5 - $d; + + if ($d >= 2299161) { + $c = (int)(($d - 1867216.25) / 36524.25); + $d += 1 + $c - intdiv($c, 4); + } + $d += 1524; + $year = (int)(($d - 122.1) / 365.25); + $d -= (int)(365.25 * $year); + $month = (int)($d / 30.601); + $d -= (int)(30.601 * $month); + $day = $d; + if ($month > 13) { + $month -= 13; + $year -= 4715; + } else { + $month -= 1; + $year -= 4716; + } + $f *= 24; + $hour = (int)$f; + + $f -= $hour; + $f *= 60; + $minute = (int)$f; + + $f -= $minute; + $f *= 60; + $second = (int)(round($f)); + if ($second > 59) { + $minute++; + } + if ($minute > 59) { + $hour++; + } + if ($hour > 23) { + $day += 1; + } + return SolarDay::fromYmd($year, $month, $day); + } + + /** + * 公历时刻 + * + * @return SolarTime 公历时刻 + */ + function getSolarTime(): SolarTime + { + $d = (int)($this->day + 0.5); + $f = $this->day + 0.5 - $d; + + if ($d >= 2299161) { + $c = (int)(($d - 1867216.25) / 36524.25); + $d += 1 + $c - intdiv($c, 4); + } + $d += 1524; + $year = (int)(($d - 122.1) / 365.25); + $d -= (int)(365.25 * $year); + $month = (int)($d / 30.601); + $d -= (int)(30.601 * $month); + $day = $d; + if ($month > 13) { + $month -= 13; + $year -= 4715; + } else { + $month -= 1; + $year -= 4716; + } + $f *= 24; + $hour = (int)$f; + + $f -= $hour; + $f *= 60; + $minute = (int)$f; + + $f -= $minute; + $f *= 60; + $second = (int)round($f); + if ($second > 59) { + $second -= 60; + $minute++; + } + if ($minute > 59) { + $minute -= 60; + $hour++; + } + if ($hour > 23) { + $hour -= 24; + $day += 1; + } + return SolarTime::fromYmdHms($year, $month, $day, $hour, $minute, $second); + } + + /** + * 星期 + * + * @return Week 星期 + */ + function getWeek(): Week + { + return Week::fromIndex((int)($this->day + 0.5) + 7000001); + } + + +} diff --git a/src/lunar/LunarDay.php b/src/lunar/LunarDay.php new file mode 100644 index 0000000..8543bf2 --- /dev/null +++ b/src/lunar/LunarDay.php @@ -0,0 +1,349 @@ + $m->getDayCount()) { + throw new InvalidArgumentException(sprintf('illegal day %d in %s', $day, $m)); + } + $this->month = $m; + $this->day = $day; + } + + static function fromYmd(int $year, int $month, int $day): static + { + return new static($year, $month, $day); + } + + /** + * 月 + * + * @return LunarMonth 月 + */ + function getMonth(): LunarMonth + { + return $this->month; + } + + /** + * 日 + * + * @return int 日 + */ + function getDay(): int + { + return $this->day; + } + + function getName(): string + { + return self::$NAMES[$this->day - 1]; + } + + function __toString(): string + { + return sprintf('%s%s', $this->month, $this->getName()); + } + + function next(int $n): LunarDay + { + if ($n == 0) { + return self::fromYmd($this->month->getYear()->getYear(), $this->month->getMonthWithLeap(), $this->day); + } + $d = $this->day + $n; + $lm = $this->month; + $daysInMonth = $lm->getDayCount(); + $forward = $n > 0; + $add = $forward ? 1 : -1; + while ($forward ? ($d > $daysInMonth) : ($d <= 0)) { + if ($forward) { + $d -= $daysInMonth; + } + $lm = $lm->next($add); + $daysInMonth = $lm->getDayCount(); + if (!$forward) { + $d += $daysInMonth; + } + } + return self::fromYmd($lm->getYear()->getYear(), $lm->getMonthWithLeap(), $d); + } + + /** + * 是否在指定农历日之前 + * + * @param LunarDay target 农历日 + * @return bool true/false + */ + function isBefore(LunarDay $target): bool + { + $aYear = $this->month->getYear()->getYear(); + $targetMonth = $target->getMonth(); + $bYear = $targetMonth->getYear()->getYear(); + if ($aYear == $bYear) { + $aMonth = $this->month->getMonth(); + $bMonth = $targetMonth->getMonth(); + if ($aMonth == $bMonth) { + if ($this->month->isLeap() && !$targetMonth->isLeap()) { + return false; + } + return $this->day < $target->getDay(); + } + return $aMonth < $bMonth; + } + return $aYear < $bYear; + } + + /** + * 是否在指定农历日之后 + * + * @param LunarDay target 农历日 + * @return bool true/false + */ + function isAfter(LunarDay $target): bool + { + $aYear = $this->month->getYear()->getYear(); + $targetMonth = $target->getMonth(); + $bYear = $targetMonth->getYear()->getYear(); + if ($aYear == $bYear) { + $aMonth = $this->month->getMonth(); + $bMonth = $targetMonth->getMonth(); + if ($aMonth == $bMonth) { + if ($this->month->isLeap() && !$targetMonth->isLeap()) { + return true; + } + return $this->day > $target->getDay(); + } + return $aMonth > $bMonth; + } + return $aYear > $bYear; + } + + /** + * 星期 + * + * @return Week 星期 + */ + function getWeek(): Week + { + return $this->getSolarDay()->getJulianDay()->getWeek(); + } + + /** + * 当天的年干支 + * + * @return SixtyCycle 干支 + */ + function getYearSixtyCycle(): SixtyCycle + { + $solarDay = $this->getSolarDay(); + $solarYear = $solarDay->getMonth()->getYear()->getYear(); + $springSolarDay = SolarTerm::fromIndex($solarYear, 3)->getJulianDay()->getSolarDay(); + $lunarYear = $this->month->getYear(); + $year = $lunarYear->getYear(); + $sixtyCycle = $lunarYear->getSixtyCycle(); + if ($year == $solarYear) { + if ($solarDay->isBefore($springSolarDay)) { + $sixtyCycle = $sixtyCycle->next(-1); + } + } else if ($year < $solarYear) { + if (!$solarDay->isBefore($springSolarDay)) { + $sixtyCycle = $sixtyCycle->next(1); + } + } + return $sixtyCycle; + } + + /** + * 当天的月干支 + * + * @return SixtyCycle 干支 + */ + function getMonthSixtyCycle(): SixtyCycle + { + $solarDay = $this->getSolarDay(); + $year = $solarDay->getMonth()->getYear()->getYear(); + $term = $solarDay->getTerm(); + $index = $term->getIndex() - 3; + if ($index < 0 && $term->getJulianDay()->getSolarDay()->isAfter(SolarTerm::fromIndex($year, 3)->getJulianDay()->getSolarDay())) { + $index += 24; + } + return LunarMonth::fromYm($year, 1)->getSixtyCycle()->next((int)floor($index / 2)); + } + + /** + * 干支 + * + * @return SixtyCycle 干支 + */ + function getSixtyCycle(): SixtyCycle + { + $offset = (int)$this->month->getFirstJulianDay()->next($this->day - 12)->getDay(); + return SixtyCycle::fromName(sprintf('%s%s', HeavenStem::fromIndex($offset)->getName(), EarthBranch::fromIndex($offset)->getName())); + } + + /** + * 建除十二值神 + * + * @return Duty 建除十二值神 + */ + function getDuty(): Duty + { + return Duty::fromIndex($this->getSixtyCycle()->getEarthBranch()->getIndex() - $this->getMonthSixtyCycle()->getEarthBranch()->getIndex()); + } + + /** + * 黄道黑道十二神 + * + * @return TwelveStar 黄道黑道十二神 + */ + function getTwelveStar(): TwelveStar + { + return TwelveStar::fromIndex($this->getSixtyCycle()->getEarthBranch()->getIndex() + (8 - $this->getMonthSixtyCycle()->getEarthBranch()->getIndex() % 6) * 2); + } + + /** + * 九星 + * + * @return NineStar 九星 + */ + function getNineStar(): NineStar + { + $solar = $this->getSolarDay(); + $dongZhi = SolarTerm::fromIndex($solar->getMonth()->getYear()->getYear(), 0); + $xiaZhi = $dongZhi->next(12); + $dongZhi2 = $dongZhi->next(24); + $dongZhiSolar = $dongZhi->getJulianDay()->getSolarDay(); + $xiaZhiSolar = $xiaZhi->getJulianDay()->getSolarDay(); + $dongZhiSolar2 = $dongZhi2->getJulianDay()->getSolarDay(); + $dongZhiIndex = $dongZhiSolar->getLunarDay()->getSixtyCycle()->getIndex(); + $xiaZhiIndex = $xiaZhiSolar->getLunarDay()->getSixtyCycle()->getIndex(); + $dongZhiIndex2 = $dongZhiSolar2->getLunarDay()->getSixtyCycle()->getIndex(); + $solarShunBai = $dongZhiSolar->next($dongZhiIndex > 29 ? 60 - $dongZhiIndex : -$dongZhiIndex); + $solarShunBai2 = $dongZhiSolar2->next($dongZhiIndex2 > 29 ? 60 - $dongZhiIndex2 : -$dongZhiIndex2); + $solarNiZi = $xiaZhiSolar->next($xiaZhiIndex > 29 ? 60 - $xiaZhiIndex : -$xiaZhiIndex); + $offset = 0; + if (!$solar->isBefore($solarShunBai) && $solar->isBefore($solarNiZi)) { + $offset = $solar->subtract($solarShunBai); + } else if (!$solar->isBefore($solarNiZi) && $solar->isBefore($solarShunBai2)) { + $offset = 8 - $solar->subtract($solarNiZi); + } else if (!$solar->isBefore($solarShunBai2)) { + $offset = $solar->subtract($solarShunBai2); + } else if ($solar->isBefore($solarShunBai)) { + $offset = 8 + $solarShunBai->subtract($solar); + } + return NineStar::fromIndex($offset); + } + + /** + * 太岁方位 + * + * @return Direction 方位 + */ + function getJupiterDirection(): Direction + { + $index = $this->getSixtyCycle()->getIndex(); + if ($index % 12 < 6) { + return Direction::fromIndex([2, 8, 4, 6, 0][intdiv($index, 12)]); + } + return $this->month->getYear()->getJupiterDirection(); + } + + /** + * 逐日胎神 + * + * @return FetusDay 逐日胎神 + */ + function getFetusDay(): FetusDay + { + return FetusDay::fromLunarDay($this); + } + + /** + * 月相 + * + * @return Phase 月相 + */ + function getPhase(): Phase + { + return Phase::fromIndex($this->day - 1); + } + + /** + * 公历日 + * + * @return SolarDay 公历日 + */ + function getSolarDay(): SolarDay + { + return $this->month->getFirstJulianDay()->next($this->day - 1)->getSolarDay(); + } + + /** + * 二十八宿 + * + * @return TwentyEightStar 二十八宿 + */ + function getTwentyEightStar(): TwentyEightStar + { + return TwentyEightStar::fromIndex([10, 18, 26, 6, 14, 22, 2][$this->getSolarDay()->getWeek()->getIndex()])->next(-7 * $this->getSixtyCycle()->getEarthBranch()->getIndex()); + } + + /** + * 农历传统节日,如果当天不是农历传统节日,返回null + * + * @return ?LunarFestival 农历传统节日 + */ + function getFestival(): ?LunarFestival + { + $m = $this->getMonth(); + return LunarFestival::fromYmd($m->getYear()->getYear(), $m->getMonthWithLeap(), $this->day); + } + + function equals(mixed $o): bool + { + if (!($o instanceof LunarDay)) { + return false; + } + return $this->month->equals($o->getMonth()) && $this->day == $o->getDay(); + } + +} diff --git a/src/lunar/LunarHour.php b/src/lunar/LunarHour.php new file mode 100644 index 0000000..e3862a4 --- /dev/null +++ b/src/lunar/LunarHour.php @@ -0,0 +1,287 @@ + 23) { + throw new InvalidArgumentException(sprintf('illegal hour: %d', $hour)); + } + if ($minute < 0 || $minute > 59) { + throw new InvalidArgumentException(sprintf('illegal minute: %d', $minute)); + } + if ($second < 0 || $second > 59) { + throw new InvalidArgumentException(sprintf('illegal second: %d', $second)); + } + $this->day = LunarDay::fromYmd($year, $month, $day); + $this->hour = $hour; + $this->minute = $minute; + $this->second = $second; + } + + static function fromYmdHms(int $year, int $month, int $day, int $hour, int $minute, int $second): static + { + return new static($year, $month, $day, $hour, $minute, $second); + } + + /** + * 农历日 + * + * @return LunarDay 农历日 + */ + function getDay(): LunarDay + { + return $this->day; + } + + /** + * 时 + * + * @return int 时 + */ + function getHour(): int + { + return $this->hour; + } + + /** + * 分 + * + * @return int 分 + */ + function getMinute(): int + { + return $this->minute; + } + + /** + * 秒 + * + * @return int 秒 + */ + function getSecond(): int + { + return $this->second; + } + + function getName(): string + { + return sprintf('%s时', EarthBranch::fromIndex($this->getIndexInDay())->getName()); + } + + function __toString(): string + { + return sprintf('%s%s时', $this->day, $this->getSixtyCycle()->getName()); + } + + function getIndexInDay(): int + { + return intdiv($this->hour + 1, 2); + } + + /** + * 是否在指定农历时辰之前 + * + * @param LunarHour $target 农历时辰 + * @return bool true/false + */ + function isBefore(LunarHour $target): bool + { + if (!$this->day->equals($target->getDay())) { + return $this->day->isBefore($target->getDay()); + } + $bHour = $target->getHour(); + if ($this->hour == $bHour) { + $bMinute = $target->getMinute(); + return $this->minute == $bMinute ? $this->second < $target->getSecond() : $this->minute < $bMinute; + } + return $this->hour < $bHour; + } + + /** + * 是否在指定农历时辰之后 + * + * @param LunarHour $target 农历时辰 + * @return true/false + */ + function isAfter(LunarHour $target): bool + { + if (!$this->day->equals($target->getDay())) { + return $this->day->isAfter($target->getDay()); + } + $bHour = $target->getHour(); + if ($this->hour == $bHour) { + $bMinute = $target->getMinute(); + return $this->minute == $bMinute ? $this->second > $target->getSecond() : $this->minute > $bMinute; + } + return $this->hour > $bHour; + } + + function next(int $n): LunarHour + { + $h = $this->hour + $n * 2; + $diff = $h < 0 ? -1 : 1; + $hour = abs($h); + $days = intdiv($hour, 24) * $diff; + $hour = ($hour % 24) * $diff; + if ($hour < 0) { + $hour += 24; + $days--; + } + $d = $this->day->next($days); + $month = $d->getMonth(); + return self::fromYmdHms($month->getYear()->getYear(), $month->getMonthWithLeap(), $d->getDay(), $hour, $this->minute, $this->second); + } + + /** + * 当时的年干支 + * + * @return SixtyCycle 干支 + */ + function getYearSixtyCycle(): SixtyCycle + { + $solarTime = $this->getSolarTime(); + $solarYear = $this->day->getSolarDay()->getMonth()->getYear()->getYear(); + $springSolarTime = SolarTerm::fromIndex($solarYear, 3)->getJulianDay()->getSolarTime(); + $lunarYear = $this->day->getMonth()->getYear(); + $year = $lunarYear->getYear(); + $sixtyCycle = $lunarYear->getSixtyCycle(); + if ($year == $solarYear) { + if ($solarTime->isBefore($springSolarTime)) { + $sixtyCycle = $sixtyCycle->next(-1); + } + } else if ($year < $solarYear) { + if (!$solarTime->isBefore($springSolarTime)) { + $sixtyCycle = $sixtyCycle->next(1); + } + } + return $sixtyCycle; + } + + /** + * 当时的月干支 + * + * @return SixtyCycle 干支 + */ + function getMonthSixtyCycle(): SixtyCycle + { + $solarTime = $this->getSolarTime(); + $year = $solarTime->getDay()->getMonth()->getYear()->getYear(); + $term = $solarTime->getTerm(); + $index = $term->getIndex() - 3; + if ($index < 0 && $term->getJulianDay()->getSolarTime()->isAfter(SolarTerm::fromIndex($year, 3)->getJulianDay()->getSolarTime())) { + $index += 24; + } + return LunarMonth::fromYm($year, 1)->getSixtyCycle()->next((int)floor($index / 2)); + } + + /** + * 当时的日干支(23:00开始算做第二天) + * + * @return SixtyCycle 干支 + */ + function getDaySixtyCycle(): SixtyCycle + { + $d = $this->day->getSixtyCycle(); + return $this->hour > 22 ? $d->next(1) : $d; + } + + /** + * 干支 + * + * @return SixtyCycle 干支 + */ + function getSixtyCycle(): SixtyCycle + { + $earthBranchIndex = $this->getIndexInDay() % 12; + $heavenStemIndex = $this->getDaySixtyCycle()->getHeavenStem()->getIndex() % 5 * 2 + $earthBranchIndex; + return SixtyCycle::fromName(sprintf('%s%s', HeavenStem::fromIndex($heavenStemIndex)->getName(), EarthBranch::fromIndex($earthBranchIndex)->getName())); + } + + /** + * 九星(时家紫白星歌诀:三元时白最为佳,冬至阳生顺莫差,孟日七宫仲一白,季日四绿发萌芽,每把时辰起甲子,本时星耀照光华,时星移入中宫去,顺飞八方逐细查。夏至阴生逆回首,孟归三碧季加六,仲在九宫时起甲,依然掌中逆轮跨。) + * + * @return NineStar 九星 + */ + function getNineStar(): NineStar + { + $solar = $this->day->getSolarDay(); + $dongZhi = SolarTerm::fromIndex($solar->getMonth()->getYear()->getYear(), 0); + $xiaZhi = $dongZhi->next(12); + $asc = !$solar->isBefore($dongZhi->getJulianDay()->getSolarDay()) && $solar->isBefore($xiaZhi->getJulianDay()->getSolarDay()); + $start = [8, 5, 2][$this->day->getSixtyCycle()->getEarthBranch()->getIndex() % 3]; + if ($asc) { + $start = 8 - $start; + } + $earthBranchIndex = $this->getIndexInDay() % 12; + return NineStar::fromIndex($start + ($asc ? $earthBranchIndex : -$earthBranchIndex)); + } + + /** + * 公历时刻 + * + * @return SolarTime 公历时刻 + */ + function getSolarTime(): SolarTime + { + $d = $this->day->getSolarDay(); + $m = $d->getMonth(); + return SolarTime::fromYmdHms($m->getYear()->getYear(), $m->getMonth(), $d->getDay(), $this->hour, $this->minute, $this->second); + } + + /** + * 八字 + * + * @return EightChar 八字 + */ + function getEightChar(): EightChar + { + return new EightChar($this->getYearSixtyCycle(), $this->getMonthSixtyCycle(), $this->getDaySixtyCycle(), $this->getSixtyCycle()); + } + + function equals(mixed $o): bool + { + if (!($o instanceof LunarHour)) { + return false; + } + return $this->day->equals($o->getDay()) && $this->hour == $o->getHour() && $this->minute == $o->getMinute() && $this->second == $o->getSecond(); + } +} diff --git a/src/lunar/LunarMonth.php b/src/lunar/LunarMonth.php new file mode 100644 index 0000000..edb300f --- /dev/null +++ b/src/lunar/LunarMonth.php @@ -0,0 +1,341 @@ +getLeapMonth(); + if ($month == 0 || $month > 12 || $month < -12) { + throw new InvalidArgumentException(sprintf('illegal lunar month: %d', $month)); + } + $leap = $month < 0; + $m = abs($month); + if ($leap && $m != $currentLeapMonth) { + throw new InvalidArgumentException(sprintf('illegal leap month %d in lunar year %d', $m, $year)); + } + + // 冬至 + $dongZhi = SolarTerm::fromIndex($year, 0); + $dongZhiJd = $dongZhi->getCursoryJulianDay(); + + // 冬至前的初一,今年首朔的日月黄经差 + $w = ShouXingUtil::calcShuo($dongZhiJd); + if ($w > $dongZhiJd) { + $w -= 29.53; + } + + // 计算正月初一的偏移 + $prevYear = LunarYear::fromYear($year - 1); + $prevLeapMonth = $prevYear->getLeapMonth(); + + // 正常情况正月初一为第3个朔日,但有些特殊的 + $offset = 2; + if ($year > 8 && $year < 24) { + $offset = 1; + } else if ($prevLeapMonth > 10 && $year != 239 && $year != 240) { + $offset = 3; + } + + // 位于当年的索引 + $index = $m - 1; + if ($leap || ($currentLeapMonth > 0 && $m > $currentLeapMonth)) { + $index += 1; + } + $this->indexInYear = $index; + + // 本月初一 + $w += 29.5306 * ($offset + $index); + $firstDay = ShouXingUtil::calcShuo($w); + $this->firstJulianDay = JulianDay::fromJulianDay(JulianDay::$J2000 + $firstDay); + // 本月天数 = 下月初一 - 本月初一 + $this->dayCount = (int)(ShouXingUtil::calcShuo($w + 29.5306) - $firstDay); + $this->year = $currentYear; + $this->month = $m; + $this->leap = $leap; + } + + static function fromYm(int $year, int $month): static + { + return new static($year, $month); + } + + /** + * 农历年 + * + * @return LunarYear 农历年 + */ + function getYear(): LunarYear + { + return $this->year; + } + + /** + * 月 + * + * @return int 月 + */ + function getMonth(): int + { + return $this->month; + } + + /** + * 月 + * + * @return int 月,当月为闰月时,返回负数 + */ + function getMonthWithLeap(): int + { + return $this->leap ? -$this->month : $this->month; + } + + /** + * 天数(大月30天,小月29天) + * + * @return int 天数 + */ + function getDayCount(): int + { + return $this->dayCount; + } + + /** + * 位于当年的索引(0-12) + * + * @return int 索引 + */ + function getIndexInYear(): int + { + return $this->indexInYear; + } + + /** + * 农历季节 + * + * @return LunarSeason 农历季节 + */ + function getSeason(): LunarSeason + { + return LunarSeason::fromIndex($this->month - 1); + } + + /** + * 初一的儒略日 + * + * @return JulianDay 儒略日 + */ + function getFirstJulianDay(): JulianDay + { + return $this->firstJulianDay; + } + + /** + * 是否闰月 + * + * @return bool true/false + */ + function isLeap(): bool + { + return $this->leap; + } + + /** + * 周数 + * + * @param int $start 起始星期,1234560分别代表星期一至星期天 + * @return int 周数 + */ + function getWeekCount(int $start): int + { + return (int)ceil(($this->indexOf($this->firstJulianDay->getWeek()->getIndex() - $start, null, 7) + $this->getDayCount()) / 7); + } + + /** + * 依据国家标准《农历的编算和颁行》GB/T 33661-2017中农历月的命名方法。 + * + * @return string 名称 + */ + function getName(): string + { + return sprintf('%s%s', $this->leap ? '闰' : '', self::$NAMES[$this->month - 1]); + } + + function __toString(): string + { + return sprintf('%s%s', $this->year, $this->getName()); + } + + function next(int $n): LunarMonth + { + if ($n == 0) { + return static::fromYm($this->year->getYear(), $this->getMonthWithLeap()); + } + $m = $this->indexInYear + 1 + $n; + $y = $this->year; + $leapMonth = $y->getLeapMonth(); + $monthSize = 12 + ($leapMonth > 0 ? 1 : 0); + $forward = $n > 0; + $add = $forward ? 1 : -1; + while ($forward ? ($m > $monthSize) : ($m <= 0)) { + if ($forward) { + $m -= $monthSize; + } + $y = $y->next($add); + $leapMonth = $y->getLeapMonth(); + $monthSize = 12 + ($leapMonth > 0 ? 1 : 0); + if (!$forward) { + $m += $monthSize; + } + } + $leap = false; + if ($leapMonth > 0) { + if ($m == $leapMonth + 1) { + $leap = true; + } + if ($m > $leapMonth) { + $m--; + } + } + return static::fromYm($y->getYear(), $leap ? -$m : $m); + } + + /** + * 获取本月的农历日列表 + * + * @return LunarDay[] 农历日列表 + */ + function getDays(): array + { + $size = $this->getDayCount(); + $y = $this->year->getYear(); + $m = $this->getMonthWithLeap(); + $l = array(); + for ($i = 0; $i < $size; $i++) { + $l[] = LunarDay::fromYmd($y, $m, $i + 1); + } + return $l; + } + + /** + * 获取本月的农历周列表 + * + * @param int $start 星期几作为一周的开始,1234560分别代表星期一至星期天 + * @return LunarWeek[] 周列表 + */ + function getWeeks(int $start): array + { + $size = $this->getWeekCount($start); + $y = $this->year->getYear(); + $m = $this->getMonthWithLeap(); + $l = array(); + for ($i = 0; $i < $size; $i++) { + $l[] = LunarWeek::fromYm($y, $m, $i, $start); + } + return $l; + } + + /** + * 干支 + * + * @return SixtyCycle 干支 + */ + function getSixtyCycle(): SixtyCycle + { + return SixtyCycle::fromName(sprintf('%s%s', HeavenStem::fromIndex(($this->year->getSixtyCycle()->getHeavenStem()->getIndex() + 1) * 2 + $this->indexInYear)->getName(), EarthBranch::fromIndex($this->indexInYear + 2)->getName())); + } + + /** + * 九星 + * + * @return NineStar 九星 + */ + function getNineStar(): NineStar + { + return NineStar::fromIndex(27 - $this->year->getSixtyCycle()->getEarthBranch()->getIndex() % 3 * 3 - $this->getSixtyCycle()->getEarthBranch()->getIndex()); + } + + /** + * 太岁方位 + * + * @return Direction 方位 + */ + function getJupiterDirection(): Direction + { + $sixtyCycle = $this->getSixtyCycle(); + $n = [7, -1, 1, 3][$sixtyCycle->getEarthBranch()->next(-2)->getIndex() % 4]; + return $n == -1 ? $sixtyCycle->getHeavenStem()->getDirection() : Direction::fromIndex($n); + } + + /** + * 逐月胎神 + * + * @return FetusMonth 逐月胎神 + */ + function getFetus(): FetusMonth + { + return FetusMonth::fromLunarMonth($this); + } + + function equals(mixed $o): bool + { + if (!($o instanceof LunarMonth)) { + return false; + } + return $this->year->equals($o->getYear()) && $this->getMonthWithLeap() == $o->getMonthWithLeap(); + } + +} diff --git a/src/lunar/LunarSeason.php b/src/lunar/LunarSeason.php new file mode 100644 index 0000000..b1beb2d --- /dev/null +++ b/src/lunar/LunarSeason.php @@ -0,0 +1,40 @@ +nextIndex($n)); + } +} diff --git a/src/lunar/LunarWeek.php b/src/lunar/LunarWeek.php new file mode 100644 index 0000000..7d0e20c --- /dev/null +++ b/src/lunar/LunarWeek.php @@ -0,0 +1,158 @@ + 5) { + throw new InvalidArgumentException(sprintf('illegal lunar week index: %d', $index)); + } + if ($start < 0 || $start > 6) { + throw new InvalidArgumentException(sprintf('illegal lunar week start: %d', $start)); + } + $m = LunarMonth::fromYm($year, $month); + if ($index >= $m->getWeekCount($start)) { + throw new InvalidArgumentException(sprintf('illegal lunar week index: %d in month: %s', $index, $m)); + } + $this->month = $m; + $this->index = $index; + $this->start = Week::fromIndex($start); + } + + static function fromYm(int $year, int $month, int $index, int $start): static + { + return new static($year, $month, $index, $start); + } + + /** + * 月 + * + * @return LunarMonth 月 + */ + function getMonth(): LunarMonth + { + return $this->month; + } + + /** + * 索引 + * + * @return int 索引,0-5 + */ + function getIndex(): int + { + return $this->index; + } + + /** + * 起始星期 + * + * @return Week 星期 + */ + function getStart(): Week + { + return $this->start; + } + + function getName(): string + { + return self::$NAMES[$this->index]; + } + + function __toString(): string + { + return sprintf('%s%s', $this->month, $this->getName()); + } + + function next(int $n): static + { + if ($n == 0) { + return static::fromYm($this->month->getYear()->getYear(), $this->month->getMonthWithLeap(), $this->index, $this->start->getIndex()); + } + $d = $this->index + $n; + $m = $this->month; + $startIndex = $this->start->getIndex(); + $weeksInMonth = $m->getWeekCount($startIndex); + $forward = $n > 0; + $add = $forward ? 1 : -1; + while ($forward ? ($d >= $weeksInMonth) : ($d < 0)) { + if ($forward) { + $d -= $weeksInMonth; + } + if (!$forward) { + if (!LunarDay::fromYmd($m->getYear()->getYear(), $m->getMonthWithLeap(), 1)->getWeek()->equals($this->start)) { + $d += $add; + } + } + $m = $m->next($add); + if ($forward) { + if (!LunarDay::fromYmd($m->getYear()->getYear(), $m->getMonthWithLeap(), 1)->getWeek()->equals($this->start)) { + $d += $add; + } + } + $weeksInMonth = $m->getWeekCount($startIndex); + if (!$forward) { + $d += $weeksInMonth; + } + } + return static::fromYm($m->getYear()->getYear(), $m->getMonthWithLeap(), $d, $startIndex); + } + + /** + * 本周第1天 + * + * @return LunarDay 公历日 + */ + function getFirstDay(): LunarDay + { + $m = $this->getMonth(); + $firstDay = LunarDay::fromYmd($m->getYear()->getYear(), $m->getMonthWithLeap(), 1); + return $firstDay->next($this->index * 7 - $this->indexOf($firstDay->getWeek()->getIndex() - $this->start->getIndex(), null, 7)); + } + + /** + * 本周农历日列表 + * + * @return LunarDay[] 农历日列表 + */ + function getDays(): array + { + $l = array(); + $d = $this->getFirstDay(); + $l[] = $d; + for ($i = 1; $i < 7; $i++) { + $l[] = $d->next($i); + } + return $l; + } + +} diff --git a/src/lunar/LunarYear.php b/src/lunar/LunarYear.php new file mode 100644 index 0000000..b7cda5d --- /dev/null +++ b/src/lunar/LunarYear.php @@ -0,0 +1,191 @@ + -1; $x--) { + $t += $c * strpos($chars, substr($s, $x, 1)); + $c *= 64; + } + $n += $t; + $l[] = $n; + } + $leap[$i + 1] = $l; + } + self::$LEAP = $leap; + } + + protected function __construct(int $year) + { + if (null == self::$LEAP) { + self::init(); + } + if ($year < -1 || $year > 9999) { + throw new InvalidArgumentException(sprintf('illegal lunar year: %d', $year)); + } + $this->year = $year; + } + + static function fromYear(int $year): static + { + return new static($year); + } + + /** + * 年 + * + * @return int 年 + */ + function getYear(): int + { + return $this->year; + } + + /** + * 天数 + * + * @return int 天数 + */ + function getDayCount(): int + { + $n = 0; + foreach ($this->getMonths() as $m) { + $n += $m->getDayCount(); + } + return $n; + } + + /** + * 依据国家标准《农历的编算和颁行》GB/T 33661-2017,农历年有2种命名方法:干支纪年法和生肖纪年法,这里默认采用干支纪年法。 + * + * @return string 名称 + */ + function getName(): string + { + return sprintf('农历%s年', $this->getSixtyCycle()); + } + + function next(int $n): LunarYear + { + return $this->fromYear($this->year + $n); + } + + /** + * 闰月 + * + * @return int 闰月数字,1代表闰1月,0代表无闰月 + */ + function getLeapMonth(): int + { + if ($this->year == -1) { + return 11; + } + foreach (self::$LEAP as $key => $value) { + if (in_array($this->year, $value)) { + return $key; + } + } + return 0; + } + + /** + * 干支 + * + * @return SixtyCycle 干支 + */ + function getSixtyCycle(): SixtyCycle + { + return SixtyCycle::fromIndex($this->year - 4); + } + + /** + * 运 + * + * @return Twenty 运 + */ + function getTwenty(): Twenty + { + return Twenty::fromIndex((int)floor(($this->year - 1864) / 20)); + } + + /** + * 九星 + * + * @return NineStar 九星 + */ + function getNineStar(): NineStar + { + return NineStar::fromIndex(63 + $this->getTwenty()->getSixty()->getIndex() * 3 - $this->getSixtyCycle()->getIndex()); + } + + /** + * 太岁方位 + * + * @return Direction 方位 + */ + function getJupiterDirection(): Direction + { + return Direction::fromIndex([0, 7, 7, 2, 3, 3, 8, 1, 1, 6, 0, 0][$this->getSixtyCycle()->getEarthBranch()->getIndex()]); + } + + /** + * 月份列表 + * + * @return LunarMonth[] 月份列表,一般有12个月,当年有闰月时,有13个月。 + */ + function getMonths(): array + { + $l = array(); + $m = LunarMonth::fromYm($this->year, 1); + while ($m->getYear()->getYear() == $this->year) { + $l[] = $m; + $m = $m->next(1); + } + return $l; + } + + function equals(mixed $o): bool + { + return $o instanceof LunarYear && $o->getYear() == $this->year; + } + +} diff --git a/src/sixtycycle/EarthBranch.php b/src/sixtycycle/EarthBranch.php new file mode 100644 index 0000000..58aa714 --- /dev/null +++ b/src/sixtycycle/EarthBranch.php @@ -0,0 +1,147 @@ +nextIndex($n)); + } + + /** + * 五行 + * + * @return Element 五行 + */ + function getElement(): Element + { + return Element::fromIndex([4, 2, 0, 0, 2, 1, 1, 2, 3, 3, 2, 4][$this->index]); + } + + /** + * 阴阳 + * + * @return YinYang 阴阳 + */ + function getYinYang(): YinYang + { + return $this->index % 2 == 0 ? YinYang::YANG : YinYang::YIN; + } + + /** + * 藏干之本气 + * + * @return HeavenStem 天干 + */ + function getHideHeavenStemMain(): HeavenStem + { + return HeavenStem::fromIndex([9, 5, 0, 1, 4, 2, 3, 5, 6, 7, 4, 8][$this->index]); + } + + /** + * 藏干之中气,无中气返回null + * + * @return ?HeavenStem 天干 + */ + function getHideHeavenStemMiddle(): ?HeavenStem + { + $n = [-1, 9, 2, -1, 1, 6, 5, 3, 8, -1, 7, 0][$this->index]; + return $n == -1 ? null : HeavenStem::fromIndex($n); + } + + /** + * 藏干之余气,无余气返回null + * + * @return ?HeavenStem 天干 + */ + function getHideHeavenStemResidual(): ?HeavenStem + { + $n = [-1, 7, 4, -1, 9, 4, -1, 1, 4, -1, 3, -1][$this->index]; + return $n == -1 ? null : HeavenStem::fromIndex($n); + } + + /** + * 生肖 + * + * @return Zodiac 生肖 + */ + function getZodiac(): Zodiac + { + return Zodiac::fromIndex($this->index); + } + + /** + * 方位 + * + * @return Direction 方位 + */ + function getDirection(): Direction + { + return Direction::fromIndex([0, 4, 2, 2, 4, 8, 8, 4, 6, 6, 4, 0][$this->index]); + } + + /** + * 相冲的地支(子午冲,丑未冲,寅申冲,辰戌冲,卯酉冲,巳亥冲) + * + * @return EarthBranch 地支 + */ + function getOpposite(): static + { + return $this->next(6); + } + + /** + * 煞(逢巳日、酉日、丑日必煞东;亥日、卯日、未日必煞西;申日、子日、辰日必煞南;寅日、午日、戌日必煞北。) + * + * @return Direction 方位 + */ + function getOminous(): Direction + { + return Direction::fromIndex([8, 2, 0, 6][$this->index % 4]); + } + + /** + * 地支彭祖百忌 + * + * @return PengZuEarthBranch 地支彭祖百忌 + */ + function getPengZuEarthBranch(): PengZuEarthBranch + { + return PengZuEarthBranch::fromIndex($this->index); + } +} diff --git a/src/sixtycycle/HeavenStem.php b/src/sixtycycle/HeavenStem.php new file mode 100644 index 0000000..4784eb8 --- /dev/null +++ b/src/sixtycycle/HeavenStem.php @@ -0,0 +1,172 @@ +nextIndex($n)); + } + + /** + * 五行 + * + * @return Element 五行 + */ + function getElement(): Element + { + return Element::fromIndex(intdiv($this->index, 2)); + } + + /** + * 阴阳 + * + * @return YinYang 阴阳 + */ + function getYinYang(): YinYang + { + return $this->index % 2 == 0 ? YinYang::YANG : YinYang::YIN; + } + + /** + * 十神(生我者,正印偏印。我生者,伤官食神。克我者,正官七杀。我克者,正财偏财。同我者,劫财比肩。) + * + * @param HeavenStem $target 天干 + * @return TenStar 十神 + */ + function getTenStar(HeavenStem $target): TenStar + { + $hostElement = $this->getElement(); + $guestElement = $target->getElement(); + $index = 0; + $sameYinYang = $this->getYinYang()->equals($target->getYinYang()); + if ($hostElement->getReinforce()->equals($guestElement)) { + $index = 1; + } else if ($hostElement->getRestrain()->equals($guestElement)) { + $index = 2; + } else if ($guestElement->getRestrain()->equals($hostElement)) { + $index = 3; + } else if ($guestElement->getReinforce()->equals($hostElement)) { + $index = 4; + } + return TenStar::fromIndex($index * 2 + ($sameYinYang ? 0 : 1)); + } + + /** + * 方位 + * + * @return Direction 方位 + */ + function getDirection(): Direction + { + return Direction::fromIndex([2, 8, 4, 6, 0][intdiv($this->index, 2)]); + } + + /** + * 喜神方位(《喜神方位歌》甲己在艮乙庚乾,丙辛坤位喜神安。丁壬只在离宫坐,戊癸原在在巽间。) + * + * @return Direction 方位 + */ + function getJoyDirection(): Direction + { + return Direction::fromIndex([7, 5, 1, 8, 3][$this->index % 5]); + } + + /** + * 阳贵神方位(《阳贵神歌》甲戊坤艮位,乙己是坤坎,庚辛居离艮,丙丁兑与乾,震巽属何日,壬癸贵神安。) + * + * @return Direction 方位 + */ + function getYangDirection(): Direction + { + return Direction::fromIndex([1, 1, 6, 5, 7, 0, 8, 7, 2, 3][$this->index]); + } + + /** + * 阴贵神方位(《阴贵神歌》甲戊见牛羊,乙己鼠猴乡,丙丁猪鸡位,壬癸蛇兔藏,庚辛逢虎马,此是贵神方。) + * + * @return Direction 方位 + */ + function getYinDirection(): Direction + { + return Direction::fromIndex([7, 0, 5, 6, 1, 1, 7, 8, 3, 2][$this->index]); + } + + /** + * 财神方位(《财神方位歌》甲乙东北是财神,丙丁向在西南寻,戊己正北坐方位,庚辛正东去安身,壬癸原来正南坐,便是财神方位真。) + * + * @return Direction 方位 + */ + function getWealthDirection(): Direction + { + return Direction::fromIndex([7, 1, 0, 2, 8][intdiv($this->index, 2)]); + } + + /** + * 福神方位(《福神方位歌》甲乙东南是福神,丙丁正东是堪宜,戊北己南庚辛坤,壬在乾方癸在西。) + * + * @return Direction 方位 + */ + function getMascotDirection(): Direction + { + return Direction::fromIndex([3, 3, 2, 2, 0, 8, 1, 1, 5, 6][$this->index]); + } + + /** + * 天干彭祖百忌 + * + * @return PengZuHeavenStem 天干彭祖百忌 + */ + function getPengZuHeavenStem(): PengZuHeavenStem + { + return PengZuHeavenStem::fromIndex($this->index); + } + + /** + * 地势(长生十二神) + * + * @param EarthBranch $earthBranch 地支 + * @return Terrain 地势(长生十二神) + */ + function getTerrain(EarthBranch $earthBranch): Terrain + { + $earthBranchIndex = $earthBranch->getIndex(); + return Terrain::fromIndex([1, 6, 10, 9, 10, 9, 7, 0, 4, 3][$this->index] + (YinYang::YANG == $this->getYinYang() ? $earthBranchIndex : -$earthBranchIndex)); + } +} diff --git a/src/sixtycycle/SixtyCycle.php b/src/sixtycycle/SixtyCycle.php new file mode 100644 index 0000000..24ac1bf --- /dev/null +++ b/src/sixtycycle/SixtyCycle.php @@ -0,0 +1,107 @@ +nextIndex($n)); + } + + /** + * 天干 + * + * @return HeavenStem 天干 + */ + function getHeavenStem(): HeavenStem + { + return HeavenStem::fromIndex($this->index % count(HeavenStem::$NAMES)); + } + + /** + * 地支 + * + * @return EarthBranch 地支 + */ + function getEarthBranch(): EarthBranch + { + return EarthBranch::fromIndex($this->index % count(EarthBranch::$NAMES)); + } + + /** + * 纳音 + * + * @return Sound 纳音 + */ + function getSound(): Sound + { + return Sound::fromIndex(intdiv($this->index, 2)); + } + + /** + * 彭祖百忌 + * + * @return PengZu 彭祖百忌 + */ + function getPengZu(): PengZu + { + return PengZu::fromSixtyCycle($this); + } + + /** + * 旬 + * + * @return Ten 旬 + */ + function getTen(): Ten + { + return Ten::fromIndex(intdiv($this->getHeavenStem()->getIndex() - $this->getEarthBranch()->getIndex(), 2)); + } + + /** + * 旬空(空亡),因地支比天干多2个,旬空则为每一轮干支一一配对后多出来的2个地支 + * + * @return EarthBranch[] 旬空(空亡) + */ + function getExtraEarthBranches(): array + { + $l = array(); + $l[] = EarthBranch::fromIndex(10 + $this->getEarthBranch()->getIndex() - $this->getHeavenStem()->getIndex()); + $l[] = $l[0]->next(1); + return $l; + } + +} diff --git a/src/solar/SolarDay.php b/src/solar/SolarDay.php new file mode 100644 index 0000000..be6c564 --- /dev/null +++ b/src/solar/SolarDay.php @@ -0,0 +1,362 @@ +month = SolarMonth::fromYm($year, $month); + if ($day < 1) { + throw new InvalidArgumentException(sprintf('illegal solar day: %d-%d-%d', $year, $month, $day)); + } + if (1582 == $year && 10 == $month) { + if ($day > 4 && $day < 15) { + throw new InvalidArgumentException(sprintf('illegal solar day: %d-%d-%d', $year, $month, $day)); + } else if ($day > 31) { + throw new InvalidArgumentException(sprintf('illegal solar day: %d-%d-%d', $year, $month, $day)); + } + } else if ($day > SolarMonth::fromYm($year, $month)->getDayCount()) { + throw new InvalidArgumentException(sprintf('illegal solar day: %d-%d-%d', $year, $month, $day)); + } + $this->day = $day; + } + + static function fromYmd(int $year, int $month, int $day): static + { + return new static($year, $month, $day); + } + + /** + * 月 + * + * @return SolarMonth 月 + */ + function getMonth(): SolarMonth + { + return $this->month; + } + + /** + * 日 + * + * @return int 日 + */ + function getDay(): int + { + return $this->day; + } + + /** + * 星期 + * + * @return Week 星期 + */ + function getWeek(): Week + { + return $this->getJulianDay()->getWeek(); + } + + /** + * 星座 + * + * @return Constellation 星座 + */ + function getConstellation(): Constellation + { + $index = 11; + $y = $this->month->getMonth() * 100 + $this->day; + if ($y >= 321 && $y <= 419) { + $index = 0; + } else if ($y >= 420 && $y <= 520) { + $index = 1; + } else if ($y >= 521 && $y <= 621) { + $index = 2; + } else if ($y >= 622 && $y <= 722) { + $index = 3; + } else if ($y >= 723 && $y <= 822) { + $index = 4; + } else if ($y >= 823 && $y <= 922) { + $index = 5; + } else if ($y >= 923 && $y <= 1023) { + $index = 6; + } else if ($y >= 1024 && $y <= 1122) { + $index = 7; + } else if ($y >= 1123 && $y <= 1221) { + $index = 8; + } else if ($y >= 1222 || $y <= 119) { + $index = 9; + } else if ($y <= 218) { + $index = 10; + } + return Constellation::fromIndex($index); + } + + function getName(): string + { + return self::$NAMES[$this->day - 1]; + } + + function __toString(): string + { + return sprintf('%s%s', $this->month, $this->getName()); + } + + function next(int $n): SolarDay + { + return $this->getJulianDay()->next($n)->getSolarDay(); + } + + /** + * 是否在指定公历日之前 + * + * @param SolarDay $target 公历日 + * @return bool true/false + */ + function isBefore(SolarDay $target): bool + { + $aYear = $this->month->getYear()->getYear(); + $targetMonth = $target->getMonth(); + $bYear = $targetMonth->getYear()->getYear(); + if ($aYear == $bYear) { + $aMonth = $this->month->getMonth(); + $bMonth = $targetMonth->getMonth(); + return $aMonth == $bMonth ? $this->day < $target->getDay() : $aMonth < $bMonth; + } + return $aYear < $bYear; + } + + /** + * 是否在指定公历日之后 + * + * @param SolarDay $target 公历日 + * @return bool true/false + */ + function isAfter(SolarDay $target): bool + { + $aYear = $this->month->getYear()->getYear(); + $targetMonth = $target->getMonth(); + $bYear = $targetMonth->getYear()->getYear(); + if ($aYear == $bYear) { + $aMonth = $this->month->getMonth(); + $bMonth = $targetMonth->getMonth(); + return $aMonth == $bMonth ? $this->day > $target->getDay() : $aMonth > $bMonth; + } + return $aYear > $bYear; + } + + /** + * 节气 + * + * @return SolarTerm 节气 + */ + function getTerm(): SolarTerm + { + $term = SolarTerm::fromIndex($this->month->getYear()->getYear() + 1, 0); + while ($this->isBefore($term->getJulianDay()->getSolarDay())) { + $term = $term->next(-1); + } + return $term; + } + + /** + * 七十二候 + * + * @return PhenologyDay 七十二候 + */ + function getPhenologyDay(): PhenologyDay + { + $term = $this->getTerm(); + $dayIndex = $this->subtract($term->getJulianDay()->getSolarDay()); + $index = intdiv($dayIndex, 5); + if ($index > 2) { + $index = 2; + } + $dayIndex -= $index * 5; + return new PhenologyDay(Phenology::fromIndex($term->getIndex() * 3 + $index), $dayIndex); + } + + /** + * 三伏天 + * + * @return DogDay|null 三伏天 + */ + function getDogDay(): ?DogDay + { + $xiaZhi = SolarTerm::fromIndex($this->month->getYear()->getYear(), 12); + // 第1个庚日 + $start = $xiaZhi->getJulianDay()->getSolarDay(); + $add = 6 - $start->getLunarDay()->getSixtyCycle()->getHeavenStem()->getIndex(); + if ($add < 0) { + $add += 10; + } + // 第3个庚日,即初伏第1天 + $add += 20; + $start = $start->next($add); + $days = $this->subtract($start); + // 初伏以前 + if ($days < 0) { + return null; + } + if ($days < 10) { + return new DogDay(Dog::fromIndex(0), $days); + } + // 第4个庚日,中伏第1天 + $start = $start->next(10); + $days = $this->subtract($start); + if ($days < 10) { + return new DogDay(Dog::fromIndex(1), $days); + } + // 第5个庚日,中伏第11天或末伏第1天 + $start = $start->next(10); + $days = $this->subtract($start); + // 立秋 + if ($xiaZhi->next(3)->getJulianDay()->getSolarDay()->isAfter($start)) { + if ($days < 10) { + return new DogDay(Dog::fromIndex(1), $days + 10); + } + $start = $start->next(10); + $days = $this->subtract($start); + } + if ($days < 10) { + return new DogDay(Dog::fromIndex(2), $days); + } + return null; + } + + /** + * 数九天 + * + * @return NineDay|null 数九天 + */ + function getNineDay(): ?NineDay + { + $year = $this->month->getYear()->getYear(); + $start = SolarTerm::fromIndex($year + 1, 0)->getJulianDay()->getSolarDay(); + if ($this->isBefore($start)) { + $start = SolarTerm::fromIndex($year, 0)->getJulianDay()->getSolarDay(); + } + $end = $start->next(81); + if ($this->isBefore($start) || !$this->isBefore($end)) { + return null; + } + $days = $this->subtract($start); + return new NineDay(Nine::fromIndex(intdiv($days, 9)), $days % 9); + } + + /** + * 位于当年的索引 + * + * @return int 索引 + */ + function getIndexInYear(): int + { + $m = $this->month->getMonth(); + $y = $this->month->getYear()->getYear(); + $days = 0; + for ($i = 1; $i < $m; $i++) { + $days += SolarMonth::fromYm($y, $i)->getDayCount(); + } + $d = $this->day; + if (1582 == $y && 10 == $m) { + if ($d >= 15) { + $d -= 10; + } + } + return $days + $d - 1; + } + + /** + * 公历日期相减,获得相差天数 + * + * @param SolarDay $target 公历 + * @return int 天数 + */ + function subtract(SolarDay $target): int + { + return (int)($this->getJulianDay()->getDay() - $target->getJulianDay()->getDay()); + } + + /** + * 儒略日 + * + * @return JulianDay 儒略日 + */ + function getJulianDay(): JulianDay + { + return JulianDay::fromYmdHms($this->month->getYear()->getYear(), $this->month->getMonth(), $this->day, 0, 0, 0); + } + + /** + * 农历日 + * + * @return LunarDay 农历日 + */ + function getLunarDay(): LunarDay + { + $m = LunarMonth::fromYm($this->month->getYear()->getYear(), $this->month->getMonth())->next(-3); + $days = $this->subtract($m->getFirstJulianDay()->getSolarDay()); + while ($days >= $m->getDayCount()) { + $m = $m->next(1); + $days = $this->subtract($m->getFirstJulianDay()->getSolarDay()); + } + return LunarDay::fromYmd($m->getYear()->getYear(), $m->getMonthWithLeap(), $days + 1); + } + + /** + * 法定假日,如果当天不是法定假日,返回null + * + * @return ?LegalHoliday 法定假日 + */ + function getLegalHoliday(): ?LegalHoliday + { + $m = $this->getMonth(); + return LegalHoliday::fromYmd($m->getYear()->getYear(), $m->getMonth(), $this->day); + } + + /** + * 公历现代节日,如果当天不是公历现代节日,返回null + * + * @return ?SolarFestival 公历现代节日 + */ + function getFestival(): ?SolarFestival + { + $m = $this->getMonth(); + return SolarFestival::fromYmd($m->getYear()->getYear(), $m->getMonth(), $this->day); + } + +} diff --git a/src/solar/SolarHalfYear.php b/src/solar/SolarHalfYear.php new file mode 100644 index 0000000..fff33b2 --- /dev/null +++ b/src/solar/SolarHalfYear.php @@ -0,0 +1,107 @@ +year = SolarYear::fromYear($year); + if ($index < 0 || $index > 1) { + throw new InvalidArgumentException(sprintf('illegal solar half year index: %d', $index)); + } + $this->index = $index; + } + + static function fromIndex(int $year, int $index): static + { + return new static($year, $index); + } + + /** + * 年 + * @return SolarYear 年 + */ + function getYear(): SolarYear + { + return $this->year; + } + + /** + * 索引 + * + * @return int 索引,0-1 + */ + function getIndex(): int + { + return $this->index; + } + + function getName(): string + { + return self::$NAMES[$this->index]; + } + + function __toString(): string + { + return sprintf('%s%s', $this->year, $this->getName()); + } + + function next(int $n): static + { + $m = $this->index + $n; + return self::fromIndex($this->year->getYear() + intdiv($m, 2), abs($m % 2)); + } + + /** + * 月份列表 + * + * @return SolarMonth[] 月份列表,1年有12个月。 + */ + function getMonths(): array + { + $l = array(); + $y = $this->year->getYear(); + for ($i = 0; $i < 6; $i++) { + $l[] = SolarMonth::fromYm($y, $this->index * 6 + $i + 1); + } + return $l; + } + + /** + * 季度列表 + * + * @return SolarSeason[] 季度列表,1年有4个季度。 + */ + function getSeasons(): array + { + $l = array(); + $y = $this->year->getYear(); + for ($i = 0; $i < 2; $i++) { + $l[] = SolarSeason::fromIndex($y, $this->index * 2 + $i); + } + return $l; + } + +} diff --git a/src/solar/SolarMonth.php b/src/solar/SolarMonth.php new file mode 100644 index 0000000..dc50286 --- /dev/null +++ b/src/solar/SolarMonth.php @@ -0,0 +1,173 @@ +year = SolarYear::fromYear($year); + if ($month < 1 || $month > 12) { + throw new InvalidArgumentException(sprintf('illegal solar month: %d', $month)); + } + $this->month = $month; + } + + static function fromYm(int $year, int $month): static + { + return new static($year, $month); + } + + /** + * 年 + * @return SolarYear 年 + */ + function getYear(): SolarYear + { + return $this->year; + } + + /** + * 月 + * + * @return int 月 + */ + function getMonth(): int + { + return $this->month; + } + + /** + * 天数(1582年10月只有21天) + * + * @return int 天数 + */ + function getDayCount(): int + { + if (1582 == $this->year->getYear() && 10 == $this->month) { + return 21; + } + $d = self::$DAYS[$this->getIndexInYear()]; + //公历闰年2月多一天 + if (2 == $this->month && $this->year->isLeap()) { + $d++; + } + return $d; + } + + /** + * 位于当年的索引(0-11) + * + * @return int 索引 + */ + function getIndexInYear(): int + { + return $this->month - 1; + } + + /** + * 公历季度 + * + * @return SolarSeason 公历季度 + */ + function getSeason(): SolarSeason + { + return SolarSeason::fromIndex($this->year->getYear(), intdiv($this->getIndexInYear(), 3)); + } + + /** + * 周数 + * + * @param int $start 起始星期,1234560分别代表星期一至星期天 + * @return int 周数 + */ + function getWeekCount(int $start): int + { + return (int)ceil(($this->indexOf(SolarDay::fromYmd($this->year->getYear(), $this->month, 1)->getWeek()->getIndex() - $start, null, 7) + $this->getDayCount()) / 7); + } + + function getName(): string + { + return self::$NAMES[$this->getIndexInYear()]; + } + + function __toString(): string + { + return sprintf('%s%s', $this->year, $this->getName()); + } + + function next(int $n): SolarMonth + { + if ($n == 0) { + return self::fromYm($this->year->getYear(), $this->month); + } + $m = $this->month + $n; + $y = $this->year->getYear() + intdiv($m, 12); + $m %= 12; + if ($m < 1) { + $m += 12; + $y--; + } + return self::fromYm($y, $m); + } + + /** + * 获取本月的公历周列表 + * + * @param int $start 星期几作为一周的开始,1234560分别代表星期一至星期天 + * @return SolarWeek[] 周列表 + */ + function getWeeks(int $start): array + { + $size = $this->getWeekCount($start); + $y = $this->year->getYear(); + $l = array(); + for ($i = 0; $i < $size; $i++) { + $l[] = SolarWeek::fromYm($y, $this->month, $i, $start); + } + return $l; + } + + /** + * 获取本月的公历日列表 + * + * @return SolarDay[] 公历日列表 + */ + function getDays(): array + { + $size = $this->getDayCount(); + $y = $this->year->getYear(); + $l = array(); + for ($i = 0; $i < $size; $i++) { + $l[] = SolarDay::fromYmd($y, $this->month, $i + 1); + } + return $l; + } +} diff --git a/src/solar/SolarSeason.php b/src/solar/SolarSeason.php new file mode 100644 index 0000000..7f0b2ca --- /dev/null +++ b/src/solar/SolarSeason.php @@ -0,0 +1,87 @@ +year = SolarYear::fromYear($year); + if ($index < 0 || $index > 1) { + throw new InvalidArgumentException(sprintf('illegal solar half year index: %d', $index)); + } + $this->index = $index; + } + + static function fromIndex(int $year, int $index): static + { + return new static($year, $index); + } + + /** + * 年 + * @return SolarYear 年 + */ + function getYear(): SolarYear + { + return $this->year; + } + + /** + * 索引 + * + * @return int 索引,0-1 + */ + function getIndex(): int + { + return $this->index; + } + + function getName(): string + { + return self::$NAMES[$this->index]; + } + + function next(int $n): static + { + $m = $this->index + $n; + return self::fromIndex($this->year->getYear() + intdiv($m, 4), abs($m % 4)); + } + + /** + * 月份列表 + * + * @return SolarMonth[] 月份列表,1年有12个月。 + */ + function getMonths(): array + { + $l = array(); + $y = $this->year->getYear(); + for ($i = 0; $i < 3; $i++) { + $l[] = SolarMonth::fromYm($y, $this->index * 3 + $i + 1); + } + return $l; + } + +} diff --git a/src/solar/SolarTerm.php b/src/solar/SolarTerm.php new file mode 100644 index 0000000..deb1b42 --- /dev/null +++ b/src/solar/SolarTerm.php @@ -0,0 +1,106 @@ +index; + } + if ($year !== null) { + $this->initByYear($year, $idx); + } else if ($cursoryJulianDay !== null) { + $this->cursoryJulianDay = $cursoryJulianDay; + } + } + + protected function initByYear(int $year, int $offset): void + { + $jd = floor(($year - 2000) * 365.2422 + 180); + // 355是2000.12冬至,得到较靠近jd的冬至估计值 + $w = floor(($jd - 355 + 183) / 365.2422) * 365.2422 + 355; + if (ShouXingUtil::calcQi($w) > $jd) { + $w -= 365.2422; + } + $this->cursoryJulianDay = ShouXingUtil::calcQi($w + 15.2184 * $offset); + } + + static function fromIndex(int $year, int $index): static + { + return new static($year, $index); + } + + static function fromName(int $year, string $name): static + { + return new static($year, null, $name); + } + + function next(int $n): SolarTerm + { + return new static(null, $this->nextIndex($n), null, $this->cursoryJulianDay + 15.2184 * $n); + } + + /** + * 是否节 + * + * @return bool true/false + */ + function isJie(): bool + { + return $this->index % 2 == 1; + } + + /** + * 是否气 + * + * @return bool true/false + */ + function isQi(): bool + { + return $this->index % 2 == 0; + } + + /** + * 儒略日 + * + * @return JulianDay 儒略日 + */ + function getJulianDay(): JulianDay + { + return JulianDay::fromJulianDay(ShouXingUtil::qiAccurate2($this->cursoryJulianDay) + JulianDay::$J2000); + } + + /** + * 粗略的儒略日 + * + * @return float 儒略日数 + */ + function getCursoryJulianDay(): float + { + return $this->cursoryJulianDay; + } + +} diff --git a/src/solar/SolarTime.php b/src/solar/SolarTime.php new file mode 100644 index 0000000..37fee14 --- /dev/null +++ b/src/solar/SolarTime.php @@ -0,0 +1,221 @@ + 23) { + throw new InvalidArgumentException(sprintf('illegal hour: %d', $hour)); + } + if ($minute < 0 || $minute > 59) { + throw new InvalidArgumentException(sprintf('illegal minute: %d', $minute)); + } + if ($second < 0 || $second > 59) { + throw new InvalidArgumentException(sprintf('illegal second: %d', $second)); + } + $this->day = SolarDay::fromYmd($year, $month, $day); + $this->hour = $hour; + $this->minute = $minute; + $this->second = $second; + } + + static function fromYmdHms(int $year, int $month, int $day, int $hour, int $minute, int $second): static + { + return new static($year, $month, $day, $hour, $minute, $second); + } + + /** + * 日 + * + * @return SolarDay 日 + */ + function getDay(): SolarDay + { + return $this->day; + } + + /** + * 时 + * + * @return int 时 + */ + function getHour(): int + { + return $this->hour; + } + + /** + * 分 + * + * @return int 分 + */ + function getMinute(): int + { + return $this->minute; + } + + /** + * 秒 + * + * @return int 秒 + */ + function getSecond(): int + { + return $this->second; + } + + function getName(): string + { + return sprintf('%02d:%02d:%02d', $this->hour, $this->minute, $this->second); + } + + function __toString(): string + { + return sprintf('%s %s', $this->day, $this->getName()); + } + + /** + * 是否在指定公历时刻之前 + * + * @param SolarTime $target 公历时刻 + * @return bool true/false + */ + function isBefore(SolarTime $target): bool + { + if (!$this->day->equals($target->getDay())) { + return $this->day->isBefore($target->getDay()); + } + $bHour = $target->getHour(); + if ($this->hour == $bHour) { + $bMinute = $target->getMinute(); + return $this->minute == $bMinute ? $this->second < $target->getSecond() : $this->minute < $bMinute; + } + return $this->hour < $bHour; + } + + /** + * 是否在指定公历时刻之后 + * + * @param SolarTime $target 公历时刻 + * @return true/false + */ + function isAfter(SolarTime $target): bool + { + if (!$this->day->equals($target->getDay())) { + return $this->day->isAfter($target->getDay()); + } + $bHour = $target->getHour(); + if ($this->hour == $bHour) { + $bMinute = $target->getMinute(); + return $this->minute == $bMinute ? $this->second > $target->getSecond() : $this->minute > $bMinute; + } + return $this->hour > $bHour; + } + + /** + * 节气 + * + * @return SolarTerm 节气 + */ + function getTerm(): SolarTerm + { + $term = SolarTerm::fromIndex($this->day->getMonth()->getYear()->getYear() + 1, 0); + while ($this->isBefore($term->getJulianDay()->getSolarTime())) { + $term = $term->next(-1); + } + return $term; + } + + /** + * 儒略日 + * + * @return JulianDay 儒略日 + */ + function getJulianDay(): JulianDay + { + $month = $this->day->getMonth(); + return JulianDay::fromYmdHms($month->getYear()->getYear(), $month->getMonth(), $this->day->getDay(), $this->hour, $this->minute, $this->second); + } + + /** + * 公历时刻相减,获得相差秒数 + * + * @param SolarTime $target 公历时刻 + * @return int 秒数 + */ + function subtract(SolarTime $target): int + { + $days = $this->day->subtract($target->getDay()); + $cs = $this->hour * 3600 + $this->minute * 60 + $this->second; + $ts = $target->getHour() * 3600 + $target->getMinute() * 60 + $target->getSecond(); + $seconds = $cs - $ts; + if ($seconds < 0) { + $seconds += 86400; + $days--; + } + $seconds += $days * 86400; + return $seconds; + } + + /** + * 推移 + * + * @param int $n 推移秒数 + * @return SolarTime 公历时刻 + */ + function next(int $n): SolarTime + { + $ts = $this->second + $n; + $tm = $this->minute + intdiv($ts, 60); + $th = $this->hour + intdiv($tm, 60); + $d = $this->day->next(intdiv($th, 24)); + $month = $d->getMonth(); + return SolarTime::fromYmdHms($month->getYear()->getYear(), $month->getMonth(), $d->getDay(), $th % 24, $tm % 60, $ts % 60); + } + + /** + * 时辰 + * + * @return LunarHour 农历时辰 + */ + function getLunarHour(): LunarHour + { + $d = $this->day->getLunarDay(); + $m = $d->getMonth(); + return LunarHour::fromYmdHms($m->getYear()->getYear(), $m->getMonthWithLeap(), $d->getDay(), $this->hour, $this->minute, $this->second); + } + +} diff --git a/src/solar/SolarWeek.php b/src/solar/SolarWeek.php new file mode 100644 index 0000000..cac758f --- /dev/null +++ b/src/solar/SolarWeek.php @@ -0,0 +1,158 @@ + 5) { + throw new InvalidArgumentException(sprintf('illegal solar week index: %d', $index)); + } + if ($start < 0 || $start > 6) { + throw new InvalidArgumentException(sprintf('illegal solar week start: %d', $start)); + } + $m = SolarMonth::fromYm($year, $month); + if ($index >= $m->getWeekCount($start)) { + throw new InvalidArgumentException(sprintf('illegal solar week index: %d in month: %s', $index, $m)); + } + $this->month = $m; + $this->index = $index; + $this->start = Week::fromIndex($start); + } + + static function fromYm(int $year, int $month, int $index, int $start): static + { + return new static($year, $month, $index, $start); + } + + /** + * 月 + * + * @return SolarMonth 月 + */ + function getMonth(): SolarMonth + { + return $this->month; + } + + /** + * 索引 + * + * @return int 索引,0-5 + */ + function getIndex(): int + { + return $this->index; + } + + /** + * 起始星期 + * + * @return Week 星期 + */ + function getStart(): Week + { + return $this->start; + } + + function getName(): string + { + return self::$NAMES[$this->index]; + } + + function __toString(): string + { + return sprintf('%s%s', $this->month, $this->getName()); + } + + function next(int $n): static + { + if ($n == 0) { + return static::fromYm($this->month->getYear()->getYear(), $this->month->getMonth(), $this->index, $this->start->getIndex()); + } + $d = $this->index + $n; + $m = $this->month; + $startIndex = $this->start->getIndex(); + $weeksInMonth = $m->getWeekCount($startIndex); + $forward = $n > 0; + $add = $forward ? 1 : -1; + while ($forward ? ($d >= $weeksInMonth) : ($d < 0)) { + if ($forward) { + $d -= $weeksInMonth; + } + if (!$forward) { + if (!SolarDay::fromYmd($m->getYear()->getYear(), $m->getMonth(), 1)->getWeek()->equals($this->start)) { + $d += $add; + } + } + $m = $m->next($add); + if ($forward) { + if (!SolarDay::fromYmd($m->getYear()->getYear(), $m->getMonth(), 1)->getWeek()->equals($this->start)) { + $d += $add; + } + } + $weeksInMonth = $m->getWeekCount($startIndex); + if (!$forward) { + $d += $weeksInMonth; + } + } + return static::fromYm($m->getYear()->getYear(), $m->getMonth(), $d, $startIndex); + } + + /** + * 本周第1天 + * + * @return SolarDay 公历日 + */ + function getFirstDay(): SolarDay + { + $m = $this->getMonth(); + $firstDay = SolarDay::fromYmd($m->getYear()->getYear(), $m->getMonth(), 1); + return $firstDay->next($this->index * 7 - $this->indexOf($firstDay->getWeek()->getIndex() - $this->start->getIndex(), null, 7)); + } + + /** + * 本周公历日列表 + * + * @return SolarDay[] 公历日列表 + */ + function getDays(): array + { + $l = array(); + $d = $this->getFirstDay(); + $l[] = $d; + for ($i = 1; $i < 7; $i++) { + $l[] = $d->next($i); + } + return $l; + } + +} diff --git a/src/solar/SolarYear.php b/src/solar/SolarYear.php new file mode 100644 index 0000000..e2acdab --- /dev/null +++ b/src/solar/SolarYear.php @@ -0,0 +1,121 @@ + 9999) { + throw new InvalidArgumentException(sprintf('illegal solar year: %d', $year)); + } + $this->year = $year; + } + + static function fromYear(int $year): static + { + return new static($year); + } + + /** + * 年 + * @return int 年 + */ + function getYear(): int + { + return $this->year; + } + + /** + * 天数(1582年355天,平年365天,闰年366天) + * + * @return int 天数 + */ + function getDayCount(): int + { + if (1582 == $this->year) { + return 355; + } + return $this->isLeap() ? 366 : 365; + } + + /** + * 是否闰年(1582年以前,使用儒略历,能被4整除即为闰年。以后采用格里历,四年一闰,百年不闰,四百年再闰。) + * + * @return bool true/false + */ + function isLeap(): bool + { + if ($this->year < 1600) { + return $this->year % 4 == 0; + } + return ($this->year % 4 == 0 && $this->year % 100 != 0) || ($this->year % 400 == 0); + } + + function getName(): string + { + return sprintf('%d年', $this->year); + } + + function next(int $n): static + { + return static::fromYear($this->year + $n); + } + + /** + * 月份列表 + * + * @return SolarMonth[] 月份列表,1年有12个月。 + */ + function getMonths(): array + { + $l = array(); + for ($i = 0; $i < 12; $i++) { + $l[] = SolarMonth::fromYm($this->year, $i + 1); + } + return $l; + } + + /** + * 季度列表 + * + * @return SolarSeason[] 季度列表,1年有4个季度。 + */ + function getSeasons(): array + { + $l = array(); + for ($i = 0; $i < 4; $i++) { + $l[] = SolarSeason::fromIndex($this->year, $i); + } + return $l; + } + + /** + * 半年列表 + * + * @return SolarHalfYear[] 半年列表,1年有2个半年。 + */ + function getHalfYears(): array + { + $l = array(); + for ($i = 0; $i < 2; $i++) { + $l[] = SolarHalfYear::fromIndex($this->year, $i); + } + return $l; + } + +} diff --git a/src/util/ShouXingUtil.php b/src/util/ShouXingUtil.php new file mode 100644 index 0000000..925a482 --- /dev/null +++ b/src/util/ShouXingUtil.php @@ -0,0 +1,678 @@ + $n2) { + $m = $n2; + } + } + $c = 0; + for ($j = $n1; $j < $m; $j += 3) { + $c += self::$XL0[$j] * cos(self::$XL0[$j + 1] + $t * self::$XL0[$j + 2]); + } + $v += $c * $tn; + } + $v /= self::$XL0[0]; + $t2 = $t * $t; + $v += (-0.0728 - 2.7702 * $t - 1.1019 * $t2 - 0.0996 * $t2 * $t) / self::$SECOND_PER_RAD; + return $v; + } + + static function mLon($t, $n): float + { + $ob = self::$XL1; + $obl = count($ob[0]); + $tn = 1; + $v = 0; + $t2 = $t * $t; + $t3 = $t2 * $t; + $t4 = $t3 * $t; + $t5 = $t4 * $t; + $tx = $t - 10; + $v += (3.81034409 + 8399.684730072 * $t - 3.319e-05 * $t2 + 3.11e-08 * $t3 - 2.033e-10 * $t4) * self::$SECOND_PER_RAD; + $v += 5028.792262 * $t + 1.1124406 * $t2 + 0.00007699 * $t3 - 0.000023479 * $t4 - 0.0000000178 * $t5; + if ($tx > 0) { + $v += -0.866 + 1.43 * $tx + 0.054 * $tx * $tx; + } + $t2 /= 1e4; + $t3 /= 1e8; + $t4 /= 1e8; + + $n *= 6; + if ($n < 0) { + $n = $obl; + } + for ($i = 0, $x = count($ob); $i < $x; $i++, $tn *= $t) { + $f = $ob[$i]; + $l = count($f); + $m = (int)($n * $l / $obl + 0.5); + if ($i > 0) { + $m += 6; + } + if ($m >= $l) { + $m = $l; + } + for ($j = 0, $c = 0; $j < $m; $j += 6) { + $c += $f[$j] * cos($f[$j + 1] + $t * $f[$j + 2] + $t2 * $f[$j + 3] + $t3 * $f[$j + 4] + $t4 * $f[$j + 5]); + } + $v += $c * $tn; + } + $v /= self::$SECOND_PER_RAD; + return $v; + } + + static function gxcSunLon($t): float + { + $t2 = $t * $t; + $v = -0.043126 + 628.301955 * $t - 0.000002732 * $t2; + $e = 0.016708634 - 0.000042037 * $t - 0.0000001267 * $t2; + return -20.49552 * (1 + $e * cos($v)) / self::$SECOND_PER_RAD; + } + + static function ev($t): float + { + $f = 628.307585 * $t; + return 628.332 + 21 * sin(1.527 + $f) + 0.44 * sin(1.48 + $f * 2) + 0.129 * sin(5.82 + $f) * $t + 0.00055 * sin(4.21 + $f) * $t * $t; + } + + static function saLon($t, $n) + { + return self::eLon($t, $n) + self::nutationLon2($t) + self::gxcSunLon($t) + M_PI; + } + + static function dtExt($y, $jsd): float + { + $dy = ($y - 1820) / 100; + return -20 + $jsd * $dy * $dy; + } + + static function dtCalc($y) + { + $size = count(self::$DT_AT); + $y0 = self::$DT_AT[$size - 2]; + $t0 = self::$DT_AT[$size - 1]; + if ($y >= $y0) { + $jsd = 31; + if ($y > $y0 + 100) { + return self::dtExt($y, $jsd); + } + return self::dtExt($y, $jsd) - (self::dtExt($y0, $jsd) - $t0) * ($y0 + 100 - $y) / 100; + } + for ($i = 0; $i < $size; $i += 5) { + if ($y < self::$DT_AT[$i + 5]) { + break; + } + } + $t1 = ($y - self::$DT_AT[$i]) / (self::$DT_AT[$i + 5] - self::$DT_AT[$i]) * 10; + $t2 = $t1 * $t1; + $t3 = $t2 * $t1; + return self::$DT_AT[$i + 1] + self::$DT_AT[$i + 2] * $t1 + self::$DT_AT[$i + 3] * $t2 + self::$DT_AT[$i + 4] * $t3; + } + + static function dtT($t): float + { + return self::dtCalc($t / 365.2425 + 2000) / self::$SECOND_PER_DAY; + } + + static function mv($t): float + { + $v = 8399.71 - 914 * sin(0.7848 + 8328.691425 * $t + 0.0001523 * $t * $t); + $v -= 179 * sin(2.543 + 15542.7543 * $t) + 160 * sin(0.1874 + 7214.0629 * $t) + 62 * sin(3.14 + 16657.3828 * $t) + 34 * sin(4.827 + 16866.9323 * $t) + 22 * sin(4.9 + 23871.4457 * $t) + 12 * sin(2.59 + 14914.4523 * $t) + 7 * sin(0.23 + 6585.7609 * $t) + 5 * sin(0.9 + 25195.624 * $t) + 5 * sin(2.32 - 7700.3895 * $t) + 5 * sin(3.88 + 8956.9934 * $t) + 5 * sin(0.49 + 7771.3771 * $t); + return $v; + } + + static function saLonT($w): float + { + $v = 628.3319653318; + $t = ($w - 1.75347 - M_PI) / $v; + $v = self::ev($t); + $t += ($w - self::saLon($t, 10)) / $v; + $v = self::ev($t); + $t += ($w - self::saLon($t, -1)) / $v; + return $t; + } + + static function saLonT2($w): float + { + $v = 628.3319653318; + $t = ($w - 1.75347 - M_PI) / $v; + $t -= (0.000005297 * $t * $t + 0.0334166 * cos(4.669257 + 628.307585 * $t) + 0.0002061 * cos(2.67823 + 628.307585 * $t) * $t) / $v; + $t += ($w - self::eLon($t, 8) - M_PI + (20.5 + 17.2 * sin(2.1824 - 33.75705 * $t)) / self::$SECOND_PER_RAD) / $v; + return $t; + } + + static function msaLon($t, $mn, $sn): float + { + return self::mLon($t, $mn) + (-3.4E-6) - (self::eLon($t, $sn) + self::gxcSunLon($t) + M_PI); + } + + static function msaLonT($w): float + { + $v = 7771.37714500204; + $t = ($w + 1.08472) / $v; + $t += ($w - self::msaLon($t, 3, 3)) / $v; + $v = self::mv($t) - self::ev($t); + $t += ($w - self::msaLon($t, 20, 10)) / $v; + $t += ($w - self::msaLon($t, -1, 60)) / $v; + return $t; + } + + static function msaLonT2($w): float + { + $v = 7771.37714500204; + $t = ($w + 1.08472) / $v; + $t2 = $t * $t; + $t -= (-0.00003309 * $t2 + 0.10976 * cos(0.784758 + 8328.6914246 * $t + 0.000152292 * $t2) + 0.02224 * cos(0.18740 + 7214.0628654 * $t - 0.00021848 * $t2) - 0.03342 * cos(4.669257 + 628.307585 * $t)) / $v; + $l = self::mLon($t, 20) - (4.8950632 + 628.3319653318 * $t + 0.000005297 * $t * $t + 0.0334166 * cos(4.669257 + 628.307585 * $t) + 0.0002061 * cos(2.67823 + 628.307585 * $t) * $t + 0.000349 * cos(4.6261 + 1256.61517 * $t) - 20.5 / self::$SECOND_PER_RAD); + $v = 7771.38 - 914 * sin(0.7848 + 8328.691425 * $t + 0.0001523 * $t * $t) - 179 * sin(2.543 + 15542.7543 * $t) - 160 * sin(0.1874 + 7214.0629 * $t); + $t += ($w - $l) / $v; + return $t; + } + + static function qiHigh($w): float + { + $t = self::saLonT2($w) * 36525; + $t = $t - self::dtT($t) + self::$ONE_THIRD; + $v = ((int)($t + 0.5) % 1) * self::$SECOND_PER_DAY; + if ($v < 1200 || $v > self::$SECOND_PER_DAY - 1200) { + $t = self::saLonT($w) * 36525 - self::dtT($t) + self::$ONE_THIRD; + } + return $t; + } + + static function shuoHigh($w): float + { + $t = self::msaLonT2($w) * 36525; + $t = $t - self::dtT($t) + self::$ONE_THIRD; + $v = ((int)($t + 0.5) % 1) * self::$SECOND_PER_DAY; + if ($v < 1800 || $v > self::$SECOND_PER_DAY - 1800) { + $t = self::msaLont($w) * 36525 - self::dtT($t) + self::$ONE_THIRD; + } + return $t; + } + + static function qiLow($w): float + { + $v = 628.3319653318; + $t = ($w - 4.895062166) / $v; + $t -= (53 * $t * $t + 334116 * cos(4.67 + 628.307585 * $t) + 2061 * cos(2.678 + 628.3076 * $t) * $t) / $v / 10000000; + $n = 48950621.66 + 6283319653.318 * $t + 53 * $t * $t + 334166 * cos(4.669257 + 628.307585 * $t) + 3489 * cos(4.6261 + 1256.61517 * $t) + 2060.6 * cos(2.67823 + 628.307585 * $t) * $t - 994 - 834 * sin(2.1824 - 33.75705 * $t); + $t -= ($n / 10000000 - $w) / 628.332 + (32 * ($t + 1.8) * ($t + 1.8) - 20) / self::$SECOND_PER_DAY / 36525; + return $t * 36525 + self::$ONE_THIRD; + } + + static function shuoLow($w): float + { + $v = 7771.37714500204; + $t = ($w + 1.08472) / $v; + $t -= (-0.0000331 * $t * $t + 0.10976 * cos(0.785 + 8328.6914 * $t) + 0.02224 * cos(0.187 + 7214.0629 * $t) - 0.03342 * cos(4.669 + 628.3076 * $t)) / $v + (32 * ($t + 1.8) * ($t + 1.8) - 20) / self::$SECOND_PER_DAY / 36525; + return $t * 36525 + self::$ONE_THIRD; + } + + static function calcShuo($jd): float + { + if (null == self::$SB) { + self::$SB = self::decode('EqoFscDcrFpmEsF2DfFideFelFpFfFfFiaipqti1ksttikptikqckstekqttgkqttgkqteksttikptikq2fjstgjqttjkqttgkqtekstfkptikq2tijstgjiFkirFsAeACoFsiDaDiADc1AFbBfgdfikijFifegF1FhaikgFag1E2btaieeibggiffdeigFfqDfaiBkF1kEaikhkigeidhhdiegcFfakF1ggkidbiaedksaFffckekidhhdhdikcikiakicjF1deedFhFccgicdekgiFbiaikcfi1kbFibefgEgFdcFkFeFkdcfkF1kfkcickEiFkDacFiEfbiaejcFfffkhkdgkaiei1ehigikhdFikfckF1dhhdikcfgjikhfjicjicgiehdikcikggcifgiejF1jkieFhegikggcikFegiegkfjebhigikggcikdgkaFkijcfkcikfkcifikiggkaeeigefkcdfcfkhkdgkegieidhijcFfakhfgeidieidiegikhfkfckfcjbdehdikggikgkfkicjicjF1dbidikFiggcifgiejkiegkigcdiegfggcikdbgfgefjF1kfegikggcikdgFkeeijcfkcikfkekcikdgkabhkFikaffcfkhkdgkegbiaekfkiakicjhfgqdq2fkiakgkfkhfkfcjiekgFebicggbedF1jikejbbbiakgbgkacgiejkijjgigfiakggfggcibFifjefjF1kfekdgjcibFeFkijcfkfhkfkeaieigekgbhkfikidfcjeaibgekgdkiffiffkiakF1jhbakgdki1dj1ikfkicjicjieeFkgdkicggkighdF1jfgkgfgbdkicggfggkidFkiekgijkeigfiskiggfaidheigF1jekijcikickiggkidhhdbgcfkFikikhkigeidieFikggikhkffaffijhidhhakgdkhkijF1kiakF1kfheakgdkifiggkigicjiejkieedikgdfcggkigieeiejfgkgkigbgikicggkiaideeijkefjeijikhkiggkiaidheigcikaikffikijgkiahi1hhdikgjfifaakekighie1hiaikggikhkffakicjhiahaikggikhkijF1kfejfeFhidikggiffiggkigicjiekgieeigikggiffiggkidheigkgfjkeigiegikifiggkidhedeijcfkFikikhkiggkidhh1ehigcikaffkhkiggkidhh1hhigikekfiFkFikcidhh1hitcikggikhkfkicjicghiediaikggikhkijbjfejfeFhaikggifikiggkigiejkikgkgieeigikggiffiggkigieeigekijcijikggifikiggkideedeijkefkfckikhkiggkidhh1ehijcikaffkhkiggkidhh1hhigikhkikFikfckcidhh1hiaikgjikhfjicjicgiehdikcikggifikigiejfejkieFhegikggifikiggfghigkfjeijkhigikggifikiggkigieeijcijcikfksikifikiggkidehdeijcfdckikhkiggkhghh1ehijikifffffkhsFngErD1pAfBoDd1BlEtFqA2AqoEpDqElAEsEeB2BmADlDkqBtC1FnEpDqnEmFsFsAFnllBbFmDsDiCtDmAB2BmtCgpEplCpAEiBiEoFqFtEqsDcCnFtADnFlEgdkEgmEtEsCtDmADqFtAFrAtEcCqAE1BoFqC1F1DrFtBmFtAC2ACnFaoCgADcADcCcFfoFtDlAFgmFqBq2bpEoAEmkqnEeCtAE1bAEqgDfFfCrgEcBrACfAAABqAAB1AAClEnFeCtCgAADqDoBmtAAACbFiAAADsEtBqAB2FsDqpFqEmFsCeDtFlCeDtoEpClEqAAFrAFoCgFmFsFqEnAEcCqFeCtFtEnAEeFtAAEkFnErAABbFkADnAAeCtFeAfBoAEpFtAABtFqAApDcCGJ'); + } + $size = count(self::$SHUO_KB); + $d = 0; + $pc = 14; + $jd += 2451545; + $f1 = self::$SHUO_KB[0] - $pc; + $f2 = self::$SHUO_KB[$size - 1] - $pc; + $f3 = 2436935; + if ($jd < $f1 || $jd >= $f3) { + $d = floor(self::shuoHigh(floor(($jd + $pc - 2451551) / 29.5306) * M_PI * 2) + 0.5); + } else if ($jd >= $f1 && $jd < $f2) { + for ($i = 0; $i < $size; $i += 2) { + if ($jd + $pc < self::$SHUO_KB[$i + 2]) { + break; + } + } + $d = self::$SHUO_KB[$i] + self::$SHUO_KB[$i + 1] * floor(($jd + $pc - self::$SHUO_KB[$i]) / self::$SHUO_KB[$i + 1]); + $d = floor($d + 0.5); + if ($d == 1683460) { + $d++; + } + $d -= 2451545; + } else if ($jd >= $f2) { + $d = floor(self::shuoLow(floor(($jd + $pc - 2451551) / 29.5306) * M_PI * 2) + 0.5); + $from = (int)(($jd - $f2) / 29.5306); + $n = substr(self::$SB, $from, 1); + if (strcmp('1', $n) == 0) { + $d += 1; + } elseif (strcmp('2', $n) == 0) { + $d -= 1; + } + } + return $d; + } + + static function calcQi($jd): float + { + if (null == self::$QB) { + self::$QB = self::decode('FrcFs22AFsckF2tsDtFqEtF1posFdFgiFseFtmelpsEfhkF2anmelpFlF1ikrotcnEqEq2FfqmcDsrFor22FgFrcgDscFs22FgEeFtE2sfFs22sCoEsaF2tsD1FpeE2eFsssEciFsFnmelpFcFhkF2tcnEqEpFgkrotcnEqrEtFermcDsrE222FgBmcmr22DaEfnaF222sD1FpeForeF2tssEfiFpEoeFssD1iFstEqFppDgFstcnEqEpFg11FscnEqrAoAF2ClAEsDmDtCtBaDlAFbAEpAAAAAD2FgBiBqoBbnBaBoAAAAAAAEgDqAdBqAFrBaBoACdAAf1AACgAAAeBbCamDgEifAE2AABa1C1BgFdiAAACoCeE1ADiEifDaAEqAAFe1AcFbcAAAAAF1iFaAAACpACmFmAAAAAAAACrDaAAADG0'); + } + $size = count(self::$QI_KB); + $d = 0; + $pc = 7; + $jd += 2451545; + $f1 = self::$QI_KB[0] - $pc; + $f2 = self::$QI_KB[$size - 1] - $pc; + $f3 = 2436935; + if ($jd < $f1 || $jd >= $f3) { + $d = floor(self::qiHigh(floor(($jd + $pc - 2451259) / self::$DAY_PER_YEAR * 24) * M_PI / 12) + 0.5); + } else if ($jd >= $f1 && $jd < $f2) { + for ($i = 0; $i < $size; $i += 2) { + if ($jd + $pc < self::$QI_KB[$i + 2]) { + break; + } + } + $d = self::$QI_KB[$i] + self::$QI_KB[$i + 1] * floor(($jd + $pc - self::$QI_KB[$i]) / self::$QI_KB[$i + 1]); + $d = floor($d + 0.5); + if ($d == 1683460) { + $d++; + } + $d -= 2451545; + } else if ($jd >= $f2) { + $d = floor(self::qiLow(floor(($jd + $pc - 2451259) / self::$DAY_PER_YEAR * 24) * M_PI / 12) + 0.5); + $from = (int)(($jd - $f2) / self::$DAY_PER_YEAR * 24); + $n = substr(self::$QB, $from, 1); + if (strcmp('1', $n) == 0) { + $d += 1; + } elseif (strcmp('2', $n) == 0) { + $d -= 1; + } + } + return $d; + } + + static function qiAccurate($w): float + { + $t = self::saLonT($w) * 36525; + return $t - self::dtT($t) + self::$ONE_THIRD; + } + + static function qiAccurate2($jd): float + { + $d = M_PI / 12; + $w = floor(($jd + 293) / self::$DAY_PER_YEAR * 24) * $d; + $a = self::qiAccurate($w); + if ($a - $jd > 5) { + return self::qiAccurate($w - $d); + } + if ($a - $jd < -5) { + return self::qiAccurate($w + $d); + } + return $a; + } + +} diff --git a/test/ConstellationTest.php b/test/ConstellationTest.php new file mode 100644 index 0000000..99f53e2 --- /dev/null +++ b/test/ConstellationTest.php @@ -0,0 +1,35 @@ +assertEquals('白羊', SolarDay::fromYmd(2020, 3, 21)->getConstellation()->getName()); + $this->assertEquals('白羊', SolarDay::fromYmd(2020, 4, 19)->getConstellation()->getName()); + } + + function test1() + { + $this->assertEquals('金牛', SolarDay::fromYmd(2020, 4, 20)->getConstellation()->getName()); + $this->assertEquals('金牛', SolarDay::fromYmd(2020, 5, 20)->getConstellation()->getName()); + } + + function test2() + { + $this->assertEquals('双子', SolarDay::fromYmd(2020, 5, 21)->getConstellation()->getName()); + $this->assertEquals('双子', SolarDay::fromYmd(2020, 6, 21)->getConstellation()->getName()); + } + + function test3() + { + $this->assertEquals('巨蟹', SolarDay::fromYmd(2020, 6, 22)->getConstellation()->getName()); + $this->assertEquals('巨蟹', SolarDay::fromYmd(2020, 7, 22)->getConstellation()->getName()); + } +} diff --git a/test/DirectionTest.php b/test/DirectionTest.php new file mode 100644 index 0000000..a6634f9 --- /dev/null +++ b/test/DirectionTest.php @@ -0,0 +1,36 @@ +assertEquals('东南', SolarDay::fromYmd(2021, 11, 13)->getLunarDay()->getSixtyCycle()->getHeavenStem()->getMascotDirection()->getName()); + } + + /** + * 福神方位 + */ + function test2() + { + $this->assertEquals('东南', SolarDay::fromYmd(2024, 1, 1)->getLunarDay()->getSixtyCycle()->getHeavenStem()->getMascotDirection()->getName()); + } + + /** + * 太岁方位 + */ + function test3() + { + $this->assertEquals('东', SolarDay::fromYmd(2023, 11, 6)->getLunarDay()->getJupiterDirection()->getName()); + } + +} diff --git a/test/DogDayTest.php b/test/DogDayTest.php new file mode 100644 index 0000000..7aaf210 --- /dev/null +++ b/test/DogDayTest.php @@ -0,0 +1,103 @@ +getDogDay(); + $this->assertEquals('初伏', $d->getName()); + $this->assertEquals('初伏', $d->getDog()->__toString()); + $this->assertEquals('初伏第1天', $d->__toString()); + } + + + function test1() + { + $d = SolarDay::fromYmd(2011, 7, 23)->getDogDay(); + $this->assertEquals('初伏', $d->getName()); + $this->assertEquals('初伏', $d->getDog()->__toString()); + $this->assertEquals('初伏第10天', $d->__toString()); + } + + + function test2() + { + $d = SolarDay::fromYmd(2011, 7, 24)->getDogDay(); + $this->assertEquals('中伏', $d->getName()); + $this->assertEquals('中伏', $d->getDog()->__toString()); + $this->assertEquals('中伏第1天', $d->__toString()); + } + + + function test3() + { + $d = SolarDay::fromYmd(2011, 8, 12)->getDogDay(); + $this->assertEquals('中伏', $d->getName()); + $this->assertEquals('中伏', $d->getDog()->__toString()); + $this->assertEquals('中伏第20天', $d->__toString()); + } + + + function test4() + { + $d = SolarDay::fromYmd(2011, 8, 13)->getDogDay(); + $this->assertEquals('末伏', $d->getName()); + $this->assertEquals('末伏', $d->getDog()->__toString()); + $this->assertEquals('末伏第1天', $d->__toString()); + } + + + function test5() + { + $d = SolarDay::fromYmd(2011, 8, 22)->getDogDay(); + $this->assertEquals('末伏', $d->getName()); + $this->assertEquals('末伏', $d->getDog()->__toString()); + $this->assertEquals('末伏第10天', $d->__toString()); + } + + + function test6() + { + $this->assertNull(SolarDay::fromYmd(2011, 7, 13)->getDogDay()); + } + + + function test7() + { + $this->assertNull(SolarDay::fromYmd(2011, 8, 23)->getDogDay()); + } + + + function test8() + { + $d = SolarDay::fromYmd(2012, 7, 18)->getDogDay(); + $this->assertEquals('初伏', $d->getName()); + $this->assertEquals('初伏', $d->getDog()->__toString()); + $this->assertEquals('初伏第1天', $d->__toString()); + } + + + function test9() + { + $d = SolarDay::fromYmd(2012, 8, 5)->getDogDay(); + $this->assertEquals('中伏', $d->getName()); + $this->assertEquals('中伏', $d->getDog()->__toString()); + $this->assertEquals('中伏第9天', $d->__toString()); + } + + + function test10() + { + $d = SolarDay::fromYmd(2012, 8, 8)->getDogDay(); + $this->assertEquals('末伏', $d->getName()); + $this->assertEquals('末伏', $d->getDog()->__toString()); + $this->assertEquals('末伏第2天', $d->__toString()); + } +} diff --git a/test/DutyTest.php b/test/DutyTest.php new file mode 100644 index 0000000..ef9e0ed --- /dev/null +++ b/test/DutyTest.php @@ -0,0 +1,31 @@ +assertEquals('闭', SolarDay::fromYmd(2023, 10, 30)->getLunarDay()->getDuty()->getName()); + } + + function test1() + { + $this->assertEquals('建', SolarDay::fromYmd(2023, 10, 19)->getLunarDay()->getDuty()->getName()); + } + + function test2() + { + $this->assertEquals('除', SolarDay::fromYmd(2023, 10, 7)->getLunarDay()->getDuty()->getName()); + } + + function test3() + { + $this->assertEquals('除', SolarDay::fromYmd(2023, 10, 8)->getLunarDay()->getDuty()->getName()); + } +} diff --git a/test/EarthlyBranchTest.php b/test/EarthlyBranchTest.php new file mode 100644 index 0000000..8fb03d3 --- /dev/null +++ b/test/EarthlyBranchTest.php @@ -0,0 +1,21 @@ +assertEquals('子', EarthBranch::fromIndex(0)->getName()); + } + + function test1() + { + $this->assertEquals(0, EarthBranch::fromName('子')->getIndex()); + } +} diff --git a/test/EclipticTest.php b/test/EclipticTest.php new file mode 100644 index 0000000..68f0e24 --- /dev/null +++ b/test/EclipticTest.php @@ -0,0 +1,43 @@ +getLunarDay()->getTwelveStar(); + $this->assertEquals('天德', $star->getName()); + $this->assertEquals('黄道', $star->getEcliptic()->getName()); + $this->assertEquals('吉', $star->getEcliptic()->getLuck()->getName()); + } + + function test1() + { + $star = SolarDay::fromYmd(2023, 10, 19)->getLunarDay()->getTwelveStar(); + $this->assertEquals('白虎', $star->getName()); + $this->assertEquals('黑道', $star->getEcliptic()->getName()); + $this->assertEquals('凶', $star->getEcliptic()->getLuck()->getName()); + } + + function test2() + { + $star = SolarDay::fromYmd(2023, 10, 7)->getLunarDay()->getTwelveStar(); + $this->assertEquals('天牢', $star->getName()); + $this->assertEquals('黑道', $star->getEcliptic()->getName()); + $this->assertEquals('凶', $star->getEcliptic()->getLuck()->getName()); + } + + function test3() + { + $star = SolarDay::fromYmd(2023, 10, 8)->getLunarDay()->getTwelveStar(); + $this->assertEquals('玉堂', $star->getName()); + $this->assertEquals('黄道', $star->getEcliptic()->getName()); + $this->assertEquals('吉', $star->getEcliptic()->getLuck()->getName()); + } +} diff --git a/test/EightCharTest.php b/test/EightCharTest.php new file mode 100644 index 0000000..bf9b05a --- /dev/null +++ b/test/EightCharTest.php @@ -0,0 +1,496 @@ +getYear(); + // 月柱 + $month = $eightChar->getMonth(); + // 日柱 + $day = $eightChar->getDay(); + // 时柱 + $hour = $eightChar->getHour(); + + // 日元(日主、日干) + $me = $day->getHeavenStem(); + + // 年柱天干十神 + $this->assertEquals('正财', $me->getTenStar($year->getHeavenStem())->getName()); + // 月柱天干十神 + $this->assertEquals('比肩', $me->getTenStar($month->getHeavenStem())->getName()); + // 时柱天干十神 + $this->assertEquals('七杀', $me->getTenStar($hour->getHeavenStem())->getName()); + + // 年柱地支十神(本气) + $this->assertEquals('伤官', $me->getTenStar($year->getEarthBranch()->getHideHeavenStemMain())->getName()); + // 年柱地支十神(中气) + $this->assertEquals('正财', $me->getTenStar($year->getEarthBranch()->getHideHeavenStemMiddle())->getName()); + // 年柱地支十神(余气) + $this->assertEquals('正官', $me->getTenStar($year->getEarthBranch()->getHideHeavenStemResidual())->getName()); + + // 日柱地支十神(本气) + $this->assertEquals('偏印', $me->getTenStar($day->getEarthBranch()->getHideHeavenStemMain())->getName()); + // 日柱地支藏干(中气) + $this->assertNull($day->getEarthBranch()->getHideHeavenStemMiddle()); + // 日柱地支藏干(余气) + $this->assertNull($day->getEarthBranch()->getHideHeavenStemResidual()); + + // 指定任意天干的十神 + $this->assertEquals('正财', $me->getTenStar(HeavenStem::fromName('丙'))->getName()); + } + + /** + * 地势(长生十二神) + */ + function test2() + { + // 八字 + $eightChar = new EightChar( + SixtyCycle::fromName('丙寅'), + SixtyCycle::fromName('癸巳'), + SixtyCycle::fromName('癸酉'), + SixtyCycle::fromName('己未') + ); + + // 年柱 + $year = $eightChar->getYear(); + // 月柱 + $month = $eightChar->getMonth(); + // 日柱 + $day = $eightChar->getDay(); + // 时柱 + $hour = $eightChar->getHour(); + + // 日元(日主、日干) + $me = $day->getHeavenStem(); + + // 年柱地势 + $this->assertEquals('沐浴', $me->getTerrain($year->getEarthBranch())->getName()); + // 月柱地势 + $this->assertEquals('胎', $me->getTerrain($month->getEarthBranch())->getName()); + // 日柱地势 + $this->assertEquals('病', $me->getTerrain($day->getEarthBranch())->getName()); + // 时柱地势 + $this->assertEquals('墓', $me->getTerrain($hour->getEarthBranch())->getName()); + } + + /** + * 胎元/胎息/命宫 + */ + function test3() + { + // 八字 + $eightChar = new EightChar( + SixtyCycle::fromName('癸卯'), + SixtyCycle::fromName('辛酉'), + SixtyCycle::fromName('己亥'), + SixtyCycle::fromName('癸酉') + ); + + // 胎元 + $taiYuan = $eightChar->getFetalOrigin(); + $this->assertEquals('壬子', $taiYuan->getName()); + // 胎元纳音 + $this->assertEquals('桑柘木', $taiYuan->getSound()->getName()); + } + + /** + * 胎息 + */ + function test4() + { + // 八字 + $eightChar = new EightChar( + SixtyCycle::fromName('癸卯'), + SixtyCycle::fromName('辛酉'), + SixtyCycle::fromName('己亥'), + SixtyCycle::fromName('癸酉') + ); + + // 胎息 + $taiXi = $eightChar->getFetalBreath(); + $this->assertEquals('甲寅', $taiXi->getName()); + // 胎息纳音 + $this->assertEquals('大溪水', $taiXi->getSound()->getName()); + } + + /** + * 命宫 + */ + function test5() + { + // 八字 + $eightChar = new EightChar( + SixtyCycle::fromName('癸卯'), + SixtyCycle::fromName('辛酉'), + SixtyCycle::fromName('己亥'), + SixtyCycle::fromName('癸酉') + ); + + // 命宫 + $mingGong = $eightChar->getOwnSign(); + $this->assertEquals('癸亥', $mingGong->getName()); + // 命宫纳音 + $this->assertEquals('大海水', $mingGong->getSound()->getName()); + } + + /** + * 身宫 + */ + function test6() + { + // 八字 + $eightChar = new EightChar( + SixtyCycle::fromName('癸卯'), + SixtyCycle::fromName('辛酉'), + SixtyCycle::fromName('己亥'), + SixtyCycle::fromName('癸酉') + ); + + // 身宫 + $shenGong = $eightChar->getBodySign(); + $this->assertEquals('己未', $shenGong->getName()); + // 身宫纳音 + $this->assertEquals('天上火', $shenGong->getSound()->getName()); + } + + /** + * 地势(长生十二神) + */ + function test7() + { + // 八字 + $eightChar = new EightChar( + SixtyCycle::fromName('乙酉'), + SixtyCycle::fromName('戊子'), + SixtyCycle::fromName('辛巳'), + SixtyCycle::fromName('壬辰') + ); + + // 日干 + $me = $eightChar->getDay()->getHeavenStem(); + // 年柱地势 + $this->assertEquals('临官', $me->getTerrain($eightChar->getYear()->getEarthBranch())->getName()); + // 月柱地势 + $this->assertEquals('长生', $me->getTerrain($eightChar->getMonth()->getEarthBranch())->getName()); + // 日柱地势 + $this->assertEquals('死', $me->getTerrain($eightChar->getDay()->getEarthBranch())->getName()); + // 时柱地势 + $this->assertEquals('墓', $me->getTerrain($eightChar->getHour()->getEarthBranch())->getName()); + } + + /** + * 公历时刻转八字 + */ + function test8() + { + $eightChar = SolarTime::fromYmdHms(2005, 12, 23, 8, 37, 0)->getLunarHour()->getEightChar(); + $this->assertEquals('乙酉', $eightChar->getYear()->getName()); + $this->assertEquals('戊子', $eightChar->getMonth()->getName()); + $this->assertEquals('辛巳', $eightChar->getDay()->getName()); + $this->assertEquals('壬辰', $eightChar->getHour()->getName()); + } + + function test9() + { + $eightChar = SolarTime::fromYmdHms(1988, 2, 15, 23, 30, 0)->getLunarHour()->getEightChar(); + $this->assertEquals('戊辰', $eightChar->getYear()->getName()); + $this->assertEquals('甲寅', $eightChar->getMonth()->getName()); + $this->assertEquals('辛丑', $eightChar->getDay()->getName()); + $this->assertEquals('戊子', $eightChar->getHour()->getName()); + } + + /** + * 童限测试 + */ + function test11() + { + $childLimit = ChildLimit::fromSolarTime(SolarTime::fromYmdHms(2022, 3, 9, 20, 51, 0), Gender::MAN); + $this->assertEquals(8, $childLimit->getYearCount()); + $this->assertEquals(9, $childLimit->getMonthCount()); + $this->assertEquals(2, $childLimit->getDayCount()); + $this->assertEquals(10, $childLimit->getHourCount()); + $this->assertEquals(26, $childLimit->getMinuteCount()); + $this->assertEquals('2030年12月12日 07:17:00', $childLimit->getEndTime()->__toString()); + } + + /** + * 童限测试 + */ + function test12() + { + $childLimit = ChildLimit::fromSolarTime(SolarTime::fromYmdHms(2018, 6, 11, 9, 30, 0), Gender::WOMAN); + $this->assertEquals(1, $childLimit->getYearCount()); + $this->assertEquals(9, $childLimit->getMonthCount()); + $this->assertEquals(10, $childLimit->getDayCount()); + $this->assertEquals(1, $childLimit->getHourCount()); + $this->assertEquals(42, $childLimit->getMinuteCount()); + $this->assertEquals('2020年3月21日 11:12:00', $childLimit->getEndTime()->__toString()); + } + + /** + * 大运测试 + */ + function test13() + { + // 童限 + $childLimit = ChildLimit::fromSolarTime(SolarTime::fromYmdHms(1983, 2, 15, 20, 0, 0), Gender::WOMAN); + // 八字 + $this->assertEquals('癸亥 甲寅 甲戌 甲戌', $childLimit->getEightChar()->__toString()); + // 童限年数 + $this->assertEquals(6, $childLimit->getYearCount()); + // 童限月数 + $this->assertEquals(2, $childLimit->getMonthCount()); + // 童限日数 + $this->assertEquals(18, $childLimit->getDayCount()); + // 童限结束(即开始起运)的公历时刻 + $this->assertEquals('1989年5月4日 18:24:00', $childLimit->getEndTime()->__toString()); + // 童限开始(即出生)的农历年干支 + $this->assertEquals('癸亥', $childLimit->getStartTime()->getLunarHour()->getDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + // 童限结束(即开始起运)的农历年干支 + $this->assertEquals('己巳', $childLimit->getEndTime()->getLunarHour()->getDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + + // 第1轮大运 + $decadeFortune = $childLimit->getStartDecadeFortune(); + // 开始年龄 + $this->assertEquals(7, $decadeFortune->getStartAge()); + // 结束年龄 + $this->assertEquals(16, $decadeFortune->getEndAge()); + // 开始年 + $this->assertEquals(1989, $decadeFortune->getStartLunarYear()->getYear()); + // 结束年 + $this->assertEquals(1998, $decadeFortune->getEndLunarYear()->getYear()); + // 干支 + $this->assertEquals('乙卯', $decadeFortune->getName()); + // 下一大运 + $this->assertEquals('丙辰', $decadeFortune->next(1)->getName()); + // 上一大运 + $this->assertEquals('甲寅', $decadeFortune->next(-1)->getName()); + // 第9轮大运 + $this->assertEquals('癸亥', $decadeFortune->next(8)->getName()); + + // 小运 + $fortune = $childLimit->getStartFortune(); + // 年龄 + $this->assertEquals(7, $fortune->getAge()); + // 农历年 + $this->assertEquals(1989, $fortune->getLunarYear()->getYear()); + // 干支 + $this->assertEquals('辛巳', $fortune->getName()); + + // 流年 + $this->assertEquals('己巳', $fortune->getLunarYear()->getSixtyCycle()->getName()); + } + + function test14() + { + // 童限 + $childLimit = ChildLimit::fromSolarTime(SolarTime::fromYmdHms(1992, 2, 2, 12, 0, 0), Gender::MAN); + // 八字 + $this->assertEquals('辛未 辛丑 戊申 戊午', $childLimit->getEightChar()->__toString()); + // 童限年数 + $this->assertEquals(9, $childLimit->getYearCount()); + // 童限月数 + $this->assertEquals(0, $childLimit->getMonthCount()); + // 童限日数 + $this->assertEquals(9, $childLimit->getDayCount()); + // 童限结束(即开始起运)的公历时刻 + $this->assertEquals('2001年2月11日 18:58:00', $childLimit->getEndTime()->__toString()); + // 童限开始(即出生)的农历年干支 + $this->assertEquals('辛未', $childLimit->getStartTime()->getLunarHour()->getDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + // 童限结束(即开始起运)的农历年干支 + $this->assertEquals('辛巳', $childLimit->getEndTime()->getLunarHour()->getDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + + // 第1轮大运 + $decadeFortune = $childLimit->getStartDecadeFortune(); + // 开始年龄 + $this->assertEquals(10, $decadeFortune->getStartAge()); + // 结束年龄 + $this->assertEquals(19, $decadeFortune->getEndAge()); + // 开始年 + $this->assertEquals(2001, $decadeFortune->getStartLunarYear()->getYear()); + // 结束年 + $this->assertEquals(2010, $decadeFortune->getEndLunarYear()->getYear()); + // 干支 + $this->assertEquals('庚子', $decadeFortune->getName()); + // 下一大运 + $this->assertEquals('己亥', $decadeFortune->next(1)->getName()); + + // 小运 + $fortune = $childLimit->getStartFortune(); + // 年龄 + $this->assertEquals(10, $fortune->getAge()); + // 农历年 + $this->assertEquals(2001, $fortune->getLunarYear()->getYear()); + // 干支 + $this->assertEquals('戊申', $fortune->getName()); + // 小运推移 + $this->assertEquals('丙午', $fortune->next(2)->getName()); + $this->assertEquals('庚戌', $fortune->next(-2)->getName()); + + // 流年 + $this->assertEquals('辛巳', $fortune->getLunarYear()->getSixtyCycle()->getName()); + } + + function test16() + { + // 童限 + $childLimit = ChildLimit::fromSolarTime(SolarTime::fromYmdHms(1990, 3, 15, 10, 30, 0), Gender::MAN); + // 八字 + $this->assertEquals('庚午 己卯 己卯 己巳', $childLimit->getEightChar()->__toString()); + // 童限年数 + $this->assertEquals(6, $childLimit->getYearCount()); + // 童限月数 + $this->assertEquals(11, $childLimit->getMonthCount()); + // 童限日数 + $this->assertEquals(23, $childLimit->getDayCount()); + // 童限结束(即开始起运)的公历时刻 + $this->assertEquals('1997年3月11日 00:22:00', $childLimit->getEndTime()->__toString()); + + // 小运 + $fortune = $childLimit->getStartFortune(); + // 年龄 + $this->assertEquals(7, $fortune->getAge()); + } + + function test17() + { + $eightChar = new EightChar( + SixtyCycle::fromName('己丑'), + SixtyCycle::fromName('戊辰'), + SixtyCycle::fromName('戊辰'), + SixtyCycle::fromName('甲子') + ); + $this->assertEquals('丁丑', $eightChar->getOwnSign()->getName()); + } + + function test18() + { + $eightChar = new EightChar( + SixtyCycle::fromName('戊戌'), + SixtyCycle::fromName('庚申'), + SixtyCycle::fromName('丁亥'), + SixtyCycle::fromName('丙午') + ); + $this->assertEquals('乙卯', $eightChar->getOwnSign()->getName()); + } + + function test19() + { + $eightChar = new EightChar( + SixtyCycle::fromName('甲子'), + SixtyCycle::fromName('壬申'), + SixtyCycle::fromName('庚子'), + SixtyCycle::fromName('乙亥') + ); + $this->assertEquals('甲戌', $eightChar->getOwnSign()->getName()); + } + + function test20() + { + $eightChar = ChildLimit::fromSolarTime(SolarTime::fromYmdHms(2024, 1, 29, 9, 33, 0), Gender::MAN)->getEightChar(); + $this->assertEquals('癸亥', $eightChar->getOwnSign()->getName()); + $this->assertEquals('己未', $eightChar->getBodySign()->getName()); + } + + function test21() + { + $eightChar = new EightChar( + SixtyCycle::fromName('辛亥'), + SixtyCycle::fromName('乙未'), + SixtyCycle::fromName('庚子'), + SixtyCycle::fromName('甲辰') + ); + $this->assertEquals('庚子', $eightChar->getBodySign()->getName()); + } + + function test22() + { + $this->assertEquals('丙寅', ChildLimit::fromSolarTime(SolarTime::fromYmdHms(1990, 1, 27, 0, 0, 0), Gender::MAN)->getEightChar()->getBodySign()->getName()); + } + + function test23() + { + $this->assertEquals('甲戌', ChildLimit::fromSolarTime(SolarTime::fromYmdHms(2019, 3, 7, 8, 0, 0), Gender::MAN)->getEightChar()->getOwnSign()->getName()); + } + + function test24() + { + $this->assertEquals('丁丑', ChildLimit::fromSolarTime(SolarTime::fromYmdHms(2019, 3, 27, 2, 0, 0), Gender::MAN)->getEightChar()->getOwnSign()->getName()); + } + + function test25() + { + $this->assertEquals('丙寅', LunarHour::fromYmdHms(1994, 5, 20, 18, 0, 0)->getEightChar()->getOwnSign()->getName()); + } + + function test26() + { + $this->assertEquals('己丑', SolarTime::fromYmdHms(1986, 5, 29, 13, 37, 0)->getLunarHour()->getEightChar()->getBodySign()->getName()); + } + + function test27() + { + $this->assertEquals('乙丑', SolarTime::fromYmdHms(1994, 12, 6, 2, 0, 0)->getLunarHour()->getEightChar()->getBodySign()->getName()); + } + + function test28() + { + $eightChar = new EightChar( + SixtyCycle::fromName('辛亥'), + SixtyCycle::fromName('丁酉'), + SixtyCycle::fromName('丙午'), + SixtyCycle::fromName('癸巳') + ); + $this->assertEquals('辛卯', $eightChar->getOwnSign()->getName()); + } + + function test29() + { + $eightChar = new EightChar( + SixtyCycle::fromName('丙寅'), + SixtyCycle::fromName('庚寅'), + SixtyCycle::fromName('辛卯'), + SixtyCycle::fromName('壬辰') + ); + $this->assertEquals('己亥', $eightChar->getOwnSign()->getName()); + $this->assertEquals('乙未', $eightChar->getBodySign()->getName()); + } + + function test30() + { + $eightChar = new EightChar( + SixtyCycle::fromName('壬子'), + SixtyCycle::fromName('辛亥'), + SixtyCycle::fromName('壬戌'), + SixtyCycle::fromName('乙巳') + ); + $this->assertEquals('乙巳', $eightChar->getBodySign()->getName()); + } +} diff --git a/test/ElementTest.php b/test/ElementTest.php new file mode 100644 index 0000000..c674be9 --- /dev/null +++ b/test/ElementTest.php @@ -0,0 +1,29 @@ +assertEquals('碓磨厕 外东南', SolarDay::fromYmd(2021, 11, 13)->getLunarDay()->getFetusDay()->getName()); + } + + function test2() + { + $this->assertEquals('占门碓 外东南', SolarDay::fromYmd(2021, 11, 12)->getLunarDay()->getFetusDay()->getName()); + } + + function test3() + { + $this->assertEquals('厨灶厕 外西南', SolarDay::fromYmd(2011, 11, 12)->getLunarDay()->getFetusDay()->getName()); + } +} diff --git a/test/FetusTest.php b/test/FetusTest.php new file mode 100644 index 0000000..5068295 --- /dev/null +++ b/test/FetusTest.php @@ -0,0 +1,21 @@ +assertEquals('子', EarthBranch::fromIndex(0)->getName()); + } + + function test1() + { + $this->assertEquals(0, EarthBranch::fromName('子')->getIndex()); + } +} diff --git a/test/HeavenlyStemTest.php b/test/HeavenlyStemTest.php new file mode 100644 index 0000000..a12e0ef --- /dev/null +++ b/test/HeavenlyStemTest.php @@ -0,0 +1,46 @@ +assertEquals('甲', HeavenStem::fromIndex(0)->getName()); + } + + function test1() + { + $this->assertEquals(0, HeavenStem::fromName('甲')->getIndex()); + } + + /** + * 天干的五行生克 + */ + function test2() + { + $this->assertEquals(HeavenStem::fromName('丙')->getElement(), HeavenStem::fromName('甲')->getElement()->getReinforce()); + } + + /** + * 十神 + */ + function test3() + { + $this->assertEquals('比肩', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('甲'))->getName()); + $this->assertEquals('劫财', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('乙'))->getName()); + $this->assertEquals('食神', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('丙'))->getName()); + $this->assertEquals('伤官', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('丁'))->getName()); + $this->assertEquals('偏财', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('戊'))->getName()); + $this->assertEquals('正财', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('己'))->getName()); + $this->assertEquals('七杀', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('庚'))->getName()); + $this->assertEquals('正官', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('辛'))->getName()); + $this->assertEquals('偏印', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('壬'))->getName()); + $this->assertEquals('正印', HeavenStem::fromName('甲')->getTenStar(HeavenStem::fromName('癸'))->getName()); + } +} diff --git a/test/JulianDayTest.php b/test/JulianDayTest.php new file mode 100644 index 0000000..6b67b3d --- /dev/null +++ b/test/JulianDayTest.php @@ -0,0 +1,16 @@ +assertEquals('2023年1月1日', SolarDay::fromYmd(2023, 1, 1)->getJulianDay()->getSolarDay()->__toString()); + } +} diff --git a/test/LegalHolidayTest.php b/test/LegalHolidayTest.php new file mode 100644 index 0000000..5974d85 --- /dev/null +++ b/test/LegalHolidayTest.php @@ -0,0 +1,54 @@ +assertNotNull($d); + $this->assertEquals('2011年5月1日 劳动节(休)', $d->__toString()); + + $this->assertEquals('2011年5月2日 劳动节(休)', $d->next(1)->__toString()); + $this->assertEquals('2011年6月4日 端午节(休)', $d->next(2)->__toString()); + $this->assertEquals('2011年4月30日 劳动节(休)', $d->next(-1)->__toString()); + $this->assertEquals('2011年4月5日 清明节(休)', $d->next(-2)->__toString()); + } + + + function test1() + { + $this->assertNotNull(LegalHoliday::fromYmd(2010, 1, 1)); + } + + function test3() + { + $d = LegalHoliday::fromYmd(2001, 12, 29); + $this->assertNotNull($d); + $this->assertEquals('2001年12月29日 元旦节(班)', $d->__toString()); + $this->assertNull($d->next(-1)); + } + + function test4() + { + $d = LegalHoliday::fromYmd(2022, 10, 5); + $this->assertNotNull($d); + $this->assertEquals('2022年10月5日 国庆节(休)', $d->__toString()); + $this->assertEquals('2022年10月4日 国庆节(休)', $d->next(-1)->__toString()); + $this->assertEquals('2022年10月6日 国庆节(休)', $d->next(1)->__toString()); + } + + function test5() + { + $d = SolarDay::fromYmd(2010, 10, 1)->getLegalHoliday(); + $this->assertNotNull($d); + $this->assertEquals('2010年10月1日 国庆节(休)', $d->__toString()); + } +} diff --git a/test/LunarDayTest.php b/test/LunarDayTest.php new file mode 100644 index 0000000..90667ad --- /dev/null +++ b/test/LunarDayTest.php @@ -0,0 +1,166 @@ +assertEquals('1年1月1日', LunarDay::fromYmd(0, 11, 18)->getSolarDay()->__toString()); + } + + function test2() + { + $this->assertEquals('9999年12月31日', LunarDay::fromYmd(9999, 12, 2)->getSolarDay()->__toString()); + } + + function test3() + { + $this->assertEquals('1905年2月4日', LunarDay::fromYmd(1905, 1, 1)->getSolarDay()->__toString()); + } + + function test4() + { + $this->assertEquals('2039年1月23日', LunarDay::fromYmd(2038, 12, 29)->getSolarDay()->__toString()); + } + + function test5() + { + $this->assertEquals('1500年1月31日', LunarDay::fromYmd(1500, 1, 1)->getSolarDay()->__toString()); + } + + function test6() + { + $this->assertEquals('1501年1月18日', LunarDay::fromYmd(1500, 12, 29)->getSolarDay()->__toString()); + } + + function test7() + { + $this->assertEquals('1582年10月4日', LunarDay::fromYmd(1582, 9, 18)->getSolarDay()->__toString()); + } + + function test8() + { + $this->assertEquals('1582年10月15日', LunarDay::fromYmd(1582, 9, 19)->getSolarDay()->__toString()); + } + + function test9() + { + $this->assertEquals('2020年1月6日', LunarDay::fromYmd(2019, 12, 12)->getSolarDay()->__toString()); + } + + function test10() + { + $this->assertEquals('2033年12月22日', LunarDay::fromYmd(2033, -11, 1)->getSolarDay()->__toString()); + } + + function test11() + { + $this->assertEquals('2021年7月16日', LunarDay::fromYmd(2021, 6, 7)->getSolarDay()->__toString()); + } + + function test12() + { + $this->assertEquals('2034年2月19日', LunarDay::fromYmd(2034, 1, 1)->getSolarDay()->__toString()); + } + + function test13() + { + $this->assertEquals('2034年1月20日', LunarDay::fromYmd(2033, 12, 1)->getSolarDay()->__toString()); + } + + function test14() + { + $this->assertEquals('7013年12月24日', LunarDay::fromYmd(7013, -11, 4)->getSolarDay()->__toString()); + } + + function test15() + { + $this->assertEquals('己亥', LunarDay::fromYmd(2023, 8, 24)->getSixtyCycle()->__toString()); + } + + function test16() + { + $this->assertEquals('癸酉', LunarDay::fromYmd(1653, 1, 6)->getSixtyCycle()->__toString()); + } + + function test17() + { + $this->assertEquals('农历庚寅年二月初二', LunarDay::fromYmd(2010, 1, 1)->next(31)->__toString()); + } + + function test18() + { + $this->assertEquals('农历壬辰年闰四月初一', LunarDay::fromYmd(2012, 3, 1)->next(60)->__toString()); + } + + function test19() + { + $this->assertEquals('农历壬辰年闰四月廿九', LunarDay::fromYmd(2012, 3, 1)->next(88)->__toString()); + } + + function test20() + { + $this->assertEquals('农历壬辰年五月初一', LunarDay::fromYmd(2012, 3, 1)->next(89)->__toString()); + } + + function test21() + { + $this->assertEquals('2020年4月23日', LunarDay::fromYmd(2020, 4, 1)->getSolarDay()->__toString()); + } + + function test22() + { + $this->assertEquals('甲辰', LunarDay::fromYmd(2024, 1, 1)->getMonth()->getYear()->getSixtyCycle()->getName()); + } + + function test23() + { + $this->assertEquals('癸卯', LunarDay::fromYmd(2023, 12, 30)->getMonth()->getYear()->getSixtyCycle()->getName()); + } + + /** + * 二十八宿 + */ + function test24() + { + $d = LunarDay::fromYmd(2020, 4, 13); + $star = $d->getTwentyEightStar(); + $this->assertEquals('南', $star->getZone()->getName()); + $this->assertEquals('朱雀', $star->getZone()->getBeast()->getName()); + $this->assertEquals('翼', $star->getName()); + $this->assertEquals('火', $star->getSevenStar()->getName()); + $this->assertEquals('蛇', $star->getAnimal()->getName()); + $this->assertEquals('凶', $star->getLuck()->getName()); + + $this->assertEquals('阳天', $star->getLand()->getName()); + $this->assertEquals('东南', $star->getLand()->getDirection()->getName()); + } + + function test25() + { + $d = LunarDay::fromYmd(2023, 9, 28); + $star = $d->getTwentyEightStar(); + $this->assertEquals('南', $star->getZone()->getName()); + $this->assertEquals('朱雀', $star->getZone()->getBeast()->getName()); + $this->assertEquals('柳', $star->getName()); + $this->assertEquals('土', $star->getSevenStar()->getName()); + $this->assertEquals('獐', $star->getAnimal()->getName()); + $this->assertEquals('凶', $star->getLuck()->getName()); + + $this->assertEquals('炎天', $star->getLand()->getName()); + $this->assertEquals('南', $star->getLand()->getDirection()->getName()); + } + + function test26() + { + $lunar = LunarDay::fromYmd(2005, 11, 23); + $this->assertEquals('戊子', $lunar->getMonth()->getSixtyCycle()->getName()); + $this->assertEquals('戊子', $lunar->getMonthSixtyCycle()->getName()); + } +} diff --git a/test/LunarFestivalTest.php b/test/LunarFestivalTest.php new file mode 100644 index 0000000..4c628eb --- /dev/null +++ b/test/LunarFestivalTest.php @@ -0,0 +1,59 @@ +assertNotNull($f); + $this->assertEquals(LunarFestival::$NAMES[$i], $f->getName()); + } + } + + function test1() + { + $f = LunarFestival::fromIndex(2023, 0); + $this->assertNotNull($f); + for ($i = 0, $j = count(LunarFestival::$NAMES); $i < $j; $i++) { + $this->assertEquals(LunarFestival::$NAMES[$i], $f->next($i)->getName()); + } + } + + function test2() + { + $f = LunarFestival::fromIndex(2023, 0); + $this->assertNotNull($f); + $this->assertEquals('农历甲辰年正月初一 春节', $f->next(13)->__toString()); + $this->assertEquals('农历壬寅年十一月廿九 冬至节', $f->next(-3)->__toString()); + } + + function test3() + { + $f = LunarFestival::fromIndex(2023, 0); + $this->assertNotNull($f); + $this->assertEquals('农历壬寅年三月初五 清明节', $f->next(-9)->__toString()); + } + + function test4() + { + $f = LunarDay::fromYmd(2010, 1, 15)->getFestival(); + $this->assertNotNull($f); + $this->assertEquals('农历庚寅年正月十五 元宵节', $f->__toString()); + } + + function test5() + { + $f = LunarDay::fromYmd(2021, 12, 29)->getFestival(); + $this->assertNotNull($f); + $this->assertEquals('农历辛丑年十二月廿九 除夕', $f->__toString()); + } +} diff --git a/test/LunarHourTest.php b/test/LunarHourTest.php new file mode 100644 index 0000000..80fe8d9 --- /dev/null +++ b/test/LunarHourTest.php @@ -0,0 +1,96 @@ +assertEquals('子时', $h->getName()); + $this->assertEquals('农历庚子年闰四月初五戊子时', $h->__toString()); + } + + function test2() + { + $h = LunarHour::fromYmdHms(2020, -4, 5, 0, 59, 0); + $this->assertEquals('子时', $h->getName()); + $this->assertEquals('农历庚子年闰四月初五丙子时', $h->__toString()); + } + + function test3() + { + $h = LunarHour::fromYmdHms(2020, -4, 5, 1, 0, 0); + $this->assertEquals('丑时', $h->getName()); + $this->assertEquals('农历庚子年闰四月初五丁丑时', $h->__toString()); + } + + function test4() + { + $h = LunarHour::fromYmdHms(2020, -4, 5, 21, 30, 0); + $this->assertEquals('亥时', $h->getName()); + $this->assertEquals('农历庚子年闰四月初五丁亥时', $h->__toString()); + } + + function test5() + { + $h = LunarHour::fromYmdHms(2020, -4, 2, 23, 30, 0); + $this->assertEquals('子时', $h->getName()); + $this->assertEquals('农历庚子年闰四月初二壬子时', $h->__toString()); + } + + function test6() + { + $h = LunarHour::fromYmdHms(2020, 4, 28, 23, 30, 0); + $this->assertEquals('子时', $h->getName()); + $this->assertEquals('农历庚子年四月廿八甲子时', $h->__toString()); + } + + function test7() + { + $h = LunarHour::fromYmdHms(2020, 4, 29, 0, 0, 0); + $this->assertEquals('子时', $h->getName()); + $this->assertEquals('农历庚子年四月廿九甲子时', $h->__toString()); + } + + function test8() + { + $h = LunarHour::fromYmdHms(2023, 11, 14, 23, 0, 0); + $this->assertEquals('甲子', $h->getSixtyCycle()->getName()); + + $this->assertEquals('己未', $h->getDaySixtyCycle()->getName()); + $this->assertEquals('戊午', $h->getDay()->getSixtyCycle()->getName()); + $this->assertEquals('农历癸卯年十一月十四', $h->getDay()->__toString()); + + $this->assertEquals('甲子', $h->getMonthSixtyCycle()->getName()); + $this->assertEquals('农历癸卯年十一月', $h->getDay()->getMonth()->__toString()); + $this->assertEquals('乙丑', $h->getDay()->getMonth()->getSixtyCycle()->getName()); + + $this->assertEquals('癸卯', $h->getYearSixtyCycle()->getName()); + $this->assertEquals('农历癸卯年', $h->getDay()->getMonth()->getYear()->__toString()); + $this->assertEquals('癸卯', $h->getDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + } + + function test9() + { + $h = LunarHour::fromYmdHms(2023, 11, 14, 6, 0, 0); + $this->assertEquals('乙卯', $h->getSixtyCycle()->getName()); + + $this->assertEquals('戊午', $h->getDaySixtyCycle()->getName()); + $this->assertEquals('戊午', $h->getDay()->getSixtyCycle()->getName()); + $this->assertEquals('农历癸卯年十一月十四', $h->getDay()->__toString()); + + $this->assertEquals('甲子', $h->getMonthSixtyCycle()->getName()); + $this->assertEquals('农历癸卯年十一月', $h->getDay()->getMonth()->__toString()); + $this->assertEquals('乙丑', $h->getDay()->getMonth()->getSixtyCycle()->getName()); + + $this->assertEquals('癸卯', $h->getYearSixtyCycle()->getName()); + $this->assertEquals('农历癸卯年', $h->getDay()->getMonth()->getYear()->__toString()); + $this->assertEquals('癸卯', $h->getDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + } +} diff --git a/test/LunarMonthTest.php b/test/LunarMonthTest.php new file mode 100644 index 0000000..b0caf1e --- /dev/null +++ b/test/LunarMonthTest.php @@ -0,0 +1,258 @@ +assertEquals('七月', LunarMonth::fromYm(2359, 7)->getName()); + } + + /** + * 闰月 + */ + + function test1() + { + $this->assertEquals('闰七月', LunarMonth::fromYm(2359, -7)->getName()); + } + + function test2() + { + $this->assertEquals(29, LunarMonth::fromYm(2023, 6)->getDayCount()); + } + + function test3() + { + $this->assertEquals(30, LunarMonth::fromYm(2023, 7)->getDayCount()); + } + + function test4() + { + $this->assertEquals(30, LunarMonth::fromYm(2023, 8)->getDayCount()); + } + + function test5() + { + $this->assertEquals(29, LunarMonth::fromYm(2023, 9)->getDayCount()); + } + + function test6() + { + $this->assertEquals('2023年10月15日', LunarMonth::fromYm(2023, 9)->getFirstJulianDay()->getSolarDay()->__toString()); + } + + function test7() + { + $this->assertEquals('甲寅', LunarMonth::fromYm(2023, 1)->getSixtyCycle()->getName()); + } + + function test8() + { + $this->assertEquals('丙辰', LunarMonth::fromYm(2023, -2)->getSixtyCycle()->getName()); + } + + function test9() + { + $this->assertEquals('丁巳', LunarMonth::fromYm(2023, 3)->getSixtyCycle()->getName()); + } + + function test10() + { + $this->assertEquals('丙寅', LunarMonth::fromYm(2024, 1)->getSixtyCycle()->getName()); + } + + function test11() + { + $this->assertEquals('丙寅', LunarMonth::fromYm(2023, 12)->getSixtyCycle()->getName()); + } + + function test12() + { + $this->assertEquals('壬寅', LunarMonth::fromYm(2022, 1)->getSixtyCycle()->getName()); + } + + function test13() + { + $this->assertEquals('闰十二月', LunarMonth::fromYm(37, -12)->getName()); + } + + function test14() + { + $this->assertEquals('闰十二月', LunarMonth::fromYm(5552, -12)->getName()); + } + + function test15() + { + $this->assertEquals('农历戊子年十二月', LunarMonth::fromYm(2008, 11)->next(1)->__toString()); + } + + function test16() + { + $this->assertEquals('农历己丑年正月', LunarMonth::fromYm(2008, 11)->next(2)->__toString()); + } + + function test17() + { + $this->assertEquals('农历己丑年五月', LunarMonth::fromYm(2008, 11)->next(6)->__toString()); + } + + function test18() + { + $this->assertEquals('农历己丑年闰五月', LunarMonth::fromYm(2008, 11)->next(7)->__toString()); + } + + function test19() + { + $this->assertEquals('农历己丑年六月', LunarMonth::fromYm(2008, 11)->next(8)->__toString()); + } + + function test20() + { + $this->assertEquals('农历庚寅年正月', LunarMonth::fromYm(2008, 11)->next(15)->__toString()); + } + + function test21() + { + $this->assertEquals('农历戊子年十一月', LunarMonth::fromYm(2008, 12)->next(-1)->__toString()); + } + + function test22() + { + $this->assertEquals('农历戊子年十一月', LunarMonth::fromYm(2009, 1)->next(-2)->__toString()); + } + + function test23() + { + $this->assertEquals('农历戊子年十一月', LunarMonth::fromYm(2009, 5)->next(-6)->__toString()); + } + + function test24() + { + $this->assertEquals('农历戊子年十一月', LunarMonth::fromYm(2009, -5)->next(-7)->__toString()); + } + + function test25() + { + $this->assertEquals('农历戊子年十一月', LunarMonth::fromYm(2009, 6)->next(-8)->__toString()); + } + + function test26() + { + $this->assertEquals('农历戊子年十一月', LunarMonth::fromYm(2010, 1)->next(-15)->__toString()); + } + + function test27() + { + $this->assertEquals(29, LunarMonth::fromYm(2012, -4)->getDayCount()); + } + + function test28() + { + $this->assertEquals('癸亥', LunarMonth::fromYm(2023, 9)->getSixtyCycle()->__toString()); + } + + function test29() + { + $d = SolarDay::fromYmd(2023, 10, 7)->getLunarDay(); + $this->assertEquals('壬戌', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('辛酉', $d->getMonthSixtyCycle()->__toString()); + } + + function test30() + { + $d = SolarDay::fromYmd(2023, 10, 8)->getLunarDay(); + $this->assertEquals('壬戌', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('壬戌', $d->getMonthSixtyCycle()->__toString()); + } + + function test31() + { + $d = SolarDay::fromYmd(2023, 10, 15)->getLunarDay(); + $this->assertEquals('九月', $d->getMonth()->getName()); + $this->assertEquals('癸亥', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('壬戌', $d->getMonthSixtyCycle()->__toString()); + } + + function test32() + { + $d = SolarDay::fromYmd(2023, 11, 7)->getLunarDay(); + $this->assertEquals('癸亥', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('壬戌', $d->getMonthSixtyCycle()->__toString()); + } + + function test33() + { + $d = SolarDay::fromYmd(2023, 11, 8)->getLunarDay(); + $this->assertEquals('癸亥', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('癸亥', $d->getMonthSixtyCycle()->__toString()); + } + + function test34() + { + // 2023年闰2月 + $m = LunarMonth::fromYm(2023, 12); + $this->assertEquals('农历癸卯年十二月', $m->__toString()); + $this->assertEquals('农历癸卯年十一月', $m->next(-1)->__toString()); + $this->assertEquals('农历癸卯年十月', $m->next(-2)->__toString()); + } + + function test35() + { + // 2023年闰2月 + $m = LunarMonth::fromYm(2023, 3); + $this->assertEquals('农历癸卯年三月', $m->__toString()); + $this->assertEquals('农历癸卯年闰二月', $m->next(-1)->__toString()); + $this->assertEquals('农历癸卯年二月', $m->next(-2)->__toString()); + $this->assertEquals('农历癸卯年正月', $m->next(-3)->__toString()); + $this->assertEquals('农历壬寅年十二月', $m->next(-4)->__toString()); + $this->assertEquals('农历壬寅年十一月', $m->next(-5)->__toString()); + } + + function test36() + { + $d = SolarDay::fromYmd(1983, 2, 15)->getLunarDay(); + $this->assertEquals('甲寅', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('甲寅', $d->getMonthSixtyCycle()->__toString()); + } + + function test37() + { + $d = SolarDay::fromYmd(2023, 10, 30)->getLunarDay(); + $this->assertEquals('癸亥', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('壬戌', $d->getMonthSixtyCycle()->__toString()); + } + + function test38() + { + $d = SolarDay::fromYmd(2023, 10, 19)->getLunarDay(); + $this->assertEquals('癸亥', $d->getMonth()->getSixtyCycle()->__toString()); + $this->assertEquals('壬戌', $d->getMonthSixtyCycle()->__toString()); + } + + function test39() + { + $m = LunarMonth::fromYm(2023, 11); + $this->assertEquals('农历癸卯年十一月', $m->__toString()); + $this->assertEquals('乙丑', $m->getSixtyCycle()->__toString()); + } + + function test40() + { + $this->assertEquals('庚申', LunarDay::fromYmd(2018, 6, 26)->getMonthSixtyCycle()->__toString()); + } + + function test41() + { + $this->assertEquals('辛丑', LunarMonth::fromYm(1991, 12)->getSixtyCycle()->__toString()); + } + +} diff --git a/test/LunarYearTest.php b/test/LunarYearTest.php new file mode 100644 index 0000000..671d7d7 --- /dev/null +++ b/test/LunarYearTest.php @@ -0,0 +1,138 @@ +assertEquals('农历癸卯年', LunarYear::fromYear(2023)->getName()); + } + + function test1() + { + $this->assertEquals('农历戊申年', LunarYear::fromYear(2023)->next(5)->getName()); + } + + function test2() + { + $this->assertEquals('农历戊戌年', LunarYear::fromYear(2023)->next(-5)->getName()); + } + + /** + * 农历年的干支 + */ + function test3() + { + $this->assertEquals('庚子', LunarYear::fromYear(2020)->getSixtyCycle()->getName()); + } + + /** + * 农历年的生肖(农历年->干支->地支->生肖) + */ + function test4() + { + $this->assertEquals('虎', LunarYear::fromYear(1986)->getSixtyCycle()->getEarthBranch()->getZodiac()->getName()); + } + + function test5() + { + $this->assertEquals(12, LunarYear::fromYear(151)->getLeapMonth()); + } + + function test6() + { + $this->assertEquals(1, LunarYear::fromYear(2357)->getLeapMonth()); + } + + function test7() + { + $y = LunarYear::fromYear(2023); + $this->assertEquals('癸卯', $y->getSixtyCycle()->getName()); + $this->assertEquals('兔', $y->getSixtyCycle()->getEarthBranch()->getZodiac()->getName()); + } + + function test8() + { + $this->assertEquals('上元', LunarYear::fromYear(1864)->getTwenty()->getSixty()->getName()); + } + + function test9() + { + $this->assertEquals('上元', LunarYear::fromYear(1923)->getTwenty()->getSixty()->getName()); + } + + function test10() + { + $this->assertEquals('中元', LunarYear::fromYear(1924)->getTwenty()->getSixty()->getName()); + } + + function test11() + { + $this->assertEquals('中元', LunarYear::fromYear(1983)->getTwenty()->getSixty()->getName()); + } + + function test12() + { + $this->assertEquals('下元', LunarYear::fromYear(1984)->getTwenty()->getSixty()->getName()); + } + + function test13() + { + $this->assertEquals('下元', LunarYear::fromYear(2043)->getTwenty()->getSixty()->getName()); + } + + function test14() + { + $this->assertEquals('一运', LunarYear::fromYear(1864)->getTwenty()->getName()); + } + + function test15() + { + $this->assertEquals('一运', LunarYear::fromYear(1883)->getTwenty()->getName()); + } + + function test16() + { + $this->assertEquals('二运', LunarYear::fromYear(1884)->getTwenty()->getName()); + } + + function test17() + { + $this->assertEquals('二运', LunarYear::fromYear(1903)->getTwenty()->getName()); + } + + function test18() + { + $this->assertEquals('三运', LunarYear::fromYear(1904)->getTwenty()->getName()); + } + + function test19() + { + $this->assertEquals('三运', LunarYear::fromYear(1923)->getTwenty()->getName()); + } + + function test20() + { + $this->assertEquals('八运', LunarYear::fromYear(2004)->getTwenty()->getName()); + } + + function test21() + { + $year = LunarYear::fromYear(1); + $this->assertEquals('六运', $year->getTwenty()->getName()); + $this->assertEquals('中元', $year->getTwenty()->getSixty()->getName()); + } + + function test22() + { + $year = LunarYear::fromYear(1863); + $this->assertEquals('九运', $year->getTwenty()->getName()); + $this->assertEquals('下元', $year->getTwenty()->getSixty()->getName()); + } +} diff --git a/test/NineDayTest.php b/test/NineDayTest.php new file mode 100644 index 0000000..7b9d8c3 --- /dev/null +++ b/test/NineDayTest.php @@ -0,0 +1,65 @@ +getNineDay(); + $this->assertEquals('一九', $d->getName()); + $this->assertEquals('一九', $d->getNine()->__toString()); + $this->assertEquals('一九第1天', $d->__toString()); + } + + function test1() + { + $d = SolarDay::fromYmd(2020, 12, 22)->getNineDay(); + $this->assertEquals('一九', $d->getName()); + $this->assertEquals('一九', $d->getNine()->__toString()); + $this->assertEquals('一九第2天', $d->__toString()); + } + + function test2() + { + $d = SolarDay::fromYmd(2020, 1, 7)->getNineDay(); + $this->assertEquals('二九', $d->getName()); + $this->assertEquals('二九', $d->getNine()->__toString()); + $this->assertEquals('二九第8天', $d->__toString()); + } + + function test3() + { + $d = SolarDay::fromYmd(2021, 1, 6)->getNineDay(); + $this->assertEquals('二九', $d->getName()); + $this->assertEquals('二九', $d->getNine()->__toString()); + $this->assertEquals('二九第8天', $d->__toString()); + } + + function test4() + { + $d = SolarDay::fromYmd(2021, 1, 8)->getNineDay(); + $this->assertEquals('三九', $d->getName()); + $this->assertEquals('三九', $d->getNine()->__toString()); + $this->assertEquals('三九第1天', $d->__toString()); + } + + function test5() + { + $d = SolarDay::fromYmd(2021, 3, 5)->getNineDay(); + $this->assertEquals('九九', $d->getName()); + $this->assertEquals('九九', $d->getNine()->__toString()); + $this->assertEquals('九九第3天', $d->__toString()); + } + + function test6() + { + $d = SolarDay::fromYmd(2021, 7, 5)->getNineDay(); + $this->assertNull($d); + } +} diff --git a/test/NineStarTest.php b/test/NineStarTest.php new file mode 100644 index 0000000..93a11b8 --- /dev/null +++ b/test/NineStarTest.php @@ -0,0 +1,99 @@ +getNineStar(); + $this->assertEquals('六', $nineStar->getName()); + $this->assertEquals('六白金', $nineStar->__toString()); + } + + function test1() + { + $nineStar = LunarYear::fromYear(2022)->getNineStar(); + $this->assertEquals('五黄土', $nineStar->__toString()); + $this->assertEquals('玉衡', $nineStar->getDipper()->__toString()); + } + + function test2() + { + $nineStar = LunarYear::fromYear(2033)->getNineStar(); + $this->assertEquals('三碧木', $nineStar->__toString()); + $this->assertEquals('天玑', $nineStar->getDipper()->__toString()); + } + + function test3() + { + $nineStar = LunarMonth::fromYm(1985, 2)->getNineStar(); + $this->assertEquals('四绿木', $nineStar->__toString()); + $this->assertEquals('天权', $nineStar->getDipper()->__toString()); + } + + function test4() + { + $nineStar = LunarMonth::fromYm(1985, 2)->getNineStar(); + $this->assertEquals('四绿木', $nineStar->__toString()); + $this->assertEquals('天权', $nineStar->getDipper()->__toString()); + } + + function test5() + { + $nineStar = LunarMonth::fromYm(2022, 1)->getNineStar(); + $this->assertEquals('二黒土', $nineStar->__toString()); + $this->assertEquals('天璇', $nineStar->getDipper()->__toString()); + } + + function test6() + { + $nineStar = LunarMonth::fromYm(2033, 1)->getNineStar(); + $this->assertEquals('五黄土', $nineStar->__toString()); + $this->assertEquals('玉衡', $nineStar->getDipper()->__toString()); + } + + function test7() + { + $nineStar = SolarDay::fromYmd(1985, 2, 19)->getLunarDay()->getNineStar(); + $this->assertEquals('五黄土', $nineStar->__toString()); + $this->assertEquals('玉衡', $nineStar->getDipper()->__toString()); + } + + function test8() + { + $nineStar = LunarDay::fromYmd(2022, 1, 1)->getNineStar(); + $this->assertEquals('四绿木', $nineStar->__toString()); + $this->assertEquals('天权', $nineStar->getDipper()->__toString()); + } + + function test9() + { + $nineStar = LunarDay::fromYmd(2033, 1, 1)->getNineStar(); + $this->assertEquals('一白水', $nineStar->__toString()); + $this->assertEquals('天枢', $nineStar->getDipper()->__toString()); + } + + function test10() + { + $nineStar = LunarHour::fromYmdHms(2033, 1, 1, 12, 0, 0)->getNineStar(); + $this->assertEquals('七赤金', $nineStar->__toString()); + $this->assertEquals('摇光', $nineStar->getDipper()->__toString()); + } + + function test11() + { + $nineStar = LunarHour::fromYmdHms(2011, 5, 3, 23, 0, 0)->getNineStar(); + $this->assertEquals('七赤金', $nineStar->__toString()); + $this->assertEquals('摇光', $nineStar->getDipper()->__toString()); + } +} diff --git a/test/PhenologyTest.php b/test/PhenologyTest.php new file mode 100644 index 0000000..440314a --- /dev/null +++ b/test/PhenologyTest.php @@ -0,0 +1,39 @@ +getPhenologyDay(); + // 三候 + $threePhenology = $phenology->getPhenology()->getThreePhenology(); + $this->assertEquals('谷雨', $solarDay->getTerm()->getName()); + $this->assertEquals('初候', $threePhenology->getName()); + $this->assertEquals('萍始生', $phenology->getName()); + // 该候的第5天 + $this->assertEquals(4, $phenology->getDayIndex()); + } + + function test1() + { + $solarDay = SolarDay::fromYmd(2021, 12, 26); + // 七十二候 + $phenology = $solarDay->getPhenologyDay(); + // 三候 + $threePhenology = $phenology->getPhenology()->getThreePhenology(); + $this->assertEquals('冬至', $solarDay->getTerm()->getName()); + $this->assertEquals('二候', $threePhenology->getName()); + $this->assertEquals('麋角解', $phenology->getName()); + // 该候的第1天 + $this->assertEquals(0, $phenology->getDayIndex()); + } +} diff --git a/test/SixtyCycleTest.php b/test/SixtyCycleTest.php new file mode 100644 index 0000000..746bfa4 --- /dev/null +++ b/test/SixtyCycleTest.php @@ -0,0 +1,62 @@ +assertEquals('丁丑', SixtyCycle::fromIndex(13)->getName()); + } + + function test1() + { + $this->assertEquals(13, SixtyCycle::fromName('丁丑')->getIndex()); + } + + /** + * 五行 + */ + function test2() + { + $this->assertEquals('石榴木', SixtyCycle::fromName('辛酉')->getSound()->getName()); + $this->assertEquals('剑锋金', SixtyCycle::fromName('癸酉')->getSound()->getName()); + $this->assertEquals('平地木', SixtyCycle::fromName('己亥')->getSound()->getName()); + } + + /** + * 旬 + */ + function test3() + { + $this->assertEquals('甲子', SixtyCycle::fromName('甲子')->getTen()->getName()); + $this->assertEquals('甲寅', SixtyCycle::fromName('乙卯')->getTen()->getName()); + $this->assertEquals('甲申', SixtyCycle::fromName('癸巳')->getTen()->getName()); + } + + /** + * 旬空 + */ + function test4() + { + $this->assertEquals([EarthBranch::fromName('戌'), EarthBranch::fromName('亥')], SixtyCycle::fromName('甲子')->getExtraEarthBranches()); + $this->assertEquals([EarthBranch::fromName('子'), EarthBranch::fromName('丑')], SixtyCycle::fromName('乙卯')->getExtraEarthBranches()); + $this->assertEquals([EarthBranch::fromName('午'), EarthBranch::fromName('未')], SixtyCycle::fromName('癸巳')->getExtraEarthBranches()); + } + + /** + * 地势(长生十二神) + */ + function test5() + { + $this->assertEquals('长生', HeavenStem::fromName('丙')->getTerrain(EarthBranch::fromName('寅'))->getName()); + $this->assertEquals('沐浴', HeavenStem::fromName('辛')->getTerrain(EarthBranch::fromName('亥'))->getName()); + } +} diff --git a/test/SolarDayTest.php b/test/SolarDayTest.php new file mode 100644 index 0000000..dfb6c58 --- /dev/null +++ b/test/SolarDayTest.php @@ -0,0 +1,92 @@ +assertEquals('1日', SolarDay::fromYmd(2023, 1, 1)->getName()); + $this->assertEquals('2023年1月1日', SolarDay::fromYmd(2023, 1, 1)->__toString()); + } + + function test1() + { + $this->assertEquals('29日', SolarDay::fromYmd(2000, 2, 29)->getName()); + $this->assertEquals('2000年2月29日', SolarDay::fromYmd(2000, 2, 29)->__toString()); + } + + function test2() + { + $this->assertEquals(0, SolarDay::fromYmd(2023, 1, 1)->getIndexInYear()); + $this->assertEquals(364, SolarDay::fromYmd(2023, 12, 31)->getIndexInYear()); + $this->assertEquals(365, SolarDay::fromYmd(2020, 12, 31)->getIndexInYear()); + } + + function test3() + { + $this->assertEquals(0, SolarDay::fromYmd(2023, 1, 1)->subtract(SolarDay::fromYmd(2023, 1, 1))); + $this->assertEquals(1, SolarDay::fromYmd(2023, 1, 2)->subtract(SolarDay::fromYmd(2023, 1, 1))); + $this->assertEquals(-1, SolarDay::fromYmd(2023, 1, 1)->subtract(SolarDay::fromYmd(2023, 1, 2))); + $this->assertEquals(31, SolarDay::fromYmd(2023, 2, 1)->subtract(SolarDay::fromYmd(2023, 1, 1))); + $this->assertEquals(-31, SolarDay::fromYmd(2023, 1, 1)->subtract(SolarDay::fromYmd(2023, 2, 1))); + $this->assertEquals(365, SolarDay::fromYmd(2024, 1, 1)->subtract(SolarDay::fromYmd(2023, 1, 1))); + $this->assertEquals(-365, SolarDay::fromYmd(2023, 1, 1)->subtract(SolarDay::fromYmd(2024, 1, 1))); + $this->assertEquals(1, SolarDay::fromYmd(1582, 10, 15)->subtract(SolarDay::fromYmd(1582, 10, 4))); + } + + function test4() + { + $this->assertEquals('1582年10月4日', SolarDay::fromYmd(1582, 10, 15)->next(-1)->__toString()); + } + + function test5() + { + $this->assertEquals('2000年3月1日', SolarDay::fromYmd(2000, 2, 28)->next(2)->__toString()); + } + + function test6() + { + $this->assertEquals('农历庚子年闰四月初二', SolarDay::fromYmd(2020, 5, 24)->getLunarDay()->__toString()); + } + + function test7() + { + $this->assertEquals(31, SolarDay::fromYmd(2020, 5, 24)->subtract(SolarDay::fromYmd(2020, 4, 23))); + } + + function test8() + { + $this->assertEquals('农历丙子年十一月十二', SolarDay::fromYmd(16, 11, 30)->getLunarDay()->__toString()); + } + + function test9() + { + $this->assertEquals('霜降', SolarDay::fromYmd(2023, 10, 27)->getTerm()->__toString()); + } + + function test10() + { + $this->assertEquals('豺乃祭兽第4天', SolarDay::fromYmd(2023, 10, 27)->getPhenologyDay()->__toString()); + } + + function test11() + { + $this->assertEquals('初候', SolarDay::fromYmd(2023, 10, 27)->getPhenologyDay()->getPhenology()->getThreePhenology()->__toString()); + } + + function test22() + { + $this->assertEquals('甲辰', SolarDay::fromYmd(2024, 2, 10)->getLunarDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + } + + function test23() + { + $this->assertEquals('癸卯', SolarDay::fromYmd(2024, 2, 9)->getLunarDay()->getMonth()->getYear()->getSixtyCycle()->getName()); + } +} diff --git a/test/SolarFestivalTest.php b/test/SolarFestivalTest.php new file mode 100644 index 0000000..09a35cb --- /dev/null +++ b/test/SolarFestivalTest.php @@ -0,0 +1,65 @@ +assertNotNull($f); + $this->assertEquals(SolarFestival::$NAMES[$i], $f->getName()); + } + } + + function test1() + { + $f = SolarFestival::fromIndex(2023, 0); + $this->assertNotNull($f); + for ($i = 0, $j = count(SolarFestival::$NAMES); $i < $j; $i++) { + $this->assertEquals(SolarFestival::$NAMES[$i], $f->next($i)->getName()); + } + } + + function test2() + { + $f = SolarFestival::fromIndex(2023, 0); + $this->assertNotNull($f); + $this->assertEquals('2024年5月1日 五一劳动节', $f->next(13)->__toString()); + $this->assertEquals('2022年8月1日 八一建军节', $f->next(-3)->__toString()); + } + + function test3() + { + $f = SolarFestival::fromIndex(2023, 0); + $this->assertNotNull($f); + $this->assertEquals('2022年3月8日 三八妇女节', $f->next(-9)->__toString()); + } + + function test4() + { + $f = SolarDay::fromYmd(2010, 1, 1)->getFestival(); + $this->assertNotNull($f); + $this->assertEquals('2010年1月1日 元旦', $f->__toString()); + } + + function test5() + { + $f = SolarDay::fromYmd(2021, 5, 4)->getFestival(); + $this->assertNotNull($f); + $this->assertEquals('2021年5月4日 五四青年节', $f->__toString()); + } + + function test6() + { + $f = SolarDay::fromYmd(1939, 5, 4)->getFestival(); + $this->assertNull($f); + } +} diff --git a/test/SolarHalfYearTest.php b/test/SolarHalfYearTest.php new file mode 100644 index 0000000..62df3cd --- /dev/null +++ b/test/SolarHalfYearTest.php @@ -0,0 +1,41 @@ +assertEquals('上半年', SolarHalfYear::fromIndex(2023, 0)->getName()); + $this->assertEquals('2023年上半年', SolarHalfYear::fromIndex(2023, 0)->__toString()); + } + + function test1() + { + $this->assertEquals('下半年', SolarHalfYear::fromIndex(2023, 1)->getName()); + $this->assertEquals('2023年下半年', SolarHalfYear::fromIndex(2023, 1)->__toString()); + } + + function test2() + { + $this->assertEquals('下半年', SolarHalfYear::fromIndex(2023, 0)->next(1)->getName()); + $this->assertEquals('2023年下半年', SolarHalfYear::fromIndex(2023, 0)->next(1)->__toString()); + } + + function test3() + { + $this->assertEquals('上半年', SolarHalfYear::fromIndex(2023, 0)->next(2)->getName()); + $this->assertEquals('2024年上半年', SolarHalfYear::fromIndex(2023, 0)->next(2)->__toString()); + } + + function test4() + { + $this->assertEquals('上半年', SolarHalfYear::fromIndex(2023, 0)->next(-2)->getName()); + $this->assertEquals('2022年上半年', SolarHalfYear::fromIndex(2023, 0)->next(-2)->__toString()); + } +} diff --git a/test/SolarMonthTest.php b/test/SolarMonthTest.php new file mode 100644 index 0000000..3ce8629 --- /dev/null +++ b/test/SolarMonthTest.php @@ -0,0 +1,61 @@ +assertEquals('5月', $m->getName()); + $this->assertEquals('2019年5月', $m->__toString()); + } + + function test1() + { + $m = SolarMonth::fromYm(2023, 1); + $this->assertEquals(5, $m->getWeekCount(0)); + $this->assertEquals(6, $m->getWeekCount(1)); + $this->assertEquals(6, $m->getWeekCount(2)); + $this->assertEquals(5, $m->getWeekCount(3)); + $this->assertEquals(5, $m->getWeekCount(4)); + $this->assertEquals(5, $m->getWeekCount(5)); + $this->assertEquals(5, $m->getWeekCount(6)); + } + + function test2() + { + $m = SolarMonth::fromYm(2023, 2); + $this->assertEquals(5, $m->getWeekCount(0)); + $this->assertEquals(5, $m->getWeekCount(1)); + $this->assertEquals(5, $m->getWeekCount(2)); + $this->assertEquals(4, $m->getWeekCount(3)); + $this->assertEquals(5, $m->getWeekCount(4)); + $this->assertEquals(5, $m->getWeekCount(5)); + $this->assertEquals(5, $m->getWeekCount(6)); + } + + function test3() + { + $m = SolarMonth::fromYm(2023, 10)->next(1); + $this->assertEquals('11月', $m->getName()); + $this->assertEquals('2023年11月', $m->__toString()); + } + + function test4() + { + $m = SolarMonth::fromYm(2023, 10); + $this->assertEquals('2023年12月', $m->next(2)->__toString()); + $this->assertEquals('2024年1月', $m->next(3)->__toString()); + $this->assertEquals('2023年5月', $m->next(-5)->__toString()); + $this->assertEquals('2023年1月', $m->next(-9)->__toString()); + $this->assertEquals('2022年12月', $m->next(-10)->__toString()); + $this->assertEquals('2025年10月', $m->next(24)->__toString()); + $this->assertEquals('2021年10月', $m->next(-24)->__toString()); + } +} diff --git a/test/SolarTermTest.php b/test/SolarTermTest.php new file mode 100644 index 0000000..16a79f7 --- /dev/null +++ b/test/SolarTermTest.php @@ -0,0 +1,69 @@ +assertEquals('冬至', $dongZhi->getName()); + $this->assertEquals(0, $dongZhi->getIndex()); + // 儒略日 + // 公历日 + $this->assertEquals('2022年12月22日', $dongZhi->getJulianDay()->getSolarDay()->__toString()); + + // 冬至顺推23次,就是大雪 2023-12-07 17:32:55 + $daXue = $dongZhi->next(23); + $this->assertEquals('大雪', $daXue->getName()); + $this->assertEquals(23, $daXue->getIndex()); + $this->assertEquals('2023年12月7日', $daXue->getJulianDay()->getSolarDay()->__toString()); + + // 冬至逆推2次,就是上一年的小雪 2022-11-22 16:20:28 + $xiaoXue = $dongZhi->next(-2); + $this->assertEquals('小雪', $xiaoXue->getName()); + $this->assertEquals(22, $xiaoXue->getIndex()); + $this->assertEquals('2022年11月22日', $xiaoXue->getJulianDay()->getSolarDay()->__toString()); + + // 冬至顺推24次,就是下一个冬至 2023-12-22 11:27:20 + $dongZhi2 = $dongZhi->next(24); + $this->assertEquals('冬至', $dongZhi2->getName()); + $this->assertEquals(0, $dongZhi2->getIndex()); + $this->assertEquals('2023年12月22日', $dongZhi2->getJulianDay()->getSolarDay()->__toString()); + } + + function test1() + { + // 公历2023年的雨水,2023-02-19 06:34:16 + $jq = SolarTerm::fromName(2023, '雨水'); + $this->assertEquals('雨水', $jq->getName()); + $this->assertEquals(4, $jq->getIndex()); + } + + function test2() + { + // 公历2023年的大雪,2023-12-07 17:32:55 + $jq = SolarTerm::fromName(2023, '大雪'); + $this->assertEquals('大雪', $jq->getName()); + // 索引 + $this->assertEquals(23, $jq->getIndex()); + // 公历 + $this->assertEquals('2023年12月7日', $jq->getJulianDay()->getSolarDay()->__toString()); + // 农历 + $this->assertEquals('农历癸卯年十月廿五', $jq->getJulianDay()->getSolarDay()->getLunarDay()->__toString()); + // 推移 + $this->assertEquals('雨水', $jq->next(5)->getName()); + } + + function test3() + { + $this->assertEquals('寒露', SolarDay::fromYmd(2023, 10, 10)->getTerm()->getName()); + } +} diff --git a/test/SolarYearTest.php b/test/SolarYearTest.php new file mode 100644 index 0000000..9205594 --- /dev/null +++ b/test/SolarYearTest.php @@ -0,0 +1,46 @@ +assertEquals('2023年', SolarYear::fromYear(2023)->getName()); + } + + function test1() + { + $this->assertFalse(SolarYear::fromYear(2023)->isLeap()); + } + + function test2() + { + $this->assertTrue(SolarYear::fromYear(1500)->isLeap()); + } + + function test3() + { + $this->assertFalse(SolarYear::fromYear(1700)->isLeap()); + } + + function test4() + { + $this->assertEquals(365, SolarYear::fromYear(2023)->getDayCount()); + } + + function test5() + { + $this->assertEquals('2028年', SolarYear::fromYear(2023)->next(5)->getName()); + } + + function test6() + { + $this->assertEquals('2018年', SolarYear::fromYear(2023)->next(-5)->getName()); + } +} diff --git a/test/WeekTest.php b/test/WeekTest.php new file mode 100644 index 0000000..0e60553 --- /dev/null +++ b/test/WeekTest.php @@ -0,0 +1,152 @@ +assertEquals('一', SolarDay::fromYmd(1582, 10, 1)->getWeek()->getName()); + } + + function test1() + { + $this->assertEquals('五', SolarDay::fromYmd(1582, 10, 15)->getWeek()->getName()); + } + + function test2() + { + $this->assertEquals(2, SolarDay::fromYmd(2023, 10, 31)->getWeek()->getIndex()); + } + + function test3() + { + $w = SolarWeek::fromYm(2023, 10, 0, 0); + $this->assertEquals('第一周', $w->getName()); + $this->assertEquals('2023年10月第一周', $w->__toString()); + } + + function test5() + { + $w = SolarWeek::fromYm(2023, 10, 4, 0); + $this->assertEquals('第五周', $w->getName()); + $this->assertEquals('2023年10月第五周', $w->__toString()); + } + + function test6() + { + $w = SolarWeek::fromYm(2023, 10, 5, 1); + $this->assertEquals('第六周', $w->getName()); + $this->assertEquals('2023年10月第六周', $w->__toString()); + } + + function test7() + { + $w = SolarWeek::fromYm(2023, 10, 0, 0)->next(4); + $this->assertEquals('第五周', $w->getName()); + $this->assertEquals('2023年10月第五周', $w->__toString()); + } + + function test8() + { + $w = SolarWeek::fromYm(2023, 10, 0, 0)->next(5); + $this->assertEquals('第二周', $w->getName()); + $this->assertEquals('2023年11月第二周', $w->__toString()); + } + + function test9() + { + $w = SolarWeek::fromYm(2023, 10, 0, 0)->next(-1); + $this->assertEquals('第五周', $w->getName()); + $this->assertEquals('2023年9月第五周', $w->__toString()); + } + + function test10() + { + $w = SolarWeek::fromYm(2023, 10, 0, 0)->next(-5); + $this->assertEquals('第一周', $w->getName()); + $this->assertEquals('2023年9月第一周', $w->__toString()); + } + + function test11() + { + $w = SolarWeek::fromYm(2023, 10, 0, 0)->next(-6); + $this->assertEquals('第四周', $w->getName()); + $this->assertEquals('2023年8月第四周', $w->__toString()); + } + + function test12() + { + $solar = SolarDay::fromYmd(1582, 10, 1); + $this->assertEquals(1, $solar->getWeek()->getIndex()); + } + + function test13() + { + $solar = SolarDay::fromYmd(1582, 10, 15); + $this->assertEquals(5, $solar->getWeek()->getIndex()); + } + + function test14() + { + $solar = SolarDay::fromYmd(1129, 11, 17); + $this->assertEquals(0, $solar->getWeek()->getIndex()); + } + + function test15() + { + $solar = SolarDay::fromYmd(1129, 11, 1); + $this->assertEquals(5, $solar->getWeek()->getIndex()); + } + + function test16() + { + $solar = SolarDay::fromYmd(8, 11, 1); + $this->assertEquals(4, $solar->getWeek()->getIndex()); + } + + function test17() + { + $solar = SolarDay::fromYmd(1582, 9, 30); + $this->assertEquals(0, $solar->getWeek()->getIndex()); + } + + function test18() + { + $solar = SolarDay::fromYmd(1582, 1, 1); + $this->assertEquals(1, $solar->getWeek()->getIndex()); + } + + function test19() + { + $solar = SolarDay::fromYmd(1500, 2, 29); + $this->assertEquals(6, $solar->getWeek()->getIndex()); + } + + function test20() + { + $solar = SolarDay::fromYmd(9865, 7, 26); + $this->assertEquals(3, $solar->getWeek()->getIndex()); + } + + function test21() + { + $week = LunarWeek::fromYm(2023, 1, 0, 2); + $this->assertEquals('农历癸卯年正月第一周', $week->__toString()); + $this->assertEquals('农历壬寅年十二月廿六', $week->getFirstDay()->__toString()); + } + + function test22() + { + $week = SolarWeek::fromYm(2023, 1, 0, 2); + $this->assertEquals('2023年1月第一周', $week->__toString()); + $this->assertEquals('2022年12月27日', $week->getFirstDay()->__toString()); + } +} diff --git a/tools/build-standalone.php b/tools/build-standalone.php new file mode 100644 index 0000000..b0212a5 --- /dev/null +++ b/tools/build-standalone.php @@ -0,0 +1,120 @@ +path = $path; + $classInfo->namespaces = $namespaces; + $classInfo->uses = $uses; + return $classInfo; +} + +function parseDirectory($path): void +{ + global $out; + $namespaces = array(); + $uses = array(); + + $files = glob(rtrim($path, '/') . '/*'); + if ('../src' == $path) { + usort($files, function ($a, $b) { + $sorts = array(); + foreach (['Culture', 'Tyme', 'AbstractCulture', 'AbstractCultureDay', 'AbstractTyme', 'LoopTyme'] as $name) { + $sorts[] = '../src/'. $name . '.php'; + } + return array_search($a, $sorts) - array_search($b, $sorts); + }); + } + foreach ($files as $file) { + if (is_file($file)) { + $classInfo = parseFile($file); + foreach ($classInfo->namespaces as $line) { + if (!in_array($line, $namespaces)) { + $namespaces[] = $line; + } + } + foreach ($classInfo->uses as $line) { + if (!in_array($line, $uses)) { + $uses[] = $line; + } + } + } + } + + if (count($namespaces) > 0) { + foreach ($namespaces as $line) { + fwrite($out, $line); + } + fwrite($out, "\r\n\r\n"); + } + + if (count($uses) > 0) { + foreach ($uses as $line) { + fwrite($out, $line); + } + fwrite($out, "\r\n"); + } + + foreach ($files as $file) { + if (is_file($file)) { + writeClass($file); + } + } + + foreach ($files as $file) { + if (is_dir($file)) { + parseDirectory($file); + } + } +} + +parseDirectory('../src'); + +echo('Done!');