OSDev.org
https://forum.osdev.org/

RTC to POSIX time
https://forum.osdev.org/viewtopic.php?f=1&t=57154
Page 1 of 1

Author:  Xeno [ Sat Mar 09, 2024 5:12 pm ]
Post subject:  RTC to POSIX time

I am having trouble figuing out how to convert the time i get from the RTC to a Unix Timestamp.

Code:
typedef struct {
    uint8_t sec;
    uint8_t min;
    uint8_t hour;
    uint8_t day;
    uint8_t month;
    uint64_t year;
} human_time_t;

//Needs to be converted to time_t


Does anyone know how to do this???

Author:  nullplan [ Sat Mar 09, 2024 10:52 pm ]
Post subject:  Re: RTC to POSIX time

Ah, time. Yes, that gets quite complicated quite fast. The issue is mostly to do with leap days, actually. I am using an abstraction I call the GDN, or Gregorian Day Number (similar to Julian Day Number), which is the number of days since January 1, year 1 (which is a hypothetical date; it never happened; but it is a good abstraction).

In the Gregorian calendar, there are 365 days in a normal year, 366 days in a leap year. With the basis being year 1, the nice thing is that a leap year, if it occurs, will always be at the end of a four-year cycle. And a normal four-year cycle will have 1461 days. In the Gregorian calendar, of course, all centuries that are not divisible by 4 end in a non-leap cycle. But we can deal with that one layer further up. A normal century has 76 normal years and 24 leap years, making a total of 36,524 days. A leap century of course has 36,525 days. And a cycle, a group of four centuries, has 146,097 days.

With these definitions out of the way, converting a calendar year to a GDN is quite simple. I am assuming the normal C struct tm here, because it is the standard type for this. This means you may have to convert the RTC time into this standard type before hand. That likely means subtracting 1900 from the year and 1 from the month. I am further assuming that you have a signed 64-bit type for time_t, because otherwise you get into trouble in January of 2038.
Code:
/* returns the GDN of January 1 of a given year */
int year_to_gdn(int year, int *is_leap) {
  /* year is the Gregorian calendar year */
  assert(year >= 1); /* C integer math with negative numbers is weird. And you don't really work with Roman times, right? */
  assert(year < INT_MAX/146097); /* otherwise this overflows the calculation down there */
  year--; /* makes the upcoming calculations easier */
  int cycle = year / 400;
  int year_in_cycle = year % 400;
  int century = year_in_cycle / 100;
  int year_in_century = year_in_cycle % 100;
  int group = year_in_century / 4;
  int year_in_group = year_in_century % 4;
  if (is_leap) *is_leap = year_in_group == 3 && (year_in_century != 99 || year_in_cycle == 399);
  return 146097 * cycle + 36524 * century + 1461 * group + 365* year_in_group;
}
Now you only have to calculate the number of days since January 1, and you have the GDN of the day you wanted.
Code:
int tm_to_gdn(const struct tm *tm) {
  int is_leap;
  static const unsigned short month_to_gdn[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
  int gdn = year_to_gdn(tm->tm_year + 1900, &is_leap);
  gdn += month_to_gdn[tm->tm_mon] + (is_leap && tm->tm_mon > 1);
  gdn += tm->tm_mday - 1;
  return gdn;
}
And now with all of that out of the way, we do have to calculate the GDN of January 1, 1970. Well, 1970 is 4 cycles, 3 centuries, 17 groups and 1 year after the start of the calendar, so:
Code:
#define GDN_EPOCH 719162

And with all of that out of the way, the remaining steps for converting the GDN to UNIX time and adding the time component as well are actually very simple:
Code:
time_t tm_to_time(const struct tm *tm) {
  int gdn = tm_to_gdn(tm);
  return (gdn - GDN_EPOCH) * 86400LL + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
}
Leap seconds you don't really need to care about. Your computer likely stores time as UTC (there are very very few machines out there storing time as TAI), which ignores leap seconds. Indeed, the standard UNIX time APIs cannot show a leap second at all. Time stamps are kept in UTC, which has no leap seconds, and converted to struct tm with no additional info.

As for time zones, I was assuming the struct tm was in UTC. If not, then you have to find the timezone offset somehow and subtract it from the result to get UTC, because time_t is supposed to contain the time as UTC.

Page 1 of 1 All times are UTC - 6 hours
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/