Android Get Photo Taken Date

April 23, 2020
MediaStore.MediaColumns.DATE_TAKEN, ExifInterface.TAG_DATETIME_ORIGINAL or Filename Date

You can list albums on your Android phone, and you should access image files via scoped storage.

To find photos on Android device, refer to Android Find/Search Photos/Images in Album.

Code to find photos

fun findImagesInAlbum(albumId: String) {
    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI

    val projections = arrayOf(
        MediaStore.Images.ImageColumns._ID,
        MediaStore.Images.ImageColumns.DATE_TAKEN,
        MediaStore.Images.ImageColumns.DISPLAY_NAME,
    )
    val selection = "${MediaStore.Images.ImageColumns.BUCKET_ID} == ?"
    val selectionArgs = arrayOf(
        albumId
    )

    contentResolver.query(contentUri, projections, selection, selectionArgs, "${MediaStore.Images.ImageColumns.DATE_TAKEN} ASC")?.use { cursor ->

        if (cursor.moveToFirst()) {
            val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)
            val dateTakenIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN)
            val displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME)

            do {
                val mediaId = cursor.getLong(idIndex)
                val filename = cursor.getString(displayNameIndex)
                val millis = cursor.getLong(dateTakenIndex)

                // scoped storage - access file via uri instead of filepath + filename
                var uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediaId)

                // read file
                contentResolver.openInputStream(uri).use { stream ->
                    val exif = ExifInterface(stream)
                })
            }
        }
    }
}

Option 1: MediaStore.MediaColumns.DATE_TAKEN

You can get DATE_TAKEN which is in epoch milli seconds.

Indexed value of MediaMetadataRetriever#METADATA_KEY_DATE or ExifInterface#TAG_DATETIME_ORIGINAL extracted from this media item.

Note that images must define both ExifInterface#TAG_DATETIME_ORIGINAL and ExifInterface#TAG_OFFSET_TIME_ORIGINAL to reliably determine this value in relation to the epoch. Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z.

NOTE: DATE_TAKEN might be miscalculated if some photos have ExifInterface#TAG_OFFSET_TIME_ORIGINAL or GPS info, while others photos doesn’t have such info.

val millis = cursor.getLong(dateTakenIndex)

// convert epoch millis to device timezone date
val date = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault())

// convert epoch millis to UTC date
val dateUtc = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC)

NOTE: If you need to convert millis to DateTime, you need to specify a timezone. Using UTC would gain the most persistence result, or you can guess the timezone of the photo.

Option 2: ExifInterface.TAG_DATETIME_ORIGINAL

Read TAG_DATETIME_ORIGINAL

The date and time when the original image data was generated. For a DSC the date and time the picture was taken are recorded. The format is “YYYY:MM:DD HH:MM:SS” with time shown in 24-hour format

// scoped storage - access file via uri instead of filepath + filename
var uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediaId)

// read file
var exifDate: LocalDateTime? = null
contentResolver.openInputStream(uri)?.use { stream ->
    val exif = ExifInterface(stream)

    val exifDateFormatter = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss")
    var exifDateString = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)
    if (exifDateString == null) {
        exifDateString = exif.getAttribute(ExifInterface.TAG_DATETIME)
    }
    if (exifDateString != null) {
        exifDate = LocalDateTime.parse(exifDateString, exifDateFormatter)
    }
}

NOTE: if MediaStore.MediaColumns.DATE_TAKEN gives you epoch millis seconds, ExifInterface.TAG_DATETIME_ORIGINAL gives you the date and time of the device when it was taken.

Option 3: Extra Date from Filename

Filename usually comes in the format of IMG_20190907_143933.jpg, which gives you a clue of the date. There is no guarantee though.

val filenameRegex = """(\w|-|_|\s)(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})(-|_|\s)(?<hour>\d{2})(?<minute>\d{2})(?<second>\d{2})""".toRegex() // (-|_|\s|\.|$)
fun filenameToDate(filename: String): LocalDateTime? {
    // IMG_20190907_143933.jpg
    val m = filenameRegex.find(filename)
    if (m != null) {
        val (_, year, month, day, _, hour, minute, second) = m.destructured
        return LocalDateTime.of(year.toInt(), month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt())
    }
    return null
}

Usage

val filename = cursor.getString(displayNameIndex)
val filenameDate = filenameToDate(filename)

NOTE: The filename date usually is the same as the exif date.

Timezone?

Refer Android Get Photo Timezone.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.