import 'package:intl/intl.dart'; import 'package:timezone/timezone.dart' as tz; // Source - https://stackoverflow.com/a/54775297 // Posted by diegoveloper, modified by community. See post 'Timeline' for change history // Retrieved 2026-05-09, License - CC BY-SA 4.0 String _printDuration(Duration duration) { String negativeSign = duration.isNegative ? '-' : ''; String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigitMinutes = twoDigits(duration.inMinutes.abs()); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60).abs()); return "$negativeSign$twoDigitMinutes:$twoDigitSeconds"; } String formatTimeWindow(String? starts, String? ends) { if (starts != null && ends != null) { DateTime? startDt = _tryParseServerDateTime(starts); DateTime? endDt = _tryParseServerDateTime(ends); if (startDt == null || endDt == null) { return '--:--'; } Duration songLength = endDt.difference(startDt); return _printDuration(songLength); } return "--:--"; } String formatStartingTime(String? startingTime, {String? sourceTimezone}) { if (startingTime != null) { final DateTime? localStart = _serverTimeToLocal( startingTime, sourceTimezone: sourceTimezone, ); if (localStart == null) { return '--:--'; } return DateFormat('jm').format(localStart); } return "--:--"; } DateTime? _serverTimeToLocal(String rawTime, {String? sourceTimezone}) { final DateTime? parsed = _tryParseServerDateTime(rawTime); if (parsed == null) { return null; } if (_hasExplicitTimezone(rawTime)) { return parsed.toLocal(); } // API timestamps are wall-clock values in the API timezone field. if (sourceTimezone == null || sourceTimezone.trim().isEmpty) { return parsed; } try { final tz.Location sourceLocation = tz.getLocation(sourceTimezone.trim()); final tz.TZDateTime sourceTime = tz.TZDateTime( sourceLocation, parsed.year, parsed.month, parsed.day, parsed.hour, parsed.minute, parsed.second, parsed.millisecond, parsed.microsecond, ); return DateTime.fromMillisecondsSinceEpoch( sourceTime.millisecondsSinceEpoch, isUtc: true, ).toLocal(); } catch (_) { return parsed; } } bool _hasExplicitTimezone(String value) { final String trimmed = value.trim(); if (trimmed.endsWith('Z') || trimmed.endsWith('z')) { return true; } final RegExp offsetPattern = RegExp(r'[+-]\d{2}:?\d{2}$'); return offsetPattern.hasMatch(trimmed); } DateTime? _tryParseServerDateTime(String value) { final String trimmed = value.trim(); if (trimmed.isEmpty) { return null; } final DateTime? isoAttempt = DateTime.tryParse(trimmed); if (isoAttempt != null) { return isoAttempt; } final RegExp serverDateTimePattern = RegExp( r'^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?$', ); final RegExpMatch? match = serverDateTimePattern.firstMatch(trimmed); if (match == null) { return null; } int parsePart(int group) => int.parse(match.group(group)!); final int year = parsePart(1); final int month = parsePart(2); final int day = parsePart(3); final int hour = parsePart(4); final int minute = parsePart(5); final int second = parsePart(6); final String fraction = (match.group(7) ?? '').padRight(6, '0'); final int microseconds = fraction.isEmpty ? 0 : int.parse(fraction.substring(0, 6)); return DateTime( year, month, day, hour, minute, second, microseconds ~/ 1000, microseconds % 1000, ); }