getCalendar method

  1. @override
Future<List<CalendarEventDto>> getCalendar(
  1. DateTime startDate,
  2. DateTime endDate
)
override

Fetches academic calendar events within a date range.

Returns a list of calendar events (e.g., holidays, exam periods, registration deadlines) between startDate and endDate inclusive.

Requires an active portal session (call login first).

Implementation

@override
Future<List<CalendarEventDto>> getCalendar(
  DateTime startDate,
  DateTime endDate,
) async {
  final formatter = DateFormat('yyyy/MM/dd');
  final response = await _portalDio.get(
    'calModeApp.do',
    queryParameters: {
      'startDate': formatter.format(startDate),
      'endDate': formatter.format(endDate),
    },
  );

  final List<dynamic> events = jsonDecode(response.data);

  String? normalizeEmpty(String? value) =>
      value?.isNotEmpty == true ? value : null;

  // The portal returns proper Unix epoch ms (e.g. 1753977600000 ↔
  // 2025-08-01 00:00 +08:00). Naively decoding via the device's local
  // timezone would shift the displayed date when the user is outside
  // Taipei — Aug 1 would render as Jul 31 in London. We instead build
  // a UTC DateTime, add 8h, and copy its wall-clock fields into a local
  // DateTime so the result reads as Taipei time regardless of the
  // device's offset (and DST).
  DateTime taipeiWallClock(int ms) {
    final taipei = DateTime.fromMillisecondsSinceEpoch(
      ms,
      isUtc: true,
    ).add(const Duration(hours: 8));
    return DateTime(
      taipei.year,
      taipei.month,
      taipei.day,
      taipei.hour,
      taipei.minute,
      taipei.second,
      taipei.millisecond,
    );
  }

  return events
      .where(
        // Filter out weekend markers
        (e) => e['isHoliday'] != '1',
      )
      .map<CalendarEventDto>(
        (e) => (
          id: e['id'],
          start: taipeiWallClock(e['calStart']),
          end: taipeiWallClock(e['calEnd']),
          allDay: e['allDay'] == '1',
          title: normalizeEmpty(e['calTitle']),
          place: normalizeEmpty(e['calPlace']),
          content: normalizeEmpty(e['calContent']),
          ownerName: normalizeEmpty(e['ownerName']),
          creatorName: normalizeEmpty(e['creatorName']),
        ),
      )
      .toList();
}