Linux-shell实现阳历转农历(序)

好些天没有登陆邮箱,前几天上班打开一看垃圾箱中有一封邮件让我好激动,还是国外友人的英文邮件。^_^
大概内容是我早些时候写的一个阳历转农历的shell小程序,他在用的时候发现了bug,但是这个bug我在去年年底就修改了。而且,他给了此程序的具体出处,但并不是我发布此程序的地址,显然是XX拷贝过去的(管它XX是人还是机器),庆幸的是我在程序的log文件中加入了我的邮箱地址,此友人才能找到我。废话少说,入正题。

缘由

本脚本实现原理是查表法(因为公式有误差);基于农历新年为基准,对农历新年前后两个不同的农历进行计算。

写这个脚本之前是想在Linux 终端命令提示符中加入阳历及农历日期。在Ubuntu中有Lunar软件可以获取农历日期,但在Fedora或CentOS中并没有类似软件,所以就想自己来实现一个,但网上用其他语言写的一大把,如果再写没什么必要。所以就想用shell来写一个。

主要功能

将阳历转换为农历。

Ubuntu 12.04 LTS dash和bash都测试通过(在此系统中,默认的/bin/sh是连接到/bin/dash的,dash是一个小而相容于POSIX标准的Unix shell,bash进行了扩展)

CentOS 6.4 测试通过(很久以前的事了,限于手中的资源,现在就不再在此系统上测试,不知道现在的版本还能否通过)

Fedora 18 测试通过(很久以前的事了,限于手中的资源,现在就不再在此系统上测试,不知道现在的版本还能否通过)

参数要求

  • 参数数据为8位,其中年为4位,月和日各2位,不足前面补0,如2013年1月1号:20130101
  • 无参数时默认当前系统日期为需转换日期


农历是通过观测及推算而得出的历法,所以通过通用公式计算得到的农历时间多少会有误差,特别是时间范围比较大的时候,这种误差就不好再无视它了。

基本算法:以每年的农历新年为基准。新年后的农历年份对应于阳历所在的年份;新年前为上一个农历年份。

验证

如果想验证可通过以下网址进行验证:

http://www.herongyang.com/Year_zh/Program-Chinese-Calendar-Algorithm.html

数据来源

以下关键的农历元数据来源于:

http://www.cppblog.com/ctou45/archive/2012/08/21/187846.html

//0~4 共5bit 春节日

//5~6 共2bit 春节月

//7~19 共13bit 13个月的大小月情况(如果无闰月,最后位无效),大月为1,小月为0(从左到右)

//20~23 共4bit 记录闰月的月份,如果没有闰月为0

0x04AE53,0x0A5748,0x5526BD,0x0D2650,0x0D9544,
0x46AAB9,0x056A4D,0x09AD42,0x24AEB6,0x04AE4A, //1901-1910

0x6A4DBE,0x0A4D52,0x0D2546,0x5D52BA,0x0B544E,
0x0D6A43,0x296D37,0x095B4B,0x749BC1,0x049754, //1911-1920

0x0A4B48,0x5B25BC,0x06A550,0x06D445,0x4ADAB8,
0x02B64D,0x095742,0x2497B7,0x04974A,0x664B3E, //1921-1930

0x0D4A51,0x0EA546,0x56D4BA,0x05AD4E,0x02B644,
0x393738,0x092E4B,0x7C96BF,0x0C9553,0x0D4A48, //1931-1940

0x6DA53B,0x0B554F,0x056A45,0x4AADB9,0x025D4D,
0x092D42,0x2C95B6,0x0A954A,0x7B4ABD,0x06CA51, //1941-1950

0x0B5546,0x555ABB,0x04DA4E,0x0A5B43,0x352BB8,
0x052B4C,0x8A953F,0x0E9552,0x06AA48,0x7AD53C, //1951-1960

0x0AB54F,0x04B645,0x4A5739,0x0A574D,0x052642,
0x3E9335,0x0D9549,0x75AABE,0x056A51,0x096D46, //1961-1970

0x54AEBB,0x04AD4F,0x0A4D43,0x4D26B7,0x0D254B,
0x8D52BF,0x0B5452,0x0B6A47,0x696D3C,0x095B50, //1971-1980

