Calculate Chinese Zodiac sign

10

6

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.

Eduard Florinescu

Posted 2014-01-21T23:40:33.680

Reputation: 1 863

3Do you have a convenient link to how to calculate the start of a zodiac year? – Hand-E-Food – 2014-01-21T23:46:07.077

@Hand-E-Food Unfortunately no, I am trying to figure out and come with details, but other than that it seems pretty simple. – Eduard Florinescu – 2014-01-21T23:52:06.290

3You 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? – None – 2014-01-22T00:07:03.177

@LegoStormtroopr "other than that seems pretty simple" i.e. the beginning of the new Chinese year. I provided the link to Wikipedia an there is the table which is to big to bring it up here – Eduard Florinescu – 2014-01-22T00:14:54.887

1Another thing to note is that it is always between Jan 21 and Feb 21 inclusive. – Justin – 2014-01-22T00:32:11.260

@Quincunx It has to do with the new moon http://en.wikipedia.org/wiki/New_moon#Determining_new_moons:_an_approximate_formula

– Eduard Florinescu – 2014-01-22T00:42:34.537

This and this might be useful. – plannapus – 2014-01-22T08:41:42.470

3The 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 – 2014-01-22T09:57:26.330

2Eduard 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 – 2014-01-22T18:32:50.007

I missed the latest edit; it fixes the previous problems, but introduces one new ambiguity: given that the requirement is now to take a date as input, can people opt to take dates in a sensible (e.g. ISO 8601) format as opposed to U.S. middle-endian? – Peter Taylor – 2014-02-01T19:23:45.220

@PeterTaylor you can opt to ISO 8601 see edit, but the question is closed – Eduard Florinescu – 2014-02-01T20:34:19.293

Answers

4

Python 2: 360* 387 586 594 - 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.

Eduard Florinescu

Posted 2014-01-21T23:40:33.680

Reputation: 1 863

2Did you notice your base-36 number contains “math” twice? :) – Timwi – 2014-02-02T07:36:15.713

1@Timwi :D No, I didn't, and that is both nice and spooky! – Eduard Florinescu – 2014-02-02T07:54:06.880

1@Timwi 1math6 and 2math6. Even the preceeding 5 and the following 3 are mirrored with different chars inbetween. That's 3spooky5me. – Magic Octopus Urn – 2019-04-18T16:23:00.573

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 – 2014-12-05T15:55:49.923

@LMNYC Every letter in the string contains the day date of the beginning of the Chinese year between 1924 and 2043 in base 36, if the day is less than 21 then it is February else it is January.

Basically this is my way of compressing all those dates. So this is not an year calculation but more like just storing the dates, with a lunar-solar year calculation this will be way bigger – Eduard Florinescu – 2014-12-05T16:06:04.323

Very cool and even more so when the string math shows up twice, as somebody indicated before. Thank you very much for the explanation. – Luis Miguel – 2014-12-05T17:28:17.923

1

You can save another 23 characters with some microoptimisations. See http://www.reddit.com/r/Python/comments/2oqobw/code_golf_calculate_chinese_zodiac_sign/cmppcy1

– Brian – 2014-12-09T13:54:17.920

@Brian Great tips, thanks, can I put them in the answer? – Eduard Florinescu – 2014-12-09T14:05:46.740

Sure, go ahead. – Brian – 2014-12-09T17:50:04.620

4

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)"

Yves Klett

Posted 2014-01-21T23:40:33.680

Reputation: 251

The breadth of Mathematica amazes me. Your output format is incorrect, but I'm sure that's easily fixed. – Darren Stone – 2014-01-22T17:47:01.503

1@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 – 2014-01-22T18:02:02.713

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 – 2014-01-22T18:25:37.333

@DarrenStone true. This will certainly add quite a bit of overhead. W&C... – Yves Klett – 2014-01-22T18:27:02.780

4

C# – 357 337 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");
}

Mormegil

Posted 2014-01-21T23:40:33.680

Reputation: 1 148

+1 Also, you can save over 20 characters by avoiding string.Format. – Hand-E-Food – 2014-01-22T22:42:37.150

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 – 2014-01-23T09:37:50.927

3

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.

Darren Stone

Posted 2014-01-21T23:40:33.680

Reputation: 5 072

What the hell is that "magic" number? – tomsmeding – 2014-02-19T15:23:19.653

1@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 – 2014-02-19T19:02:51.727

3

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.

Peter Taylor

Posted 2014-01-21T23:40:33.680

Reputation: 41 901

Nice, shortest thus far, except for the Mathematica one is using an external resource. +1 – Eduard Florinescu – 2014-02-19T16:16:51.750

2

C#, 630 611 604 572 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){

and add the line:

var d=DateTime.Parse(i);

EDITS:

  • Put all strings in one array and added offsets to the output selectors.
  • Changed the day offset to 28-Jan.
  • string.Split() Inspiration taken from Eduard Florinescu's answer.
  • Inlined all arrays. It only saved me 2 characters. :-/

Hand-E-Food

Posted 2014-01-21T23:40:33.680

Reputation: 7 912

Save 5 characters by changing " - "+new[]{"Yang","Yin"}[x%2] to " - Y"+(x%2<1?"ang":"in") :) – Timwi – 2014-02-02T07:27:23.427

-1

Pretty straight forward with PHP using Switch statement.

  • Calculation is as far as you want to...

    <?php
    
    // input the year
    $Y = 2020;
    
    // calculate the Z sign
    switch (($Y - 4) % 12) {
      case  0: $z = 'Rat';break;
      case  1: $z = 'Ox';break;
      case  2: $z = 'Tiger';break;
      case  3: $z = 'Rabbit';break;
      case  4: $z = 'Dragon';break;
      case  5: $z = 'Snake';break;
      case  6: $z = 'Horse';break;
      case  7: $z = 'Goat';break;
      case  8: $z = 'Monkey';break;
      case  9: $z = 'Rooster'; break;
      case 10: $z = 'Dog';break;
      case 11: $z = 'Pig';break;
    }
    
    // calculate the e
    switch (($Y / 2) % 5) {
      case  0: $e = 'Metal';break;
      case  1: $e = 'Water';break;
      case  2: $e = 'Wood'; break;
      case  3: $e = 'Fire'; break;
      case  4: $e = 'Earth'; break;
    }
    
    // calculate the polarity
     switch ($Y % 2) {
        case  0: $p = 'Yang';break;
        case  1: $p = 'Yin';break;
    
    }
    
     echo '$Y is $z - $e - $p.';
    

MACboyet

Posted 2014-01-21T23:40:33.680

Reputation: 1

1

Welcome to PPCG! We require all answers to be serious competitors, so in the case of code-golf, we require answers to make an effort on shortening the code. For example, single letter variable names, removing extra whitespace, etc

– Embodiment of Ignorance – 2019-04-18T15:27:18.343

1Welcome to PPCG, your code works only for years not complete dates – Eduard Florinescu – 2019-04-18T15:55:02.590

that is true. It is harder than I think… – MACboyet – 2019-04-19T05:55:54.877

removed the spaces and shorten the var already. thanks for the info – MACboyet – 2019-04-19T06:00:04.660