Custom Comparator not sorting time - java

I am working on a project where I am comparing the date and time in a custom Comparator. I actually concatenated the date with date and time. When I debugged the issue, I realized that time is not getting sorted. Here is the snippet of my code from my Comparator.
Date dateObject1= new Date();
Date dateObject2 = new Date();
try {
dateObject1 = sdf.parse(date1 + "T" + time1);
dateObject2 = sdf.parse(date2 + "T" + time2);
} catch (Exception e) { }
if (dateObject1.compareTo(dateObject2) > 0)
return 1;
else if (dateObject1.compareTo(dateObject2) < 0)
return -1;
else
return 0;
Test cases:
1. date1 - 2019-12-13 , date2 - 2019-12-13
time1 - 08:00:00, time2 - 12:00:00
When i debugged the issue I found it's returning 0 for the above test case. I am not sure why it's happening but I intent to return -1 such that it's sorted in ascending order.
Please advice.

Your problem is here
} catch (Exception e) { }
You initialize your 2 dates, both of which get initialized to the current time (System.currentTimeMillis()).
Date dateObject1= new Date();
Date dateObject2 = new Date();
Your parsing then fails, but you swallow the exception so you never noticed it.
Then you try to sort two dates which are either exactly same, or separated by a couple of milliseconds, but are certainly unrelated to the actual timestamps that you're trying to sort.
Check the exception, fix the parsing, and then it will work.

java.time and Comparator.comparing … thenComparing
I don’t know what your Java version is. The following snippet works on Java 8 and above. The most important ideas can be applied on Java 6 and 7 too.
List<MyObject> listToBeSorted = Arrays.asList(
new MyObject("2019-12-12", "11:53:50"),
new MyObject("2019-12-11", "13:07:05"),
new MyObject("2019-12-13", "05:02:16"),
new MyObject("2019-12-11", "09:54:57"),
new MyObject("2019-12-12", "05:53:52"),
new MyObject("2019-12-13", "06:56:08"),
new MyObject("2019-12-12", "02:31:55"),
new MyObject("2019-12-11", "09:28:16"),
new MyObject("2019-12-11", "20:58:55"));
Comparator<MyObject> cmpr = Comparator.comparing(MyObject::getDate)
.thenComparing(MyObject::getTime);
listToBeSorted.sort(cmpr);
listToBeSorted.forEach(System.out::println);
Output is:
MyObject [date=2019-12-11, time=09:28:16]
MyObject [date=2019-12-11, time=09:54:57]
MyObject [date=2019-12-11, time=13:07:05]
MyObject [date=2019-12-11, time=20:58:55]
MyObject [date=2019-12-12, time=02:31:55]
MyObject [date=2019-12-12, time=05:53:52]
MyObject [date=2019-12-12, time=11:53:50]
MyObject [date=2019-12-13, time=05:02:16]
MyObject [date=2019-12-13, time=06:56:08]
You will observe that the objects have been sorted by date and objects with the same date also by time. Here is the MyObject class that I used:
public class MyObject {
LocalDate date;
LocalTime time;
public MyObject(String dateString, String timeString) {
date = LocalDate.parse(dateString);
time = LocalTime.parse(timeString);
}
public LocalDate getDate() {
return date;
}
public LocalTime getTime() {
return time;
}
#Override
public String toString() {
return "MyObject [date=" + date + ", time=" + time + "]";
}
}
The two key messages are:
Don’t keep your dates and times as strings in your objects. Keep proper date and time objects. It may require parsing strings when you build your objects, but everything else gets noticeably easier.
Don’t use Date and SimpleDateFormat at all. Use classes from java.time, the modern Java date and time API. In this case LocalDate and LocalTime. The SimpleDateFormat and Date classes are poorly designed and long outdated, the former in particular notoriously troublesome. The modern API is so much nicer to work with.
The advantage of the Comparator methods comparing and thenComparing is not so much that code gets considerably shorter. The really important gain is that writing comparators in this style is much less error prone, and the code reads more naturally.
What went wrong in your code?
The problem is in the line that you posted in a comment:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
This formatter only parses the date from each string and ignores the time. It’s one of many confusing traits of SimpleDateFormat that it is happy to parse only a part of the string and doesn’t draw our attention to the fact that some of the text is ignored — in this case the T and the entire time.
Link
Oracle tutorial: Date Time explaining how to use java.time.

