How to convert string to get java.sql.Timestamp using DateTimeFormatter - java

I have a timestamp in string and I am using DateTimeFormatter to parse the string as below and assign that to a timestamp type variable
import java.sql.Timestamp
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAccessor
import java.time.Instant
String myTime = "2020-08-03T20:15:49"
String myTimeFormat = "yyyy-MM-dd'T'HH:mm:ss"
DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern(myTimeFormat);
TemporalAccessor ta = timestampFormatter.parse(tempValue);
// getting error that Cannot create Instant from java.time.format.Parsed
Timestamp finalTime = Timestamp.from(Instant.from(ta));
How to convert it to java.sql.Timestamp?
Context: I am trying to convert a string column in a spark dataframe (using timestamp format) to a timestamp column and for which I am using the above logic in my udf (using udf as I need to perform other checks in addition to just casting) and thus trying to convert to Timestamp to apply the spark schema with this column as Timestamp
Ref: https://spark.apache.org/docs/latest/sql-ref-datatypes.html

You do not need a DateTimeFormatter
The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards.
Once parsed into LocalDateTime, you can obtain java.sql.Timestamp using Timestamp#valueOf.
Demo:
import java.sql.Timestamp;
import java.time.LocalDateTime;
public class Main {
public static void main(String[] args) {
String myTime = "2020-08-03T20:15:49";
LocalDateTime ldt = LocalDateTime.parse(myTime);
Timestamp finalTime = Timestamp.valueOf(ldt);
System.out.println(finalTime);
}
}
Output:
2020-08-03 20:15:49.0
ONLINE DEMO
Note: The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. Since java.sql.Timestamp extends java.util.Date, it inherits the same drawbacks. It is recommended to stop using them completely and switch to the modern Date-Time API*. Check this answer and this answer to learn how to use java.time API with JDBC.
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Your issue isn't that you can't create a Timestamp: it's that you can't create an Instant.
An Instant identifies a single point on the timeline, identified via an offset from Unix epoch (1970-1-1 00:00:00 UTC).
The problem with your input is that it doesn't identify a timezone. As such, it doesn't uniquely identify a single point in time, because 2020-08-03T20:15:49 isn't the same instant in London vs New York vs Shanghai vs Delhi (for example).
As such: parse your string to a LocalDateTime; then specify the time zone; then convert to an Instant; then convert to a Timestamp:
LocalDateTime ldt = LocalDateTime.parse(myTime, timestampFormatter);
// Or whichever time zone.
Instant instant = ldt.atZone(ZoneId.of("UTC")).toInstant();
Timestamp finalTime = Timestamp.from(instant);

What is a timestamp?
Wikipedia defines a timestamp as
a sequence of characters or encoded information identifying when a
certain event occurred …
So your string is not a timestamp or at least can only be considered one if we know which time zone (or UTC offset) is assumed for the date and time of day it contains. Today most IT systems are not confined to one time zone, so it is generally recommended to include UTC offset information with your timestamps and/or keep them in UTC. I am assuming that you asked for an old-fashioned java.sql.Timestamp for use with your SQL database. Depending on your adherence to the recommendations you will need different types both in SQL and in Java.
Recommended: Use explicit offset, preferably UTC. With most database engines this means using its timestamp with time zone or timestamptz data type. The JDBC standard since 4.2 says that you should then use OffsetDateTime in Java. Many drivers also handle Instant, the class we would normally prefer for an unambiguous timestamp in Java. Your attempt to create an Instant may hint that this agrees with your intentions.
Not recommended: Use implicit offset, preferably UTC. Many old applications would use their own time zone. In any case use timestamp (without time zone) in SQL and LocalDateTime in Java. The lack of offset in your string may hint that this was your approach.
The java.sql.Timestamp class that you mentioned is poorly designed, in fact a true hack on top of the already poorly designed java.util.Date class. So Timestamp is not among the classes I recommend for sending your tiemstamp value to the database, whether for storage or for use in a query.
Saving your timestamp to SQL
Here’s a code example using OffsetDateTime and a custom assumed time zone.
String myTime = "2020-08-03T20:15:49";
OffsetDateTime odt = LocalDateTime.parse(myTime)
.atZone(ZoneId.of("Asia/Colombo"))
.toOffsetDateTime();
PreparedStatement ps = yourDatabaseConnection
.prepareStatement("insert into your_table(your_timestamptz_column) values (?);");
ps.setObject(1, odt);
ps.executeUpdate();
Since JDBC 4.2 the setObject method accepts java.time types including OffsetDateTime.
If your time string is UTC, the conversion to OffsetDateTime is a bit simpler:
OffsetDateTime odt = LocalDateTime.parse(myTime).atOffset(ZoneOffset.UTC);
Using Instant: You may convert the OffsetDateTime from before simply:
Instant inst = odt.toInstant();
Now you can pass inst to setObject() in the same way that we passed odt before.
If you are using timestamp without time zone in SQL and LocalDateTime in Java, the answer by Arvind Kumar Avinash already shows the simple way to parse your string. Also a LocalDateTime can be passed to setObject() in the same way as above.
By the way, we most often neither need to nor want to use the TemporalAccessor interface. Its documentation says:
This interface is a framework-level interface that should not be
widely used in application code. …

