Search code examples
ctimelibc

Changing the timezone in libc time at compile time


We have firmware that will be deployed on devices all over the world and that will be synced to UTC by a host device. To track time, we end up converting between struct tm and time_t (Unix time) because we use a hardware RTC whose registers closely mirror struct tm, but any time-based operations are done on Unix time. However, when we use mktime it attempts to put everything into localtime, which is not UTC on the build system. We could just add an offset, but it would be easier to just tell time.h that our local time should be UTC, since the device is otherwise agnostic to the local time.

Is there a (non-invasive) way to do this other than changing the local time on the build system to UTC? Like, can we somehow use tzset to inject this data? Or just set TZ to something? I'm having difficulty understanding how I would set TZ if I just wanted UTC.


Solution

  • However, when we use mktime it attempts to put everything into localtime ...

    The compiler's time zone setting at compile time is irrelevant to how code handles time at run time.

    The easiest way to avoid local time in conversions is to hope your compiler offers struct_tm (UTC) to time_t, instead of using mktime() (time zone dependent) as an extension like time_t timegm(struct tm *tm).

    There is no simple standard solution. Tricks with divining the offset via locatime(), gmtime() fail corner cases.


    IMO, to convert struct_tm (UTC) to UNIX time time_t, simply write that code and get it reviewed. It is not that hard as there are few corner cases (aside from overflow).

    Some sample code to change int year, int month, int day (0:00:00) to MJD to get OP started.

    #include <stdint.h>
    static const short DaysMarch1ToBeginingOfMonth[12] = { //
        0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};
    #ifndef INT32_C
    #define INT32_C(x) ((int_least32_t)1*(x))
    #endif
    #define DaysPer400Years   (INT32_C(365)*400 + 97)
    #define DaysPer100Years   (INT32_C(365)*100 + 24)
    #define DaysPer4Years     (365*4    +  1)
    #define DaysPer1Year      365
    #define MonthsPerYear     12
    #define MonthsPer400Years (12*400)
    #define MonthMarch        3
    #define mjdOffset         0xA5BE1
    #define mjd1900Jan1       15020
    // November 17, 1858
    
    // Example: 2015 December 31 -->  ymd_to_mjd(2015, 12, 31)
    int2x ymd_to_mjd(int year, int month, int day) {
      // int2x is a type twice as wide as int to handle extreme int values.
      // Use int (at least 32-bit) to handle common values.
      int2x year2x = year;
    
      year2x += month / MonthsPerYear;
      month %= MonthsPerYear;
      // Adjust for month/year to Mar ... Feb
      while (month < MonthMarch) {
        month += MonthsPerYear;
        year2x--;
      }
    
      int2x d = (year2x / 400) * DaysPer400Years;
      int y400 = (int) (year2x % 400);
      d += (y400 / 100) * DaysPer100Years;
      int y100 = y400 % 100;
      d += (y100 / 4) * DaysPer4Years;
      int y4 = y100 % 4;
      d += y4 * DaysPer1Year;
      d += DaysMarch1ToBeginingOfMonth[month - MonthMarch];
      d += day;
      // November 17, 1858 == MJD 0
      d--;
      d -= mjdOffset;
      return d;
    }