Parsing string to local date doesn't use desired century
Since this question is really about new java.time
-package and NOT SimpleDateFormat
I will cite following relevant section:
Year: The count of letters determines the minimum field width below
which padding is used. If the count of letters is two, then a reduced
two digit form is used. For printing, this outputs the rightmost two
digits. For parsing, this will parse using the base value of 2000,
resulting in a year within the range 2000 to 2099 inclusive.
We see that Java-8 uses the range 2000-2099 per default, not like SimpleDateFormat
the range -80 years until +20 years relative to today.
If you want to configure it then you have to use appendValueReduced(). This is designed in an inconvenient way, but possible, see here:
String s = "150790";
// old code with base range 2000-2099
DateTimeFormatter dtf1 =
new DateTimeFormatterBuilder().appendPattern("ddMMyy").toFormatter();
System.out.println(dtf1.parse(s)); // 2090-07-15
// improved code with base range 1935-2034
DateTimeFormatter dtf2 =
new DateTimeFormatterBuilder().appendPattern("ddMM")
.appendValueReduced(
ChronoField.YEAR, 2, 2, Year.now().getValue() - 80
).toFormatter();
System.out.println(dtf2.parse(s)); // 1990-07-15
By the way, if you really want week-based years then you have to use Y instead of y or the appropriate field IsoFields.WEEK_BASED_YEAR
. Regarding the fact that you don't have any other week-related fields I would assume the normal calendar year, not the week-based one.
How to force java.time.LocalDate to assume 19th century as YY year pattern?
The base year can be controlled using DateTimeFormatterBuilder.appendValueReduced().
This code would parse with a base date of 1900 rather than 2000:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("ddMM")
.appendValueReduced(ChronoField.YEAR, 2, 2, 1900)
.toFormatter();
LocalDate date = LocalDate.parse("070886", f);
How to change the base date for parsing two letter years with Java 8 DateTimeFormatter?
You can create a custom formatter, for example for the d/M/yy
pattern:
new DateTimeFormatterBuilder()
.appendPattern("d/M/")
.appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2, LocalDate.now().minusYears(80))
Example usage:
public static void main(String[] args) throws Exception {
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("d/M/")
.appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2, LocalDate.now().minusYears(80))
.toFormatter();
parse("13/5/99", fmt);
parse("13/5/36", fmt);
parse("13/5/35", fmt);
parse("13/5/34", fmt);
parse("13/5/33", fmt);
}
private static void parse(String date, DateTimeFormatter fmt) {
System.out.println(date + " = " + LocalDate.parse(date, fmt));
}
which prints:
13/5/99 = 1999-05-13
13/5/36 = 1936-05-13
13/5/35 = 1935-05-13
13/5/34 = 2034-05-13
13/5/33 = 2033-05-13
Java 8 DateTimeFormatter two digit year 18 parsed to 0018 instead of 2018?
There is not a single string of y
or u
that will allow you to parse both two and four digit years. However, you may use optional parts in the format pattern string to specify that a two or four digit year may be present:
public static LocalDate parseDateString(CharSequence date) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/[uuuu][uu]");
return LocalDate.parse(date, formatter);
}
Try it:
System.out.println(parseDateString("01/05/18"));
System.out.println(parseDateString("01/06/2018"));
This printed:
2018-01-05
2018-01-06
In the format pattern string you need to put the four digit year first. With the opposite order, when trying to parse a four digit year, the formatter will parse two digits, decide it was successful this far, and then complain about unparsed text after the two digits.
If you want more precise control over how two digit years are interpreted:
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("M/d/")
.optionalStart()
.appendPattern("uuuu")
.optionalEnd()
.optionalStart()
.appendValueReduced(ChronoField.YEAR, 2, 2, 1920)
.optionalEnd()
.toFormatter();
Using this formatter in the above method let’s try:
System.out.println(parseDateString("01/05/22"));
This prints:
1922-01-05
Giving 1920 as base (as in my example code) will cause two digit years to end up in the interval from 1920 through 2019. Adjust the value to your requirements.
Parsing 2-digit years: Setting the pivot date with an unknown date pattern
Here the Java-8-solution (it rather looks like a hack):
String pattern = "MM/dd/yy 'US'"; // user-input
String text = "10/04/69 US"; // user-input
Locale locale = Locale.US; // user-input, too?
int yy = pattern.indexOf("yy");
DateTimeFormatter dtf;
if (
(yy != -1) // explanation: condition ensures exactly two letters y
&& ((yy + 2 >= pattern.length()) || pattern.charAt(yy + 2) != 'y')
) {
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
String part1 = pattern.substring(0, yy);
if (!part1.isEmpty()) {
builder.appendPattern(part1);
}
builder.appendValueReduced(ChronoField.YEAR, 2, 2, 1970);
String part2 = pattern.substring(yy + 2);
if (!part2.isEmpty()) {
builder.appendPattern(part2);
}
dtf = builder.toFormatter(locale);
} else {
dtf = DateTimeFormatter.ofPattern(pattern, locale);
}
LocalDate ld = LocalDate.parse(text, dtf);
System.out.println("user-date: " + ld); // 2069-10-04
There is only one tiny caveat: If any user gets the crazy idea to define two times separately "yy" in the pattern then the proposed solution will fail. The correct solution would then require some kind of iterating over the pattern chars but I think that is overkill, so I leave it out here.
Just for comparison, using my external library Time4J enables following short solution because its parse engine also has the concept of setting any suitable format attributes:
LocalDate ld =
ChronoFormatter.ofDatePattern(pattern, PatternType.CLDR, locale)
.with(Attributes.PIVOT_YEAR, 2070).parse(text).toTemporalAccessor();
Year 2038 issue still happens with Jackson of date deserialization in Java 8
While I don't know about Jackson, i can tell you that you've taken a poor approach to handling this credit card expiration data.
Firstly you chose to roll-your-own rather than look to existing classes and standards. Always search for prior work; roll-your-own approach should be a last resort, not first. Less wild-cowboy, more judge-guided-by-precedent.
Secondly, you are using troublesome old date-time classes that are legacy, now supplanted by the java.time classes.
YearMonth
To represent a year and a month, use the YearMonth
class built into Java. The months are numbered sanely, 1-12 for January-December, unlike the legacy classes.
YearMonth ym = YearMonth.of( 2040 , 3 ) ;
By the way, you may find the Month
enum handy in your work.
YearMonth ym = YearMonth.of( 2040 , Month.MARCH ) ;
Use this YearMonth
class as your member rather than a specific date.
When comparing to a date such as today, get the YearMonth
for that date.
ZoneId z = ZoneId.of( "Asia/Amman" ) ;
LocalDate today = LocalDate.now( z ) ;
YearMonth ymToday = YearMonth.from( today ) ;
Boolean isExpired = ymToday.isAfter( ym ) ;
ISO 8601
The ISO 8601 standard defines many practical sensible formats for representing date-time values as text.
The standard format for year-month is YYYY-MM
. So March 2040 is 2040-03
.
The java.time classes use the standard formats by default when parsing or generating strings.
Generate a string.
ym.toString()
2040-03
Parse a string.
YearMonth ym = YearMonth.parse( "2040-03" );
Always use 4-digit years
For both storage and presentation, always use four digits for years. The endless confusion, errors, and ambiguity is not worth saving two octets of memory/storage or half a centimeter of space on paper.
How to convert two digit year to full year using Java 8 time API
The problem is that you can't parse a year on its own into a LocalDate. A LocalDate needs more information than that.
You can use the parse
method of the formatter, which will give you a TemporalAccessor
, and then get the year field from that:
int year = TWO_YEAR_FORMATTER.parse("99").get(ChronoField.YEAR);
System.out.println(year);
Addressing the discrepancy between the two: these are two distinct APIs. Yes, they are very similar, and the java.time
package was informed by design decisions of JodaTime, but it was never intended to be a drop-in replacement for it.
See this answer if you would like to change the pivot year (by default '99' will resolve to 2099 and not 1999).
Related Topics
Why Doesn't Java.Util.Collection Implement the New Stream Interface
Adding New Paths for Native Libraries at Runtime in Java
Functional Interface That Takes Nothing and Returns Nothing
How to Append a Node to an Existing Xml File in Java
How to Get the Array Class for a Given Class in Java
How to Implement Task Prioritization Using an Executorservice in Java 5
Managing Dll Dependencies with Maven
How to Extract Parameters from a Given Url
What Does a Jvm Have to Do When Calling a Native Method
Understanding Scanner's Nextline(), Next(), and Nextint() Methods
Why Is Files.Lines (And Similar Streams) Not Automatically Closed
Which Is More Effective: If (Null == Variable) or If (Variable == Null)
How Read Doc or Docx File in Java
In Java Lambda's Why Is Getclass() Called on a Captured Variable