本项目已上传至Maven仓库和Gitee
Gitee:gitee.com/blacol/chin…
Maven仓库:
<dependency>
<groupId>com.gitee.blacol</groupId>
<artifactId>chinese-time-transfer</artifactId>
<version>1.4.2</version>
</dependency>
背景
我们现在所使用的日期系统是西历系统。但是,在古代,我们使用干支纪年法表示日期。因此,我产生了一个想法——把西历按照古代的干支纪年法表示,用我们的方法去表示日期。
之所以会有这个想法是因为前些天看到了《胶囊计划》中的《界》这部作品当中,所有数字使用汉字表示,包括日期。因此便产生了这个想法。
下面是代码:
关于《胶囊计划》
“胶囊计划”是哔哩哔哩扶持国产动画的特别项目之一。项目邀请来自各行业优秀的专业团队,以“极致情绪”的胶囊为主题创作独立动画,组成精品动画短片集,以不同的形式、风格传递出纷呈的情绪故事。
——引自百度百科胶囊计划
里面有很多国人制作的优质动画短片,大家可以看一下。
关于《界》
这是国产的一个优质动画短片,讲述的是人工智能高速发展的时代,关于这个时代的终极课题——系统的人格建设与道德边界。主人公李未是一人工智能公司实验助理,她收到了公司的指示回公司协助甲级工程师完成一项实验。最后实验结果如何,大家可以看一下原片。
核心部分代码讲解
天干和地支
我国在古代采用的就是干支纪年法,地支是子丑寅卯...共计12个(不知道各位知不知道有一首十二生肖儿歌,子鼠丑牛寅虎卯兔...作者我就是靠这首儿歌记住的十二生肖:>)
天干则是甲乙丙丁...共计10个。
在翻阅百科的时候发现了一篇文章,上面表示癸和亥作为天干地支的第一位,因此程序中它俩在第一位。
为了便于程序的编写,创建了ChineseConsts类,此类包含了天干和地支的序列还包括了汉字数字的序列以及各个节气的日期表(这个在计算月天干和月地支时使用,同时判断年干支的时候也要用到)。
class ChineseConsts {
public static char[] 天干={'癸','甲','乙','丙','丁','戊','己','庚','辛','壬'};
public static char[] 地支={'亥','子','丑','寅','卯','辰','巳','午','未','申','酉','戌'};
public static char[] 数字={'零','一','二','三','四','五','六','七','八','九','十'};
public static char[] 时辰符号={'末','初'};
public static Map<String,Integer[]> 取节气表(){
Map<String,Integer[]> 节气表=new LinkedHashMap<>();
节气表.put("小寒",new Integer[]{1,5});
节气表.put("立春",new Integer[]{2,4});
节气表.put("惊蛰",new Integer[]{3,6});
节气表.put("清明",new Integer[]{4,5});
节气表.put("立夏",new Integer[]{5,6});
节气表.put("芒种",new Integer[]{6,5});
节气表.put("小暑",new Integer[]{7,7});
节气表.put("立秋",new Integer[]{8,8});
节气表.put("白露",new Integer[]{9,7});
节气表.put("寒露",new Integer[]{10,7});
节气表.put("立冬",new Integer[]{11,7});
节气表.put("大雪",new Integer[]{12,7});
return 节气表;
}
}
干支纪年法
通过公元年去计算年的干支有很多种方法,在百科上找到了一种计算方法,我稍作改良得到了下面的公式:
是公元年份,这样可以获得天干和地支的序号。
需要注意,用干支纪年法表示日期时,每年的第一天是立春的那天(在这里为便于计算假定所有节气的时间都是固定的,而现实情况是每个节气每一年的日期可能都不一样但都是早一天或者晚一天,如果不能理解可以研究日历。),因此,在立春之前都应该使用上一年的年天干和年地支(以下简称年干,年支)。比如,求2022年1月5日的年干支得到的结果应该是辛丑而不是壬寅,而到了2022年的2月4日,年干支才是壬寅。 在干支纪年法中,一年的开始是立春,这点一定要注意!
下面是求年干支的代码:
private static char[] 计算年干支(Date date) {
int[] westernDate = DateUtil.getWesternDate(date);
Map<String, Integer[]> 取节气表 = ChineseConsts.取节气表();
Integer[] 立春日期 = 取节气表.get("立春");
if (westernDate[1]<立春日期[0]||westernDate[1]==立春日期[0]&&westernDate[2]<立春日期[1]){
westernDate[0]-=1;
}
char 年干=ChineseConsts.天干[(westernDate[0]-3)%10];
char 年支 = ChineseConsts.地支[(westernDate[0] - 3) % 12];
return new char[]{年干,年支};
}
干支纪月法
干支纪月法有一点复杂,它的天干取决于年的天干,地支则取决于节气,这里节气的日期都已经确定了,所以只需要判断月干即可。 (其实还有一种是以农历正月为基准计算的,这种方法太复杂了,感兴趣的读者可以自行探索)
private static char[] 计算月干支(Date date,char 年干) {
int[] westernDate=DateUtil.getWesternDate(date);
int 天干索引 = 0;
switch (年干){
case '甲':
case '己': {
天干索引=3;
};break;
case '乙':
case '庚': {
天干索引=5;
};break;
case '丙':
case '辛': {
天干索引=7;
};break;
case '丁':
case '壬': {
天干索引=9;
};break;
case '戊':
case '癸': {
天干索引=1;
};break;
}
int 地支索引=0;
int 月=westernDate[1];
int 日=westernDate[2];
//判断是不是1月5日之前、1月5日-2月4日这几个特殊日期
if (月==1&&日<5) {
地支索引 = 1;
}else if (月==2&&日<4||月==1){
地支索引=2;
}else{
地支索引=3;
}
Map<String, Integer[]> 节气表 = ChineseConsts.取节气表();
Set<String> strings = 节气表.keySet();
Iterator<String> iterator = strings.iterator();
int 节气索引=-1;
Integer[] 节气日期=new Integer[2];
while (iterator.hasNext()){
if (节气索引+1==月-1){
String next = iterator.next();
Integer[] integers = 节气表.get(next);
节气日期=integers;
break;
}else{
节气索引++;
iterator.next();
}
}
if (日<节气日期[1]&&月>2){
月=月-1;
}
if(月==1&&日<5){
天干索引 += 10;
}else if (月==1||(月==2&&日<4)){
天干索引+=11;
}else{
if (月==2){
地支索引+=月-2;
}else{
地支索引+=月-2;
天干索引+=月-2;
}
}
char 月干=ChineseConsts.天干[天干索引%10];
char 月支=ChineseConsts.地支[地支索引%12];
return new char[]{月干,月支};
}
1月份天干和年天干的对照表如下:
| 年天干 | 1月的天干 |
|---|---|
| 甲、己 | 丙 |
| 乙、庚 | 戊 |
| 丙、辛 | 庚 |
| 丁、壬 | 壬 |
| 戊、癸 | 甲 |
得到1月份的天干(不是公历1月)就可以推算出所求月的天干了。 月支的计算遵循下列规则:
| 月支 | 开始的节气 | 日期 |
|---|---|---|
| 寅 | 立春 | 2/4 |
| 卯 | 惊蛰 | 3/6 |
| 辰 | 清明 | 4/5 |
| 巳 | 立夏 | 5/6 |
| 午 | 芒种 | 6/5 |
| 未 | 小暑 | 7/7 |
| 申 | 立秋 | 8/8 |
| 酉 | 白露 | 9/7 |
| 戌 | 寒露 | 10/7 |
| 亥 | 立冬 | 11/7 |
| 子 | 大雪 | 12/7 |
| 丑 | 小寒 | 1/5 |
由上表可以注意到第二年1月1日-2月3日都需要单独做判断,如果不单独做判断的话会得出错误结果。
干支纪日法
日干支的求解比较复杂,日干支的求法需要六十花甲子作为辅助,所谓六十花甲子就是将天干和地支按顺序排列得到的一种表格,甲子为1,乙丑为2以此类推。
除了六十花甲子之外还有一个月基数表:{0,31,-1,30,0,31,1,32,3,33,4,34}
这里得到了一套计算方法,从百科上查到的:
- 计算六十花甲子序数
世纪常数,世纪(例如2022年是21世纪,那么C=21),求解六十花甲子序数的一个中间变量,N为公元年分数的后两位,要转换的月份的月基数表(例如求3月,月基数数组索引为,故月基数为-1),要转换的日期(例如求3月22日,那么),六十花甲子序数。
到这一步如果知道六十花甲子表可以直接通过求得日的干支,由于录入六十花甲子表很麻烦所以通过第二步求得干支
- 计算天干和地支
需要注意的是,如果天干或地支索引达到了10或12则将索引置为0.
地支时间表示
| 地支 | 时间 |
|---|---|
| 子 | 23-0点 |
| 丑 | 1-2点 |
| 寅 | 3-4点 |
| 卯 | 5-6点 |
| 辰 | 7-8点 |
| 巳 | 9-10点 |
| 午 | 11-12点 |
| 未 | 13-14点 |
| 申 | 15-16点 |
| 酉 | 17-18点 |
| 戌 | 19-20点 |
| 亥 | 21-22点 |
根据短片中的时间通过再设立一个标识来准确识别时间,选取“初”和“末”来表示具体时间,比如12点,是午时,但午时快要结束了,所以为午末时。
“初”和“末”可以根据小时数是奇数还是偶数来判断。偶数都是“末”,奇数都是“初”。
可以根据小时数推算出地支——因为一个地支表示2小时,所以小时数除以二再加1就可以得到地支。(加1是因为我将亥作为地支列表的第一个元素了)。
上面就是对小时的干支纪年表示,而分钟和秒数貌似无法用传统的方式表示,所以直接将阿拉伯数字转换为汉字。 下面是时间表示的代码
private static ChineseTime simpleDateTime(Date date){
ChineseTime chineseTime = onlyDate(date);
int[] westernTime = DateUtil.getWesternTime(date);
char 时辰符号=ChineseConsts.时辰符号[westernTime[0]==0?0:westernTime[0]%2];
int 数字时辰= (int) Math.ceil(westernTime[0]/2.0);
if (数字时辰+1==13){
数字时辰=0;
}
char 时辰=ChineseConsts.地支[数字时辰+1];
chineseTime.设时辰符号(时辰符号);
chineseTime.设时辰(时辰);
chineseTime.设分钟(String.valueOf(westernTime[1]));
chineseTime.设秒(String.valueOf(westernTime[2]));
chineseTime.设模式(TransferMode.简单的日期时间);
return chineseTime;
}