Files
photo-live/apps/mobile/lib/features/albums/album_format.dart
T

87 lines
2.8 KiB
Dart
Raw Normal View History

/// 相册时区感知的时间展示(P3 计划 Task 4)。
///
/// 契约:startAt/endAt 按 UTC 存储,「展示按 timezone」。Dart 核心库无
/// IANA 时区数据库,引入 `timezone` 包(内嵌 tzdata)做墙钟换算,字符串
/// 格式化仍走 intl [DateFormat](App 语言)。tzdata 初始化为幂等懒加载
/// ([ensureTzInitialized]),生产与测试共用同一路径。
library;
import 'package:intl/intl.dart';
import 'package:timezone/data/latest.dart' as tzdata;
import 'package:timezone/timezone.dart' as tz;
bool _tzInitialized = false;
/// 初始化内嵌 tzdata(幂等;首次调用后为空操作)。
void ensureTzInitialized() {
if (!_tzInitialized) {
tzdata.initializeTimeZones();
_tzInitialized = true;
}
}
/// 解析 IANA 时区名;无效返回 null。
///
/// 内嵌 tzdata(10 年窗口)不含 `UTC` 顶名(仅 `Etc/UTC`),此处将
/// `UTC` 归一化到 `Etc/UTC`,避免最常用的时区名被误判非法。
tz.Location? _resolveLocation(String timeZone) {
ensureTzInitialized();
final name = timeZone.trim();
try {
return tz.getLocation(name == 'UTC' ? 'Etc/UTC' : name);
} on Object {
return null;
}
}
/// UTC 瞬间 → 相册时区墙钟;IANA 名无效返回 null。
tz.TZDateTime? tryToAlbumZone(DateTime instant, String timeZone) {
final location = _resolveLocation(timeZone);
if (location == null) return null;
return tz.TZDateTime.from(instant.toUtc(), location);
}
/// 时区名是否为合法 IANA 标识(表单校验用)。
bool isValidTimezone(String timeZone) =>
timeZone.trim().isNotEmpty &&
tryToAlbumZone(DateTime.utc(2026, 1, 1), timeZone) != null;
/// 按相册时区格式化时间(`y/M/d HH:mm` 模式,App locale)。
///
/// 时区无效时回退 UTC 展示(表单校验另行拦截无效时区,此处仅兜底)。
String formatAlbumDateTime(
DateTime utcInstant,
String timeZone,
String locale,
) {
final zoned = tryToAlbumZone(utcInstant, timeZone) ?? utcInstant.toUtc();
return DateFormat('y/M/d HH:mm', locale).format(zoned);
}
/// 相册墙钟(表单里选择的日期时间,设备本地时区标记的 naive 值)→
/// UTC 瞬间(提交与回显往返一致)。
///
/// 时区无效时按字面值视为 UTC(校验先行拦截,避免设备时区偏移污染)。
DateTime albumWallClockToUtc(DateTime wallClock, String timeZone) {
final location = _resolveLocation(timeZone);
if (location == null) {
return DateTime.utc(
wallClock.year,
wallClock.month,
wallClock.day,
wallClock.hour,
wallClock.minute,
);
}
return tz
.TZDateTime(
location,
wallClock.year,
wallClock.month,
wallClock.day,
wallClock.hour,
wallClock.minute,
)
.toUtc();
}