-30

How to format java.util.Date with DateTimeFormatter portable?

I can't use

Date in = readMyDateFrom3rdPartySource();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
ldt.format(dateTimeFormatter);

because I afraid that usage of ZoneId.systemDefault() can introduce some changes.

I need to format exactly that object I have.

UPDATE

Note: time is time. Not space. Timezone is very rough measure of longitude, i.e. space. I don't need it. Only time (and date).

UPDATE 2

I wrote the following program, proving, that Date DOES NOT only contain correct "instant":

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DataNature2 {

   public static void main(String[] args) throws ParseException {

      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

      String dateTimeString = "1970-01-01 00:00:01";

      Date date = simpleDateFormat.parse(dateTimeString);

      System.out.println("1 second = " + date.getTime());

   }
}

The output is follows:

1 second = -10799000

While it should be

1 second = 1000

if Date was "Instant".

The number 10799000 is 3*60*60*1000-1000 - the timezone offset of my local time.

This means, that Date class is dual. It's millisecond part may be shifted relatively to hh mm ss part by timezone offset.

This means, that if any utility returns Date object in terms of it's parts (hh mm ss) then it implicitly converted to local time. And getTime() means DIFFERENT time simultaneously. I mean on different machines if this program run at the same time, getTime() will be the same, while time parts will be different.

So, the code example in the beginning is correct: it takes "instant" part of Date, and supplies system timezone part, which was implicitly used inside Date. I.e. it converts dual Date object into explicit LocalDateTime object with the same parts. And hence, formatting after that, is correct.

UPDATE 3

Event funnier:

Date date = new Date(70, 0, 1, 0, 0, 1);
assertEquals(1000, date.getTime());

this test fails.

UDPATE 4

New code. Dedicated to all believers.

public class DataNature3 {

   public static class TZ extends java.util.TimeZone {


      private int offsetMillis;

      public TZ(int offsetHours) {
         this.offsetMillis = offsetHours * 60 * 60 * 1000;
      }

      @Override
      public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
         throw new UnsupportedOperationException();
      }

      @Override
      public void setRawOffset(int offsetMillis) {
         this.offsetMillis = offsetMillis;
      }

      @Override
      public int getRawOffset() {
         return offsetMillis;
      }

      @Override
      public boolean useDaylightTime() {
         return false;
      }

      @Override
      public boolean inDaylightTime(Date date) {
         return false;
      }
   }

   public static void main(String[] args) {

      Date date = new Date(0);

      for(int i=0; i<10; ++i) {

         TimeZone.setDefault(new TZ(i));

         if( i<5 ) {
            System.out.println("I am date, I am an instant, I am immutable, my hours property is " + date.getHours() + ", Amen!");
         }
         else {
            System.out.println("WTF!? My hours property is now " + date.getHours() + " and changing! But I AM AN INSTANT! I AM IMMUTABLE!");
         }

      }

      System.out.println("Oh, please, don't do that, this is deprecated!");

   }
}

Output:

