简体   繁体   English

无需系统库即可将 Unix 时间戳转换为日期

[英]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.我正在构建一个嵌入式项目,该项目在显示器上显示从 GPS 模块检索的时间,但我还想显示当前日期。 I currently have the time as a unix time stamp and the progject is written in C.我目前将时间作为 unix 时间戳,并且该项目是用 C 编写的。

I am looking for a way to calculate the current UTC date from the timestamp, taking leap years into account?我正在寻找一种从时间戳计算当前 UTC 日期的方法,同时考虑闰年? 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.请记住,这是针对没有 FPU 的嵌入式项目,因此模拟浮点数学,为了性能需要尽可能避免它。

EDIT编辑

After looking at @R...'s code, I decided to have a go a writing this myself and came up with the following.在查看@R... 的代码后,我决定自己写一个并提出以下内容。

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

First divide by 86400;先除以86400; the remainder can be used trivially to get the HH:MM:SS part of your result.余数可以简单地用于获得结果的 HH:MM:SS 部分。 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;现在,自 1970 年 1 月 1 日以来,您还剩下一些天数。然后我会将其调整为自 2000 年 3 月 1 日以来的天数(可能为负数); 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.这是因为 2000 是 400 的倍数,即闰年周期,使用除法可以轻松(或至少更容易)计算过去了多少个闰年。

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 http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c?h=v0.9.15

Here's a portable implementation of mktime() .这是mktime()的可移植实现。 It includes support for DST that you might remove in order reduce the size somewhat for UTC only.它包括对 DST 的支持,您可能会删除该支持,以便仅针对 UTC 减少一些大小。 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.它还规范化数据(例如,如果您有 65 秒,它将增加分钟并将秒设置为 5,因此可能有一些您不需要的开销。

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).我可能会将两者都实现为测试(在 PC 上而不是嵌入式)并遍历大范围的纪元时间值并将结果与​​ PC 编译器自己的std::mktime (使用 C++ 将避免名称冲突而不必改名)。 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.我认为典型的库mktime执行二进制收敛比较localtime()与目标的返回。 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.这比直接日历计算效率低,但我认为这样做是为了确保从struct tmtime_t (反之亦然)和返回的往返转换产生相同的结果。 The portable implementation I suggested above uses the same convergence technique but replaces localtime() to remove library dependencies.我上面建议的可移植实现使用相同的收敛技术,但替换localtime()以删除库依赖项。 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.因此,经过反思,我怀疑直接计算方法在您的情况下更可取,因为您不需要可逆性 - 只要它当然是正确的。

It is not clear why you cannot use the standard library; 目前尚不清楚为什么你不能使用标准库; if size is an issue, only the library components used by your code will be linked, and time_t to struct tm conversion will be pretty small, but more importantly correct and eficient. 如果大小是个问题,那么只有代码使用的库组件会被链接,而time_tstruct tm转换将非常小,但更重要的是正确和有效。

However a better question is why you would not use the data directly from the GPS module? 但是,更好的问题是为什么不直接使用GPS模块中的数据? It is in the eighth field of the standard RMC sentence that is almost certainly output by the device. 它位于标准RMC句子的第八个字段中,几乎可以肯定由设备输出。

Example for 25 November 2011: 2011年11月25日的例子:

$GPRMC,001225,A,2832.1834,N,08101.0536,W,12,25, 251211 ,1.2,E,A*03 $ GPRMC,001225,A,2832.1834,N,08101.0536,W,12,25,251211,1.2,E,A * 03

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM