LocalDateTime.atZone() means: Take this local time and consider it to be the local time in this time zone.
That means that 6:19 PM "local time" in Chicago is ... 6:19 PM in time zone EST, and that 6:19 PM "local time" in Tokyo is ... 6:19 PM in time zone JST.
When you call LocalDateTime.now(), you get the local time in your current default time zone, but the returned value don't know what time zone that is. As the javadoc says:
This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
If you want to take the current real time and see what it is in Chicago and Tokyo, then you need to use either a universal time (Instant) or a time that know what time zone it represents (ZonedDateTime).
Using Instant
Instant now = Instant.now();
ZonedDateTime chicago = now.atZone(ZoneId.of("America/New_York"));
System.out.println("Chicago: " + chicago);
System.out.println("Chicago formatted: " + chicago.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL)));
ZonedDateTime tokyo = now.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println("Tokyo: " + tokyo);
System.out.println("Tokyo formatted: " + tokyo.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL)));
Output
Chicago: 2017-11-05T20:02:45.444-05:00[America/New_York]
Chicago formatted: 8:02:45 PM EST
Tokyo: 2017-11-06T10:02:45.444+09:00[Asia/Tokyo]
Tokyo formatted: 10:02:45 AM JST
Using ZonedDateTime
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime chicago = now.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("Chicago: " + chicago);
System.out.println("Chicago formatted: " + chicago.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL)));
ZonedDateTime tokyo = now.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("Tokyo: " + tokyo);
System.out.println("Tokyo formatted: " + tokyo.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL)));
Output
Chicago: 2017-11-05T20:04:19.368-05:00[America/New_York]
Chicago formatted: 8:04:19 PM EST
Tokyo: 2017-11-06T10:04:19.368+09:00[Asia/Tokyo]
Tokyo formatted: 10:04:19 AM JST
Answer from Andreas on Stack OverflowJesper has already given you short answers to your questions.
The no-arg method LocalDate.html#now obtains the current date from the system clock in the default (system) time-zone. Therefore, when you print LocalDate.now() in Madrid, India and New York, at the same time, you may get different results (depending on what time you do it).
Demo:
import java.time.LocalDate;
import java.time.ZoneId;
public class Main {
public static void main(String[] args) {
System.out.println(LocalDate.now(ZoneId.of("Europe/Madrid")));
System.out.println(LocalDate.now(ZoneId.of("Asia/Kolkata")));
System.out.println(LocalDate.now(ZoneId.of("America/New_York")));
}
}
Output:
2022-09-28
2022-09-29
2022-09-28
Note: to understand this difference, check the output of the following program:
import java.time.LocalDateTime;
import java.time.ZoneId;
public class Main {
public static void main(String[] args) {
System.out.println(LocalDateTime.now(ZoneId.of("Europe/Madrid")));
System.out.println(LocalDateTime.now(ZoneId.of("Asia/Kolkata")));
System.out.println(LocalDateTime.now(ZoneId.of("America/New_York")));
}
}
Result:
2022-09-28T21:03:56.438167
2022-09-29T00:33:56.443577
2022-09-28T15:03:56.444049
However, a LocalDate object does not store the time-zone information. Therefore, when you print an object of LocalDate (i.e. LocalDate#toString), the printed value will remain the same irrespective of the system time-zone.
A java.util.Date object too does not store the time-zone information. It stores the number of milliseconds from January 1, 1970, 00:00:00 GMT. However, when you print an object of java.util.Date (i.e. Date#toString), it uses the default (system) time-zone to print the value i.e. if you print an object of java.util.Date on systems with different time-zones set to them, you will get different results.
I don't think Question # 4 was answered well. I think it is obvious to Jasper and others but not so obvious to me and I think OP.
LocalDateTime.now() will use either system clock or provided timezone to determine what "now" is, but then removes the time zone reference. It is impossible to determine what original time zone was used to create the LocalDateTime object.
That had me wondering how does LocalDateTime.atZone(ZoneId.of("Europe/Madrid")) properly convert the LocalDateTime to Europe/Madrid timezone if there is no time zone reference point to convert from? For example, if LocalDateTime is 2022-09-28T21:03:56, how can it then convert that to Europe/Madrid when it does not understand what time zone the date/time it holds originated from?
The solution is obvious now. It doesn't convert and it does not try to figure out what timezone it originated from. It just appends the timezone specified to the date/time it has.
So if a LocalDateTime object has a value of 2022-09-28T21:03:56, then LocalDateTime.atZone(ZoneId.of("Europe/Madrid")) instantiates a ZonedDateTime object with a value of 2022-09-28T21:03:56+01:00. Notice there is no change other than adding the +01:00 offset in use by the people of that time zone at that moment.
If that particular time-of-day does not exist in that time zone at that moment, such as during a Daylight Saving Time (DST) cutover, the time-of-day in the new ZonedDateTime is adjusted logically.
After I understood those two points it was all really clear how it worked.
Hope this helps someone else that this was not that obvious for.
Since Java 8 the date/time API is pretty easy to work with.
In your case:
// your local date/time with no timezone information
LocalDateTime localNow = LocalDateTime.now();
// setting UTC as the timezone
ZonedDateTime zonedUTC = localNow.atZone(ZoneId.of("UTC"));
// converting to IST
ZonedDateTime zonedIST = zonedUTC.withZoneSameInstant(ZoneId.of("Asia/Kolkata"));
You'll see a difference in the time (and possibly the date) between zonedUTC and zonedIST, reflecting the time zone offset between the two.
Note the usage of withZoneSameInstant here, e.g. as opposed to withZoneSameLocal.
tl;dr
Use Instant, not LocalDateTime for tracking a moment.
Instant // Represents a moment in UTC with a resolution of nanoseconds.
.ofEpochSecond(
myCountOfWholeSecondsSinceStartOf1970UTC // Internally, time is tracked as a count of seconds since 1970-01-01T00:00Z plus a fractional second as nanoseconds.
) // Returns a moment in UTC.
.atZone( // Adjust from UTC to another time zone. Same moment, different wall-clock time.
ZoneId.of( "Asia/Kolkata" ) // Specify time zone name in `Continent/Region` format, never 2-4 letter pseudo-zone.
) // Returns a `ZonedDateTime` object.
.toString() // Generate a string representing the value of this `ZonedDateTime` in standard ISO 8601 format extended to append the name of the time zone in square brackets.
Wrong class
LocalDateTime dateTimeOfAfterFiveDays = LocalDateTime.ofEpochSecond(after5,0,ZoneOffset.UTC);
LocalDateTime is the wrong class to use here. This class cannot represent a moment. It lacks any concept of time zone or offset-from-UTC. A LocalDateTime holds just a date and a time-of-day, say noon on the 23rd of January this year. But we have no idea if what was intended was noon in Tokyo, noon in Kolkata, noon in Paris, or noon in Montréal — all of these are hours apart, very different moments.
Instant
To represent a moment in UTC, use Instant.
Apparently you have a count of whole seconds since the epoch reference of first moment of 1970 in UTC.
Instant instant = Instant.ofEpochSecond( count ) ;
ZoneId & ZonedDateTime
To see this value through the wall-clock time used by the people of a particular region (a time zone), apply a ZoneId to get a ZonedDateTime.
Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-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( "Asia/Kolkata" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
LocalDateTime is a date-time without a time zone. You specified the time zone offset format symbol in the format, however, LocalDateTime doesn't have such information. That's why the error occurred.
If you want time zone information, you should use ZonedDateTime.
DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss.SSSSSS Z");
ZonedDateTime.now().format(FORMATTER);
=> "20140829 14:12:22.122000 +09"
The prefix "Local" in JSR-310 (aka java.time-package in Java-8) does not indicate that there is a timezone information in internal state of that class (here: LocalDateTime). Despite the often misleading name such classes like LocalDateTime or LocalTime have NO timezone information or offset.
You tried to format such a temporal type (which does not contain any offset) with offset information (indicated by pattern symbol Z). So the formatter tries to access an unavailable information and has to throw the exception you observed.
Solution:
Use a type which has such an offset or timezone information. In JSR-310 this is either OffsetDateTime (which contains an offset but not a timezone including DST-rules) or ZonedDateTime. You can watch out all supported fields of such a type by look-up on the method isSupported(TemporalField).. The field OffsetSeconds is supported in OffsetDateTime and ZonedDateTime, but not in LocalDateTime.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss.SSSSSS Z");
String s = ZonedDateTime.now().format(formatter);
Offsets vary over time
On April 6, 2020, most time zones on the east coast of North America such as America/Montreal and America/New_York use an offset of four hours behind UTC.
So, in those zones 11 PM on 2020-04-06 is simultaneously 3 AM on the 7th in UTC. Add four hours to 2020-04-06T23:00 to get 2020-04-07T03:00. Adding four hours brings us from the wall-clock time used in America/Port-au-Prince and America/Nassau to UTC.
The offset used by the time zones mentioned above are four hours behind UTC only half the year. The politicians in those locations have decided to observe Daylight Saving Time (DST) for about half the year. So half the year they jump their clocks ahead an hour for an offset-from-UTC of -04:00, and later in the year they fall back an hour for an offset-from-UTC of -05:00. For example, earlier in the year 11 PM in America/New_York would be 04:00 in UTC rather than the 03:00 time seen in April.
Standard time, in January, is five hours behind UTC. So, 11 PM plus five hours is 4 AM next day.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ZonedDateTime.of( 2020 , 1 , 6 , 23 , 0 , 0 , 0 , zNewYork ) ;
Instant instant = zdt.toInstant() ; // 2020-01-07T04:00Z
Daylight Saving Time, in April, is four hours behind UTC. So, 11 PM plus four hours is 3 AM next day.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ZonedDateTime.of( 2020 , 4 , 6 , 23 , 0 , 0 , 0 , zNewYork ) ;
Instant instant = zdt.toInstant() ; // 2020-04-07T03:00Z
By the way, DST is only one of many reasons politicians have for changing the offset used by the time zone(s) of their jurisdiction. Politicians around the world have shown a penchant for changing their offsets surprisingly often. Your programming should always expect a time zone to change its offset. Just because your particular zone of concern does not currently observe DST does not mean the offset will never change.
As for parsing 2020-04-06T23:00:00.000 as a LocalDateTime, you will get 2020-04-06T23:00:00.000. Ignoring time zones is the entire point of LocalDateTime. So no adjustments are made.

You may be thinking of LocalDateTime as representing a particular locality. But, no, it represents any locality or all localities. But never any one particular locality. For a particular locality, use ZonedDateTime.
Pseudo time zones
Another point: EST is not a time zone. Such 2-4 letter letter codes are not real time zones, are not standardized, and are not even unique. For example, by EST, did you mean Australian Eastern Standard Time or North American Eastern Standard Time? Is CST Central Standard Time or China Standard Time? Avoid these pseudo-zones in your date-time-handling.
And you happened to use the wrong pseudo-code. On April 6 2020, most of those east coast time zones are observing Daylight Saving Time (DST). So they would be considered to be in “EDT” rather than “EST”. Avoid the problem by specifying the real time zone name.
Real time zones are named using Continent/Region format. See Wikipedia for a list of real time zones.
Never call LocalDateTime.now
I cannot imagine a case where calling LocalDateTime.now would be the right thing to do. Determining the current moment requires a time zone (or offset). And LocalDateTime by definition has no time zone or offset. When a programmer writes LocalDateTime.now, you can bet they do not fully understand the necessary concepts.
When you call LocalDateTime.now, the JVM’s current default time zone is implicitly used to capture the current time as seen by the people in the region of that zone. And then the fact of that time zone is deleted.
This:
LocalDateTime.now()
…is the same as this:
LocalDateDate.now( ZoneId.systemDefault() )
…which is the same as getting the current moment as seen in a particular time zone, followed by removing the time zone information:
ZonedDateTime.now( ZoneId.systemDefault() ).toLocalDateTime()
For more code examples demonstrating LocalDateTime.now, see the correct Answer by Ole V.V.
Where to use LocalDateTime
If LocalDateTime is not appropriate for getting the current moment, and is not appropriate for tracking any moment, what is the appropriate use for this class? Three things: representing any locality, representing all localities, and booking future appointments.
- Any locality would be something like stating when Christmas starts. This year Christmas starts at 2020-12-25T00:00 wherever you are in the world. Of course this means Christmas starts first in Kiribati after midnight, later in Japan after midnight, even later in Tunisia after midnight, and still later in Chicago after midnight.
- All localities would be something like stating our company policy that lunch breaks at all our factories in Delhi, Düsseldorf, and Detroit are scheduled for 12:30. So on April 6 2020, the break will be at 2020-04-06T12:30:00. This break will occur first in Delhi, several hours later in Düsseldorf, and even more hours later in Detroit.
- Booking future appointments where you intend to keep the same time-of-day regardless of changes to the time zone’s offset must recorded without the offset. If your next dental appointments is in six months at 3 PM, we want to record the 3 PM without regard for the offset. If the offset were to be changed by politicians, we still want the appointment to start when the clock strikes three on that date in that zone.
Set the appointment.
LocalDateTime appointment = LocalDateTime.of( 2021 , 1 , 23 , 15 , 0 , 0 , 0 ) ;
Determine a moment for producing a calendar. Every time you do this, you may get a different result if the politicians have changed the rules of this time zone.
ZoneId z = ZoneId.of ( "America/Panama" ) ;
ZonedDateTime dueToArrive = appointment.atZone( z ) ;
LocalDate
As for LocalDate, we saw in the examples above that the date depends on time zone. For any given moment, the date varies around the globe by time zone. It may be “tomorrow” in Tokyo Japan while still “yesterday” in Montréal Québec Canada.
So, you must specify a time zone when asking for the current date.
LocalDate ld = LocalDate.now( ZoneId.of( "Asia/Kolkata" ) ) ;
If you omit the time zone, the JVM’s current default time zone is implicitly applied. So LocalDate.now() becomes LocalDate.now( ZoneId.systemDefault() ). I recommend specifying your desired/expected zone explicitly, rather than rely on implicit default.
Server time zone
You said:
server which runs in EST vs UTC
FYI, servers should generally be set to UTC. Most of your thinking, programming, logging, and so on should be done in UTC. Learn to think of UTC as The One True Time, with all other zones but mere variations.
As a programmer, you should never rely on the default time zone. That is far outside your control. That default can be changed so easily by the user or sysadmin. Furthermore, any code in any thread of any app within the JVM can instantly change the JVM’s current default time with a call to TimeZone.setDefault. So even during execution of your app, the default can be changed at any moment.
To prevent confusion pass explicit time zone to now() and see for yourself:
ZoneId easternTime = ZoneId.of("America/Montreal");
System.out.println(LocalDateTime.now(easternTime));
System.out.println(LocalDateTime.now(ZoneOffset.UTC));
System.out.println(LocalDate.now(easternTime));
System.out.println(LocalDate.now(ZoneOffset.UTC));
Output when I ran just now:
2020-04-06T09:56:17.381558 2020-04-06T13:56:17.385215 2020-04-06 2020-04-06
While you are correct that LocalDateTime and LocalDate don’t contain any time zone information, their now methods do use time zones. Either the one passed to them, or if you use the no-arg variant, the default time zone of the JVM.
You also asked:
Also, If I convert below date string to LocalDateTime and then toLocalDate, what would be the outcome?
2020-04-06T23:00:00.000
Why not try out that too?
LocalDateTime ldt = LocalDateTime.parse("2020-04-06T23:00:00.000");
System.out.println(ldt);
LocalDate ld = ldt.toLocalDate();
System.out.println(ld);
2020-04-06T23:00 2020-04-06
Converting from LocalDateTime to LocalDate involves no time zone (or UTC offset) whatsoever. The time part is simply discarded and the date part kept unchanged.