0x049B45,0x4A4BB9,0x0A4B4D,0xAB25C2,0x06A554,
0x06D449,0x6ADA3D,0x0AB651,0x093746,0x5497BB, //1981-1990

0x04974F,0x064B44,0x36A537,0x0EA54A,0x86B2BF,
0x05AC53,0x0AB647,0x5936BC,0x092E50,0x0C9645, //1991-2000

0x4D4AB8,0x0D4A4C,0x0DA541,0x25AAB6,0x056A49,
0x7AADBD,0x025D52,0x092D47,0x5C95BA,0x0A954E, //2001-2010

0x0B4A43,0x4B5537,0x0AD54A,0x955ABF,0x04BA53,
0x0A5B48,0x652BBC,0x052B50,0x0A9345,0x474AB9, //2011-2020

0x06AA4C,0x0AD541,0x24DAB6,0x04B64A,0x69573D,
0x0A4E51,0x0D2646,0x5E933A,0x0D534D,0x05AA43, //2021-2030

0x36B537,0x096D4B,0xB4AEBF,0x04AD53,0x0A4D48,
0x6D25BC,0x0D254F,0x0D5244,0x5DAA38,0x0B5A4C, //2031-2040

0x056D41,0x24ADB6,0x049B4A,0x7A4BBE,0x0A4B51,
0x0AA546,0x5B52BA,0x06D24E,0x0ADA42,0x355B37, //2041-2050

0x09374B,0x8497C1,0x049753,0x064B48,0x66A53C,
0x0EA54F,0x06B244,0x4AB638,0x0AAE4C,0x092E42, //2051-2060

0x3C9735,0x0C9649,0x7D4ABD,0x0D4A51,0x0DA545,
0x55AABA,0x056A4E,0x0A6D43,0x452EB7,0x052D4B, //2061-2070

0x8A95BF,0x0A9553,0x0B4A47,0x6B553B,0x0AD54F,
0x055A45,0x4A5D38,0x0A5B4C,0x052B42,0x3A93B6, //2071-2080

0x069349,0x7729BD,0x06AA51,0x0AD546,0x54DABA,
0x04B64E,0x0A5743,0x452738,0x0D264A,0x8E933E, //2081-2090

0x0D5252,0x0DAA47,0x66B53B,0x056D4F,0x04AE45,
0x4A4EB9,0x0A4D4C,0x0D1541,0x2D92B5 //2091-2099
View Code

主要代码

因为时常更新,所以打包文件不再提供,只提供github。

github地址:

https://github.com/snowsolf/lunar

此程序包括如下几个文件:

  • lunar.sh 主脚本,具体实现
  • datebases 农历元数据
  • change.log 更改日志
  • readme 脚本说明及注意事项
  • shengxiao 生肖数据

主脚本

lunar.sh代码如下:

#########################################################################
# File Name: lunar.sh
# Author: snowsolf
# E-mail: snowsolf@hotmail.com
# Created Time: 2013年07月***********
#########################################################################
#!/bin/sh

Version=1.0
Editor=snowsolf
Email=snowsolf@hotmail.com

# print help
function Usage()
{
   cat << EOF
==============================================================
Valid date: 19010101 ~ 20991231
But 'date' program support: 19011215 ~ 20380119

  -h, --help              display this help and exit
  -V, --version           output version information and exit

Usage: $0 [-h|--help|-V|--version] | [date(yyyymmdd)]

Examples:
Usage input time:    $0 20130101
Usage system time:    $0

Editor: $Editor
E-mail: $Email
EOF
   exit 0
}

#################################################################
#get year,month,day and day of year
#system 'date' program support:19011215 ~ 20380119
#################################################################
function Date_data()
{
   date_year=$(echo $DATE |sed 's/^(.{4}).*/1/')
   date_month=$(echo $DATE |sed 's/.*(..)..$/1/')
   date_day=$(echo $DATE |sed 's/.*(..)$/1/')
   date_days=$(date -d $DATE +%j)
}

DATE=$@
# handle difference input
case "$#" in
   0)
      echo "No parameters!"
      echo -e "Usage system time: $(date +%Y-%m-%d)
