class ShanghaiTime { ShanghaiTime._(); static const offset = Duration(hours: 8); /// A device-timezone-independent civil clock for Asia/Shanghai. static DateTime get now => toCivil(DateTime.now()); static DateTime toCivil(DateTime instant) { final shifted = instant.toUtc().add(offset); return DateTime( shifted.year, shifted.month, shifted.day, shifted.hour, shifted.minute, shifted.second, shifted.millisecond, shifted.microsecond, ); } static DateTime civilToUtc(DateTime civil) => DateTime.utc( civil.year, civil.month, civil.day, civil.hour, civil.minute, civil.second, civil.millisecond, civil.microsecond, ).subtract(offset); /// Parses API/cache instants. Legacy values without a zone were stored as UTC. static DateTime parseUtcInstant(String value) { final parsed = DateTime.parse(value); if (value.endsWith('Z') || RegExp(r'[+-]\d{2}:?\d{2}$').hasMatch(value)) { return parsed.toUtc(); } return DateTime.utc( parsed.year, parsed.month, parsed.day, parsed.hour, parsed.minute, parsed.second, parsed.millisecond, parsed.microsecond, ); } static DateTime parseCivil(String value) => toCivil(parseUtcInstant(value)); static DateTime startOfDayUtc(DateTime civil) => civilToUtc(DateTime(civil.year, civil.month, civil.day)); static ({DateTime start, DateTime end}) monthRangeUtc(int year, int month) => ( start: civilToUtc(DateTime(year, month, 1)), end: civilToUtc(DateTime(year, month + 1, 1)), ); static ({DateTime start, DateTime end}) yearRangeUtc(int year) => ( start: civilToUtc(DateTime(year, 1, 1)), end: civilToUtc(DateTime(year + 1, 1, 1)), ); static ({DateTime start, DateTime end}) weekRangeUtc(DateTime civil) { final date = DateTime(civil.year, civil.month, civil.day); final start = date.subtract(Duration(days: date.weekday - DateTime.monday)); return ( start: civilToUtc(start), end: civilToUtc(start.add(const Duration(days: 7))), ); } static String formatCivil(DateTime value) => '${value.year.toString().padLeft(4, '0')}-' '${value.month.toString().padLeft(2, '0')}-' '${value.day.toString().padLeft(2, '0')} ' '${value.hour.toString().padLeft(2, '0')}:' '${value.minute.toString().padLeft(2, '0')}'; static String formatDateTime(DateTime instant) { final value = toCivil(instant); return formatCivil(value); } }