Я пытаюсь достичь довольно тривиальной вещи: мне нужно хранить целочисленную 32-битную временную метку Unix времени сборки, но все макросы, которые я нашел (__DATE__
, __TIME__
, __TIMESTAMP__
) расширить до строки, а не целое число.
Кажется, у нас просто этого нет (что довольно странно для меня). Я действительно хочу иметь целое число, а не строку.
Каковы лучшие практики, чтобы получить его?
UPD:
В качестве примечания: я занимаюсь встроенными вещами, поэтому у меня недостаточно ресурсов (скажем, 128 КБ флэш-памяти), поэтому разбирать строку очень плохо.
Зачем мне это нужно: мне просто нужно иметь уникальный номер версии каждой бета-сборки. Во-первых, шестнадцатеричный файл будет назван как my-firmware-v2-33-BETA-1397315745.hex
и, во-вторых, когда мне нужно показать текущую версию на экране устройства, я могу захотеть отобразить ее в другом формате.
Вы можете создать timestamp.h
файл в каждой сборке в качестве шага перед сборкой и включите этот файл в Ваши исходные коды. Я не знаю, какие инструменты сборки вы используете (встроенный мир очень широк), но каждый инструмент сборки, который я видел до сих пор, позволял пользователю определять пользовательские этапы до сборки и после сборки (Freescale CodeWarrior, AVR studio, MSVS. ..).
Например, в студии AVR на Windows я использовал этот этап предварительной сборки (обратите внимание, что $(SolutionDir)
специфичен для AVR Stuio, который основан на MSVS, Вы можете заменить любой путь к файловой системе, который Вам нужен):
FOR /F %%A IN ('C:\cygwin\bin\date +%s') DO SET BUILD_TIMESTAMP=%%A
echo #define BUILD_TIME %BUILD_TIMESTAMP% > "$(SolutionDir)timestamp.h"
И в один из файлов C проекта я обычно включаю этот сгенерированный файл (Ваш путь к этому файлу может отличаться …):
#include "../timestamp.h"
Сгенерированный файл выглядит так:
#define BUILD_TIME 1397317498
Поэтому, когда я нажимаю «построить проект», студия сначала запускает мои команды, генерирует новые timestamp.h
а затем использует его в качестве включения в любые другие файлы C.
Обратите внимание, что в приведенном выше примере используется Cygwin (C:\cygwin\bin\date +%s
), чтобы получить метку времени. Если вы не хотите использовать Cygwin, вам нужно будет найти другой способ для Windows сгенерировать для вас метку времени. Вы можете написать свою собственную утилиту командной строки (это должно быть около 10 строк кода на C 🙂 или поискать в Интернете какой-то другой способ.
Итак, я немного повеселился этим вечером и создал заголовочный файл с макросами для генерации метки времени UNIX, без любая внешняя программа или специальная функция компилятора! Просто включите заголовок и используйте __TIME_UNIX__
макро.
На самом деле код довольно прост:
str[i]-'0'
как loreb предложил и взвесил в соответствии с их положением.? :
оператор.? :
выражения.SEC_PER_DAY
должен быть вычтен один раз, так как JAN 01 1970, 00:00:00
должно быть 0
,Код был протестирован в ATMEL Studio 7 (Visual Studio 2015) с компилятором и настройками по умолчанию (оптимизация avr-gcc, -O1), и результат был подтвержден путем проверки сгенерированного файла .lss.
копия & Вставьте приведенный ниже код в заголовочный файл и включите его там, где вам нужно. Наслаждайтесь!
/*
* compile_time.h
*
* Created: 30.05.2017 20:57:58
* Author: Dennis (instructable.com/member/nqtronix)
*
* This code provides the macro __TIME_UNIX__ which returns the current time in UNIX format. It can
* be used to identify a version of code on an embedded device, to initialize its RTC and much more.
* Along that several more constants for seconds, minutes, etc. are provided
*
* The macro is based on __TIME__ and __DATE__, which are assumed to be formatted "HH:MM:SS" and
* "MMM DD YYYY", respectively. The actual value can be calculated by the C compiler at compile time
* as all inputs are literals. MAKE SURE TO ENABLE OPTIMISATION!
*/#ifndef COMPILE_TIME_H_
#define COMPILE_TIME_H_
// extracts 1..4 characters from a string and interprets it as a decimal value
#define CONV_STR2DEC_1(str, i) (str[i]>'0'?str[i]-'0':0)
#define CONV_STR2DEC_2(str, i) (CONV_STR2DEC_1(str, i)*10 + str[i+1]-'0')
#define CONV_STR2DEC_3(str, i) (CONV_STR2DEC_2(str, i)*10 + str[i+2]-'0')
#define CONV_STR2DEC_4(str, i) (CONV_STR2DEC_3(str, i)*10 + str[i+3]-'0')
// Some definitions for calculation
#define SEC_PER_MIN 60UL
#define SEC_PER_HOUR 3600UL
#define SEC_PER_DAY 86400UL
#define SEC_PER_YEAR (SEC_PER_DAY*365)
#define UNIX_START_YEAR 1970UL
// Custom "glue logic" to convert the month name to a usable number
#define GET_MONTH(str, i) (str[i]=='J' && str[i+1]=='a' && str[i+2]=='n' ? 1 : \
str[i]=='F' && str[i+1]=='e' && str[i+2]=='b' ? 2 : \
str[i]=='M' && str[i+1]=='a' && str[i+2]=='r' ? 3 : \
str[i]=='A' && str[i+1]=='p' && str[i+2]=='r' ? 4 : \
str[i]=='M' && str[i+1]=='a' && str[i+2]=='y' ? 5 : \
str[i]=='J' && str[i+1]=='u' && str[i+2]=='n' ? 6 : \
str[i]=='J' && str[i+1]=='u' && str[i+2]=='l' ? 7 : \
str[i]=='A' && str[i+1]=='u' && str[i+2]=='g' ? 8 : \
str[i]=='S' && str[i+1]=='e' && str[i+2]=='p' ? 9 : \
str[i]=='O' && str[i+1]=='c' && str[i+2]=='t' ? 10 : \
str[i]=='N' && str[i+1]=='o' && str[i+2]=='v' ? 11 : \
str[i]=='D' && str[i+1]=='e' && str[i+2]=='c' ? 12 : 0)
#define GET_MONTH2DAYS(month) ((month == 1 ? 0 : 31 + \
(month == 2 ? 0 : 28 + \
(month == 3 ? 0 : 31 + \
(month == 4 ? 0 : 30 + \
(month == 5 ? 0 : 31 + \
(month == 6 ? 0 : 30 + \
(month == 7 ? 0 : 31 + \
(month == 8 ? 0 : 31 + \
(month == 9 ? 0 : 30 + \
(month == 10 ? 0 : 31 + \
(month == 11 ? 0 : 30)))))))))))) \#define GET_LEAP_DAYS ((__TIME_YEARS__-1968)/4 - (__TIME_MONTH__ <=2 ? 1 : 0))#define __TIME_SECONDS__ CONV_STR2DEC_2(__TIME__, 6)
#define __TIME_MINUTES__ CONV_STR2DEC_2(__TIME__, 3)
#define __TIME_HOURS__ CONV_STR2DEC_2(__TIME__, 0)
#define __TIME_DAYS__ CONV_STR2DEC_2(__DATE__, 4)
#define __TIME_MONTH__ GET_MONTH(__DATE__, 0)
#define __TIME_YEARS__ CONV_STR2DEC_4(__DATE__, 7)
#define __TIME_UNIX__ ((__TIME_YEARS__-UNIX_START_YEAR)*SEC_PER_YEAR+ \
GET_LEAP_DAYS*SEC_PER_DAY+ \
GET_MONTH2DAYS(__TIME_MONTH__)*SEC_PER_DAY+ \
__TIME_DAYS__*SEC_PER_DAY-SEC_PER_DAY+ \
__TIME_HOURS__*SEC_PER_HOUR+ \
__TIME_MINUTES__*SEC_PER_MIN+ \
__TIME_SECONDS__)
#endif /* COMPILE_TIME_H_ */
Редактировать:
Первоначальная версия не учитывает влияние 100 и 400 по модулю лет на количество дней в феврале. Это не должно быть проблемой между 2001 и 2101 годами, но вот более общий макрос:
/*
*
* Created: 29.03.2018
*
* Authors:
*
* Assembled from the code released on Stackoverflow by:
* Dennis (instructable.com/member/nqtronix) | https://stackoverflow.com/questions/23032002/c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string
* and
* Alexis Wilke | https://stackoverflow.com/questions/10538444/do-you-know-of-a-c-macro-to-compute-unix-time-and-date
*
* Assembled by Jean Rabault
*
* UNIX_TIMESTAMP gives the UNIX timestamp (unsigned long integer of seconds since 1st Jan 1970) of compilation from macros using the compiler defined __TIME__ macro.
* This should include Gregorian calendar leap days, in particular the 29ths of February, 100 and 400 years modulo leaps.
*
* Careful: __TIME__ is the local time of the computer, NOT the UTC time in general!
*
*/
#ifndef COMPILE_TIME_H_
#define COMPILE_TIME_H_
// Some definitions for calculation
#define SEC_PER_MIN 60UL
#define SEC_PER_HOUR 3600UL
#define SEC_PER_DAY 86400UL
#define SEC_PER_YEAR (SEC_PER_DAY*365)
// extracts 1..4 characters from a string and interprets it as a decimal value
#define CONV_STR2DEC_1(str, i) (str[i]>'0'?str[i]-'0':0)
#define CONV_STR2DEC_2(str, i) (CONV_STR2DEC_1(str, i)*10 + str[i+1]-'0')
#define CONV_STR2DEC_3(str, i) (CONV_STR2DEC_2(str, i)*10 + str[i+2]-'0')
#define CONV_STR2DEC_4(str, i) (CONV_STR2DEC_3(str, i)*10 + str[i+3]-'0')
// Custom "glue logic" to convert the month name to a usable number
#define GET_MONTH(str, i) (str[i]=='J' && str[i+1]=='a' && str[i+2]=='n' ? 1 : \
str[i]=='F' && str[i+1]=='e' && str[i+2]=='b' ? 2 : \
str[i]=='M' && str[i+1]=='a' && str[i+2]=='r' ? 3 : \
str[i]=='A' && str[i+1]=='p' && str[i+2]=='r' ? 4 : \
str[i]=='M' && str[i+1]=='a' && str[i+2]=='y' ? 5 : \
str[i]=='J' && str[i+1]=='u' && str[i+2]=='n' ? 6 : \
str[i]=='J' && str[i+1]=='u' && str[i+2]=='l' ? 7 : \
str[i]=='A' && str[i+1]=='u' && str[i+2]=='g' ? 8 : \
str[i]=='S' && str[i+1]=='e' && str[i+2]=='p' ? 9 : \
str[i]=='O' && str[i+1]=='c' && str[i+2]=='t' ? 10 : \
str[i]=='N' && str[i+1]=='o' && str[i+2]=='v' ? 11 : \
str[i]=='D' && str[i+1]=='e' && str[i+2]=='c' ? 12 : 0)
// extract the information from the time string given by __TIME__ and __DATE__
#define __TIME_SECONDS__ CONV_STR2DEC_2(__TIME__, 6)
#define __TIME_MINUTES__ CONV_STR2DEC_2(__TIME__, 3)
#define __TIME_HOURS__ CONV_STR2DEC_2(__TIME__, 0)
#define __TIME_DAYS__ CONV_STR2DEC_2(__DATE__, 4)
#define __TIME_MONTH__ GET_MONTH(__DATE__, 0)
#define __TIME_YEARS__ CONV_STR2DEC_4(__DATE__, 7)
// Days in February
#define _UNIX_TIMESTAMP_FDAY(year) \
(((year) % 400) == 0UL ? 29UL : \
(((year) % 100) == 0UL ? 28UL : \
(((year) % 4) == 0UL ? 29UL : \
28UL)))
// Days in the year
#define _UNIX_TIMESTAMP_YDAY(year, month, day) \
( \
/* January */ day \
/* February */ + (month >= 2 ? 31UL : 0UL) \
/* March */ + (month >= 3 ? _UNIX_TIMESTAMP_FDAY(year) : 0UL) \
/* April */ + (month >= 4 ? 31UL : 0UL) \
/* May */ + (month >= 5 ? 30UL : 0UL) \
/* June */ + (month >= 6 ? 31UL : 0UL) \
/* July */ + (month >= 7 ? 30UL : 0UL) \
/* August */ + (month >= 8 ? 31UL : 0UL) \
/* September */+ (month >= 9 ? 31UL : 0UL) \
/* October */ + (month >= 10 ? 30UL : 0UL) \
/* November */ + (month >= 11 ? 31UL : 0UL) \
/* December */ + (month >= 12 ? 30UL : 0UL) \
)
// get the UNIX timestamp from a digits representation
#define _UNIX_TIMESTAMP(year, month, day, hour, minute, second) \
( /* time */ second \
+ minute * SEC_PER_MIN \
+ hour * SEC_PER_HOUR \
+ /* year day (month + day) */ (_UNIX_TIMESTAMP_YDAY(year, month, day) - 1) * SEC_PER_DAY \
+ /* year */ (year - 1970UL) * SEC_PER_YEAR \
+ ((year - 1969UL) / 4UL) * SEC_PER_DAY \
- ((year - 1901UL) / 100UL) * SEC_PER_DAY \
+ ((year - 1601UL) / 400UL) * SEC_PER_DAY \
)
// the UNIX timestamp
#define UNIX_TIMESTAMP (_UNIX_TIMESTAMP(__TIME_YEARS__, __TIME_MONTH__, __TIME_DAYS__, __TIME_HOURS__, __TIME_MINUTES__, __TIME_SECONDS__))
#endif
Почему бы не определить это в командной строке?
gcc -DCOMPILE_TIME=`date '+%s'` mysources.c
Редактировать:
Согласно комментарию @ L @u Vĩnh Phúc, ключевая идея нуждается в объяснении: Все расчет сделан как компилировать время. См. Далеко ниже сгенерированные коды OP.
Вместо того, чтобы генерировать time_t
ниже генерирует struct tm
в часовом поясе компилятора. При необходимости позвоните mktime()
потом вернусь time_t
,
time_t CompileTime = mktime(CompileTimeTM());
printf("%lld\n", (long long) CompileTime);
В других приложениях вместо возврата struct tm
Каждое из полей назначения просто выводит значение, чтобы показать версию.
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) Print2Dig(5);
Приведенный ниже длинный код генерирует несколько инструкций в компиляторе PIC, поскольку он оптимизирует многое. Подобный подход может использовать __TIMESTAMP__
,
// Dummy: used to detect a bad date parsing in Send_ID()
extern void BadDateM(void);
struct tm *CompileTimeTM(void) {
static const char d[10] = __DATE__;
static const char t[10] = __TIME__;
static struct tm ct;
ct.tm_year = (d[7]-'0')*10 + (d[8]-'0') + 2000 - 1900;
#IGNORE_WARNINGS 204
if (0) ;
else if ((d[3]=='J') && (d[4]=='a') && (d[5]=='n')) ct.tm_mon = 1-1;
else if ((d[3]=='F') && (d[4]=='e') && (d[5]=='b')) ct.tm_mon = 2-1;
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='r')) ct.tm_mon = 3-1;
else if ((d[3]=='A') && (d[4]=='p') && (d[5]=='r')) ct.tm_mon = 4-1;
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) ct.tm_mon = 5-1;
else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='n')) ct.tm_mon = 6-1;
else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='l')) ct.tm_mon = 7-1;
else if ((d[3]=='A') && (d[4]=='u') && (d[5]=='g')) ct.tm_mon = 8-1;
else if ((d[3]=='S') && (d[4]=='e') && (d[5]=='p')) ct.tm_mon = 9-1;
else if ((d[3]=='O') && (d[4]=='c') && (d[5]=='t')) ct.tm_mon = 10-1;
else if ((d[3]=='N') && (d[4]=='o') && (d[5]=='v')) ct.tm_mon = 11-1;
else if ((d[3]=='D') && (d[4]=='e') && (d[5]=='c')) ct.tm_mon = 12-1;
else BadDateM(); // compile this if no match above, and thus fail link.
#IGNORE_WARNINGS NONE
ct.tm_mday = (d[0]-'0')*10 + (d[1]-'0');
ct.tm_hour = (t[0]-'0')*10 + (t[1]-'0');
ct.tm_min = (t[3]-'0')*10 + (t[4]-'0');
ct.tm_sec = (t[6]-'0')*10 + (t[7]-'0');
ct.tm_isdst = -1; // information is not available.
// ct.tm_yday = 0;
// ct.tm_wday = 0;
return &ct;
}
список
struct tm *CompileTimeTM(void) {
static const char d[10] = __DATE__;
static const char t[10] = __TIME__;
static struct tm ct;
ct.tm_year = (d[7]-'0')*10 + (d[8]-'0') + 2000 - 1900;
0F78 200724 MOV #72,W4 : W4 = 72
0F7A 8864D4 MOV W4,C9A : C9A = W4
#IGNORE_WARNINGS 204
if (0) ;
else if ((d[3]=='J') && (d[4]=='a') && (d[5]=='n')) ct.tm_mon = 1-1;
else if ((d[3]=='F') && (d[4]=='e') && (d[5]=='b')) ct.tm_mon = 2-1;
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='r')) ct.tm_mon = 3-1;
else if ((d[3]=='A') && (d[4]=='p') && (d[5]=='r')) ct.tm_mon = 4-1;
0F7C 200034 MOV #3,W4 : W4 = 3
0F7E 8864C4 MOV W4,C98 : C98 = W4
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) ct.tm_mon = 5-1;
else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='n')) ct.tm_mon = 6-1;
else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='l')) ct.tm_mon = 7-1;
else if ((d[3]=='A') && (d[4]=='u') && (d[5]=='g')) ct.tm_mon = 8-1;
else if ((d[3]=='S') && (d[4]=='e') && (d[5]=='p')) ct.tm_mon = 9-1;
else if ((d[3]=='O') && (d[4]=='c') && (d[5]=='t')) ct.tm_mon = 10-1;
else if ((d[3]=='N') && (d[4]=='o') && (d[5]=='v')) ct.tm_mon = 11-1;
else if ((d[3]=='D') && (d[4]=='e') && (d[5]=='c')) ct.tm_mon = 12-1;
else BadDateM(); // compile this if no match above, and thus fail link.
#IGNORE_WARNINGS NONE
ct.tm_mday = (d[0]-'0')*10 + (d[1]-'0');
0F80 2000E4 MOV #E,W4 : W4 = E
0F82 8864B4 MOV W4,C96 : C96 = W4
ct.tm_hour = (t[0]-'0')*10 + (t[1]-'0');
0F84 2000B4 MOV #B,W4 : W4 = B
0F86 8864A4 MOV W4,C94 : C94 = W4
ct.tm_min = (t[3]-'0')*10 + (t[4]-'0');
0F88 200354 MOV #35,W4 : W4 = 35
0F8A 886494 MOV W4,C92 : C92 = W4
ct.tm_sec = (t[6]-'0')*10 + (t[7]-'0');
0F8C 2000D4 MOV #D,W4 : W4 = D
0F8E 886484 MOV W4,C90 : C90 = W4
Посмотрите на следующее злодеяние:
#include <stdio.h>
#define dec(ch) ((ch)-'0')
#define t(index, multiplier) (dec(__TIME__[index]) * (multiplier))
/* only minutes and seconds - you get the idea */
#define mmss() (t(3,600) + t(4,60) + t(6,10) + t(7,1))
int main()
{
/*
int i;
printf("time = %s\n", __TIME__);
for(i=0; __TIME__[i]; i++)
printf("t(%d) = %d\n", i, t(i,1));
*/
printf("mmss = %d\n", mmss());
return 0;
}
На моем компьютере gcc -O может оптимизировать его до постоянного значения; получить полный time_t
вам понадобится макрос двоюродного брата для __DATE__
и посмотрите на сгенерированную сборку.
Если кто-нибудь спросит, я не написал этот ответ;)
Изменить: просто чтобы быть ясно, вы, вероятно, должны следовать ответу Романа Хокке и написать короткую программу на C, чтобы сгенерировать для вас значение (конечно, если вы кросс-компиляции, вы должны быть немного осторожны …)
Я обнаружил, что должен был добавить специальный чек, если день месяца был меньше 10, иначе он возвращает отрицательное число
// special case to handle __DATE__ not inserting leading zero on day of month
// if Day of month is less than 10 - it inserts a blank character
// this results in a negative number for tm_mday
if(d[4] == ' ')
{
ct.tm_mday = d[5]-'0';
}
else
{
ct.tm_mday = (d[4]-'0')*10 + (d[5]-'0');
}