flutter_relative_time 0.2.0
flutter_relative_time: ^0.2.0 copied to clipboard
Human-friendly past and future DateTime formatting with localized timeago and calendar labels.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_relative_time/flutter_relative_time.dart';
import 'package:intl/date_symbol_data_local.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting();
runApp(const RelativeTimeExampleApp());
}
class RelativeTimeExampleApp extends StatelessWidget {
const RelativeTimeExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'flutter_relative_time example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF4E4193)),
useMaterial3: true,
),
home: const RelativeTimeExamplePage(),
);
}
}
class RelativeTimeExamplePage extends StatefulWidget {
const RelativeTimeExamplePage({super.key});
@override
State<RelativeTimeExamplePage> createState() =>
_RelativeTimeExamplePageState();
}
class _RelativeTimeExamplePageState extends State<RelativeTimeExamplePage> {
static final DateTime _now = DateTime(2026, 8, 2, 12);
String _locale = 'en_US';
RelativeTimeClockFormat _clockFormat = RelativeTimeClockFormat.auto;
@override
Widget build(BuildContext context) {
final examples = _buildExamples();
return Scaffold(
appBar: AppBar(title: const Text('flutter_relative_time')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(
'Format DateTime values for future, past, calendar and very recent UI labels.',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 20),
_ExampleSection(
title: 'Locale',
child: Wrap(
spacing: 8,
children: [
_ChoiceChip(
label: 'English US',
selected: _locale == 'en_US',
onSelected: () => setState(() => _locale = 'en_US'),
),
_ChoiceChip(
label: 'Spanish ES',
selected: _locale == 'es_ES',
onSelected: () => setState(() => _locale = 'es_ES'),
),
_ChoiceChip(
label: 'Portuguese BR',
selected: _locale == 'pt_BR',
onSelected: () => setState(() => _locale = 'pt_BR'),
),
],
),
),
const SizedBox(height: 12),
_ExampleSection(
title: 'Clock format',
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
_ChoiceChip(
label: 'Auto',
selected: _clockFormat == RelativeTimeClockFormat.auto,
onSelected: () => setState(
() => _clockFormat = RelativeTimeClockFormat.auto,
),
),
_ChoiceChip(
label: '12h',
selected: _clockFormat == RelativeTimeClockFormat.twelveHour,
onSelected: () => setState(
() => _clockFormat = RelativeTimeClockFormat.twelveHour,
),
),
_ChoiceChip(
label: '24h',
selected:
_clockFormat == RelativeTimeClockFormat.twentyFourHour,
onSelected: () => setState(
() => _clockFormat = RelativeTimeClockFormat.twentyFourHour,
),
),
],
),
),
const SizedBox(height: 20),
for (final example in examples) ...[
_ExampleCard(example: example),
const SizedBox(height: 12),
],
],
),
);
}
List<_RelativeTimeExample> _buildExamples() {
final timeAgoOptions = RelativeTimeFormatOptions.timeAgo(now: _now);
final calendarOptions = RelativeTimeFormatOptions.calendar(
now: _now,
clockFormat: _clockFormat,
);
return [
_RelativeTimeExample(
title: 'Future · time ago',
input: 'DateTime(2026, 8, 2, 14)',
code:
"startsAt.toTimeAgo(locale: '$_locale', options: RelativeTimeFormatOptions.timeAgo(now: now))",
value: DateTime(
2026,
8,
2,
14,
).toTimeAgo(locale: _locale, options: timeAgoOptions),
),
_RelativeTimeExample(
title: 'Future · calendar',
input: 'DateTime(2026, 8, 3, 5, 3)',
code:
"tomorrow.toRelativeCalendar(locale: '$_locale', options: RelativeTimeFormatOptions.calendar(now: now, clockFormat: $_clockFormat))",
value: DateTime(
2026,
8,
3,
5,
3,
).toRelativeCalendar(locale: _locale, options: calendarOptions),
),
_RelativeTimeExample(
title: 'Past · time ago',
input: 'DateTime(2026, 8, 2, 11, 55)',
code:
"createdAt.toTimeAgo(locale: '$_locale', options: RelativeTimeFormatOptions.timeAgo(now: now))",
value: DateTime(
2026,
8,
2,
11,
55,
).toTimeAgo(locale: _locale, options: timeAgoOptions),
),
_RelativeTimeExample(
title: 'Past · calendar',
input: 'DateTime(2026, 7, 28, 18, 45)',
code:
"paidAt.toRelativeCalendar(locale: '$_locale', options: RelativeTimeFormatOptions.calendar(now: now, clockFormat: $_clockFormat))",
value: DateTime(
2026,
7,
28,
18,
45,
).toRelativeCalendar(locale: _locale, options: calendarOptions),
),
_RelativeTimeExample(
title: 'Very recent · past',
input: 'DateTime(2026, 8, 2, 11, 59, 50)',
code:
"sentAt.toTimeAgo(locale: '$_locale', options: RelativeTimeFormatOptions.timeAgo(now: now))",
value: DateTime(
2026,
8,
2,
11,
59,
50,
).toTimeAgo(locale: _locale, options: timeAgoOptions),
),
_RelativeTimeExample(
title: 'Very recent · future',
input: 'DateTime(2026, 8, 2, 12, 0, 10)',
code:
"retryAt.toTimeAgo(locale: '$_locale', options: RelativeTimeFormatOptions.timeAgo(now: now))",
value: DateTime(
2026,
8,
2,
12,
0,
10,
).toTimeAgo(locale: _locale, options: timeAgoOptions),
),
_RelativeTimeExample(
title: 'Nullable DateTime',
input: 'DateTime? value = null',
code: "value.toRelativeTime(locale: '$_locale')",
value: null.toRelativeTime(locale: _locale),
),
];
}
}
class _ExampleSection extends StatelessWidget {
const _ExampleSection({required this.title, required this.child});
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 8),
child,
],
);
}
}
class _ChoiceChip extends StatelessWidget {
const _ChoiceChip({
required this.label,
required this.selected,
required this.onSelected,
});
final String label;
final bool selected;
final VoidCallback onSelected;
@override
Widget build(BuildContext context) {
return ChoiceChip(
label: Text(label),
selected: selected,
onSelected: (_) => onSelected(),
);
}
}
class _ExampleCard extends StatelessWidget {
const _ExampleCard({required this.example});
final _RelativeTimeExample example;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Card(
elevation: 0,
color: colorScheme.surfaceContainerHighest,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(example.title, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(
example.value,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: colorScheme.primary,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 12),
_Metadata(label: 'Input', value: example.input),
const SizedBox(height: 8),
_Metadata(label: 'Usage', value: example.code),
],
),
),
);
}
}
class _Metadata extends StatelessWidget {
const _Metadata({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: Theme.of(context).textTheme.labelMedium),
const SizedBox(height: 4),
SelectableText(
value,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'),
),
],
);
}
}
class _RelativeTimeExample {
const _RelativeTimeExample({
required this.title,
required this.input,
required this.code,
required this.value,
});
final String title;
final String input;
final String code;
final String value;
}