15

I'm trying to convert the string produced from the __DATE__ macro into a time_t. I don't need a full-blown date/time parser, something that only handles the format of the __DATE__ macro would be great.

A preprocessor method would be nifty, but a function would work just as well. If it's relevant, I'm using MSVC.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
tfinniga
  • 6,693
  • 3
  • 32
  • 37

7 Answers7

23

Edit: the corrected function should look something like this:

time_t cvt_TIME(char const *time) { 
    char s_month[5];
    int month, day, year;
    struct tm t = {0};
    static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

    sscanf(time, "%s %d %d", s_month, &day, &year);

    month = (strstr(month_names, s_month)-month_names)/3;

    t.tm_mon = month;
    t.tm_mday = day;
    t.tm_year = year - 1900;
    t.tm_isdst = -1;

    return mktime(&t);
}
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • One big thing - the month 'returned' by `__DATE__` is in name form ('Jan', 'Feb' etc). Another minor suggestion: you should set `t.tm_dst = -1` so `mktime()` will attempt to use the appropriate DST. – Michael Burr Nov 19 '09 at 17:40
  • @Michael - it looks like t.tm_dst is not part of visual c++. – tfinniga Nov 19 '09 at 17:48
  • I'm sorry - that should have been `t.tm_isdst = -1` – Michael Burr Nov 19 '09 at 18:11
  • 1
    The function above does not work properly -- strstr() arguments are in a wrong order, it should be strstr(month_names, s_month). See http://www.cplusplus.com/reference/clibrary/cstring/strstr/ for a reference. – Miro Kropacek Aug 18 '11 at 14:48
  • @Miro: Oops, quite right. Some day perhaps I'll learn to test code before posting (but probably not...). – Jerry Coffin Aug 18 '11 at 17:06
  • Perfect! Here is the same code but for COleDateTime: COleDateTime GetAppBuildDate() { //return app compilation/build date static COleDateTime buildDate(DATE(0)); if (buildDate.m_dt == 0) { char s_month[5]; int month, day, year; static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; sscanf(__DATE__, "%s %d %d", s_month, &day, &year); month = ((strstr(month_names, s_month) - month_names) / 3) + 1; buildDate.SetDate(year, month, day); ASSERT(buildDate.m_status == COleDateTime::valid); } ASSERT(buildDate.m_dt > 0); return buildDate; } – Volodymyr Frytskyy Apr 11 '12 at 10:09
  • This function addresses the months as 0-11 instead of 1-12. Not sure if that is what you intend as I don't use Visual C++. – Bruno Bronosky Oct 05 '14 at 06:20
  • Use ```#include // sscanf #include // strstr #include // mktime``` – Tim Kuipers Apr 25 '16 at 14:16
  • Evil thought: What if this is a mobile computer and between the time of compilation and the time of execution the computer changes time zones? The result will be off by the difference between the two timezones. It's a shame that `__DATE__` isn't specified to be in terms of UTC, as that would solve this otherwise unsolvable problem. – Howard Hinnant Jun 23 '17 at 02:12
  • 1
    @HowardHinnant: Well, there are a few other UTC-like things that could be used instead (e.g. TAI and GPS are both independent of time zones, but neither includes leap seconds like UTC does). But yes--could be particularly pernicious if `now() < __DATE__` -- a situation many would normally assume couldn't possibly arise. Doesn't even need to involve a portable computer though: if you compile on your machine, and I download it on mine in a different time-zone, the same basic situation arises. – Jerry Coffin Jun 23 '17 at 03:53
  • Why s_month[5] and not s_month[4]? – NoSenseEtAl Oct 23 '19 at 11:52
12

Basing on the description given at gcc.gnu.org the build date can be obtain at the compilation time using following macros.

#define BUILDTM_YEAR (\
    __DATE__[7] == '?' ? 1900 \
    : (((__DATE__[7] - '0') * 1000 ) \
    + (__DATE__[8] - '0') * 100 \
    + (__DATE__[9] - '0') * 10 \
    + __DATE__[10] - '0'))

#define BUILDTM_MONTH (\
    __DATE__ [2] == '?' ? 1 \
    : __DATE__ [2] == 'n' ? (__DATE__ [1] == 'a' ? 1 : 6) \
    : __DATE__ [2] == 'b' ? 2 \
    : __DATE__ [2] == 'r' ? (__DATE__ [0] == 'M' ? 3 : 4) \
    : __DATE__ [2] == 'y' ? 5 \
    : __DATE__ [2] == 'l' ? 7 \
    : __DATE__ [2] == 'g' ? 8 \
    : __DATE__ [2] == 'p' ? 9 \
    : __DATE__ [2] == 't' ? 10 \
    : __DATE__ [2] == 'v' ? 11 \
    : 12)

