Meglio usare Clock oppure ZoneId? Qual è la differenza?

di il
7 risposte

Meglio usare Clock oppure ZoneId? Qual è la differenza?


// FONTE: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html
// 1) OffsetDateTime dataOra = OffsetDateTime.now(Clock.system(ZoneId.of(zona)));
// 2) OffsetDateTime dataOra = OffsetDateTime.now(ZoneId.of(zona));
Qualcuno sa spiegarmi la differenza tra 1 e 2 in modo che anche un comune mortale come me possa capire?

7 Risposte

  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    iBaffiPro ha scritto:


    
    // FONTE: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html
    // 1) OffsetDateTime dataOra = OffsetDateTime.now(Clock.system(ZoneId.of(zona)));
    // 2) OffsetDateTime dataOra = OffsetDateTime.now(ZoneId.of(zona));
    
    Qualcuno sa spiegarmi la differenza tra 1 e 2 in modo che anche un comune mortale come me possa capire?
    ZoneId rappresenta solamente un timezone, ovvero un "fuso orario". Né più né meno. L'equivalente, in pratica, del "vecchio" (per così dire ...) java.util.TimeZone esistente dal JDK 1.1.

    Clock è un'altra classe della nuova Date/Time API da Java 8 ma ha un altro scopo, diverso da ZoneId. Clock funge da "fornitore" del tempo più "a monte". E se usato appropriatamente può essere molto utile per "mockare" il tempo negli unit-test.

    Se fai sempre e solo es. LocalDate.now() prendi sempre la data reale ma che non va bene negli unit-test.
    Se invece fai LocalDate.now(unClock) puoi fare in modo che unClock sia ad esempio Clock.systemDefaultZone() durante la esecuzione normale della applicazione mentre negli unit-test gli assegni Clock.fixed(istanteCheVuoi, ZoneId.of("UTC")) in modo da "fermare" il tempo per gli unit-test.

    L'esempio è ben chiaro nel javadoc di Clock (l'hai letto??): https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Clock.html
  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    Non capisco, ho letto qualcosa di simile prima di aprire questa discussione ma non riesco proprio a capire.
    Cosa significa "fornitore" del tempo più "a monte"?
    Cosa significa "mockare" il tempo negli unit-test? Un sinonimo di "mockare"?
    Io nei metodi @Test di jUnit per settare una data diversa da quella attuale, scrivo una stringa e poi la converto in OffsetDateTime con un metodo apposito.
  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    Ciao

    Mockare è un termine che si usa spesso quando si parla di restituire oggetti "fittizi". Pensa ad esempio ad un repository che deve tornarti l'elenco delle fatture: quando sei in produzione, il repository punterà sicuramente alla tua base dati.
    Se però devi fare dei test, ti conviene usare qualcosa "in memoria" che torna sempre gli stessi dati, così da poter replicare gli stessi test nel tempo e in macchine diverse (per quando si sviluppa in gruppo).
    Ecco, questo "produrre un repository fittizio" si traduce con un "mockare il repository"


    Quanto riguarda il discorso del clock, è qualcosa di simile ma che lavora col tempo.
    Supponi di avere una classe che per logica applicativa debba tornare "il fine mese". Quindi, a prescindere dalla data attuale, deve trovarne la fine.
    Vuoi quindi svilupparne un apposito test-unit per verificare che funzioni correttamente anche nel caso di anno bisestile o cose simili.

    In quel caso non puoi lavorare col giorno "di oggi", poichè nei test unit devi verificare il valore di ritorno, pertanto in quei test devi "forzare" una data specifica.

    E' vero che in qualche modo puoi forzare la data usando una stringa (come dicevi tu), però non sempre è la cosa migliore, anche perchè in alcuni casi devi impostare anche i millisecondi o altre cose.

    Pertanto esiste la classe "Clock" che ti consente di forzare in qualche modo l'istante a cui fare riferimento. Pertanto:
    • Quando lavori a runtime, passi il Clock.systemDefaultZone per tornare il clock del giorno in cui viene eseguito il codice
    • Quando lavori negli unit-test, usi un Clock.fixed così da fare in modo che lavorino sempre con lo stesso riferimento nel tempo, indipendentemente dal momento e dal PC su cui vengono eseguiti

    Spero sia più chiaro, anche se alla fine ho ripetuto quanto detto da altri
  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    Grazie mille. Quale dei 2 metodi è meglio usare nei test se si desidera ottenere null quando l'utente non setta un fuso orario?
    
        public OffsetDateTime dataOraDaStringa(String stringaDataOra, String formatoDataOra){
            try{
                DateTimeFormatter f = DateTimeFormatter.ofPattern(formatoDataOra, nazione).withZone(ZoneId.of(zona));
                OffsetDateTime dataOra = OffsetDateTime.parse(stringaDataOra, f);
                return dataOra;
            }catch (Exception e){
                return null;
            }
        }
    
    
        public OffsetDateTime dataOraDaStringa(String stringaDataOra, String formatoDataOra){
            try{
                final DateTimeFormatter DTF = new DateTimeFormatterBuilder()
                        .appendPattern(formatoDataOra)
                        .toFormatter();
                Instant istante = DTF.parse(stringaDataOra, Instant::from);
                ZoneId fusoOrario = ZoneId.of(zona);
                Clock orologio = Clock.fixed(istante, fusoOrario);
                OffsetDateTime dataOra = OffsetDateTime.now(orologio);
                return dataOra;
            }catch (Exception e){
                return null;
            }
        }
    
    Come al solito non riesco a capire in quale circostanza un metodo potrebbe andare e l'altro dare problemi...
  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    iBaffiPro ha scritto:


    
                Clock orologio = Clock.fixed(istante, fusoOrario);
                OffsetDateTime dataOra = OffsetDateTime.now(orologio);
    
    No, sbagliato. NON ti serve lì il Clock.fixed. Lo ripeto: Clock.fixed si usa praticamente quasi esclusivamente nel contesto degli unit-test perché serve sostanzialmente per "fissare" il tempo di un now().

    Ok, vediamo meglio con un esempio più pratico.

    ESEMPIO:

    Hai una classe User dove tra altre cose c'è un Instant passwordUpdateInstant, l'istante in cui la password è stata creata (o aggiornata).
    public class User {
        private Instant passwordUpdateInstant;
    
        // metodi getter/setter ecc...
    }
    Poi c'è una classe PasswordStatus che descrive se la password è scaduta e quando scade:
    public class PasswordStatus {
        private boolean expired;
        private Instant expireInstant;
    
        // metodi getter/setter ecc...
    }
    Poi in uno UserService (NON conta ora se c'è Spring o no di mezzo):
    public class UserService {
        public PasswordStatus getPasswordStatus(User user, int daysDuration) {
            // check precondizioni .... (es. daysDuration non negativo)
    
            Instant expireInstant = user.getPasswordUpdateInstant().plus(daysDuration, ChronoUnit.DAYS);
            Instant now = Instant.now();
    
            PasswordStatus status = new PasswordStatus();
            status.setExpired(now.compareTo(expireInstant) >= 0);
            status.setExpireInstant(expireInstant);
            return status;
        }
    }
    La implementazione mi pare abbastanza semplice e comprensibile: dato lo User e un numero di giorni di "validità", mi dice lo stato di scadenza o no della password. Non ho messo check su precondizioni varie ma NON ha importanza ...

    Ora: questo getPasswordStatus è testabile? NO, non è testabile. O perlomeno per dirlo meglio: è molto difficile testarlo.
    Per un motivo molto semplice: quel now() che usa sempre la data/ora di sistema.

    Due test ragionevoli da fare potrebbero essere questi. Dato un User con passwordUpdateInstant uguale a 2021-10-11T11:22:33.123Z

    a) verificare che con una data "now" del 2021-10-21T11:22:33.123Z la password sia "scaduta"
    b) verificare invece che con un "now" 1 millisecondo prima, ovvero 2021-10-21T11:22:33.122Z la password sia ancora valida

    Domanda per te: COME faresti dei test del genere se non puoi sapere/controllare la data/ora "corrente" ??
    Pensaci ....

    P.S. piccola risposta: UserService andrebbe innanzitutto rifattorizzato per usare Clock.
  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    Grazie ho capito qualcosa di più, il Clock è uno strumento più potente per la gestione delle ore utile in fase di test.
    Nel mio caso l'utilizzo del Clock non è indispensabile perché devo scrivere una serie di record in un database e contare quelli che sono stati messi nella data di oggi.
    Nella mia classe di test, in jUnit uso questo approccio per mettere date false:
    
        public OffsetDateTime dataOraDaStringa(String stringaDataOra, String formatoDataOra){
            try{
                DateTimeFormatter f = DateTimeFormatter.ofPattern(formatoDataOra, nazione).withZone(ZoneId.of(zona));
                OffsetDateTime dataOra = OffsetDateTime.parse(stringaDataOra, f);
                return dataOra;
            }catch (Exception e){
                return null;
            }
        }
    
    Se questo approccio è corretto il mio metodo che usa jdbctemplate non funziona per qualche altro motivo ma questo è un altro discorso...
  • Re: Meglio usare Clock oppure ZoneId? Qual è la differenza?

    iBaffiPro ha scritto:


    Nella mia classe di test, in jUnit uso questo approccio per mettere date false:
    
        public OffsetDateTime dataOraDaStringa(String stringaDataOra, String formatoDataOra){
            try{
                DateTimeFormatter f = DateTimeFormatter.ofPattern(formatoDataOra, nazione).withZone(ZoneId.of(zona));
                OffsetDateTime dataOra = OffsetDateTime.parse(stringaDataOra, f);
                return dataOra;
            }catch (Exception e){
                return null;
            }
        }
    
    Se questo approccio è corretto
    Nì .. o perlomeno non lo posso sapere. Questo qui sembra più che altro un metodo di "utilità" che probabilmente potrebbe non essere nemmeno necessario.

    Se vuoi comprendere meglio ragiona ulteriormente sull'esempio riportato prima.
Devi accedere o registrarti per scrivere nel forum
7 risposte