1

Simply I have the following class to get my JSON body that received from remote response to deserialize to CreditCardDTO the date recieved inside exp_date like "0820" for 8/2010, and "0240" for 2/2040:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Date;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyOrder(alphabetic = true)
public class CreditCardDTO {
    private String brand;
    private Date expirationDate;

    @JsonProperty("brand")
    public String getBrand() {
        return brand;
    }

    @JsonProperty("credit_card_type")
    public CreditCardDTO setBrand(String brand) {
        this.brand = brand;
        return this;
    }

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    @JsonProperty("expirationDate")
    public Date getExpirationDate() {
        return expirationDate;
    }

    @JsonFormat(pattern = "MMyy")
    @JsonProperty("exp_date")
    public CreditCardDTO setExpirationDate(Date expirationDate) {
        System.out.println(expirationDate);
        this.expirationDate = expirationDate;
        return this;
    }
}

The problem that if it is before Year 2038 everything is OK, but once the data is after that critical date, it still happens, the data is back to 1941, I searched about the problem and found that it should not be happen in Java 8 : Why should a Java programmer care about year 2038 bug? , so I'm wondering what the issue here!

Jackson version 2.8.0, Java 8 for sure.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Al-Mothafar
  • 7,949
  • 7
  • 68
  • 102
  • @AndyTurner so what the problem? have you see the link I mentioned " Java's Date class stores a 64-bit long" – Al-Mothafar Jul 03 '17 at 08:35
  • 1
    You are using the `java.util.Date` class, which still belongs to the old Java set of functions. To use the Java 8 benefits, you need to use classes from the `java.time` packages. – RealSkeptic Jul 03 '17 at 08:36
  • 3
    `System.out.println(new java.util.Date(Long.MAX_VALUE));` give `Sun Aug 17 08:12:55 CET 292278994` on Java 8. So you could check how Jackson manage the `Date` conversion – AxelH Jul 03 '17 at 08:38
  • 5
    If there's anything we learned from the Y2K bug it is *don't use two-digit year format for data transfer or storage*. You are doing that. It means that it has to guess that "40" is "2040" rather than "1940". Read the documentation for `SimpleDateFormat` to see how it decides that. And switch to using four-digit years for data transfer. – RealSkeptic Jul 03 '17 at 08:43
  • @RealSkeptic that makes sense, but still I see inside Date class they using long, but about `two-digit year` this lesson must be learned from 2000 problem, but I have to do with this remote API, developed by old style guy, he said that he can't change that because of another old project running it. – Al-Mothafar Jul 03 '17 at 08:49
  • 1
    If you would have provide a simple [mcve], you would notice that this is reproduced simple with `new java.text.SimpleDateFormat("MMyy").parse("0240")` > `Thu Feb 01 00:00:00 CET 1940`. Confirming RealSkeptic comment (by the way). So unless you can update the [`2 digit year start`](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html#set2DigitYearStart-java.util.Date-), you are **** – AxelH Jul 03 '17 at 08:49
  • Note the part in the [`SimpleDateFormat` javadoc](https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) which says "It does [parsing of two-digit years] by adjusting dates to be within 80 years before and 20 years after the time the SimpleDateFormat instance is created" (noting that 2040 is more than 20 years hence). There's more relevant stuff before and after. – Andy Turner Jul 03 '17 at 08:52
  • @AxelH no it is not duplicate! – Al-Mothafar Jul 03 '17 at 08:59
  • 1
    The answer provide the explanation about `java.time.LocalDate` and `java.util.Date` conversion for 2-digit year. As you said, you can't update the API, so you don't have a solution here. Just wait 3year to be able to pass a 2040 year. – AxelH Jul 03 '17 at 09:01
  • @AxelH so it could be Jackon bug then – Al-Mothafar Jul 03 '17 at 09:06
  • 4
    Are you reading the comments? That's not a bug. That's the expected output from a "2 digit year" conversion. This was a bad design at the beginning, just to save 2 character. Read the comments again and follow the research that are proposed (linked or not). – AxelH Jul 03 '17 at 09:10
  • @AxelH yes, it is not a bug for Jackon (Not said Jackon's bug), I meant it could be my bug on the bad design as what you said, that limited to Jackson abilities, not a bug with Jackson but I have to find another way or just tell that guy to fix that bad API somehow. sorry for the confusion (my English got a lot of bugs :D ) I could be using `java.time.YearMonth` as for example – Al-Mothafar Jul 03 '17 at 09:16
  • 2
    If you cannot change the expiration date format to 4 digit year, you may have to check the date yourself and add 100 years on some condition that you will have to figure out (don’t just do it if the date is in the past, or credit cards will never expire). Adding 100 years is easier with `java.time` API. I believe you can use it with Jackson if you get [jackson-datatype-jsr310](https://github.com/FasterXML/jackson-datatype-jsr310). – Ole V.V. Jul 03 '17 at 09:18
  • Do people actually have credit card dates more than 20 years in the future? Isn't this something you could solve with validation elsewhere? – Andy Turner Jul 03 '17 at 09:29
  • 1
    @AndyTurner it is +20 years max in most uses, anyway it is here https://stackoverflow.com/questions/2500588/maximum-year-in-expiry-date-of-credit-card – Al-Mothafar Jul 03 '17 at 09:34

1 Answers1

4

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Thank you for reply, kinda I'm following the points that you saying, as I said, the reply from remote API is not my own issue and that developer is thickhead enough to reject my request to change the year, as I found solution to use either `YearMonth` or keep my own way to use `ISO 8601` and limit the form to +20 years from now maximum, it seems the parsing with Jackson use SimpleDateFormat to parse 'yy'. – Al-Mothafar Jul 03 '17 at 18:49