Strange String Format for Date - java

Java application gets a stream of JSON messages,
within those, the String fields, that represent some date are formatted like \"2019-01-01+01:00\".
It looks like a normal date, followed by the timezone-offset, this guess of mine has been confirmed on the producer side.
Problem:
Even though I still do not really get the logic behind it, I have to parse it in my Java App.
The simplest approach would be to split that string on the '+' character and just parse the date part with some pattern in LocalDate.
But out of curiosity, maybe there is some Date format in Java I do not know of, which could allow for this strange string format?

Yes there is. DateTimeFormatter.ISO_OFFSET_DATE. I agree that it’s weird, and I don’t know what use you should have of the offset. But you can parse into a LocalDate using the formatter:
String stringFromJson = "2019-01-01+01:00";
LocalDate date = LocalDate.parse(stringFromJson, DateTimeFormatter.ISO_OFFSET_DATE);
System.out.println(date);
Output:
2019-01-01
Splitting at the + (plus sign) will not always work. A date with an offset may also come with a negative offset, for example -04:00, and with offset Z (for zero, that is, UTC). Of course you could write code to handle each of those situations manually, but it’s better to use the built-in formatter.
Don’t use Date and SimpleDateFormat. Those classes are poorly designed and long outdated, the latter in particular notoriously troublesome. Use LocalDate and DateTimeFormatter, both from java.time, the modern Java date and time API.

Related

Is there a simple way to change a timestamp value containing seconds and ms to a timestamp value having hours and minutes?

So I have an object ('Task') that has an attribute 'Start Date' which is basically a Timestamp object. So this date is in this format 'YYYY/MM/dd hh:mm:ss:ms'. But for a test case I am authoring, I need this date to be in this format 'YYYY/MM/dd hh:mm'. Also it needs to be a timestamp object as I have to set this value back to the 'Task' object.
I have tried several approaches including the snippet shown below:
SimpleDateFormat formatter = new SimpleDateFormat("YYYY-MM-dd hh:mm");
if (task.getStartDate() != null) {
String newDate = formatter.format(task.getStartDate());
Date date = formatter.parse(newDate);
task.setStartDate(new Timestamp(date.getTime()));
}
I expected the value of the timestamp to be in the format '2018-12-30 09:54' but it resulted in '2018-12-30 09:54:00.0'. So the questions that I have in mind is:
Is there a way to not consider the seconds and millis in the Timestamp object?
If no, then, is the snippet provided an efficient way to update the Timestamp object?
TL;DR
Avoid the Timestamp class if you can. It’s poorly designed and long outdated.
To answer your questions, no, a Timestamp hasn’t got, as in cannot have a format (the same holds true for its modern replacement, Instant (or LocalDateTime)).
Under all circumstances avoid SimpleDateFormat and Date. The former in particular is notoriously troublesome, and both are long outdated too.
Don’t put a format into your model class
You should not want an Instant nor a Timestamp with a specific format. Good practice in all but the simplest throw-away programs is to keep your user interface apart from your model and your business logic. The value of the Instant object belongs in your model, so keep your Instant or Timestamp there and never let the user see it directly. I hope that it’s clear to you that 2018-12-30 09:54 and 2018-12-30 09:54:00.0 represent the same value, the same Timestamp. Just like 17, 0017 and 0x11 represent the same integer value. When you adhere to what I said, it will never matter which format the Instant has got.
Whenever the user should see the date and time, this happens in the UI, not in the model. Format it into a String and show the string to the user. Similarly if you need a specific format for persistence or exchange with another system, format the Instant into a string for that purpose.
java.time and JDBC 4.2
Also for exchange with your database over JDBC, provided that you’ve got a JDBC 4.2 compliant driver, prefer to use a type from java.time over Timestamp. If the datatype on the database side is timestamp with time zone, very clearly recommended for a timestamp, pass an OffsetDateTime like
OffsetDateTime dateTime = yourInstant.atOffset(ZoneOffset.UTC);
yourPreparedStatement.setObject(4, dateTime);
Use setObject, not setTimestamp. Some drivers accept the Instant directly, without conversion to OffsetDateTime. If on the database side you need a mere timestamp (without time zone), use LocalDateTime in Java instead and pass one to setObject in the same way as above.
PS There are errors in your format pattern string
In a format pattern string, uppercase YYYY is for week based year and only useful with a week number. For year use either uuuu or lowercase yyyy. Similarly lowercase hh is for hour within AM or PM from 01 through 12 and only useful with an AM or PM marker. For hour of day from 00 through 23 you need uppercase HH. These errors will give you incorrect dates and times in most cases. Using the wrong case of format pattern letters is a very common mistake. SimpleDateFormat generally doesn’t mind, it just gives incorrect results. The modern DateTimeFormatter does a somewhat better job of notifying you of such errors.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Related questions
Formatting timestamp in Java about getting rid of the .0 decimal on the second of a Timestamp.
timestamp formatting in scala [duplicate] about getting a Timestamp with only date and hour (no minute, second or fraction of second).
java parsing string to date about uppercase Y for year in a format pattern string.
Comparing two times in android about lowercase h for hour of day in a format pattern string.

