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_end_time.setOnClickListener { setupEndTime() }
|
||||||
event_time_zone.setOnClickListener { setupTimeZone() }
|
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.setOnClickListener { showRepeatIntervalDialog() }
|
||||||
event_repetition_rule_holder.setOnClickListener { showRepetitionRuleDialog() }
|
event_repetition_rule_holder.setOnClickListener { showRepetitionRuleDialog() }
|
||||||
event_repetition_limit_holder.setOnClickListener { showRepetitionTypePicker() }
|
event_repetition_limit_holder.setOnClickListener { showRepetitionTypePicker() }
|
||||||
|
@ -213,7 +213,7 @@ class EventActivity : SimpleActivity() {
|
||||||
|
|
||||||
event_type_holder.setOnClickListener { showEventTypeDialog() }
|
event_type_holder.setOnClickListener { showEventTypeDialog() }
|
||||||
event_all_day.apply {
|
event_all_day.apply {
|
||||||
isChecked = mEvent.flags and FLAG_ALL_DAY != 0
|
isChecked = mEvent.getIsAllDay()
|
||||||
jumpDrawablesToCurrentState()
|
jumpDrawablesToCurrentState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,13 +227,6 @@ class EventActivity : SimpleActivity() {
|
||||||
showOrHideTimeZone()
|
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() {
|
private fun refreshMenuItems() {
|
||||||
if (::mEvent.isInitialized) {
|
if (::mEvent.isInitialized) {
|
||||||
event_toolbar.menu.apply {
|
event_toolbar.menu.apply {
|
||||||
|
@ -258,17 +251,25 @@ class EventActivity : SimpleActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStartEndTimes(): Pair<Long, Long> {
|
private fun getStartEndTimes(): Pair<Long, Long> {
|
||||||
val offset = if (!config.allowChangingTimeZones || mEvent.getTimeZoneString().equals(mOriginalTimeZone, true)) {
|
if (mIsAllDayEvent) {
|
||||||
0
|
val newStartTS = mEventStartDateTime.withTimeAtStartOfDay().seconds()
|
||||||
|
val newEndTS = mEventEndDateTime.withTimeAtStartOfDay().withHourOfDay(12).seconds()
|
||||||
|
return Pair(newStartTS, newEndTS)
|
||||||
} else {
|
} else {
|
||||||
val original = if (mOriginalTimeZone.isEmpty()) DateTimeZone.getDefault().id else mOriginalTimeZone
|
val offset = if (!config.allowChangingTimeZones || mEvent.getTimeZoneString().equals(mOriginalTimeZone, true)) {
|
||||||
val millis = System.currentTimeMillis()
|
0
|
||||||
(DateTimeZone.forID(mEvent.getTimeZoneString()).getOffset(millis) - DateTimeZone.forID(original).getOffset(millis)) / 1000L
|
} 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 newStartTS = mEventStartDateTime.seconds() - offset
|
||||||
val newEndTS = mEventEndDateTime.seconds() - offset
|
val newEndTS = mEventEndDateTime.seconds() - offset
|
||||||
return Pair(newStartTS, newEndTS)
|
return Pair(newStartTS, newEndTS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getReminders(): ArrayList<Reminder> {
|
private fun getReminders(): ArrayList<Reminder> {
|
||||||
|
@ -309,6 +310,7 @@ class EventActivity : SimpleActivity() {
|
||||||
mRepeatRule != mEvent.repeatRule ||
|
mRepeatRule != mEvent.repeatRule ||
|
||||||
mEventTypeId != mEvent.eventType ||
|
mEventTypeId != mEvent.eventType ||
|
||||||
mWasCalendarChanged ||
|
mWasCalendarChanged ||
|
||||||
|
mIsAllDayEvent != mEvent.getIsAllDay() ||
|
||||||
hasTimeChanged
|
hasTimeChanged
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
|
@ -998,17 +1000,23 @@ class EventActivity : SimpleActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleAllDay(isChecked: Boolean) {
|
private fun toggleAllDay(isAllDay: Boolean) {
|
||||||
mIsAllDayEvent = isChecked
|
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
event_start_time.beGoneIf(isChecked)
|
mIsAllDayEvent = isAllDay
|
||||||
event_end_time.beGoneIf(isChecked)
|
event_start_time.beGoneIf(isAllDay)
|
||||||
mEvent.timeZone = if (isChecked) DateTimeZone.UTC.id else DateTimeZone.getDefault().id
|
event_end_time.beGoneIf(isAllDay)
|
||||||
updateTimeZoneText()
|
updateTimeZoneText()
|
||||||
showOrHideTimeZone()
|
showOrHideTimeZone()
|
||||||
resetTime()
|
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() {
|
private fun shareEvent() {
|
||||||
shareEvents(arrayListOf(mEvent.id!!))
|
shareEvents(arrayListOf(mEvent.id!!))
|
||||||
}
|
}
|
||||||
|
@ -1157,11 +1165,7 @@ class EventActivity : SimpleActivity() {
|
||||||
reminder3Type = mReminder3Type
|
reminder3Type = mReminder3Type
|
||||||
repeatInterval = mRepeatInterval
|
repeatInterval = mRepeatInterval
|
||||||
importId = newImportId
|
importId = newImportId
|
||||||
timeZone = when {
|
timeZone = if (mIsAllDayEvent || timeZone.isEmpty()) DateTimeZone.getDefault().id else timeZone
|
||||||
mIsAllDayEvent -> DateTimeZone.UTC.id
|
|
||||||
timeZone.isEmpty() -> DateTimeZone.getDefault().id
|
|
||||||
else -> timeZone
|
|
||||||
}
|
|
||||||
flags = mEvent.flags.addBitIf(event_all_day.isChecked, FLAG_ALL_DAY)
|
flags = mEvent.flags.addBitIf(event_all_day.isChecked, FLAG_ALL_DAY)
|
||||||
repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit
|
repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit
|
||||||
repeatRule = mRepeatRule
|
repeatRule = mRepeatRule
|
||||||
|
@ -1195,7 +1199,7 @@ class EventActivity : SimpleActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun storeEvent(wasRepeatable: Boolean) {
|
private fun storeEvent(wasRepeatable: Boolean) {
|
||||||
if (mEvent.id == null || mEvent.id == null) {
|
if (mEvent.id == null) {
|
||||||
eventsHelper.insertEvent(mEvent, addToCalDAV = true, showToasts = true) {
|
eventsHelper.insertEvent(mEvent, addToCalDAV = true, showToasts = true) {
|
||||||
hideKeyboard()
|
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 repeatRule = Parser().parseRepeatInterval(rrule, startTS)
|
||||||
val event = Event(
|
val event = Event(
|
||||||
null, startTS, endTS, title, location, description, reminder1?.minutes ?: REMINDER_OFF,
|
null, startTS, endTS, title, location, description, reminder1?.minutes ?: REMINDER_OFF,
|
||||||
reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF, reminder1?.type
|
reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF,
|
||||||
?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION, reminder3?.type
|
reminder1?.type ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION,
|
||||||
?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule,
|
reminder3?.type ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule,
|
||||||
repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId, source = source, availability = availability
|
repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId, source = source, availability = availability
|
||||||
)
|
)
|
||||||
|
|
||||||
if (event.getIsAllDay()) {
|
if (event.getIsAllDay()) {
|
||||||
event.startTS = Formatter.getShiftedImportTimestamp(event.startTS)
|
event.toLocalAllDayEvent()
|
||||||
event.endTS = Formatter.getShiftedImportTimestamp(event.endTS)
|
|
||||||
if (event.endTS > event.startTS) {
|
|
||||||
event.endTS -= DAY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedEventIds.add(importId)
|
fetchedEventIds.add(importId)
|
||||||
|
@ -402,9 +398,6 @@ class CalDAVHelper(val context: Context) {
|
||||||
put(Events.CALENDAR_ID, event.getCalDAVCalendarId())
|
put(Events.CALENDAR_ID, event.getCalDAVCalendarId())
|
||||||
put(Events.TITLE, event.title)
|
put(Events.TITLE, event.title)
|
||||||
put(Events.DESCRIPTION, event.description)
|
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.EVENT_LOCATION, event.location)
|
||||||
put(Events.STATUS, Events.STATUS_CONFIRMED)
|
put(Events.STATUS, Events.STATUS_CONFIRMED)
|
||||||
put(Events.AVAILABILITY, event.availability)
|
put(Events.AVAILABILITY, event.availability)
|
||||||
|
@ -416,9 +409,15 @@ class CalDAVHelper(val context: Context) {
|
||||||
put(Events.RRULE, repeatRule)
|
put(Events.RRULE, repeatRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getIsAllDay() && event.endTS >= event.startTS)
|
if (event.getIsAllDay()) {
|
||||||
event.endTS += DAY
|
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) {
|
if (event.repeatInterval > 0) {
|
||||||
put(Events.DURATION, getDurationCode(event))
|
put(Events.DURATION, getDurationCode(event))
|
||||||
putNull(Events.DTEND)
|
putNull(Events.DTEND)
|
||||||
|
|
|
@ -53,6 +53,7 @@ const val DEFAULT_START_TIME_CURRENT_TIME = -2
|
||||||
const val TYPE_EVENT = 0
|
const val TYPE_EVENT = 0
|
||||||
const val TYPE_TASK = 1
|
const val TYPE_TASK = 1
|
||||||
|
|
||||||
|
const val TWELVE_HOURS = 43200
|
||||||
const val DAY = 86400
|
const val DAY = 86400
|
||||||
const val WEEK = 604800
|
const val WEEK = 604800
|
||||||
const val MONTH = 2592001 // exact value not taken into account, Joda is used for adding months and years
|
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 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 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.R
|
||||||
import com.simplemobiletools.calendar.pro.extensions.calDAVHelper
|
import com.simplemobiletools.calendar.pro.extensions.calDAVHelper
|
||||||
import com.simplemobiletools.calendar.pro.extensions.eventTypesDB
|
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.CalDAVCalendar
|
||||||
import com.simplemobiletools.calendar.pro.models.Event
|
import com.simplemobiletools.calendar.pro.models.Event
|
||||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||||
|
@ -61,7 +63,7 @@ class IcsExporter {
|
||||||
|
|
||||||
if (event.getIsAllDay()) {
|
if (event.getIsAllDay()) {
|
||||||
out.writeLn("$DTSTART;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.startTS)}")
|
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 {
|
} else {
|
||||||
event.startTS.let { out.writeLn("$DTSTART:${Formatter.getExportedTime(it * 1000L)}") }
|
event.startTS.let { out.writeLn("$DTSTART:${Formatter.getExportedTime(it * 1000L)}") }
|
||||||
event.endTS.let { out.writeLn("$DTEND:${Formatter.getExportedTime(it * 1000L)}") }
|
event.endTS.let { out.writeLn("$DTEND:${Formatter.getExportedTime(it * 1000L)}") }
|
||||||
|
|
|
@ -232,7 +232,7 @@ class IcsImporter(val activity: SimpleActivity) {
|
||||||
curRepeatExceptions,
|
curRepeatExceptions,
|
||||||
"",
|
"",
|
||||||
curImportId,
|
curImportId,
|
||||||
if (isAllDay) DateTimeZone.UTC.id else DateTimeZone.getDefault().id,
|
DateTimeZone.getDefault().id,
|
||||||
curFlags,
|
curFlags,
|
||||||
curEventTypeId,
|
curEventTypeId,
|
||||||
0,
|
0,
|
||||||
|
@ -242,8 +242,7 @@ class IcsImporter(val activity: SimpleActivity) {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isAllDay && curEnd > curStart) {
|
if (isAllDay && curEnd > curStart) {
|
||||||
event.endTS -= DAY
|
event.endTS -= TWELVE_HOURS
|
||||||
|
|
||||||
// fix some glitches related to daylight saving shifts
|
// fix some glitches related to daylight saving shifts
|
||||||
if (event.startTS - event.endTS == HOUR_SECONDS.toLong()) {
|
if (event.startTS - event.endTS == HOUR_SECONDS.toLong()) {
|
||||||
event.endTS += HOUR_SECONDS
|
event.endTS += HOUR_SECONDS
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.simplemobiletools.commons.extensions.areDigitsOnly
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
import org.joda.time.DateTimeZone
|
import org.joda.time.DateTimeZone
|
||||||
import org.joda.time.format.DateTimeFormat
|
import org.joda.time.format.DateTimeFormat
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
// from RRULE:FREQ=DAILY;COUNT=5 to Daily, 5x...
|
// from RRULE:FREQ=DAILY;COUNT=5 to Daily, 5x...
|
||||||
|
@ -101,8 +102,9 @@ class Parser {
|
||||||
return if (edited.length == 14) {
|
return if (edited.length == 14) {
|
||||||
parseLongFormat(edited, value.endsWith("Z"))
|
parseLongFormat(edited, value.endsWith("Z"))
|
||||||
} else {
|
} else {
|
||||||
val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMdd")
|
val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC()
|
||||||
dateTimeFormat.parseDateTime(edited).withHourOfDay(13).seconds()
|
val dateTime = dateTimeFormat.parseDateTime(edited)
|
||||||
|
Formatter.getShiftedTS(dateTime = dateTime, toZone = DateTimeZone.getDefault())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,12 +229,12 @@ class Parser {
|
||||||
var hours = 0
|
var hours = 0
|
||||||
var remainder = minutes
|
var remainder = minutes
|
||||||
if (remainder >= DAY_MINUTES) {
|
if (remainder >= DAY_MINUTES) {
|
||||||
days = Math.floor((remainder / DAY_MINUTES).toDouble()).toInt()
|
days = floor((remainder / DAY_MINUTES).toDouble()).toInt()
|
||||||
remainder -= days * DAY_MINUTES
|
remainder -= days * DAY_MINUTES
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainder >= 60) {
|
if (remainder >= 60) {
|
||||||
hours = Math.floor((remainder / 60).toDouble()).toInt()
|
hours = floor((remainder / 60).toDouble()).toInt()
|
||||||
remainder -= hours * 60
|
remainder -= hours * 60
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue