Calculating Chinese zodiac signs based on birthdays I am attempting to write a function to calculate a user's Chinese zodiac sign based on his or her birthday for Drupal.

I originally had a function for calculating based on the Gregorian calendar (1924 = Rat, 1925 = Ox).  This was easy to write, but several of my users have complained that the Chinese zodiac is actually based on the lunar calendar, and a list of relevant start and end dates can be found here.

I started writing the code as follows.  The big problem is that I have an if, else if, and else per year, and at this rate I will have to manually code 100 years worth of birthdays.  On the other hand, I'm unsure of how to use a loop to iterate over this, because the day the lunar year starts and ends each year is different.  Any insight on how to make this more efficient would be highly appreciated.

function mymodule_calculate_chinese_zodiac($birthdate) {
  // Drupal MySQL data looks like this: 1981-07-30 00:00:00
  $year = substr($birthdate, 0, 4);
  $month = substr($birthdate, 5, 2);
  $day = substr($birthdate, 8, 2);

  if ($year == 1924) {
    if ($month &gt; 2) {
      // Rat
      $sign = "1";
    }
    else if (($month == 2) &amp;&amp; ($day &gt; 4) {
      $sign = "1";
    }
    else {
      // Pig
      $sign = "12";
    }
  }
  else if ($year == 1925) {
    if ($month &gt; 1) {
      // Ox
      $sign = "2";
    }
    else if (($month == 1) &amp;&amp; ($day &gt; 23)) {
      $sign = "2";
    }
    else {
      $sign = "1";
    }
  }
  else if ($year == 1926) {
    if ($month &gt; 2) {
      // Tiger
      $sign = "3";
    }
    else if ($month == 2) &amp;&amp; ($day &gt; 12) {
      $sign = "3";
    }
    else {
      $sign = "2";
    }
  }
  //All years until 2020
  return $sign;
}
To start, lets look for something that we can loop through. From your link to relevant dates, we can see that the associated animals loop through 12 animals, in order:

Rat
Ox
Tiger
Rabbit
Dragon
Snake
Horse
Goat
Monkey
Rooster
Dog
Pig


After [Pig], it restarts again with [Rat]. So let's put these into an associative array, with the associated element:

$assocs = array(
    "Rat"     =&gt;  "Yang Wood", 
    "Ox"      =&gt;  "Yin Wood", 
    "Tiger"   =&gt;  "Yang Fire",
    "Rabbit"  =&gt;  "Yin Fire", 
    "Dragon"  =&gt;  "Yang Earth", 
    "Snake"   =&gt;  "Yin Earth", 
    "Horse"   =&gt;  "Yang Metal", 
    "Goat"    =&gt;  "Yin Metal", 
    "Monkey"  =&gt;  "Yang Water", 
    "Rooster" =&gt;  "Yin Water", 
    "Dog"     =&gt;  "Yang Wood", 
    "Pig"     =&gt;  "Yin Wood" )
);


Now, to determine the sexagenary cycle of a gregorian year, the algorightm is as follows:

sCycle = (grego - 3) - (60 X ([(grego - 3)] / 60))


So, let's translate that to a PHP function:

function sexagenaryToGregorian($gYear){
    return ($gYear - 3) - (60 * (floor(( $gYear - 3) / 60)));
}


Now, we know that the Sexagenary cycle consists of 60 periods, thus, to determine what animal and element a certain year matches:

$bdayYear = 1989;
$sYear = sexagenaryToGregorian($bdayYear);
// $sYear == 6


For birth year 1989, sYear should be period 6. Now, let's generate a function that will hold the animal-element array above ($assoc) and contain two loops. The outer loop will decrement through the ceiling of the sexagenarian cycle divided by 12, and the inner loop is a foreach() over the animal-element array. This method is accurate all the way back to 4AD. 

function generateAnimal($sexaCycle){
    $assocs = array(
        "Rat"     =&gt;  "Yang Wood", 
        "Ox"      =&gt;  "Yin Wood", 
        "Tiger"   =&gt;  "Yang Fire",
        "Rabbit"  =&gt;  "Yin Fire", 
        "Dragon"  =&gt;  "Yang Earth", 
        "Snake"   =&gt;  "Yin Earth", 
        "Horse"   =&gt;  "Yang Metal", 
        "Goat"    =&gt;  "Yin Metal", 
        "Monkey"  =&gt;  "Yang Water", 
        "Rooster" =&gt;  "Yin Water", 
        "Dog"     =&gt;  "Yang Wood", 
        "Pig"     =&gt;  "Yin Wood"
    );

    $i = 1;
    while(ceil($sexaCycle / 12) &gt; 0){
        foreach($assocs as $animal =&gt; $element){
            if($i == $sexaCycle)
                return array($animal =&gt; $element);
            $i++;
        }
    }
}


Let's give it a few runs:

// Let's find the result for the year 2041
$result = generateAnimal(sexagenaryToGregorian(2041));
print_r($result); // Array ( [Rooster] =&gt; Yin Water )

// Let's find the result for the year 4
$result = generateAnimal(sexagenaryToGregorian(4));
print_r($result); // Array ( [Rat] =&gt; Yang Wood )

// Let's find the result for the year 1989
$result = generateAnimal(sexagenaryToGregorian(1989));
print_r($result); // Array ( [Snake] =&gt; Yin Earth )


Looks good to me! Now, if you want to include the dates, that's when you may want to start thinking about just using a database as a static lookup table.