How can a game automatically display the date/time of when it was built, and/or an incremented revision string?

Sometimes it is useful for a game to display version information, or a revision number such as is assigned by version control or continuous integration solution. What's a good way to do this for a Unity game?

The usual way how this is done is by having keywords which are expanded by the version control on checkin. You can then use that keyword in a string in your game and simply display that.

Something like:

const string GAME_VERSION = "$Date: $"; // that's how it would be done with CVS

public void OnGUI() {
    GUI.Label(new Rect (10, 10, 100, 20), GAME_VERSION);
}

Now, as far as I know, Asset Server doesn't have such keywords (that would be a feature-request ;-) ). Examples of keywords used by other version control systems:

Of course, you might want to be doing some string-cleanup to remove the "cryptic" parts of the version control keywords.

Personally, I prefer to use labels I manually assign for releases (you could do that in a property in one of your "core management" prefabs that you can easily change in the Unity editor and that is accessible from where ever you have the GUI code to display that information). Usually, that creates more meaningful information for actual users and seems like a small extra-step to take while creating my builds. However, using the automatic assignment of dates through version control might be helpful in "hot development" (when you push out releases every few days). Keep in mind, though: That value will only change when you actually made a change to that file.

Finally, there's yet another solution: If you have a custom build-system (which can be implemented Unity's batch mode features), you might do your own replacement in the code during the build; and you'd increase the number for each build (or include the build-date, or whichever information you find useful). You could also keep that number / text in a little text-file that your game-code can access (might be easier to set up than doing the string replacement in your code but adds a little complexity in your game-code).

In my opinion, that's the optimal solution (meaningful values + no manual work involved once it's set up + totally customizable) - but also the most complex one to set up. But when you know how to set up a build-system, that should be one of your smaller concerns ;-)

Here’s a simple solution that uses the C# assembly version.

Specify the AssemblyVersion attribute like so:

using System.Reflection;

[assembly:AssemblyVersion( "1.0.*.*" )]

public class MainMenu : MonoBehaviour {
...

And you can retrieve a formatted version number like so:

print( Assembly.GetExecutingAssembly().GetName().Version.Tostring() );

This prints out the Version in Major.Minor.Build.Revision format (e.g. “1.0.5557.38042”). The Build number is the number of days since Jan. 1, 2000. The Revision number is half the number of seconds since 12:00 AM that day.

If you want, you can use this information to print out a human-readable timestamp (e.g. “3/20/2015 9:08:04 PM”):

System.Version version = Assembly.GetExecutingAssembly().GetName().Version;
System.DateTime startDate = new System.DateTime( 2000, 1, 1, 0, 0, 0 );
System.TimeSpan span = new System.TimeSpan( version.Build, 0, 0, version.Revision * 2 );
System.DateTime buildDate = startDate.Add( span );
print( buildDate.ToString() );

This solution was adapted from some other solutions:

You can use a compile-time language to create a string. At least I know that Boo has this.

import UnityEngine



macro build_buildtime_info():

    dateString = System.DateTime.Now.ToString()

    yield [|

        class BuildtimeInfo:

            static def DateTimeString() as string:

                return "${$(dateString)}"

    |]



build_buildtime_info



# # Now you can do this:

# Debug.Log( BuildtimeInfo.DateTimeString() )

Edit:

Unity actually opens every scene during the build process:
This can be exploited as follows:

using UnityEngine;

[ExecuteInEditMode]
public class RefreshTestCS: MonoBehaviour
{
    public string buildName;

    public void OnGUI() {
        GUILayout.Label("Build name: " + buildName);
    }

#if UNITY_EDITOR
    public void Awake() {
        if (Application.isEditor && !Application.isPlaying) {
            buildName = System.DateTime.Now.ToString() + " (" + (UnityEditor.BuildPipeline.isBuildingPlayer ? "during build)" : "during edit)");
        }
    }
#endif
}

EDIT: updated code to make copy of string before returning to C#

Okay here it is:

To be able to show date/time of a build (useful to make sure your testers are using the right version) I made a very simple plugin that adds two functions to retrieve date and time string of the build process. To use it, add the Test.h and Test.mm to the xcode project and JBuildInfo.cs to your unity project. Shouldn't be hard to figure out how to use this I guess...

BuildInfo.h:

//  BuildInfo.h

#import <Foundation/Foundation.h>

@interface BuildInfo : NSObject {
}

@end

BuildInfo.mm:

//  BuildInfo.mm

#import "BuildInfo.h"

@implementation BuildInfo

@end

// Helper method to create C string copy
static char* MakeStringCopy (const char* string)
{
    if (string == NULL)
        return NULL;

    char* res = (char*)malloc(strlen(string) + 1);
    strcpy(res, string);
    return res;
}

extern "C" {
    const char* getBuildDate() {
        return MakeStringCopy(__DATE__);
    }

    const char* getBuildTime() {
        return MakeStringCopy(__TIME__);
    }   
}

JBuildInfo.cs:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class JBuildInfo {

    [DllImport("__Internal")] private static extern string getBuildDate();
    [DllImport("__Internal")] private static extern string getBuildTime();

    public static string GetBuildDate() {
        if (Application.isEditor) {
            return "<not build>";
        }
        else {
            return getBuildDate();
        }
    }

    public static string GetBuildTime() {
        if (Application.isEditor) {
            return "--:--:--";
        }
        else {
            return getBuildTime();
        }
    }
}

For iPhone projects, you can use Apple's built-in tool, agvtool, described here. You can probably even use Jaap's answer to provide the generated strings to Unity.

There is another way for those who don’t want to use source control keywords or a custom build pipeline. The idea is to read the COFF header as described here (“The new way”):

Now, it’s Unity so there is no main assembly. What I check instead is the DLL built from my C# scripts which is in “/Managed/Assembly-CSharp.dll”. Note that it only works outside of the Unity Editor, with an actual built game.

To summarize (C#):

public static string GetScriptBuildDateTime()
{
    string scripDllPath = Path.Combine(Application.dataPath, "Managed/Assembly-CSharp.dll");
    return GetBuildDateTime(scripDllPath).ToString("yyyy-MM-dd HH:mm");
}

#pragma warning disable 0649
struct _IMAGE_FILE_HEADER
{
    public ushort Machine;
    public ushort NumberOfSections;
    public uint TimeDateStamp;
    public uint PointerToSymbolTable;
    public uint NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
};

public static DateTime GetBuildDateTime(string objectPath)
{
    if (File.Exists(objectPath))
    {
        var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
        using (var fileStream = new FileStream(objectPath, FileMode.Open, FileAccess.Read))
        {
            fileStream.Position = 0x3C;
            fileStream.Read(buffer, 0, 4);
            fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
            fileStream.Read(buffer, 0, 4); // "PE\0\0"
            fileStream.Read(buffer, 0, buffer.Length);
        }
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));

            return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
        }
        finally
        {
            pinnedBuffer.Free();
        }
    }
    return new DateTime();
}