Xmlgregoriancalendar not allowed leading zero in ddMMyyyy

I tried to get XMLGregorianCalendar with date format ddMMyyy. When I tried to convert it from Date, it kept throwing me:
java.lang.IllegalArgumentException: Leading zeros are not allowed.
Is there anyway to fix it?
Thanks!
DateFormat format = new SimpleDateFormat("ddMMyyyy");
XMLGregorianCalendar gTest = DatatypeFactory.newInstance().newXMLGregorianCalendar(format.format(new Date(1, 9, 2018)));
First, you shouldn’t use the Date class if you can avoid it. You may also want to avoid XMLGregorianCalendar. Date and SimpleDateFormat are long outdated, and the latter in particular renowned for being troublesome. Today we have so much better in java.time, the modern Java date and time API. It may also sometimes replace XMLGregorianCalendar.
But taking your word for it, you’ve got an old-fashioned Date object — maybe from a legacy API that you cannot change or don’t want to change just now. You need to convert it to an XMLGregorianCalendar. I still prefer to use java.time for the conversion.
Date oldfashionedDate = // some Date equal to 1 August 2018 at 00:00 in my time zone;
LocalDate modernDate = oldfashionedDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
XMLGregorianCalendar gTest = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(modernDate.toString());
System.out.println(gTest);
This prints:
2018-08-01
You asked for format ddMMyyyy. You cannot have that. Or more precisely, the only way you can have that is in a string, not in an XMLGregorianCalendar. XMLGregorianCalendar.toString() produces XML format, and this is defined in the XML Schema (see the link at the bottom). It goes like for example 2018-08-01, 2018-08-01T00:00:00 or 2018-08-01T00:00:00+02:00.
What went wrong in your code?
I could not reproduce your IllegalArgumentException. On my Java 10 the leading 0 of 01082018 was accepted (on Java 8 too). However, the data type factory parsed the string into a year of 1082018 (more than a million years from now), and since the string ended there, it didn’t parse any month or day of month. So the resulting XMLGregorianCalendar had only a year in it, no other fields were defined.
newXMLGregorianCalendar(String) accepts only XML format. Apparently a year alone is accepted. The format still follows the specification linked to at the bottom of this answer.
Accepting the leading 0 is probably a bug, though, since the leading zero is not printed back from the toXMLFormat method, which was supposed to give the same string back as was parsed, according to the documentation of newXMLGregorianCalendar(String).
Links
Oracle tutorial: Date Time explaining how to use java.time.
XML Schema Part 2: Datatypes Second Edition: Lexical representation
Documentation of DatatypeFactory.newXMLGregorianCalendar(String)

SimpleDateFormat Ignore Characters

