Lịch Âm – Lịch Dương

Mã nguồn về lịch Âm – lịch Dương trong bài này được phát triễn bởi một Việt kiều người Đức. Anh viết ứng dụng theo phong cách người Việt từ cách đọc ngày tháng đến số liệu thiên văn theo múi giờ GMT+7

Tôi đóng gói thành class và chỉnh sửa đôi chút. Có 2 hàm public là solar2lunar và lunar2solar dùng để đổi ngày Dương lịch sang Âm lich và ngược lại.

Một thí dụ là dùng script PHP để tính một ngày Âm Lịch trong tương lai:

// Ngày DL 30 ngày sau hôm nay
$d = explode('-', date('d-m-Y', strtotime('+30 day')));
// Đổi sang AL
$sl = new SolarLunar();
$AL = $sl->solar2lunar($d[0], $d[1], $d[2], 7.0);
echo $AL[0] . '/' . $AL[1] . '/' . $AL[2];

Trong đó 7.0 là múi giờ GMT.

<?php
class SolarLunar {
  private function jdFromDate($dd, $mm, $yy) {
    $a = floor((14 - $mm) / 12);
    $y = $yy + 4800 - $a;
    $m = $mm + 12 * $a - 3;
    $jd = $dd + floor((153 * $m + 2) / 5) + 365 * $y + floor($y / 4) - floor($y / 100) + floor($y / 400) - 32045;
    if ($jd < 2299161) {
      $jd = $dd + floor((153* $m + 2)/5) + 365 * $y + floor($y / 4) - 32083;
    }
    return $jd;
  }

  private function jdToDate($jd) {
    if ($jd > 2299160) { // After 5/10/1582, Gregorian calendar
      $a = $jd + 32044;
      $b = floor((4*$a+3)/146097);
      $c = $a - floor(($b*146097)/4);
    } else {
      $b = 0;
      $c = $jd + 32082;
    }
    $d = floor((4*$c+3)/1461);
    $e = $c - floor((1461*$d)/4);
    $m = floor((5*$e+2)/153);
    $day = $e - floor((153*$m+2)/5) + 1;
    $month = $m + 3 - 12*floor($m/10);
    $year = $b*100 + $d - 4800 + floor($m/10);
    return array($day, $month, $year);
  }

  private function getNewMoonDay($k, $timeZone) {
    $T = $k/1236.85; // Time in Julian centuries from 1900 January 0.5
    $T2 = $T * $T;
    $T3 = $T2 * $T;
    $dr = M_PI/180;
    $Jd1 = 2415020.75933 + 29.53058868*$k + 0.0001178*$T2 - 0.000000155*$T3;
    $Jd1 = $Jd1 + 0.00033*sin((166.56 + 132.87*$T - 0.009173*$T2)*$dr); // Mean new moon
    $M = 359.2242 + 29.10535608*$k - 0.0000333*$T2 - 0.00000347*$T3; // Sun's mean anomaly
    $Mpr = 306.0253 + 385.81691806*$k + 0.0107306*$T2 + 0.00001236*$T3; // Moon's mean anomaly
    $F = 21.2964 + 390.67050646*$k - 0.0016528*$T2 - 0.00000239*$T3; // Moon's argument of latitude
    $C1=(0.1734 - 0.000393*$T)*sin($M*$dr) + 0.0021*sin(2*$dr*$M);
    $C1 = $C1 - 0.4068*sin($Mpr*$dr) + 0.0161*sin($dr*2*$Mpr);
    $C1 = $C1 - 0.0004*sin($dr*3*$Mpr);
    $C1 = $C1 + 0.0104*sin($dr*2*$F) - 0.0051*sin($dr*($M+$Mpr));
    $C1 = $C1 - 0.0074*sin($dr*($M-$Mpr)) + 0.0004*sin($dr*(2*$F+$M));
    $C1 = $C1 - 0.0004*sin($dr*(2*$F-$M)) - 0.0006*sin($dr*(2*$F+$Mpr));
    $C1 = $C1 + 0.0010*sin($dr*(2*$F-$Mpr)) + 0.0005*sin($dr*(2*$Mpr+$M));
    if ($T < -11) {
      $deltat= 0.001 + 0.000839*$T + 0.0002261*$T2 - 0.00000845*$T3 - 0.000000081*$T*$T3;
    } else {
      $deltat= -0.000278 + 0.000265*$T + 0.000262*$T2;
    };
    $JdNew = $Jd1 + $C1 - $deltat;
    return floor($JdNew + 0.5 + $timeZone/24);
  }

  private function getSunLongitude($jdn, $timeZone) {
    $T = ($jdn - 2451545.5 - $timeZone/24) / 36525; // Time in Julian centuries from 2000-01-01 12:00:00 GMT
    $T2 = $T * $T;
    $dr = M_PI/180; // degree to radian
    $M = 357.52910 + 35999.05030*$T - 0.0001559*$T2 - 0.00000048*$T*$T2; // mean anomaly, degree
    $L0 = 280.46645 + 36000.76983*$T + 0.0003032*$T2; // mean longitude, degree
    $DL = (1.914600 - 0.004817*$T - 0.000014*$T2)*sin($dr*$M);
    $DL = $DL + (0.019993 - 0.000101*$T)*sin($dr*2*$M) + 0.000290*sin($dr*3*$M);
    $L = $L0 + $DL; // true longitude, degree
    $omega = 125.04 - 1934.136 * $T;
    $L = $L - 0.00569 - 0.00478 * sin($omega * $dr);
    $L = $L*$dr;
    $L = $L - M_PI*2*(floor($L/(M_PI*2))); // Normalize to (0, 2*PI)
    return floor($L/M_PI*6);
  }

