65
  1. Making some changes in Android Contacts package
  2. Using mm (make) command to build this application

Because I have to change and build this app again and again, so I want to add a build time stamp in the Contacts.apk to check the build time when we runn it in the handset.

As we know, when we run mm command, the Android.mk (makefile) in Contacts package will be called.

And now, we can get the build time using date-macro.

But how we can write this build time stamp into a file that our application can read at runtime?

Any suggestions?

Lukas Knuth
  • 25,449
  • 15
  • 83
  • 111
FallingRain
  • 671
  • 1
  • 5
  • 7
  • Are you wanting to add the build timestamp to the apk itself? so it becomes `MyApp-201109302359.apk`? – Jimmy Sep 30 '11 at 07:30
  • 1
    Thanks, I have achieve this. as MyApp-201109302359.apk, but as you know, I have to build this app many times. If I do so, there will be many MyApp-*.apk in *system/app folder, it's not what I want. – FallingRain Sep 30 '11 at 07:55
  • I want to write the build timestamp into a file that my apk can read from and display it in Activity at runtime. – FallingRain Sep 30 '11 at 09:10
  • I posted a way to do this w/Android Studio & gradle here: http://stackoverflow.com/a/22649533/3035127 – fattire Mar 26 '14 at 01:09

10 Answers10

154

If you use Gradle, you can add buildConfigField with timestamp updated at build time.

android {
    defaultConfig {
        buildConfigField "long", "TIMESTAMP", System.currentTimeMillis() + "L"
    }
}

Then read it at runtime.

Date buildDate = new Date(BuildConfig.TIMESTAMP);
Ben
  • 258
  • 3
  • 11
Aleksejs Mjaliks
  • 8,647
  • 6
  • 38
  • 44
  • 3
    I find this to be the best option if you are using Gradle – Hugh Jeffner Dec 02 '16 at 15:46
  • 1
    Thanks, I was looking quite a while for something so easy – Seltsam Mar 01 '17 at 16:30
  • 3
    This should be considered as the best answer. – pepan Apr 03 '17 at 21:46
  • What is BuildConfig?. you should add the class definition. – Yamur Feb 07 '18 at 10:23
  • No longer seems to work. I get the error: Could not find method buildConfigField() for arguments [long, BUILD_TIMESTAMP] on DefaultConfig_Decorated – Stephen M -on strike- Jul 20 '18 at 16:49
  • 2
    This will slow down your build times. Avoid using this for developer builds – Simon Featherstone Jan 08 '20 at 14:04
  • Attention: build your project after you edited gradle file, but before using BuildConfig.BUILD_TIME in code, else you may see "cannot find BUILD_TIME "! – Mneckoee Jul 12 '21 at 08:28
  • On new Android Studio post Flamingo Canary 9 you'll need to make sure buildconfig is manually enabled for this to not error out, so see https://stackoverflow.com/questions/74634321/fixing-the-build-type-contains-custom-buildconfig-fields-but-the-feature-is-di/74634322#74634322 where I also put a newer/different way to add the timestamp. – fattire Dec 03 '22 at 00:19
96

Method which checks date of last modification of classes.dex, this means last time when your app's code was built:

  try{
     ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), 0);
     ZipFile zf = new ZipFile(ai.sourceDir);
     ZipEntry ze = zf.getEntry("classes.dex");
     long time = ze.getTime();
     String s = SimpleDateFormat.getInstance().format(new java.util.Date(time));
     zf.close();
  }catch(Exception e){
  }

Tested, and works fine, even if app is installed on SD card.

Pointer Null
  • 39,597
  • 13
  • 90
  • 111
  • 2
    Yes, now I use it too in About dialog :) – Pointer Null Sep 30 '11 at 19:51
  • how can I add that "String s" to .apk name? – hariszaman Jun 24 '15 at 09:18
  • 2
    Hmm, I just got a NullPointerException using this snippet today, after switching Android Studio to use Instant Run. When building with Instant Run, the ZipFile obtained from ApplicationInfo does not contain a "classes.dex" entry, causing the NPE. I switched to "AndroidManifest.xml" and it seems to have resolved the issue in my project. – Damien Diehl Jan 15 '16 at 01:54
  • 8
    With Gradle 2.2.1 the exact code above returns 11/30/1979 12:00 AM for classes.dex (and all other files in the APK). Anyone get around to this issue? EDIT: Ignore. As per comment below this is an issue as mentioned here: https://code.google.com/p/android/issues/detail?id=220039 – Gautam Oct 19 '16 at 13:40
22

Since API version 9 there's:

PackageInfo.lastUpdateTime

The time at which the app was last updated.

try {
    PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
    //TODO use packageInfo.lastUpdateTime
} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
}

