diff --git a/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 0be60d73..817c20d6 100644 --- a/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -468,25 +468,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val values = ContentValues() val duration: String? = null values.put(Events.ALL_DAY, event.eventAllDay) - - if (event.eventAllDay) { - val calendar = java.util.Calendar.getInstance() - calendar.timeInMillis = event.eventStartDate!! - calendar.set(java.util.Calendar.HOUR, 0) - calendar.set(java.util.Calendar.MINUTE, 0) - calendar.set(java.util.Calendar.SECOND, 0) - calendar.set(java.util.Calendar.MILLISECOND, 0) - - values.put(Events.DTSTART, calendar.timeInMillis) - values.put(Events.DTEND, calendar.timeInMillis) - values.put(Events.EVENT_TIMEZONE, getTimeZone(event.eventStartTimeZone).id) - } else { - values.put(Events.DTSTART, event.eventStartDate!!) - values.put(Events.EVENT_TIMEZONE, getTimeZone(event.eventStartTimeZone).id) - - values.put(Events.DTEND, event.eventEndDate!!) - values.put(Events.EVENT_END_TIMEZONE, getTimeZone(event.eventEndTimeZone).id) - } + values.put(Events.DTSTART, event.eventStartDate!!) + values.put(Events.EVENT_TIMEZONE, getTimeZone(event.eventStartTimeZone).id) + values.put(Events.DTEND, event.eventEndDate!!) + values.put(Events.EVENT_END_TIMEZONE, getTimeZone(event.eventEndTimeZone).id) values.put(Events.TITLE, event.eventTitle) values.put(Events.DESCRIPTION, event.eventDescription) values.put(Events.EVENT_LOCATION, event.eventLocation) diff --git a/example/lib/presentation/event_item.dart b/example/lib/presentation/event_item.dart index be7fce02..4e54b6f7 100644 --- a/example/lib/presentation/event_item.dart +++ b/example/lib/presentation/event_item.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -76,10 +77,9 @@ class _EventItemState extends State { Text( widget._calendarEvent == null ? '' - : DateFormat('yyyy-MM-dd HH:mm:ss').format( - TZDateTime.from( - widget._calendarEvent!.start!, - _currentLocation!)), + : _formatDateTime( + dateTime: widget._calendarEvent!.start!, + ), ) ], ), @@ -99,9 +99,9 @@ class _EventItemState extends State { Text( widget._calendarEvent?.end == null ? '' - : DateFormat('yyyy-MM-dd HH:mm:ss').format( - TZDateTime.from(widget._calendarEvent!.end!, - _currentLocation!)), + : _formatDateTime( + dateTime: widget._calendarEvent!.end!, + ), ), ], ), @@ -301,4 +301,22 @@ class _EventItemState extends State { _currentLocation = timeZoneDatabase.locations[timezone]; setState(() {}); } + + /// Formats [dateTime] into a human-readable string. + /// If [_calendarEvent] is an Android allDay event, then the output will + /// omit the time. + String _formatDateTime({DateTime? dateTime}) { + if (dateTime == null) { + return 'Error'; + } + var output = ''; + if (Platform.isAndroid && widget._calendarEvent?.allDay == true) { + // just the dates, no times + output = DateFormat.yMd().format(dateTime); + } else { + output = DateFormat('yyyy-MM-dd HH:mm:ss') + .format(TZDateTime.from(dateTime, _currentLocation!)); + } + return output; + } } diff --git a/example/lib/presentation/pages/calendar_event.dart b/example/lib/presentation/pages/calendar_event.dart index 3461103c..21a4ca06 100644 --- a/example/lib/presentation/pages/calendar_event.dart +++ b/example/lib/presentation/pages/calendar_event.dart @@ -299,26 +299,29 @@ class _CalendarEventPageState extends State { }, ), ), - if (_event?.allDay == false) ...[ - if (Platform.isAndroid) - Padding( - padding: const EdgeInsets.all(10.0), - child: TextFormField( - initialValue: _event?.start?.location.name, - decoration: const InputDecoration( - labelText: 'Start date time zone', - hintText: 'Australia/Sydney'), - onSaved: (String? value) { - _event?.updateStartLocation(value); - }, - ), + if ((_event?.allDay == false) && Platform.isAndroid) + Padding( + padding: const EdgeInsets.all(10.0), + child: TextFormField( + initialValue: _event?.start?.location.name, + decoration: const InputDecoration( + labelText: 'Start date time zone', + hintText: 'Australia/Sydney'), + onSaved: (String? value) { + _event?.updateStartLocation(value); + }, ), + ), + // Only add the 'To' Date for non-allDay events on all + // platforms except Android (which allows multiple-day allDay events) + if (_event?.allDay == false || Platform.isAndroid) Padding( padding: const EdgeInsets.all(10.0), child: DateTimePicker( labelText: 'To', selectedDate: _endDate, selectedTime: _endTime, + enableTime: _event?.allDay == false, selectDate: (DateTime date) { setState( () { @@ -344,6 +347,7 @@ class _CalendarEventPageState extends State { }, ), ), + if (_event?.allDay == false && Platform.isAndroid) Padding( padding: const EdgeInsets.all(10.0), child: TextFormField( @@ -355,7 +359,6 @@ class _CalendarEventPageState extends State { _event?.updateEndLocation(value), ), ), - ], GestureDetector( onTap: () async { var result = await Navigator.push( @@ -1007,6 +1010,7 @@ class _CalendarEventPageState extends State { currentLocation!); if (time == null) return dateWithoutTime; + if (Platform.isAndroid && _event?.allDay == true) return dateWithoutTime; return dateWithoutTime .add(Duration(hours: time.hour, minutes: time.minute)); diff --git a/lib/src/device_calendar.dart b/lib/src/device_calendar.dart index 369f6434..77ec3450 100644 --- a/lib/src/device_calendar.dart +++ b/lib/src/device_calendar.dart @@ -1,5 +1,6 @@ import 'dart:collection'; import 'dart:convert'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -211,14 +212,25 @@ class DeviceCalendarPlugin { if (event.start != null) { var dateStart = DateTime(event.start!.year, event.start!.month, event.start!.day, 0, 0, 0); - event.start = TZDateTime.from(dateStart, + // allDay events on Android need to be at midnight UTC + event.start = Platform.isAndroid + ? TZDateTime.utc(event.start!.year, event.start!.month, + event.start!.day, 0, 0, 0) + : TZDateTime.from(dateStart, timeZoneDatabase.locations[event.start!.location.name]!); } if (event.end != null) { var dateEnd = DateTime( event.end!.year, event.end!.month, event.end!.day, 0, 0, 0); - event.end = TZDateTime.from( - dateEnd, timeZoneDatabase.locations[event.end!.location.name]!); + // allDay events on Android need to be at midnight UTC on the + // day after the last day. For example, a 2-day allDay event on + // Jan 1 and 2, should be from Jan 1 00:00:00 to Jan 3 00:00:00 + event.end = Platform.isAndroid + ? TZDateTime.utc(event.end!.year, event.end!.month, + event.end!.day, 0, 0, 0) + .add(Duration(days: 1)) + : TZDateTime.from(dateEnd, + timeZoneDatabase.locations[event.end!.location.name]!); } } diff --git a/lib/src/models/event.dart b/lib/src/models/event.dart index 9f67f8b9..adb19d03 100644 --- a/lib/src/models/event.dart +++ b/lib/src/models/event.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import '../../device_calendar.dart'; import '../common/error_messages.dart'; import 'package:timezone/timezone.dart'; @@ -79,12 +81,24 @@ class Event { final int? endTimestamp = json['eventEndDate']; final String? endLocationName = json['eventEndTimeZone']; var endLocation = timeZoneDatabase.locations[endLocationName]; - endLocation ??= local; + endLocation ??= startTimeZone; end = endTimestamp != null ? TZDateTime.fromMillisecondsSinceEpoch(endLocation, endTimestamp) : TZDateTime.now(local); - allDay = json['eventAllDay'] ?? false; + if (Platform.isAndroid && (allDay ?? false)){ + // On Android, the datetime in an allDay event is adjusted to local + // timezone, which can result in the wrong day, so we need to bring the + // date back to midnight UTC to get the correct date + var startOffset = start?.timeZoneOffset.inMilliseconds ?? 0; + var endOffset = end?.timeZoneOffset.inMilliseconds ?? 0; + // subtract the offset to get back to midnight on the correct date + start = start?.subtract(Duration(milliseconds: startOffset)); + end = end?.subtract(Duration(milliseconds: endOffset)); + // The Event End Date for allDay events is midnight of the next day, so + // subtract one day + end = end?.subtract(Duration(days: 1)); + } location = json['eventLocation']; availability = parseStringToAvailability(json['availability']);