From 41e707d20967ec7bbbaf5644d622a5ac3538911c Mon Sep 17 00:00:00 2001 From: kleponijo <158453666+kleponijo@users.noreply.github.com> Date: Wed, 6 May 2026 17:47:40 +0700 Subject: [PATCH] update pembaruan time series mapper --- lib/core/utils/time_series_mapper.dart | 28 ++++++ lib/core/utils/timestamp_parser.dart | 6 -- .../evaporasi/blocs/evaporasi_bloc.dart | 89 ++++++------------- .../evaporasi/blocs/evaporasi_state.dart | 27 +++++- .../wind_speed/blocs/wind_speed_bloc.dart | 73 +++------------ .../lib/monitoring_repository.dart | 1 + .../lib/src/firebase_monitoring_repo.dart | 71 +++++++-------- .../lib/src/models/evaporasi.dart | 38 ++++---- .../lib/src/models/has_timestamp.dart | 5 ++ .../lib/src/models/models.dart | 5 +- .../lib/src/models/wind_speed.dart | 16 ++-- .../lib/src/monitoring_repo.dart | 15 ++-- .../lib/src/utils/timestamp_parser.dart | 46 ++++++++++ 13 files changed, 206 insertions(+), 214 deletions(-) delete mode 100644 lib/core/utils/timestamp_parser.dart create mode 100644 packages/monitoring_repository/lib/src/models/has_timestamp.dart create mode 100644 packages/monitoring_repository/lib/src/utils/timestamp_parser.dart diff --git a/lib/core/utils/time_series_mapper.dart b/lib/core/utils/time_series_mapper.dart index 3133012..f93a5f6 100644 --- a/lib/core/utils/time_series_mapper.dart +++ b/lib/core/utils/time_series_mapper.dart @@ -1,3 +1,5 @@ +import 'package:monitoring_repository/monitoring_repository.dart'; + class TimeSeriesMapper { /// ========================= /// 📅 DAILY (24 JAM) @@ -114,4 +116,30 @@ class TimeSeriesMapper { return result; } + + // ── Tambahkan ini setelah method smooth() ────────────────────── + + /// Shortcut untuk model yang implement HasTimestamp. + /// Dari: TimeSeriesMapper.toDaily(data: h, getTime: (e) => e.timestamp, getValue: (e) => e.speed) + /// Ke: TimeSeriesMapper.dailyFrom(h, (e) => e.speed) + static List dailyFrom( + List data, + double Function(T) getValue, + ) => + smooth( + toDaily(data: data, getTime: (e) => e.timestamp, getValue: getValue)); + + static List weeklyFrom( + List data, + double Function(T) getValue, + ) => + smooth(toWeekly( + data: data, getTime: (e) => e.timestamp, getValue: getValue)); + + static List monthlyFrom( + List data, + double Function(T) getValue, + ) => + smooth(toMonthly( + data: data, getTime: (e) => e.timestamp, getValue: getValue)); } diff --git a/lib/core/utils/timestamp_parser.dart b/lib/core/utils/timestamp_parser.dart deleted file mode 100644 index 8ef2ca1..0000000 --- a/lib/core/utils/timestamp_parser.dart +++ /dev/null @@ -1,6 +0,0 @@ -// Perlu handle semua kasus ini: -TimestampParser.parse(1777807041) // Unix detik → DateTime -TimestampParser.parse(1777807041000) // Unix milidetik → DateTime -TimestampParser.parse("14:26:01") // String jam saja → DateTime hari ini -TimestampParser.parse("2025-03-05T14:26:01") // ISO string → DateTime -TimestampParser.parse(null) // fallback → DateTime.now() \ No newline at end of file diff --git a/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart b/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart index 7758d67..94edc46 100644 --- a/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart +++ b/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart @@ -35,26 +35,29 @@ class EvaporasiBloc extends Bloc { emit(state.copyWith(isLoading: true)); final history = await _repository.getSensorHistory( - 'evaporasi/history', + 'Monitoring/History', (json) => Evaporasi.fromJson(json), + orderByChild: null, // waktu = string, tidak bisa orderByChild + limit: 500, ); - final dailyGraph = TimeSeriesMapper.toDaily( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.evaporasi, - ); - - final dailyTempGraph = TimeSeriesMapper.toDaily( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.suhu, - ); + final dailyGraph = + TimeSeriesMapper.dailyFrom(history, (e) => e.evaporasi); + final dailyTempGraph = + TimeSeriesMapper.dailyFrom(history, (e) => e.suhu); + final weeklyGraph = + TimeSeriesMapper.weeklyFrom(history, (e) => e.evaporasi); + final monthlyGraph = + TimeSeriesMapper.monthlyFrom(history, (e) => e.evaporasi); + final weeklyTemp = + TimeSeriesMapper.weeklyFrom(history, (e) => e.suhu); + final monthlyTemp = + TimeSeriesMapper.monthlyFrom(history, (e) => e.suhu); // Hitung status dari data terakhir history jika ada final lastValue = history.isNotEmpty ? history.last.evaporasi : 0.0; final (status, rain) = _computeWeatherStatus(lastValue); - _emitEvaporasiAlert(status, rain, lastValue); + _emitEvaporasiAlert(status, lastValue); emit(state.copyWith( history: history, @@ -63,6 +66,10 @@ class EvaporasiBloc extends Bloc { weatherStatus: status, willRain: rain, isLoading: false, + weeklyValues: weeklyGraph, + monthlyValues: monthlyGraph, + weeklyTemperatures: weeklyTemp, + monthlyTemperatures: monthlyTemp, )); await _subscription?.cancel(); @@ -88,7 +95,7 @@ class EvaporasiBloc extends Bloc { } final (status, rain) = _computeWeatherStatus(event.data.evaporasi); - _emitEvaporasiAlert(status, rain, event.data.evaporasi); + _emitEvaporasiAlert(status, event.data.evaporasi); emit(state.copyWith( currentValue: event.data.evaporasi, @@ -104,57 +111,11 @@ class EvaporasiBloc extends Bloc { /// ========================= /// 📊 PERIOD /// ========================= - Future _onPeriodChanged( + void _onPeriodChanged( EvaporasiPeriodChanged event, Emitter emit, - ) async { - emit(state.copyWith(isLoading: true, selectedPeriod: event.period)); - final history = state.history; - - List updated; - List updatedTemp; - - if (event.period == "Minggu Ini") { - updated = TimeSeriesMapper.toWeekly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.evaporasi, - ); - updatedTemp = TimeSeriesMapper.toWeekly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.suhu, - ); - } else if (event.period == "Bulan Ini") { - updated = TimeSeriesMapper.toMonthly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.evaporasi, - ); - updatedTemp = TimeSeriesMapper.toMonthly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.suhu, - ); - } else { - updated = TimeSeriesMapper.toDaily( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.evaporasi, - ); - updatedTemp = TimeSeriesMapper.toDaily( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.suhu, - ); - } - - // Pertahankan status cuaca saat ini (tidak berubah karena hanya ganti periode) - emit(state.copyWith( - dailyValues: updated, - dailyTemperatures: updatedTemp, - isLoading: false, - )); + ) { + emit(state.copyWith(selectedPeriod: event.period)); } @override @@ -172,7 +133,7 @@ class EvaporasiBloc extends Bloc { return ('Buruk', true); } - void _emitEvaporasiAlert(String status, bool willRain, double value) { + void _emitEvaporasiAlert(String status, double value) { final AlertSeverity severity; final String message; diff --git a/lib/screens/monitoring/evaporasi/blocs/evaporasi_state.dart b/lib/screens/monitoring/evaporasi/blocs/evaporasi_state.dart index 742ce96..2cac51b 100644 --- a/lib/screens/monitoring/evaporasi/blocs/evaporasi_state.dart +++ b/lib/screens/monitoring/evaporasi/blocs/evaporasi_state.dart @@ -7,12 +7,17 @@ class EvaporasiState extends Equatable { final String selectedPeriod; final List dailyValues; // untuk grafik evaporasi + final List weeklyValues; + final List monthlyValues; + final List dailyTemperatures; // untuk grafik suhu + final List weeklyTemperatures; + final List monthlyTemperatures; + final List history; - final String weatherStatus; // Baik / Sedang / Buruk - final bool willRain; // true jika status Sedang/Buruk - + final String weatherStatus; + final bool willRain; final bool isLoading; const EvaporasiState({ @@ -21,7 +26,11 @@ class EvaporasiState extends Equatable { this.waterLevel = 0.0, this.selectedPeriod = "Hari Ini", this.dailyValues = const [], + this.weeklyValues = const [], + this.monthlyValues = const [], this.dailyTemperatures = const [], + this.weeklyTemperatures = const [], + this.monthlyTemperatures = const [], this.history = const [], this.weatherStatus = "Baik", this.willRain = false, @@ -34,7 +43,11 @@ class EvaporasiState extends Equatable { double? waterLevel, String? selectedPeriod, List? dailyValues, + List? weeklyValues, + List? monthlyValues, List? dailyTemperatures, + List? weeklyTemperatures, + List? monthlyTemperatures, List? history, String? weatherStatus, bool? willRain, @@ -46,7 +59,11 @@ class EvaporasiState extends Equatable { waterLevel: waterLevel ?? this.waterLevel, selectedPeriod: selectedPeriod ?? this.selectedPeriod, dailyValues: dailyValues ?? this.dailyValues, + weeklyValues: weeklyValues ?? this.weeklyValues, + monthlyValues: monthlyValues ?? this.monthlyValues, dailyTemperatures: dailyTemperatures ?? this.dailyTemperatures, + weeklyTemperatures: weeklyTemperatures ?? this.weeklyTemperatures, + monthlyTemperatures: monthlyTemperatures ?? this.monthlyTemperatures, history: history ?? this.history, weatherStatus: weatherStatus ?? this.weatherStatus, willRain: willRain ?? this.willRain, @@ -61,7 +78,11 @@ class EvaporasiState extends Equatable { waterLevel, selectedPeriod, dailyValues, + weeklyValues, + monthlyValues, dailyTemperatures, + weeklyTemperatures, + monthlyTemperatures, history, weatherStatus, willRain, diff --git a/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart b/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart index 9a995a7..05f0616 100644 --- a/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart +++ b/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart @@ -48,32 +48,16 @@ class WindSpeedBloc extends Bloc { final history = await _repository.getSensorHistory( 'anemometer/history', (json) => MyWindSpeed.fromJson(json), + orderByChild: 'timestamp', + limit: 500, ); - // ✅ Ini saja yang benar - final dailyGraph = TimeSeriesMapper.smooth( - TimeSeriesMapper.toDaily( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.speed, - ), - ); - - final weekly = TimeSeriesMapper.smooth( - TimeSeriesMapper.toWeekly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.speed, - ), - ); - - final monthly = TimeSeriesMapper.smooth( - TimeSeriesMapper.toMonthly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.speed, - ), - ); + final dailyGraph = + TimeSeriesMapper.dailyFrom(history, (e) => e.speed); + final weekly = + TimeSeriesMapper.weeklyFrom(history, (e) => e.speed); + final monthly = + TimeSeriesMapper.monthlyFrom(history, (e) => e.speed); // Cek kondisi awal dari history if (history.isNotEmpty) { @@ -141,48 +125,11 @@ class WindSpeedBloc extends Bloc { /// ========================= /// 📊 CHANGE PERIOD /// ========================= - Future _onPeriodChanged( + void _onPeriodChanged( WindSpeedPeriodChanged event, Emitter emit, - ) async { + ) { emit(state.copyWith(selectedPeriod: event.period)); - - final history = state.history; - - List raw; - - if (event.period == "Minggu Ini") { - raw = TimeSeriesMapper.toWeekly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.speed, - ); - } else if (event.period == "Bulan Ini") { - raw = TimeSeriesMapper.toMonthly( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.speed, - ); - } else { - raw = TimeSeriesMapper.toDaily( - data: history, - getTime: (e) => e.timestamp, - getValue: (e) => e.speed, - ); - } - - /// 🔥 baru di-smooth - final updatedGraph = TimeSeriesMapper.smooth(raw); - - emit(state.copyWith( - dailySpeeds: - event.period == "Hari Ini" ? updatedGraph : state.dailySpeeds, - weeklySpeeds: - event.period == "Minggu Ini" ? updatedGraph : state.weeklySpeeds, - monthlySpeeds: - event.period == "Bulan Ini" ? updatedGraph : state.monthlySpeeds, - isLoading: false, - )); } @override diff --git a/packages/monitoring_repository/lib/monitoring_repository.dart b/packages/monitoring_repository/lib/monitoring_repository.dart index e5ff71e..b69e0b2 100644 --- a/packages/monitoring_repository/lib/monitoring_repository.dart +++ b/packages/monitoring_repository/lib/monitoring_repository.dart @@ -3,3 +3,4 @@ library monitoring_repository; export 'src/monitoring_repo.dart'; export 'src/firebase_monitoring_repo.dart'; export 'src/models/models.dart'; +export 'src/utils/timestamp_parser.dart'; diff --git a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart index f7cf78c..a8f54eb 100644 --- a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart @@ -1,72 +1,61 @@ -// import 'dart:developer'; import 'dart:async'; -// import 'package:rxdart/rxdart.dart'; import 'package:firebase_database/firebase_database.dart'; -import 'package:monitoring_repository/monitoring_repository.dart'; +import 'monitoring_repo.dart'; +import 'models/has_timestamp.dart'; +import 'models/models.dart'; -/// === ambil data dari realtime database firebase === /// class FirebaseMonitoringRepo implements MonitoringRepository { final FirebaseDatabase _db = FirebaseDatabase.instance; - // Satu fungsi untuk semua jenis sensor - // Kamu cukup masukkan "path" database-nya saja @override Stream getSensorStream( String path, T Function(Map json) mapper, ) { - /// === ambil data mentah dari firebase === /// return _db.ref(path).onValue.map((event) { - final Object? value = event.snapshot.value; - - if (value is Map) { - return mapper(value); - } else { - // Jika data kosong atau bukan Map, berikan Map kosong agar mapper tidak crash - return mapper({}); - } + final value = event.snapshot.value; + return mapper(value is Map ? value : {}); }); } @override - // Jika ingin mengambil data sekali saja (bukan stream) Future getSensorSnapshot( String path, T Function(Map json) mapper, ) async { final snapshot = await _db.ref(path).get(); - final data = snapshot.value as Map? ?? {}; - return mapper(data); + return mapper(snapshot.value as Map? ?? {}); } @override - Future> getSensorHistory( + Future> getSensorHistory( String path, - T Function(Map json) mapper, - ) async { + T Function(Map json) mapper, { + String? orderByChild, + int limit = 500, + }) async { try { - // Ambil data dari path history - final snapshot = await _db.ref(path).get(); + Query query = _db.ref(path); - if (snapshot.exists && snapshot.value is Map) { - final Map data = snapshot.value as Map; - - final list = data.values.map((item) { - return mapper(item as Map); - }).toList(); - - // ✅ Sort by timestamp — Firebase push tidak jamin urutan - list.sort((a, b) { - if (a is MyWindSpeed && b is MyWindSpeed) { - return a.timestamp.compareTo(b.timestamp); - } - return 0; - }); - - return list; + if (orderByChild != null) { + query = query.orderByChild(orderByChild).limitToLast(limit); + } else { + query = query.limitToLast(limit); } - return []; - } catch (e) { + + final snapshot = await query.get(); + if (!snapshot.exists || snapshot.value is! Map) return []; + + final data = snapshot.value as Map; + final list = data.values + .whereType() + .map((item) => mapper(item)) + .toList(); + + // Sort generik — works untuk SEMUA sensor karena T extends HasTimestamp + list.sort((a, b) => a.timestamp.compareTo(b.timestamp)); + return list; + } catch (_) { return []; } } diff --git a/packages/monitoring_repository/lib/src/models/evaporasi.dart b/packages/monitoring_repository/lib/src/models/evaporasi.dart index 907cbc5..caec82e 100644 --- a/packages/monitoring_repository/lib/src/models/evaporasi.dart +++ b/packages/monitoring_repository/lib/src/models/evaporasi.dart @@ -1,7 +1,13 @@ -class Evaporasi { +import 'has_timestamp.dart'; +import '../utils/timestamp_parser.dart'; + +class Evaporasi implements HasTimestamp { final double evaporasi; final double suhu; final double tinggiAir; + final String status; + + @override final DateTime timestamp; Evaporasi({ @@ -9,6 +15,7 @@ class Evaporasi { required this.suhu, required this.tinggiAir, required this.timestamp, + this.status = '', }); static final empty = Evaporasi( @@ -19,27 +26,22 @@ class Evaporasi { ); factory Evaporasi.fromJson(Map json) { - final now = DateTime.now(); - DateTime timestamp = now; + // ✅ Handle perbedaan field name antara history dan realtime: + // History path : suhu, tinggi, evaporasi, waktu + // Realtime path : suhu_air, tinggi_air, evaporasi, waktu, status + final suhu = (json['suhu'] ?? json['suhu_air'] ?? 0).toDouble(); + final tinggi = (json['tinggi'] ?? json['tinggi_air'] ?? 0).toDouble(); - final waktuStr = json['waktu'] as String?; - if (waktuStr != null) { - final parts = waktuStr.split(':'); - if (parts.length >= 2) { - final jam = int.tryParse(parts[0]) ?? 0; - final menit = int.tryParse(parts[1]) ?? 0; - final detik = parts.length >= 3 ? (int.tryParse(parts[2]) ?? 0) : 0; - timestamp = DateTime(now.year, now.month, now.day, jam, menit, detik); - } - } + // ✅ Prioritas: timestamp (Unix, kalau firmware sudah diupdate) + // → fallback ke waktu ("HH:MM:SS") + final rawTime = json['timestamp'] ?? json['waktu']; return Evaporasi( evaporasi: (json['evaporasi'] ?? 0).toDouble(), - suhu: (json['suhu_air'] ?? 0).toDouble(), - tinggiAir: (json['tinggi_air'] ?? 0).toDouble(), - - /// 🔥 bikin timestamp dari jam & menit - timestamp: timestamp, + suhu: suhu, + tinggiAir: tinggi, + status: json['status'] ?? '', + timestamp: TimestampParser.parse(rawTime), // ✅ pakai TimestampParser ); } } diff --git a/packages/monitoring_repository/lib/src/models/has_timestamp.dart b/packages/monitoring_repository/lib/src/models/has_timestamp.dart new file mode 100644 index 0000000..46a4e63 --- /dev/null +++ b/packages/monitoring_repository/lib/src/models/has_timestamp.dart @@ -0,0 +1,5 @@ +/// Semua model sensor wajib implement ini. +/// Memungkinkan sorting & TimeSeriesMapper yang generik. +abstract class HasTimestamp { + DateTime get timestamp; +} diff --git a/packages/monitoring_repository/lib/src/models/models.dart b/packages/monitoring_repository/lib/src/models/models.dart index bf7f507..c51ddde 100644 --- a/packages/monitoring_repository/lib/src/models/models.dart +++ b/packages/monitoring_repository/lib/src/models/models.dart @@ -1,7 +1,4 @@ export 'wind_speed.dart'; export 'evaporasi.dart'; export 'atmospheric_conditions.dart'; - -abstract class HasTimestamp { - DateTime get timestamp; -} +export 'has_timestamp.dart'; diff --git a/packages/monitoring_repository/lib/src/models/wind_speed.dart b/packages/monitoring_repository/lib/src/models/wind_speed.dart index b35d05a..90ab814 100644 --- a/packages/monitoring_repository/lib/src/models/wind_speed.dart +++ b/packages/monitoring_repository/lib/src/models/wind_speed.dart @@ -1,23 +1,23 @@ -class MyWindSpeed { - double speed; +import 'has_timestamp.dart'; +import '../utils/timestamp_parser.dart'; - DateTime timestamp; +class MyWindSpeed implements HasTimestamp { + final double speed; + + @override + final DateTime timestamp; MyWindSpeed({required this.speed, required this.timestamp}); static final empty = MyWindSpeed( speed: 0.0, - timestamp: DateTime.fromMillisecondsSinceEpoch(0), ); factory MyWindSpeed.fromJson(Map json) { return MyWindSpeed( speed: (json['speed'] ?? 0).toDouble(), - - timestamp: DateTime.fromMillisecondsSinceEpoch( - (json['timestamp'] ?? 0) * 1000, // kalau dari Unix detik - ).toLocal(), + timestamp: TimestampParser.parse(json['timestamp']), ); } } diff --git a/packages/monitoring_repository/lib/src/monitoring_repo.dart b/packages/monitoring_repository/lib/src/monitoring_repo.dart index 555796a..9d0afb5 100644 --- a/packages/monitoring_repository/lib/src/monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/monitoring_repo.dart @@ -1,21 +1,22 @@ -// import 'models/models.dart'; +/// file monitoring_repo.dart di "C:\App_project\klimatologi\packages\monitoring_repository\lib\src\monitoring_repo.dart" + +import 'models/has_timestamp.dart'; abstract class MonitoringRepository { - // fungsi untuk ambil data berkali/streaming Stream getSensorStream( String path, T Function(Map json) mapper, ); - // fungsi untuk ambil sensor sekali Future getSensorSnapshot( String path, T Function(Map json) mapper, ); - // Fungsi untuk ambil riwayat (List) - Future> getSensorHistory( + Future> getSensorHistory( String path, - T Function(Map json) mapper, - ); + T Function(Map json) mapper, { + String? orderByChild, + int limit = 500, + }); } diff --git a/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart b/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart new file mode 100644 index 0000000..dd8c978 --- /dev/null +++ b/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart @@ -0,0 +1,46 @@ +/// Mengubah berbagai format waktu Firebase → DateTime. +/// +/// Format yang didukung: +/// int Unix detik 1777807041 (anemometer) +/// int Unix milidetik 1777950497446 (sensor/atmospheric) +/// String "HH:MM:SS" "11:09:00" (Monitoring/evaporasi, intraday only) +/// String ISO 8601 "2025-03-05T14:26" (untuk masa depan) +class TimestampParser { + // Angka > ini dianggap milidetik (threshold: ~Nov 2001 dalam detik) + static const int _msThreshold = 10000000000; + + static DateTime parse(dynamic raw) { + if (raw == null) return DateTime.now(); + + if (raw is int) { + if (raw <= 0) return DateTime.now(); + if (raw > _msThreshold) { + return DateTime.fromMillisecondsSinceEpoch(raw).toLocal(); + } + return DateTime.fromMillisecondsSinceEpoch(raw * 1000).toLocal(); + } + + if (raw is double) return parse(raw.toInt()); + + if (raw is String) { + // "HH:MM:SS" → pakai tanggal hari ini (intraday only) + final m = RegExp(r'^(\d{1,2}):(\d{2}):(\d{2})$').firstMatch(raw); + if (m != null) { + final now = DateTime.now(); + return DateTime( + now.year, + now.month, + now.day, + int.parse(m.group(1)!), + int.parse(m.group(2)!), + int.parse(m.group(3)!), + ); + } + try { + return DateTime.parse(raw).toLocal(); + } catch (_) {} + } + + return DateTime.now(); + } +}