I'm trying to achieve pretty trivial thing: I need to store integer 32-bit unix timestamp of the build time, but all the macro I've found (__DATE__
, __TIME__
, __TIMESTAMP__
) expand to string, not integer.
It seems, we just haven't it (which is pretty strange to me). I really want to have integer, not string.
What are the best practices to get it?
UPD:
As a side note: I do embedded stuff, so I have insufficient resources (say, 128 KB of flash memory), therefore it's really bad idea to parse string.
Why do I need that: I just need to have unique version number of each beta build. Firstly, the hex file will be named like my-firmware-v2-33-BETA-1397315745.hex
, and secondly, when I need to show current version on device's screen, I might want to echo it in various format.
So, I had a little fun this evening and created a header file with macros to generate a UNIX timestamp, without any external program or special compiler feature! Just include the header and use the __TIME_UNIX__
macro.
Actually the code is pretty simple:
str[i]-'0'
as loreb suggested and weighed according to their position.? :
operator.? :
expressions.SEC_PER_DAY
must be subracted once, as JAN 01 1970, 00:00:00
must be 0
.The code has been tested in ATMEL Studio 7 (Visual Studio 2015) with the default compiler and settings (avr-gcc, -O1 optimisation) and the result has been confirmed by checking the generated .lss file.
Copy & paste the code below into a header file and include it wherever you need it. Enjoy!
/*
* 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_ */
Edit:
The initial version does not take care of 100 and 400 modulo years effects on the number of days in February. This should not be a problem between 2001 and 2101, but here is a more general macro:
/*
*
* 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