# Calculate Chinese Zodiac sign

Given that the Chinese New Year is coming, here is a challenge to calculate the Chinese Zodiac, element and Taijitu for a given date.

The input should be a date:

1/31/2014


The date used here is the US style mm/dd/yyyy, but may alternatively be ISO-8601 style: yyyy-mm-dd so the style of date input is not strict.

The output should be the animal sign in the Chinese Zodiac including the element that corresponds to the date, and its Taijitu counterpart:

Horse - Wood - Yang


A table showing the Zodiac - element - Taijitu relationship can be seen here: Chinese Zodiac - Years

The output format is not strict but should contain animal, element and Taijitu component, and should be delimited in some fashion.

Examples of valid output for the above date might include:

(horse, wood, yang)
["Horse", "Wood", "Yang"]


Your program should return valid output for a range of at least 100 years, including today's date. This means that it will need to account for at least two 60 year (Sexagenary) cycles.

• Do you have a convenient link to how to calculate the start of a zodiac year? – Hand-E-Food Jan 21 '14 at 23:46
• You have no idea how to calculate the zodaic year but it "seems pretty simple". Perhaps we should close this question for now, and reopen it when you have those little details ironed out? – user8777 Jan 22 '14 at 0:07
• Another thing to note is that it is always between Jan 21 and Feb 21 inclusive. – Justin Jan 22 '14 at 0:32
• The table you link is of no use unless you add a restriction that it is unnecessary to give the correct output for dates outside that range. – Peter Taylor Jan 22 '14 at 9:57
• Eduard Florinescu, two things you should clarify in the question: 1) input date range as @PeterTaylor points out, and 2) does the output format have to be exactly as you specify in your example? Every character counts so please make this objective. Thanks. – Darren Stone Jan 22 '14 at 18:32

## Mathematica 63

Okay, this may be getting old, but it works (date input is ambiguous in many cases):

z=WolframAlpha[#<>" Chinese zodiac",{{"Result",1},"Plaintext"}] &


Example:

z@"1/31/2014"


"Horse (Yang Wood)"

• The breadth of Mathematica amazes me. Your output format is incorrect, but I'm sure that's easily fixed. – Darren Stone Jan 22 '14 at 17:47
• @DarrenStone it is somewhat inglorious because Mathematica simply calls W|A. Do you think the output has to fit exactly the OP´s formatting? – Yves Klett Jan 22 '14 at 18:02
• It looks like other answers are formatted exactly like the question's example output and it certainly takes extra code to do so in any language. But that's really a question for @EduardFlorinescu – Darren Stone Jan 22 '14 at 18:25
• @DarrenStone true. This will certainly add quite a bit of overhead. W&C... – Yves Klett Jan 22 '14 at 18:27

## C# – 357337 339 bytes

string Z(System.DateTime d){var c=new System.Globalization.ChineseLunisolarCalendar();var y=c.GetSexagenaryYear(d);var s=c.GetCelestialStem(y)-1;return",Rat,Ox,Tiger,Rabbit,Dragon,Snake,Horse,Goat,Monkey,Rooster,Dog,Pig".Split(',')[c.GetTerrestrialBranch(y)]+" - "+"Wood,Fire,Earth,Metal,Water".Split(',')[s/2]+" - Y"+(s%2>0?"in":"ang");}


Formatted:

string Z(System.DateTime d)
{
var c = new System.Globalization.ChineseLunisolarCalendar();
var y = c.GetSexagenaryYear(d);
var s = c.GetCelestialStem(y) - 1;
return
",Rat,Ox,Tiger,Rabbit,Dragon,Snake,Horse,Goat,Monkey,Rooster,Dog,Pig".Split(',')[c.GetTerrestrialBranch(y)]
+ " - "
+ "Wood,Fire,Earth,Metal,Water".Split(',')[s / 2]
+ " - Y" + (s % 2 > 0 ? "in" : "ang");
}

• +1 Also, you can save over 20 characters by avoiding string.Format. – Hand-E-Food Jan 22 '14 at 22:42
• Yeah, right, thanks, I tried to golf the various s%2 == 0 etc., and forgot the big one. :-) And, actually, it was exactly 20 characters, IIANM. – Mormegil Jan 23 '14 at 9:37

# Python 2: 360*387586594 - Characters

Reduced further

m,n,p=map(int,raw_input().split("/"));p-=1924;d=int("5od2nauh6qe4obvj8rf5pd2math6re3ocvi8sf5pd2l9uh6rf3nbvi7sg5pd2k9th6rf4navj7sg5oc1m9ti7qe3navj8sg5pc1math6qd3nbvj8sf4oc1ma"[p],36);p-=31*m-31+n<(d<21)*31+d;print"Rat Ox Tiger Rabbit Dragon Snake Horse Goat Monkey Rooster Dog Pig".split()[p%12],"Wood Fire Earth Metal Water".split()[p%10/2],["Yang","Yin"][p%2]