Just return the value of the comparison since that is what you return anyway.
Date dateObject1= new Date();
Date dateObject2 = new Date();
try {
dateObject1 = sdf.parse(date1 + "T" + time1);
dateObject2 = sdf.parse(date2 + "T" + time2);
} catch (Exception e) {
e.printStackTrace(); // always print these. They are there to help you.
}
return dateObject1.compareTo(dateObject2);

Related

convert xmlGregorianCalendar to Date and reverse

i all, i have a spring boot application. what i want in specific is to convert a class (that have nestet object field) in his corrispective entity. example:
public class example{
String string;
ObjectExample object;
}
public class ObjectExample{
String oneString;
XMLGregorianCalendar date;
}
this 2 object are also marked in another package as entities, but ovviusly in the ObjectExampleEntity i have Date date instead XMLGregorianCalendar, like this with the example
#Entity
public class example{
String string;
ObjectExample object;
}
#Entity
public class ObjectExample{
String oneString;
Date date;
}
because i have a big model and big entity (this above is only an example) with a lot of nested classes , i use dozer to convert from the model to the class.
consider for example that the repository jpa is only created for the father example class.
i want to know how i can with dozer convert from Date (entity) to XMLGregorianCalendar (model) and reverse. the model and the entity,i repeat are equal. the only difference is the type of the date. thanks
I am assuming:
Since your variable is named date it contains a calendar date (without time of day).
You are tied to XMLGregorianCalendar because of a WSDL outside your control, but you can change type on the entity side.
Based on these assumptions I recommend LocalDate on the entity side. It’s part of java.time, the modern Java date and time API, and represents exactly a date without time of day. The Date class that you used is poorly designed, long outdated and not recommended. Also despite the name a Date never represented a date, but a point in time.
There are more options. I am presenting three.
Option 1: transfer individual fields
From XMLGregorianCalendar to LocalDate:
DatatypeFactory xmlFactory = DatatypeFactory.newInstance();
XMLGregorianCalendar wsDate = xmlFactory
.newXMLGregorianCalendarDate(2019, DatatypeConstants.MARCH, 30,
DatatypeConstants.FIELD_UNDEFINED);
// Validate
if ((wsDate.getHour() != 0 && wsDate.getHour() != DatatypeConstants.FIELD_UNDEFINED)
|| (wsDate.getMinute() != 0 && wsDate.getMinute() != DatatypeConstants.FIELD_UNDEFINED)
|| (wsDate.getSecond() != 0 && wsDate.getSecond() != DatatypeConstants.FIELD_UNDEFINED)
|| (wsDate.getMillisecond() != 0 && wsDate.getMillisecond() != DatatypeConstants.FIELD_UNDEFINED)) {
System.out.println("Warning: time of day will be lost in conversion");
}
if (wsDate.getTimezone() != DatatypeConstants.FIELD_UNDEFINED) {
System.out.println("Warning: UTC offset will be lost in conversion");
}
// Convert
LocalDate entityDate = LocalDate.of(wsDate.getYear(), wsDate.getMonth(), wsDate.getDay());
System.out.println(entityDate);
The output is in this case:
2019-03-30
From LocalDate to XMLGregorianCalendar:
LocalDate entityDate = LocalDate.of(2019, Month.MARCH, 31);
XMLGregorianCalendar wsDate = xmlFactory.newXMLGregorianCalendarDate(
entityDate.getYear(),
entityDate.getMonthValue(),
entityDate.getDayOfMonth(),
DatatypeConstants.FIELD_UNDEFINED);
System.out.println(wsDate);
2019-03-31
Advantage of this way: It’s pretty straightforward. Disadvantage: You and your reader need to take care that the fields are mentioned in the right order.
Option 2: convert via strings
// Validate as before
// Convert
LocalDate entityDate = LocalDate.parse(wsDate.toXMLFormat());
Result is as before.
XMLGregorianCalendar wsDate
= xmlFactory.newXMLGregorianCalendar(entityDate.toString());
Advantage: it’s brief, and there’s no surprise that the results are correct. Disadvantage: To me it feels like a waste to format into a string only to parse it back.
Option 3: convert via GregorianCalendar and ZonedDateTime
ZonedDateTime zdt = wsDate.toGregorianCalendar().toZonedDateTime();
// Validate
if (! zdt.toLocalTime().equals(LocalTime.MIN)) {
System.out.println("Warning: time of day will be lost in conversion");
}
if (! zdt.getZone().equals(ZoneId.systemDefault())) {
System.out.println("Warning: UTC offset will be lost in conversion");
}
// Finish conversion
LocalDate entityDate = zdt.toLocalDate();
And the other way:
// It doesn’t matter which time zone we pick
// since we are discarding it after conversion anyway
ZonedDateTime zdt = entityDate.atStartOfDay(ZoneOffset.UTC);
GregorianCalendar gCal = GregorianCalendar.from(zdt);
XMLGregorianCalendar wsDate = xmlFactory.newXMLGregorianCalendar(gCal);
wsDate.setTime(DatatypeConstants.FIELD_UNDEFINED, DatatypeConstants.FIELD_UNDEFINED,
DatatypeConstants.FIELD_UNDEFINED, DatatypeConstants.FIELD_UNDEFINED);
wsDate.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
The validation I present here is a bit simpler but also not quite so strict. If you want strict validation, you can just use the validation from before.
Advantages: I think it’s the official way; at least it uses the conversion methods offered. What I like is that the conversion itself is direct and brief. Disadvantage: When converting to XMLGregorianCalendar we need to set the unused fields to undefined manually, which makes it wordy.
Conclusion
I have presented three options each with their pros and cons. You may also mix, of course, but using a similar conversion both ways is probably less confusing in the end.

