Search code examples
javaspring-bootdatejpaxmlgregoriancalendar

convert xmlGregorianCalendar to Date and reverse


i all, i have a spring boot application. what i want in specific is to convert a class (that have nestet object field) in his corrispective entity. example:

 public class example{
String string;
ObjectExample object;
}
public class ObjectExample{
String oneString;
XMLGregorianCalendar date;
}

this 2 object are also marked in another package as entities, but ovviusly in the ObjectExampleEntity i have Date date instead XMLGregorianCalendar, like this with the example

@Entity

public class example{
String string;
ObjectExample object;
}

@Entity

public class ObjectExample{
String oneString;
Date date;
}

because i have a big model and big entity (this above is only an example) with a lot of nested classes , i use dozer to convert from the model to the class. consider for example that the repository jpa is only created for the father example class. i want to know how i can with dozer convert from Date (entity) to XMLGregorianCalendar (model) and reverse. the model and the entity,i repeat are equal. the only difference is the type of the date. thanks


Solution

  • I am assuming:

    • Since your variable is named date it contains a calendar date (without time of day).
    • You are tied to XMLGregorianCalendar because of a WSDL outside your control, but you can change type on the entity side.

    Based on these assumptions I recommend LocalDate on the entity side. It’s part of java.time, the modern Java date and time API, and represents exactly a date without time of day. The Date class that you used is poorly designed, long outdated and not recommended. Also despite the name a Date never represented a date, but a point in time.

    There are more options. I am presenting three.

    Option 1: transfer individual fields

    From XMLGregorianCalendar to LocalDate:

        DatatypeFactory xmlFactory = DatatypeFactory.newInstance();
        XMLGregorianCalendar wsDate = xmlFactory
                .newXMLGregorianCalendarDate(2019, DatatypeConstants.MARCH, 30,
                        DatatypeConstants.FIELD_UNDEFINED);
    
        // Validate
        if ((wsDate.getHour() != 0 && wsDate.getHour() != DatatypeConstants.FIELD_UNDEFINED)
                || (wsDate.getMinute() != 0 && wsDate.getMinute() != DatatypeConstants.FIELD_UNDEFINED)
                || (wsDate.getSecond() != 0 && wsDate.getSecond() != DatatypeConstants.FIELD_UNDEFINED)
                || (wsDate.getMillisecond() != 0 && wsDate.getMillisecond() != DatatypeConstants.FIELD_UNDEFINED)) {
            System.out.println("Warning: time of day will be lost in conversion");
        }
        if (wsDate.getTimezone() != DatatypeConstants.FIELD_UNDEFINED) {
            System.out.println("Warning: UTC offset will be lost in conversion");
        }
    
        // Convert
        LocalDate entityDate = LocalDate.of(wsDate.getYear(), wsDate.getMonth(), wsDate.getDay());
        System.out.println(entityDate);
    

    The output is in this case:

    2019-03-30

    From LocalDate to XMLGregorianCalendar:

        LocalDate entityDate = LocalDate.of(2019, Month.MARCH, 31);
    
        XMLGregorianCalendar wsDate = xmlFactory.newXMLGregorianCalendarDate(
                entityDate.getYear(),
                entityDate.getMonthValue(),
                entityDate.getDayOfMonth(),
                DatatypeConstants.FIELD_UNDEFINED);
        System.out.println(wsDate);
    

    2019-03-31

    Advantage of this way: It’s pretty straightforward. Disadvantage: You and your reader need to take care that the fields are mentioned in the right order.

    Option 2: convert via strings

        // Validate as before
    
        // Convert
        LocalDate entityDate = LocalDate.parse(wsDate.toXMLFormat());
    

    Result is as before.

        XMLGregorianCalendar wsDate
                = xmlFactory.newXMLGregorianCalendar(entityDate.toString());
    

    Advantage: it’s brief, and there’s no surprise that the results are correct. Disadvantage: To me it feels like a waste to format into a string only to parse it back.

    Option 3: convert via GregorianCalendar and ZonedDateTime

        ZonedDateTime zdt = wsDate.toGregorianCalendar().toZonedDateTime();
    
        // Validate
        if (! zdt.toLocalTime().equals(LocalTime.MIN)) {
            System.out.println("Warning: time of day will be lost in conversion");
        }
        if (! zdt.getZone().equals(ZoneId.systemDefault())) {
            System.out.println("Warning: UTC offset will be lost in conversion");
        }
    
        // Finish conversion
        LocalDate entityDate = zdt.toLocalDate();
    

    And the other way:

        // It doesn’t matter which time zone we pick
        // since we are discarding it after conversion anyway
        ZonedDateTime zdt = entityDate.atStartOfDay(ZoneOffset.UTC);
        GregorianCalendar gCal = GregorianCalendar.from(zdt);
        XMLGregorianCalendar wsDate = xmlFactory.newXMLGregorianCalendar(gCal);
        wsDate.setTime(DatatypeConstants.FIELD_UNDEFINED, DatatypeConstants.FIELD_UNDEFINED,
                DatatypeConstants.FIELD_UNDEFINED, DatatypeConstants.FIELD_UNDEFINED);
        wsDate.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
    

    The validation I present here is a bit simpler but also not quite so strict. If you want strict validation, you can just use the validation from before.

    Advantages: I think it’s the official way; at least it uses the conversion methods offered. What I like is that the conversion itself is direct and brief. Disadvantage: When converting to XMLGregorianCalendar we need to set the unused fields to undefined manually, which makes it wordy.

    Conclusion

    I have presented three options each with their pros and cons. You may also mix, of course, but using a similar conversion both ways is probably less confusing in the end.