I am trying to create a method which converts DAY, MONTH, YEAR to Date object in Java
I have
String DAY = "31"
String YEAR = "2012"
String MONTH = "11"
I need a configurable Format as output.
String format = "MM/dd/yyy";
I will always get DAY string from "01" to "31" only
I will always get YEAR string from "1000" to "9999" only
I will always get MONTH string from "01" to "12" only (Never get Jan, Feb etc)
I understand that SimpleDateFormat(format) can work up to some extend.
new SimpleDateFormat("MM/dd/yyyy") will parse "02/01/2010" as Feb 01 2010 but
new SimpleDateFormat("mm/dd/yyyy") will parse "02/01/2010" as Jan 01 2010
Is it possible to write a generic method in java which converts given Strings (DAY, MONTH, YEAR) to Date object based on a configurable pattern?? and which can throw exception is I supply a wrong combination of DAY, MONTH, YEAR (like DAY = "31", MONTH = "02", YEAR = "2010")
Something like :
Date dateParser(String format){
String DAY = "01";
String YEAR = "1922";
String MONTH = "02";
return date;
}
There are basically two ways:
Parse each string to a number and put the numbers together to a date.
Put the strings together to one string and parse it into a date.
Some middle forms are thinkable, but I recommend you take a pure stance and use one of the ways only. The answer by Arvind Kumar Avinash shows option 1. My taste is rather for option 2., so let me demonstrate.
I recommend you use java.time, the modern Java date and time API for your date work.
String format = "MM/dd/yyy";
String day = "31";
String year = "2012";
String month = "11";
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(format, Locale.US);
String isoDateString = year + '-' + month + '-' + day;
LocalDate date = LocalDate.parse(isoDateString);
String formattedDate = date.format(dateFormatter);
System.out.println(formattedDate);
Since there are only 30 days in November (month 11), this code very sensibly throws an exception:
Exception in thread "main" java.time.format.DateTimeParseException:
Text '2012-11-31' could not be parsed: Invalid date 'NOVEMBER 31'
That is, we have got input validation for free. For a valid date the code will parse the date and format as requested.
Link
Oracle tutorial: Date Time explaining how to use java.time.
I recommend you switch from the outdated and error-prone java.util date-time API and SimpleDateFormat to the modern java.time date-time API and the corresponding formatting API (package, java.time.format). Learn more about the modern date-time API from Trail: Date Time.
Note that mm is used for minute; not for month. For month, you use MM. Check this for more information.
Using the modern date-time API:
import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
// Test
System.out.println(getLocalDate("2010", "02", "28"));
System.out.println(getLocalDate("2010", "02", "31"));
}
static LocalDate getLocalDate(String year, String month, String dayOfMonth) {
return LocalDate.of(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(dayOfMonth));
}
}
Output:
2010-02-28
Exception in thread "main" java.time.DateTimeException: Invalid date 'FEBRUARY 31'
at java.base/java.time.LocalDate.create(LocalDate.java:459)
at java.base/java.time.LocalDate.of(LocalDate.java:271)
at Main.getLocalDate(Main.java:11)
at Main.main(Main.java:7)
Note that a date-time object is supposed to store the information about date, time, time-zone etc. but not about the formatting. When you print an object of a date-time type, its is supposed to print what its toString method returns. If you need to print the date-time in a different format, you need a formatting class (e.g. DateTimeFormatter) which can return you a string in your desired pattern e.g.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
// Test
System.out.println(getFormattedLocalDate("2010/02/28", "yyyy/MM/dd", "EEEE dd MMMM yyyy"));
System.out.println(getFormattedLocalDate("28/02/2010", "dd/MM/yyyy", "yyyy MMM EEE dd"));
}
static String getFormattedLocalDate(String date, String inputPattern, String outputPattern) {
return LocalDate.parse(date, DateTimeFormatter.ofPattern(inputPattern))
.format(DateTimeFormatter.ofPattern(outputPattern));
}
}
Output:
Sunday 28 February 2010
2010 Feb Sun 28
Related
The function shown below returns the date, e.g. "Sat Sep 8 00:00 PDT 2010". But I expected to get the date in the following format "yyyy-MM-dd HH:mm". What's wrong in this code?
String date = "2010-08-25";
String time = "00:00";
Also in one laptop the output for,e.g. 23:45 is 11:45. How can I define exactly the 24 format?
private static Date date(final String date,final String time) {
final Calendar calendar = Calendar.getInstance();
String[] ymd = date.split("-");
int year = Integer.parseInt(ymd[0]);
int month = Integer.parseInt(ymd[1]);
int day = Integer.parseInt(ymd[2]);
String[] hm = time.split(":");
int hour = Integer.parseInt(hm[0]);
int minute = Integer.parseInt(hm[1]);
calendar.set(Calendar.YEAR,year);
calendar.set(Calendar.MONTH,month);
calendar.set(Calendar.DAY_OF_MONTH,day);
calendar.set(Calendar.HOUR,hour);
calendar.set(Calendar.MINUTE,minute);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
Date d = calendar.getTime();
String dateString= dateFormat.format(d);
Date result = null;
try {
result = (Date)dateFormat.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
return result;
}
What's wrong in this code?
You seem to be expecting the returned Date object to know about the format you've parsed it from - it doesn't. It's just an instant in time. When you want a date in a particular format, you use SimpleDateFormat.format, it's as simple as that. (Well, or you use a better library such as Joda Time.)
Think of the Date value as being like an int - an int is just a number; you don't have "an int in hex" or "an int in decimal"... you make that decision when you want to format it. The same is true with Date.
(Likewise a Date isn't associated with a specific calendar, time zone or locale. It's just an instant in time.)
How did you print out the return result? If you simply use System.out.println(date("2010-08-25", "00:00") then you might get Sat Sep 8 00:00 PDT 2010 depending on your current date time format setting in your running machine. But well what you can do is:
Date d = date("2010-08-25", "00:00");
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(d));
Just curious why do you bother with this whole process as you can simple get the result by concatenate your initial date and time string.
just use SimpleDateFormat class
See
date formatting java simpledateformat
The standard library does not support a formatted Date-Time object.
The function shown below returns the date, e.g. "Sat Sep 8 00:00 PDT
2010". But I expected to get the date in the following format
"yyyy-MM-dd HH:mm".
The standard Date-Time classes do not have any attribute to hold the formatting information. Even if some library or custom class promises to do so, it is breaking the Single Responsibility Principle. A Date-Time object is supposed to store the information about Date, Time, Timezone etc., not about the formatting. The only way to represent a Date-Time object in the desired format is by formatting it into a String using a Date-Time parsing/formatting type:
For the modern Date-Time API: java.time.format.DateTimeFormatter
For the legacy Date-Time API: java.text.SimpleDateFormat
About java.util.Date:
A java.util.Date object simply represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). Since it does not hold any timezone information, its toString function applies the JVM's timezone to return a String in the format, EEE MMM dd HH:mm:ss zzz yyyy, derived from this milliseconds value. To get the String representation of the java.util.Date object in a different format and timezone, you need to use SimpleDateFormat with the desired format and the applicable timezone e.g.
Date date = new Date(); // In your case, it will be Date date = date("2010-08-25", "00:00");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH);
// sdf.setTimeZone(TimeZone.getTimeZone("America/New_York")); // For a timezone-specific value
String strDate = sdf.format(date);
System.out.println(strDate);
Your function, Date date(String, String) is error-prone.
You can simply combine the date and time string with a separator and then use SimpleDateFormat to parse the combined string e.g. you can combine them with a whitespace character as the separator to use the same SimpleDateFormat shown above.
private static Date date(final String date, final String time) throws ParseException {
return sdf.parse(date + " " + time);
}
Note that using a separator is not a mandatory requirement e.g. you can do it as sdf.parse(date + time) but for this, you need to change the format of sdf to yyyy-MM-ddHH:mm which, although correct, may look confusing.
Demo:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class Main {
static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH);
public static void main(String[] args) throws ParseException {
Date date = date("2010-08-25", "00:00");
String strDate = sdf.format(date);
System.out.println(strDate);
}
private static Date date(final String date, final String time) throws ParseException {
return sdf.parse(date + " " + time);
}
}
Output:
2010-08-25 00:00
ONLINE DEMO
Switch to java.time API.
The java.util Date-Time API 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*.
Solution using java.time, the modern Date-Time API:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
LocalDateTime ldt = localDateTime("2010-08-25", "00:00");
// Default format i.e. the value of ldt.toString()
System.out.println(ldt);
// Custom format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm", Locale.ENGLISH);
String strDate = dtf.format(ldt);
System.out.println(strDate);
}
private static LocalDateTime localDateTime(final String date, final String time) {
return LocalDateTime.of(LocalDate.parse(date), LocalTime.parse(time));
}
}
Output:
2010-08-25T00:00
2010-08-25 00:00
ONLINE DEMO
You must have noticed that I have not used DateTimeFormatter for parsing the String date and String time. It is because your date and time strings conform to the ISO 8601 standards. The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards.
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. 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.
I'm surprise you are getting different date outputs on the different computers. In theory, SimpleDateFormat pattern "H" is supposed to output the date in a 24h format. Do you get 11:45pm or 11:45am?
Although it should not affect the result, SimpleDateFormat and Calendar are Locale dependent, so you can try to specify the exact locale that you want to use (Locale.US) and see if that makes any difference.
As a final suggestion, if you want, you can also try to use the Joda-Time library (DateTime) to do the date manipulation instead. It makes it significantly easier working with date objects.
DateTime date = new DateTime( 1991, 10, 13, 23, 39, 0);
String dateString = new SimpleDateFormat("yyyy-MM-dd HH:mm").format( date.toDate());
DateTime newDate = DateTime.parse( dateString, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"));
I'm having issues with the below code displays Thursday as the dayOfTheWeek regardless of the date. Any ideas where I've gone wrong with this?
public void CreatePlan() {
editPlanName = findViewById(R.id.editPlanName);
String plan_name = editPlanName.getText().toString();
DatabaseManager db;
int day = datepicker.getDayOfMonth();
int month = datepicker.getMonth();
int year = datepicker.getYear();
SimpleDateFormat sdf = new SimpleDateFormat("EEEE");
Integer d_name = day;
Integer plan_recipe = 0;
Log.d("Date", String.valueOf(d_name));
String dayOfTheWeek = sdf.format(d_name);
String date = day + "/" + month + "/" +year;
db = new DatabaseManager(getApplicationContext());
Log.d("Recipe name", recipe_name);
db.createPlanRecipe(d_name, date, dayOfTheWeek, recipe_name);
db.createPlan(plan_name, plan_recipe);
}
… Any ideas where I've gone wrong with this?
day in your program is the day of the month from 1 to 31. Therefore d_name holds this number too.
Your SimpleDateFormat accepts formatting a number as a date and time expecting a count of milliseconds since the epoch of January 1, 1970 at 00:00 in UTC. So it will always format a date and time within the first 31 milliseconds after the epoch. Depending on your time zone the point in time that you format falls either on Wednesday, December 31, 1969 or on Thursday, January 1, 1970. So you will either always get Wednesday or always Thursday.
SimpleDateFormat.format(Object) accepts either a Date or a Number. Since Integer is a subclass of Number, it works as described.
The SimpleDateFormat class is notoriously troublesome, you have seen but a small corner of the problems that people often have with it. The Calendar class used in one other answer is poorly designed too. Both are long outdated. I suggest you look into java.time, the modern Java date and time API instead.
Further link: My answer to another question about getting the day of week from an Android date picker.
You are getting the value of the day-of-the-month, the month and the year in the following lines of code but you are not setting these values into the Calendar object which is supposed to give you other information (e.g. the day-of-week) by processing these values:
int day = datepicker.getDayOfMonth();
int month = datepicker.getMonth();
int year = datepicker.getYear();
So, before you try to get any other information from the Calendar object, set these values to the object as shown below:
// Set the picked values into an instance of Calendar
Calendar calendar = Calendar.getInstance();
calendar.clear();// Make sure to call this to reset all fields
calendar.set(year, month - 1, day);// Make sure to decrease month by 1
Now, your rest of code will work as you are expecting e.g. let's say you select 4 as the day-of-the-month, 10 as the month, and 2020 as the year, the following code will give you Sunday as the day-of-the-week.
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class Main {
public static void main(String[] args) {
int day = 4;
int month = 10;
int year = 2020;
// Set the picked values into an instance of Calendar
Calendar calendar = Calendar.getInstance();
calendar.clear();// Make sure to call this to reset all fields
calendar.set(year, month - 1, day);// Make sure to decrease month by 1
System.out.println(calendar.getTime());
// Your desired format
SimpleDateFormat sdf = new SimpleDateFormat("EEEE");
// The day-of-the-week for the specified date
String dayOfTheWeek = sdf.format(calendar.getTime());
System.out.println(dayOfTheWeek);
}
}
Output:
Sun Oct 04 00:00:00 BST 2020
Sunday
Note that I have decreased the month (picked from the date-picker) by 1 because java.util date-time API is based on 0 as the month of January.
A piece of advice:
I recommend you switch from the outdated and error-prone java.util date-time API and SimpleDateFormat to the modern java.time date-time API and the corresponding formatting API (from the package, java.time.format). Learn more about the modern date-time API from Trail: Date Time.
If 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.
By using the modern date-time API:
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
int dayOfMonth = 4;
int month = 10;
int year = 2020;
// Instantiate a LocalDate object using the picked values
LocalDate date = LocalDate.of(year, month, dayOfMonth);
// The day-of-the-week for the specified date
String dayOfTheWeek = date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH);
System.out.println(dayOfTheWeek);
}
}
Output:
Sunday
You have to create Date from your datepicker, then format it to find day like below:
int day = datePicker.getDayOfMonth();
int month = datePicker.getMonth();
int year = datePicker.getYear();
Calendar calendar = Calendar.getInstance();
calendar.set(year, month - 1, day);
SimpleDateFormat sdf = new SimpleDateFormat("EEEE");
dayOfTheWeek = sdf.format(calendar.getTime());
I have a string with both year-week e.g. "2015-40" and year-month formats e.g. "2015-08", that would like to transform into LocalDate in Scala.
I have tried using
val date = "2015-40"
val formatter = DateTimeFormatter.ofPattern("yyyy-ww")
LocalDate.parse(date, formatter)
but end up with DateTimeParseException error. Would appreciate any help on how to do this
Thanks in advance
String date = "2015-40";
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("YYYY-ww")
.parseDefaulting(ChronoField.DAY_OF_WEEK, DayOfWeek.MONDAY.getValue())
.toFormatter(Locale.FRANCE);
LocalDate ld = LocalDate.parse(date, formatter);
System.out.println(ld);
Sorry that I can write only Java, I trust you to translate to Scala. Output is:
2015-09-28
Why were you getting an exception? There were a couple of things missing for us to parse 2015-40 into a LocalDate:
A LocalDate is a calendar date, and week 40 consists of 7 days. Java doesn’t know which of those 7 days you want and refuses to make the choice for you. In my code above I have specified Monday. Any other day of the week should work.
A bit subtler. While to a human year 2015 and week 40 is unambiguous, the same is not always the case around New Year where week 1 may begin before New Year or week 52 or 53 extend after New Year. So a calendar year and a week number don’t always define one specific week. Instead we need the concept of a week year or week-based year. A week year lasts from week 1 inclusive no matter if that means it begins a few days before or after New Year. And it lasts until the last day of the last week, again most often a few days before or after New Year. To tell a DateTimeFormatter that we want to parse (or print) the week year we need to use upper case YYYY instead of lower case yyyy (or uuuu).
As an aside, if you can influence the format, consider 2015-W40 with a W. This is ISO 8601 format for year and week. In ISO 2015-12 denotes year and month, and many will read it this way. So to disambiguate and avoid misreading.
Edit:
In my explanation I have assumed ISO week scheme (Monday is the first day of week and week 1 is defined as the first week having at least 4 days in the new year). You may pass a different locale to the formatter builder to obtain a different week scheme.
If you are sure that your weeks follow ISO, Andreas’ suggestion in the comment is good enough that we want as part of the answer:
Alternatively, add the ThreeTen Extra library, so you can use the
YearWeek
class, that has a nice atDay(DayOfWeek dayOfWeek)
method for getting a LocalDate.
Link: Wikipedia article: ISO 8601
LocalDate has three parts: year, month and day of the month. So, in the case of year-month string, you will have to get the LocalDate on a specific day as per your requirement. Parsing the year-month string is straight forward as shown in the demo code.
In the case of year-week string, you will have to get the LocalDate on a specific day of the week e.g. Mon or today's day etc. Also, instead of parsing the string directly, I found it easier to get the year and week and then use the methods LocalDate to get the required instance of LocalDate.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
public class Main {
public static void main(String[] args) {
//#################### Year-Month #######################
// Given year-month string
var yearMonthStr = "2015-08";
// LocalDate parsed from yearMonthStr and on the 1st day of the month
LocalDate date2 = YearMonth.parse(yearMonthStr, DateTimeFormatter.ofPattern("u-M")).atDay(1);
System.out.println(date2);
// LocalDate parsed from yearMonthStr and on the last day of the month
date2 = YearMonth.parse(yearMonthStr, DateTimeFormatter.ofPattern("u-M")).atEndOfMonth();
System.out.println(date2);
// LocalDate parsed from yearMonthStr and on specific day of the month
date2 = YearMonth.parse(yearMonthStr, DateTimeFormatter.ofPattern("u-M")).atDay(1).withDayOfMonth(10);
System.out.println(date2);
//#################### Year-Week #######################
// Given year-week string
var yearWeekStr = "2015-40";
// Split the string on '-' and get year and week values
String[] parts = yearWeekStr.split("-");
int year = Integer.parseInt(parts[0]);
int week = Integer.parseInt(parts[1]);
// LocalDate with year, week and today's day e.g. Fri
LocalDate date1 = LocalDate.now()
.withYear(year)
.with(WeekFields.ISO.weekOfYear(), week);
System.out.println(date1);
// LocalDate with year, week and next Mon (or same if today is Mon)
date1 = LocalDate.now()
.withYear(year)
.with(WeekFields.ISO.weekOfYear(), week)
.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
System.out.println(date1);
// LocalDate with year, week and today's day previous Mon (or same if today is Mon)
date1 = LocalDate.now()
.withYear(year)
.with(WeekFields.ISO.weekOfYear(), week)
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
System.out.println(date1);
}
}
Output:
2015-08-01
2015-08-31
2015-08-10
2015-10-02
2015-10-05
2015-09-28
I'm trying to use Java 8 to re-format today's date but I'm getting the following error:
java.time.format.DateTimeParseException: Text '09-OCT-2017' could not be parsed:
Unable to obtain LocalDate from TemporalAccessor:
{WeekBasedYear[WeekFields[SUNDAY,1]]=2017, MonthOfYear=10, DayOfYear=9},ISO of type java.time.format.Parsed
Code:
public static String formatDate(String inputDate, String inputDateFormat, String returnDateFormat){
try {
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern(inputDateFormat).toFormatter(Locale.ENGLISH);
LocalDate localDate = LocalDate.parse(inputDate, inputFormatter);
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(returnDateFormat);
String formattedString = localDate.format(outputFormatter);
return formattedString;
} catch (DateTimeParseException dtpe) {
log.error("A DateTimeParseException exception occured parsing the inputDate : " + inputDate + " and converting it to a " + returnDateFormat + " format. Exception is : " + dtpe);
}
return null;
}
I previously tried using SimpleDateFormat, but the problem is my inputDateFormat format is always in uppercase DD-MMM-YYYY, which was giving me incorrect results, so I tried using parseCaseInsensitive() to ignore the case sensitivity.
In the comments you told that the input format is DD-MMM-YYYY. According to javadoc, uppercase DD is the day of year field, and YYYY is the week based year field (which might be different from the year field).
You need to change them to lowercase dd (day of month) and yyyy (year of era). The parseCaseInsensitive() only takes care of the text fields - in this case, the month name (numbers are not affected by the case sensitivity - just because the month is in uppercase, it doesn't mean that the numbers patterns should also be).
The rest of the code is correct. Example (changing the format to yyyyMMdd):
String inputDate = "09-OCT-2017";
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
// use "dd" for day of month and "yyyy" for year
.appendPattern("dd-MMM-yyyy")
.toFormatter(Locale.ENGLISH);
LocalDate localDate = LocalDate.parse(inputDate, inputFormatter);
// use "dd" for day of month and "yyyy" for year
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String formattedString = localDate.format(outputFormatter);
System.out.println(formattedString); // 20171009
The output of the code above is:
20171009
Regarding your other comment about not having control over the input pattern, one alternative is to manually replace the letters to their lowercase version:
String pattern = "DD-MMM-YYYY";
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
// replace DD and YYYY with the lowercase versions
.appendPattern(pattern.replace("DD", "dd").replaceAll("YYYY", "yyyy"))
.toFormatter(Locale.ENGLISH);
// do the same for output format if needed
I don't think it needs a complex-replace-everything-in-one-step regex. Just calling the replace method multiple times can do the trick (unless you have really complex patterns that would require lots of different and complex calls to replace, but with only the cases you provided, that'll be enough).
I hope I got you right.
Formatting a String to LocalDate is acutally pretty simple. Your date format is that here right 09-Oct-2017?
Now you just need use the split command to divide that into a day, month and year:
String[] tempStr = inputDate.split("-");
int year = Integer.parseInt(tempStr[2]);
int month = Integer.parseInt(tempStr[1]);
int day = Integer.parseInt(tempStr[0]);
After that it´s pretty easy to get that to LocalDate:
LocalDate ld = LocalDate.of(year, month, day);
I hope that helps.
One of our customers wants to be able to enter a date with only 2 digits for the year component. The date will be in the past, so we want it to work for the previous century if the 2 digit year is after the current year, but work for the current century if the 2 digit year is equal to or less than the current year.
as of today 10/30/2008
01/01/01 = 01/01/2001
01/01/09 = 01/01/1909
This is a strange requirement, and I solved the problem, I just don't like my solution.
It feels like there is a better way to do this.
Thanks for the help.
public static String stupidDate(String dateString)
{
String twoDigitYear = StringUtils.right(dateString, 2);
String newDate = StringUtils.left(dateString, dateString.length() - 2);
int year = NumberUtils.toInt(twoDigitYear);
Calendar c = GregorianCalendar.getInstance();
int centuryInt = c.get(Calendar.YEAR) - year;
newDate = newDate + StringUtils.left(Integer.toString(centuryInt), 2) + twoDigitYear;
return newDate;
}
Groovy script (easy enough to throw into java) demonstrating the point #bobince made about SimpleDateFormat.
import java.text.SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat('MM/dd/yy')
SimpleDateFormat fmt = new SimpleDateFormat('yyyy-MM-dd')
Calendar cal = Calendar.getInstance()
cal.add(Calendar.YEAR, -100)
sdf.set2DigitYearStart(cal.getTime())
dates = ['01/01/01', '10/30/08','01/01/09']
dates.each {String d ->
println fmt.format(sdf.parse(d))
}
Yields
2001-01-01
2008-10-30
1909-01-01
SimpleDateFormat already does two-digit year parsing for you, using the two-letter ‘yy’ format. (It'll still allow four digits, obviously.)
By default it uses now-80→now+20, so it's not exactly the same rule you propose, but it's reasonable and standardised (in the Java world at least), and can be overridden using set2DigitYearStart() if you want.
DateFormat informat= new SimpleDateFormat("MM/dd/yy");
DateFormat outformat= new SimpleDateFormat("MM/dd/yyyy");
return outformat.format(informat.parse(dateString));
In the longer term, try to migrate to ISO8601 date formatting (yyyy-MM-dd), because MM/dd/yy is approximately the worst possible date format and is bound to cause problems eventually.
How about this:
public static String anEasierStupidDateWithNoStringParsing(String dateString) {
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
//handling ParseExceptions is an exercise left to the reader!
Date date = df.parse(dateString);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
Calendar now = Calendar.getInstance();
if (cal.after(now)) {
cal.add(Calendar.YEAR, -100);
}
return cal;
}
In other words, let SimpleDateFormat parse the String and just adjust the year to be the previous century if SimpleDateFormat (which has it's own rules for interpreting year strings) returns a date that is after the current date.
This would guarantee that all dates returned are in the past. However, it doesn't account for any dates that might be parsed as before this past century - for example, with the format MM/dd/yyyy, a date string like "01/11/12" parses to Jan 11, 12 A.D.
If Joda Time is an option:
String inputDate = "01/01/08";
// assuming U.S. style date, since it's not clear from your original question
DateTimeFormatter parser = DateTimeFormat.forPattern("MM/dd/yy");
DateTime dateTime = parser.parseDateTime(inputDate);
// if after current time
if (dateTime.isAfter(new DateTime())) {
dateTime = dateTime.minus(Years.ONE);
}
return dateTime.toString("MM/dd/yyyy");
I know Joda Time isn't part of Java SE, and as I've said in another thread, I usually do not condone using a third-party library when there's a Java library that does the same thing. However, the person who is developing Joda Time is also leading JSR310 - the Date and Time API that'll make it into Java 7. So I Joda Time is basically going to be in future Java releases.
The accepted answer uses legacy date-time API which was the correct thing to do in 2008 when the question was asked. In March 2014, java.time API supplanted the error-prone legacy date-time API. Since then, it has been strongly recommended to use this modern date-time API.
java.time API
You can put optional patterns between DateTimeFormatterBuilder#optionalStart and DateTimeFormatterBuilder#optionalEnd and create a formatter which can parse a date string with either a four-digit year or a two-digit year.
Using the DateTimeFormatterBuilder#appendValueReduced, you can specify a base value for the year as per your requirement.
Demo:
import java.time.LocalDate;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.appendPattern("M/d/")
.optionalStart()
.appendPattern("uuuu")
.optionalEnd()
.optionalStart()
.appendValueReduced(ChronoField.YEAR, 2, 2, Year.now().minusYears(100).getValue())
.optionalEnd()
.toFormatter(Locale.ENGLISH);
// Test
Stream.of(
"1/2/2022",
"01/2/2022",
"1/02/2022",
"01/02/2022",
"1/2/22",
"1/2/21",
"1/2/20",
"1/2/23",
"1/2/24"
)
.map(s -> LocalDate.parse(s, parser))
.forEach(System.out::println);
}
}
Output:
2022-01-02
2022-01-02
2022-01-02
2022-01-02
1922-01-02
2021-01-02
2020-01-02
1923-01-02
1924-01-02
Note that the dates with a two-digit year greater than the current year are parsed into a LocalDate with the last century.
How to switch from the legacy to the modern date-time API?
You can switch from the legacy to the modern date-time API using Date#toInstant on a java-util-date instance. Once you have an Instant, you can easily obtain other date-time types of java.time API. An Instant represents a moment in time and is independent of a time-zone i.e. it represents a date-time in UTC (often displayed as Z which stands for Zulu-time and has a ZoneOffset of +00:00).
Demo:
public class Main {
public static void main(String[] args) {
Date date = new Date();
Instant instant = date.toInstant();
System.out.println(instant);
ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Kolkata"));
System.out.println(zdt);
OffsetDateTime odt = instant.atOffset(ZoneOffset.of("+05:30"));
System.out.println(odt);
// Alternatively, using time-zone
odt = instant.atZone(ZoneId.of("Asia/Kolkata")).toOffsetDateTime();
System.out.println(odt);
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Kolkata"));
System.out.println(ldt);
// Alternatively,
ldt = instant.atZone(ZoneId.of("Asia/Kolkata")).toLocalDateTime();
System.out.println(ldt);
}
}
Output:
2022-11-20T20:32:42.823Z
2022-11-21T02:02:42.823+05:30[Asia/Kolkata]
2022-11-21T02:02:42.823+05:30
2022-11-21T02:02:42.823+05:30
2022-11-21T02:02:42.823
2022-11-21T02:02:42.823
Learn more about the modern Date-Time API from Trail: Date Time.
Date deliverDate = new SimpleDateFormat("MM/dd/yy").parse(deliverDateString);
String dateString2 = new SimpleDateFormat("yyyy-MM-dd").format(deliverDate);
Working for me.