Go Down

Topic: Julian/Gregorian calendar conversion working partially... (Read 502 times) previous topic - next topic

pcborges

Hi, I ported the conversion code from https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html to include in my Arduino project.

The conversion of the dates suggested on the page (1582 October 15) work OK back and forth but when I challenge the code to convert 2020 February 29 it crashes and return wrong date.

It converts well from 2020 February 29 (leap year) to JD: 2458908.50 but when I test the convertion back to calendar day it returns 2021 February 31.

On the site above there are some instructions on how to deal with multiplications and divisions on the Gregorian part of the conversion but so far I did not get it to work.

Appreciate any help, test code included.

Thanks

Code: [Select]


int   gDay, gMonth, gYear;

void setup() {
  Serial.begin(38400);

  int day = 29;
  int month = 2;
  int year = 2020;

  float jD;
  Serial.print("Julian date for ");
  Serial.print(day);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.print(year);
  Serial.print(": ");
  jD = julianDate(year, month, day);
  Serial.println(jD);
  Serial.println("");
  //********************************************************************************

  Serial.println("Back to Gregorian...");
  Serial.println("");
 
  gregorianDate(jD);
 
  Serial.print("Day: ");
  Serial.println(gDay);

  Serial.print("Month: ");
  Serial.println(gMonth);

  Serial.print("Year: ");
  Serial.println(gYear);
}

void loop() {
}


float julianDate(int year, int month, int day){

  if(month==1||month==2){
    year=year-1;
    month=month+12;
  }

  int a = year/100;
  int b = a/4;
  int c = 2-a+b;
  long e = 365.25 * (year + 4716);
  long f = 30.6001 * (month+1);
  return (float)c + (float)day + (float)e + (float)f - 1524.5; 
}


void gregorianDate(float jD) {

  Serial.print("jD: ");
  Serial.println(jD);

  long Q = jD + 0.5;

  Serial.print("Q: ");
  Serial.println(Q);

  long  Z = Q; //Integer part of Q

  Serial.print("Z: ");
  Serial.println(Z);

  long W = (Z - 1867216.25) / 36524.25;

  Serial.print("W: ");
  Serial.println(W);

  long X = W / 4;

  Serial.print("X: ");
  Serial.println(X); 

  long A = Z + 1 + W - X;

  Serial.print("A: ");
  Serial.println(A);

  long B = A + 1524;

  Serial.print("B: ");
  Serial.println(B);

  long C = (B - 122.1) / 365.25;

  Serial.print("C: ");
  Serial.println(C);

  long D = 365.25 * C;

  Serial.print("D: ");
  Serial.println(D);

  long E = (B - D) / 30.6001;

  Serial.print("E: ");
  Serial.println(E);

  long F = 30.6001 * E;

  Serial.print("F: ");
  Serial.println(F);
 
  gDay = B - D - F + (Q - Z);

  if (E - 1 >= 1 || E - 1 <= 12) {
    gMonth = E - 1;
  } else {
    gMonth = E - 13;
  }
 
  if (gMonth == 1 || gMonth == 2) {
    gYear = C - 4715;
  } else {
    gYear = C - 4716;
  }
}



PaulS

Quote
but when I challenge the code to convert 2020 February 29 it crashes and return wrong date.
If the Arduino crashed, it wouldn't have returned anything. So, clearly, this statement is wrong.

How many significant digits are there in a float on the Arduino? How many are in 2458908.50? Are they the same? Is either one reasonable, for your purposes?
The art of getting good answers lies in asking good questions.

PaulS

Code: [Select]
  long  Z = Q; //Integer part of Q
The integer portion of an integer?
The art of getting good answers lies in asking good questions.

pcborges

Dear PaulS, thanks for your response.

When I say crashes I am referring to the logic of the code and not the hardware.

About the "long  Z = Q; //Integer part of Q"...

The original code on the page I mention in my original post makes no reference to variable types so I have been trying options based on other examples I find at the internet.

The code that I ended up posting is one of the tries to see what would happen and the comment "//Integer part of Q" is part of the original code I found at "https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html"

About your question:
How many significant digits are there in a float on the Arduino? How many are in 2458908.50? Are they the same? Is either one reasonable, for your purposes?

In some tests I made some of these calculation result in more than 2 decimal digits that I do not really understand what they are used for, I do not directly make use of them but my understanding is that it is important to assure I can reliably convert from Julian to Gregorian back to Julian.

The text on the page says I should discard all decimal places resulting from multiplications and divisions, I am still not sure how do I do that and thus trying to play around with variable types.

As the code comes from a reliable source I guess the problem may be related with variable type.

Thanks
Paulo

PaulS

Quote
As the code comes from a reliable source I guess the problem may be related with variable type.
Or the limitations of the microcontroller.

Look at the documentation for the float type on the Arduino. A float has 6 or 7 digits of precision (NOT digits after the decimal point).