Why GregorianCalendar.getTimeInMillis() changes the value of the instance?

I found a very strange behavior of GregorianCalendar.getTimeInMillis(), it seems that it changes the value of the instance content. In the following code you can see that two blocks of code differ in only one commented line, where getTimeInMillis() is called. Why is the result different when I uncomment the line?
With commented call the output is
2014-10-25T22:00:00Z -> 2014-10-26T22:00:00.000+01:00
2014-10-25T22:00:00Z -> 2014-10-27T00:00:00.000+01:00
but when I uncomment the getTimeInMillis() line, both results are the same:
2014-10-25T22:00:00Z -> 2014-10-27T00:00:00.000+01:00
2014-10-25T22:00:00Z -> 2014-10-27T00:00:00.000+01:00
Code:
package com.test;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
public class Main {
public static void main(String[] args) {
try {
XMLGregorianCalendar date1 = DatatypeFactory.newInstance()
.newXMLGregorianCalendar("2014-10-25T22:00:00Z");
XMLGregorianCalendar date2 = DatatypeFactory.newInstance()
.newXMLGregorianCalendar("2014-10-25T22:00:00Z");
int days = 1;
GregorianCalendar gregorianCalendar1 = date1.toGregorianCalendar();
// gregorianCalendar1.getTimeInMillis(); //UNCOMMENT THIS LINE TO GET A DIFFERENT RESULT
gregorianCalendar1.setTimeZone(TimeZone.getDefault());
gregorianCalendar1.add(Calendar.DAY_OF_MONTH, days);
XMLGregorianCalendar newXMLGregorianCalendar1 = DatatypeFactory
.newInstance().newXMLGregorianCalendar(gregorianCalendar1);
System.out.printf("%s -> %s\n", date1, newXMLGregorianCalendar1);
GregorianCalendar gregorianCalendar2 = date2.toGregorianCalendar();
gregorianCalendar2.getTimeInMillis();
gregorianCalendar2.setTimeZone(TimeZone.getDefault());
gregorianCalendar2.add(Calendar.DAY_OF_MONTH, days);
XMLGregorianCalendar newXMLGregorianCalendar2 = DatatypeFactory
.newInstance().newXMLGregorianCalendar(gregorianCalendar2);
System.out.printf("%s -> %s\n", date2, newXMLGregorianCalendar2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
It's a time zone change. Not on December 31st in Shanghai, but manually, in your code.
Particularly, you are changing the time zone after having forced the calendar to compute its fields (based on the "old" time zone). This messes up the internal state of the calendar. Of course, this should not be the case, but is only one of the many strange behaviors exposed by the Calendar classes - and, most likely, mainly caused by their mutability.
Some of the potential difficulties are also stated in a comment in the implementation of Calendar#setTimeZone:
* Consider the sequence of calls:
* cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
* Is cal set to 1 o'clock EST or 1 o'clock PST? Answer: PST.
You could possibly work around this by studying the source code of GregorianCalendar and trying to avoid the critical sequences of calls. But as others already have pointed out: The whole old Date/Time API is horribly broken. If you have the chance, you should consider using the new Date/Time API of Java 8 (or the Joda Time API, which is similar enough to Java 8 to make it easy to later change existing Joda-based code to Java 8 code).
Here is an example that demonstrates the difference between setting the time zone before the call to getTimeMillis and after the call to getTimeMillis:
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
public class GregorianCalendarTest {
public static void main(String[] args) {
String fromSettingTimeZoneBeforeCall = createString(true);
String fromSettingTimeZoneAfterCall = createString(false);
System.out.println("Before: "+fromSettingTimeZoneBeforeCall);
System.out.println("After : "+fromSettingTimeZoneAfterCall);
}
private static String createString(boolean setTimeZoneBeforeCall)
{
try {
XMLGregorianCalendar date = DatatypeFactory.newInstance()
.newXMLGregorianCalendar("2014-10-25T22:00:00Z");
int days = 1;
GregorianCalendar gregorianCalendar = date.toGregorianCalendar();
System.out.println("After creating: "+gregorianCalendar);
if (!setTimeZoneBeforeCall)
{
gregorianCalendar.getTimeInMillis();
System.out.println("After millis : "+gregorianCalendar);
}
gregorianCalendar.setTimeZone(TimeZone.getDefault());
System.out.println("After timezone: "+gregorianCalendar);
if (setTimeZoneBeforeCall)
{
gregorianCalendar.getTimeInMillis();
System.out.println("After millis : "+gregorianCalendar);
}
gregorianCalendar.add(Calendar.DAY_OF_MONTH, days);
System.out.println("After adding : "+gregorianCalendar);
XMLGregorianCalendar newXMLGregorianCalendar = DatatypeFactory
.newInstance().newXMLGregorianCalendar(gregorianCalendar);
System.out.println("After all : "+gregorianCalendar);
return newXMLGregorianCalendar.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
EDIT: This behavior is also described in this bug report: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5026826
Pre-Java 8 Calendar implementations have been under a lot of criticism for "weird" behavior. I think that this is due to the following documentation:
Getting and Setting Calendar Field Values
The calendar field values can be set by calling the set methods. Any field values set in a Calendar will not be interpreted until it needs to calculate its time value (milliseconds from the Epoch) or values of the calendar fields. Calling the get, getTimeInMillis, getTime, add and roll involves such calculation.
Note that the toString() method is marked as debug-only:
Return a string representation of this calendar. This method is intended to be used only for debugging purposes, and the format of the returned string may vary between implementations. The returned string may be empty but may not be null.
Though this will not probably end-up in a bug (as long as you don't use toString() in actual logic), it is better to use Joda-Time or new Java-8 Date and Time

Does Joda-Time have a method called isToday

I need to check if a given timestamp is today. I am using Joda-Time. Is there a method or a simple way to check this? What Joda-Time class is better suited for this? LocalDate? DateTime?
The date can be compared by single statement so why you need a special function.
when dateTimeis an object of DateTime()
if((dateTime.toLocalDate()).equals(new LocalDate()))
when date is an object of java.util.date
if((new DateTime(date).toLocalDate()).equals(new LocalDate()))
What Joda-time class is better suited for this? LocalDate? DateTime?
The understanding that you need to know what is LocalDate and DateTime.
LocalDate() is an immutable datetime class representing a date without a
time zone. So is not having a time part.
DateTime() is the standard implementation of an unmodifiable datetime
class. Its having all the attributes of the Date, which includes
date, time and timezone.
So if you need to compare both the date and time better go with datetime, if you just need to check the date you must use localDate because the datetime will produce a false if an .equal operator is used, unless the time including the seconds part are same for both the objects.
Here are some simple methods to check if a DateTime is today, tomorrow or yesterday:
public boolean isToday(DateTime time) {
return LocalDate.now().compareTo(new LocalDate(time)) == 0;
}
public boolean isTomorrow(DateTime time) {
return LocalDate.now().plusDays(1).compareTo(new LocalDate(time)) == 0;
}
public boolean isYesterday(DateTime time) {
return LocalDate.now().minusDays(1).compareTo(new LocalDate(time)) == 0;
}
One possibility is to create an interval covering the whole day in question, and then check if the various timestamps are contained in this interval.
Constructing the initial interval could look like:
Interval today = new Interval(DateTime.now().withTimeAtStartOfDay(), Days.ONE);
Then the timestamps could be checked like so:
today.contains(DateTime.now()); // True
today.contains(DateTime.now().minusDays(1)); // False
today.contains(DateTime.now().plusDays(1)); // False
today.contains(someOtherTimeStamp.toDateTime()); // And so on...
The recommended way to do this would be:
DateTime midnightToday = DateTime.now().withTimeAtStartOfDay();
DateTime myDateTime = <whatever>;
if(myDateTime.isAfter(midnightToday)) {
}
I think you need Joda 2.5 to do this, but that should do the trick.
I like #JustinMorris's answer. But I found this even better:
public static boolean isToday(DateTime time) {
return LocalDate.now().equals(new LocalDate(time));
}
public static boolean isTomorrow(DateTime time) {
return LocalDate.now().plusDays(1).equals(new LocalDate(time));
}
public static boolean isYesterday(DateTime time) {
return LocalDate.now().minusDays(1).equals(new LocalDate(time));
}
AFAIK there is no direct method available by which you can check the Date is Today Date or not.
The simplest approach will be constructing two DateTime one with the Timestamp, and another with today Date and then comparing day from dayOfYear() and year from year() but do remember whether both Date are in UTC or in Local Time Zone.
A small sample,
DateTime date = new DateTime(TimeStamp);
DateTime todayDate = new DateTime();
if(date.dayOfYear().get() == todayDate.dayOfYear().get() && date.year().get() == todayDate.year().get())
{
System.out.println("Date is today date");
}
Joda time actually have a method for this:
DateUtils#isToday(ReadablePartial);
DateUtils#isToday(ReadableInstant);
Simplest way I've found:
public boolean isToday(DateTime dateTime) {
return dateTime.withTimeAtStartOfDay().getMillis() ==
new DateTime().withTimeAtStartOfDay().getMillis();
}

Java system time

I have this code copied from one of questions from SO:
public static String getCurrentTimeStamp() {
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
String strDate = sdfDate.format(now);
return strDate;
}
I want to get only the system time and NOT the date. Then I must change second line of code to:
SimpleDateFormat sdfDate = new SimpleDateFormat(" HH:mm:ss") ;
Then, DATE() must get the current time. Clear upto this point but I can't understand the format() function used.
I mean cant we simply output variable now instead of strdate?
Is it just because that the return type of function getCurrentTimeStamp() is String?
Please clarify and if there is any other simpler and one line code for getting system time alone, do share.
I mean cant we simply output variable now instead of strdate.
Well you could return now.toString() - but that will use the format that Date.toString() happens to choose, whereas you want a specific format. The point of the SimpleDateFormat object in this case is to convert a Date (which is a point in time, without reference to any particular calendar or time zone) into a String, applying an appropriate time zone, calendar system, and text format (in your case HH:mm:ss).
You can still simplify your method somewhat though, by removing the local variables (which are each only used once):
public static String getCurrentTimeStamp() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
Or maybe you'd find it more readable to keep the variable for the date format, but not the date and the return value:
public static String getCurrentTimeStamp() {
DateFormat format = new SimpleDateFormat("HH:mm:ss");
return format.format(new Date());
}
Personally I'd recommend using Joda Time instead, mind you - it's a much nicer date/time API, and its formatted are thread-safe so you could easily keep a reference to a single formatting object.
public static String getCurrentTimeStampwithTimeOnly() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
Helps you to do this.
you can call this line any time
Date now = new Date();
The now variable will contain the current timestamp
The format function just generates a String from this timestamp
also take a look at the Calendar class ( Calendar.getInstance())

Determine if a String is a valid date before parsing

I have this situation where I am reading about 130K records containing dates stored as String fields. Some records contain blanks (nulls), some contain strings like this: 'dd-MMM-yy' and some contain this 'dd/MM/yyyy'.
I have written a method like this:
public Date parsedate(String date){
if(date !== null){
try{
1. create a SimpleDateFormat object using 'dd-MMM-yy' as the pattern
2. parse the date
3. return the parsed date
}catch(ParseException e){
try{
1. create a SimpleDateFormat object using 'dd/MM/yyy' as the pattern
2. parse the date
3. return parsed date
}catch(ParseException e){
return null
}
}
}else{
return null
}
}
So you may have already spotted the problem. I am using the try .. catch as part of my logic. It would be better is I can determine before hand that the String actually contains a parseable date in some format then attempt to parse it.
So, is there some API or library that can help with this? I do not mind writing several different Parse classes to handle the different formats and then creating a factory to select the correct6 one, but, how do I determine which one?
Thanks.
See Lazy Error Handling in Java for an overview of how to eliminate try/catch blocks using an Option type.
Functional Java is your friend.
In essence, what you want to do is to wrap the date parsing in a function that doesn't throw anything, but indicates in its return type whether parsing was successful or not. For example:
import fj.F; import fj.F2;
import fj.data.Option;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import static fj.Function.curry;
import static fj.Option.some;
import static fj.Option.none;
...
F<String, F<String, Option<Date>>> parseDate =
curry(new F2<String, String, Option<Date>>() {
public Option<Date> f(String pattern, String s) {
try {
return some(new SimpleDateFormat(pattern).parse(s));
}
catch (ParseException e) {
return none();
}
}
});
OK, now you've a reusable date parser that doesn't throw anything, but indicates failure by returning a value of type Option.None. Here's how you use it:
import fj.data.List;
import static fj.data.Stream.stream;
import static fj.data.Option.isSome_;
....
public Option<Date> parseWithPatterns(String s, Stream<String> patterns) {
return stream(s).apply(patterns.map(parseDate)).find(isSome_());
}
That will give you the date parsed with the first pattern that matches, or a value of type Option.None, which is type-safe whereas null isn't.
If you're wondering what Stream is... it's a lazy list. This ensures that you ignore patterns after the first successful one. No need to do too much work.
Call your function like this:
for (Date d: parseWithPatterns(someString, stream("dd/MM/yyyy", "dd-MM-yyyy")) {
// Do something with the date here.
}
Or...
Option<Date> d = parseWithPatterns(someString,
stream("dd/MM/yyyy", "dd-MM-yyyy"));
if (d.isNone()) {
// Handle the case where neither pattern matches.
}
else {
// Do something with d.some()
}
Don't be too hard on yourself about using try-catch in logic: this is one of those situations where Java forces you to so there's not a lot you can do about it.
But in this case you could instead use DateFormat.parse(String, ParsePosition).
You can take advantage of regular expressions to determine which format the string is in, and whether it matches any valid format. Something like this (not tested):
(Oops, I wrote this in C# before checking to see what language you were using.)
Regex test = new Regex(#"^(?:(?<formatA>\d{2}-[a-zA-Z]{3}-\d{2})|(?<formatB>\d{2}/\d{2}/\d{3}))$", RegexOption.Compiled);
Match match = test.Match(yourString);
if (match.Success)
{
if (!string.IsNullOrEmpty(match.Groups["formatA"]))
{
// Use format A.
}
else if (!string.IsNullOrEmpty(match.Groups["formatB"]))
{
// Use format B.
}
...
}
If you formats are exact (June 7th 1999 would be either 07-Jun-99 or 07/06/1999: you are sure that you have leading zeros), then you could just check for the length of the string before trying to parse.
Be careful with the short month name in the first version, because Jun may not be June in another language.
But if your data is coming from one database, then I would just convert all dates to the common format (it is one-off, but then you control the data and its format).
In this limited situation, the best (and fastest method) is certinally to parse out the day, then based on the next char either '/' or '-' try to parse out the rest. and if at any point there is unexpected data, return NULL then.
Assuming the patterns you gave are the only likely choices, I would look at the String passed in to see which format to apply.
public Date parseDate(final String date) {
if (date == null) {
return null;
}
SimpleDateFormat format = (date.charAt(2) == '/') ? new SimpleDateFormat("dd/MMM/yyyy")
: new SimpleDateFormat("dd-MMM-yy");
try {
return format.parse(date);
} catch (ParseException e) {
// Log a complaint and include date in the complaint
}
return null;
}
As others have mentioned, if you can guarantee that you will never access the DateFormats in a multi-threaded manner, you can make class-level or static instances.
Looks like three options if you only have two, known formats:
check for the presence of - or / first and start with that parsing for that format.
check the length since "dd-MMM-yy" and "dd/MM/yyyy" are different
use precompiled regular expressions
The latter seems unnecessary.
Use regular expressions to parse your string. Make sure that you keep both regex's pre-compiled (not create new on every method call, but store them as constants), and compare if it actually is faster then the try-catch you use.
I still find it strange that your method returns null if both versions fail rather then throwing an exception.
you could use split to determine which format to use
String[] parts = date.split("-");
df = (parts.length==3 ? format1 : format2);
That assumes they are all in one or the other format, you could improve the checking if need be
An alternative to creating a SimpleDateFormat (or two) per iteration would be to lazily populate a ThreadLocal container for these formats. This will solve both Thread safety concerns and concerns around object creation performance.
A simple utility class I have written for my project. Hope this helps someone.
Usage examples:
DateUtils.multiParse("1-12-12");
DateUtils.multiParse("2-24-2012");
DateUtils.multiParse("3/5/2012");
DateUtils.multiParse("2/16/12");
public class DateUtils {
private static List<SimpleDateFormat> dateFormats = new ArrayList<SimpleDateFormat>();
private Utils() {
dateFormats.add(new SimpleDateFormat("MM/dd/yy")); // must precede yyyy
dateFormats.add(new SimpleDateFormat("MM/dd/yyyy"));
dateFormats.add(new SimpleDateFormat("MM-dd-yy"));
dateFormats.add(new SimpleDateFormat("MM-dd-yyyy"));
}
private static Date tryToParse(String input, SimpleDateFormat format) {
Date date = null;
try {
date = format.parse(input);
} catch (ParseException e) {
}
return date;
}
public static Date multiParse(String input) {
Date date = null;
for (SimpleDateFormat format : dateFormats) {
date = tryToParse(input, format);
if (date != null) break;
}
return date;
}
}
On one hand I see nothing wrong with your use of try/catch for the purpose, it’s the option I would use. On the other hand there are alternatives:
Take a taste from the string before deciding how to parse it.
Use optional parts of the format pattern string.
For my demonstrations I am using java.time, the modern Java date and time API, because the Date class used in the question was always poorly designed and is now long outdated. For a date without time of day we need a java.time.LocalDate.
try-catch
Using try-catch with java.time looks like this:
DateTimeFormatter ddmmmuuFormatter = DateTimeFormatter.ofPattern("dd-MMM-uu", Locale.ENGLISH);
DateTimeFormatter ddmmuuuuFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");
String dateString = "07-Jun-09";
LocalDate result;
try {
result = LocalDate.parse(dateString, ddmmmuuFormatter);
} catch (DateTimeParseException dtpe) {
result = LocalDate.parse(dateString, ddmmuuuuFormatter);
}
System.out.println("Date: " + result);
Output is:
Date: 2009-06-07
Suppose instead we defined the string as:
String dateString = "07/06/2009";
Then output is still the same.
Take a taste
If you prefer to avoid the try-catch construct, it’s easy to make a simple check to decide which of the formats your string conforms to. For example:
if (dateString.contains("-")) {
result = LocalDate.parse(dateString, ddmmmuuFormatter);
} else {
result = LocalDate.parse(dateString, ddmmuuuuFormatter);
}
The result is the same as before.
Use optional parts in the format pattern string
This is the option I like the least, but it’s short and presented for some measure of completeness.
DateTimeFormatter dateFormatter
= DateTimeFormatter.ofPattern("[dd-MMM-uu][dd/MM/uuuu]", Locale.ENGLISH);
LocalDate result = LocalDate.parse(dateString, dateFormatter);
The square brackets denote optional parts of the format. So Java first tries to parse using dd-MMM-uu. No matter if successful or not it then tries to parse the remainder of the string using dd/MM/uuuu. Given your two formats one of the attempts will succeed, and you have parsed the date. The result is still the same as above.
Link
Oracle tutorial: Date Time explaining how to use java.time.

Categories