ZoneOffset ZoneId custom serialization - java

What is the best way to serialize ZoneId or ZoneOffset from format +03:00 to format +03.00 using Jackson? Or may be there is another way how to change : to .

The zone offset, +03:00 is already in the ISO 8601 standard format. Therefore, you should educate the publisher/consumer to stick to it. However, if you want to change it in the desired format for any reason, here is how you can do it:
import java.time.ZoneOffset;
class Main {
public static void main(String[] args) {
ZoneOffset offset = ZoneOffset.of("+03:00");
String formatted = offset.toString().replace(':', '.');
System.out.println(formatted);
}
}
Output:
+03.00
Learn more about the modern Date-Time API from Trail: Date Time.

To resolve this tak I made custom serializer:
object ZoneOffsetSerializer : JsonSerializer<ZoneOffset>() {
override fun serialize(value: ZoneOffset, jsonGenerator: JsonGenerator, serializers: SerializerProvider) {
val result = "0".takeIf { value.totalSeconds == 0 } ?: value.toString().replace(':', '.')
jsonGenerator.writeString(result)
}
}
and used annotation #JsonSerialize(using = ZoneOffsetSerializer::class) for necessary fields

Related

Convert timestamp GMT+1 to europe/brussels timezone

I'm receiving this timestamp in a json body:
{
"timestamp": "2019-03-27 10:04:01.446937+01"
}
And I would like to convert this timestamp into europe/brussels timezone.
I'm using com.fasterxml.jackson.core so I'm wondering if this is possible with annotations in this class for example.
public class MyClass {
#JsonFormat(...)
Date timestamp;
}
If not how can this be achieved using plain java code?
ISO 8601
If possible, educate the publisher of your data about using standard ISO 8601 formats when exchanging date-time values textually. That means:
Using a T in the middle instead of a SPACE character.
Using hours with minutes in the offset, delimited by a COLON character, rather than abbreviating.
DateTimeFormatter
If switching to ISO 8601 is not possible, define a formatting pattern to match your input.
String input = "2019-03-27 10:04:01.446937+01" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd HH:mm:ss.SSSSSSx" ) ;
java.time.OffsetDateTime
Parse as an OffsetDateTime.
OffsetDateTime odt = OffsetDateTime.parse( input , f ) ;
odt.toString(): 2019-03-27T10:04:01.446937+01:00
ZonedDateTime
Apply your desired time zone.
ZoneId z = ZoneId.of( "Europe/Brussels" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
See this code run at Ideone.com.
zdt.toString(): 2019-03-27T10:04:01.446937+01:00[Europe/Brussels]
In this particular case, Brussels time is already using an offset of one hour ahead of UTC. So no change for the time-of-day from our original.
Extracts the "timestamp" field in the JSON object as String. Then use the java.text.SimpleDateFormat with the correct formatter to parse the string into java.util.Date object. Then change to the required timezone before displayed it back into JSON string.
Here's the plain Java solution. I guess there's more elegant way for this.
static {
// make sure your JVM's timezone won't be "combined" with the input's timezone.
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
public class DateHandler extends StdDeserializer<Date> {
public DateHandler() {
this(null);
}
public DateHandler(Class<?> clazz) {
super(clazz);
}
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
String date = jsonParser.getText();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSZ"); // or DateTimeFormatter.ISO_OFFSET_DATE_TIME
return sdf.parse(date+"00");
} catch (Exception e) {
log.error(e, e);
return null; // TODO throw new JsonParseException..
}
}
}
and instead of your #JsonFormat(...)
do #JsonDeserialize(using = DateHandler.class)
Now new ObjectMapper().readValue("{ \"timestamp\": \"2019-03-27 10:04:01.446937+01\"}", MyClass.class)
returns a date with +0100 offset as required.

How to parse UTC timestamp of format yyyy-MM-ddTHH:mm:ss.SSS+ZZZZ