I am using a SimpleDateFormat and I am getting results from two different sources. One source uses the format "yyyy-MM-dd HH:mm:ss", while the other uses "yyyy-MM-ddTHH:mm:ssZ". I am not interested in obtaining the time zone ('Z' value) from the second format, is there a way I can obtain these times without using different format strings? Something that will ignore the middle character as well as the characters after 'ss'.
The cleanest and clearest solution is if you can separate the strings from the two sources and use an appropriate formatter for each.
Another approach that you might consider is “taking a taste” to determine which format you’ve got and pick the formatter based on that. For example if (result.contains("T") && results.endsWith("Z")).
Since you asked about avoiding different format strings, that is possible too:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd[ ]['T']HH:mm:ss[X]");
System.out.println(LocalDateTime.parse("2015-11-06 21:23:18", formatter));
System.out.println(LocalDateTime.parse("2018-08-25T08:18:49Z", formatter));
Output from this snippet is:
2015-11-06T21:23:18
2018-08-25T08:18:49
I recommend you avoid the SimpleDateFormat class. It is long outdated and notoriously troublesome. Instead I recommend you use java.time, the modern Java date and time API. It’s so much nicer to work with.
The square brackets denote optional parts of the format. The format will accept also a string that has both a space and a T in the middle, and one that hasn’t got any of them. For most purposes I suggest that we can live with that. Edit: You cannot play a similar trick with SimpleDateFormat, it does not accept square brackets or any other syntax for optional parts.
I am not happy about ignoring the offset in the second string and doing that only because you said you wanted to. I’d clearly prefer the following just slightly longer solution:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd[ ]['T']HH:mm:ss[X]")
.withZone(ZoneOffset.UTC);
System.out.println(OffsetDateTime.parse("2015-11-06 21:23:18", formatter));
System.out.println(OffsetDateTime.parse("2018-08-25T08:18:49Z", formatter));
Output is the same, only now with offset:
2015-11-06T21:23:18Z
2018-08-25T08:18:49Z
Link: Oracle tutorial: Date Time explaining how to use java.time.
I know this is old but for the date with the T in the middle and the time zone at the end "2018-08-24T08:02:05-04:00" use the following in your simpledateformat: "yyyy-MM-dd\'T\'HH:mm:ssX"

How to present a date that can be parsed by any DateTimeFormatter pattern in Java?

I'm working with a software that uses a lot of DateTimeFormat parsing, in order to minimize the errors, I wonder if I can present the date String in a certain way that it can be parsed by any DateTimeFormat pattern. Ideally it should work as follows:
String date = "...."
DateTimeFormatter format = DateTimeFormat.forPattern(any pattern I want);
DateTime result = format.parseDateTime(date);
Or does the date have to follow the pattern? Thanks for your help
No, you can not get one size fits all. Think if your string is not a legal date at all, something like "hello", how are you going to parse it?
java.time
Java 8 and later includes the java.time framework (Tutorial). The java.time formatter’s pattern may contain []to mark optional parts. This gives you some flexibility. Say you use format:
M[M]['/']['-']['.']d[d]['/']['-']['.']yyyy[' ']['T'][' ']h[h]:mm:ss
So in this case your string may have one or two digits specifying month, day and hour. Month, day and year may be separated by ., - or / and so forth. For example with format above the following strings will be parsed successfully:
1/10/1995 9:34:45
01-10-1995 09:34:45
01.10.1995T09:34:45
…and so forth.
I wrote a utility that has a set of patterns. Once it gets a String it tries to parse it with all the patterns in the set and sees if it succeeds with one of them. If you write such a set of patterns correctly you may ensure that your util supports any possible String that denotes a valid date.
SimpleDateFromat let you set your own date patters. for example dd/mm/yyyy, mm/dd/yyyy, yyyy-mm-dd etc..
This link can give you a better understanding about date patterns and how to use it
use SimpleDateFormat
SimpleDateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");
Date d=sdf.parse("07/12/2014");

Comparing date strings in Java [duplicate]

