← Back to Blog

Fix Flutter LocaleDataException in DateFormat

flutterintldateformatlocalizationtable_calendar

Fix Flutter LocaleDataException in DateFormat

You wired up intl, called DateFormat.yMMMMd('fr_FR'), and the moment a non-English screen rendered your app died with:

LocaleDataException: Locale data has not been initialized, call initializeDateFormatting(<locale>).

This is one of the most common crashes when adding date formatting, a date picker, or table_calendar to a Flutter app. The good news: it is never a bug in intl. It is a load-order problem, and there are exactly three variants of it. Here is what the error actually means and three copy-paste main() fixes.

What the crash string is really telling you

The intl package does not ship every locale's symbol data loaded and ready. By design, locale data (month names, weekday names, date patterns for fr_FR, de_DE, ja, and hundreds more) sits inert until you explicitly initialize it. This keeps your bundle small — you pay only for the locales you load.

So when DateFormat('fr_FR').format(date) runs and the fr_FR symbol table was never initialized, intl throws LocaleDataException. The default en_US locale is the only one that works with zero setup, which is exactly why the crash shows up the first time a non-en_US locale renders — and why it often slips past testing on an English device.

The fix is always the same shape: call initializeDateFormatting from package:intl/date_symbol_data_local.dart and let its Future complete before any DateFormat runs. The three causes below are the three ways people get that timing wrong.

Cause 1: Not awaiting initialization before runApp

The classic mistake is calling initializeDateFormatting() without await, or forgetting it entirely. initializeDateFormatting returns a Future<void> — fire it without awaiting and runApp starts building widgets, and calling DateFormat, before the data lands.

Make main async, and gate runApp behind the initialization:

import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeDateFormatting('fr_FR', null);
  runApp(const MyApp());
}

WidgetsFlutterBinding.ensureInitialized() is required whenever you do async work before runApp. The second argument to initializeDateFormatting is a legacy parameter that is ignored for the local (bundled) data source — pass null or omit it.

If you prefer the callback style, this is equivalent and just as valid:

void main() {
  initializeDateFormatting('fr_FR', null).then((_) => runApp(const MyApp()));
}

Cause 2: Initializing one locale but formatting with another

This one is sneaky because your main() looks correct. You initialized en, but somewhere a widget calls DateFormat.yMMMMd('en_US') — or you initialized pt_BR and a calendar defaults to pt. Locale codes are matched fairly literally, so en and en_US are not interchangeable, and initializing the wrong one throws the same exception.

Two reliable ways out.

Option A — initialize exactly the locales you format with, using Future.wait so they load in parallel:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Future.wait([
    initializeDateFormatting('en_US', null),
    initializeDateFormatting('fr_FR', null),
    initializeDateFormatting('pt_BR', null),
  ]);
  runApp(const MyApp());
}

Whenever you add a language to your app, add its locale here too. If you generate strings from ARB files, keep this list in lockstep with your supported locales — our ARB editor makes it easy to see exactly which locales you ship so this list never drifts.

Option B — load every locale and stop worrying about mismatches. Call initializeDateFormatting() with no arguments:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeDateFormatting(); // all locales
  runApp(const MyApp());
}

This is bulletproof against Cause 2, at the cost of bundling all locale symbol data. See the tradeoff table below.

Cause 3: Flutter Web loading symbol data asynchronously

On Flutter Web the timing bites harder. With the standard date_symbol_data_local source the data is compiled into your JS bundle, but initializeDateFormatting still returns a Future — and if any DateFormat runs in an early build pass (a splash screen, a top-level widget, a table_calendar created before init resolves) you get the crash even though main() awaits.

The robust pattern is to never build formatting-dependent UI until initialization is confirmed complete. A FutureBuilder guarantees it:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<void>(
      future: initializeDateFormatting('de_DE', null),
      builder: (context, snapshot) {
        if (snapshot.connectionState != ConnectionState.done) {
          return const MaterialApp(
            home: Scaffold(body: Center(child: CircularProgressIndicator())),
          );
        }
        return const MaterialApp(home: HomePage());
      },
    );
  }
}

If you are loading locale data over the network to shrink the initial download — package:intl/date_symbol_data_http.dart — then loading is genuinely asynchronous and the FutureBuilder (or an awaited main()) is mandatory, not optional. Do not create DateFormat instances at the top level of a file, where they run before any await has a chance to complete.

table_calendar: same crash, same fix

table_calendar accepts a locale: parameter (e.g. locale: 'pl_PL') and internally builds DateFormat for that locale. It does not initialize the data for you — that is your job. So the crash from table_calendar is Cause 1 or Cause 2 wearing a different hat:

import 'package:intl/date_symbol_data_local.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeDateFormatting('pl_PL', null);
  runApp(const MyApp());
}

// then, in your widget tree:
// TableCalendar(locale: 'pl_PL', ...)

Rule of thumb: the string you pass to TableCalendar(locale: ...) must be a locale you passed to initializeDateFormatting. Same story for third-party date pickers.

Per-locale vs all-locales: the tradeoff

Per-locale (initializeDateFormatting('fr_FR')) All locales (initializeDateFormatting())
Bundle / download size Smallest — only what you load Largest — all symbol data included
Startup cost Minimal Slightly higher init time
Risk of Cause 2 mismatch Present — must keep list in sync None — every locale is loaded
Best for Production apps shipping a known set of languages Prototypes, internal tools, apps with dynamic locale switching
Web Recommended, keeps JS smaller Fine for small apps, watch bundle size

For most shipping apps, initialize the exact set of locales you support and keep that set identical to your translation catalog. For quick prototypes or admin tools where size is irrelevant, load everything and move on.

Quick checklist

  • Import package:intl/date_symbol_data_local.dart.
  • Call WidgetsFlutterBinding.ensureInitialized() then await initializeDateFormatting(...) before runApp.
  • Initialize the same locale string you format with — enen_US.
  • On web (or with async/HTTP data), gate formatting UI behind a FutureBuilder.
  • Keep your table_calendar locale: in your initialized set.

Get the load order right once and LocaleDataException is gone for good.


Managing which locales you actually ship — and keeping your initializeDateFormatting list honest against your translations — is exactly what FlutterLocalisation is built for. Edit ARB files, spot missing locales, and manage everything in one place. Try FlutterLocalisation free and stop chasing locale bugs by hand.