Conversão simples entre java.util.Date e XMLGregorianCalendar

110

Estou procurando um método simples de conversão entre java.util.Date e javax.xml.datatype.XMLGregorianCalendar em ambas as direções.

Aqui está o código que estou usando agora :

import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

/**
 * Utility class for converting between XMLGregorianCalendar and java.util.Date
 */
public class XMLGregorianCalendarConverter {  

    /**
     * Needed to create XMLGregorianCalendar instances
     */
    private static DatatypeFactory df = null;
    static {
        try {
            df = DatatypeFactory.newInstance();
        } catch (DatatypeConfigurationException dce) {
            throw new IllegalStateException(
                "Exception while obtaining DatatypeFactory instance", dce);
        }
    }  

    /**
     * Converts a java.util.Date into an instance of XMLGregorianCalendar
     *
     * @param date Instance of java.util.Date or a null reference
     * @return XMLGregorianCalendar instance whose value is based upon the
     *  value in the date parameter. If the date parameter is null then
     *  this method will simply return null.
     */
    public static XMLGregorianCalendar asXMLGregorianCalendar(java.util.Date date) {
        if (date == null) {
            return null;
        } else {
            GregorianCalendar gc = new GregorianCalendar();
            gc.setTimeInMillis(date.getTime());
            return df.newXMLGregorianCalendar(gc);
        }
    }

    /**
     * Converts an XMLGregorianCalendar to an instance of java.util.Date
     *
     * @param xgc Instance of XMLGregorianCalendar or a null reference
     * @return java.util.Date instance whose value is based upon the
     *  value in the xgc parameter. If the xgc parameter is null then
     *  this method will simply return null.
     */
    public static java.util.Date asDate(XMLGregorianCalendar xgc) {
        if (xgc == null) {
            return null;
        } else {
            return xgc.toGregorianCalendar().getTime();
        }
    }
}

Existe algo mais simples, como alguma chamada de API que esqueci?

A conversão entre uma data / hora XML padrão e um objeto de data Java parece uma tarefa bastante rotineira e estou surpreso por ter que escrever este código.

Alguma sugestão?

NOTAS: Minhas classes JAXB são geradas automaticamente a partir de um esquema. O processo de construção em meu projeto não permite que eu faça alterações manuais nas classes geradas. Os elementos xs: dateTime estão sendo gerados por XJC como XMLGregorianCalendar nas classes JAXB. O esquema é estendido e ajustado periodicamente, portanto, tenho permissão para fazer alterações limitadas no arquivo XSD do esquema.

ATUALIZAÇÃO NA SOLUÇÃO: A solução proposta por Blaise me permitiu tirar XMLGregorianCalendar da mistura e lidar com objetos java.util.Calendar. Adicionando uma cláusula de ligação JAXB na parte superior do meu arquivo de esquema, o XJC é capaz de gerar mapeamentos mais apropriados para xs: dateTime em minhas classes JAXB. Aqui estão alguns trechos que mostram as modificações em meu arquivo XSD.

O elemento raiz no arquivo XSD:

<xs:schema xmlns:mydata="http://my.example.com/mydata" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" targetNamespace="http://my.example.com/mydata" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="0.2" xml:lang="en" jaxb:version="2.0">

Bloco de anotação de ligação JAXB, inserido imediatamente após o elemento raiz em XSD:

<xs:annotation>
    <xs:appinfo>
        <jaxb:globalBindings>
            <jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime" parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime" printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />
        </jaxb:globalBindings>
    </xs:appinfo>
</xs:annotation>

Como o campo XML xs: dateTime também armazena fuso horário, pode ser melhor para mim trabalhar com Calendário em vez de Data de qualquer maneira, já que os objetos Calendar têm uma API muito boa para trabalhar com localidades e fusos horários. Em qualquer caso, fico muito mais feliz em lidar com objetos Calendar em vez de XMLGregorianCalendar. Não há mais necessidade dos métodos de conversão que listei acima. Não cheguei até java.util.Date, mas perto o suficiente!