"
      DATE=$(date +%Y%m%d)
      Date_data
   ;;
   1)
       date -d $DATE +%j > /dev/null || ((Usage && exit 0))
      case "$1" in
         -h|--help)
            Usage
         ;;
         -V|--version)
            echo "$0: Version $Version"
            echo "Editor: $Editor"
            echo "E-mail: $Email"
            exit 0
         ;;
         [1][9][0-9][0-9][0-9][0-9][0-9][0-9]|[2][0][0-9][0-9][0-9][0-9][0-9][0-9])
            [ "$1" -ge "19010101" ] && [ "$1" -lt "19011215" ] || [ "$1" -gt "20380119" ] && [ "$1" -le "20991231" ] 
            && echo -e "'date' program no support: $1
" && Usage
            [ "$1" -ge "19000000" ] && [ "$1" -lt "19010101" ] || [ "$1" -gt "20991231" ] && [ "$1" -le "20999999" ] 
            && echo -e "Invalid parameter: $1
" && Usage
            Date_data
         ;;
         *)
            echo -e "Invalid parameter: $1
"
            Usage
         ;;
      esac
   ;;
   *)
      echo -e "The number of parameter greater than one !
"
      Usage
   ;;
esac

# lunar databases
databases_path=databases

# get lunar year
lunar_year=$(sed /$date_year/!d $databases_path |sed 's/^(....).*/1/')

# get all for lunar year, and form hexadecimal to binary
# include lunar year, month, day, and leap month
lunar_year_data=$(sed /$date_year/!d $databases_path |sed 's/.* (.*)/1/')
lunar_year_data_bin=$(echo "ibase=16;obase=2;$lunar_year_data"|bc |sed -e :a -e 's/^.{1,23}$/0&/;ta')

new_year_month_bin=$(echo $lunar_year_data_bin |sed -e 's/^.{17}(.{2}).*/1/')
new_year_month=$(echo "ibase=2;$new_year_month_bin"|bc |sed -e :a -e 's/^.{1,1}$/0&/;ta')

new_year_day_bin=$(echo $lunar_year_data_bin |sed -e 's/.*(.{5})$/1/')
new_year_day=$(echo "ibase=2;$new_year_day_bin"|bc |sed -e :a -e 's/^.{1,1}$/0&/;ta')

new_year_days=$(date -d $date_year$new_year_month$new_year_day +%j)
lunar_days=$(expr $date_days - $new_year_days + 1)
# flag
befor_or_after=0

if [ "$lunar_days" -le "0" ]; then
   befor_or_after=1
   date_year=$(($date_year - 1))

   lunar_year=$(sed /$date_year/!d $databases_path |sed 's/^(....).*/1/')

   lunar_year_data=$(sed /$date_year/!d $databases_path |sed 's/.* (.*)/1/')
   lunar_year_data_bin=$(echo "ibase=16;obase=2;$lunar_year_data"|bc |sed -e :a -e 's/^.{1,23}$/0&/;ta')
fi

lunar_leap_month_bin=$(echo $lunar_year_data_bin |sed -e 's/^(.{4}).*/1/')
lunar_leap_month=$(echo "ibase=2;$lunar_leap_month_bin"|bc)

lunar_month_all_bin=$(echo $lunar_year_data_bin |sed -e 's/^.{4}(.{13}).*/1/')
[ "$lunar_leap_month" = "0" ] && lunar_month_all_bin=$(echo $lunar_year_data_bin |sed -e 's/^.{4}(.{12}).*/1/')
lunar_month_all=$(echo $lunar_month_all_bin |sed -e 's/0/29 /g' |sed -e 's/1/30 /g')

if [ "$befor_or_after" = "0" ];then
   lunar_month=1
   lunar_day=$lunar_days
   for i in $lunar_month_all
   do
      [ "$lunar_day" -eq "$i" ] && break
      [ "$lunar_day" -gt "$i" ] && lunar_day=$(($lunar_day - $i)) && lunar_month=$(($lunar_month + 1))
   done
else
   lunar_month=12
   lunar_day=$((-$lunar_days))
   lunar_month_all_bin=$(echo $lunar_month_all_bin |rev)
   lunar_month_all=$(echo $lunar_month_all_bin |sed -e 's/0/29 /g' |sed -e 's/1/30 /g')
   for i in $lunar_month_all
   do
      [ "$lunar_day" -eq "$i" ] && break
      if [ "$lunar_day" -gt "$i" ]; then
         lunar_day=$(($lunar_day - $i))
         lunar_month=$(($lunar_month - 1))
      else
         lunar_day=$(($i - $lunar_day))
         break
      fi
   done
