23

I would like to define in my code a constant holding the date on which the executable was built. I would naturally like to automate that process.

I know that I can write a pre-build script using, for example, Perl, to write out a .inc file containing the date. I would prefer a more lightweight solution using, perhaps, environment variables or build variables. Does msbuild provide any variables that would help? Does anyone know a neater solution to the problem?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I would naturally look at the EXE file's `Date Modified` and/or `Date Created` to identify this, but not sure if that may differ from the official build date - so I'm commenting rather than answering. – Jerry Dodge Dec 08 '11 at 20:42
  • That is, use `Application.ExeName` to get the attributes of the file, reading the necessary date(s). – Jerry Dodge Dec 08 '11 at 20:43
  • 1
    @jerry That can be modified. I want the date when the exe was built, as a constant – David Heffernan Dec 08 '11 at 20:45
  • Is'nt in the last Delphi versions a TCompile unit wich may be used for it ? – philnext Dec 08 '11 at 21:00
  • True, I didn't think of the modification abilities. – Jerry Dodge Dec 08 '11 at 21:07
  • In the prebuild phase, call an exe which produces a .pas file with constants holding your actual wanted values. This .pas file is compiled into your software. Mission complete. Used to to this in TP. – LU RD Dec 08 '11 at 21:11
  • @philnext, that TCompile unit seems to be in Code Central (http://cc.embarcadero.com/item/25359), though can not test it because it's on maintenance :S – Guillem Vicens Dec 08 '11 at 21:13
  • You want something lighter weight than a one-line Perl command? You don't even need a separate script file. Just run `perl -e "print 'const BuildDate = ' . time() . ';\n'" > BuildDate.inc`. Or are you looking to remove the Perl dependency? – Rob Kennedy Dec 08 '11 at 21:15
  • 1
    @Rob I suppose I'm trying to avoid needing a .inc file but I have a feeling that's just not going to be possible. I was hoping there might be a slick way to get a build variable into the source. – David Heffernan Dec 08 '11 at 21:17
  • Unless there's a compiler directive that nobody's ever heard of yet, I don't think you're going to get anything better, so then it just becomes an exercise in making the inc-file-generator as lean as possible. I'm sure there are IDE experts that will do something like that, but they're only appropriate if you only ever build from within the IDE, so it won't work with external build tools. – Rob Kennedy Dec 08 '11 at 21:21
  • @GuillemVicens Yes, so I explain it in an answer, since the link may be out of order. – philnext Dec 08 '11 at 22:43
  • I loved the wild solution that Marcus offered and it would be great if it could be undeleted! – David Heffernan Dec 08 '11 at 22:47
  • @David: It is interesting. Voted to undelete. :) – Ken White Dec 08 '11 at 23:31

8 Answers8

38

You can read the linker timestamp from the PE header of the executable:

uses
  ImageHlp;

function LinkerTimeStamp(const FileName: string): TDateTime; overload;
var
  LI: TLoadedImage;
begin
  Win32Check(MapAndLoad(PChar(FileName), nil, @LI, False, True));
  Result := LI.FileHeader.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;
  UnMapAndLoad(@LI);
end;

For the loaded image of the current module, the following seems to work:

function LinkerTimestamp: TDateTime; overload;
begin
  Result := PImageNtHeaders(HInstance + Cardinal(PImageDosHeader(HInstance)^._lfanew))^.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;
end;

Earlier versions of Delphi didn't update it correctly but it has been fixed around Delphi 2010 or so. For the earlier versions, I used an IDE plugin to update it automatically after a successful compile.

Note: The value is stored as UTC so for display purposes you may need to convert it to an appropriate timezone.

Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
  • 2
    Thanks a lot. I didn't think this would be possible. I'm glad I asked now! – David Heffernan Dec 08 '11 at 23:25
  • 3
    Great! You can use DateUtils routine to convert to local time: `TTimeZone.Local.ToLocalTime()` – Francesca Dec 09 '11 at 19:41
  • Note that `PImageDosHeader(HInstance)^._lfanew` is a signed integer, while `HInstance` is an unsigned integer. To prevent warnings, cast it to an unsigned integer like so: `Cardinal(PImageDosHeader(HInstance)^._lfanew)` – vehystrix Jan 29 '16 at 14:53
9

If you use/have JCL then jclPEImage.PeReadLinkerTimeStamp() does what you're after. Old versions of Delphi didn't set the linker timestamp, but AFAIK new versions do (ie it seems to work OK with D2010).

ain
  • 22,394
  • 3
  • 54
  • 74
5

DDevExtensions has an option to include the compile date and time into the versioninfo resource. I guess I don't have to show you how to extract that from inside the program...

Update Regarding automated builds: FinalBuilder has a similar option.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
2

This code works in newer Delphi versions, combines getting of the time and converting it to local time.

function GetExeBuildTime: TDateTime;
var
  LI: TLoadedImage;
  {$IF CompilerVersion >= 26.0}
  m: TMarshaller;
  {$IFEND}
  timeStamp: Cardinal;
  utcTime: TDateTime;
begin
  {$IF CompilerVersion >= 26.0} //XE7 requires TMarshaller to convert to PAnsiChar
  Win32Check(MapAndLoad(PAnsiChar(m.AsAnsi(ParamStr(0)).ToPointer), nil, @LI, False, True));
  {$ELSE}
  Win32Check(MapAndLoad(PAnsiChar(AnsiString(ParamStr(0))), nil, @LI, False, True));
  {$IFEND}
  timeStamp := LI.FileHeader.FileHeader.TimeDateStamp;
  UnMapAndLoad(@LI);

  utcTime := UnixToDateTime(timeStamp);
  Result := TTimeZone.Local.ToLocalTime(utcTime);
end;
Kromster
  • 7,181
  • 7
  • 63
  • 111
1

In fact TCompile is not a Delphi unite. You can find it here, on EDN. And (just a copy of the page) :

Nonvisual component that modifies the unit file "Project.pas". This file has the date and time the project was compiled along with the build number.Drop this component on the TForm, then set the project path. This can be done by placing the caret in the ProjectPath property field, then pressing the ENTER key.

Include "Project" in the USES clause; Save the project, then exit Delphi. The next time the project is opened in Delphi, the unit file "Project.pas" will be created. Then each time you run the program from inside Delphi, the unit file will be updated with the current date, time and the build number.

philnext
  • 3,242
  • 5
  • 39
  • 62
1

I use FinalBuilder to do all my builds, and with that it is easy to add a utility that will update any source file in advance of compilation to search for and modify the definition of a variable or constant. I do build version numbers, dates, anything that makes sense this way.

mj2008
  • 6,647
  • 2
  • 38
  • 56
0

Any version of Delphi, just windows batch to create an .inc file before build. No IDE needed at all.

Here the script:

REM CommandInterpreter: $(COMSPEC)
@echo off

setlocal enabledelayedexpansion  
setlocal
rem determine project top level directory from command file name
set PRJDIR=%~dp0
cd %PRJDIR%

set BUILD_DATE_FILE=%PRJDIR%Source\BuildDate.inc

call :GetGurrentDateTime&set BUILD_YEAR=!current_year!&set BUILD_MONTH=!current_month!&set BUILD_DAY=!current_day!&set BUILD_TIME=!current_time!

echo const BUILD_YEAR = %BUILD_YEAR%;> "%BUILD_DATE_FILE%"
:: 3 letter name Apr for April
echo const BUILD_MONTH = '%BUILD_MONTH%';>> "%BUILD_DATE_FILE%"
echo const BUILD_DAY = %BUILD_DAY%;>> "%BUILD_DATE_FILE%"
echo const BUILD_TIME = '%BUILD_TIME%';>> "%BUILD_DATE_FILE%"

goto :EOF
echo Done
exit /b 0

:GetGurrentDateTime
rem GET CURRENT DATE
echo.>"%TEMP%\~.ddf"
makecab /D RptFileName="%TEMP%\~.rpt" /D InfFileName="%TEMP%\~.inf" -f "%TEMP%\~.ddf">nul
for /f "tokens=4,5,6,7" %%a in ('type "%TEMP%\~.rpt"') do (
    if not defined current_date ( 
        set "current_date=%%d-%%a-%%b"
        set "current_time=%%c"   
    set "current_year=%%d"
    set "current_month=%%a"
    set "current_day=%%b"
    )
)

In the source code just include BuildDate.inc file and use consts BUILD_*.

Dr.eel
  • 1,837
  • 3
  • 18
  • 28
-1
  TDateTime     date_time;
  long          dt_sec;
  LOADED_IMAGE  LI;

  MapAndLoad   (ParamStr (0). c_str (), NULL, &LI, False, True);
  dt_sec = ((LI.FileHeader)-> FileHeader). TimeDateStamp;
  UnMapAndLoad (&LI);

  display_memo ("%x", dt_sec);

  date_time = EncodeDate (1970, 1, 1) + (double) dt_sec / 24. / 60. / 60.;

  display_memo ("%s", FormatDateTime ("yyyy-mm-dd hh:nn:ss", date_time). c_str ());
puiu29
  • 11
  • 1
    How does this answer the question? Especially since this seems to be C code and the question is tagged Delphi? – dummzeuch Jan 07 '22 at 12:47