Jim Tough
fonte
Eu não sei de nenhum. Mas o seu parece muito bom - basta colocá-lo em um utilpacote e usá-lo.
Bozho,
Um pouco à parte, mas por que você tem que lidar com objetos XMLGregorianCalendar em primeiro lugar? Eles são irritantes. Se vierem de jaxb, é possível usar @XMLTypeAdapter para vincular diretamente a java.util.Date. Obviamente, se você estiver gerando automaticamente a partir de um esquema, alterar os objetos pode ser tão irritante quando você regenera.
Affe,
@Affe Estou gerando automaticamente a partir de um esquema, então não posso fazer nenhuma alteração manual nas classes JAXB geradas
Jim Tough,
É o mesmo que stackoverflow.com/questions/835889/… ?
Jacob Tomaw,
1
@Jacob - não é. Ele já descobriu como fazer isso, ele se pergunta se não existe uma classe de utilitário pronta para usar.
Bozho,

Respostas:

46

Por que não usar um arquivo de ligação externa para informar ao XJC para gerar campos java.util.Date em vez de XMLGregorianCalendar?

Consulte também Como mapeio xs: date para java.util.Date? Blog

bdoughan
fonte
Eu vou olhar para isso. Obrigado.
Jim Tough,
Sem problemas. JAXB pode manipular o tipo java.util.Date, você só precisa gerá-lo em seu modelo. O que pode ser complicado.
bdoughan
Isso funcionou para mim. Veja as edições da minha pergunta acima para obter detalhes sobre o que eu fiz.
Jim Tough
Eu adicionei ligações jaxb, mas logo abaixo de xs: schema e recebo o seguinte erro: com.sun.istack.SAXParseException2: o compilador não foi capaz de honrar esta personalização globalBindings. Ele está preso a um lugar errado ou é inconsistente com outras ligações. em com.sun.tools.xjc.ErrorReceiver.error (ErrorReceiver.java:86) em ..
pri
@pritam - Aqui está outro exemplo que pode ajudar: blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html . Pode valer a pena começar uma nova questão para o problema que você está vendo.
bdoughan
81

De XMLGregorianCalendar a java.util.Date, você pode simplesmente fazer:

java.util.Date dt = xmlGregorianCalendarInstance.toGregorianCalendar().getTime();  
Zé Carlos
fonte
Obrigado ... Eu estava procurando uma maneira de converter XMLGregorianCalendar para hora em milis.
Andez
6

De java.util.Date a XMLGregorianCalendar, você pode simplesmente fazer:

import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.datatype.DatatypeFactory;
import java.util.GregorianCalendar;
......
GregorianCalendar gcalendar = new GregorianCalendar();
gcalendar.setTime(yourDate);
XMLGregorianCalendar xmlDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(gcalendar);

Código editado após o primeiro comentário de @f-puras, por causa eu cometi um erro.

moralejaSinCuentoNiProverbio
fonte
1
Não funciona da maneira que você escreveu: GregorianCalendar.setTime () não retornará nada.
f_puras
5

Tive de fazer algumas alterações para que funcionasse, pois algumas coisas parecem ter mudado nesse ínterim:

  • xjc reclamaria que meu adaptador não estende XmlAdapter
  • algumas importações bizarras e desnecessárias foram desenhadas em (org.w3._2001.xmlschema)
  • os métodos de análise não devem ser estáticos ao estender o XmlAdapter, obviamente

Aqui está um exemplo prático, espero que ajude (estou usando JodaTime, mas neste caso SimpleDate seria suficiente):

import java.util.Date;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.DateTime;

public class DateAdapter extends XmlAdapter<Object, Object> {
    @Override
    public Object marshal(Object dt) throws Exception {
        return new DateTime((Date) dt).toString("YYYY-MM-dd");
    }

