Consider a code:
TemporalAccessor date = DateTimeFormatter.ofPattern("yyyy-MM-dd").parse("9999-12-31");
Instant.from(date);
The last line throws an exception:
Unable to obtain Instant from TemporalAccessor: {},ISO resolved to 9999-12-31 of type java.time.format.Parsed
How to create Instant from yyyy-MM-dd pattern?
The string "9999-12-31" only contains information about a date. It does not contain any information about the time-of-day or offset. As such, there is insufficient information to create an Instant. (Other date and time libraries are more lenient, but java.time avoids defaulting these values)
Your first choice is to use a LocalDate instead of an `Instant:
LocalDate date = LocalDate.parse("9999-12-31");
Your second choice is to post process the date to convert it to an instant, which requires a time-zone, here chosen to be Paris:
LocalDate date = LocalDate.parse("9999-12-31");
Instant instant = date.atStartOfDay(ZoneId.of("Europe/Paris")).toInstant();
Your third choice is to add the time-zone to the formatter, and default the time-of-day:
static final DateTimeFormatter FMT = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd")
.parseDefaulting(ChronoField.NANO_OF_DAY, 0)
.toFormatter()
.withZone(ZoneId.of("Europe/Paris"));
Instant instant = FMT.parse("9999-31-12", Instant::from);
(If this doesn't work, ensure you have the latest JDK 8 release as a bug was fixed in this area).
It is worth noting that none of these possibilities use TemporalAccessor directly, because that type is a low-level framework interface, not one for most application developers to use.
The problem isn't the fact that you are using the year 9999. The Instant.MAX field evaluates to the timestamp 1000000000-12-31T23:59:59.999999999Z, so 9999 as a year is fine.
Dealing with TemporalAccessors instead of the more semantically rich types like LocalDateTime or ZonedDateTime is like using a Map to model an object and its properties instead of writing a class -- you have to assure that the value has the fields (like seconds, nanoseconds, etc) that are expected by something receiving it, rather than depending on formally declared operations in a higher level class to prevent dependencies from going unmet.
In your case it is likely that the temporal accessor contained the parsed date fields it was given, but didn't have a "seconds" field that the Instant needed. It is best to use the more semantically rich types like LocalDateTime in most instances.
Since you only have date fields, you should parse it as a date, then add the time fields before converting it to an Instant. Here is one way, using LocalDate to parse the date:
LocalDate localDate = LocalDate.parse("2016-04-17");
LocalDateTime localDateTime = localDate.atStartOfDay();
Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
Either you are only interested in the date itself (31st of December 9999), in which case the appropriate type would be a LocalDate:
LocalDate date = LocalDate.parse("9999-12-31");
Or you do want an Instant, in which case you need to set a time and time zone, for example, 00:00 in Tokyo:
Instant instant = date.atStartOfDay(ZoneId.of("Asia/Tokyo")).toInstant();
public static void main(String[] args) throws ParseException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse("2016-12-31").toInstant());
}
the above code gives the following output:
2016-12-31T00:00:00Z
i have answered this question using features('toInstant' method) of java 8. hope this answers your question...
Related
I'm receiving a query parameter date, as yyyy-MM-DD (2022-03-08).
I want to conver this to java.util.Calendar / java.util.GregorianCalendar formmat.
So my idea is converto:
String -> ZonedDateTime -> Calendar.
What I did:
ZonedDateTime parsedDate = ZonedDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//date = 2022-03-08
even with the correct format, I'm getting:
Text '2022-03-08' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2022-03-08 of type java.time.format.Parsed
I found out that this error occurs because my string does not have a TimeZone.
One suggestion was to use LocalDate
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date = LocalDate.parse(fecha, formatter);
but I can't use localDate as an argument for ZonedDateTime.parse().
What else could I try?
I want to conver this to java.util.Calendar / java.util.GregorianCalendar formmat.
That seems silly; Calendar/GregorianCalendar is obsolete, and the API is horrendous. Why use a broken screwdriver when there's a shiny new one right there in the toolbox? Don't do this.
So my idea is converto: String -> ZonedDateTime -> Calendar.
That seems silly. The string does not contain a ZonedDateTime. It doesn't even contain a LocalDateTime. It is clearly a LocalDate. So, convert it to a localdate, and you go from there.
The power of the java.time package is that each different concept in time has a matching type in the j.t package that is properly named. For example, java.util.Date is a lie: It is a timestamp, and it has nothing whatsoever to do with dates; asking a Date object for 'what year is it', is broken (try it, you get a warning).
Calendar, similarly, is an utter falsehood. It does not represent a calendar at all; it, too, represents a timestamp.
LocalDate on the other hand is perfect truth: It represents a date (not a time), and it does not include timezone or other localizing information: It makes sense only as 'locally'.
Each stop should just make sense, on its own:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
LocalDate date = LocalDate.parse("2022-10-01", formatter);
So far, so good. I'd just stop there - why lie? Why return a Calendar which is both API wise a lie (that class does not represent calendars), and even if someone knows exactly what Calendar is, it's still a lie: A calendar implies it has exact time and a timezone. You do not have a time, and also don't have a timezone. Why return something that suggests stuff that isn't there?
But, if you MUST, then explicitly add a timezone and a time, and THEN go for it:
ZonedDateTime zdt = someLocalDate.atStartOfDay().atZone(ZoneId.of("Europe/Amsterdam"));
GregorianCalendar gc = GregorianCalendar.from(zdt);
This code is clear and legible: It makes crystal clear that the code picks a time, and picks a zone.
But, again, now you ended up with a horrible, horrible object you should not be using, for anything.
There are other ways of getting a ZonedDateTime than just its static parse() method. Here's how to turn a LocalDateTime into a ZonedDateTime:
ZonedDateTime zoned = dateTime.atZone(ZoneId.of( "America/New_York"));
or if you have a LocalDate:
ZonedDateTime zoned =
date.atStartOfDay( ZoneId.of( "America/New_York" ));
I would not use java.util.Calendar or Date. They're junk. I'd either stick with LocalDate or use ZonedDateTime depending on if you need to keep track of time zones or not. This should get you where you want to go either way, I guess, as it sounds like you know what you want to do once you have a ZonedDateTime.
UPDATE: I looked up how to convert a ZoneDateTime to a Calendar:
Calendar calendar = GregorianCalendar.from(zoned);
just in case you hadn't gotten that far and really want to go that way.
I'm trying to parse lets say "2020-01-12+01:00" with JSR-310 time.
I read it via DateTimeFormatter.ofPattern("yyyy-MM-ddVV"), however now if I want to transform that into a Instant via Instant.from(DateTimeFormatter.ofPattern("yyyy-MM-ddVV").parse("..."), it throws where it complains that time is null.
Which granted it is, but, I'd like to get Instant from that, i.e. epochMillis, so I can serialize the long into a database.
Is there a way around it? Basically I'd like to extend the "2020-01-12+01:00" to "2020-01-12T00:00.000+01:00" and parse that to Instant as usual
You need to use DateTimeFormatterBuilder, specifying ISO_DATE format and a default time-of-day (midnight1):
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_DATE)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter();
Instant instant = Instant.from(formatter.parse("2020-01-12+01:00"));
System.out.println(instant);
1) The ChronoField can be any time-of-day field, i.e. HOUR_OF_DAY, CLOCK_HOUR_OF_DAY, MINUTE_OF_DAY, SECOND_OF_DAY, MILLI_OF_DAY, MICRO_OF_DAY, or NANO_OF_DAY.
Output
2020-01-11T23:00:00Z
If you want to retain the time zone offset, you need to use OffsetDateTime (or ZonedDateTime) instead of Instant:
OffsetDateTime dateTime = OffsetDateTime.parse("2020-01-12+01:00", formatter);
System.out.println(dateTime);
System.out.println(dateTime.format(DateTimeFormatter.ISO_DATE));
Output (from both OffsetDateTime and ZonedDateTime)
2020-01-12T00:00+01:00
2020-01-12+01:00
You can use LocalDate.parse(dateString, formatter) using the Formatter you've made above to give you a LocalDate instance.
LocalDate can then give you a LocalDateTime at any time in that day, but (for example) you can get the start of day from it.
LocalDateTime has a toInstant method to give you an Instant.
Instant has a toEpochMilli method to get your long.
It’s easy enough when you know how. The formatter we need is built in. There’s a complication in the fact that there isn’t a type to parse the string into, no OffsetDate. I present two options for tackling this.
String s = "2020-01-12+01:00";
TemporalAccessor parsed = DateTimeFormatter.ISO_OFFSET_DATE.parse(s);
LocalDate date = LocalDate.from(parsed);
ZoneOffset offset = ZoneOffset.from(parsed);
Instant result = date.atStartOfDay(offset).toInstant();
System.out.println(result);
Output from this snippet is:
2020-01-11T23:00:00Z
We seldom need to use the TemporalAccessor interface directly, and it’s considered low-level. It also isn’t the only way to go here. The other good option is to define a default time of day so we can parse directly into an Instant:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_OFFSET_DATE)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter();
Instant result = formatter.parse(s, Instant::from);
The result is the same as before.
An external API returns an object with a date.
According to their API specification, all dates are always reported in GMT.
However, the generated client classes (which I can't edit) doesn't set the timezone correctly. Instead, it uses the local timezone without converting the date to that timezone.
So, long story short, I have an object with a date that I know to be GMT but it says CET. How can I adjust for this mistake withouth changing my local timezone on the computer or doing something like this:
LocalDateTime.ofInstant(someObject.getDate().toInstant().plus(1, ChronoUnit.HOURS),
ZoneId.of("CET"));
Thank you.
tl;dr ⇒ use ZonedDateTime for conversion
public static void main(String[] args) {
// use your date here, this is just "now"
Date date = new Date();
// parse it to an object that is aware of the (currently wrong) time zone
ZonedDateTime wrongZoneZdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("CET"));
// print it to see the result
System.out.println(wrongZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the information that should stay (only date and time, NOT zone or offset)
LocalDateTime ldt = wrongZoneZdt.toLocalDateTime();
// print it, too
System.out.println(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// then take the object without zone information and simply add a zone
ZonedDateTime correctZoneZdt = ldt.atZone(ZoneId.of("GMT"));
// print the result
System.out.println(correctZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
Output:
2020-01-24T09:21:37.167+01:00[CET]
2020-01-24T09:21:37.167
2020-01-24T09:21:37.167Z[GMT]
Explanation:
The reason why your approach did not just correct the zone but also adjusted the time accordingly (which is good when desired) is your use of a LocalDateTime created from an Instant. An Instant represents a moment in time which could have different representations in different zones but it stays the same moment. If you create a LocalDateTime from it and put another zone, the date and time are getting converted to the target zone's. This is not just replacing the zone while keeping the date and time as they are.
If you use a LocalDateTime from a ZonedDateTime, you extract the date and time representation ignoring the zone, which enables you to add a different zone afterwards and keep the date and time as it was.
Edit: If the code is running in the same JVM as the faulty code, you can use ZoneId.systemDefault() to get the same time zone as the faulty code is using. And depending on taste you may use ZoneOffset.UTC instead of ZoneId.of("GMT").
I am afraid you will not get around some calculations here. I'd strongly suggest to follow an approach based on java.time classes, but alternatively you might use the java.util.Calendar class and myCalendar.get(Calendar.ZONE_OFFSET) for those calculations:
https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#ZONE_OFFSET
How to calculate the difference between current day and date of the object that user had previously selected from jXDatePicker swing component and that had been added as Date to that object.
In my current code at the last line I'm getting this error message:
no suitable method found for between(Date, Date)
Date currentDate = new Date();
Date objDate = obj.getSelectedDate(); //getting date the user had
//previously selected and that been
//added to object
long daysDifference = ChronoUnit.DAYS.between(objDate, currentDate);
You are mixing up the legacy Date-Time code with the new Java 8 Date-Time API. The ChronoUnit.between(Temporal, Temporal) method is from java.time.temporal package which takes two Temporal objects. It does not support the java.util.Date as an argument, hence the compilation error.
Instead of using the legacy Date class, you can use java.time.LocalDate class , and then get the difference between the two dates.
LocalDate currentDate = LocalDate.now(ZoneId.systemDefault());
LocalDate objDate = obj.getSelectedDate(); // object should also store date as LocalDate
long daysDifference = ChronoUnit.DAYS.between(objDate, currentDate);
Update
As per your comment , the objDate can only be a Date, so in this case you can use the inter-operability between the Legacy Date -Time and the Java 8 Date-Time classes.
LocalDateTime currentDate = LocalDateTime.now(ZoneId.systemDefault());
Instant objIns = obj.getSelectedDate().toInstant();
LocalDateTime objDtTm = LocalDateTime.ofInstant(objIns, ZoneId.systemDefault());
long daysDifference = ChronoUnit.DAYS.between(objDtTm, currentDate);
Update 2
As pointed out by Ole V.V in the comments, to handle Time Zone issues that may occur , calculating the difference using Instant is a better approach.
Instant now = Instant.now();
long daysDifference = obj.getSelectedDate()
.toInstant()
.until(now, ChronoUnit.DAYS);
I agree with Pallavi Sonal’s answer that when you can use the modern java.time classes, you should keep your use of the oldfashioned classes like Date to an absolute minimum. I don’t know JXDatePicker, but I see that its getDate method returns a Date. So the first thing you should do with this is convert it to a more modern thing.
It may seem from your question that in this case you are only concerned with days, not times. If this is correct, Pallavi Sonal is also correct that LocalDate is the correct class for you. I think that this conversion should work for you
LocalDate selectedDate = jXDatePicker.getDate()
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
This is with a bit of reservation for time zone issues since I don’t know in which time zone the date picker is giving you the date. Once you know that, you can fill in the correct time zone instead of ZoneId.systemDefault().
Unfortunately I am not aware of a date picker component that can give you a LocalDate directly. There could well be one, I hope there is, so it’s probably worth searching for one.
I'm new to the java.time formats in Java 8 and later, but I'm reasonably comfortable with Joda-Time and I'm very familiar with Java's java.util.Date, java.util.Calendar, and DateFormat classes along with ISO 8601.
I'm using PostgreSQL 9.3 with jOOQ 3.6.4, with a foo table column containing a timestamp:
bar timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
I retrieve that bar value using jOOQ, and try to print it out using java.time's DateTimeFormatter.ISO_OFFSET_DATE_TIME:
DateTimeFormatter timestampFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
Cursor<FooRecord> fooRecordCursor = createDSLContext().selectFrom(FOO).fetchLazy();
for(FooRecord fooRecord : fooRecordCursor) {
System.out.println(timestampFormatter.format(fooRecord.getBar().toInstant());
}
This throws an UnsupportedTemporalTypeException:
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: Year
at java.time.Instant.getLong(Instant.java:608)
at java.time.format.DateTimePrintContext$1.getLong(DateTimePrintContext.java:205)
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2543)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1745)
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1719)
But if I use my own custom ISO8601DateFormat which extends the old-school SimpleDateFormat, I can parse the value just fine:
final DateFormat timestampFormatter = new ISO8601DateFormat();
...
System.out.println(timestampFormatter.format(fooRecord.getBar());
I find this very confusing:
The jOOQ bar field accessor returns a java.sql.Timestamp. In the Java 8 version I convert that to an Instant, but why would that reduce the amount of information available?
An Instant is supposed to be an absolute point of time --- isn't it simply based upon a long offset just like JodaTime and Java Date?
Why is DateTimeFormatter.ISO_OFFSET_DATE_TIME expecting a Year field from an Instant? Shouldn't the formatter just convert the long offset to a date/time in the current time zone, and retrieve the year from that? I wouldn't expect any instant to contain a Year field.
In short: If my SimpleDateFormat-based ISO8601DateFormat works fine for a Timestamp from PostgreSQL, why can't DateTimeFormatter.ISO_OFFSET_DATE_TIME figure out how to format the Instant version of the same value?
Short answer:
It's because the java.time classes are separating the concepts "point in time" and "time as a human sees it" whereas Timestamp/Date don't.
Long answer:
You are right, an Instant is representing a single point in the time line. That's why it is not possible to give a correct/unique answer to the question "what's the year/day/time?". It depends on where on the world the question is asked: In New York it differs from Sidney.
But your DateTimeFormatter is asking exactly this question. And that is why you get an UnsupportedTemporalTypeException.
Date and its subclass Timestamp on the other hand are mixing up the two concepts. While internally storing a long "point in time", they "answer" if asked for their year. Usually they are assuming the local time zone of the system to pin the the long-offset to an specific time zone.
This is error-prone and let to introduction of Calendar, JodaTime and java.time.
Now, why is your DateTimeFormatter not smart enough to align Instant to the default TimeZone?
DateTimeFormatter works on the TemporalAccessor interface and does not differ between concrete implementations like Instant, LocalDateTime or ZonedDateTime. There are legitimate formatting cases for all of those implementations and I assume it is simply not feasible to check the concrete object's compatibility with the given format let alone to perform correct conversions.
The solution:
You have to align your timestamp to a timezone/offset yourself:
System.out.println(timestampFormatter.format(
fooRecord.getBar().toLocalDateTime().atZone(ZoneId.systemDefault()));