Related

Why does Instant.EPOCH and Timestamp.from(Instant.EPOCH) return different results? [duplicate]

This question already has answers here:
Converting java.util.Date to java.sql.Timestamp results into wrong value
(2 answers)
Convert java.time.Instant to java.sql.Timestamp without Zone offset
(5 answers)
Closed 1 year ago.
Using java.time's Instant and java.sql's Timestamp, why do these:
System.out.println("epoch: " + Instant.EPOCH);
System.out.println("timestamp: " + Timestamp.from(Instant.EPOCH));
produce different results:
epoch: 1970-01-01T00:00:00Z
timestamp: 1970-01-01 01:00:00.0
The Timestamp class injects the JVM‘s current default time zone. One of many reasons why you should never use these legacy date-time classes. Use only their replacement, the java.time classes. Sun, Oracle, and the JCP community gave up on these wretched date-time classes such as Date, Calendar, and Timestamp with their unanimous adoption of JSR 310.
Your JVM’s current default time zone on the first moment of 1970 as seen in UTC was using an offset-from-UTC of one hour ahead. Hence the 1 AM display, adding one hour to the same simultaneous moment with a time of 00:00 seen in places such as Iceland whose offset from UTC on that date at that time was zero hours-minutes-seconds.
Another problem with that output from Timestamp#toString is that it neglects to indicate its time zone or offset-from-UTC. In contrast, the java.time.Instant#toString method puts a Z on the end to indicate, per ISO 8601 standard, an offset of zero hours-minutes-seconds.
The Instant class represents a moment in UTC, always UTC, an offset of zero. So no such confusion and ambiguity as with Timestamp.
Why does Instant.EPOCH and Timestamp.from(Instant.EPOCH) return
different results?
Echoing Stultuske's comment:
they don't, it's just the formatting that is different.
Let's see how
Given below is the hierarchy of java.sql.Timestamp:
java.lang.Object
java.util.Date
java.sql.Timestamp
On the same page of the documentation, you will find the following information:
The inheritance relationship between Timestamp and java.util.Date
really denotes implementation inheritance, and not type inheritance.
The implementation inheritance is a way of reusing the code of the superclass in a subclass and type inheritance is a way of specializing (subclassing) a supertype for a specific implementation**.
The purpose of creating java.sql.Timestamp has been summarized in the following line on the same page of the documentation:
A thin wrapper around java.util.Date that allows the JDBC API to
identify this as an SQL TIMESTAMP value.
So, do not expect java.sql.Timestamp to be much different from java.util.Date.
About java.util.Date:
A java.util.Date object simply represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). Since it does not hold any timezone information, its toString function applies the JVM's timezone to return a String in the format, EEE MMM dd HH:mm:ss zzz yyyy, derived from this milliseconds value. To get the String representation of the java.util.Date object in a different format and timezone, you need to use SimpleDateFormat with the desired format and the applicable timezone e.g.
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
String strDateNewYork = sdf.format(date);
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
String strDateUtc = sdf.format(date);
On the other hand, An Instant represents an instantaneous point on the timeline in UTC.
Putting all in a demo:
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) {
System.out.println("Instant: " + Instant.EPOCH);
System.out.println("Timestamp: " + epochMilliFormatted());
}
public static String epochMilliFormatted() {
Date date = new Date(Timestamp.from(Instant.EPOCH).getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
return sdf.format(date);
}
}
Output:
Instant: 1970-01-01T00:00:00Z
Timestamp: 1970-01-01T00:00:00Z
The Z in the output is the timezone designator for a zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).
ONLINE DEMO
Note: The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*. Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
** To learn more about the implementation inheritance and the type inheritance, check the following links:
Multiple Inheritance of State, Implementation, and Type
implementation inheritance vs type inheritance
difference between interface inheritance and implementation inheritance