I have timestamp as 2020-12-03T05:35:59.398+0000 in String format being recieved in a streaming batch.
I want only 2020-12-03 05:35:59 as java.sql.Timestamp instance in order to be able to compare it with other Timestamp instance.
Getting following error with Timestamp.valueOf() function:
Exception in thread "main" java.time.format.DateTimeParseException : Text '2020-12-03T05:35:59.398+0000' could not be parsed at index 23
I tried the answer given here , and conversion did happen but the time was changed to 2020-12-03 11:05:59
I have tried changing between the formats given here but still no solution.
Is there even a format for timestamp with wierd + in between 398+0000?
java.time
I recommend that you use java.time, the modern Java date and time API, for your date and time work.
DateTimeFormatter isoFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSxx");
DateTimeFormatter sqlTimestampFormatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter();
String aTimestampString = "2020-12-03T05:35:59.398+0000";
String anotherTimestampString = "2020-12-04 06:43:58.556385";
Instant anInstant = isoFormatter.parse(aTimestampString, Instant::from);
Instant anotherInstant = LocalDateTime.parse(anotherTimestampString, sqlTimestampFormatter)
.atOffset(ZoneOffset.UTC)
.toInstant();
if (anInstant.isBefore(anotherInstant)) {
System.out.println(aTimestampString + " is earlier");
} else {
System.out.println(anotherTimestampString + " is earlier");
}
Output from this example is:
2020-12-03T05:35:59.398+0000 is earlier
The +0000 in the former string above is an offset from UTC — an offset of 00 hours 00 minutes. Since it is zero, we know that the time is in UTC. I don’t know the time zone or UTC offset of the other string. You need to know, or you will get incorrect results. In the code above I have assumed that the other string is in UTC too.
Don’t use Timestamp
I tried the answer given here, and conversion did happen but the time
was changed to 2020-12-03 11:05:59
This is how confusing the Timestamp class is. You got the correct timestamp value. What happens when you print the Timestamp object, is that you are (implicitly or explicitly) calling its toString method. Timestamp.toString() confusingly uses the default time zone of your JVM for rendering the string. So if your timestamp is equal to 2020-12-03T05:35:59.398 UTC and your time zone is, say, Asia/Kolkata, then time is converted to Asia/Kolkata time zone and the string 2020-12-03 11:05:59 is returned and printed.
You have nothing good to use the old-fashioned java.sql.Timestamp class for. It was originally meant for transferring timestamp values with and without time zone to and from SQL databases. Since JDBC 4.2 we prefer OffsetDateTime, Instant and LocalDateTime for that purpose. So just forget about the Timestamp class.
Link
Oracle tutorial: Date Time explaining how to use java.time.
Is there even a format for timestamp with wierd + in between 398+0000?
The 398 part is fraction-of-second (millisecond) while the +0000 part is the zone offset part.
You can parse 2020-12-03T05:35:59.398+0000 into an OffsetDateTime using the format pattern, uuuu-MM-dd'T'HH:mm:ss.SSSX.
Demo:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String text = "2020-12-03T05:35:59.398+0000";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(text, formatter);
System.out.println(odt);
}
}
Output:
2020-12-03T05:35:59.398Z
Check the DateTimeFormatter documentation page to learn more about the letters used for formatting.
You can use isBefore and isAfter functions of OffsetDateTime to compare its two instances.
Demo:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String text = "2020-12-03T05:35:59.398+0000";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(text, formatter);
OffsetDateTime odtNow = OffsetDateTime.now();
System.out.println(odtNow.isBefore(odt));
System.out.println(odtNow.isAfter(odt));
}
}
Output:
false
true
Learn more about the modern date-time API at Trail: Date Time. 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.
The date-time API of java.util 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. Since java.sql.Timestamp extends java.util.Date, it is recommended to stop using that as well. However, for any reason, if you still want to use conversion between the modern and the legacy date-time API, use Instant as the bridge.
import java.sql.Timestamp;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String text = "2020-12-03T05:35:59.398+0000";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(text, formatter);
Instant instant = odt.toInstant();
Timestamp timestamp = new Timestamp(instant.toEpochMilli());
System.out.println(timestamp);
}
}
Output:
2020-12-03 05:35:59.398
You can use a custom DateFormatter for non-standard formats. Here is a working example for your use case.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import static java.time.temporal.ChronoField.*;
public class Main {
private static final DateTimeFormatter INPUT_NON_STANDARD_FORMAT;
static {
INPUT_NON_STANDARD_FORMAT =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.optionalStart()
.appendLiteral('.')
.appendValue(MILLI_OF_SECOND, 3)
.appendLiteral("+0000")
.toFormatter();
}
public static void main(String[] args) {
String timestamp = "2020-12-03T05:35:59.398+0000";
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(timestamp, INPUT_NON_STANDARD_FORMAT);
System.out.println(localDateTime.format(dateTimeFormatter));
}
}
Output
2020-12-03 05:35:59

Get the Gregorian date from a java.sql.Timestamp

