137 lines
3.5 KiB
Dart
137 lines
3.5 KiB
Dart
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,
|
|
);
|
|
}
|