Java Data Time

Datum

Zahlreiche Methoden der Date Objekte sind seit einiger Zeit deprecated. Mit der Hilfe der Calendar Klasse kann man weiterhin auf die entsprechenden Datumsfelder zugreifen:

Date lCurrentDate=new Date();
Calendar lCal=Calendar.getInstance();
lCal.setTime(lCurrentDate);
lCal.get(Calendar.YEAR);
lCal.get(Calendar.MONTH);
lCal.get(Calendar.DAY_OF_MONTH);

Und entsprechend lässt sich das Datum auch manipulieren

Calendar lToday = new GregorianCalendar();
lToday.set(Calendar.HOUR_OF_DAY, 23);
lToday.set(Calendar.MINUTE, 59);
lToday.set(Calendar.SECOND, 0);
lToday.set(Calendar.MILLISECOND, 0);
Date lNewDate=lToday.getTime();

Möchte man von einem gegebenen Datum einige Tage abziehen oder addieren geht das so

Date yourDate=...;
Calendar calendar = new GregorianCalendar();

// remove timezone
Date cleanDate=new Date(yourDate.getTime());

calendar.setTime(cleanDate);
calendar.add(Calendar.DAY_OF_MONTH, -14);
Date lNewDate=calendar.getTime();

So kann man ein Datum in einen schönen String umwandeln (format()) oder aus einem beliebigen String ein Datum erzeugen (parse())

Date d=new Date();
...
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String s=f.format(d);
f.setLenient(false);
Date d2=f.parse(s);

For example for LocalDate you need to use the DateTimeFormatter instead of the SimpleDateFormat.

DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// to date
LocalDate date = LocalDate.parse("2021-12-31, f);
// to string
String s=f.format(date);

There are some predefined formats where you can learn / steal from

FormatOutput
DateTimeFormatter.BASIC_ISO_DATE20190916+0200
DateTimeFormatter.ISO_LOCAL_TIME13:31:47.3
DateTimeFormatter.ISO_OFFSET_DATE_TIME2019-09-16T13:31:47.3+02:00
DateTimeFormatter.ISO_OFFSET_DATE2019-09-16+02:00
DateTimeFormatter.ISO_OFFSET_TIME13:31:47.3+02:00
DateTimeFormatter.ISO_TIME13:31:47.3+02:00
DateTimeFormatter.ISO_OFFSET_TIME13:31:47.3+02:00
DateTimeFormatter.ISO_DATE2019-09-16+02:00
DateTimeFormatter.ISO_DATE_TIME2019-09-16T13:31:47.3+02:00[Europe/Berlin]
DateTimeFormatter.ISO_INSTANT2019-09-16T11:31:47.300Z
DateTimeFormatter.ISO_LOCAL_DATE2019-09-16
DateTimeFormatter.ISO_LOCAL_DATE_TIME2019-09-16T13:31:47.3
DateTimeFormatter.ISO_ORDINAL_DATE2019-259+02:00
DateTimeFormatter.ISO_WEEK_DATE2019-W38-1+02:00
DateTimeFormatter.ISO_ZONED_DATE_TIME2019-09-16T13:31:47.3+02:00[Europe/Berlin]

Mit dem Datum kann man in Java viel Spaß haben. Angenommen man hat zwei Datumsobjekte

long time1=1376639746195L;

java.util.Date date1;
java.util.Date date2;

Eines davon ist wirklich vom Typ Date, das andere ein Timestamp (ein Erbe von Date)

date1=new java.util.Date(time1);
date2=new java.sql.Timestamp(time1);

Das ist keine künstliche Situation, das passiert z.B. schnell wenn man ein Datum per Hibernate in einer Datenbank persistiert und wieder ausliest. Beide zeigen auf exakt dieselbe Uhrzeit und sind sogar equal

date1.getTime(); // 1376639746195
date2.getTime(); // 1376639746195
date1.equals(date2); // true

Was passiert wohl, wenn man beide vergleicht?

date1.compareTo(date2); // 1
Die ersten der beiden gleichen Daten ist also größer!
 
(equals) ... is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

Testen wir doch mal

date1.equals(date2); // true
date2.equals(date1); // false

Die Ursache ist offenbar, dass die Genauigkeit von Timestamp eigentlich nicht genau genug für Millisekunden ist. Also entfernen wir im Date doch auch mal die Millisekunden (die letzten 3 Stellen)

long time1=1376639746195L;
long time2=1376639746000L;
date1=new java.util.Date(time2);

Und schon findet compareTo, dass beide gleich sind.

date1.getTime(); // 1376639746000
date2.getTime(); // 1376639746195
date1.compareTo(date2); // 0

Aber sind sie eigentlich trotz der abweichenden getTime() Werte noch equals?

date1.equals(date2); // false
date2.equals(date1); // false

Es hilft hier nur, in beiden den Millisekundenanteil zu streichen. Kleiner Auszug aus dem JavaDoc von compareTo()

It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)).

