Java Best way to parse quarterly dates - java

I have a date as APR-JUN10 or APR-JUN 2010 and i need output as 2010-06-30 I need the best way to parse above date in java and should be flexible in adding more such format of dates. note: APR-JUN10 will not parse by any java api, we have to break down APR & JUN 10 and get date as 2010-06-30.

You need to firm up your requirements.
Currently all you have told us is that APR-JUN 2010 should translate to the last day of June.
But what about FEB-JUN 2010? Should that also translate to the last day of June? Or should it throw a parse exception due to not being a full quarter? What about JUL-JUN 2010, where the second month is before the first? What about MAY-JUL 2010 -- three months but perhaps your definition of "quarter" requires starts of January, April, July, October.
Once you have your own requirements down, you can get to work on the conversion.
It's unlikely that an existing DateFormat implementation will do this exact task for you. You're likely to need to parse the string in your own code.
If the only legal options are JAN-MAR, APR-JUN, JUL-SEP, OCT-DEC, then you just have a five-way switch statement to set the month and day on a Calendar object (the fifth way being a default: case that throws an exception.
If your requirement is more complex, then your code will need to be more complex. Breaking the string into parts using a regex would be a good first step.
Pattern patt = Pattern.compile("(.{3})-(.{3}) (\d+)");
Matcher matcher = patt.matcher(qaurterString);
if(! matcher.find() || m.groupCount() != 3) {
throw new ParseException(...)
}
String fromMonth = matcher.group(1);
String toMonth = matcher.group(2);
int year = Integer.parseInt(matcher.group(3));
I think you'll have to write parsing code from scratch, whatever you do. The neatest end result would for you to create a class that implements DateFormat.

String s = "APR-JUN10";
// validation of quarter part
String quarter = s.substring(0, 7);
if (
!quarter.equals("JAN-MAR") && !quarter.equals("APR-JUN")
&& !quarter.equals("JUL-SEP") && !quarter.equals("OCT-DEC")
) {
throw new IllegalArgumentException("Input is not a quarter date: " + s);
}
// text processing with preprocessing hack (substring(4))
SimpleDateFormat inputFormat = new SimpleDateFormat("MMMyy", Locale.ENGLISH);
inputFormat.setLenient(false);
Date date = inputFormat.parse(s.substring(4));
System.out.println(date);
// Output: Tue Jun 01 00:00:00 CEST 2010 [format chooses 1 as default day-of-month]
// Go to end of month/quarter
GregorianCalendar gcal = new GregorianCalendar();
gcal.clear();
gcal.setTime(date);
gcal.set(Calendar.DAY_OF_MONTH, gcal.getActualMaximum(Calendar.DAY_OF_MONTH));
// format as ISO-date
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd");
String output = outputFormat.format(gcal.getTime());
System.out.println(output); // 2010-06-30
For the input "APR-JUN 2010" you need the input format pattern "MMM yyyy", else the solution is the same. Of course, the proposed solution assumes that every input starts with JAN-MAR, APR-JUN, JUL-SEP or OCT-DEC (you wrote about quarters). If you want you can validate it before processing phase by mean of s.substring(0, 7) etc.
UPDATE: I have now added the validation feature, see code.

Related

Safe SimpleDateFormat parsing

I have a small block of code which parses response generation time from the response itself and turns it into a date for future purposes. It goes like this:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
Date responseTime = sdf.parse(RStime);
And it almost works like a charm. To be precise, it works 99.9% of the time, with the exception of one case: When the millisecond part is 000 then the Server doesn't append the .000 milliseconds at all, hence we have a problem.
Now, according to SimpleDateFormat docs if parsing fails, the function returns null. However, I probably misinterpreted it as it just throws an exception.
I am very new to Java and try-catch mechanisms, so could anyone please provide an elegant good-practice solution for handling such cases?
Thanks!
java.time
String rsTime = "2018-04-09T10:47:16.999-02:00";
OffsetDateTime responseTime = OffsetDateTime.parse(rsTime);
System.out.println("Parsed date and time: " + responseTime);
Output from this snippet is:
Parsed date and time: 2018-04-09T10:47:16.999-02:00
It works just as well for the version with the 000 milliseconds omitted:
String rsTime = "2018-04-09T10:47:16-02:00";
Parsed date and time: 2018-04-09T10:47:16-02:00
The classes you used, SimpleDateFormat and Date, are poorly designed and long outdated (the former in particular notoriously troublesome). So it is not only in this particular case I recommend using java.time, the modern Java date and time API, instead. However, the strings from your server are in ISO 8601 format, and OffsetDateTime and the other classes of java.time parse this format as their default, that is, without any explicit formatter, which already makes the task remarkably easier. Furthermore, in the standard the fractional seconds are optional, which is why both the variants of the string are parsed without any problems. OffsetDateTime also prints ISO 8601 back from it’s toString method, which is why in both cases a string identical to the parsed one is printed.
Only in case you indispensably need an old-fashioned Date object for a legacy API that you cannot change just now, convert like this:
Instant responseInstant = responseTime.toInstant();
Date oldfashionedDateObject = Date.from(responseInstant);
System.out.println("Converted to old-fashioned Date: " + oldfashionedDateObject);
Output on my computer in Europe/Copenhagen time zone is:
Converted to old-fashioned Date: Mon Apr 09 14:47:16 CEST 2018
Link: Oracle tutorial: Date Time explaining how to use java.time.
According to the SimpleDateFormat doc that you mentioned the parse method:
public Date parse(String text, ParsePosition pos)
Throws:
NullPointerException - if text or pos is null.
So one option is to catch that exception and do what you need inside the catch, for example:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
try {
Date responseTime = sdf.parse(RStime, position);
} catch (NullPointerException e) {
e.printStackTrace();
//... Do extra stuff if needed
}
Or the inherited method from DateFormat:
public Date parse(String source)
Throws:
ParseException - if the beginning of the specified string cannot be
parsed.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
try {
Date responseTime = sdf.parse(RStime);
} catch (ParseException e) {
e.printStackTrace();
//... Do extra stuff if needed
}
Is it actually an exceptional situation? If it is not then you probably shouldn't use exceptions in that case. In my opinion it is normal that time can end with .000ms. In this case you can check if the string contains . (dot) and if not append .000 to the end.
if(!RStime.contains(".")){
RStime+=".000";
}
Edit: I've forgot about time zone in the time String. You probably need something a little bit more complicated for that. Something like this should do it:
if(!RStime.contains(".")){
String firstPart = RStime.substring(0, 21);
String secondPart = RStime.substring(21);
RStime = firstPart + ".000" + secondPart;
}
You can check for a dot and then use the first or second format:
String timeString = "2018-04-09T10:47:16.999-02:00";
//String timeString = "2018-04-09T10:47:16-02:00";
String format = timeString.contains(".") ? "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" : "yyyy-MM-dd'T'HH:mm:ssXXX";
Date responseTime = new SimpleDateFormat(format).parse(timeString);
System.out.println("responseTime: " + responseTime);
If you comment-out the first line and comment-in the second and run it again, it will both print out:
responseTime: Mon Apr 09 14:47:16 CEST 2018
By the way:
Java 7 (the version you use obviously) returns a java.text.ParseException: Unparseable date: "2018-04-09T10:47:16-02:00"
Optionals are supported since Java 8.

Generic date parsing in java

I have date strings in various formats like Oct 10 11:05:03 or 12/12/2016 4:30 etc
If I do
// some code...
getDate("Oct 10 11:05:03", "MMM d HH:mm:ss");
// some code ...
The date gets parsed, but I am getting the year as 1970 (since the year is not specified in the string.) But I want the year as current year if year is not specidied. Same applies for all fields.
here is my getDate function:
public Date getDate(dateStr, pattern) {
SimpleDateFormat parser = new SimpleDateFormat(pattern);
Date date = parser.parse(myDate);
return date;
}
can anybody tell me how to do that inside getDate function (because I want a generic solution)?
Thanks in advance!
If you do not know the format in advance, you should list the actual formats you are expecting and then try to parse them. If one fails, try the next one.
Here is an example of how to fill in the default.
You'll end up with something like this:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("ddMM")
.parseDefaulting(YEAR, currentYear)
.toFormatter();
LocalDate date = LocalDate.parse("yourstring", f);
Or even better, the abovementioned formatter class supports optional elements. Wrap the year specifier in square brackets and the element will be optional. You can then supply a default with parseDefaulting.
Here is an example:
String s1 = "Oct 5 11:05:03";
String s2 = "Oct 5 1996 13:51:56"; // Year supplied
String format = "MMM d [uuuu ]HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern(format)
.parseDefaulting(ChronoField.YEAR, Year.now().getValue())
.toFormatter(Locale.US);
System.out.println(LocalDate.parse(s1, f));
System.out.println(LocalDate.parse(s2, f));
Note: Dates and times are not easy. You should take into consideration that date interpreting is often locale-dependant and this sometimes leads to ambiguity. For example, the date string "05/12/2018" means the 12th of May, 2018 when you are American, but in some European areas it means the 5th of December 2018. You need to be aware of that.
One option would be to concatenate the current year onto the incoming date string, and then parse:
String ts = "Oct 10 11:05:03";
int currYear = Calendar.getInstance().get(Calendar.YEAR);
ts = String.valueOf(currYear) + " " + ts;
Date date = getDate(ts, "yyyy MMM d HH:mm:ss");
System.out.println(date);
Wed Oct 10 11:05:03 CEST 2018
Demo
Note that we could have used StringBuilder above, but the purpose of brevity of code, I used raw string concatenations instead. I also fixed a few typos in your helper method getDate().

Fetch a range of dates using jQueryUI datepicker and php, in Spanish

I'm trying to save a range of dates that will be filled in by the user, via 2 datepickers that were previously translated to Spanish.
The problem is that when I use strtotime() on the dates it takes the month as the day and vice versa.
Example:
I choose day 27 month 05 and year 2017, but the return value is an incorrect date format since the month is 27. If I choose day 01 month 05 year 2017 then it shows in the array as day 05 month 01 and year 2017.
Here are the functions I use to take the dates from the input texts, and to generate the range between the dates
function takedates() {
if(isset($_POST['repS'])){
$dateStart = $_POST['txtdesde'];
$dateEnd = $_POST['txthasta'];
$fechaArray = generafechas($dateStart,$dateEnd);
}
function generafechas($date1,$date2){
$fecharray = array();
if (is_string($date1) === true){
$deit1 = strftime("%d-%m-%Y",strtotime($date1));
}
if (is_string($date2) === true){
$date2 = strftime("%d-%m-%Y",strtotime($date2));
}
do {
$fecharray[] = date("m-d-Y", $date1);
$date1 = strtotime("+1 day", $date1);
} while($date1 <= $date2);
return $fecharray;
}
?>
My question is: How do i fill the array with the dates in the spanish date format?
PS: I've already used setLocale(LC_TIME,'es_ES') in the file where I'm using these functions, and the input shows the dates like this "dd/mm/yyyy"
strtotime does not take your locale into consideration when parsing the datetime string. If you use a date separated by slashes it is assumed to be American-style m/d/y. If you use a date separated by periods (or dashes if the year is four digits), it is assumed to be rest-of-the-world-style (d.m.y or d-m-Y). (Note that if you only use a two digit year and use dashes, PHP will try try to parse it as y-m-d.)
Instead of strtotime, you should use date-create-from-format / DateTime::createFromFormat to get a DateTime object, then build your date string from that.
UPDATE BASED ON COMMENTS: In order to get the output you want, you need to use the intl extension's IntlDateFormatter class to make the output.
To modify your code above (untested):
function generafechas($date1,$date2){
$fecharray = array();
if (is_string($date1) && is_string($date2)){
// These lines assume the input is formatted `day-month-year`,
// with 2-digit day and month, and 4-digit year.
$date1 = DateTime::createFromFormat('d-m-Y', $date1)
$date2 = DateTime::createFromFormat('d-m-Y', $date2)
} else {
throw new InvalidArgumentException('Must provide two date strings');
}
// Create the formatter
$formatter = IntlDateFormatter::create('es_ES', null, null, null, null, "d 'de' MMMM 'del' yyyy");
do {
// This line prints the first date in the format you chose above
$fecharray[] = $formatter->format($date1);
$date1->add(new DateInterval("P1D")); // `P1D` means "Duration of 1 Day" in the ISO 8601 standard
} while($date1 <= $date2);
return $fecharray;
}
If you provide the Locale along with the data, you can change what format string is used in createFromFormat as needed.

Wildcard in DateTimeFormatter

I need to parse a string into a LocalDate. The string looks like 31.* 03 2016 in regex terms (i.e. .* means that there may be 0 or more unknown characters after the day number).
Example input/output: 31xy 03 2016 ==> 2016-03-31
I was hoping to find a wildcard syntax in the DateTimeFormatter documentation to allow a pattern such as:
LocalDate.parse("31xy 03 2016", DateTimeFormatter.ofPattern("dd[.*] MM yyyy"));
but I could not find anything.
Is there a simple way to express optional unknown characters with a DateTimeFormatter?
ps: I can obviously modify the string before parsing it but that's not what I'm asking for.
There is no direct support for this in java.time.
The closest would be to use parse(CharSequence,ParsePosition) using two different formatters.
// create the formatter for the first half
DateTimeFormatter a = DateTimeFormatter.ofPattern("dd")
// setup a ParsePosition to keep track of where we are in the parse
ParsePosition pp = new ParsePosition();
// parse the date, which will update the index in the ParsePosition
String str = "31xy 03 2016";
int dom = a.parse(str, pp).get(DAY_OF_MONTH);
// some logic to skip the messy 'xy' part
// logic must update the ParsePosition to the start of the month section
pp.setIndex(???)
// use the parsed day-of-month in the formatter for the month and year
DateTimeFormatter b = DateTimeFormatter.ofPattern("MM yyyy")
.parseDefaulting(DAY_OF_MONTH, dom);
// parse the date, using the *same* ParsePosition
LocalDate date = b.parse(str, pp).query(LocalDate::from);
While the above is untested it should basically work. However, it would be far easier parse it manually.
I’d do it in two steps, use a regexp to get the original string into something that LocalDate can parse, for example:
String dateSource = "31xy 03 2016";
String normalizedDate = dateSource.replaceFirst("^(\\d+).*? (\\d+ \\d+)", "$1 $2");
LocalDate date = LocalDate.parse(normalizedDate, DateTimeFormatter.ofPattern("dd MM yyyy"));
System.out.println(date);
I know it’s not what you asked for.

Difference, in minutes, between two dates

I need to get the number of minutes between two dates. I know Joda is the best to use, but this is for an Android project, so I prefer to use a little external libraries as possible, and the app I'm building doesn't require the calculation to be surgically precise.
However, the code I'm using doesn't seem to be working. I'm trying to get the number of minutes between "11/21/2011 7:00:00 AM" and "11/21/2011 1:00:00 PM" (which should be 360 minutes), but the code I'm using returns 0:
int diff = minutesDiff(GetItemDate("11/21/2011 7:00:00 AM"), GetItemDate("11/21/2011 1:00:00 PM"));
public static Date GetItemDate(final String date)
{
final Calendar cal = Calendar.getInstance(TimeZone.getDefault());
final SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss a");
format.setCalendar(cal);
try {
return format.parse(date);
} catch (ParseException e) {
return null;
}
}
public static int minutesDiff(Date earlierDate, Date laterDate)
{
if( earlierDate == null || laterDate == null ) return 0;
return (int)((laterDate.getTime()/60000) - (earlierDate.getTime()/60000));
}
If your GetItemDate is failing for either date, you will get zero. On the catch statement, place a println or debug to output the issue and see if that tells you something.
Also, just as a matter of practice, I'd change:
return (int)((laterDate.getTime()/60000) - (earlierDate.getTime()/60000));
to:
long result = ((laterDate.getTime()/60000) - (earlierDate.getTime()/60000));
return (int) result;
This gives you the opportunity to view the result of the math in an IDE.
I debugged through your code: there is ParseException in GetItemDate() for both of the date strings like:
Unparseable date: 11/21/2011 07:00:00 AM
The problem is that parsing AM/PM hours only works with "hh" (small caps!) or "KK". At least that is the case in my phone (it seems some of us didn't see this issue at all). I tested it also works with a single "h" too.
hh = 1-12
KK = 0-11
HH = 0-23
kk = 1-24
Change your SimpleDateFormat line to this:
final SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a", Locale.US);
This post explains the "a" and Locale: Unable to parse DateTime-string with AM/PM marker
PS. I'm from Finland so I must use the "Locale.US" part for this to work. US residents may test the code without it, I think.

Categories

Resources