I'm trying to validate a given date, but it's not working how I would like it. The user inputs a date, and it gets parsed and passed to an array, and I'm trying to validate the date is a correct date(taking into account leap years, Feb, etc) without making the code extremely lengthy.
Calendar calendar = new GregorianCalendar(getValidYear(), getValidMonth() - 1, getValidDay());
if( validYear < 2010 )
{
throw new InvalidDateException("Year must be greater than 2009");
} else {
if(validMonth < 1 || validMonth > 12 )
{
throw new InvalidDateException("Month value must be greater than or equal to 1 or less than or eqaul to 12");
} else {
if(validDay > calendar.getActualMaximum(Calendar.DAY_OF_MONTH) || validDay <= 0)
{
throw new InvalidDateException(String.format ("Day value must be greater than 0 and less than or equal to %s ",
calendar.getActualMaximum(Calendar.DAY_OF_MONTH) ) );
}
}//end nested if month
}//end nested IF year
}
If I put 2018/02/33 in and have it print for the exception it shows the date as March 05, 2018 and Im not exactly sure where these numbers are coming from. The code to parse the date is
String dateGiven[] = validDate.split("/");
validYear = Integer.parseInt(dateGiven[0]);
validMonth = Integer.parseInt( dateGiven[1] );
validDay = Integer.parseInt( dateGiven[2] );
And when I build the string to show the date it prints correctly, but it is not working with Calendar and Im not sure what Im doing wrong. Any help is appreciated!
It would be easier to use Java 8 time features:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date = LocalDate.parse("2018/02/33", formatter);
will result in a DateTimeException when date is invalid:
java.time.DateTimeException: Invalid value for DayOfMonth (valid values 1 - 28/31): 33
Calendar is lenient by default. Quoting from the documentation of its method setLenient:
With lenient interpretation, a date such as "February 942, 1996" will
be treated as being equivalent to the 941st day after February 1,
1996. With strict (non-lenient) interpretation, such dates will cause an exception to be thrown. The default is lenient.
If you want to receive an exception if an invalid date is set on the Calendar then use setLenient(false);.
Otherwise 2018/02/33 is going to be interpreted as 32 days after February 1, which is March 5.
Related
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.
I have put together a method that is supposed to check if a given date is within a certain lower and upper limit.
It shouldn't consider the time, only the dates. It seemed to be a quite simple task but I am getting some strange behavior when debugging it.
I have tried different approaches for removing the time value from the input dates. (inDate.) but they all seem to give me the same unexpected behavior of putting my inLeft date before inLeft and thus making the function result as false.
As you can see from the screenshot inLeft and inLeft are equal (if we ignore time) so why is int leftLimit = DateTimeComparator.getDateOnlyInstance().compare(inLeft, inDate); resulting in 1 rather that 0?
My conclusion is that I am doing something wrong but cannot find what. Please forgive me if this is something totally obvious.
Here is my Code:
protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
System.out.println("inDate = " + inDate);
System.out.println("inLeft = " + inLeft);
System.out.println("inRight = " + inRight);
int leftLimit = DateTimeComparator.getDateOnlyInstance().compare(inLeft, inDate);
int rightLimit = DateTimeComparator.getDateOnlyInstance().compare(inDate, inRight);
System.out.println("leftLimit = " + leftLimit);
System.out.println("rightLimit = " + rightLimit);
if (leftLimit > 0 || rightLimit > 0) {
return false;
}
return true;
}
I/System.out: inDate = Mon Sep 26 00:00:00 GMT+02:00 2016
I/System.out: inLeft = Mon Sep 26 20:14:13 GMT+02:00 2016
I/System.out: inRight = Mon Sep 26 00:00:00 GMT+02:00 2016
I/System.out: leftLimit = 1
I/System.out: rightLimit = 0
The problem is that leftLimit == 1, instead of 0, inDate and inLeft without the time are the same. but leftLimit still results 1.
Edit (Final Solution):
According to sumandas' and Roberts solution i have written a new method that gives me the expected behaviour.
New code:
protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
SimpleDateFormat simpleDateFormatter = new SimpleDateFormat("yyyy-MM-dd");
String dateString = simpleDateFormatter.format(inDate);
String leftString = simpleDateFormatter.format(inLeft);
String rightString = simpleDateFormatter.format(inRight);
if (leftString.compareTo(dateString) > 0 || dateString.compareTo(rightString) > 0)
return false;
return true;
}
I still don't understand why my initial solution should not work.
Try this:
String date1 = "2014/09/12"
String date2 = "2016/09/12"
SimpleDateFormat simpleDateFormatter = new SimpleDateFormat("yyyy-MM-dd");
Date inDate = simpleDateFormatter.parse(date1);
Date inLeft = simpleDateFormatter.parse(date2);
Date rightDate = simpleDateFormatter.parse(date2);
Pass of of these to the function as below and do the comparison:
protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
if (inDate.before(inLeft) || inDate.after(inRight)) {
// Do something
}
}
My two cents, I tried using Joda Time earlier and due to too many if else I switched to simpledateformat, but joda time helps a lot in ISO date formats.
Why the proposed solution didn't work:
If you read the documentation it says LHS < RHS returns -ve
Else returns +ve
Now with your example, getDateOnlyInstance returns only date where
LHS = Sep 26 2016 = RHS and returns a 1. // Hope this helps.
Based on the comments, I'm assuming you're using joda-time API (I used version 2.7 for this test).
To explain why your first attempt doesn't work, I've made a test. First, I created the equivalent DateTime objects for your test inputs:
// equivalent date/times in GMT+02:00
DateTime in = new DateTime(2016, 9, 26, 0, 0, 0, 0, DateTimeZone.forOffsetHours(2));
DateTime left = new DateTime(2016, 9, 26, 20, 14, 13, 0, DateTimeZone.forOffsetHours(2));
DateTime right = new DateTime(2016, 9, 26, 0, 0, 0, 0, DateTimeZone.forOffsetHours(2));
Then I called your method:
// using toDate to convert org.joda.time.DateTime to java.util.Date
checkDateInRangeWrong(in.toDate(), left.toDate(), right.toDate());
And the output I've got:
inDate = Sun Sep 25 19:00:00 BRT 2016
inLeft = Mon Sep 26 15:14:13 BRT 2016
inRight = Sun Sep 25 19:00:00 BRT 2016
Note that inDate's day is 25 (instead of 26), and time is 19 (instead of 00). Also note that the timezone is BRT (instead of GMT+02:00).
This happens because when DateTimeComparator.compare() is called with java.util.Date instances, it uses the default timezone to convert the objects.
As my default timezone (java.util.TimeZone.getDefault()) is "America/Sao_Paulo" (or BRT - Brazil Standard Time), the date/time 26/09 at 00:00 GMT+02:00 is converted to 25/09 at 19:00 in BRT.
So the code is actually comparing leftDate (26/09) with inDate (25/09), and that's why leftLimit is 1.
Depending on how you're creating the variables inDate, leftDate and rightDate, there might be variations during the day (so the code might work during only some part of the day, for example). And there could also be differences during Daylight Saving Time (when hours move forward or back 1 hour), which can also cause a shift in the day.
And your new code (using SimpleDateFormat) also doesn't work for me (it has the same problem, as SimpleDateFormat also uses the default timezone) - and the same error occurs if I create the Date objects with java.util.Calendar instead of using DateTime.toDate().
So, to solve this, I've used org.joda.time.LocalDate class (a date without the time fields), because I just need to compare the dates (day/month/year) and ignore the time (hour/minute/second). I used the constructor that receives the timezone, so I don't depend on the system's default and I can be sure that I'm always working on the same timezone.
protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
// convert inputs to LocalDate, using the specified timezone
LocalDate left = new LocalDate(inLeft, DateTimeZone.forOffsetHours(2));
LocalDate right = new LocalDate(inRight, DateTimeZone.forOffsetHours(2));
LocalDate date = new LocalDate(inDate, DateTimeZone.forOffsetHours(2));
System.out.println("inDate = " + date);
System.out.println("inLeft = " + left);
System.out.println("inRight = " + right);
int leftLimit = left.compareTo(date);
int rightLimit = date.compareTo(right);
System.out.println("leftLimit = " + leftLimit);
System.out.println("rightLimit = " + rightLimit);
if (leftLimit > 0 || rightLimit > 0) {
return false;
}
return true;
}
Now calling:
checkDateInRange(in.toDate(), left.toDate(), right.toDate());
produces the output:
inDate = 2016-10-26
inLeft = 2016-10-26
inRight = 2016-10-26
leftLimit = 0
rightLimit = 0
true
Notes:
As you're using joda-time, you can make a method that receives a DateTime object (or even better, a LocalDate), instead of using the old error-prone java.util.Date API. Changing to a LocalDate is better because it's making explicit that the method doesn't need the time fields:
protected boolean checkDateInRange(LocalDate inDate, LocalDate inLeft, LocalDate inRight) {
System.out.println("inDate = " + inDate);
System.out.println("inLeft = " + inLeft);
System.out.println("inRight = " + inRight);
int leftLimit = inLeft.compareTo(inDate);
int rightLimit = inDate.compareTo(inRight);
System.out.println("leftLimit = " + leftLimit);
System.out.println("rightLimit = " + rightLimit);
if (leftLimit > 0 || rightLimit > 0) {
return false;
}
return true;
}
// in, left and right are DateTime instances
checkDateInRange(in.toLocalDate(), left.toLocalDate(), right.toLocalDate());
I'm assuming that DateTimeZone.forOffsetHours(2) is the timezone for all your inputs. If it's not the case, I recommend to handle each case accordingly and extract the LocalDate (using the constructor LocalDate(dateObject, DateTimeZone) described above) before doing any comparison.
I have a number of items with dates specified in MM/dd format, with no year specified.
However, the year is implied from the context of the data, since the data shows dates no older than 12 months from the "current date" (which is specified in the data)
For example, let's say the current date is January 31, 2013.
This means that there will be information from February 1 2012 to January 31, 2013, inclusive.
The problem I'm facing here is, because there are no years specified in the data, I will need to generate the years myself before I load them into my database.
From the context of the data, we know that any dates greater than the current month is from the previous year, while any dates less than or equal to the current month is the current year.
So assuming current date is Jan 2013, we have things like
01/31 - 2013
01/01 - 2013
12/31 - 2012
02/29 - 2012
Now, the problem here is the date on the last line.
2012 was a leap year, so February 29 does exist.
However, 2013 is not.
My current approach to date parsing is as follows (using SimpleDateFormat)
Grab the date: 01/31
Append the current year to it: 01/31/2016
Parse the date using date format MM/dd/yyyy
Date parsing is performed under strict mode, so something like 02/29 isn't going to be rolled over to 03/01.
However, this algorithm fails on leap years, because assuming the current year is 2013, I'm going to try to parse 02/29/2013 and it will fail.
What is an approach I can use to determine the year of the date?
Parse the day/month to a java.time.MonthDay, compare it to MonthDay.now() and call atYear with the correct year (using Year.now() for example) depending on the result.
You may also want to take time zones into account.
A simple version applied to your example, assuming that the input is well formed and ignoring time zone issues, could look like:
public static void main(String[] args) {
m("01/31", YearMonth.of(2013, 1));
m("01/01", YearMonth.of(2013, 1));
m("12/31", YearMonth.of(2013, 1));
m("02/29", YearMonth.of(2013, 1));
}
private static void m(String date, YearMonth currentMonth) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd");
MonthDay md = MonthDay.parse(date, fmt);
int year = currentMonth.getYear();
MonthDay cutoffDate = MonthDay.from(currentMonth.atEndOfMonth());
if (md.isAfter(cutoffDate)) year--;
LocalDate result = md.atYear(year);
System.out.println(date + " - " + year + " ==> " + result);
}
which outputs:
01/31 - 2013 ==> 2013-01-31
01/01 - 2013 ==> 2013-01-01
12/31 - 2012 ==> 2012-12-31
02/29 - 2012 ==> 2012-02-29
You cannot do it the way you want. You have to determine the year from the month and day before putting the whole thing into a Date. Here's some pseudocode assuming a date is always within the prior year, and a date equal to today is today and not 1 year ago (adjust as needed):
int cy = current year
int cm = current month
int cd = current day of month
int im = input month
int id = input day of month
int year = (im < cm || im==cm && id <= cd) ? cy : cy-1
try
Date d = new Date(year, im, id) strict
catch
invalid date
So what about doing something like:
Grab the date from your file.
Append current year.
Grab today's date.
Compare today's date with the file's date. If today's date is before, then subtract a year from the files date.
Parse the File Date.
I have following code which checks for the valid format date
private void validateDate(String date){
try {
String validDate = "MM/dd/yyyy";
SimpleDateFormat format = new SimpleDateFormat(validDate);
format.setLenient(false);
Date theDate = new Date();
theDate = format.parse(date);
}
catch (Exception e) {
}
}
I am passing the date value as 06/25/20014. In this year format is wrong and I was expecting that it will throw exception and will go inside catch, but it never happens and it successfully passes the code format.parse(date); due to which my application is not throwing error.
I also debugged the line format.parse(date); and it returns "Fri Jul 31 00:00:00 MST 20015". I am not sure why that line is not throwing any exception.
Thanks for your help!!
In the standard date formats for SimpleDateFormat, the number 'y' 's doesn't necessarily correspond to the number of digits (it isn't a regex). One or two y's is an indicator for a 2 digit year (15, 98, etc.) and 3 or more y's is an indicator for the full year.
If this line did throw an exception, then your program would stop working after year 9999, which is not usually what you want. Sure, you do not expect your program to last this long, but people did not expect to last up to y2k either; so Java's choice not to block at any date seems reasonable.
If you want to check the year is between 1 and 9999, you can just write a if: (see related question I want to get Year, Month, Day, etc from Java Date to compare with Gregorian Calendar date in Java. Is this possible?)
Calendar cal = Calendar.getInstance();
cal.setTime(date);
if (cal.get(Calendar.YEAR) > 9999)
throw new RuntimeException("Blah");
This is actually documented behaviour (at least for the Gregorian calendar, which I assume you will be using unless you explicitely set it to a different calendar):
Year: If the formatter's Calendar is the Gregorian calendar, the
following rules are applied.
For formatting, if the number of pattern
letters is 2, the year is truncated to 2 digits; otherwise it is
interpreted as a number. For parsing, if the number of pattern letters
is more than 2, the year is interpreted literally, regardless of the
number of digits.
And yes, 20015 will probably be a valid year some day ;) If you want to check for exactly 4 digits, you might want to consider using a regular expression matching before parsing.
I can see that the provided date is valid (although 18k years ahead)
public static void validateDate(String date){
try {
String validDate = "MM/dd/yyyy";
SimpleDateFormat format = new SimpleDateFormat(validDate);
format.setLenient(false);
Date theDate = format.parse(date);
Calendar c = new GregorianCalendar();
c.setTime(theDate);
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
int pos = c.get(Calendar.DAY_OF_WEEK);
if (Calendar.WEDNESDAY == pos)
System.out.print("day is Wednesday on " + year + "/" + (month+1) + "/" + day);
}
catch (Exception e) {
System.out.print("Inside exception block " + e);
}
}
prints: day is Wednesday on 20014/6/25
If you need some year validation you can add additional check
if (year > MAX_YEAR) {
// add error here
}
Here is DEMO
I have verified that the date is read correctly from a file, but once I use SimpleDateFormat.format with the pattern "dd/MM/yy" it suddenly adds a month. This leads me to believe lenient mode is calculating the wrong value. But I have no idea what would make it add a full month.
Some example dates I read:
16/09/2013
23/09/2013
30/09/2013
07/10/2013
14/10/2013
21/10/2013
The code used to parse the date (it's a wrapper around Calendar I made):
public static SimpleDateTime parseDate(String date)
{
String[] dateParts = date.split("[-\\.:/]");
int day = Integer.parseInt(dateParts[0]);
int month = Integer.parseInt(dateParts[1]);
int year = Integer.parseInt(dateParts[2]);
return new SimpleDateTime(dag, maand, jaar);
}
The constructor used here:
public SimpleDateTime(int day, int month, int year)
{
date = Calendar.getInstance();
date.setLenient(true);
setDay(day);
setMonth(month);
setYear(year);
}
The setters for day, month and year:
public void setYear(int year)
{
date.set(Calendar.YEAR, year);
}
public void setMonth(int month)
{
date.set(Calendar.MONTH, month);
}
public void setDay(int day)
{
date.set(Calendar.DAY_OF_MONTH, day);
}
And the code used to format the date:
public String toString(String pattern)
{
String output = new SimpleDateFormat(pattern, Locale.getDefault()).format(date.getTime());
return output;
}
where the pattern passed is:
"dd/MM/yy"
Intended to print a date as:
16/09/13
23/09/13
Instead I get:
16/10/13
23/10/13
January is 0 in Java; February is 1 and so on.
See Calendar.JANUARY, Calendar.FEBRUARY.
So when you're reading 1 from the file
you think you read JAN but you read FEB.
You should do: date.set(Calendar.MONTH, month-1); to fix this.
Months are indexed from 0 not 1 so 10 is November and 11 will be December.
Calendar.MONTH
From documentation:
Field number for get and set indicating the month. This is a calendar-specific value. The first month of the year is JANUARY; the last depends on the number of months in a year.
So if you check JANUARY you see it starts in zero.
Make sure your month is in the interval 0-11. Possibly it is in 1-12.
The reason for this is that the counting starts at 0.
January == 0
February == 1
and so on. See the documentation.
THe problem is that you pass 9 to SimpleDateFormat and since month are indexed from 0 to 11 it will parse month '9' as the 10th month.
You need to subtract 1 from the month :)
Calendar class in Java holds months starting from 0, hence when you set the month as 0, it would consider it as January. SimpleDateFormat provides for a way to correctly display the value as 01.
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 0);
System.out.println(new SimpleDateFormat("dd/MM/yy").format(cal.getTime()));
Output:
29/01/14
The workaround for you to align you file that Calendar can work with (since December - or 12 would trickle over to the next year) or modify your logic to pick Constants like:
cal.set(Calendar.MONTH, Calendar.JANUARY);
The answer by peter.petrov is almost correct, except for one major problem. Like your question, it neglects to account for time zone.
For your information, this kind of work is much easier in Joda-Time (or new java.time.* classes in Java 8). Joda-Time is so much cleaner you won't even feel the need to create a wrapper class.
// Specify the time zone for which the incoming date is intended.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Brussels" );
String input = "16/09/2013";
DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy").withZone( timeZone );
DateTime dateTime = formatter.parseDateTime( input );
String output = formatter.print( dateTime );
Dump to console…
System.out.println( "dateTime: " + dateTime );
System.out.println( "output: " + output );
System.out.println( "millis since Unix epoch: " + dateTime.getMillis() );
When run…
dateTime: 2013-09-16T00:00:00.000+02:00
output: 16/09/2013
millis since Unix epoch: 1379282400000