The context is implementing conversion functions between Gregorian calendar dates and Julian dates.

Most (nearly all?) such implementations have restrictions on the dates - typically requiring that the Julian date be non-negative.

How do I implement such conversion functions without restricting the date in any way?

3 Replies 3

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;
}

What is the use case for having a date before day o which is earlier than 4000BC - does anything have a precision that needs a day?

Use case: astronomy, calculate the appearance of the night sky in ancient times. It's true you don't need an exact date for that, but you need something - typically any day of the year would do. Second use case: I saw a bug report recently for negative Julian dates, in the context of software that was modeling the progress of ice sheets over long time scales.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.