How to save and retrieve Date in SharedPreferences

To save and load accurate date, you could use the long (number) representation of a Date object.

Example:

//getting the current time in milliseconds, and creating a Date object from it:
Date date = new Date(System.currentTimeMillis()); //or simply new Date();

//converting it back to a milliseconds representation:
long millis = date.getTime();

You can use this to save or retrieve Date/Time data from SharedPreferences like this

Save:

SharedPreferences prefs = ...;
prefs.edit().putLong("time", date.getTime()).apply();

Read it back:

Date myDate = new Date(prefs.getLong("time", 0));

Edit

If you want to store the TimeZone additionaly, you could write some helper method for that purpose, something like this (I have not tested them, feel free to correct it, if something is wrong):

public static Date getDate(final SharedPreferences prefs, final String key, final Date defValue) {
    if (!prefs.contains(key + "_value") || !prefs.contains(key + "_zone")) {
        return defValue;
    }
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(prefs.getLong(key + "_value", 0));
    calendar.setTimeZone(TimeZone.getTimeZone(prefs.getString(key + "_zone", TimeZone.getDefault().getID())));
    return calendar.getTime();
}

public static void putDate(final SharedPreferences prefs, final String key, final Date date, final TimeZone zone) {
    prefs.edit().putLong(key + "_value", date.getTime()).apply();
    prefs.edit().putString(key + "_zone", zone.getID()).apply();
}

tl;dr

The modern approach uses java.time classes and ISO 8601 strings.

Reading.

Instant                             // Represent a moment in UTC with a resolution of nanoseconds.
.ofEpochMilli( 
    Long.getLong( incomingText )  
)                                   // Returns a `Instant` object.
.atZone(                            // Adjust from UTC to some time zone. Same moment, same point on the timeline, different wall-clock time.
    ZoneId.of( "Europe/Paris" ) 
)                                   // Returns a `ZonedDateTime` object.

Writing.

ZonedDateTime
.of(
    LocalDate.of( 2018 , Month.JANUARY , 23 ) ,
    LocalTime.of( 15 , 35 ) ,
    ZoneId.of( "Europe/Paris" ) 
)                                   // Returns a `ZonedDateTime` object.
.toInstant()                        // Returns an `Instant`. Adjust from a time zone to UTC. Same moment, same point on the timeline, different wall-clock time.
.toEpochMilli()                     // Returns a `long` integer number primitive. Any microseconds or nanoseconds are ignored, of course.

If your alarm manager has not yet been modernized to handle java.time objects, convert between legacy & modern classes using new methods added to the old classes.

java.util.Date d = java.util.Date.from( instant ) ;

…and…

Instant instant = d.toInstant() ;

java.time

The troublesome old date-time classes were supplanted by the java.time classes.

For a moment in UTC, with a resolution of nanoseconds, use Instant.

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

You want only milliseconds for your needs, so truncate any microseconds & nanoseconds.

Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLIS ) ;

To determine a moment by date and time-of-day requires a time zone. A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.

If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

Or specify a date. You may set the month by a number, with sane numbering 1-12 for January-December.

LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ;  // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.

Or, better, use the Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety.

LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;

Combine with a time-of-day, a LocalTime.

LocalTime lt = LocalTime.of( 14 , 0 ) ;

Wrap it all together as a ZonedDateTime object.

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

Adjust to UTC by extracting a Instant.

Instant instant = zdt.toInstant() ;

Extract your desired count-of-milliseconds since the epoch reference of first moment of 1970 in UTC. Again, be aware that any micros/nanos in your Instant will be ignored when extracting milliseconds.

long milliseconds = instant.toEpochMilli() ;  // Be aware of potential data loss, ignoring any microseconds or nanoseconds. 

Read those milliseconds back from storage as text using the Long class.

long milliseconds = Long.getLong( incomingText ) ;
Instant instant = Instant.ofEpochMilli( milliseconds ) ;

To see that moment through the lens of the wall-clock time used by the people of a particular region (a time zone), apply a ZoneId to get a ZonedDateTime.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

To generate text representing that value, use DateTimeFormatter.ofLocalizedDateTime to automatically localize.

Tip: Consider writing your date-time values to storage in standard ISO 8601 format rather than as a count-of-milliseconds. The milliseconds cannot be read meaningfully by humans, making debugging & monitoring tricky.

String output = instant.toString() ; 

2018-10-05T20:28:48.584Z

Instant instant = Instant.parse( 2018-10-05T20:28:48.584Z ) ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.