I am date, I am an instant, I am immutable, my hours property is 0, Amen!
I am date, I am an instant, I am immutable, my hours property is 1, Amen!
I am date, I am an instant, I am immutable, my hours property is 2, Amen!
I am date, I am an instant, I am immutable, my hours property is 3, Amen!
I am date, I am an instant, I am immutable, my hours property is 4, Amen!
WTF!? My hours property is now 5 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 6 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 7 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 8 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 9 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
Oh, please, don't do that, this is deprecated!
Dims
  • 47,675
  • 117
  • 331
  • 600
  • Then which time zone do you want to format in? – SLaks Apr 20 '17 at 18:47
  • Neither. Timezone is unknown. – Dims Apr 20 '17 at 18:54
  • 25
    Then `Date` isn't what you want, as that represents an *instant* in time, which only has a local year/month/day/hour/minute/second when you apply a time zone. The same instant has different values in different time zones, and they don't make sense without a time zone. It's like asking for the binary encoding of a string without specifying an encoding... – Jon Skeet Apr 20 '17 at 18:59
  • @JonSkeet no, `Date` represents measure of time. It can mean one instant in one timezone and another instant in another. – Dims Apr 20 '17 at 19:02
  • @JonSkeet no, it's like asking a distance without specifying an origin. We have `double` type which holds this. Nobody requires to provide units of measurement and origin for doubles. The same should be for date-time. – Dims Apr 20 '17 at 19:04
  • 19
    @Dims: No, it really doesn't `Date` is an instant in time - a number of milliseconds since the Unix epoch. It's not since some arbitrary point in time - it has a well-specified origin. The longer you fight against this, the longer you will have problems. *Please* accept it. If you don't believe me, you should read the `java.util.Date` documentation, which starts "The class Date represents a specific instant in time, with millisecond precision." – Jon Skeet Apr 20 '17 at 19:12
  • Your code is correct. You are also correct that it depends on your JVM's time zone and for the same `Date` object will produce differnt results in different time zones. Exactly therefore you need to specify for which time zone you want the result. – Ole V.V. Apr 20 '17 at 19:59
  • 4
    If all you have is a point in absolute time (relative to UNIX timestamp), there is no way to format it without a time zone, because that will display as a different time in different time zones. Where, and in what format, does your value come from? – SLaks Apr 20 '17 at 20:09
  • @SLaks suppose it is in Shire – Dims Apr 20 '17 at 20:23
  • @JonSkeet what are you saying does imply UTC. – Dims Apr 20 '17 at 20:25
  • @Dims: If you have a timezone, you can specify that timezone and you won't have a problem. – SLaks Apr 20 '17 at 21:08
  • @SLaks I have no timezone. I have `HH:MM:SS` from third party source. It's timezone if unknown and may mean different instant than one I get if parse this string. – Dims Apr 20 '17 at 21:10
  • @JonSkeet see my very simple example code, showing, that `Date` is not instant. – Dims Apr 20 '17 at 21:26
  • @Slaks it not only format matter, you can also call `getHours()` getter and see the same. `Date()` is JUST NOT AN INSTANCE if you use it's components in any way. And you use it in numerous cases. For example, if you read `Date` from database server by clients all over the world, you will get the same components, but different `getTime()`. – Dims Apr 20 '17 at 21:33
  • 3
    IOW, you _don't_ have an absolute time. You want https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html – SLaks Apr 20 '17 at 21:41
  • 23
    Your tests don't show what you seem to think it shows. You're parsing it in the system default time zone, and constructing 1970-01-01T00:00:01 *in the system default time zone*. However, I'm done here. You're ignoring both what the documentation says and what someone with considerably experience in date/time work says. You're welcome to do that of course, but you're not going to make any progress. You should follow SLaks advice and use `LocalTime`, as that's what the value you're receiving represents. It doesn't represent an unambiguous instant in time. – Jon Skeet Apr 21 '17 at 06:22
  • @JonSkeet and you are ignoring how `Date` is used in numerous libraries and tools. I am done here too. – Dims Apr 21 '17 at 08:57
  • @SLaks I don't want absolute time, I want relative time. – Dims Apr 21 '17 at 08:59
  • 2
    Then you want `LocalTime` or `LocalDateTime`, as that's precisely what they model. – Jon Skeet Apr 21 '17 at 09:11
  • 2
    A `Date` only contains an instant. Whether it contains a *correct* instance, well, if it contains an incorrect one it certainly wouldn’t be the first time. In you so-called(!) proof, it contains an instant of 2 hours 59 minutes and 59 seconds before the epoch — which in turn tells me you JVM’s time zone is at an offset of 3 hours from UTC. – Ole V.V. Apr 21 '17 at 09:50
  • 7
    Dims, I believe the real answer to your question is: stay away from `java.util.Date`. It confuses you, and there’s no reason why you should want that. Use the newer classes in `java.time` instead, like those @JonSkeet mentions. – Ole V.V. Apr 21 '17 at 09:58
  • 3
    _I don't want absolute time, I want relative time._ I think what you are looking for is `Duration` or `Period` from `java.time`. Even so numerous libraries and tools use `Date` doesn't mean it's correct. – Lukas Wöhrl Apr 21 '17 at 11:11
  • @LukasWöhrl, that depends on relative to what. If relative to a time zone, Jon Skeet’s suggestion is better. If relative between two points in time, you are entirely correct. – Ole V.V. Apr 21 '17 at 14:02
  • @LukasWöhrl I agree it is not correct, this is my point. And it is not "an instant", but it is dual. Second nature of `Date` appears not in formatting only, but in many cases. – Dims Apr 21 '17 at 14:12
  • @OleV.V. absolutely, just wanted to throw those into the field, as they haven't mentioned yet. @ Dims This simply shows that time is a **very** complex field. Jon Skeet is an expert there, trust him. – Lukas Wöhrl Apr 21 '17 at 14:54
  • 1
    I agree in your view that the `Date` class was designed with a “second nature”. I just meant to say, the state a `Date` object holds is an instant only. The second nature is in constructors, the methods, particularly the many deprecated methods, but also in `toString()`. This design is generally considered poor in retrospect and is one of the reasons new classes were designed twice, first `Calendar` and later all the `java.time` classes. – Ole V.V. Apr 21 '17 at 15:13
  • @Dims Assume you have a `Date` object created by expression `new Date(60*1000)`. What is the expected String you want to generate from this? – infiniteRefactor Apr 21 '17 at 20:12
  • @OleV.V. according to OOP paradigm, the internals of object is black box; the "true nature" of object -- is what you can get from it or set to it by it's methods. You can't say, that some of methods are "true" and another methods are "false". The matter of fact is that `Date` class has TWO contracts, regardless the number of downvotes and regardless documentation. There are a lot of libraries, that READ datetime and present it in `Date` format for java. In this moment, the contract of "number of milliseconds from UNIX era" is violated. – Dims Apr 21 '17 at 21:23
  • @infiniteRefactor in my situation I am receiving date time from another source and it is constructed from parts. And I need to print exactly these parts. Your case is irrelevant. – Dims Apr 21 '17 at 21:26
  • @Dims If the `Date` is constructed from, say, year, month, day in month, hrs and min and you cannot guess the time zone used when constructing it, there is no way you can fulfil the requirement of printing those parts from the `Date`. – Ole V.V. Apr 21 '17 at 23:13
  • @Dims OK. Assume you have a `Date` object created by expression `new Date(70, 0, 1, 0, 1, 0)`. What is the expected String you want to generate from this? – infiniteRefactor Apr 21 '17 at 23:59
  • @infiniteRefactor something like `1970-01-01 00:01:00` Order should depend on format, but components should be kept. – Dims Apr 22 '17 at 09:43
  • @OleV.V. Please, understand, that timezone should not be part of date, because date should measure time, while timezone is a measure for space. We are not storing kilograms, meters, and so on units in double. And whe should not keep timezone in Date. – Dims Apr 22 '17 at 09:46
  • 2
    Your "Update 4" only proves that `getHours()` reflects the local time zone, which you are changing. If one repeatedly evaluates `a+b`, and gets different sums when changing `b`, that doesn't mean that `a` is also changing. – Matt Johnson-Pint Apr 22 '17 at 20:28
  • @MattJohnson but this means that `a+b` contains also `b`, but not only `a`. – Dims Apr 22 '17 at 20:32
  • "Contains" is the wrong word, but sure, `a+b` *is dependent on* both `a` and `b`, in the same way that the hour returned from `getHours` is dependent on both the instant held by the `Date` object and the local time zone. That does not mean that the instant changes when you change the time zone, no more than it means that `a` changes when you change `b`. – Matt Johnson-Pint Apr 22 '17 at 20:41
  • 2
    Also, you say in the sample: "hours property". However, `date.getHours()` is a method, not a property. Java doesn't have properties in the same way that C# has. [It only has methods.](http://stackoverflow.com/a/2963249/634824). A method that takes as input some outside information cannot be described as [pure](http://stackoverflow.com/a/22395430/634824). Therefore, what you are observing is simply that the `getHours` method is impure because it takes the system time zone as one of its inputs. – Matt Johnson-Pint Apr 22 '17 at 20:50
  • @MattJohnson expression `a+b` namely CONTAINS `b`. You MAY say "depends on", but you can't say "contains" is wrong. I never said instant changes, I only said `Date` contains not only instant and this is true. Date is the same as `a+b` in your excellent example, where `a` is an instant and `b` is timezone. In terms of Java bean convention, `getHours` is a property getter for property "hours". I may rewrite access to this property with any bean library. The fact that getters in Java do not have additional brace level changes nothing. Of course you may say `getHours` is "impure". – Dims Apr 23 '17 at 06:45
  • I believe that it is finally clear what the problem is here. If class `a` has a method `getData` and this `getData` method is returning `a` internal state (which is an instant) modified by some external value (let's skip the timezone, let's say it's user's screen resolution) does this make `a` an instant or not. And does this imply that `a` stores screen resolution or not. I believe that this is simply semantics thing - how you define what is an instant? It seems that most people define instant differently from the question's author... – Pawel Gradecki Apr 23 '17 at 07:37
  • @PawelGradecki I understand instant as a point on time dimension. I don't deny `Date` contains instant. I deny it IS an instant. We may say it is IMPURE instant if you wish. The very fact that `Date` has so much depreated methods (and that there are so many alternative implementations) alone proves that `java.util.Date` is both impure and bad design. – Dims Apr 23 '17 at 08:48
  • Ok I understand what you mean, but can you refer to the example with screen resolution? If the class with a private instant field would have a `getData` method which would return value based on local screen resolution (or any other environmental data), would that mean that it is not instant? Just want to make sure that I get your instant definition right. I believe that a game changer here would be to provide a definition of an "instant" from some trustful source, because the discussion is about instants and nobody posted any definition yet. – Pawel Gradecki Apr 23 '17 at 09:29
  • @PawelGradecki it depends on whether it is possible to reach instant pure. If the class would have only `getData()` which returns only instant, dirtied with screen resolution, I would say either it is not instant, or some special sort of instant, defined by class designer. I am opened to any definition, but it should not drift. – Dims Apr 23 '17 at 12:03