My xmlGregorianCalendar coming with offset value ..how to adjust it? in java

My xmlGregorianCalendar value coming as 2020-10-02T13:07:38-06:00 .. I want to pass thisxmlGregorianCalendar value and get the output like 2020-10-02T07:07:38(which reduces 6 hours from the time) any suggestion on this please?
I have used this below method.
return new Timestamp(xmlGregorianCalendar.toGregorianCalendar(TimeZone.getTimeZone("GMT"),null,null).getTimeInMillis())..
But it removing the off set value but not adjusting the time. the output i am getting is 2020-10-02T13:07:38..But i am expecting the 2020-10-02T07:07:38 like this.
First of all, I recommend you switch from the outdated and error-prone java.util date-time API to the modern java.time date-time API. Learn more about the modern date-time API from Trail: Date Time.
Your understanding of Zone-Offset is not correct
The date-time string, 2020-10-02T13:07:38-06:00 tells us that the given date and time has been adjusted with an offset of -06:00 hours from UTC i.e. the corresponding date-time at UTC would be 2020-10-02T19:07:38Z where Z specifies a Zone-Offset of 00:00 hours.
It means that if you are expecting a date-time of 2020-10-02T07:07:38, you need to offset the given date-time further by -06:00 hours i.e. it will be at a total of -12:00 hours offset from UTC.
The following example illustrates this concept:
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
OffsetDateTime odtGiven = OffsetDateTime.parse("2020-10-02T13:07:38-06:00");
System.out.println(odtGiven);
// Date and time at Zone-Offset of 00:00 hours
OffsetDateTime odtUTC = odtGiven.withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(odtUTC);
// Date and time at Zone-Offset of -12:00 hours
OffsetDateTime odtDerived = odtGiven.withOffsetSameInstant(ZoneOffset.of("-12:00"));
System.out.println(odtDerived);
// Get the date-time string in the format with Zone-Offset dropped
String strDateTimeZoneOffsetDropped = odtDerived.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println(strDateTimeZoneOffsetDropped);
}
}
Output:
2020-10-02T13:07:38-06:00
2020-10-02T19:07:38Z
2020-10-02T07:07:38-12:00
2020-10-02T07:07:38
Using the legacy API:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
public class Main {
public static void main(String[] args) throws DatatypeConfigurationException, ParseException {
String givenDateTimeString = "2020-10-02T13:07:38-06:00";
XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(givenDateTimeString);
System.out.println(xmlGregorianCalendar);
// Derive the date-time string at Zone-Offset of UTC-12:00 hours
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT-12:00"));
Date date = xmlGregorianCalendar.toGregorianCalendar().getTime();
String derivedDateTimeString = sdf.format(date);
System.out.println(derivedDateTimeString);
}
}
Output:
2020-10-02T13:07:38-06:00
2020-10-02T07:07:38
JDBC 4.2 and java.time
Arvind Kumar Avinash has already nicely explained how the offset works (what XMLGregorianCalendar misleadingly calls a time zone). He has also already recommended java.time, the modern Java date and time API. I should like to elaborate on this recommendation. Assuming that you thought you wanted a java.sql.Timestamp for your SQL database, you should not want that. Since JDBC 4.2 and Hibernate 5 we can seamlessly transfer the types of java.time to our SQL databases. Which I recommend that you do instead.
If using timestamp with time zone in SQL, use OffsetDateTime in UTC in Java
If the datatype in SQL is timestamp with time zone (recommended for timestamps), transfer an OffsetDateTime from Java to it. To most database engines timestamp with time zone really means timestamp in UTC, so for clarity I prefer to pass an OffsetDateTime that is in UTC too. There are a couple of ways to convert, each with their pros and cons.
1. Convert via GregorianCalendar.
// For demonstration build an XMLGregorianCalenadr equivalent to yours
XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance()
.newXMLGregorianCalendar("2020-10-02T13:07:38-06:00");
// Convert to OffsetDateTime for your SQL database
OffsetDateTime dateTime = xmlGregorianCalendar.toGregorianCalendar()
.toZonedDateTime()
.toOffsetDateTime()
.withOffsetSameInstant(ZoneOffset.UTC);
// Show what we’ve got
System.out.println(dateTime);
Output is:
2020-10-02T19:07:38Z
Pro: it’s the official conversion.
Or 2. Convert via String.
OffsetDateTime dateTime = OffsetDateTime.parse(xmlGregorianCalendar.toString())
.withOffsetSameInstant(ZoneOffset.UTC);
Pro: it’s short, and I don’t think it gives any surprises. Con: To me it feels like a waste to format into a string and parse back into a date-time object again.
3. Convert via int. You may convert by taking the individual fields out of the XMLGregorianCalendar and building an OffsetDateTime from them. It’s getting long-winded, handling fraction of second is a bit complicated, and there’s a risk of accidentally swapping fields, so I don’t recommend it.
How to pass to SQL. Here’s an example of transferring the OffsetDateTime to SQL.
String sqlString = "insert into your_table(your_timestamp_column) values (?);";
PreparedStatement ps = yourDatabaseConnection.prepareStatement(sqlString);
ps.setObject(1, dateTime);
int rowsInserted = ps.executeUpdate();
If using timestamp without time zone in SQL, instead pass a LocalDateTime from Java
If the datatype in SQL is timestamp without time zone (not recommended for timestamps, but often seen), you need to pass a LocalDateTime from Java. Again there are a couple of ways to convert. I am showing just one:
LocalDateTime dateTime = xmlGregorianCalendar.toGregorianCalendar()
.toZonedDateTime()
.withZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
I am using the default time zone of the JVM in agreement with what the old-fashioned Timestamp class did, so the result is time zone dependent. In my time zone (Europe/Copenhagen) currently at UTC offset +02:00, the result is:
2020-10-02T21:07:38
What went wrong in your code?
The three-arg XMLGregorianCalendar.toGregorianCalendar​(TimeZone, Locale, XMLGregorianCalendar) that you were using is designed to introduce bugs like the one you observed. It promises to construct a GregorianCalendar with the specified time zone (if one is given) and with the field values from the XMLGregorianCalendar (year, month, day, hour, minute, etc.). So the documentation says it quite clearly: if you specify a time zone that does not agree with the offset of the XMLGregorianCalendar, it gives you a different point in time than the one specified by the XMLGregorianCalendar. It might occasionally be useful if the XMLGregorianCalendar hadn’t got any offset and we knew which time zone was intended. But your XMLGregorianCalendar has got offset, so this method definitely is not what you want to use.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of the three-arg XMLGregorianCalendar.toGregorianCalendar​(TimeZone, Locale, XMLGregorianCalendar)