i have executed a query with entity manager.
it’s returning data from multiple tables. in that case i am getting list of object arrays. so i have written a loop as below.
for (Object[] row : rows) {
row[0]; //row[0] has the date from database as timestamp.
}
here row[0] is a sql Timestamp.
if we evaluate with debugging, i am getting Timestamp with result: 2020-05-27 18:37:39.0.
i can see cdate, a private variable which has value as 2020-05-27T18:37:39.000+0530. it’s the Gregorian date.
i can't use it because it’s a private variable and it is of type BaseCalendar.Date.
if you do something and convert it in to UTC, i am getting as 2020-05-27T13:07:39Z
BUT i want it as 2020-05-27T18:37:39.000+0530
I too recommend that you use java.time, the modern Java date and time API. If you can, modify your query not to return an old-fashioned Timestamp object but rather an Instant or at least a LocalDateTime.
Then you may do for example:
// Example row
Object[] row = { Instant.parse("2020-05-27T13:07:39.000Z") };
System.out.println(row[0]);
ZonedDateTime zdt = ((Instant) row[0]).atZone(ZoneId.systemDefault());
System.out.println(zdt);
Output from this example is, when running in Asia/Kolkata time zone:
2020-05-27T13:07:39Z
2020-05-27T18:37:39+05:30[Asia/Kolkata]
If you can’t change the return type from your query, the correct conversion is:
// Example row
Object[] row = { Timestamp.from(Instant.parse("2020-05-27T13:07:39.000Z")) };
System.out.println(row[0]);
ZonedDateTime zdt = ((Timestamp) row[0]).toInstant()
.atZone(ZoneId.systemDefault());
System.out.println(zdt);
Output is:
2020-05-27 18:37:39.0
2020-05-27T18:37:39+05:30[Asia/Kolkata]
Please note: Don’t use any strings for the conversion, a more direct conversion exists.
I have assumed that you were after that value, not necessarily after the same format that you saw in your debugger. In case you did want that format: What you saw was the result of BaseCalendar.Date.toString(), and it’s a variant of ISO 8601 format, the international standard. To obtain it, use a formatter. For example:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendPattern("HH:mm:ss.SSSxx")
.toFormatter();
String formattedDateTime = zdt.format(formatter);
System.out.println(formattedDateTime);
2020-05-27T18:37:39.000+0530
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Use the modern date/time API as follows:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String args[]) {
// Define the format
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
// Parse the date/time string using the defined format
OffsetDateTime odt = OffsetDateTime.parse("2020-05-27T18:37:39.000+0530", formatter);
// Display
System.out.println(odt);
}
}
You can make use of java.time, which enables you to parse the datetime without considering a zone or offset but provides the possibility of adding one afterwards without changing or adjusting the values parsed:
public static void main(String[] args) {
// let's assume you have a timestamp with the following value
Timestamp ts = Timestamp.valueOf("2020-05-27 18:37:39");
// then you can parse it to a datetime that doesn't consider a zone or offset
LocalDateTime ldt = LocalDateTime.parse(ts.toString(),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"));
// print it once
System.out.println(ldt);
// then add an offset without changing / adjusting the time values
OffsetDateTime odt = ldt.atOffset(ZoneOffset.of("+05:30"));
// and print that
System.out.println(odt);
// or use a default format to exactly meet your requirements
System.out.println(odt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
}
This outputs
2020-05-27T18:37:39
2020-05-27T18:37:39+05:30
2020-05-27T18:37:39.000+0530

What I am doing wrong when parsing an ISO8601 date time?

I am expecting the test below to pass. Could someone tell me what I am doing wrong here? Most likely I am using a wrong pattern but I can't see what is wrong.
#Test
public void parseDateTest() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.sss'Z'");
DateTime dt = formatter.parseDateTime("1983-03-06T05:00:03.000Z");
assertEquals("1983-03-06T05:00:03.000Z", dt.toString());
}
FYI dt.toString() does print: 1983-03-06T05:00:00.000Z
Thanks!
P.S.
Please note that on this snippet I rely on the default timezone. This is not production code and how to handle correctly the timezone based on needs is covered by many other questions.
The default time zone is derived from the system property user.timezone. If that is null or is not a valid identifier, then the value of the JDK TimeZone default is converted. If that fails, UTC is used.
This checks to see if "1983-03-06T05:00:03.000Z" is equal to dt.toString(). You say that dt.toString() is equal to "1983-03-06T05:00:00.000Z".
"1983-03-06T05:00:03.000Z" !== "1983-03-06T05:00:00.000Z"
Now the problem is why dt.toString() does not have the correct amount of seconds. Let's look at your DateTimeFormat pattern:
"yyyy-MM-dd'T'HH:mm:ss.sss'Z'"
According to the docs, s is for "second of minute" and S is for "fraction of second" (note the case). This means in your input string, both 03 and 000 are being parsed as seconds (when the later should be fractions) and your DateTime's seconds are being overridden with 00. Try updating this format string:
#Test
public void parseDateTest() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
DateTime dt = formatter.parseDateTime("1983-03-06T05:00:03.000Z");
assertEquals("1983-03-06T05:00:03.000Z", dt.toString());
}
java.time
Quoted below is a notice from the home page of Joda-Time:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
Solution using java.time API: java.time API is based on ISO 8601 and therefore you do not need a DateTimeFormatter to parse a date-time string which is already in ISO 8601 format (e.g. your date-time string, 1983-03-06T05:00:03.000Z).
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
class Main {
public static void main(String[] args) {
String strModifiedDate = "1983-03-06T05:00:03.000Z";
Instant instant = Instant.parse(strModifiedDate);
System.out.println(instant);
// It can also be directly parsed into a ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.parse(strModifiedDate);
System.out.println(zdt);
// or even into an OffsetDateTime
OffsetDateTime odt = OffsetDateTime.parse(strModifiedDate);
System.out.println(odt);
}
}
Output:
1983-03-06T05:00:03Z
1983-03-06T05:00:03Z
1983-03-06T05:00:03Z
Learn more about the modern Date-Time API from Trail: Date Time.
Don't use dt.toString(), use formatter.format(dt) instead. That's what the formatter is for:
#Test
public void parseDateTest() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
LocalDateTime dt = formatter.parse("1983-03-06T05:00:03.000Z", LocalDateTime::from);
assertEquals("1983-03-06T05:00:03.000Z", formatter.format(dt));
}

