web-dev-qa-db-fra.com

Mettre en forme une date à l'aide de la nouvelle API date / heure

Je jouais avec la nouvelle API d'heure date mais en exécutant ceci:

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);
    }
}

Il jette:

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)

En regardant le code source de la classe LocalDate, je vois:

  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);
    }

Comme décrit dans la doc:

Cette méthode créera un formateur basé sur un motif simple de lettres et de symboles, comme décrit dans la documentation de la classe.

Et toutes ces lettres sont définies .

Alors pourquoi DateTimeFormatter.ofPattern ne nous permet pas d’utiliser des lettres de modèle?

95
user2336315

LocalDate représente simplement une date, pas un DateTime. Donc, "HH: mm: ss" n'a pas de sens lors du formatage d'un LocalDate. Utilisez plutôt LocalDateTime, en supposant que vous souhaitez représenter à la fois une date et une heure.

170
James_D

J'aimerais ajouter les détails suivants à la réponse correcte de @James_D:

Arrière-plan: La plupart des bibliothèques de dates et d'heures (Java.util.Calendar en Java, voir aussi .Net-DateTime ou Date en JavaScript ou DateTime en Perl) sont basés sur le concept d'un type temporel unique, universel et polyvalent (en allemand, le expression poétique "eierlegende Wollmilchsau"). Dans cette conception, il ne peut y avoir de champ non pris en charge. Mais le prix est élevé: de nombreux problèmes de temps ne peuvent pas être traités de manière adéquate avec une approche aussi inflexible, car il est difficile de trouver un dénominateur commun à toutes sortes d'objets temporels.

JSR-310 a choisi une autre méthode , à savoir autoriser différents types temporels constitués d’ensembles spécifiques de types de champs intégrés pris en charge. La conséquence naturelle est que tous les champs possibles ne sont pas supportés par tous les types (et les utilisateurs peuvent même définir leurs propres champs spécialisés). Il est également possible de demander par programme chaque objet de type TemporalAccessor pour son ensemble spécifique de champs pris en charge. Pour LocalDate on trouve:

•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 

Il n'y a pas de champ HOUR_OF_DAY qui explique le problème de UnsupportedTemporalTypeException. Et si nous regardons le JSR-310 - mappage des symboles de modèle sur des champs , nous voyons que le symbole H est mappé sur HOUR_OF_DAY non pris en charge:

/** 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);    
}

Cette correspondance de champ ne signifie pas que le champ est pris en charge par le type concret. L'analyse se déroule en plusieurs étapes. Le mappage de champ n'est que la première étape. La deuxième étape consiste ensuite à analyser un objet brut de type TemporalAccessor. Et enfin, analyser les délégués dans le type de cible (ici: LocalDate) et lui permettre de décider s’il accepte toutes les valeurs de champ dans l’objet intermédiaire analysé.

33
Meno Hochschild

La bonne classe pour moi était ZonedDateTime qui inclut à la fois l'heure et le fuseau horaire.

LocalDate ne dispose pas d'informations sur l'heure, vous obtenez donc un UnsupportedTemporalTypeException: Unsupported field: HourOfDay.

Vous pouvez utiliser LocalDateTime mais vous ne disposez pas des informations sur le fuseau horaire. Par conséquent, si vous essayez d'y accéder (même en utilisant l'un des formateurs prédéfinis), vous obtiendrez un UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds.

3
isapir