javadatedate-formatandroid-calendar

Which is the correct way to use Java's date formats in Android Studio?


I am trying to build a chat app and I want to store date for every message in Firebase RealTime-Database using ServerValue.TIMESTAMP which stores date and time in milliseconds and it is server sided. All I want to achieve is to convert milliseconds to a safe date format which will have a pattern like "Thursday, 11th November 2022, 14:42" but I read a lot of things about Java's unsafe date and format libraries. My app uses FirebaseUI which is like an asynchronous way to observe/notify/listen to changes of my db so I need something that will be safe and supported from API/SDK level 21+

Here is my code that I currently have

Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(model.sentOnDateInMilliseconds); String date = DateFormat.getDateInstance(DateFormat.FULL).format(calendar.getTime());

and I make attempts to preview the format that I want like this

textView.setText(MessageFormat.format("{0} {1}:{2} {3}", date, calendar.get(Calendar.HOUR), calendar.get(Calendar.MINUTE), calendar.get(Calendar.AM_PM)));

any suggestions?


Solution

  • Avoid legacy classes

    Never use SimpleDateFormat, Calendar, or either Date class. These legacy classes are terribly flawed with poor design decisions by people who did not understand date-time handling.

    Their modern replacements are the java.time classes defined in JSR 310.

    java.time

    You are using terrible date-time classes that were years ago supplanted by the modern java.time classes defined in JSR 310.

    Though not documented clearly, apparently a firebase.database.ServerValue.TIMESTAMP type represents a moment as a count of milliseconds since the epoch reference of the first moment of 1970 in UTC, 1970-01-01T00:00:00Z.

    java.time.Instant

    The matching class in Java is java.time.Instant.

    Instant instant = Instant.ofEpochMilli( millisSinceEpoch ) ;
    

    You can extract that millisecond count from an Instant. Beware of data loss: A Instant has a resolution of nanoseconds, so getting a count of milliseconds ignores the microseconds/nanoseconds.

    Instant instant = Instant.now() ;  // Capture the current moment as seen with an offset of zero hours-minutes-seconds from UTC.
    long millisSinceEpoch = instant.toEpochMilli() ;
    

    java.time.ZonedDateTime

    convert milliseconds to a safe date format which will have a pattern like "Thursday, 11th November 2022, 14:42"

    To generate text in formats other than standard ISO 8601, we need a class more flexible than Instant. And we need to adjust that moment from an offset of zero hours-minutes-seconds from UTC to a time zone (or offset) expected by the user.

    ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
    ZonedDateTime zdt = instant.atZone( z ) ;
    

    java.time.format.DateTimeFormatter

    To generate text, we must specify a format. You can hard-code a format, or you can let java.time automatically localize. To localize, we specify a Locale to determine the human language and cultural norms needed for localization such as name of day of week.

    Locale locale = Locale.UK ;
    DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG ).withLocale( locale ) ;
    String output = zdt.format( f ) ;
    

    Dump to console.

    System.out.println( "instant = " + instant );
    System.out.println( "millisSinceEpoch = " + millisSinceEpoch );
    System.out.println( "output = " + output );
    

    When run.

    instant = 2022-11-18T19:27:22.561715Z
    millisSinceEpoch = 1668799642561
    output = Saturday, November 19, 2022 at 4:27:22 AM Japan Standard Time
    

    Android 26+ carries an implementation of the java.time classes. For earlier Android, the latest tooling provides access to most of the java.time functionality via “API desugaring”.