3 Answers3

38

TL;DR: You're right to be concerned about the use of the system local time zone, but you should have been concerned earlier in the process, when you used the system local time zone to construct a Date in the first place.

If you just want the formatted string to have the same components that Date.getDate(), Date.getMonth(), Date.getYear() etc return then your original code is appropriate:

LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

You say you're "afraid that usage of ZoneId.systemDefault() can introduce some changes" - but that's precisely what Date.getDate() etc use.

Date doesn't have any kind of "dual contract" that lets you view it as a time-zone-less representation. It is just an instant in time. Almost every single method that lets you construct or deconstruct it into components is clearly documented to use the system default time zone, just like your use of ZoneId.systemDefault(). (One notable exception is the UTC method.)

Implicitly using the system default time zone is not the same as Date being a valid time-zone-less representation, and it's easy to demonstrate why: it can lose data, very easily. Consider the time-zone-free date and time of "March 26th 2017, 1:30am". You may well want to be able to take a text representation of that, parse it, and then later reformat it. If you do that in the Europe/London time zone, you'll have problems, as demonstrated below:

import java.util.*;
import java.time.*;
import java.time.format.*;

public class Test {

    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
        Date date = new Date(2017 - 1900, 3 - 1, 26, 1, 30);

        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime ldt = LocalDateTime.ofInstant(instant, zone);
        System.out.println(ldt); // Use ISO-8601 by default
    }
}