fi

# output
if [ "$lunar_leap_month" = "0" ]; then
    echo $lunar_year-$lunar_month-$lunar_day
else
   if [ "$lunar_leap_month" -ge "$lunar_month" ]; then
      echo $lunar_year-$lunar_month-$lunar_day
   elif [ "$befor_or_after" = "0" ]; then
      if [ "$(($lunar_leap_month + 1))" = "$lunar_month" ];then
         lunar_month=$(($lunar_month - 1))
         echo $lunar_year-*$lunar_month-$lunar_day
      else
         lunar_month=$(($lunar_month - 1))
         echo $lunar_year-$lunar_month-$lunar_day
      fi
   else
      echo $lunar_year-$lunar_month-$lunar_day
   fi
fi

sed -n $(($(($lunar_year - 4598 + 2)) % 12))p shengxiao
lunar.sh

数据文件

databases文件中存储了日期的元数据。为了更容易查表,此文件对元数据进行了处理。

4598 1901 04AE53
4599 1902 0A5748
4600 1903 5526BD
4601 1904 0D2650
4602 1905 0D9544
4603 1906 46AAB9
4604 1907 056A4D
4605 1908 09AD42
4606 1909 24AEB6
4607 1910 04AE4A
4608 1911 6A4DBE
4609 1912 0A4D52
4610 1913 0D2546
4611 1914 5D52BA
4612 1915 0B544E
4613 1916 0D6A43
4614 1917 296D37
4615 1918 095B4B
4616 1919 749BC1
4617 1920 049754
4618 1921 0A4B48
4619 1922 5B25BC
4620 1923 06A550
4621 1924 06D445
4622 1925 4ADAB8
4623 1926 02B64D
4624 1927 095742
4625 1928 2497B7
4626 1929 04974A
4627 1930 664B3E
4628 1931 0D4A51
4629 1932 0EA546
4630 1933 56D4BA
4631 1934 05AD4E
4632 1935 02B644
4633 1936 393738
4634 1937 092E4B
4635 1938 7C96BF
4636 1939 0C9553
4637 1940 0D4A48
4638 1941 6DA53B
4639 1942 0B554F
4640 1943 056A45
4641 1944 4AADB9
4642 1945 025D4D
4643 1946 092D42
4644 1947 2C95B6
4645 1948 0A954A
4646 1949 7B4ABD
4647 1950 06CA51
4648 1951 0B5546
4649 1952 555ABB
4650 1953 04DA4E
4651 1954 0A5B43
4652 1955 352BB8
4653 1956 052B4C
4654 1957 8A953F
4655 1958 0E9552
4656 1959 06AA48
4657 1960 7AD53C
4658 1961 0AB54F
4659 1962 04B645
4660 1963 4A5739
4661 1964 0A574D
4662 1965 052642
4663 1966 3E9335
4664 1967 0D9549
4665 1968 75AABE
4666 1969 056A51
4667 1970 096D46
4668 1971 54AEBB
4669 1972 04AD4F
4670 1973 0A4D43
4671 1974 4D26B7
4672 1975 0D254B
4673 1976 8D52BF
4674 1977 0B5452
4675 1978 0B6A47
4676 1979 696D3C
4677 1980 095B50
4678 1981 049B45
4679 1982 4A4BB9
4680 1983 0A4B4D
4681 1984 AB25C2
4682 1985 06A554
4683 1986 06D449
4684 1987 6ADA3D
4685 1988 0AB651
4686 1989 093746
4687 1990 5497BB
4688 1991 04974F
4689 1992 064B44
4690 1993 36A537
4691 1994 0EA54A
4692 1995 86B2BF
4693 1996 05AC53
4694 1997 0AB647
4695 1998 5936BC
4696 1999 092E50
4697 2000 0C9645
4698 2001 4D4AB8
4699 2002 0D4A4C
4700 2003 0DA541
4701 2004 25AAB6
4702 2005 056A49
4703 2006 7AADBD
4704 2007 025D52
4705 2008 092D47
4706 2009 5C95BA
4707 2010 0A954E
4708 2011 0B4A43
4709 2012 4B5537
4710 2013 0AD54A
4711 2014 955ABF
4712 2015 04BA53
4713 2016 0A5B48
4714 2017 652BBC
4715 2018 052B50
4716 2019 0A9345
4717 2020 474AB9
4718 2021 06AA4C
4719 2022 0AD541
4720 2023 24DAB6
4721 2024 04B64A
4722 2025 69573D
4723 2026 0A4E51
4724 2027 0D2646
4725 2028 5E933A
4726 2029 0D534D
4727 2030 05AA43
4728 2031 36B537
4729 2032 096D4B
4730 2033 B4AEBF
4731 2034 04AD53
4732 2035 0A4D48
4733 2036 6D25BC
4734 2037 0D254F
4735 2038 0D5244
4736 2039 5DAA38
4737 2040 0B5A4C
4738 2041 056D41
4739 2042 24ADB6
4740 2043 049B4A
4741 2044 7A4BBE
4742 2045 0A4B51
4743 2046 0AA546
4744 2047 5B52BA
4745 2048 06D24E
4746 2049 0ADA42
4747 2050 355B37
4748 2051 09374B
4749 2052 8497C1
4750 2053 049753
4751 2054 064B48
4752 2055 66A53C
4753 2056 0EA54F
4754 2057 06B244
4755 2058 4AB638
4756 2059 0AAE4C
4757 2060 092E42
4758 2061 3C9735
4759 2062 0C9649
4760 2063 7D4ABD
4761 2064 0D4A51
4762 2065 0DA545
4763 2066 55AABA
4764 2067 056A4E
4765 2068 0A6D43
4766 2069 452EB7
4767 2070 052D4B
4768 2071 8A95BF
4769 2072 0A9553
4770 2073 0B4A47
4771 2074 6B553B
4772 2075 0AD54F
4773 2076 055A45
4774 2077 4A5D38
4775 2078 0A5B4C
4776 2079 052B42
4777 2080 3A93B6
4778 2081 069349
4779 2082 7729BD
4780 2083 06AA51
4781 2084 0AD546
4782 2085 54DABA
4783 2086 04B64E
4784 2087 0A5743
4785 2088 452738
4786 2089 0D264A
4787 2090 8E933E
4788 2091 0D5252
4789 2092 0DAA47
4790 2093 66B53B
4791 2094 056D4F
4792 2095 04AE45
4793 2096 4A4EB9
4794 2097 0A4D4C
4795 2098 0D1541
4796 2099 2D92B5
databases

