diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt index e9d11fa60..8ec102fee 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt @@ -161,7 +161,7 @@ class EventActivity : SimpleActivity() { event_end_time.setOnClickListener { setupEndTime() } event_time_zone.setOnClickListener { setupTimeZone() } - event_all_day.setOnCheckedChangeListener { compoundButton, isChecked -> toggleAllDay(isChecked) } + event_all_day.setOnCheckedChangeListener { _, isChecked -> toggleAllDay(isChecked) } event_repetition.setOnClickListener { showRepeatIntervalDialog() } event_repetition_rule_holder.setOnClickListener { showRepetitionRuleDialog() } event_repetition_limit_holder.setOnClickListener { showRepetitionTypePicker() } @@ -213,7 +213,7 @@ class EventActivity : SimpleActivity() { event_type_holder.setOnClickListener { showEventTypeDialog() } event_all_day.apply { - isChecked = mEvent.flags and FLAG_ALL_DAY != 0 + isChecked = mEvent.getIsAllDay() jumpDrawablesToCurrentState() } @@ -227,13 +227,6 @@ class EventActivity : SimpleActivity() { showOrHideTimeZone() } - private fun showOrHideTimeZone() { - val allowChangingTimeZones = config.allowChangingTimeZones && !event_all_day.isChecked - event_time_zone_divider.beVisibleIf(allowChangingTimeZones) - event_time_zone_image.beVisibleIf(allowChangingTimeZones) - event_time_zone.beVisibleIf(allowChangingTimeZones) - } - private fun refreshMenuItems() { if (::mEvent.isInitialized) { event_toolbar.menu.apply { @@ -258,17 +251,25 @@ class EventActivity : SimpleActivity() { } private fun getStartEndTimes(): Pair { - val offset = if (!config.allowChangingTimeZones || mEvent.getTimeZoneString().equals(mOriginalTimeZone, true)) { - 0 + if (mIsAllDayEvent) { + val newStartTS = mEventStartDateTime.withTimeAtStartOfDay().seconds() + val newEndTS = mEventEndDateTime.withTimeAtStartOfDay().withHourOfDay(12).seconds() + return Pair(newStartTS, newEndTS) } else { - val original = if (mOriginalTimeZone.isEmpty()) DateTimeZone.getDefault().id else mOriginalTimeZone - val millis = System.currentTimeMillis() - (DateTimeZone.forID(mEvent.getTimeZoneString()).getOffset(millis) - DateTimeZone.forID(original).getOffset(millis)) / 1000L - } + val offset = if (!config.allowChangingTimeZones || mEvent.getTimeZoneString().equals(mOriginalTimeZone, true)) { + 0 + } else { + val original = mOriginalTimeZone.ifEmpty { DateTimeZone.getDefault().id } + val millis = System.currentTimeMillis() + val newOffset = DateTimeZone.forID(mEvent.getTimeZoneString()).getOffset(millis) + val oldOffset = DateTimeZone.forID(original).getOffset(millis) + (newOffset - oldOffset) / 1000L + } - val newStartTS = mEventStartDateTime.seconds() - offset - val newEndTS = mEventEndDateTime.seconds() - offset - return Pair(newStartTS, newEndTS) + val newStartTS = mEventStartDateTime.seconds() - offset + val newEndTS = mEventEndDateTime.seconds() - offset + return Pair(newStartTS, newEndTS) + } } private fun getReminders(): ArrayList { @@ -309,6 +310,7 @@ class EventActivity : SimpleActivity() { mRepeatRule != mEvent.repeatRule || mEventTypeId != mEvent.eventType || mWasCalendarChanged || + mIsAllDayEvent != mEvent.getIsAllDay() || hasTimeChanged ) { return true @@ -998,17 +1000,23 @@ class EventActivity : SimpleActivity() { } } - private fun toggleAllDay(isChecked: Boolean) { - mIsAllDayEvent = isChecked + private fun toggleAllDay(isAllDay: Boolean) { hideKeyboard() - event_start_time.beGoneIf(isChecked) - event_end_time.beGoneIf(isChecked) - mEvent.timeZone = if (isChecked) DateTimeZone.UTC.id else DateTimeZone.getDefault().id + mIsAllDayEvent = isAllDay + event_start_time.beGoneIf(isAllDay) + event_end_time.beGoneIf(isAllDay) updateTimeZoneText() showOrHideTimeZone() resetTime() } + private fun showOrHideTimeZone() { + val allowChangingTimeZones = config.allowChangingTimeZones && !mIsAllDayEvent + event_time_zone_divider.beVisibleIf(allowChangingTimeZones) + event_time_zone_image.beVisibleIf(allowChangingTimeZones) + event_time_zone.beVisibleIf(allowChangingTimeZones) + } + private fun shareEvent() { shareEvents(arrayListOf(mEvent.id!!)) } @@ -1157,11 +1165,7 @@ class EventActivity : SimpleActivity() { reminder3Type = mReminder3Type repeatInterval = mRepeatInterval importId = newImportId - timeZone = when { - mIsAllDayEvent -> DateTimeZone.UTC.id - timeZone.isEmpty() -> DateTimeZone.getDefault().id - else -> timeZone - } + timeZone = if (mIsAllDayEvent || timeZone.isEmpty()) DateTimeZone.getDefault().id else timeZone flags = mEvent.flags.addBitIf(event_all_day.isChecked, FLAG_ALL_DAY) repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit repeatRule = mRepeatRule @@ -1195,7 +1199,7 @@ class EventActivity : SimpleActivity() { } private fun storeEvent(wasRepeatable: Boolean) { - if (mEvent.id == null || mEvent.id == null) { + if (mEvent.id == null) { eventsHelper.insertEvent(mEvent, addToCalDAV = true, showToasts = true) { hideKeyboard() diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Event.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Event.kt new file mode 100644 index 000000000..d69789877 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Event.kt @@ -0,0 +1,30 @@ +package com.simplemobiletools.calendar.pro.extensions + +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.helpers.TWELVE_HOURS +import com.simplemobiletools.calendar.pro.models.Event +import org.joda.time.DateTimeZone + +/** Shifts all-day events to local timezone such that the event starts and ends on the same time as in UTC */ +fun Event.toLocalAllDayEvent() { + require(this.getIsAllDay()) { "Must be an all day event!" } + + timeZone = DateTimeZone.getDefault().id + startTS = Formatter.getShiftedLocalTS(startTS) + endTS = Formatter.getShiftedLocalTS(endTS) + if (endTS > startTS) { + endTS -= TWELVE_HOURS + } +} + +/** Shifts all-day events to UTC such that the event starts on the same time in UTC too */ +fun Event.toUtcAllDayEvent() { + require(getIsAllDay()) { "Must be an all day event!" } + + if (endTS >= startTS) { + endTS += TWELVE_HOURS + } + timeZone = DateTimeZone.UTC.id + startTS = Formatter.getShiftedUtcTS(startTS) + endTS = Formatter.getShiftedUtcTS(endTS) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt index d597c4b33..adc41c6af 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt @@ -227,18 +227,14 @@ class CalDAVHelper(val context: Context) { val repeatRule = Parser().parseRepeatInterval(rrule, startTS) val event = Event( null, startTS, endTS, title, location, description, reminder1?.minutes ?: REMINDER_OFF, - reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF, reminder1?.type - ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION, reminder3?.type - ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule, + reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF, + reminder1?.type ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION, + reminder3?.type ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule, repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId, source = source, availability = availability ) if (event.getIsAllDay()) { - event.startTS = Formatter.getShiftedImportTimestamp(event.startTS) - event.endTS = Formatter.getShiftedImportTimestamp(event.endTS) - if (event.endTS > event.startTS) { - event.endTS -= DAY - } + event.toLocalAllDayEvent() } fetchedEventIds.add(importId) @@ -402,9 +398,6 @@ class CalDAVHelper(val context: Context) { put(Events.CALENDAR_ID, event.getCalDAVCalendarId()) put(Events.TITLE, event.title) put(Events.DESCRIPTION, event.description) - put(Events.DTSTART, event.startTS * 1000L) - put(Events.ALL_DAY, if (event.getIsAllDay()) 1 else 0) - put(Events.EVENT_TIMEZONE, event.getTimeZoneString()) put(Events.EVENT_LOCATION, event.location) put(Events.STATUS, Events.STATUS_CONFIRMED) put(Events.AVAILABILITY, event.availability) @@ -416,9 +409,15 @@ class CalDAVHelper(val context: Context) { put(Events.RRULE, repeatRule) } - if (event.getIsAllDay() && event.endTS >= event.startTS) - event.endTS += DAY + if (event.getIsAllDay()) { + event.toUtcAllDayEvent() + put(Events.ALL_DAY, 1) + } else { + put(Events.ALL_DAY, 0) + } + put(Events.DTSTART, event.startTS * 1000L) + put(Events.EVENT_TIMEZONE, event.getTimeZoneString()) if (event.repeatInterval > 0) { put(Events.DURATION, getDurationCode(event)) putNull(Events.DTEND) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index fe9f34b46..6cf2df21a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -53,6 +53,7 @@ const val DEFAULT_START_TIME_CURRENT_TIME = -2 const val TYPE_EVENT = 0 const val TYPE_TASK = 1 +const val TWELVE_HOURS = 43200 const val DAY = 86400 const val WEEK = 604800 const val MONTH = 2592001 // exact value not taken into account, Joda is used for adding months and years diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt index fc59197cd..50e12f4a3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt @@ -135,7 +135,11 @@ object Formatter { fun getUTCDayCodeFromTS(ts: Long) = getUTCDateTimeFromTS(ts).toString(DAYCODE_PATTERN) - fun getShiftedImportTimestamp(ts: Long) = getUTCDateTimeFromTS(ts).withTime(13, 0, 0, 0).withZoneRetainFields(DateTimeZone.getDefault()).seconds() - fun getYearFromDayCode(dayCode: String) = getDateTimeFromCode(dayCode).toString(YEAR_PATTERN) + + fun getShiftedTS(dateTime: DateTime, toZone: DateTimeZone) = dateTime.withTimeAtStartOfDay().withZoneRetainFields(toZone).seconds() + + fun getShiftedLocalTS(ts: Long) = getShiftedTS(dateTime = getUTCDateTimeFromTS(ts), toZone = DateTimeZone.getDefault()) + + fun getShiftedUtcTS(ts: Long) = getShiftedTS(dateTime = getDateTimeFromTS(ts), toZone = DateTimeZone.UTC) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt index 6ba5f4a78..b182073c9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt @@ -4,7 +4,9 @@ import android.provider.CalendarContract.Events import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.extensions.calDAVHelper import com.simplemobiletools.calendar.pro.extensions.eventTypesDB -import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.* +import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPORT_FAIL +import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPORT_OK +import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPORT_PARTIAL import com.simplemobiletools.calendar.pro.models.CalDAVCalendar import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.commons.activities.BaseSimpleActivity @@ -61,7 +63,7 @@ class IcsExporter { if (event.getIsAllDay()) { out.writeLn("$DTSTART;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.startTS)}") - out.writeLn("$DTEND;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.endTS + DAY)}") + out.writeLn("$DTEND;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.endTS + TWELVE_HOURS)}") } else { event.startTS.let { out.writeLn("$DTSTART:${Formatter.getExportedTime(it * 1000L)}") } event.endTS.let { out.writeLn("$DTEND:${Formatter.getExportedTime(it * 1000L)}") } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt index 5665978d2..7921d5501 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt @@ -232,7 +232,7 @@ class IcsImporter(val activity: SimpleActivity) { curRepeatExceptions, "", curImportId, - if (isAllDay) DateTimeZone.UTC.id else DateTimeZone.getDefault().id, + DateTimeZone.getDefault().id, curFlags, curEventTypeId, 0, @@ -242,8 +242,7 @@ class IcsImporter(val activity: SimpleActivity) { ) if (isAllDay && curEnd > curStart) { - event.endTS -= DAY - + event.endTS -= TWELVE_HOURS // fix some glitches related to daylight saving shifts if (event.startTS - event.endTS == HOUR_SECONDS.toLong()) { event.endTS += HOUR_SECONDS diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt index 1b163f828..b0b317e39 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt @@ -10,6 +10,7 @@ import com.simplemobiletools.commons.extensions.areDigitsOnly import com.simplemobiletools.commons.helpers.* import org.joda.time.DateTimeZone import org.joda.time.format.DateTimeFormat +import kotlin.math.floor class Parser { // from RRULE:FREQ=DAILY;COUNT=5 to Daily, 5x... @@ -101,8 +102,9 @@ class Parser { return if (edited.length == 14) { parseLongFormat(edited, value.endsWith("Z")) } else { - val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMdd") - dateTimeFormat.parseDateTime(edited).withHourOfDay(13).seconds() + val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC() + val dateTime = dateTimeFormat.parseDateTime(edited) + Formatter.getShiftedTS(dateTime = dateTime, toZone = DateTimeZone.getDefault()) } } @@ -227,12 +229,12 @@ class Parser { var hours = 0 var remainder = minutes if (remainder >= DAY_MINUTES) { - days = Math.floor((remainder / DAY_MINUTES).toDouble()).toInt() + days = floor((remainder / DAY_MINUTES).toDouble()).toInt() remainder -= days * DAY_MINUTES } if (remainder >= 60) { - hours = Math.floor((remainder / 60).toDouble()).toInt() + hours = floor((remainder / 60).toDouble()).toInt() remainder -= hours * 60 }