The output is 2017-03-26T02:30. It's not that there's an off-by-one error in the code - if you change it to display 9:30am, that will work just fine.

The problem is that 2017-03-26T01:30 didn't exist in the Europe/London time zone due to DST - at 1am, the clock skipped forward to 2am.

So if you're happy with that sort of brokenness, then sure, use Date and the system local time zone. Otherwise, don't try to use Date for this purpose.

If you absolutely have to use Date in this broken way, using methods that have been deprecated for about 20 years because they're misleading, but you're able to change the system time zone, then change it to something that doesn't have - and never has had - DST. UTC is the obvious choice here. At that point, you can convert between a local date/time and Date without losing data. It's still a bad use of Date, which is just an instant in time like Instant, but at least you won't lose data.

Or you could make sure that whenever you construct a Date from a local date/time, you use UTC to do the conversion, of course, instead of the system local time zone... whether that's via the Date.UTC method, or by parsing text using a SimpleDateFormat that's in UTC, or whatever it is. Unfortunately you haven't told us anything about where your Date value is coming from to start with...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I repeat, it's not me, who use `Date` this way. Look at `JDBC` library. They read date from database and keep components. Look at `Apache POI` library. They read date from Excel and keep components. I can mention more, these are just from top of the head. The methods in `Date` are deprecated, but actually they ARE INTENSIVELY USED everywhere. So, `Date` IS NOT AN INSTANT, it's TWO INSTANCES. I am very sorry for that. – Dims Apr 22 '17 at 09:39
  • 12
    @Dims: However much you shout the opposite, a `Date` represents a single instant in time. Yes, it's misused in multiple places, but the *only* piece of information in it is the number of milliseconds since the Unix epoch. It's one instant in time, and it provides deprecated methods to interpret that in the local time zone. That doesn't make it two instants - it makes it a single instant with "convenience" methods (that should be avoided). The abuse of it doesn't change what it represents. Strings are abused to store binary data (badly) - that doesn't give *them* a "dual nature" either. – Jon Skeet Apr 22 '17 at 10:33
  • 8
    @Dims: What's more worrying to me is that you refuse to accept advice from many, many people *and* the documentation itself. If that attitude is pervasive in your coding, you're making your life much harder than it needs to be. I generally don't like appeals to authority, but I *am* a partial expert on date/time things - I'm the author of the Noda Time library for .NET, for example. I've seen no sign of you attempting to understand the arguments why your "proofs" are invalid (they actually prove the opposite of what you claim). Instead, you just shout. – Jon Skeet Apr 22 '17 at 10:38
  • please remember about OOP. You should not think about what piece of information is "inside" an object (they are PRIVATE to object). Only public METHODS matters. `Date` object takes current system timezone so that is affects MANY OF IT'S METHODS. Hence, according to OOP it also contains current timezone inside. Or, which is the same: Date contains two instants. – Dims Apr 22 '17 at 11:49
  • also remember, that truth is not defined by the voting. I respect you and everybody, thank you for your advice, but I WON'T accept wrong statements just because of this. I accept only logic and experiment. Yes, it makes my life harder, but this is my life. – Dims Apr 22 '17 at 11:52
  • in my example with `1970-01-01 00:00:01` I showed these two instances: they are `1000` (which is one second past `1970-01-01 00:00:00`) and `-10799000`, which is the content of `getTime()`. – Dims Apr 22 '17 at 11:55
  • 9
    @Dims: No, it doesn't contain the current time zone... That is only used while transforming *into* and instant or *from* the instant. It's no more part of the state of the object than the encoding used to convert a string into binary data or vice versa is part of a String object. And no, your example doesn't show 1000 at all - that would be one second past the Unix epoch, and that's not what your Date instance represents... It represents the instant that was 1970-01-01 00:00:01 in your local time zone. It's still just one instant in time, which is 1969-12-31T21:00:01Z. – Jon Skeet Apr 22 '17 at 12:32
  • My true arguments about OOP stay unheard. Ok, then you forgot to add Amen :) – Dims Apr 22 '17 at 12:42
  • 7
    @Dims: They weren't unheard, but they don't prove what you want them to. While the internal representation is unimportant, the state that is being represented *is* important... And the state represented by java.util.Date is single instant. That is consistent with all the methods on it. I will update my answer to explain how all your "proofs" that a Date represents two instants are actually proofs that it represents a single instant. That will be several hours from now though. – Jon Skeet Apr 22 '17 at 12:55
  • see my update please. It proves that `Date`'s state contains also some millisecond offset. Actually, I can change all of the values of `Date`-s in the program by this "backdoor" :) – Dims Apr 22 '17 at 12:59
  • 9
    @Dims: Nope, you've just shown that you didn't read the docs for getHours, which explicitly says it uses the local time zone - in other words, the return value depends on both the state of the Date *and* the state of the system local time zone setting. You're not mutating the Date - although Date *is* mutable, via the setTime method... – Jon Skeet Apr 22 '17 at 13:08
  • what you are saying may be formulated as "Date contains current timezone in it's static state". I.e. it is NOT just an instant. – Dims Apr 22 '17 at 13:10
  • P.S. Of course the docs about `getHours()` is directly contradicting previous claim, that `Date` "is just an instant". It is not. – Dims Apr 22 '17 at 13:13
  • 7
    @Dims: That's not even slightly what I said. (How can "in its static state" be part of what an object contains? That makes no sense.) However, given the disrespectful nature of your latest update, I'm not going to waste my valuable time trying to explain the error in your reasoning. You have shown no inclination to actually see whether you might be wrong. This time I'm really, really done. I hope you learn to have more genuine respect for those trying to help you in the future. – Jon Skeet Apr 22 '17 at 13:13