On lower API versions you must make build time yourself. For example putting a file into assets folder containing the date. Or using __ DATE__ macro in native code. Or checking date when your classes.dex was built (date of file in your APK).

Evgeny Nozdrev
  • 1,530
  • 12
  • 15
Pointer Null
  • 39,597
  • 13
  • 90
  • 111
  • Thanks so much, your advice is so valueable. ^_^ I'am checking the method using PackageInfo.lastUpdateTime... – FallingRain Sep 30 '11 at 08:07
  • How to check my own classes.dex's last modified date in my own application's java code? And what is worse, I find the classes.dex is not updated when I rebuild my application. – FallingRain Sep 30 '11 at 08:35
  • PackageInfo.lastUpdate is not works for me, cause its value always is "2000-01-01". – FallingRain Sep 30 '11 at 08:46
  • i use the PackageInfo.lastUpdateTime in this way: getPackageManager().getPackageInfo(getPackageName(), 0).lastUpdateTime , am I using is correctly? but it's value always is 2000.01.01 – FallingRain Sep 30 '11 at 08:52
  • 8
    Now I see, I can not use PackageInfo.lastUpdateTime, because it depends on the target handset's time. everytime the apk is installed or updated, the PackageInfo.lastUpdateTime will be update by reading the end-user's local time.And what need is the apk's built time. So, I think I have to use __DATE__ macro in native code. – FallingRain Sep 30 '11 at 09:04
  • Try method of classes.dex date. It's supposed to be changed when some java source is rebuilt. Date may be checked by opening app's APK file as ZipFile, locate ZipEntry for classes.dex and call getTime() to get its last modification time. – Pointer Null Sep 30 '11 at 09:38
  • 15
    PackageInfo.lastUpdate is installed datetime. It is different from build datetime. – pretty angela Feb 19 '13 at 07:33
  • I can confirm that PackageInfo.lastUpdateTime was displaying the date of install. pretty angela's answer worked for me. – Joel Malone Aug 14 '13 at 10:33
13

Edit: My answer does not work anymore since option keepTimestampsInApk was removed. Working in 2020 is https://stackoverflow.com/a/26372474/6937282 (also https://stackoverflow.com/a/22649533/6937282 for more details)

Original answer:

A hint for solution "last modification time of classes.dex file" an newer AndroidStudio versions: In default config the timestamp is not written anymore to files in apk file. Timestamp is always "Nov 30 1979".

You can change this behavior by adding this line to file

%userdir%/.gradle/gradle.properties (create if not existing)

android.keepTimestampsInApk = true

See Issue 220039

(Must be in userdir, gradle.properties in project build dir seems not to work)

allofmex
  • 557
  • 1
  • 4
  • 16
6
Install time : packageInfo.lastUpdateTime
build time   : zf.getEntry("classes.dex").getTime()

Both are differnet time. You can check with the code below.

public class BuildInfoActivity extends Activity {

    private static final String TAG = BuildInfoActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try {

            PackageManager pm = getPackageManager();
            PackageInfo packageInfo = null;
            try {
                packageInfo = pm.getPackageInfo(getPackageName(), 0);
            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }

            // install datetime
            String appInstallDate = DateUtils.getDate(
                    "yyyy/MM/dd hh:mm:ss.SSS", packageInfo.lastUpdateTime);

            // build datetime
            String appBuildDate = DateUtils.getDate("yyyy/MM/dd hh:mm:ss.SSS",
                    DateUtils.getBuildDate(this));

            Log.i(TAG, "appBuildDate = " + appBuildDate);
            Log.i(TAG, "appInstallDate = " + appInstallDate);

        } catch (Exception e) {
        }

    }

    static class DateUtils {

        public static String getDate(String dateFormat) {
            Calendar calendar = Calendar.getInstance();
            return new SimpleDateFormat(dateFormat, Locale.getDefault())
                    .format(calendar.getTime());
        }

        public static String getDate(String dateFormat, long currenttimemillis) {
            return new SimpleDateFormat(dateFormat, Locale.getDefault())
                    .format(currenttimemillis);
        }

        public static long getBuildDate(Context context) {

            try {
                ApplicationInfo ai = context.getPackageManager()
                        .getApplicationInfo(context.getPackageName(), 0);
                ZipFile zf = new ZipFile(ai.sourceDir);
                ZipEntry ze = zf.getEntry("classes.dex");
                long time = ze.getTime();

                return time;

            } catch (Exception e) {
            }

            return 0l;
        }

    }
}
pretty angela
  • 2,241
  • 1
  • 19
  • 8
6

in your build.gradle:

android {
    defaultConfig {
        buildConfigField 'String', 'BUILD_TIME', 'new java.text.SimpleDateFormat("MM.dd.yy HH:mm", java.util.Locale.getDefault()).format(new java.util.Date(' + System.currentTimeMillis() +'L))'
    }
}
Matteljay
  • 620
  • 5
  • 13
Marco Schmitz
  • 219
  • 4
  • 8
5

I use the same strategy as Pointer Null except I prefer the MANIFEST.MF file. This one is regenerated even if a layout is modified (which is not the case for classes.dex). I also force the date to be formated in GMT to avoid confusion between terminal and server TZs (if a comparison has to be made, ex: check latest version).

It result in the following code:

  try{
     ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), 0);
     ZipFile zf = new ZipFile(ai.sourceDir);
     ZipEntry ze = zf.getEntry("META-INF/MANIFEST.MF");
     long time = ze.getTime();
     SimpleDateFormat formatter = (SimpleDateFormat) SimpleDateFormat.getInstance();
     formatter.setTimeZone(TimeZone.getTimeZone("gmt"));
     String s = formatter.format(new java.util.Date(time));
     zf.close();
  }catch(Exception e){
  }
Damien
  • 767
  • 9
  • 13
5

So Android Developer - Android Studio User Guide - Gradle Tips and Recipes - Simplify App Development actually documents what to add in order to have a release timestamp available to your app:

android {
  ...
  buildTypes {
    release {
      // These values are defined only for the release build, which
      // is typically used for full builds and continuous builds.
      buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
      resValue("string", "build_time", "${minutesSinceEpoch}")
      ...
    }
    debug {
      // Use static values for incremental builds to ensure that
      // resource files and BuildConfig aren't rebuilt with each run.
      // If they were dynamic, they would prevent certain benefits of
      // Instant Run as well as Gradle UP-TO-DATE checks.
      buildConfigField("String", "BUILD_TIME", "\"0\"")
      resValue("string", "build_time", "0")
    }
  }
}
...

In your app code, you can access the properties as follows:

...
Log.i(TAG, BuildConfig.BUILD_TIME);
Log.i(TAG, getString(R.string.build_time));

I'm including this here since all of the other solutions appear to be from before the official example.

Morrison Chang
  • 11,691
  • 3
  • 41
  • 77
4

I know this is really old, but here's how I did it using ant within eclipse:

build.xml in project root

<project name="set_strings_application_build_date" default="set_build_date" basedir=".">
    <description>
        This ant script updates strings.xml application_build_date to the current date
    </description>

    <!-- set global properties for this build -->
    <property name="strings.xml"  location="./res/values/strings.xml"/>

    <target name="init">
        <!-- Create the time stamp -->
        <tstamp/>
    </target>

    <target name="set_build_date" depends="init" description="sets the build date" >

        <replaceregexp file="${strings.xml}"
            match="(&lt;string name=&quot;application_build_date&quot;&gt;)\d+(&lt;/string&gt;)"
            replace="&lt;string name=&quot;application_build_date&quot;&gt;${DSTAMP}&lt;/string&gt;" />

    </target>
</project>

Then add an application_build_date string to your strings.xml

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <string name="app_name">your app name</string>
    <string name="application_build_date">20140101</string>
    ...
</resources>

Ensure the ant script is executed as a pre-build activity and you will always have a valid build date available to you within R.string.application_build_date.

ShellDude
  • 579
  • 6
  • 12
  • Maybe it is a little old, but very useful. Can you help me how to do it with gradle? – Aaron Lee Jun 04 '15 at 06:44
  • so I actually just implemented this same script in gradle, but in hindsight I should have used @AleksejsMjaliks solution. His approach would integrate it directly into BuildConfig which is even more cool because you have build type, flavor, version text, and version code are already tucked away inside of it. – ShellDude Jan 02 '16 at 01:17
  • but to answer your question, put it in the root of your project folder, update the path to strings.xml (needs to be location="./src/main/res/values/strings.xml" in Android Studio) and then simply import it into grade using ant.importBuild 'build.xml' at the top of your gradle.build file. You can then add it as a dependent task to your assembly tasks. – ShellDude Jan 02 '16 at 01:21
1

For time stamping and versioning, build.gradle/android/defaultConfig:

def buildDateStamp = new Date().format("yyyyMMdd").toInteger()
versionCode buildDateStamp
versionName "$buildDateStamp"
buildConfigField "String", "BUILD_DATE_STAMP", "\"$buildDateStamp\""

Usage in code: BuildConfig.BUILD_DATE_STAMP

resValue "string", "build_date_stamp", "$buildDateStamp"

Usage in xml: "@string/build_date_stamp"

Caveat: adding HHmm will cause errors (probably integer overflow)

Matteljay
  • 620
  • 5
  • 13