The above code works for the following interval: 5 Feb 1924 – 31 Dec 2043

Example: after pasting the above code in the python interpreter cursor

>>> m,n,p=map(int,raw_input().split("/"));p-=1924;d=int("5od2nauh6qe4obvj8rf5pd2math6re3ocvi8sf5pd2l9uh6rf3nbvi7sg5pd2k9th6rf4navj7sg5oc1m9ti7qe3navj8sg5pc1math6qd3nbvj8sf4oc1ma"[p],36);p-=31*m-31+n<(d<21)*31+d;print"Rat Ox Tiger Rabbit Dragon Snake Horse Goat Monkey Rooster Dog Pig".split()[p%12],"Wood Fire Earth Metal Water".split()[p%10/2],["Yang","Yin"][p%2]


and then press Enter, input a date like this

6/13/2018


and then press Enter again, output will be

Dog Earth Yang


* reduced further 27 bytes with Brian's help see comments.

• Did you notice your base-36 number contains “math” twice? :) – Timwi Feb 2 '14 at 7:36
• @Timwi :D No, I didn't, and that is both nice and spooky! – Eduard Florinescu Feb 2 '14 at 7:54
• Eduard, could you explain the d=int(base 36 number) part? I came across your answer doing research in stackoverflow for a similar problem. I need to expand the year calculations to the range of years 1500 - 2043. Thanks in advance for any help. – Luis Miguel Dec 5 '14 at 15:55
• You can save another 23 characters with some microoptimisations. See reddit.com/r/Python/comments/2oqobw/… – Brian Dec 9 '14 at 13:54
• @Timwi 1math6 and 2math6. Even the preceeding 5 and the following 3 are mirrored with different chars inbetween. That's 3spooky5me. – Magic Octopus Urn Apr 18 '19 at 16:23

## Ruby, 340

m,d,y=gets.split(?/).map(&:to_i);y+=1if 31*m+d-51>"d88jac8mlbuppp21ygmkjsnfsd4n3xsrhha4abnq9zgo6g2jupc39zav84434f75op4ftfhdcsa3xhc8nsw1ssw8p9qns1tf14mncy8j18msr9rsb7j7".to_i(36)>>5*y-9625&31
\$><<[%w(Goat Monkey Rooster Dog Pig Rat Ox Tiger Rabbit Dragon Snake Horse)[y%12],%w(Metal Water Wood Fire Earth)[(y-1)/2%5],%w(Yin Yang)[y%2]]*' - '


This will work for dates between Feb 5, 1924 and Jan 29, 2044.

• What the hell is that "magic" number? – tomsmeding Feb 19 '14 at 15:23
• @tomsmeding, it's a 600-bit integer encoded in Base 36 for compactness. Broken into bitfields, it's a table of the first day of the lunar calendar for each year between 1924 and 1944. – Darren Stone Feb 19 '14 at 19:02

## GolfScript (212 chars)

This uses non-printable characters, so here's the output from xxd:

