One way is described here.
The general idea is to exploit the (near) symmetry of the calendar around special days, such as January 1, in the year 0.
The core of one conversion is (in C99) :
double cal_to_jd(int y, int m, double d) {
//completed years: small asymmetry between positive and negative years
int y_p = (y >= 0) ? (y - 1) : y; //y_p = y-prime
int num_366yrs = (y_p/4) - (y_p/100) + (y_p/CYCLE_YEARS); //Robin's clever trick
if (y > 0) {
num_366yrs += 1; //since year 0 is a leap year
}
int num_365yrs = y - num_366yrs;
double jd = num_365yrs * SHORT_YR + num_366yrs * LONG_YR;
//completed months
jd += DAYS_IN_PRECEDING_MONTHS[m-1];
int is_leap = (y % 100 == 0) ? (y % 400 == 0) : (y % 4 == 0);
jd += (is_leap && (m - 1) >= 2 ? 1 : 0); //'correct' for leap years
jd += d; // the day of the month
//rebase to the usual origin of Julian date
jd += JAN_0_YEAR_0;
return jd;
}
The inverse function is longer. It's core is (in C99):
void jd_to_cal(double jd, int *y, int *m, double *d) {
//1. find the closest 'base' that PRECEDES the given moment
int num_cycles = (int)floor((jd - JAN_1_YEAR_0)/CYCLE_DAYS); //rounds towards negative infinity: good!
double base_jd = JAN_1_YEAR_0 + num_cycles * CYCLE_DAYS; //a January 1.0 in the years .., -400, 0, 400, ..
int year = num_cycles * CYCLE_YEARS; // ..,-400, 0, 400,.. (the starting value)
double jd_minus_base = jd - base_jd; //never neg
//THE GAME: is to move this cursor forward from our base Jan 1.0 taken
//as the zero-point, to the target jd_minus_base
double cursor = 0.0;
//2. remainder-years: whole, completed years after the base
//one big chunk of years: calculate a MINIMUM number of full remainder-years, to reduce loop iterations below
int approx_days = (int)floor(jd_minus_base);
int more_years = (approx_days / LONG_YR) - 1; // at least this many
if (more_years > 0) {
int m_p = more_years - 1;
int more_days = more_years * SHORT_YR + (m_p/4) - (m_p/100) + (m_p/400) + 1;
cursor += more_days; //still on a Jan 1.0!
year += more_years;
}
//loop to find the rest of the remainder-years: at most 2 iterations here!
int year_so_far = year; //for use in the loop
for(int more = 0; more < CYCLE_YEARS; ++more ) {
int year_length = is_leap(year_so_far + more) ? LONG_YR : SHORT_YR;
if (cursor + year_length <= jd_minus_base) {
cursor += year_length; // Jan 1.0 of the next year
++year;
} else { break; }
}
//3. months and days
int month = 0; //both a loop index AND a result-value
double fractional_days = 0.0;
for(month = 1; month <= 12; ++month) {
int month_length = the_month_len(year, month);
if (cursor + month_length <= jd_minus_base) {
cursor += month_length; //1st day of the next month
}
else {
fractional_days = jd_minus_base - cursor + 1.0; break;
}
}
*y = year;
*m = month;
*d = fractional_days;
}