10

tl;dr

How to format java.util.Date with DateTimeFormatter portable?

Instant instant = myJavaUtilDate.toInstant() ;  // When encountering a `Date`, immediately convert from troublesome legacy class to modern *java.time* class. Then forget all about that `Date` object!
ZoneId z = ZoneId.systemDefault() ;             // Or ZoneId.of( "America/Montreal" ) or ZoneId.of( "Africa/Tunis" ) etc.
ZonedDateTime zdt = instant.atZone( z ) ;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH ) ;
String output = zdt.format( f ) ;

Or, a one-liner… (not that I recommend such a complicated one-liner)

myJavaUtilDate.toInstant().atZone( ZoneId.systemDefault() ).format( DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH )  ) 

Details

The Answer by Jon Skeet is correct. Here is my own take, with some specific points.

Avoid legacy date-time classes.

Do not use java.util.Date, java.util.Calendar, SimpleDateFormat, java.sql.Date/Time/Timestamp and other related classes dating back to the earliest versions of Java. While a well-intentioned early attempt at sophisticated handling of date-time values, they fell short of the mark. Now supplanted by the java.time classes.

If you must inter-operate with the legacy classes in old code not yet updated for java.time, convert. Call new methods on the old classes.

Instant instant = myJavaUtilDate.toInstant() ; 