    @Override
        public Object unmarshal(Object s) throws Exception {
        return DatatypeConverter.parseDate((String) s).getTime();
    }
}

No xsd, segui as excelentes referências fornecidas acima, então incluí esta anotação xml:

<xsd:appinfo>
    <jaxb:schemaBindings>
        <jaxb:package name="at.mycomp.xml" />
    </jaxb:schemaBindings>
    <jaxb:globalBindings>
        <jaxb:javaType name="java.util.Date" xmlType="xsd:date"
              parseMethod="at.mycomp.xml.DateAdapter.unmarshal"
          printMethod="at.mycomp.xml.DateAdapter.marshal" />
    </jaxb:globalBindings>
</xsd:appinfo>
Gregor
fonte
1
Eu me tornei um fã do Joda Time desde que fiz esta pergunta. Muito melhor do que as classes de data e hora do Java SE. Excelente para lidar com fusos horários!
Jim Tough
1

Eu também tive esse tipo de dor de cabeça. Livrei-me dele simplesmente representando os campos de tempo como long primitivos no meu POJO. Agora, a geração do meu código de cliente WS lida com tudo corretamente e sem mais porcaria XML para Java. E, claro, lidar com millis no lado Java é simples e indolor. O princípio do KISS é demais!

Alex
fonte
1

Você pode usar esta personalização para alterar o mapeamento padrão para java.util.Date

<xsd:annotation>
<xsd:appinfo>
    <jaxb:globalBindings>
        <jaxb:javaType name="java.util.Date" xmlType="xsd:dateTime"
                 parseMethod="org.apache.cxf.xjc.runtime.DataTypeAdapter.parseDateTime"
                 printMethod="org.apache.cxf.xjc.runtime.DataTypeAdapter.printDateTime"/>
    </jaxb:globalBindings>
</xsd:appinfo>

Dev
fonte
0

Personalização do calendário e da data durante o Marshaling

Etapa 1: preparar o xml de ligação jaxb para propriedades personalizadas, neste caso eu preparei para data e calendário

<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings generateElementProperty="false">
<jaxb:serializable uid="1" />
<jaxb:javaType name="java.util.Date" xmlType="xs:date"
    parseMethod="org.apache.cxf.tools.common.DataTypeAdapter.parseDate"
    printMethod="com.stech.jaxb.util.CalendarTypeConverter.printDate" />
<jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
    parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
    printMethod="com.stech.jaxb.util.CalendarTypeConverter.printCalendar" />


Setp 2: Adicionar arquivo de ligação jaxb personalizado ao Apache ou qualquer plug-in relacionado na opção xsd como mencionado abaixo

<xsdOption>
  <xsd>${project.basedir}/src/main/resources/tutorial/xsd/yourxsdfile.xsd</xsd>
  <packagename>com.tutorial.xml.packagename</packagename>
  <bindingFile>${project.basedir}/src/main/resources/xsd/jaxbbindings.xml</bindingFile>
</xsdOption>

Conjunto 3: escreva o código para a classe CalendarConverter

package com.stech.jaxb.util;

import java.text.SimpleDateFormat;

/**
 * To convert the calendar to JaxB customer format.
 * 
 */

public final class CalendarTypeConverter {

    /**
     * Calendar to custom format print to XML.
     * 
     * @param val
     * @return
     */
    public static String printCalendar(java.util.Calendar val) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
        return simpleDateFormat.format(val.getTime());
    }

    /**
     * Date to custom format print to XML.
     * 
     * @param val
     * @return
     */
    public static String printDate(java.util.Date val) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return simpleDateFormat.format(val);
    }
}

Conjunto 4: Saída

  <xmlHeader>
   <creationTime>2014-09-25T07:23:05</creationTime> Calendar class formatted

   <fileDate>2014-09-25</fileDate> - Date class formatted
</xmlHeader>
Kanthishere
fonte