Properly handle all-day event timezone switch
This commit is contained in:
parent
d485be7cf4
commit
61ed3a2b90
|
@ -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,18 +251,26 @@ class EventActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun getStartEndTimes(): Pair<Long, Long> {
|
||||
if (mIsAllDayEvent) {
|
||||
val newStartTS = mEventStartDateTime.withTimeAtStartOfDay().seconds()
|
||||
val newEndTS = mEventEndDateTime.withTimeAtStartOfDay().withHourOfDay(12).seconds()
|
||||
return Pair(newStartTS, newEndTS)
|
||||
} else {
|
||||
val offset = if (!config.allowChangingTimeZones || mEvent.getTimeZoneString().equals(mOriginalTimeZone, true)) {
|
||||
0
|
||||
} else {
|
||||
val original = if (mOriginalTimeZone.isEmpty()) DateTimeZone.getDefault().id else mOriginalTimeZone
|
||||
val original = mOriginalTimeZone.ifEmpty { DateTimeZone.getDefault().id }
|
||||
val millis = System.currentTimeMillis()
|
||||
(DateTimeZone.forID(mEvent.getTimeZoneString()).getOffset(millis) - DateTimeZone.forID(original).getOffset(millis)) / 1000L
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getReminders(): ArrayList<Reminder> {
|
||||
var reminders = arrayListOf(
|
||||
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)}") }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue