Determining Build Date the hard way

One of the key diagnostic data points for any .NET assembly is "when was it built"? Until recently, I thought there were only two ways to suss this out:

This is a companion discussion topic for the original blog entry at:

Thanks for that! I’ll correct and update that post over the next few days.

I’ve read your blog before. Nice to see stuff about VB.NET. I’m not the biggest VB.NET fan, but I seem to end up using it a lot and it’s not going away. :slight_smile:


That whole “number of days” and “number of seconds” thing made me nervous, even without having to open and close the IDE. Even though some people passionately explained to me why it’s a good thing, those numbers seem basically random and make it hard to know what version you are in fact looking at. So I (and lots of others) wrote a little VS macro to update the build number every time I do a build, and I update the other three according to the bigness of the change. Then the file version is pretty useful, I think.

Great blog, btw.

Though, upon considering your post, I realize that my method doesn’t tell you anything about when the file was actually built. I bet another macro could be used to take this string and write it as a resource in the executable. Probably not as sexy as Dustin’s approach though.

So I (and lots of others) wrote a little VS macro to update the build number every time I do a build

Yes, that’s certainly logical. But it begs the question: why didn’t Microsoft do it this way? Oversight? Or did they just want to ensure the build and revision number are different every time in the absence of any real metadata provided by the developer?

My method doesn’t tell you anything about when the file was actually built.

Right, and build # is kind of a meaningless number anyway. Does anyone remember the build number of the first release of NT 4.0? of XP? The date of the build is much more useful information than how many times the developers on the project happened to press the F5 key before it was packaged into a box and shipped…

Given that a 32 bit int from 1970 will expire in 2038, which is coming up soon, I think I’d prefer the assembly version method instead.


Thanks for that great solution. I converted it to c# (with minor mod), listed here if anyone wants it (sorry the formatting looks so bad :slight_smile:

	private DateTime RetrieveLinkerTimestamp()
		string filePath = System.Reflection.Assembly.GetCallingAssembly().Location;
		const int c_PeHeaderOffset = 60;
		const int c_LinkerTimestampOffset = 8;
		byte[] b = new byte[2048];
		System.IO.Stream s = null;

			s = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
			s.Read(b, 0, 2048);
			if (s != null)

		int i = System.BitConverter.ToInt32(b, c_PeHeaderOffset);
		int secondsSince1970 = System.BitConverter.ToInt32(b, i + c_LinkerTimestampOffset);
		DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0);
		dt = dt.AddSeconds(secondsSince1970);
		dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours);
		return dt;

Thanks for the Code

txs that helped!

Best site on that topic I found in the web.
Thanks for the code.

The build hour and minutes not changing on each release .NET build compile caught me out. Norton AV prompted every time the binary changed, so I’d expected version number to change. Open/Close IDE – nice tip!

Thanks Joe, your code worked perfectly.

1.0.* doesn’t work it show 1.0.* or error. Article is useless.

Andy (above) thinks he’s being clever, but in fact his curt little ‘error report’ has robbed him of any chance of help. Well done you. It works perfectly for me, although it did take a few tries with asterisks and blanks around different places to get it going. (I’m on C# Express 2008) This is what patience and being polite gets you.

Thanks, Joe.

Thanks Jeff too – seems only fair!

Thank you so much Joe. I just added your method in my about box and sure enough, it compiled and worked right immediately!

nice try but
Function RetrieveLinkerTimestamp() As DateTime

doesn’t change with each build cycle, :

Const PeHeaderOffset As Integer = 60
Const LinkerTimestampOffset As Integer = 8
Dim filePath = System.Reflection.Assembly.GetCallingAssembly().Location

Dim b(2047) As Byte
Dim s As Stream
s = New FileStream(filePath, FileMode.Open, FileAccess.Read)
s.Read(b, 0, 2048)
If Not s Is Nothing Then s.Close()
End Try

Dim i As Integer = BitConverter.ToInt32(b, PeHeaderOffset)

Dim SecondsSince1970 As Integer = BitConverter.ToInt32(b, i + LinkerTimestampOffset)
Dim dt As New DateTime(1970, 1, 1, 0, 0, 0)
dt = dt.AddSeconds(SecondsSince1970)
dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours)
Return dt
End Function

only way so far is to exit and reload/restart vs.

what a pain.

Thank you so much Joe. I just used your C# code and it worked like a charm. Our busy testing environment needed to have the latest version with latest build date set.

Could someone please explain how/why this works?
I see we get 4 bytes as int from the 60th byte in the PE and assign it to i.
Which I assuem is the time.
We then get prosumeably the seconds since 1970, which is found at …
(i plus the constant 8)th byte in the PE.

So this means that the time we get from the PE is always in a different possition, which is dependant on the variable i (according to the code).

Or have I missed something here?