10

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.

Dmitry Frank
  • 10,417
  • 10
  • 64
  • 114
  • Could you please mention a use case for this? Usually, the software build timestamp is stored in system files, and I have not yet come across a use case knowing this in the software being built. You would like to show it in the about or enable/disable some features without reading an external file? – László Papp Apr 12 '14 at 15:09
  • Why would you need to parse the string into integers? Displaying the version does not require, nor putting the timestamp into a file name (string)? Have I missed something? – László Papp Apr 12 '14 at 15:22
  • Integer is much more convenient. I want to have a number (that is comparable to another number), and I want to have the ability to generate string in **any** format I want, not just one that compiler generated. I don't want to have special symbols in filename (spaces, colons), and `1397315745` is much shorter than `Feb 12 2014 19:28:01`. – Dmitry Frank Apr 12 '14 at 15:29
  • What build tools are You using? Is it ok for You to use Cygwin on Windows? – Roman Hocke Apr 12 '14 at 15:37
  • No, there is MPLAB IDE (based on NetBeans) that runs compiler 'natively', on windows there is `cmd.exe` – Dmitry Frank Apr 12 '14 at 15:38
  • I have a nice embedded solution for a compile time `struct tm`, not `time_t`. Can post it Monday. Please advise if that would do. – chux - Reinstate Monica Apr 12 '14 at 22:06
  • @chux, please post your solution! – Dmitry Frank Apr 13 '14 at 09:04

9 Answers9

16

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:

  1. Number characters in the strings are converted to numbers with str[i]-'0' as loreb suggested and weighed according to their position.
  2. The month string is processed similiar to chux answer; the chars are checked individually and evaluated as a group with the ? : operator.
  3. Counting the days of the last months is done with commutative ? : expressions.
  4. Leap year calculation is simple as within the UNIX time frame one leap day is inserted every 4 years
  5. Finally all individual values are weighed with the respective amount of seconds to get the UNIX time. Note that 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
Zorglub29
  • 6,979
  • 6
  • 20
  • 37