how to convert a java timestamp to a string respecting the timezone with nanosecond resolution

I want to convert a timestamp to a string given a timezone argument (in Java). From the code I'm looking at internally the timestamp has nanosecond precision (or at least one can convert it to nanoseconds and I don't care since the output format I want to generate isn't that precise).
The DateFormat allows an S specifier and will apply a TimeZone, but it seems to do only 3 digits (milliseconds?) precision. Using toString gives 6 digits of precision within the seconds (microseconds?) but doesn't seem to take a TimeZone argument.
I need something that does both, allows a TimeZone specifier and gives 6 digits of precision within a second.
What to use?
I am assuming you mean a java.sql.Timestamp that you are getting from an SQL database where it was a timestamp either with or without time zone.
Use java.time
First, you don’t need that. The Timestamp class is poorly designed and long outdated. Instead prefer to get from your database:
If your database column is a timestamp with time zone, which it should be, then get an OffsetDateTime (with some JDBC drivers an Instant works too).
If your database column is timestamp without time zone (not recommended), then get a LocalDateTime. The problem with this is that a LocalDateTime is a date and time without time zone, so not a unique point in time, so unsuited for a timestamp.
The mentioned types are all from java.time, the modern Java date and time API, and they all have nanosecond precision.
Example:
OffsetDateTime odt = yourResultSet.getObject("my_timestamp_col", OffsetDateTime.class);
System.out.println(odt);
Example output:
2018-11-29T22:34:56.123456789Z
The trailing Z in the output means Zulu time zone, UTC or offset zero.
You wanted to apply a given time zone. So for example:
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("America/Guyana"));
System.out.println(zdt);
2018-11-29T18:34:56.123456789-04:00[America/Guyana]
Getting a type from java.time from the result set using ResultSet.getObject requires JDBC 4.2 (or a modern JPA implementation or what you use for data access). Most of us have that.
Handling a Timestamp from a legacy API
In case you either haven’t got JDBC 4.2 yet or you are getting the Timestamp from a legacy API that you can’t afford to change just now:
ZonedDateTime zdt = yourTimestamp.toInstant()
.atZone(ZoneId.of("America/Guyana"));
If you have specific requirements for your string output, use a DateTimeFormatter to format your ZonedDateTime. This is described in many places, just search.
Link
Oracle tutorial: Date Time explaining how to use java.time.