#define BUILDTM_DAY (\
    __DATE__[4] == '?' ? 1 \
    : ((__DATE__[4] == ' ' ? 0 : \
    ((__DATE__[4] - '0') * 10)) + __DATE__[5] - '0'))
ptwolny
  • 121
  • 1
  • 2
9

Jerry's function looks great. Here's my attempt.. I threw in the __TIME__ as well.

#include <iostream>
#include <sstream>

using namespace std;

time_t time_when_compiled()
{
    string datestr = __DATE__;
    string timestr = __TIME__;

    istringstream iss_date( datestr );
    string str_month;
    int day;
    int year;
    iss_date >> str_month >> day >> year;

    int month;
    if     ( str_month == "Jan" ) month = 1;
    else if( str_month == "Feb" ) month = 2;
    else if( str_month == "Mar" ) month = 3;
    else if( str_month == "Apr" ) month = 4;
    else if( str_month == "May" ) month = 5;
    else if( str_month == "Jun" ) month = 6;
    else if( str_month == "Jul" ) month = 7;
    else if( str_month == "Aug" ) month = 8;
    else if( str_month == "Sep" ) month = 9;
    else if( str_month == "Oct" ) month = 10;
    else if( str_month == "Nov" ) month = 11;
    else if( str_month == "Dec" ) month = 12;
    else exit(-1);

    for( string::size_type pos = timestr.find( ':' ); pos != string::npos; pos = timestr.find( ':', pos ) )
        timestr[ pos ] = ' ';
    istringstream iss_time( timestr );
    int hour, min, sec;
    iss_time >> hour >> min >> sec;

    tm t = {0};
    t.tm_mon = month-1;
    t.tm_mday = day;
    t.tm_year = year - 1900;
    t.tm_hour = hour - 1;
    t.tm_min = min;
    t.tm_sec = sec;
    return mktime(&t);
}

int main( int, char** )
{
    cout << "Time_t when compiled: " << time_when_compiled() << endl;
    cout << "Time_t now: " << time(0) << endl;

    return 0;
}
tfinniga
  • 6,693
  • 3
  • 32
  • 37
  • 1
    Shouldn't it be `t.tm_hour = hour - 1;`? https://msdn.microsoft.com/en-us/library/windows/hardware/ff567981(v=vs.85).aspx – gmas80 Jun 17 '17 at 13:13
8

I don't know if any other Arduino hackers will stumble upon this question, but I found @JerryCoffin's answer to be quite helpful in solving this problem for my project. Here is a complete example you can paste into Arduino. It uses the Time lib referenced here.

#include "Arduino.h"
#include <Time.h>
#include <stdio.h>

time_t cvt_date(char const *date) { 
    char s_month[5];
    int month, day, year;
    tmElements_t tmel;
    static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

    sscanf(date, "%s %d %d", s_month, &day, &year);

    month = (strstr(month_names, s_month)-month_names)/3+1;

    tmel.Hour = tmel.Minute = tmel.Second = 0; // This was working perfectly until 3am then broke until I added this.
    tmel.Month = month;
    tmel.Day = day;
 // year can be given as full four digit year or two digts (2010 or 10 for 2010);
 //it is converted to years since 1970
  if( year > 99)
      tmel.Year = year - 1970;
  else
      tmel.Year = year + 30;

    return makeTime(tmel);
}

void printdate(char const *date)
{
  Serial.println((String)"cvt_date('" + date + "')");
  time_t t = cvt_date(date);

  Serial.println((String) month(t) + "-" + day(t) + "-" + year(t));
  setTime(t);
  Serial.println((String) month()  + "/" + day()  + "/" + year() + "\n");
}

void setup()
{
  Serial.begin(9600); while (!Serial);
  printdate(__DATE__);       // works with the compiler macro
  printdate("Jan  1    00"); // works with 2 digit years
  printdate("Feb  28   01");
  printdate("Mar  7 5");     // works with 1 digit years
  printdate("Apr  10 1970"); // works from 1970
  printdate("May  13 1980");
  printdate("Jun  16 1990");
  printdate("Jul  19 1997");
  printdate("Aug  22 2000");
  printdate("Sep  25 2010");
  printdate("Oct  31 2014");
  printdate("Nov  30 2020");
  printdate("Dec  31 2105"); // through 2105
  printdate("Dec  31 2106"); // fails at and after 2106
}

