I'm trying to convert java Date to java LocalTime and my code looks like this
Date memberBirthdayDate = club.getMembers().get(i).getDob();
System.out.println(memberBirthdayDate);
LocalDate memberBirthday = memberBirthdayDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
When I print out the date before and after converstion it looks like this:
Before: Wed May 21 00:00:00 GMT 94
After: 0094-05-18
It looks like it's converting backwards but I can't work out how to do it!
you can do that
LocalDateTime memberBirthday = LocalDateTime.ofInstant(memberBirthdayDate.toInstant(),
ZoneId.systemDefault());
Date out = Date.from(memberBirthday.atZone(ZoneId.systemDefault()).toInstant()); System.out.println(out);
There seems to be a bug in your years. You gave the answer yourself in the comment:
I'd imported from a csv through Excel and it had automatically
formatted the date to remove the '19'
The conversion you made works nicely for a date in 1994:
System.out.println("Before: " + memberBirthdayDate);
LocalDate memberBirthday = memberBirthdayDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
System.out.println("After: " + memberBirthday);
Output:
Before: Sat May 21 00:00:00 IST 1994
After: 1994-05-21
I used Europe/Dublin time to reproduce your exact result, so IST is for Irish Summer Time. Your Date seems to denote the start of day at some GMT offset, so you need to use a time zone that agrees with this offset. I expect the conversion to work as expected at least for all dates after year 1900, and likely earlier too.
However, when the year gets truncated from 1994 to 94, funny things start to happen. Dates that far back are not so well defined. LocalDate uses the proleptic Gregorian calendar, which is practical and well-defined, but doesn’t agree with dates used in real life before the introduction of the Gregorian calendar from 1582 and on. I’m not sure what Date uses. For dates back in year 94 AD we shouldn’t be surprised about May 21 coming through as May 18.
Before: Wed May 21 00:00:00 GMT 94
After: 0094-05-18
Link: Wikipedia article Gregorian calendar
Related
For improving performance of some legacy code, I am considering a replacement of java.text.SimpleDateFormat by java.time.format.DateTimeFormatter.
Among the tasks performed is parsing date/time values that had been serialized using java.util.Date.toString. With SimpleDateFormat, it was possible to turn them back into the original timestamps (neglecting fractional seconds), however I am facing problems when attempting to do the same with DateTimeFormatter.
When formatting with either, my local timezone is indicated as CET or CEST, depending on whether daylight savings time is in effect for the time to be formatted. However it appears that at parsing time, both CET and CEST are treated the same by DateTimeFormatter.
This creates a problem with the overlap occurring at the end of daylight savings time. When formatting, 02:00:00 is created twice, for times one hour apart, but with CEST and CET timezone names - which is fine. But at parsing time, that difference can't be reclaimed.
Here is an example:
long msecPerHour = 3600000L;
long cet_dst_2016 = 1477778400000L;
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH);
ZoneId timezone = ZoneId.of("Europe/Berlin");
for (int hours = 0; hours < 6; ++hours) {
long time = cet_dst_2016 + msecPerHour * hours;
String formatted = formatter.format(Instant.ofEpochMilli(time).atZone(timezone));
long parsedTime = Instant.from(formatter.parse(formatted)).toEpochMilli();
System.out.println(formatted + ", diff: " + (parsedTime - time));
}
which results in
Sun Oct 30 00:00:00 CEST 2016, diff: 0
Sun Oct 30 01:00:00 CEST 2016, diff: 0
Sun Oct 30 02:00:00 CEST 2016, diff: 0
Sun Oct 30 02:00:00 CET 2016, diff: -3600000
Sun Oct 30 03:00:00 CET 2016, diff: 0
Sun Oct 30 04:00:00 CET 2016, diff: 0
It shows that the second occurrence of 02:00:00, inspite of the different timezone name, was treated like the first one. So the result effectively is off by one hour.
Obviously the formatted string has all information available, and SimpleDateFormat parsing in fact honored it. Is it possible to roundtrip through formatting and parsing, using DateTimeFormatter, with the given pattern?
It is possible for a specific case:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd HH:mm:ss ")
.appendText(OFFSET_SECONDS, ImmutableMap.of(2L * 60 * 60, "CEST", 1L * 60 * 60, "CET"))
.appendPattern(" yyyy")
.toFormatter(Locale.ENGLISH);
This maps the exact offset to the expected text. Where this fails is when you need to deal with more than one time-zone.
To do the job properly requires a JDK change.
It seems like a bug. I tested in Java 17 and it's still the same behaviour. I dug into the parsing logic and I can see why this happens.
One of the first things that happens is TimeZoneNameUtility.getZoneStrings(locale) is called. This gives you a 2D array of Strings
[
[
"Europe/Paris",
"Central European Standard Time", "CET",
"Central European Summer Time", "CEST",
"Central European Time", "CET"
],
// others
]
It builds a prefix tree out of them. All items in here get mapped to the 0th item - "Europe/Paris". When it's parsing, it descends the prefix tree one character at a time e.g. C... E... T..., then returns a match if there was one. Since CEST and CET map to the same thing, they're effectively just aliases of one another.
Later on that string is passed to ZoneId.of() which means the fact of whether it's summertime or not has been thrown away.
It does seem in Java 18 that there have been significant changes in this code, so maybe they're addressing that.
The general workaround
JodaStephen, the main author of java.time, in his answer shows a workaround for the case of CET and CEST (Central European Time and Central European Summer Time). I present a workaround that I believe will work in all time zones having different abbreviations for standard time and summer time (DST).
public static ZonedDateTime parse(String text) {
ZonedDateTime result = ZonedDateTime.parse(text, FORMATTER);
if (result.format(FORMATTER).equals(text)) {
return result;
}
// Default we get the earlier offset at overlap,
// so if it didn’t work, try the later offset
result = result.withLaterOffsetAtOverlap();
if (result.format(FORMATTER).equals(text)) {
return result;
}
// As a last desperate attempt, try earlier offset explicitly
result = result.withEarlierOffsetAtOverlap();
if (result.format(FORMATTER).equals(text)) {
return result;
}
// Give up
throw new IllegalArgumentException();
}
The method could use any formatter with a time zone name or abbreviation as long as it’s supposed to give the same output from formatting as the input it parses (so optional parts are a no-no, for example). I have assumed a formatter equivalent to yours:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ROOT);
Your trouble was with a millisecond value of 1 477 789 200 000, which was formatted into Sun Oct 30 02:00:00 CET 2016 and then parsed to 1 477 785 600 000 for a difference of -3 600 000 milliseconds. So let’s try my method with that one.
private static final ZoneId TIME_ZONE = ZoneId.of("Europe/Berlin");
long trouble = 1_477_789_200_000L;
String formatted = Instant.ofEpochMilli(trouble).atZone(TIME_ZONE).format(FORMATTER);
ZonedDateTime zdt = parse(formatted);
long parsedTime = zdt.toInstant().toEpochMilli();
System.out.println(formatted + ", diff: " + (parsedTime - trouble));
Output is:
Sun Oct 30 02:00:00 CET 2016, diff: 0
But don’t parse three letter time zone abbreviations
All of the above said, even with a workaround for that case of the fall overlap, you are on shaky ground when trying to parse time zone abbreviations. Most of the most common ones are ambiguous, and you don’t know what you get from parsing. In the case of CET and CEST, they are common abbreviations for very many European time zones that at present share offset +01:00 during standard time and +02:00 during summer time, but historically have had their own offset each and are likely to go separate ways again since the EU has decided to give up summer time completely. Next year one time zone may use CET all year and another CEST all year. My code above does not account for that.
Instead simply take the output from ZonedDateTime.toString and parse it back using the one-arg ZonedDateTime.parse(CharSequence).
Consider the following program
Date date1 = new Date(-124304227239000L);
Date date2 = new Date(0);
System.out.println(date1 + " vs. " + date2);
The result (at least with Java 8 on my computer, and with Java 11 on a different computer):
Sun Jan 01 16:59:21 CET 1970 vs. Thu Jan 01 01:00:00 CET 1970
This seems strange because following the documentation (https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#Date-long-) , negative values as parameter for Date indicate dates before 1970. Instead, I get a Sunday instead of Thursday, but still 1970.
Can anybody explain this to me?
The value you've provided is around 1969/1970 BC, depending on whether you do a Gregorian/Julian cutover or not. Date.toString(), aside from all its other problems, doesn't bother to mention the era.
If you use Instant with the same value, it's clearer:
Instant instant = Instant.ofEpochMilli(-124304227239000L);
System.out.println(instant);
Output:
-1970-12-15T15:59:21Z
I'd draw the following conclusions from this:
When using values in the far past, there are lots of considerations to bear in mind, including textual representation and calendar system
Avoid java.util.Date as far as you can
I am trying to parse the string "20/08/18 13:21:00:428" using the DateFormat class and a formatting pattern of "dd/MM/yy' 'HH:mm:ss:SSS". The Timezone is set to BST.
The date returned for the above is correct but the time is getting returned as 08 for the hours instead of 13 - "Mon Aug 20 08:21:00 BST 2018"
The following snippet prints the date and time just mentioned:
String toBeParsed = "20/08/18 13:21:00:428";
DateFormat format = new SimpleDateFormat("dd/MM/yy' 'HH:mm:ss:SSS");
format.setTimeZone(TimeZone.getTimeZone("BST"));
Date parsedDate = format.parse(toBeParsed);
System.out.println(parsedDate);
Is this something to do with my timezone or have I misunderstood the pattern?
BST is Bangladesh Standard Time. The correct time zone to use is "Europe/London" if you want automatic summer time, or "UTC+1" if you want British Summer Time always.
See https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#SHORT_IDS
java.time
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uu H:mm:ss:SSS");
String toBeParsed = "20/08/18 13:21:00:428";
ZonedDateTime dateTime = LocalDateTime.parse(toBeParsed, formatter)
.atZone(ZoneId.of("Europe/London"));
System.out.println(dateTime);
Output from this snippet is:
2018-08-20T13:21:00.428+01:00[Europe/London]
What went wrong in your code?
While I always recommend against the long outdated and poorly designed classes Date, TimeZone and DateFormat, in this case they are behaving particularly confusingly. Printing a Date on a JVM with Europe/London as default time zone gives time zone as BST if the date is in the summer time part of the year:
TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
Date oldFashionedDate = new Date();
System.out.println(oldFashionedDate);
Mon Aug 20 15:45:39 BST 2018
However, when I give time zone as BST, Bangladesh time is understood, but it comes out with the non-standard abbreviation BDT:
TimeZone.setDefault(TimeZone.getTimeZone("BST"));
System.out.println(oldFashionedDate);
Mon Aug 20 20:45:39 BDT 2018
(I have observed this behaviour on Java 8 and Java 10.)
Another lesson to learn is never to rely on three and four letter time zone abbreviations. They are ambiguous and not standardized.
BST may mean Brazil Summer Time or Brazilian Summer Time, Bangladesh Standard Time, Bougainville Standard Time or British Summer Time (note that S is sometimes for Standard, sometimes for Summer, which is typically the opposite of Standard Time).
BDT may mean Brunei Darussalam Time or British Daylight Time (another name for British Summer Time (BST)), but I wasn’t aware that Bangladesh Time was also sometimes abbreviated this way.
PS Thanks to DodgyCodeException for spotting the time zone abbreviation interpretation issue.
Link
Time Zone Abbreviations — Worldwide List
Is it possible to create a java.util.Date object that is guaranteed to be UTC and at a specific time in the past, like an hour ago or a day ago, and is this a good idea?
I have reviewed the Stack Overflow question
get date as of 4 hours ago and its answers. But I want to avoid having to add more dependencies, like jodaTime, and the accepted answer uses System.currentTimeMillis() which would be the local timezone, would it not?
As discussed vividly in the comments, the recommendation is to use the java.time package. The easy solution:
Instant fourHoursAgo = Instant.now().minus(Duration.ofHours(4));
System.out.println(fourHoursAgo);
This just printed
2018-01-31T14:57:44.667255Z
Since UTC time is now 18:58, the output is what you asked for. The Instant itself is offset neutral. Its toString delivers time in UTC, but there was no mention of UTC in producing the Instant, so whether it gives you what you want, I am not sure. I will give you a result that is explicitly in UTC later.
But first, if you do need a java.util.Date, typically for a legacy API that you cannot change, the conversion is easy:
Date oldfashionedDate = Date.from(fourHoursAgo);
System.out.println(oldfashionedDate);
On my computer in Europe/Copenhagen time zone this printed:
Wed Jan 31 15:57:44 CET 2018
Again, this agrees with the time four hours before running the snippet. And again, a Date doesn’t have a UTC offset in it. Only its toString method grabs my JVM’s time zone setting and uses it for generating the string, this does not affect the Date. See the Stack Overflow question, How to set time zone of a java.util.Date?, and its answers.
As promised, if you do need to represent not only the time but also the offset, use an OffsetDateTime:
OffsetDateTime fourHoursAgoInUtc = OffsetDateTime.now(ZoneOffset.UTC).minusHours(4);
System.out.println(fourHoursAgoInUtc);
This printed
2018-01-31T14:57:44.724147Z
Z at the end means offset zero from UTC or “Zulu time zone” (which isn’t a true time zone). The conversion to a Date is not much more complicated than before, but again, you will lose the offset information in the conversion:
Date oldfashionedDate = Date.from(fourHoursAgoInUtc.toInstant());
System.out.println(oldfashionedDate);
This printed:
Wed Jan 31 15:57:44 CET 2018
Link: Oracle tutorial explaining how to use java.time
You can achieve this using the java.time package, as follows:
LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC).minusHours(4);
Date date = Date.from(localDateTime.atZone(ZoneOffset.UTC).toInstant());
Gives the following output:
2018-01-31T14:58:28.908
Wed Jan 31 20:28:28 IST 2018 //20:28:28 IST is 14:58:28 UTC
Which is correctly 4+5:30 hours behind my current time - Asia/Kolkata ZoneId.
Our server is running in netherlands but users uses the application in UK .
For UK 2017-03-26 02:30:00 is valid date-time but not in Netherlands.
I am using the code to convert the time by setting the timezone . But its not giving me right output.
String toDate ="2017-03-26 02:30:00";//Valid Time in UK
Date date = new Date();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// Use London's time zone to format the date in
df.setTimeZone(TimeZone.getTimeZone("London"));
System.out.println("Date and time in London: " + df.parse(toDate));
Output from Program : Date and time in London: Sun Mar 26 04:30:00 CEST 2017
Output Required : Date and time in London: Sun Mar 26 02:30:00 CEST 2017
Java version used : 1.7 . Can not use joda time for some dependency.
First a detail, TimeZone.getTimeZone() is dangerous, if it doesn’t recognize the ID string, it will tacitly give you GMT. Exactly in London this is so close to the correct you may get fooled for a while. TimeZone.getTimeZone("London") gives you UTC (or GMT). As Stefan Freitag pointed out, it has to be TimeZone.getTimeZone("Europe/London") for the correct British time zone.
Next a very common misunderstanding: A Date object does not have a time zone in it. It’s a point in time only. So how come it is printed as Sun Mar 26 04:30:00 CEST 2017, where CEST obviously refers to a time zone (Central European Summer Time, used in the Netherlands)? When printing the date, you are implicitly invoking Date.toString(). This methods unconditionally prints the time according the default time zone for the JVM. Therefore, setting the time zone for the DateFormat you used for parsing has no effect here. And therefore changing the JVM’s time zone setting, as Hugo did in a comment, works. The other way of getting correct output for British users is to format the Date back into a string using a DateFormat with British time zone.
If you use Hugo’s trick in the beginning, before creating the DateFormat, it too will have the British time zone, and you need not call df.setTimeZone() (but you may if you think it makes the code clearer, of course).
Look forward to using Java 8 or later some day. The new date and time classes in java.time generally don’t come with the surprises experienced with the old ones.