You did this in your Question, but then went on to ponder more about Date. Forget about java.util.Date. Pretend it never existed. Both Date and Instant represent the same thing: A moment in UTC, a point on the timeline. The only difference is concept is that the modern Instant has a finer resolution of nanoseconds rather than milliseconds in Date.

LocalDateTime != moment

You then converted from an Instant to a LocalDateTime. You moved from a specific point on the timeline, to a vague range of possible moments. This makes no sense in nearly any practical scenario.

A LocalDateTime lacks any concept of time zone or offset-from-UTC. Having no such concept is its very purpose. Ditto for LocalDate & LocalTime: no concept of zone/offset. Think of the “Local” part as meaning “any locality” or “no locality”, not any one particular locality.

Lacking zone/offset means a LocalDateTime does not represent a moment. It is not a point on the timeline. It is a vague idea about potential moments, along a range of about 26-27 hours. Until you place a LocalDateTime in a context of a particular zone or offset, it has no real meaning.

Use LocalDateTime for use such as “Christmas this year starts at first moment of December 25th, 2018”. Such a statement implies anywhere, or nowhere specifically.

LocalDate ld = LocalDate.of(2018, Month.DECEMBER , 25);
LocalTime lt = LocalTime.MIN ;  // 00:00
LocalDateTime xmasStartsAnywhere = LocalDateTime.of( ld , lt ) ;