This question already has answers here:
How to compare dates in Java? [duplicate]
(11 answers)
Closed 6 years ago.
So I am using dateString1.compareTo(dateString2) which does a lexicographic comparison with strings, based on the Unicode value of each character, and returns an int. Here is a code sample.
String dateString1 = "05-12-2012";
String dateString2 = "05-13-2012";
if (dateString1.compareTo(dateString2) <=0){
System.out.println("dateString1 is an earlier date than dateString2");
}
Is this a wrong approach to compare dates in Java?
In my tests, I have not run into a situation where I have gotten unexpected result. I really do not want to create a Date object out of the string, if I don't have to, because I am doing this inside a long running loop.
Ninja Edit
Gleaning from the answers below there is nothing wrong with comparing dates as a string if it is in yyyyMMdd format but if it is in any other format it will obviously result in error.
I actually have my date string as yyyyMMdd format in my actual code. (I typed the format wrong in the example I gave above.) So for now, I will just leave the code as it is, and add few lines of comments to justify my decision.
But I now see that comparing strings like this is very limiting and I will run into bugs if dba decides to change the date format down the road, which I don't see happening.
Use strings to handle dates in Java is not always the best option. For example, when it is a leap year, February has an extra day. Because strings can be seemingly correct, it is more appropriate to perform a conversion. Java validates that the date is correct.
You can convert strings to dates using the SimpleDateFormat class.
public static void main(String[] args) throws ParseException {
String dateString1 = "05-12-2012";
String dateString2 = "05-13-2012";
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
Date date1 = format.parse(dateString1);
Date date2 = format.parse(dateString2);
if (date1.compareTo(date2) <= 0) {
System.out.println("dateString1 is an earlier date than dateString2");
}
}
To find out which parameters are allowed to check Customizing Formats (The Java™ Tutorials > Internationalization > Formatting)
I suggest you do the Right Thing (as described here) and convert to proper Date objects to compare. Worry about the performance impact if and when it actually impacts your application (which it probably won't).
It is pretty bad as now you cannot handle a year change.
If you want to do it like that you might wanna format the date as YYYY-MM-DD so the new year doesn't ruin it.
It is bad to use the rules for alphabetization to handle date ordering, mostly because you run into issues where things are ordered differently according to the alphabet and the number system
For the alphabet
01-02-2011 comes before
01-1-2011 (because 0 in the date field is before 1 in the other date field)
For the number system
01, 02, 2011 comes after
01, 1, 2011 because all fields are being compared like numbers
Date objects extend numeric comparison to know which fields take precedence in the comparison, so you don't get a earlier month putting a date "before" another that actually occurs at a latter month but an earlier year.
If you have strict control over the date format, you can align the dates such that they also follow alphabetical rules; however, doing so runs a risk of having your entire program fail in odd ways if you accidentally inject a misformatted date.
The typical way to do this is (not recommended, please use non-String Date comparisons)
YYYYMMDD
(year)(month)(day) all zero-padded.
The last technique is included mainly as you will eventually see it in the wild, and should recognize it for what it is: an attempt to handle dates without a proper date library (aka a clever hack).
As discussed, generally better to work with date-time objects rather than strings.
java.time
The other Answers use old outmoded date-time classes that have proven to be poorly designed, confusing, and troublesome. They lack a class to truly represent a date-only value without time-of-day and without time zone.
Instead use the java.time framework built into Java 8 and later. See Oracle Tutorial. Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
String input = "05-12-2012";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "MM-dd-yyyy" );
LocalDate ld = LocalDate.parse( input , formatter );
The LocalDate implements compareTo. Also, you can call methods equals, isBefore, isAfter.
Boolean isEarlier = ld.isBefore( someOtherLocalDate );
if you are doing only one read of each date then YYYYMMDD (not MMDDYYYY as you did it) might be the most optimal solution. however when you intend to process each date more than once (e.g. you are sorting them) then for sure it's better to change them to an object that can be compared quicker than string (e.g. date)

Categories

Resources