  private function getLunarMonth11($yy, $timeZone) {
    $off = $this->jdFromDate(31, 12, $yy) - 2415021;
    $k = floor($off / 29.530588853);
    $nm = $this->getNewMoonDay($k, $timeZone);
    $sunLong = $this->getSunLongitude($nm, $timeZone); // sun longitude at local midnight
    if ($sunLong >= 9) { $nm = $this->getNewMoonDay($k-1, $timeZone); }
    return $nm;
  }

  private function getLeapMonthOffset($a11, $timeZone) {
    $k = floor(($a11 - 2415021.076998695) / 29.530588853 + 0.5);
    $last = 0;
    $i = 1; // We start with the month following lunar month 11
    $arc = $this->getSunLongitude($this->getNewMoonDay($k + $i, $timeZone), $timeZone);
    do {
      $last = $arc;
      $i = $i + 1;
      $arc = $this->getSunLongitude($this->getNewMoonDay($k + $i, $timeZone), $timeZone);
    } while ($arc != $last && $i < 14);
    return $i - 1;
  }

  public function solar2lunar($dd, $mm, $yy, $timeZone) {
    $dayNumber = $this->jdFromDate($dd, $mm, $yy);
    $k = floor(($dayNumber - 2415021.076998695) / 29.530588853);
    $monthStart = $this->getNewMoonDay($k+1, $timeZone);
    if ($monthStart > $dayNumber) { $monthStart = $this->getNewMoonDay($k, $timeZone); }
      $a11 = $this->getLunarMonth11($yy, $timeZone);
    $b11 = $a11;
    if ($a11 >= $monthStart) {
      $lunarYear = $yy;
      $a11 = $this->getLunarMonth11($yy-1, $timeZone);
    } else {
      $lunarYear = $yy+1;
      $b11 = $this->getLunarMonth11($yy+1, $timeZone);
    }
    $lunarDay = $dayNumber - $monthStart + 1;
    $diff = floor(($monthStart - $a11)/29);
    $lunarLeap = 0;
    $lunarMonth = $diff + 11;
    if ($b11 - $a11 > 365) {
      $leapMonthDiff = $this->getLeapMonthOffset($a11, $timeZone);
      if ($diff >= $leapMonthDiff) {
        $lunarMonth = $diff + 10;
        if ($diff == $leapMonthDiff) { $lunarLeap = 1; }
      }
    }
    if ($lunarMonth > 12) { $lunarMonth = $lunarMonth - 12; }
    if ($lunarMonth >= 11 && $diff < 4) { $lunarYear -= 1; }
    return array($lunarDay, $lunarMonth, $lunarYear, $lunarLeap);
  }

  public function lunar2solar($lunarDay, $lunarMonth, $lunarYear, $lunarLeap, $timeZone) {
    if ($lunarMonth < 11) {
      $a11 = $this->getLunarMonth11($lunarYear-1, $timeZone);
      $b11 = $this->getLunarMonth11($lunarYear, $timeZone);
    } else {
      $a11 = $this->getLunarMonth11($lunarYear, $timeZone);
      $b11 = $this->getLunarMonth11($lunarYear+1, $timeZone);
    }
    $k = floor(0.5 + ($a11 - 2415021.076998695) / 29.530588853);
    $off = $lunarMonth - 11;
    if ($off < 0) { $off += 12; }
    if ($b11 - $a11 > 365) {
      $leapOff = $this->getLeapMonthOffset($a11, $timeZone);
      $leapMonth = $leapOff - 2;
      if ($leapMonth < 0) { $leapMonth += 12; }
      if ($lunarLeap != 0 && $lunarMonth != $leapMonth) { return array(0, 0, 0); }
      elseif ($lunarLeap != 0 || $off >= $leapOff) { $off += 1; }
    }
    $monthStart = $this->getNewMoonDay($k + $off, $timeZone);
    $day = $this->jdToDate($monthStart + $lunarDay - 1);
    array_unshift($day,$this->weekday($day[0],$day[1],$day[2]));
    return $day;
  }

  private function weekday($d,$m,$Y){
    date_default_timezone_set('Asia/Ho_Chi_Minh');
    $weekday = date("l", strtotime("$Y/$m/$d"));
    $weekday = strtolower($weekday);
    switch($weekday) {
      case 'monday': $weekday = 'Thứ hai'; break;
      case 'tuesday': $weekday = 'Thứ ba'; break;
      case 'wednesday': $weekday = 'Thứ tư'; break;
      case 'thursday': $weekday = 'Thứ năm'; break;
      case 'friday': $weekday = 'Thứ sáu'; break;
      case 'saturday': $weekday = 'Thứ bảy'; break;
      default: $weekday = 'Chủ nhật';
    }
    return $weekday;
  }
} // end of class

Comments Off on Lịch Âm – Lịch Dương

Filed under Software

Comments are closed.