nqtronix
  • 463
  • 3
  • 14
  • 1
    Your header almost works, but when we tried to use it with a single digit day, the `__TIME_DAYS__` gives a negative number because `__DATE__` gives a space instead of a zero on our compiler ("Jun 5, 2017"). We fixed it with this `#define CONV_STR2DEC_1(str, i) (str[i]>'0'?str[i]-'0':0)`. – A. Blodgett Jun 05 '17 at 21:01
  • @A. Blodgett You're totally right! I've tested it and my computer also omits the zero. Thanks for fix, I modified the answer :) – nqtronix Jun 06 '17 at 12:40
  • Are you sure this solution works? I just tested it now, and I get a discrepancy between ```date +%s``` and your ```__TIME_UNIX__```. I think your formula for ``GET_LEAP_DAYS``` may be wrong, see for example the first answer here https://stackoverflow.com/questions/10538444/do-you-know-of-a-c-macro-to-compute-unix-time-and-date . – Zorglub29 Mar 29 '18 at 18:44
  • sorry, it seems that I was getting a discrepancy because __TIME__ gives the local time, while I was comparing with UTC time in both the macro and the ```date +%s``` command, my bad. – Zorglub29 Mar 29 '18 at 19:28
  • Ok, I still think there may be a problem with your function (at least compared with the one I give the link to), but I think the difference did not appear because ``` - ((year - 1901UL) / 100UL) * SEC_PER_DAY \ + ((year - 1601UL) / 400UL) * SEC_PER_DAY \``` is actually 0 for 2001. – Zorglub29 Mar 29 '18 at 19:46
  • @Zorglub29 `GET_LEAP_DAYS` returns the amount of leap years/ days since the start of unix time counting. Yor example seems to be a `is_leap_year()`-like function. However my macro does not account for 2000 NOT beeing a leap year, so only years > 2000 are valid. This shouldn't be a problem unless you can travel to the past ;). When I wrote this macro, I compared it against an online unix time calculator and for a limited sample size and it returned correct results. – nqtronix Mar 31 '18 at 06:53
  • 1
    Yes, I agree with you. Dates after 2001 and before 2101 should be ok. But maybe some people will try to use your macro for a more general application than you. For example, trigger an event at a given distance in time from a reference in 1999? I think you should either state the validity domain of your script with a big bold warning, or update your code. I have an updated version of your code, tell me if it is ok for you if I post it (with creds to your post of course). – Zorglub29 Apr 04 '18 at 07:24
  • @Zorglub29 I'd greatly appreciate your correction, just edit the post :D Make sure to add your name under the "Autors" section in the source :) – nqtronix Apr 04 '18 at 12:26
  • Ok. Then I just append my version to your post, and if you like my version you can remove the current one, if not you can remove mine. – Zorglub29 Apr 04 '18 at 13:06
  • I really appreciate this being made easily available. It seems as though the intention is for these to be liberally licensed: would you mind explicitly giving these a license such as MIT, ISC or BSD. – azmr Jan 07 '20 at 14:36
  • @azmr all content on stackoverflow is licensed by default under the creative commons by-sa 4.0 license: https://stackoverflow.com/help/licensing . Attribution must be clear as described by https://stackoverflow.blog/2009/06/25/attribution-required/ . I can not give you any other license as it contains work from other people. – nqtronix Jan 07 '20 at 16:29
  • 1
    I greatly appreciate you sharing this (yes, even 4 years later it is useful). However I ran into a problem. I am from Germany, meaning my computer is using, at the time of writing, the timezone CEST (UTC + 2). The snippet however converts the String (which is also in CEST) to a 1:1 UTC Timestamp. Converting it back leads to a discrepancy of 2 hours for me. Just figured I'd tell you. – Refugnic Eternium Aug 25 '22 at 08:12
  • @RefugnicEternium Yes this is an issue, but only a minor one as you can subtract your time zone offset easily. – nqtronix Aug 27 '22 at 08:15
7

You can generate a timestamp.h file on every build as a pre-build step and include that file in Your source codes. I don't know what build tools are You using (embedded world is very broad), bud every build tool I have seen so far allowed user to define custom pre-build and post-build steps (Freescale CodeWarrior, AVR studio, MSVS...).

For example in AVR studio on Windows I used this pre-build step (note that the $(SolutionDir) is specific for AVR stuio, which is based on MSVS, You may substitute with any filesystem path You need):

FOR /F %%A IN ('C:\cygwin\bin\date +%s') DO SET BUILD_TIMESTAMP=%%A
echo #define BUILD_TIME %BUILD_TIMESTAMP% > "$(SolutionDir)timestamp.h"

And in one of the project's C files I normally include this generated file (Your path to that file may differ...):

#include "../timestamp.h"

The generated file looks like this:

#define BUILD_TIME 1397317498

So when I click "build project", the studio first runs my commands, generates new timestamp.h and then uses it as an include in any other C files.

Note that the example above uses Cygwin (C:\cygwin\bin\date +%s) to get the timestamp. If You don't want to use Cygwin, You will have to find some other way for Windows to generate a timestamp for You. You may write Your own command-line utility (it should be about 10 lines of code in C :-) or search the Internet for some other way.

Roman Hocke
  • 4,137
  • 1
  • 20
  • 34
5

Why not to define it on command line?

gcc -DCOMPILE_TIME=`date '+%s'` mysources.c
Marian
  • 7,402
  • 2
  • 22
  • 34
4
/**
 * @brief  Macros to get integer build timestamp:
 *         __DATE_TIME_Y2K__  seconds since 2000-01-01,00:00:00
 *         __DATE_TIME_UNIX__ seconds since 1970-01-01 00:00:00
 *
 *           01234567890              01234567
 * __DATE__ "Jul 27 2019"   __TIME__ "12:34:56"
 */
#ifndef __DATE_TIME_H__
#define __DATE_TIME_H__

// For testing
//#define __DATE__ ("Jan 01 2000")
//#define __TIME__ ("00:00:00")

#define Y2K_UNIX_EPOCH_DIFF 946684800U
#define YEARS ((__DATE__[10] - '0' + (__DATE__[9] - '0') * 10))
#define DAY_OF_MONTH ((__DATE__[5] - '0') \
                  + (((__DATE__[4] > '0')? __DATE__[4] - '0': 0) * 10) - 1)
#define DAY_OF_YEAR ((DAY_OF_MONTH) + \
( /* Jan */ (__DATE__[0] == 'J' && __DATE__[1] == 'a')?   0: \
  /* Feb */ (__DATE__[0] == 'F'                      )?  31: \
  /* Mar */ (__DATE__[0] == 'M' && __DATE__[2] == 'r')?  59: \
  /* Apr */ (__DATE__[0] == 'A' && __DATE__[1] == 'p')?  90: \
  /* May */ (__DATE__[0] == 'M'                      )? 120: \
  /* Jun */ (__DATE__[0] == 'J' && __DATE__[2] == 'n')? 151: \
  /* Jul */ (__DATE__[0] == 'J'                      )? 181: \
  /* Aug */ (__DATE__[0] == 'A'                      )? 212: \
  /* Sep */ (__DATE__[0] == 'S'                      )? 243: \
  /* Oct */ (__DATE__[0] == 'O'                      )? 273: \
  /* Nov */ (__DATE__[0] == 'N'                      )? 304: \
  /* Dec */                                             334  ))
#define LEAP_DAYS (YEARS / 4 + ((YEARS % 4 == 0 && DAY_OF_YEAR > 58)? 1 : 0) )      
#define __DATE_TIME_Y2K__ ( (YEARS * 365 + LEAP_DAYS + DAY_OF_YEAR ) * 86400 \
                    + ((__TIME__[0] - '0') * 10 + __TIME__[1] - '0') * 3600 \
                    + ((__TIME__[3] - '0') * 10 + __TIME__[4] - '0') * 60 \
                    + ((__TIME__[6] - '0') * 10 + __TIME__[7] - '0') )
#define  __DATE_TIME_UNIX__ ( __DATE_TIME_Y2K__ + Y2K_UNIX_EPOCH_DIFF )
#endif /* __DATE_TIME_H__ */

This is just a tighter, lighter version developed from @nqtronix 's version above. Enjoy!

Note: This macro ignores century, and so only works between 2000-01-01,00:00:00 and 2099-12-31,23:59:59.

Potion
  • 92
  • 4
2

Look at the following atrocity:

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

On my computer gcc -O is able to optimize it to a constant value; to get a full time_t, you'll need a cousin macro for __DATE__ and look at the generated assembly.

If anyone asks, I didn't write this answer ;)

Edit: just to be clear, you should probably follow Roman Hocke's answer and write a short C program to generate the value for you (of course if you're cross compiling you should be a bit careful...)

loreb
  • 1,327
  • 1
  • 7
  • 6
1

Edit: As per @Lưu Vĩnh Phúc comment, it seems the key idea needs explaining: All calculation is done as compile time. See far below the OP codes generated.


Rather than generate time_t, the below generates struct tm in the timezone of the compiler. If needed, a call to mktime() will then return time_t.

time_t CompileTime = mktime(CompileTimeTM());
printf("%lld\n", (long long) CompileTime);

In other applications, instead of returning a struct tm, each of the fields assignments simple print the value to show the version.

else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) Print2Dig(5);

The below lengthy code generates few instructions in a PIC compiler as it optimizes out a lot. A similar approach could use __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;
  }

Listing

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
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • The function `CompileTimeTM()` returns pointer to a stack-allocated `struct tm`, it shouldn't work. Did you forget `static`, or did I miss something? – Dmitry Frank Apr 14 '14 at 14:57
  • 1
    It may be useful to make the function above macro so that the date time value can be calculated at compile time – phuclv Apr 14 '14 at 15:05
  • @Dmitry Frank Yes, `ct` should have been `static`. – chux - Reinstate Monica Apr 14 '14 at 15:45
  • 1
    @Lưu Vĩnh Phúc The date time _is_ calculated only at compile time. The only thing the function is doing at run-time is moving the values. _No_ calculation is done at runtime as the compiler sees that `d` and `t` are constant and optimizes all the calculation at compile time. – chux - Reinstate Monica Apr 14 '14 at 15:49
0

I found I had to add a special check if the day of month was less than 10 - otherwise it returns a negative number

       // 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');
       }
0

Since you're using C++, I think you could use constexpr functions to parse the __DATE__ __TIME__ strings on compilation.

As:

#include <stdint.h>

struct time_custom_t {
  uint16_t year;
  uint8_t month;
  uint8_t day;
  uint8_t hour;
  uint8_t minute;
  uint8_t second;
};

#define __SECONDS_FROM_1970_TO_2000                                              \
  946684800 ///< Unixtime for 2000-01-01 00:00:00, useful for initialization

constexpr uint16_t conv2d(const char *p)
{
    uint8_t v = 0;
    if ('0' <= *p && *p <= '9')
        v = *p - '0';
    return 10 * v + *++p - '0';
}

constexpr uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
  const uint8_t daysInMonth[] = {31, 28, 31, 30, 31, 30,
                                       31, 31, 30, 31, 30};
  if (y >= 2000U)
    y -= 2000U;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += daysInMonth[i - 1];
  if (m > 2 && y % 4 == 0)
    ++days;
  return days + 365 * y + (y + 3) / 4 - 1;
}

constexpr uint32_t time2ulong(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
  return ((days * 24UL + h) * 60 + m) * 60 + s;
}

constexpr time_custom_t getBuildTime(const char *date, const char *time)
{
    time_custom_t dt{};
    dt.year = conv2d(date + 9);
    dt.day = conv2d(date + 4);
    dt.hour = conv2d(time);
    dt.minute = conv2d(time + 3);
    dt.second = conv2d(time + 6);

    // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
    switch (date[0])
    {
    case 'J': dt.month = (date[1] == 'a') ? 1 : ((date[2] == 'n') ? 6 : 7); break;
    case 'F': dt.month = 2; break;
    case 'A': dt.month = date[2] == 'r' ? 4 : 8; break;
    case 'M': dt.month = date[2] == 'r' ? 3 : 5; break;
    case 'S': dt.month = 9; break;
    case 'O': dt.month = 10; break;
    case 'N': dt.month = 11; break;
    case 'D': dt.month = 12; break;
    }

    return dt;
}

constexpr uint32_t getBuildTimeAsUnixTime(const char *date, const char *time)
{
    time_custom_t dt = getBuildTime (date, time);
    uint32_t unixTime = 0;
    unixTime = time2ulong(date2days(dt.year, dt.month, dt.day), dt.hour, dt.month, dt.minute) + __SECONDS_FROM_1970_TO_2000;
    return unixTime;
}

I try with GCC and -O3 flag, here is the result:

PictureGodBolt

0

for me, use @Potion answer https://stackoverflow.com/a/58314096/5949219 except DAY_OF_MONTH modified without substracting 1 so It become :

#define Y2K_UNIX_EPOCH_DIFF 946684800U    
#define YEARS ((__DATE__[10] - '0' + (__DATE__[9] - '0') * 10))
#define DAY_OF_MONTH ((__DATE__[5] - '0') \
                  + (((__DATE__[4] > '0')? __DATE__[4] - '0': 0) * 10))
#define DAY_OF_YEAR ((DAY_OF_MONTH) + \
( (__DATE__[0] == 'J' && __DATE__[1] == 'a')?   0: \
  (__DATE__[0] == 'F'                      )?  31: \
  (__DATE__[0] == 'M' && __DATE__[2] == 'r')?  59: \
  (__DATE__[0] == 'A' && __DATE__[1] == 'p')?  90: \
  (__DATE__[0] == 'M'                      )? 120: \
  (__DATE__[0] == 'J' && __DATE__[2] == 'n')? 151: \
  (__DATE__[0] == 'J'                      )? 181: \
  (__DATE__[0] == 'A'                      )? 212: \
  (__DATE__[0] == 'S'                      )? 243: \
  (__DATE__[0] == 'O'                      )? 273: \
  (__DATE__[0] == 'N'                      )? 304: \
                                              334  ))
#define LEAP_DAYS (YEARS / 4 + ((YEARS % 4 == 0 && DAY_OF_YEAR > 58)? 1 : 0) )      
#define __DATE_TIME_Y2K__ ( (YEARS * 365 + LEAP_DAYS + DAY_OF_YEAR ) * 86400 \
                    + ((__TIME__[0] - '0') * 10 + __TIME__[1] - '0') * 3600 \
                    + ((__TIME__[3] - '0') * 10 + __TIME__[4] - '0') * 60 \
                    + ((__TIME__[6] - '0') * 10 + __TIME__[7] - '0') )
#define  UNIX_TIMESTAMP ( __DATE_TIME_Y2K__ + Y2K_UNIX_EPOCH_DIFF )
Rendra_HMD
  • 18
  • 6