-
-
Notifications
You must be signed in to change notification settings - Fork 196
Expand file tree
/
Copy pathParser.kt
More file actions
254 lines (226 loc) · 9.78 KB
/
Copy pathParser.kt
File metadata and controls
254 lines (226 loc) · 9.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package org.fossify.calendar.helpers
import org.fossify.calendar.extensions.isXMonthlyRepetition
import org.fossify.calendar.extensions.isXWeeklyRepetition
import org.fossify.calendar.extensions.isXYearlyRepetition
import org.fossify.calendar.extensions.seconds
import org.fossify.calendar.models.Event
import org.fossify.calendar.models.EventRepetition
import org.fossify.commons.extensions.areDigitsOnly
import org.fossify.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...
fun parseRepeatInterval(fullString: String, startTS: Long): EventRepetition {
val parts = fullString.split(";").filter { it.isNotEmpty() }
var repeatInterval = 0
var repeatRule = 0
var repeatLimit = 0L
for (part in parts) {
val keyValue = part.split("=")
if (keyValue.size <= 1) {
continue
}
val key = keyValue[0]
val value = keyValue[1]
if (key == FREQ) {
repeatInterval = getFrequencySeconds(value)
if (value == WEEKLY) {
val start = Formatter.getDateTimeFromTS(startTS)
repeatRule = 1 shl (start.dayOfWeek - 1)
} else if (value == MONTHLY || value == YEARLY) {
repeatRule = REPEAT_SAME_DAY
} else if (value == DAILY && (fullString.contains(INTERVAL) || fullString.contains("BYDAY"))) {
val interval = fullString.substringAfter("$INTERVAL=").substringBefore(";")
// properly handle events repeating by 14 days or so, just add a repeat rule to specify a day of the week
if (fullString.contains(INTERVAL) && interval.areDigitsOnly() && interval.toInt() % 7 == 0) {
val dateTime = Formatter.getDateTimeFromTS(startTS)
repeatRule = 1 shl (dateTime.dayOfWeek - 1)
} else if (fullString.contains("BYDAY")) {
// some services use weekly repetition for repeating on specific week days, some use daily
// make these produce the same result
// RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR
// RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
repeatInterval = WEEK_SECONDS
}
}
} else if (key == COUNT) {
repeatLimit = -value.toLong()
} else if (key == UNTIL) {
repeatLimit = parseDateTimeValue(value)
} else if (key == INTERVAL) {
repeatInterval *= value.toInt()
} else if (key == BYDAY) {
if (repeatInterval.isXWeeklyRepetition()) {
repeatRule = handleRepeatRule(value)
} else if (repeatInterval.isXMonthlyRepetition() || repeatInterval.isXYearlyRepetition()) {
repeatRule = if (value.startsWith("-1")) REPEAT_ORDER_WEEKDAY_USE_LAST else REPEAT_ORDER_WEEKDAY
}
} else if (key == BYMONTHDAY) {
if (value.split(",").any { it.toInt() == -1 }) {
repeatRule = REPEAT_LAST_DAY
}
}
}
return EventRepetition(repeatInterval, repeatRule, repeatLimit)
}
private fun getFrequencySeconds(interval: String) = when (interval) {
DAILY -> DAY
WEEKLY -> WEEK
MONTHLY -> MONTH
YEARLY -> YEAR
else -> 0
}
private fun handleRepeatRule(value: String): Int {
var newRepeatRule = 0
if (value.contains(MO))
newRepeatRule = newRepeatRule or MONDAY_BIT
if (value.contains(TU))
newRepeatRule = newRepeatRule or TUESDAY_BIT
if (value.contains(WE))
newRepeatRule = newRepeatRule or WEDNESDAY_BIT
if (value.contains(TH))
newRepeatRule = newRepeatRule or THURSDAY_BIT
if (value.contains(FR))
newRepeatRule = newRepeatRule or FRIDAY_BIT
if (value.contains(SA))
newRepeatRule = newRepeatRule or SATURDAY_BIT
if (value.contains(SU))
newRepeatRule = newRepeatRule or SUNDAY_BIT
return newRepeatRule
}
fun parseDateTimeValue(value: String, timeZone: DateTimeZone = DateTimeZone.UTC): Long {
val edited = value.replace("T", "").replace("Z", "").replace("-", "")
return if (edited.length == 14) {
val dateTimeZone = if (value.endsWith("Z")) DateTimeZone.UTC else timeZone
parseLongFormat(edited, dateTimeZone)
} else {
val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMdd").withZone(timeZone)
val dateTime = dateTimeFormat.parseDateTime(edited)
Formatter.getShiftedTS(dateTime = dateTime, toZone = DateTimeZone.getDefault())
}
}
private fun parseLongFormat(digitString: String, dateTimeZone: DateTimeZone): Long {
val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMddHHmmss")
return dateTimeFormat.parseDateTime(digitString).withZoneRetainFields(dateTimeZone).seconds()
}
// from Daily, 5x... to RRULE:FREQ=DAILY;COUNT=5
fun getRepeatCode(event: Event): String {
val repeatInterval = event.repeatInterval
if (repeatInterval == 0)
return ""
val freq = getFreq(repeatInterval)
val interval = getInterval(repeatInterval)
val repeatLimit = getRepeatLimitString(event)
val byMonth = getByMonth(event)
val byDay = getByDay(event)
return "$FREQ=$freq;$INTERVAL=$interval$repeatLimit$byMonth$byDay"
}
private fun getFreq(interval: Int) = when {
interval % YEAR == 0 -> YEARLY
interval % MONTH == 0 -> MONTHLY
interval % WEEK == 0 -> WEEKLY
else -> DAILY
}
private fun getInterval(interval: Int) = when {
interval % YEAR == 0 -> interval / YEAR
interval % MONTH == 0 -> interval / MONTH
interval % WEEK == 0 -> interval / WEEK
else -> interval / DAY
}
private fun getRepeatLimitString(event: Event) = when {
event.repeatLimit == 0L -> ""
event.repeatLimit < 0 -> ";$COUNT=${-event.repeatLimit}"
else -> if (event.getIsAllDay()) {
";$UNTIL=${Formatter.getDayCodeFromTS(event.repeatLimit)}"
} else {
val dateTime = Formatter.getUTCDateTimeFromTS(event.repeatLimit)
val dayCode = dateTime.toString(Formatter.DAYCODE_PATTERN)
val timeCode = dateTime.toString(Formatter.TIME_PATTERN)
";$UNTIL=${dayCode}T${timeCode}Z"
}
}
private fun getByMonth(event: Event) = when {
event.repeatInterval.isXYearlyRepetition() -> {
val start = Formatter.getDateTimeFromTS(event.startTS)
";$BYMONTH=${start.monthOfYear}"
}
else -> ""
}
private fun getByDay(event: Event) = when {
event.repeatInterval.isXWeeklyRepetition() -> {
val days = getByDayString(event.repeatRule)
";$BYDAY=$days"
}
event.repeatInterval.isXMonthlyRepetition() || event.repeatInterval.isXYearlyRepetition() -> when (event.repeatRule) {
REPEAT_LAST_DAY -> ";$BYMONTHDAY=-1"
REPEAT_ORDER_WEEKDAY_USE_LAST, REPEAT_ORDER_WEEKDAY -> {
val start = Formatter.getDateTimeFromTS(event.startTS)
val dayOfMonth = start.dayOfMonth
val isLastWeekday = start.monthOfYear != start.plusDays(7).monthOfYear
val order = if (isLastWeekday) "-1" else ((dayOfMonth - 1) / 7 + 1).toString()
val day = getDayLetters(start.dayOfWeek)
";$BYDAY=$order$day"
}
else -> ""
}
else -> ""
}
private fun getByDayString(rule: Int): String {
var result = ""
if (rule and MONDAY_BIT != 0)
result += "$MO,"
if (rule and TUESDAY_BIT != 0)
result += "$TU,"
if (rule and WEDNESDAY_BIT != 0)
result += "$WE,"
if (rule and THURSDAY_BIT != 0)
result += "$TH,"
if (rule and FRIDAY_BIT != 0)
result += "$FR,"
if (rule and SATURDAY_BIT != 0)
result += "$SA,"
if (rule and SUNDAY_BIT != 0)
result += "$SU,"
return result.trimEnd(',')
}
private fun getDayLetters(dayOfWeek: Int) = when (dayOfWeek) {
1 -> MO
2 -> TU
3 -> WE
4 -> TH
5 -> FR
6 -> SA
else -> SU
}
// from P0DT1H5M0S to 3900 (seconds)
fun parseDurationSeconds(duration: String): Int {
val weeks = getDurationValue(duration, "W")
val days = getDurationValue(duration, "D")
val hours = getDurationValue(duration, "H")
val minutes = getDurationValue(duration, "M")
val seconds = getDurationValue(duration, "S")
val minSecs = 60
val hourSecs = minSecs * 60
val daySecs = hourSecs * 24
val weekSecs = daySecs * 7
return seconds + (minutes * minSecs) + (hours * hourSecs) + (days * daySecs) + (weeks * weekSecs)
}
private fun getDurationValue(duration: String, char: String) = Regex("[0-9]+(?=$char)").find(duration)?.value?.toInt() ?: 0
// from 65 to P0DT1H5M0S
fun getDurationCode(minutes: Long): String {
var days = 0
var hours = 0
var remainder = minutes
if (remainder >= DAY_MINUTES) {
days = floor((remainder / DAY_MINUTES).toDouble()).toInt()
remainder -= days * DAY_MINUTES
}
if (remainder >= 60) {
hours = floor((remainder / 60).toDouble()).toInt()
remainder -= hours * 60
}
return "P${days}DT${hours}H${remainder}M0S"
}
}