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