tags:

views:

155

answers:

2

I'm trying to convert Chinese lunar system to Gregorian using Perl, both for fun and for learning purposes. I thought there was some kind of easy mathematical algorithm to do the job but turned out I was wrong. Anyway, after a little googling around I came aross some ready SAS code that could do the job. Well, it was not too difficult for me to figure out how to rewrite most of the code in Perl. But there is something like this:

Convert2Gregorian = INTNX ('day', conDate, AddDay-1);

Google tells me that INTNX is a function that can return a date after which a certain date interval has been added. But using the keywords "Perl INTNX" gave me nothing useful. I then found a script written in Javascript, the general approach is about the same except that the Javascript uses Dateadd function, something like:

Return DateAdd(DateInterval.Day, AddDay - 1, conDate)

I also tried searching using PPM but was unable to find the module I wanted. Can someone kindly give me some pointers?

Thanks in advance :)

Update

Thanks to @psilva and @hobbs :)

Haha, finally I can translate one programming language to another. It's fun :) Just for fun and maybe for later reference: Here's the original SAS code and the Perl code translated by me. Correct me if I'm wrong :)

Note: Data incomplete.

SAS CODE IS AS FOLLOWS:

data DateNtoG (drop = Ngstrings conDate AddMonth AddDay Mrunyue I);
array NGlist{43} $18_TEMPORARY_(
"010110101010170130" /*(1968)*/
"010101101010000217" /*(1969)*/
"100101101101000206" /*(1970)*/
"010010101110150127" /*(1971)*/
);

input tYear tMonth tDay RunYue;

if (tyear >1967 and tyear<2011) then do;
NGstrings = NGlist(tYear - 1967);
conDate = MDY (substr (NGstrings,15,2),(NGstrings, 17,2), tYear);

AddMonth = tMonth;

select (substr(NGstrings, 14, 1));
when ("A" )  Mrunyue=10;
when ("B" )  Mrunyue=11;
when ( "C" ) Mrunyue=12;
otherwise Mrunyue = substr (NGstrings, 14,1);
end;

if ((RunYue=1) and (tMonth=Mrunyue) ANDMrunyue>0)or ((tMonth Mrunyue) AND Mrunyue>0) then
do;
Addmonth = tMonth+1;
end;

AddDay = tDay;
do i = 1 To AddMonth-1;
AddDay = AddDay + 29 + substr(NGstrings,i,1);
end;

dNtoG = INTNX ('day', conDate, AddDay - 1);
put "Transfereddate:" dNtoGdate9.;
end;

TRANSLATED Perl CODE IS AS FOLLOWS: didn't handle the undefined situations for the moment

(I Changed the original SAS variable names)

#!perl

# A Chinese-Gregorian Calendar System Converter just for Testing

use Date::Calc qw(Add_Delta_Days); 
use integer;
use strict;
use warnings;

$_ = shift @ARGV;

if (length == 8) {
    $_.=0;
}

my ($Lunar_Year,$Lunar_Month,$Lunar_Day,$Leap_Month) = /(\d\d\d\d)(\d\d)(\d\d)(\d)/;


my %Lunar_Year_Patterns = qw/1968 010110101010170130 1969 010101101010000217 1970 100101101101000206 1971 010010101110150127/;

if (substr ($Lunar_Year_Patterns{$Lunar_Year},13,1) =~ /A/) {
         $Leap_Month = 10;
} elsif (substr ($Lunar_Year_Patterns{$Lunar_Year},13,1)=~ /B/){
     $Leap_Month = 11;
} elsif (substr ($Lunar_Year_Patterns{$Lunar_Year},13,1)=~ /C/){
        $Leap_Month = 12;
} else {
$Leap_Month = substr($Lunar_Year_Patterns{$Lunar_Year},13,1);
}

my $First_Lunar_Day_In_Gregorian_Month = substr($Lunar_Year_Patterns{$Lunar_Year},14,2);
my $First_Lunar_Day_In_Gregorian_Day = substr($Lunar_Year_Patterns{$Lunar_Year},16,2);

my $AddMonth;
if ( (($Leap_Month ==1) && ($Lunar_Month == $Leap_Month) && ($Leap_Month > 0)) || (($Lunar_Month > $Leap_Month) && ($Leap_Month>0) ) ){
    $AddMonth = $Lunar_Month +1;
} else {
$AddMonth = $Lunar_Month;
}

my $AddDay;
$AddDay = $Lunar_Day;

for(my $i = 1; $i <= $AddMonth - 1; $i++){
$AddDay = $AddDay +29 + substr($Lunar_Year_Patterns{$Lunar_Year},$i,1);
}


my @Gregorian = Add_Delta_Days($Lunar_Year,$First_Lunar_Day_In_Gregorian_Month,$First_Lunar_Day_In_Gregorian_Day,$AddDay -1);
print @Gregorian;
+4  A: 

Check out the delta functions in Date::Calc and Date::Calc::Object, and Date::Calc::PP. Specifically, you might want to look at the DateCalc_add_delta_days subroutine in the PP source.

You could also try looking at the source of Calendar::China.

Pedro Silva
@psilva, thanks for the pointer. add_delta_days in Data::Calc seems to be what I was looking for. I'm trying it.
Mike
Avoid linking to version specific documents. The links can become stale easily.
daxim
@daxim: how do you link to non-version specific source code?
Pedro Silva
@psilva: link to http://search.cpan.org/perldoc/Module::Name or http://search.cpan.org/dist/Dist-Name and it will track the latest release version.
hobbs
+10  A: 

DateTime is the 800-pound gorilla of date handling. It's quite large, but it's large because it does a lot, and more importantly, it does it right.

With DateTime you would simply construct the DateTime object for the starting date, and then obtain the ending date by adding: $dt->add(days => $add_days).

Also, there's a DateTime::Calendar::Chinese that you can compare your results with, even if you want to reinvent this particular wheel for fun :)

hobbs
@hobbs, thanks for the pointer. I'll be trying DateTime. Thanks :)
Mike
+1 for DateTime. This is one of the Perl "features" that I miss most when I'm dealing with other languages.
Leonardo Herrera