C/C++: how to get integer unix timestamp of build time (not string)
Asked Answered
G

9

11

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.

Groundsel answered 12/4, 2014 at 14:59 Comment(7)
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?Dhyana
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?Dhyana
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.Groundsel
What build tools are You using? Is it ok for You to use Cygwin on Windows?Balderas
No, there is MPLAB IDE (based on NetBeans) that runs compiler 'natively', on windows there is cmd.exeGroundsel
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.Coenocyte
@chux, please post your solution!Groundsel
B
17

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://mcmap.net/q/958251/-c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string
 * and
 *   Alexis Wilke                                 |   https://mcmap.net/q/1013435/-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
Bice answered 30/5, 2017 at 20:46 Comment(17)
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).Cort
@A. Blodgett You're totally right! I've tested it and my computer also omits the zero. Thanks for fix, I modified the answer :)Bice
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 #10538944 .Riedel
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.Riedel
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.Riedel
@Riedel 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.Bice
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).Riedel
@Riedel I'd greatly appreciate your correction, just edit the post :D Make sure to add your name under the "Autors" section in the source :)Bice
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.Riedel
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.Chrystal
@Chrystal all content on stackoverflow is licensed by default under the creative commons by-sa 4.0 license: stackoverflow.com/help/licensing . Attribution must be clear as described by stackoverflow.blog/2009/06/25/attribution-required . I can not give you any other license as it contains work from other people.Bice
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.Villasenor
@RefugnicEternium Yes this is an issue, but only a minor one as you can subtract your time zone offset easily.Bice
@RefugnicEternium I'm trying to do something like this to adjust for time zone offset ``` #define UNIX_TIMESTAMP_TZ_OFFSET_SECONDS (2*60*60) #define UNIX_TIMESTAMP_UTC (UNIX_TIMESTAMP + UNIX_TIMESTAMP_TZ_OFFSET_SECONDS) ```Chip
This is not a very good idea, @jacobq. The problem is, that you are basically applying a fixed timezone to your program. When your program is run in a different timezone, your timestamp is wrong. There is a better alternative though, namely gmtime (which takes the UTC timestamp) and returns the formatted date and time.Villasenor
@RefugnicEternium Is it possible to access gmtime at compile time? In my case I am writing embedded firmware and the team is geographically located at a single site, so I am not concerned about supporting other time zones, though I am annoyed that I would have to manually change for daylight savings (DST). I agree that generating from an external tool is a better solution, but if that is not an option then I do not know how else this can be improved.Chip
@jacobq, gmtime is, like localtime located in the C runtime and declared in <time.h>. Execution is during runtime. I don't know what you're trying to achieve, but I doubt it has anything to do with this thread. Aside from that, I made a mistake during intrepretation: DATE and TIME are in localtime, but this function assumes 'UTC', so the timestamp itself is off by 'timezone and DST'. A proper timestamp therefor requires offset calculations. This, however, can be automated.Villasenor
B
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.

Balderas answered 12/4, 2014 at 15:53 Comment(0)
G
5

Why not to define it on command line?

gcc -DCOMPILE_TIME=`date '+%s'` mysources.c
Gemeinschaft answered 12/4, 2014 at 15:23 Comment(7)
Because it will work on unix-like systems only. I need to keep it working on Windows too.Groundsel
@LaszloPapp, why? If I have integer unix timestamp (which is actually returned by date '+%s'), I can generate any version string I want. Or, what do you mean?Groundsel
How is this answer better than the originally asked macros?Dhyana
@LaszloPapp, macros are cross-platform, date '+%s' is not. For me, macros are sadly the only way, at the moment.Groundsel
@DmitryFrank: exactly what I am saying if you re-read my post. ;-)Dhyana
@LaszloPapp, sorry, I misunderstood you. But anyway, the answer might be relevant for many people who doesn't care about non-unix.Groundsel
@DmitryFrank: I would write a small util myself, basically copy/paste'ing code out of date or one from scratch.Dhyana
G
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.

Givens answered 10/10, 2019 at 1:8 Comment(0)
G
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...)

Geometer answered 14/4, 2014 at 15:54 Comment(0)
C
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
Coenocyte answered 14/4, 2014 at 14:44 Comment(4)
The function CompileTimeTM() returns pointer to a stack-allocated struct tm, it shouldn't work. Did you forget static, or did I miss something?Groundsel
It may be useful to make the function above macro so that the date time value can be calculated at compile timeSergiosergipe
@Dmitry Frank Yes, ct should have been static.Coenocyte
@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.Coenocyte
E
1

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

Endeavor answered 21/10, 2022 at 2:47 Comment(1)
The code gives wrong answers. E.g. godbolt.org/z/M76eqE4f5 - the timestamp for "15 Dec 2021 12:34:56" is in fact 1639568096, not 1639573954.Urquhart
M
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');
       }
Millford answered 8/11, 2014 at 14:52 Comment(0)
L
0

for me, use @Potion answer https://mcmap.net/q/958251/-c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string 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 )
Liquesce answered 1/3, 2023 at 5:8 Comment(1)
for easy and simple useLiquesce

© 2022 - 2024 — McMap. All rights reserved.