glp-companion/lib/core/utils/date_time_utils.dart

65 lines
2.5 KiB
Dart

// Shared, framework-free date helpers.
//
// The project intentionally avoids `intl` to keep core utilities usable from
// pure Dart domain code and tests; these formatters cover the app's display
// needs with explicit, stable output.
const List<String> _monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
];
/// Formats [time] as e.g. `Oct 16, 2026 · 2:05 PM`.
String formatDateTime(DateTime time) {
final local = time.toLocal();
final hour12 = local.hour % 12 == 0 ? 12 : local.hour % 12;
final minute = local.minute.toString().padLeft(2, '0');
final period = local.hour < 12 ? 'AM' : 'PM';
return '${_monthNames[local.month - 1]} ${local.day}, ${local.year}'
' · $hour12:$minute $period';
}
/// Start of the local calendar day containing [time] (`00:00:00.000`).
DateTime startOfDay(DateTime time) =>
DateTime(time.year, time.month, time.day);
/// End of the local calendar day containing [time] — represented as the
/// exclusive upper bound `00:00:00.000` of the following day, so half-open
/// `[startOfDay, endOfDay)` ranges cover the whole day without an off-by-one
/// at midnight.
DateTime endOfDay(DateTime time) =>
DateTime(time.year, time.month, time.day + 1);
/// Start of the ISO-8601 calendar week containing [time] — Monday
/// `00:00:00.000` as a local calendar day. Day arithmetic uses the
/// [DateTime] constructor (not `Duration`) so daylight-saving transitions
/// can never shift the result off midnight.
DateTime startOfWeek(DateTime time) {
final day = startOfDay(time);
return DateTime(day.year, day.month, day.day - (day.weekday - DateTime.monday));
}
/// Formats [time] as a short month/day label, e.g. `Oct 16`.
String formatMonthDay(DateTime time) {
final local = time.toLocal();
return '${_monthNames[local.month - 1]} ${local.day}';
}
/// Formats a coarse elapsed time relative to [now], e.g. `never`, `3 hours
/// ago`, `5 days ago`.
String formatRelativeTime(DateTime time, {DateTime? now}) {
final reference = now ?? DateTime.now();
final elapsed = reference.difference(time);
if (elapsed.isNegative) {
final ahead = time.difference(reference);
if (ahead.inDays >= 1) return 'in ${ahead.inDays} day(s)';
if (ahead.inHours >= 1) return 'in ${ahead.inHours} hour(s)';
return 'in a moment';
}
if (elapsed.inMinutes < 1) return 'just now';
if (elapsed.inHours < 1) return '${elapsed.inMinutes} minute(s) ago';
if (elapsed.inDays < 1) return '${elapsed.inHours} hour(s) ago';
return '${elapsed.inDays} day(s) ago';
}