String to Instant, String to LocalDate, LocalDate to LocalDateTime, LocalDateTime to Instant

final Instant       i = Instant.parse("2012-02-03T00:00:00.00Z");
final LocalDate     d = LocalDate.of(2012, 2, 2);
final LocalDateTime t = LocalDateTime.of(a, LocalTime.MIDNIGHT);
final Instant      ti = t.toInstant(ZoneOffset.UTC);

Zeitzonen

// a date without time zone. E.g. when this is visible on your calendar on the wall it might be Christmas for
// you. For people in different timezones that day starts and end sooner or later than for you
final LocalDate christmas = LocalDate.of(2016, 12, 24);

// a time without a timezone. E.g. when this is on your watch the new year might start for you. When your watch
// shows this time highly depends on the timezone you are in
final LocalDateTime startOfYear = LocalDateTime.of(2016, 12, 24, 0, 0);

// this is a point in time with a timezone. This is for everybody the same moment.
final ZonedDateTime callMeAt = ZonedDateTime.of(2016, 02, 07, 16, 15, 0, 0, ZoneId.of("Europe/Berlin"));

// for convenience you can get it in your timezone as well (still the same moment in time)
final ZonedDateTime callMeAt2 = callMeAt.withZoneSameInstant(ZoneId.of("Brazil/East"));

// or you can keep the time and just change the time zone (this will be a differnt moment in time)
final ZonedDateTime callMeAt3 = callMeAt.withZoneSameLocal(ZoneId.of("Brazil/East"));

Measure time inside code

http://metrics.dropwizard.io/

import java.util.concurrent.TimeUnit;

import org.slf4j.LoggerFactory;

import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Slf4jReporter;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import com.codahale.metrics.Timer.Context;

public class TimerTest {

        private final Meter requestFrequency;
        final Timer timerTotal;
        final Timer timerFirst;
        final Timer timerSecond;

        public TimerTest() {
                final MetricRegistry metricsRegistry = new MetricRegistry();

                requestFrequency = metricsRegistry.meter("request-frequency");
                timerTotal = metricsRegistry.timer("total");
                timerFirst = metricsRegistry.timer("first");
                timerSecond = metricsRegistry.timer("second");

        }

        public void handleRequest() {
                // counts and tells you later how many we had per second
                requestFrequency.mark();
                hardWork();
        }

        public void doLongTimeStuff() {
                // use Closable to stop time under every condition
                try (Context total = timerTotal.time()) {

                        // start and stop manually not via Closable
                        final Context first = timerFirst.time();
                        hardWork();
                        first.stop();

                        final Context second = timerSecond.time();
                        hardWork();
                        second.stop();
                }
        }
...

Use this to log the current timers periodically

                // logs every second everything to the console
                final ConsoleReporter reporter = ConsoleReporter.forRegistry(metricsRegistry).convertRatesTo(TimeUnit.SECONDS)
                                .convertDurationsTo(TimeUnit.MILLISECONDS).build();
                reporter.start(1, TimeUnit.SECONDS);

                // logs every second everythging to the log file
                final Slf4jReporter slf4jLogger = Slf4jReporter.forRegistry(metricsRegistry)
                                .outputTo(LoggerFactory.getLogger(TimerTest.class)).convertRatesTo(TimeUnit.SECONDS)
                                .convertDurationsTo(TimeUnit.MILLISECONDS).build();
                slf4jLogger.start(1, TimeUnit.MINUTES);