Code: [Select]
  long W = (Z - 1867216.25) / 36524.25;
36524.25 might be stored accurately. 1867216.25 will not be. Calculations that expect that value to be stored accurately in a float will not produce the correct results.
The art of getting good answers lies in asking good questions.

pcborges

Thanks again PaulS.

I have been running some more tests and noticed that only leap years return wrong values.
If I use 2016 February 28 instead of 29 it works good, the same if the year is 2020, if the day submitted is 29 it will return 31.

I believe there are 3 potential reasons for the problem that I would like to submit to your advising:

1- The code is buggy for this specific condition and I should look for this bug.
2- Limitation of the microcontroler
3- Something related with variable types as you mention and I should invest time understanding what they support and interaction between them.

NOTE:
Just tested "https://quasar.as.utexas.edu/BillInfo/JulianDateCalc.html" and simulated the dates I am having problem with and it works. So I assume only options 2 and 3 above are to be considered.

Thanks
Paulo

johnwasser

These formulas, originally in FORTRAN seem to work fine in long integers.  They convert 2020-2-29 to 2458909 and then back to 2020-2-29

http://aa.usno.navy.mil/faq/docs/JD_Formula.php

Code: [Select]
void setup()
{
  Serial.begin(115200);


  long year = 2020;
  long month = 2;
  long day = 29;


  long  JD = day - 32075 +
             1461 * (year + 4800 + (month - 14) / 12) / 4 +
             367 * (month - 2 - (month - 14) / 12 * 12) / 12 -
             3 * ((year + 4900 + (month - 14) / 12) / 100) / 4;


  Serial.println(JD);


  long L = JD + 68569;
  long N = 4 * L / 146097;
  L = L - (146097 * N + 3) / 4;
  year = 4000 * (L + 1) / 1461001;
  L = L - 1461 * year / 4 + 31;
  month = 80 * L / 2447;
  day = L - 2447 * month / 80;
  L = month / 11;
  month = month + 2 - 12 * L;
  year = 100 * (N - 49) + year + L;


  Serial.print(year);
  Serial.print("-");
  Serial.print(month);
  Serial.print("-");
  Serial.println(day);
}


void loop() {}
Send Bitcoin tips to: 1G2qoGwMRXx8az71DVP1E81jShxtbSh5Hp

odometer

#7
Jul 11, 2018, 05:43 pm Last Edit: Jul 11, 2018, 05:47 pm by odometer Reason: clarification and formatting
It looks like the code in the top post in this thread is mindlessly copying a formula without trying to understand why or how it works.

Here is one of my old pieces of code, with comments. It is for converting year-month-day to a simple count of days.
Code: [Select]

int32_t daycount(int16_t y, int8_t m, int8_t d) {
  // Input is year, month, and day, in that order.
  // For contemporary years, use the four-digit year number.
  // The return value is a day number using an "endless" count of days.
 
  // sanity checking of input
  if ((y<-30000)||(y>30000)) return -999999999;
  if ((m<1)||(m>12))         return -999999999;
  if ((d<1)||(d>31))         return -999999999;
 
  // pretend Jan. and Feb. belong to previous year
  if (m<=2) {
    y--;
    m+=12;
  }
 
  // day count is unimaginatively named "n"
  int32_t n = 365 * (int32_t)y;
 
  // next we calculate leap days
  int16_t x = y >> 2; // to get floor(y/4) and not trunc(y/4)
  n += x; // 4-year rule
  x = ((x+8000)/25)-320; // workaround for braindead C/C++ division
  // now x equals floor(y/100)
  n -= x; // 100-year rule
  x >>= 2; // make it floor(y/400)
  n += x; // 400-year rule
 
  // next we take care of the months
  n += (31 * (int16_t)m);
  // adjust for the 30-day months
  if     (m>11) n-=4;
  else if (m>9) n-=3;
  else if (m>6) n-=2;
  else if (m>4) n-=1;
 
  // next we take care of the day of the month
  n += d;
 
  // return n; // this line is for testing purposes only
 
  // finally, we add or subtract a constant
  return (n + 1721026); // day 0 == -4713-11-24(Mon) (Julian Day at noon)
  return (n -      34); // day 0 ==  0000-01-01(Sat)
  return (n -     400); // day 0 ==  0001-01-01(Mon) (think REXX)
  return (n -  693993); // day 0 ==  1899-12-30(Sat) (think MS Excel)
  return (n -  719562); // day 0 ==  1970-01-01(Thu) (think Unix time)
  return (n -  730519); // day 0 ==  2000-01-01(Sat)
}

pcborges

Hi...

Dear johnwasser and odometer, thanks a lot for your time.

Both solutions work great, thanks.

I will do some work now to see how I can integrate one of them in my project.

Thanks again for your valuable contributions
Paulo

Go Up