parse exception in java

I am trying to validate a string whether it is in ISO-8601 date or not, but it is throwing a parse exception, not sure where it is going wrong.
try {
String s = "2007-03-01T13:00:00Z";
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
ft.setLenient(false);
System.out.println(ft.format(ft.parse(s)));
} catch (ParseException e) {
System.out.println(e.getMessage());
}
output is:
Unparseable date: "2007-03-01T10:00:00Z"
I suspect that Z is being interpreted as a time zone so would match -0800 but not a literal Z so you could solve that by quoting: 'Z'.
getErrorOffset should tell you where the problem is.
If you're using Java 7, use the following format string: "yyyy-MM-dd'T'HH:mm:ssXXX"
Note: X is a new code (added in Java 7) that matches ISO 8601 time zone strings; see the API documentation of SimpleDateFormat.
If you want to validate an arbitrary string, you cannot hardcode the "Z" time zone designator, as the validation would fail for a valid ISO8601 time stamp like e.g. "2007-03-01T13:00:00+01".
If you are using Java 6 or earlier, SimpleDateFormat will not support ISO8601 time zone encoding, so you cannot use it to validate time stamps either. With Java 7 or later, you can use new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");.
Your code does not work because the SDF is very limited ad was not aware of ISO 8601 at the time when it was written.
You can take this code:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
public final class JSONDateUtil {
private static final DateFormat ISO8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
static {
ISO8601_FORMAT.setLenient(false);
ISO8601_FORMAT.setTimeZone(DateUtils.UTC_TIME_ZONE);
}
public static String toJSON(Date date) {
return ISO8601_FORMAT.format(date);
}
public static String toJSON(long millis) {
return ISO8601_FORMAT.format(millis);
}
public static Date toJava(String date) {
try {
return ISO8601_FORMAT.parse(date);
} catch (ParseException e) {
return null;
}
}
}
Note the timezone, very important.
Joda-Time
FYI, if you used Joda-Time instead of the notoriously troublesome java.util.Date/Calendar classes, you could simply pass that ISO 8601 string straight into a DateTime constructor without the bother of a formatter. Joda-Time uses ISO 8601 as its defaults.
DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );
DateTime dateTime = new DateTime( "2007-03-01T13:00:00Z", timeZone );
Validation
To determine if your input string was invalid, catch IllegalArgumentException.
java.util.Date
You can even get a java.util.Date back out if need be.
java.util.Date date = dateTime.toDate();

Categories

Resources