0000000: 272d 272f 6e2a 7e5c 2833 312a 2b5c 3a5e  '-'/n*~\(31*+\:^
0000010: 3139 2a22 d4e3 275a 747f 878c 70cb 996d  19*"..'Zt...p..m
0000020: a4f9 19e9 699e d818 bb61 c4e3 2232 3536  ....i....a.."256
0000030: 6261 7365 2033 6261 7365 5e31 3932 322d  base 3base^1922-
0000040: 3c7b 282d 7d2f 3330 2532 312b 3e5e 352d  <{(-}/30%21+>^5-
0000050: 2b3a 5e31 3225 2752 6174 0a4f 780a 5469  +:^12%'Rat.Ox.Ti
0000060: 6765 720a 5261 6262 6974 0a44 7261 676f  ger.Rabbit.Drago
0000070: 6e0a 536e 616b 650a 486f 7273 650a 476f  n.Snake.Horse.Go
0000080: 6174 0a4d 6f6e 6b65 790a 526f 6f73 7465  at.Monkey.Rooste
0000090: 720a 446f 670a 5069 6727 6e2f 3d6e 5e32  r.Dog.Pig'n/=n^2
00000a0: 2f35 2527 576f 6f64 0a46 6972 650a 4561  /5%'Wood.Fire.Ea
00000b0: 7274 680a 4d65 7461 6c0a 5761 7465 7227  rth.Metal.Water'
00000c0: 6e2f 3d6e 5e31 2627 5961 6e67 0a59 696e  n/=n^1&'Yang.Yin
00000d0: 276e 2f3d                                'n/=


An equivalent program which uses string escapes and is hence a bit longer is:

'-'/n*~\(31*+\:^19*"\xD4\xE3'Zt\x87\x8Cp\xCB\x99m\xA4\xF9\x19\xE9i\x9E\xD8\x18\xBBa\xC4\xE3"256base 3base^1922-<{(-}/30%21+>^5-+:^12%'Rat
Ox
Tiger
Rabbit
Dragon
Snake
Horse
Goat
Monkey
Rooster
Dog
Pig'n/=n^2/5%'Wood
Fire
Earth
Metal
Water'n/=n^1&'Yang
Yin'n/=


The interesting part is computing, given the date, whether it is before or after the Chinese New Year which falls in that Gregorian year. Taking inspiration from the Easter calculation algorithm which I've used on a previous question, I looked for a reasonably simple calculation of the New Year date. In the end I've gone with a simple formula which gets me most of the way there, plus a lookup table for the error term.

The simple calculation is day_of_year = 21 + (19 * year % 30). The error term ranges from 0 to 8 over the range of interest (1924 to 2044), and never changes from year to year by more than one, so the lookup table is encoded in balanced ternary and the error is computed by summing the error terms of a prefix of the table.

• Nice, shortest thus far, except for the Mathematica one is using an external resource. +1 – Eduard Florinescu Feb 19 '14 at 16:16

## C#, 630611604572 570 bytes, 120 years

### (add ~2⅔ bytes per additional year if you know the offset)

This is good for people born 31-Jan-1900 through 24-Jan-2020 and will likely crash outside of that range. Are there bonus points for the number of years covered?

string Z(DateTime date)
{
int[] days = new int[] {  3, 22, 11,  1, 19,  7, -3, 16,  5, -6, 13,  2,
21,  9, -2, 17,  6, -5, 14,  4, 23, 11,  0, 19,
8, -3, 16,  5, -5, 13,  2, 20,  9, -2, 17,  7,
-4, 14,  3, 22, 11, -1, 18,  8, -3, 16,  5, -6,
13,  1, 20,  9, -1, 17,  6, -4, 15,  3, 21, 11,
0, 18,  8, -3, 16,  5, -7, 12,  2, 20,  9, -1,
18,  6, -5, 14,  3, 21, 10,  0, 19,  8, -3, 16,
5, 23, 12,  1, 20,  9, -1, 18,  7, -5, 13,  3,
22, 10,  0, 19,  8, -4, 15,  4, -6, 12,  1, 21,
10, -2, 17,  6, -5, 13,  3, 22, 11,  0, 19,  8 };
string[] signs = "Rat,Ox,Tiger,Rabbit,Dragon,Snake,Horse,Goat,Monkey,Rooster,Dog,Pig".Split(',');
string[] elements = "Metal,Water,Wood,Fire,Earth".Split(',');
string[] polarities = new string[] { "Yang", "Yin" };
int year = date.Year - 1900;
int x = year - (date.DayOfYear < days[year] + 28 ? 1 : 0);
return signs[x % 12] + " - " + elements[x / 2 % 5] + " - " + polarities[x % 2];
}


Or condensed (with added line breaks):

string Z(DateTime d){
int y=d.Year-1900,
x=y-(d.DayOfYear<new[]{3,22,11,1,19,7,-3,16,5,-6,13,2,21,9,-2,17,6,-5,14,4,23,11,0,19,8,-3,16,5,-5,13,2,20,9,-2,17,7,-4,14,3,22,11,-1,18,8,-3,16,5,-6,13,1,20,9,-1,17,6,-4,15,3,21,11,0,18,8,-3,16,5,-7,12,2,20,9,-1,18,6,-5,14,3,21,10,0,19,8,-3,16,5,23,12,1,20,9,-1,18,7,-5,13,3,22,10,0,19,8,-4,15,4,-6,12,1,21,10,-2,17,6,-5,13,3,22,11,0,19,8}[y]+28?1:0);
return "Rat,Ox,Tiger,Rabbit,Dragon,Snake,Horse,Goat,Monkey,Rooster,Dog,Pig".Split(',')[x%12]+" - "+"Metal,Water,Wood,Fire,Earth".Split(',')[x/2%5]+" - "+new[]{"Yang","Yin"}[x%2];
}


One of the tricks was to have the offset table's origin at 28-Jan. This proved to have the lowest character count.

If by chance it is insisted the input is a string, add 22 characters to change the method signature to:

string Z(string i){


var d=DateTime.Parse(i);

• string.Split() Inspiration taken from Eduard Florinescu's answer.
• Save 5 characters by changing " - "+new[]{"Yang","Yin"}[x%2] to " - Y"+(x%2<1?"ang":"in") :) – Timwi Feb 2 '14 at 7:27