Dar formato a una fecha usando la nueva API de fecha y hora

118

Estaba jugando con la nueva API de fecha y hora, pero al ejecutar esto:

public class Test {         
    public static void main(String[] args){
        String dateFormatted = LocalDate.now()
                                        .format(DateTimeFormatter
                                              .ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println(dateFormatted);
    }
}

Arroja:

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
    at java.time.LocalDate.get0(LocalDate.java:680)
    at java.time.LocalDate.getLong(LocalDate.java:659)
    at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
    at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2543)
    at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
    at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1745)
    at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1719)
    at java.time.LocalDate.format(LocalDate.java:1685)
    at Test.main(Test.java:23)

Al mirar el código fuente de la clase LocalDate, veo:

  private int get0(TemporalField field) {
        switch ((ChronoField) field) {
            case DAY_OF_WEEK: return getDayOfWeek().getValue();
            case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1;
            case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1;
            case DAY_OF_MONTH: return day;
            case DAY_OF_YEAR: return getDayOfYear();
            case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead");
            case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1;
            case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1;
            case MONTH_OF_YEAR: return month;
            case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead");
            case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year);
            case YEAR: return year;
            case ERA: return (year >= 1 ? 1 : 0);
        }
        throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
    }

Como se describe en el documento:

Este método creará un formateador basado en un patrón simple de letras y símbolos como se describe en la documentación de la clase.

Y todas estas letras están definidas .

Entonces, ¿por qué DateTimeFormatter.ofPatternno nos permite usar algunos patrones de letras?

usuario2336315
fuente

Respuestas:

219

LocalDaterepresenta solo una fecha, no una fecha y hora. Entonces, "HH: mm: ss" no tiene sentido al formatear un LocalDate. Utilice en su LocalDateTimelugar, asumiendo que desea representar tanto una fecha como una hora.

James_D
fuente
3
¿Cómo puedo votar a favor esta respuesta y rechazar el hecho de que hay un objeto LocalDate y LocalDateTime ...
Xials
Me gustaría trabajar con LocalTime, ¿cómo se realiza el formateo sin encontrarse con esta excepción?java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfWeek
samuel owino
No importa: esto funcionaDateTimeFormatter.ofPattern("HH:mm:ss")
samuel owino
36

Me gustaría agregar los siguientes detalles a la respuesta correcta de @James_D:

Antecedentes: la mayoría de las bibliotecas de fecha y hora ( java.util.Calendaren Java, consulte también .Net-DateTime o Dateen JavaScript o DateTimeen Perl) se basan en el concepto de un tipo temporal único universal y universal (en alemán existe la expresión poética " eierlegende Wollmilchsau "). En este diseño no puede haber un campo no admitido. Pero el precio es alto: muchos problemas de tiempo no pueden manejarse adecuadamente con un enfoque tan inflexible porque es difícil o imposible encontrar un denominador común para todo tipo de objetos temporales.

JSR-310 ha elegido otra forma , a saber, permitir diferentes tipos temporales que consisten en conjuntos específicos de tipos de campos integrados admitidos. La consecuencia natural es que no todos los campos posibles son compatibles con todos los tipos (y los usuarios pueden incluso definir sus propios campos especializados). También es posible solicitar mediante programación a cada objeto de tipo TemporalAccessorsu conjunto específico de campos admitidos. Porque LocalDateencontramos:

DAY_OF_WEEK 
ALIGNED_DAY_OF_WEEK_IN_MONTH 
ALIGNED_DAY_OF_WEEK_IN_YEAR 
DAY_OF_MONTH 
DAY_OF_YEAR 
EPOCH_DAY 
ALIGNED_WEEK_OF_MONTH 
ALIGNED_WEEK_OF_YEAR 
MONTH_OF_YEAR 
PROLEPTIC_MONTH 
YEAR_OF_ERA 
YEAR 
ERA 

No hay ningún campo HOUR_OF_DAY que explique el problema de UnsupportedTemporalTypeException. Y si miramos el mapeo JSR-310- de símbolos de patrón a campos , vemos que el símbolo H está mapeado a HOUR_OF_DAY no admitido:

/** Map of letters to fields. */  
private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
static {
  FIELD_MAP.put('G', ChronoField.ERA);
  FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);
  FIELD_MAP.put('u', ChronoField.YEAR);
  FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR);
  FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR);
  FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);
  FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR);
  FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);
  FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);
  FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);
  FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);
  FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK);
  FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK);
  FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);
  FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);
  FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);
  FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);
  FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);
  FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);
  FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);
  FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);
  FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY);
  FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND);
  FIELD_MAP.put('N', ChronoField.NANO_OF_DAY);    
}

Este mapeo de campo no significa que el campo sea compatible con el tipo concreto. El análisis se realiza en varios pasos. El mapeo de campo es solo el primer paso. El segundo paso es analizar un objeto de tipo sin formato TemporalAccessor. Y finalmente, analizar los delegados al tipo de destino (aquí LocalDate:) y dejar que decida si acepta todos los valores de campo en el objeto intermedio analizado.

Meno Hochschild
fuente
4
en.wiktionary.org/wiki/eierlegende_Wollmilchsau (literalmente "cerda de leche de lana que pone huevos") Un dispositivo o persona todo en uno que tiene (o afirma tener) solo atributos positivos y que puede (o intenta) hacer el trabajo de varias herramientas especializadas. :-)
Trevor Robinson
6

La clase adecuada para mí fue la ZonedDateTimeque incluye tanto la hora como la zona horaria.

LocalDateno tiene la información de la hora, por lo que obtiene un UnsupportedTemporalTypeException: Unsupported field: HourOfDay.

Puede usar, LocalDateTimepero no tiene la información de la zona horaria, por lo que si intenta acceder a eso (incluso usando uno de los formateadores predefinidos) obtendrá un UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds.

isapir
fuente