void loop(){
}

Here are the Serial Terminal results...

cvt_date('Oct  5 2014')
10-5-2014
10/5/2014

cvt_date('Jan  1    00')
1-1-2000
1/1/2000

cvt_date('Feb  28   01')
2-28-2001
2/28/2001

cvt_date('Mar  7 5')
3-7-2005
3/7/2005

cvt_date('Apr  10 1970')
4-10-1970
4/10/1970

cvt_date('May  13 1980')
5-13-1980
5/13/1980

cvt_date('Jun  16 1990')
6-16-1990
6/16/1990

cvt_date('Jul  19 1997')
7-19-1997
7/19/1997

cvt_date('Aug  22 2000')
8-22-2000
8/22/2000

cvt_date('Sep  25 2010')
9-25-2010
9/25/2010

cvt_date('Oct  31 2014')
10-31-2014
10/31/2014

cvt_date('Nov  30 2020')
11-30-2020
11/30/2020

cvt_date('Dec  31 2105')
12-31-2105
12/31/2105

cvt_date('Dec  31 2106')
11-23-1970
11/23/1970

If all you want is to use the __DATE__ and you don't need a time_t or tmElements_t object, the code can be much simpler.

void logname(char const *date, char *buff) { 
    int month, day, year;
    static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
    sscanf(date, "%s %d %d", buff, &day, &year);
    month = (strstr(month_names, buff)-month_names)/3+1;
    sprintf(buff, "%d%02d%02d.txt", year, month, day);
}

void setup()
{
  Serial.begin(9600); while (!Serial);
  Serial.print("log file name: ");
  char filename[16];
  logname(__DATE__, filename);
  Serial.println(filename);
}

void loop(){
}

Here are the Serial Terminal results...

log file name: 20141009.txt
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
4

Bruno's response was very useful for my Arduino Project. Here is a version with both __DATE__ and __TIME__

#include <Time.h>
#include <stdio.h>

time_t cvt_date(char const *date, char const *time)
{
    char s_month[5];
    int year;
    tmElements_t t;
    static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

    sscanf(date, "%s %hhd %d", s_month, &t.Day, &year);
    sscanf(time, "%2hhd %*c %2hhd %*c %2hhd", &t.Hour, &t.Minute, &t.Second);

    // Find where is s_month in month_names. Deduce month value.
    t.Month = (strstr(month_names, s_month) - month_names) / 3 + 1;

    // year can be given as '2010' or '10'. It is converted to years since 1970
    if (year > 99) t.Year = year - 1970;
    else t.Year = year + 30;

    return makeTime(t);
}

void setup()
{
    Serial.begin(115200); 
    while (!Serial);

    // Show raw system strings
    Serial.println(String("__DATE__ = ") + __DATE__);
    Serial.println(String("__TIME__ = ") + __TIME__);

    // set system time = compile time
    setTime(cvt_date(__DATE__, __TIME__));

    // Show actual time
    Serial.println(String("System date = ") + month() + "/" + day() + "/" + year() + " " + hour() + ":" + minute() + ":" + second() + "\n");
}

void loop() {}
quickbug
  • 4,708
  • 5
  • 17
  • 21
4

Here is how I modified your sample to use with mbed for Arm32 micro controllers in C++.

// Convert compile time to system time 
time_t cvt_date(char const *date, char const *time)
{
    char s_month[5];
    int year;
    struct tm t;
    static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
    sscanf(date, "%s %d %d", s_month, &t.tm_mday, &year);
    sscanf(time, "%2d %*c %2d %*c %2d", &t.tm_hour, &t.tm_min, &t.tm_sec);
    // Find where is s_month in month_names. Deduce month value.
    t.tm_mon = (strstr(month_names, s_month) - month_names) / 3 + 1;    
    t.tm_year = year - 1900;    
    return mktime(&t);
}

See: https://developer.mbed.org/users/joeata2wh/code/compile_time_to_system_time/ for complete code. Also see https://developer.mbed.org/users/joeata2wh/code/xj-Init-clock-to-compile-time-if-not-alr/ for an example of how I use it to initialize a clock chip based on the compile time.

-2

Answer here.

Spec for the format of DATE is here.

user9876
  • 10,954
  • 6
  • 44
  • 66