fixes timestamping across timezones

This commit is contained in:
2026-05-14 22:51:04 -07:00
parent cbdc3f7710
commit d32720a2e0
6 changed files with 225 additions and 7 deletions
+107 -5
View File
@@ -1,4 +1,5 @@
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
@@ -14,8 +15,11 @@ String _printDuration(Duration duration) {
String formatTimeWindow(String? starts, String? ends) {
if (starts != null && ends != null) {
DateTime startDt = DateTime.parse(starts);
DateTime endDt = DateTime.parse(ends);
DateTime? startDt = _tryParseServerDateTime(starts);
DateTime? endDt = _tryParseServerDateTime(ends);
if (startDt == null || endDt == null) {
return '--:--';
}
Duration songLength = endDt.difference(startDt);
@@ -25,10 +29,108 @@ String formatTimeWindow(String? starts, String? ends) {
return "--:--";
}
String formatStartingTime(String? startingTime) {
String formatStartingTime(String? startingTime, {String? sourceTimezone}) {
if (startingTime != null) {
DateTime dtStartTime = DateTime.parse(startingTime);
return DateFormat('jm').format(dtStartTime);
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,
);
}