Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert unix timestamp to date without system libs

I am building a embedded project which displays the time retrieved from a GPS module on a display, but I would also like to display the current date. I currently have the time as a unix time stamp and the progject is written in C.

I am looking for a way to calculate the current UTC date from the timestamp, taking leap years into account? Remember, this is for an embedded project where there is no FPU, so floating point math is emulated, avoiding it as much as possible for performance is required.

EDIT

After looking at @R...'s code, I decided to have a go a writing this myself and came up with the following.

void calcDate(struct tm *tm)
{
  uint32_t seconds, minutes, hours, days, year, month;
  uint32_t dayOfWeek;
  seconds = gpsGetEpoch();

  /* calculate minutes */
  minutes  = seconds / 60;
  seconds -= minutes * 60;
  /* calculate hours */
  hours    = minutes / 60;
  minutes -= hours   * 60;
  /* calculate days */
  days     = hours   / 24;
  hours   -= days    * 24;

  /* Unix time starts in 1970 on a Thursday */
  year      = 1970;
  dayOfWeek = 4;

  while(1)
  {
    bool     leapYear   = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
    uint16_t daysInYear = leapYear ? 366 : 365;
    if (days >= daysInYear)
    {
      dayOfWeek += leapYear ? 2 : 1;
      days      -= daysInYear;
      if (dayOfWeek >= 7)
        dayOfWeek -= 7;
      ++year;
    }
    else
    {
      tm->tm_yday = days;
      dayOfWeek  += days;
      dayOfWeek  %= 7;

      /* calculate the month and day */
      static const uint8_t daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
      for(month = 0; month < 12; ++month)
      {
        uint8_t dim = daysInMonth[month];

        /* add a day to feburary if this is a leap year */
        if (month == 1 && leapYear)
          ++dim;

        if (days >= dim)
          days -= dim;
        else
          break;
      }
      break;
    }
  }

  tm->tm_sec  = seconds;
  tm->tm_min  = minutes;
  tm->tm_hour = hours;
  tm->tm_mday = days + 1;
  tm->tm_mon  = month;
  tm->tm_year = year;
  tm->tm_wday = dayOfWeek;
}
like image 267
Geoffrey Avatar asked Feb 06 '14 03:02

Geoffrey


People also ask

Can Excel convert Unix timestamp?

Convert date to timestampSelect a blank cell, suppose Cell C2, and type this formula =(C2-DATE(1970,1,1))*86400 into it and press Enter key, if you need, you can apply a range with this formula by dragging the autofill handle. Now a range of date cells have been converted to Unix timestamps.

Are all Unix timestamps in UTC?

A few things you should know about Unix timestamps:Unix timestamps are always based on UTC (otherwise known as GMT). It is illogical to think of a Unix timestamp as being in any particular time zone. Unix timestamps do not account for leap seconds.


2 Answers

First divide by 86400; the remainder can be used trivially to get the HH:MM:SS part of your result. Now, you're left with a number of days since Jan 1 1970. I would then adjust that by a constant to be the number of days (possibly negative) since Mar 1 2000; this is because 2000 is a multiple of 400, the leap year cycle, making it easy (or at least easier) to count how many leap years have passed using division.

Rather than trying to explain this in more detail, I'll refer you to my implementation:

http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c?h=v0.9.15

like image 68
R.. GitHub STOP HELPING ICE Avatar answered Oct 16 '22 08:10

R.. GitHub STOP HELPING ICE


Here's a portable implementation of mktime(). It includes support for DST that you might remove in order reduce the size somewhat for UTC only. It also normalizes the data (so if for example you had 65 seconds, it would increment the minute and set the seconds to 5, so perhaps has some overhead that you don't need.

It seems somewhat more complex than the solution you have arrived at already; you may want to consider whether there is a reason for that? I would perhaps implement both as a test (on a PC rather than embedded) and iterate through a large range of epoch time values and compare the results with the PC compiler's own std::mktime (using C++ will avoid the name clash without having to rename). If they all produce identical results, then use the fastest/smallest implementation as required, otherwise use the one that is correct!

I think that the typical library mktime performs a binary convergence comparing the return of localtime() with the target. This is less efficient than a direct calendrical calculation, but I presume is done to ensure that a round-trip conversion from struct tm to time_t (or vice versa) and back produces the same result. The portable implementation I suggested above uses the same convergence technique but replaces localtime() to remove library dependencies. On reflection therefore, I suspect that the direct calculation method is preferable in your case since you don't need reversibility - so long as it is correct of course.

like image 43
Clifford Avatar answered Oct 16 '22 08:10

Clifford