xmasStartsAnywhere.toString(): 2018-12-25T00:00

ZonedDateTime = moment

Now add in the context of a time zone. The first kids getting their delivery from Santa will be asleep in their beds on Kiritimati (“Christmas Island”) in the first hour of the 25th as seen on the wall-clocks of their homes.

ZoneId z = ZoneId.of("Pacific/Kiritimati");
LocalDate ld = LocalDate.of(2018, Month.DECEMBER , 25);
ZonedDateTime zdtKiritimati = ZonedDateTime.of( ld , LocalTime.MIN , z );

zdtKiritimati.toString(): 2018-12-25T00:00+14:00[Pacific/Kiritimati]

By the way, we could have assigned that time zone (ZoneId) directly to to our LocalDateTime to get a ZonedDateTime rather than start from scratch.

ZonedDateTime zdtKiritimati = xmasStartsAnywhere.atZone( z ) ;  // Move from the vague idea of the beginning of Christmas to the specific moment Christmas starts for actual people in an actual location.

map of Kiribati, a nation of islands roughly in middle of Pacific ocean

Meanwhile, at the very same moment Santa is laying out presents in Kiribati, the kids on the farms in Québec are just rising at 5 AM the day before (Christmas Eve) to milk the cows and tap the maple sap.

ZonedDateTime zdtMontreal = zdtKiribati.withZoneSameInstant( ZoneId.of( "America/Montreal") );

zdtMontreal.toString(): 2018-12-24T05:00-05:00[America/Montreal]

So, after finishing in Kiribati, the elves route Santa westward, moving through a succession of new midnight hours, starting in the far east Asia & New Zealand, then India, then the Middle East, then Africa & Europe, and eventually the Americas. The offsets currently range from 14 hours ahead of UTC to 12 hours behind. So Santa has just over 26 hours to get the job done.

Epoch

Regarding your experiments with the epoch reference of first moment of 1970 in UTC, you were inadvertently injecting your own JVM’s current default time zone. Your input string 1970-01-01 00:00:01 is faulty in that it lacks any indicator of a time zone or offset-from-UTC. In other words, that input string is the equivalent of a LocalDateTime object. When parsing that string as a Date (having UTC), the Date class silently implicitly applied your JVM’s current default time zone while interpreting that input string, in a desperate attempt to create meaning, to determine a specific moment. Once again you are inappropriately mixing a date-time lacking any concept of zone/offset with a date-time having a zone/offset.

Per the documentation for Date.parse:

If a time zone or time-zone offset has been recognized, then the year, month, day of month, hour, minute, and second are interpreted in UTC and then the time-zone offset is applied. Otherwise, the year, month, day of month, hour, minute, and second are interpreted in the local time zone.

That “local” in the last sentence was a poor choice of words. Should have been written “interpreted by applying your JVM’s current default time zone”.

The key here is that you failed to specify a zone/offset, and the Date class filled in the missing information. A well-intentioned feature, but confusing and counter-productive.

Moral of the story: If you intend a specific moment (a point on the timeline), always specify your desired/intended time zone explicitly.

If you mean UTC, say UTC. In this next line, we include a Z on the end, short for Zulu and means UTC. This part about specifying UTC is where you went wrong by omission.

Instant instant = Instant.parse( "1970-01-01T00:00:01Z" ) ;  // One second after the first moment of 1970 **in UTC**. 

instant.toString(): 1970-01-01T00:00:01Z

By the way, another way of writing that code is to use a constant defined for the epoch reference 1970-01-01T00:00:00Z, and the Duration class for representing a span of time unattached to the timeline.

Instant instant = Instant.EPOCH.plus( Duration.ofSeconds( 1 ) ) ;

instant.toString(): 1970-01-01T00:00:01Z