How to parse a String YYYYMMDD_HHMMSSZ in Java 8

I need to parse a UTC date and time string, e.g. 20180531_132001Z into a Java 8 date and time object. How do I go about doing this using Java 8's new date and time libraries? Most examples I see is for LocalDateTime, like this:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss'Z'");
LocalDateTime localDateTime = LocalDateTime.parse("20180531_132001Z", formatter);
System.out.println(localDateTime);
System.out.println(localDateTime.atOffset(ZoneOffset.UTC));
The code outputs:
2018-05-31T13:20:01
2018-05-31T13:20:01Z
Is this considered local time or UTC time? The string value I am parsing is based on UTC, so I am wondering if I need to do anything further before persisting to the database.
If the former, how do I convert that to UTC date and time?
I ultimately need to persist this to a SQL Server database table (column type is [datetime2](7), using [Spring] JDBC.
Update: Based on the comments and answers, I think my question is not well thought out. Putting it another way, if I get an input string and I parse it without factoring any zone or offset, I will get a LocalDateTime object. How do I take that object and convert the encapsulated value to UTC date and time?
LocalDateTime can be misleading. It doesn't represent your local date/time, it represents a local date/time.
It carries no time zone info at all.
That is, it just says for example "it's 13:20". It doesn't say where it's 13:20. It's up to you to interpret the where part.
Due to this LocalDateTime is usually not very useful for carrying timestamps, it's only useful for situations when the timezone is dependent on some context.1
When working with timestamps it's better to use ZonedDateTime or OffsetDateTime instead. These carry the date, time and offset.
So localDateTime.atOffset(ZoneOffset.UTC) will actually return an instance of OffsetDateTime, by interpreting localDateTime as UTC time.
One could argue that you can avoid the interpreting part by parsing with the timezone info in the first place (even though it's always Z):
String example = "20180531_132001Z";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssX");
OffsetDateTime dateTime = OffsetDateTime.parse(example, formatter);
System.out.println(dateTime); // look ma, no hardcoded UTC
Will print:
2018-05-31T13:20:01Z
The added value is that your code automatically supports timezones (e.g. "20180531_132001+05").
JDBC 4.2 compliant driver may be able to directly address java.time types by calling setObject.
For older JDBC drivers you can convert dateTime to a java.sql.Timestamp or java.util.Date:
java.sql.Timestamp.from(dateTime.toInstant());
java.util.Date.from(dateTime.toInstant());
1 There is almost always some context in which LocalDateTime operates. For example "Flight KL1302 arrives at airport X tomorrow at 13:20". Here the context of "tomorrow at 13:20" is the local time at airport X; it can be determined by looking up the time zone of X.
tl;dr
myPreparedStatement.setObject( // Pass java.time objects directly to database, as of JDBC 4.2.
… , // Indicate which placeholder in your SQL statement text.
OffsetDateTime.parse( // Parse input string as a `OffsetDateTime` as it indicates an offset-from-UTC but not a time zone.
"20180531_132001Z" , // Define a formatting pattern to match your particular input.
DateTimeFormatter.ofPattern( "uuuuMMdd_HHmmssX" ) // TIP: When exchanging date-time values as text, use use standard ISO 8601 formats rather than inventing your own.
) // Returns a `OffsetDateTime` object.
.toInstant() // Returns a `Instant` object, always in UTC by definition.
)
Details
There is some helpful information in the other Answers, but all of them have some misinformation which I tried to correct by posting comments.
Most importantly, your code is using the wrong Java class and the wrong database data type for that given input.
Below is explanation along with a complete code example, using the modern java.time classes with JDBC 4.2 or later.
Z = UTC
DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss'Z'")
Never put single-quotes around vital parts of your input such as you did here with Z. That Z means UTC and is pronounced “Zulu”. It tells us the text of the date and time-of-day should be interpreted as using the wall-clock time of UTC rather than, say, America/Montreal or Pacific/Auckland time zones.
Do not use the LocalDateTime for such inputs. That class lacks any concept of time zone or offset-from-UTC. As such, this class does not represent a moment, and is not a point on the timeline. A LocalDateTime represents the set of potential moments along a range of about 26-27 hours (across all time zones). Use LocalDateTime when you mean any or all time zones rather than one particular zone/offset. In contrast, the Z tells us this input uses the wall-clock time of UTC specifically.
Parsing
Define a formatting pattern to match all important parts of your input string.
String input = "20180531_132001Z" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuuMMdd_HHmmssX" ) ;
By the way, whenever possible, use standard ISO 8601 formats rather than a custom format as seen in your Question. Those formats are wisely designed to be easy to parse by machine and easy to read by humans across cultures while eliminating ambiguity.
Parse as a OffsetDateTime because your input indicates an offset-from-UTC (of zero hours). An offset-from-UTC is merely a number of hours and minutes, nothing more, nothing less.
Use the ZonedDateTime class only if the input string indicates a time zone. A time zone has a Contintent/Region name such as Africa/Tunis. A zone represents the history of past, present, and future changes in the offset used by the people of a particular region.
OffsetDateTime odt = OffsetDateTime.parse( input , f ) ;
odt.toString(): 2018-05-31T13:20:01Z
Database
To communicate this moment to a database using JDBC 4.2 and later, we can directly pass the java.time object.
myPreparedStatement.setObject( … , odt ) ;
If your JDBC driver does not accept the OffsetDateTime, extract the simpler class Instant. An Instant is in UTC always, by definition.
Instant instant = odt.toInstant() ;
myPreparedStatement.setObject( … , instant ) ;
And retrieval.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Beware - Wrong datatype in your database
I am not a MS SQL Server user, but according to this documentation, the column data type DATETIME2 is not appropriate to your input. That data type seems to be equivalent to the SQL-standard type DATETIME WITHOUT TIME ZONE. Such a type should never be used when recording a specific moment in history.
Lacking any concept of time zone or offset-from-UTC, that column type should only be used for three situations:
The zone or offset is unknown.This is bad. This is faulty data. Analogous to having a price/cost without knowing the currency. You should be rejecting such data, not storing it.
The intention is “everywhere”, as in, every time zone.Example, a corporate policy that states “All our factories will break for lunch at 12:30" means the factory in Delhi will break hours before the factory in Düsseldorf which breaks hours before the factory in Detroit.
A specific moment in the future is intended, but we are afraid of politicians redefining the time zone.Governments change the rules of their time zones with surprising frequency and with surprisingly little warning (even [no warning at all][10]). So if you want to book an appointment at 3 PM on a certain date, and you really mean 3 PM regardless of any crazy decision a government might make in the interim, then store a LocalDateTime. To print a report or display a calendar, dynamically apply a time zone (ZoneId) to generate a specific moment (ZonedDateTime or Instant). This must be done on-the-fly rather than storing the value.
Since your input is a specific moment, a certain point on the timeline, you should be storing it in the database using a column type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE.
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.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
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.
Maybe this can help you.
public static void main(String... strings) {
OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(utc.toString());
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy mm dd hh:mm a");
System.out.println(utc.format(format));
}
While you certainly can use LocalDateTime and format it to look like a zoned date time using offset, it would be easier to use an Object designed to store time zone.
ZonedDateTime zonedDateTime = ZonedDateTime.parse("20180531_132001Z", DateTimeFormatter.ofPattern("yyyMMdd_HHmmssX"));
This gives you the option to use Instant to convert to SQL timestamp or any other format without having to hard-code the time zone, especially if time zone is added in the future or changes.
java.sql.Timestamp timestamp = new java.sql.Timestamp(zonedDateTime.toInstant().toEpochMilli());
You can view the timestamp's instant and compare it to the toString, which should be pegged to your timezone, and instant.toString, which pegs to UTC.
System.out.print(timestamp + " " + timestamp.toInstant().toString());
this should do the trick to parse string to LocalDateTime :
String example = "20180531_132001Z";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssX");
ZonedDateTime dateTime = ZonedDateTime.parse(example, formatter);
See that code run live in IdeOne.com.
dateTime.toString(): 2018-05-31T13:20:01Z
Timestamp timestamp = Timestamp.from(dateTime.toInstant());
Timestamp then is saved into db

Formatting java.time Instant values from PostgreSQL Timestamp values

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()));

Categories

Resources