生肖数据

shengxiao文件中是生肖:

鼠
牛
虎
兔
龙
蛇
马
羊
猴
鸡
狗
猪
shengxiao

下个功能点

下个功能点是阳历日期的实现,因为系统中date程序支持的时间范围是1901-12-15到2038-01-19,显然有时此时间段并不能满足一些人的要求。所以,下一步需要单独实现如date程序功能的代码,以支持更大的时间段。

但你可知道1752年的9月是有问题的,具体缘由你可以google(话说这些天已经不好使了,我只能说,是不是被玩坏了!)或者baidu。

下个shell程序

Ubuntu 14.04 LTS出来的时候,我就迫不及待的将12.04 LTS升级到14.04 LTS,但是除了问题TMD还是问题:

*开机情况下合起笔记本盖子再打开时X僵死了,好烦躁!
*蓝牙适配器不能用了!
*每次打开电脑都会有系统错误提示,还不止一个!

所以,前天晚上将必要的数据备份后还是装回12.04,感觉整个人都舒服了。

之前UbuntuKylin出来的时候就下了天气插件使用,但不管12.04还是14.04上都会莫名其妙的死掉,所以就萌生了用shell实现一个天气察看程序,但不知道天气元数据怎么获取(中国气象局的数据),之前看了UbuntuKylin的天气插件源码,但没找到。

最后

农历是通过观测及推算而得出的历法,一直没有找到元数据的出处,这个应该天文台有,但网上找不到。

还有天气数据是从那里获取?

还望知道的大哥大姐小弟小妹告诉我。不胜感激!

更新

2014-08-01

将此脚本托管到github上,并将readme内容更新到README.md文件中。

原文地址:https://www.cnblogs.com/snowsolf/p/lunar_next.html