Your next experiment has the same story. You failed to specify a zone/offset, so Date applied one while interpreting your zone-less input. A bad idea in my opinion, but that is the documented behavior.

Date date = new Date(70, 0, 1, 0, 0, 1);
assertEquals(1000, date.getTime());  // fails

You can see from the Date object’s generated string that it represents a date-time of one second after 1970 starts in another time zone rather than in UTC. Here is output from my JVM with default time zone of America/Los_Angeles.

date.toString(): Thu Jan 01 00:00:01 PST 1970

Let's convert to Instant for clarity. Notice how the hour-of-day is 8 AM in UTC. On that first day of 1970, people in zone America/Los_Angeles used a wall-clock time eight hours behind UTC. So one second after midnight, 00:00:01, on much of the west coast of North America is simultaneously 8 AM in UTC. Nothing “funny” going on here at all.

Instant instant = date.toInstant() ; // 00:00:01 in `America/Los_Angeles` = 8 AM UTC (specifically, 08:00:01 UTC). 

instant.toString(): 1970-01-01T08:00:01Z

Two important pieces are in play here:

  • You must learn and understand that a moment, a point on the timeline, has different wall-clock time used by different different people in different places around the globe. In other words, the wall-clock time for any given moment varies around the globe by time zone.
  • The poor design choices of the legacy date-time classes such as java.util.Date unfortunately complicate the situation. The ill-advised behavior brings confusion rather than clarity to the already confusing topic of date-time handling. Avoid the legacy classes. Use only java.time classes instead. Stop banging your head against a brick wall, and then your headache will go away.

Tips:

  • Learn to think, work, debug, log, and exchange data in UTC. Think of UTC as The One True Time™. Avoid translating back-and-forth between your own parochial time zone and UTC. Instead forget about your own zone and focus on UTC while at work programming/administrating. Keep a UTC clock on your desktop.
  • Apply a time zone only when required by business logic or by expectation of user in presentation.
  • Always specify your desired/expected time zone explicitly as optional argument. Even if you intend to use the current default value, explicitly call for the default, to make your code self-documenting about your intention. By the way… Ditto for Locale: always specify explicitly, never rely implicitly on default.

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.

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

Where to obtain the java.time classes?

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
2

you can use as per your requirment.

   java.util.Date
   DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
   Date date = new Date();
   System.out.println(dateFormat.format(date));

   java.util.Calendar
   DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
   Calendar cal = Calendar.getInstance();
   System.out.println(dateFormat.format(cal.getTime()));

   java.time.LocalDateTime
   DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
   LocalDateTime localDateTime = LocalDateTime.now();
   System.out.println(dateTimeFormat.format(localDateTime));
Anshul Sharma
  • 3,432
  • 1
  • 12
  • 17
  • I need to mix `DateTimeFormatter` and `Date`. – Dims Apr 20 '17 at 18:58
  • 2
    @Dims No, avoid `Date` entirely and stick with the *java.time* classes only. The legacy `Date`/`Calendar` and related classes were replaced by *java.time* classes for many reasons (poor design, faulty concepts, hacks, well-intentioned but troublesome anti-features). You can convert with `Instant instant = myJavaUtilDate.toInstant()` . – Basil Bourque Feb 19 '18 at 02:17
  • Lot's of 3rd party are using Date, e.g. Apache httpcomponents/cookies. We must deal with it, unfortunately, until it is entirely phased out. So please report the fastest and least buggy way to convert a Date to a LocalDateTime. And a 2-cent prediction: joda time is going out in a few years too. – bliako Mar 27 '18 at 13:19
  • @bliako *Lot's of 3rd party are using Date* Unfortunately true, though they tend to get upgraded along the way. However, this was not what the question asked, and also, having a `Date` from a legacy API is no reason why anyone would want to take the bad trouble with `SimpleDateFormat`. Instead see the other answers. With `DateTimeFormatter` in place we’re also already better prepared for the day when our API gets upgraded to java.time. – Ole V.V. Jan 14 '23 at 13:03