Prefer modern date classes

The java.util.Date class and its related Calendar class have been in the JDK for a long time. However, these old date classes suffer from a number of defects in their design.

If possible, you should be using the newer java.time classes. They were introduced in JDK 8.

Reminders:

The java.time classes model time to a nanosecond precision. However, many system clocks have a precision of a few milliseconds, so beware of spurious precision. (How a library models time is independent of the precision of your system clock.)

It's always useful to remember that offset and timezone are two different ideas. It's an error to treat them the same. An offset is a fixed difference from Universal Time (UT). In general, a time zone is not the same as an offset. In North America and Europe, it's common that a time zone consists of two offsets, and a rule for switching between them. You may think that the difference is trivial, but keeping the difference clear is important for understanding what is going on.

Hence, in java.time, you have both ZonedDateTime and OffsetDateTime. (The class named ZoneOffset adds to the confusion! It should've been called simply Offset.)

Example

Here's an example of using java.time.

/* Note the static imports here. */
import static java.time.DayOfWeek.MONDAY;
import static java.time.temporal.TemporalAdjusters.firstDayOfMonth;
import static java.time.temporal.TemporalAdjusters.next;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

/** 
 The date-time classes added in JDK 8.
 https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html
 http://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html
*/
public final class PlayDate {
  
  public static void main(String... args) {
    PlayDate pd = new PlayDate();
    pd.makeDate();
    pd.makeDateTime();
    pd.format();
    pd.alterField();
    pd.conversions();
    pd.calc();
    //pd.listAllTimeZones();
    log("Done.");
  }
  
  void makeDate(){
    LocalDate date1 = LocalDate.parse("2017-12-31");
    LocalDate date2 = LocalDate.now();
    LocalDate date3 = LocalDate.of(2017, 12, 31);
    LocalDate date4 = LocalDate.of(2017, Month.DECEMBER, 31);
    LocalDate date5 = LocalDate.now(ZoneId.of("America/New_York"));
  }
  
  void makeDateTime(){
    //you can go to nanoseconds, but no further
    //note the annoying 'T' separator
    LocalDateTime a = LocalDateTime.parse("2017-12-31T23:59:59.123456789");
    //this fails; you need to pass the 'T'
    //LocalDateTime b = LocalDateTime.parse("2017-12-31 23:59:59.123456789");
    LocalDateTime b = LocalDateTime.now();
    LocalDateTime c = LocalDateTime.of(2017,12,31,18,59,59);
    LocalDateTime d = LocalDateTime.of(2017,12,31,18,59,59,1); //with nanos at the end
  }
  
  void listAllTimeZones(){
    Set<String> ids = ZoneId.getAvailableZoneIds();
    TreeSet<String> ordered = new TreeSet<>(ids);
    ordered.stream().forEach(id -> log(id));
  }
  
  void format(){
    LocalDate date = someDate();
    LocalDateTime dateTime = someDateTime();
    
    //2017-12-31
    demoFormat(DateTimeFormatter.ISO_LOCAL_DATE, date);
    
    //2017-12-31T23:59:59 (annoyance: the 'T' in the middle)
    demoFormat(DateTimeFormatter.ISO_LOCAL_DATE_TIME, dateTime); 
    
    //2017-12-31 23:59:59
    demoFormat(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), dateTime); 
  }
  
  /** Using this single date highlights some aspects of formatting. */
  LocalDate someDate(){
    return LocalDate.of(2017, 12, 31);
  }
  
  LocalDateTime someDateTime(){
    return LocalDateTime.of(2017, 12, 31, 23, 59, 59);
  }
  
  void demoFormat(DateTimeFormatter formatter, LocalDate date){
    log(formatter.format(date));
  }
  
  void demoFormat(DateTimeFormatter formatter, LocalDateTime dateTime){
    log(formatter.format(dateTime));
  }

  /** 
   Most of the java.time classes are immutable.
   To alter a date-time field to some new value, you call a method that 
   returns a new object having the desired state. 
  */
  void alterField(){
    LocalDate today = someDate();
    LocalDate tomorrow = today.plusDays(1);
    //using static imports lets you avoid the more verbose style:
    //  LocalDate startOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
    //  LocalDate followingMonday = today.with(next(DayOfWeekWeek.MONDAY));
    LocalDate startOfMonth = today.with(firstDayOfMonth());
    LocalDate followingMonday = today.with(next(MONDAY));
  }
  
  void conversions(){
    java.util.Date now = new java.util.Date();
    Instant instant = now.toInstant(); //toInstant() is a new conversion method
    
    //you need to use a timezone:
    LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
    
    LocalDateTime dateTime2 = zonedDateTime.toLocalDateTime(); //convert to the no-tz form
    assert(dateTime.equals(dateTime2));
  }
  
  void calc(){
    LocalDate a = LocalDate.parse("1900-01-01");
    LocalDate b = LocalDate.parse("1999-12-31");
    Long daysBetween = ChronoUnit.DAYS.between(a, b);
    log("Num days in the 20th century: " + (daysBetween + 1));
    
    LocalDate now = LocalDate.now();
    int year = now.getYear();
    if (now.getMonthValue() == 12 && now.getDayOfMonth() > 25){
      year = year + 1;
    }
    LocalDate christmas = LocalDate.of(year, 12, 25);
    log("Days till Christmas: " + ChronoUnit.DAYS.between(now, christmas));
  }
  
  private static void log(Object thing){
    //note the null-friendly style:
    System.out.println(Objects.toString(thing));
  }
}