Selezionare date e orari in Flutter con showDatePicker, showTimePicker e intl

Foto di Walls.io su Unsplash

GuidePrincipiante30 min Flutter 3.x

Selezionare date e orari in Flutter con showDatePicker, showTimePicker e intl

Quasi ogni app prima o poi deve chiedere una data: la scadenza di un'attività, la data di nascita, l'orario di un promemoria. Flutter mette a disposizione due dialoghi Material già pronti, showDatePicker e showTimePicker, che funzionano su Android, iOS, web e desktop senza pacchetti esterni.

In questo tutorial per principianti costruiremo una piccola schermata "Nuovo appuntamento" che permette di:

  • scegliere una data con il calendario;
  • scegliere un orario con l'orologio;
  • mostrare i valori formattati in italiano (es. lunedì 12 maggio 2025, 09:30);
  • unire data e ora in un unico oggetto DateTime;
  • selezionare un intervallo di date con showDateRangePicker.

Avrai bisogno solo di un progetto Flutter appena creato e di pochi minuti. Partiamo!

  1. 1

    Creare il progetto e aggiungere le dipendenze

    Crea (o apri) un progetto Flutter e aggiungi due dipendenze:

    • intl: per formattare date e orari secondo la lingua italiana;
    • flutter_localizations: pacchetto ufficiale che fornisce le traduzioni dei widget Material (compresi i dialoghi di data e ora).

    Puoi aggiungerli da terminale:

    flutter create date_demo
    cd date_demo
    flutter pub add intl
    flutter pub add flutter_localizations --sdk=flutter
    

    Il comando aggiorna automaticamente il pubspec.yaml come mostrato nello snippet. Ricorda l'indentazione a due spazi: nello YAML è fondamentale.

    name: date_demo
    description: Demo selezione date e orari
    
    environment:
      sdk: '>=3.0.0 <4.0.0'
    
    dependencies:
      flutter:
        sdk: flutter
      flutter_localizations:
        sdk: flutter
      intl: ^0.19.0
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
    
    flutter:
      uses-material-design: true

    Risultato atteso

    Il comando `flutter pub get` termina senza errori e le dipendenze compaiono nel pubspec.yaml.

  2. 2

    Configurare la localizzazione italiana in MaterialApp

    Senza questa configurazione i dialoghi appaiono in inglese ("Select date", "CANCEL", ecc.) e il calendario parte dalla domenica.

    Nel MaterialApp aggiungi:

    • localizationsDelegates: i delegati che caricano le traduzioni dei widget;
    • supportedLocales: le lingue supportate dall'app;
    • locale: forza l'italiano (utile per provare senza cambiare la lingua del dispositivo; in produzione puoi ometterlo e lasciare la scelta al sistema).

    Sostituisci il contenuto di lib/main.dart con lo snippet.

    import 'package:flutter/material.dart';
    import 'package:flutter_localizations/flutter_localizations.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Date e orari',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            colorSchemeSeed: Colors.indigo,
            useMaterial3: true,
          ),
          // Traduzioni dei widget Material/Cupertino/Widgets
          localizationsDelegates: const [
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: const [
            Locale('it', 'IT'),
            Locale('en', 'US'),
          ],
          locale: const Locale('it', 'IT'), // forza l'italiano
          home: const AppuntamentoPage(),
        );
      }
    }
    
    class AppuntamentoPage extends StatefulWidget {
      const AppuntamentoPage({super.key});
    
      @override
      State<AppuntamentoPage> createState() => _AppuntamentoPageState();
    }
    
    class _AppuntamentoPageState extends State<AppuntamentoPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Nuovo appuntamento')),
          body: const Center(child: Text('Iniziamo!')),
        );
      }
    }

    Risultato atteso

    L'app si avvia mostrando la AppBar "Nuovo appuntamento" e i futuri dialoghi saranno in italiano.

  3. 3

    Mostrare il calendario con showDatePicker

    showDatePicker è una funzione asincrona che apre un dialogo e restituisce un Future<DateTime?>:

    • se l'utente conferma, ottieni il DateTime scelto (con orario a mezzanotte);
    • se annulla o chiude il dialogo, ottieni null. Controlla sempre il null prima di usare il valore.

    Parametri principali:

    • initialDate: la data preselezionata;
    • firstDate / lastDate: intervallo selezionabile (obbligatori);
    • initialEntryMode: calendar (default) oppure input per la digitazione manuale;
    • helpText, confirmText, cancelText: testi personalizzabili.

    Aggiungi allo State la variabile _data e il metodo _selezionaData.

    DateTime? _data;
    
    Future<void> _selezionaData() async {
      final oggi = DateTime.now();
      final DateTime? scelta = await showDatePicker(
        context: context,
        initialDate: _data ?? oggi,
        firstDate: DateTime(oggi.year - 1),
        lastDate: DateTime(oggi.year + 5),
        helpText: 'Seleziona la data',
        confirmText: 'Conferma',
        cancelText: 'Annulla',
      );
    
      // L'utente può aver annullato: scelta è null
      if (scelta != null) {
        setState(() => _data = scelta);
      }
    }
    
    // Nel build, dentro il body:
    // ElevatedButton.icon(
    //   onPressed: _selezionaData,
    //   icon: const Icon(Icons.calendar_today),
    //   label: Text(_data == null ? 'Scegli data' : _data.toString()),
    // )

    Risultato atteso

    Toccando il pulsante si apre il calendario in italiano; dopo la conferma la data appare nel pulsante (in formato grezzo, es. 2025-05-12 00:00:00.000).

  4. 4

    Scegliere l'orario con showTimePicker

    showTimePicker restituisce un Future<TimeOfDay?>. TimeOfDay contiene solo hour e minute: non è un DateTime.

    Due parametri utili:

    • initialEntryMode: TimePickerEntryMode.dial (orologio) o .input (tastiera);
    • il builder MediaQuery con alwaysUse24HourFormat: true per forzare il formato 24 ore, tipico italiano.

    Per mostrare l'orario a schermo puoi usare ora.format(context), che rispetta automaticamente la localizzazione.

    TimeOfDay? _ora;
    
    Future<void> _selezionaOra() async {
      final TimeOfDay? scelta = await showTimePicker(
        context: context,
        initialTime: _ora ?? TimeOfDay.now(),
        helpText: 'Seleziona l\'orario',
        confirmText: 'Conferma',
        cancelText: 'Annulla',
        initialEntryMode: TimePickerEntryMode.dial,
        builder: (context, child) {
          // Forza il formato 24 ore
          return MediaQuery(
            data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
            child: child!,
          );
        },
      );
    
      if (scelta != null) {
        setState(() => _ora = scelta);
      }
    }
    
    // Esempio di visualizzazione:
    // Text(_ora == null ? 'Nessun orario' : _ora!.format(context))

    Risultato atteso

    Si apre l'orologio Material in italiano e l'orario selezionato viene mostrato nel formato 24 ore (es. 09:30).

  5. 5

    Formattare data e ora in italiano con intl

    DateTime.toString() non è adatto all'utente finale. Il pacchetto intl offre DateFormat, che produce stringhe leggibili e localizzate.

    Due passaggi obbligatori:

    1. chiamare initializeDateFormatting('it_IT') in main() prima di runApp (carica i dati della lingua);
    2. passare il locale al costruttore: DateFormat('EEEE d MMMM y', 'it_IT').

    Pattern utili:

    Pattern Esempio
    dd/MM/yyyy 12/05/2025
    EEEE d MMMM y lunedì 12 maggio 2025
    d MMM y, HH:mm 12 mag 2025, 09:30

    Se l'app è localizzata puoi anche usare le scorciatoie del framework: MaterialLocalizations.of(context).formatFullDate(data).

    import 'package:intl/date_symbol_data_local.dart';
    import 'package:intl/intl.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await initializeDateFormatting('it_IT', null);
      runApp(const MyApp());
    }
    
    // Dentro lo State:
    String get _dataFormattata {
      if (_data == null) return 'Nessuna data selezionata';
      return DateFormat('EEEE d MMMM y', 'it_IT').format(_data!);
    }
    
    String get _oraFormattata {
      if (_ora == null) return 'Nessun orario selezionato';
      final adesso = DateTime.now();
      final dt = DateTime(adesso.year, adesso.month, adesso.day, _ora!.hour, _ora!.minute);
      return DateFormat('HH:mm', 'it_IT').format(dt);
    }

    Risultato atteso

    La data compare come "lunedì 12 maggio 2025" e l'orario come "09:30".

  6. 6

    Unire data e ora in un unico DateTime

    Per salvare l'appuntamento serve un solo oggetto DateTime che contenga sia il giorno sia l'orario. Si costruisce combinando i campi:

    DateTime(data.year, data.month, data.day, ora.hour, ora.minute);
    

    Aggiungiamo anche una piccola validazione: se l'utente non ha scelto entrambi i valori mostriamo uno SnackBar. Come bonus, showDateRangePicker permette di selezionare un intervallo di date e restituisce un DateTimeRange con start, end e duration.

    DateTime? get _appuntamento {
      if (_data == null || _ora == null) return null;
      return DateTime(
        _data!.year,
        _data!.month,
        _data!.day,
        _ora!.hour,
        _ora!.minute,
      );
    }
    
    void _salva() {
      final appuntamento = _appuntamento;
      if (appuntamento == null) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Seleziona sia la data che l\'orario')),
        );
        return;
      }
      final testo = DateFormat("d MMMM y 'alle' HH:mm", 'it_IT').format(appuntamento);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Appuntamento: $testo')),
      );
    }
    
    // Bonus: intervallo di date
    Future<void> _selezionaIntervallo() async {
      final oggi = DateTime.now();
      final DateTimeRange? range = await showDateRangePicker(
        context: context,
        firstDate: DateTime(oggi.year - 1),
        lastDate: DateTime(oggi.year + 2),
        saveText: 'Conferma',
      );
      if (range != null) {
        final f = DateFormat('dd/MM/yyyy', 'it_IT');
        debugPrint('Dal ${f.format(range.start)} al ${f.format(range.end)} '
            '(${range.duration.inDays} giorni)');
      }
    }

    Risultato atteso

    Premendo "Salva" appare uno SnackBar con la frase "Appuntamento: 12 maggio 2025 alle 09:30"; se manca un valore compare l'avviso di errore.

  7. 7

    Mettere insieme tutto: la schermata completa

    Ecco il file main.dart completo, pronto da copiare e incollare. Usiamo due ListTile con icona: è il pattern più comune nei form, perché l'intera riga è toccabile e mostra chiaramente il valore selezionato.

    Prossimi passi da provare:

    • imposta selectableDayPredicate in showDatePicker per disabilitare weekend o giorni non disponibili;
    • usa initialEntryMode: DatePickerEntryMode.input per la data di nascita (più veloce da digitare);
    • salva il DateTime con toIso8601String() e rileggilo con DateTime.parse().
    import 'package:flutter/material.dart';
    import 'package:flutter_localizations/flutter_localizations.dart';
    import 'package:intl/date_symbol_data_local.dart';
    import 'package:intl/intl.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await initializeDateFormatting('it_IT', null);
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
          localizationsDelegates: const [
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: const [Locale('it', 'IT'), Locale('en', 'US')],
          locale: const Locale('it', 'IT'),
          home: const AppuntamentoPage(),
        );
      }
    }
    
    class AppuntamentoPage extends StatefulWidget {
      const AppuntamentoPage({super.key});
    
      @override
      State<AppuntamentoPage> createState() => _AppuntamentoPageState();
    }
    
    class _AppuntamentoPageState extends State<AppuntamentoPage> {
      DateTime? _data;
      TimeOfDay? _ora;
    
      Future<void> _selezionaData() async {
        final oggi = DateTime.now();
        final scelta = await showDatePicker(
          context: context,
          initialDate: _data ?? oggi,
          firstDate: DateTime(oggi.year - 1),
          lastDate: DateTime(oggi.year + 5),
          helpText: 'Seleziona la data',
          confirmText: 'Conferma',
          cancelText: 'Annulla',
        );
        if (scelta != null) setState(() => _data = scelta);
      }
    
      Future<void> _selezionaOra() async {
        final scelta = await showTimePicker(
          context: context,
          initialTime: _ora ?? TimeOfDay.now(),
          builder: (context, child) => MediaQuery(
            data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
            child: child!,
          ),
        );
        if (scelta != null) setState(() => _ora = scelta);
      }
    
      DateTime? get _appuntamento {
        if (_data == null || _ora == null) return null;
        return DateTime(_data!.year, _data!.month, _data!.day, _ora!.hour, _ora!.minute);
      }
    
      void _salva() {
        final a = _appuntamento;
        if (a == null) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Seleziona sia la data che l\'orario')),
          );
          return;
        }
        final testo = DateFormat("d MMMM y 'alle' HH:mm", 'it_IT').format(a);
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Appuntamento: $testo')),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        final dataText = _data == null
            ? 'Nessuna data selezionata'
            : DateFormat('EEEE d MMMM y', 'it_IT').format(_data!);
        final oraText = _ora == null ? 'Nessun orario selezionato' : _ora!.format(context);
    
        return Scaffold(
          appBar: AppBar(title: const Text('Nuovo appuntamento')),
          body: ListView(
            padding: const EdgeInsets.all(16),
            children: [
              Card(
                child: ListTile(
                  leading: const Icon(Icons.calendar_today),
                  title: const Text('Data'),
                  subtitle: Text(dataText),
                  trailing: const Icon(Icons.chevron_right),
                  onTap: _selezionaData,
                ),
              ),
              const SizedBox(height: 8),
              Card(
                child: ListTile(
                  leading: const Icon(Icons.access_time),
                  title: const Text('Orario'),
                  subtitle: Text(oraText),
                  trailing: const Icon(Icons.chevron_right),
                  onTap: _selezionaOra,
                ),
              ),
              const SizedBox(height: 24),
              FilledButton.icon(
                onPressed: _salva,
                icon: const Icon(Icons.save),
                label: const Text('Salva appuntamento'),
              ),
            ],
          ),
        );
      }
    }

    Risultato atteso

    Una schermata con due schede toccabili per data e orario, testi in italiano e un pulsante che riepiloga l'appuntamento scelto.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!