11/*
22 * Nextcloud - Android Client
33 *
4+ * SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
45 * SPDX-FileCopyrightText: 2015 Jon Griffiths (jon_p_griffiths@yahoo.com)
56 * SPDX-FileCopyrightText: 2013 Dominik Schürmann <dominik@dominikschuermann.de>
67 * SPDX-FileCopyrightText: 2010-2011 Lukas Aichbauer
2324import android .text .format .DateUtils ;
2425
2526import com .nextcloud .client .preferences .AppPreferences ;
27+ import com .nextcloud .utils .extensions .TemporalExtensionsKt ;
2628import com .owncloud .android .R ;
2729import com .owncloud .android .lib .common .utils .Log_OC ;
2830
2931import net .fortuna .ical4j .model .Calendar ;
30- import net .fortuna .ical4j .model .DateTime ;
3132import net .fortuna .ical4j .model .Parameter ;
3233import net .fortuna .ical4j .model .Property ;
3334import net .fortuna .ical4j .model .component .CalendarComponent ;
3738import net .fortuna .ical4j .model .parameter .Related ;
3839import net .fortuna .ical4j .model .property .Action ;
3940import net .fortuna .ical4j .model .property .DateProperty ;
41+ import net .fortuna .ical4j .model .property .DtEnd ;
42+ import net .fortuna .ical4j .model .property .DtStart ;
4043import net .fortuna .ical4j .model .property .Duration ;
4144import net .fortuna .ical4j .model .property .FreeBusy ;
4245import net .fortuna .ical4j .model .property .Transp ;
4346import net .fortuna .ical4j .model .property .Trigger ;
47+ import net .fortuna .ical4j .model .property .immutable .ImmutableAction ;
48+ import net .fortuna .ical4j .model .property .immutable .ImmutableTransp ;
4449
4550import java .net .URI ;
51+ import java .time .LocalDate ;
52+ import java .time .ZonedDateTime ;
53+ import java .time .temporal .Temporal ;
4654import java .time .temporal .TemporalAmount ;
4755import java .time .temporal .TemporalUnit ;
4856import java .util .ArrayList ;
4957import java .util .List ;
5058import java .util .Objects ;
59+ import java .util .Optional ;
5160import java .util .UUID ;
5261
5362import javax .inject .Inject ;
@@ -110,13 +119,8 @@ private boolean getTestFileSupport() {
110119 }
111120
112121 public DuplicateHandlingEnum getDuplicateHandling () {
113- // return DuplicateHandlingEnum.values()[getEnumInt(PREF_DUPLICATE_HANDLING, 0)];
114122 return DuplicateHandlingEnum .values ()[0 ]; // TODO is option needed?
115123 }
116-
117- // private int getEnumInt(final String key, final int def) {
118- // return Integer.parseInt(getString(key, String.valueOf(def)));
119- // }
120124 }
121125
122126 public ProcessVEvent (Context context , Calendar iCalCalendar , AndroidCalendar selectedCal , boolean isInserter ) {
@@ -275,7 +279,8 @@ private ContentValues convertToDB(VEvent e, Options options,
275279 reminders .clear ();
276280
277281 boolean allDay = false ;
278- boolean startIsDate = !(e .getStartDate ().getDate () instanceof DateTime );
282+ Temporal startTemporal = e .<Temporal >getStartDate ().map (DtStart ::getDate ).orElse (null );
283+ boolean startIsDate = startTemporal instanceof LocalDate ;
279284 boolean isRecurring = hasProperty (e , Property .RRULE ) || hasProperty (e , Property .RDATE );
280285
281286 if (startIsDate ) {
@@ -288,25 +293,27 @@ private ContentValues convertToDB(VEvent e, Options options,
288293 // No end date or duration given.
289294 // Since we added a duration above when the start date is a DATE:
290295 // - The start date is a DATETIME, the event lasts no time at all (RFC 2445).
291- e .getProperties (). add (ZERO_SECONDS );
296+ e .add (ZERO_SECONDS );
292297 // Zero time events are always free (RFC 2445), so override/set TRANSP accordingly.
293298 removeProperty (e , Property .TRANSP );
294- e .getProperties (). add (Transp .TRANSPARENT );
299+ e .add (ImmutableTransp .TRANSPARENT );
295300 }
296301
297302 if (isRecurring ) {
298303 // Recurring event. Android insists on a duration.
299304 if (!hasProperty (e , Property .DURATION )) {
300305 // Calculate duration from start to end date
301- Duration d = new Duration (e .getStartDate ().getDate (), e .getEndDate ().getDate ());
302- e .getProperties ().add (d );
306+ Temporal endTemporal = e .<Temporal >getEndDate ().map (DtEnd ::getDate ).orElse (null );
307+ Duration d = new Duration (startTemporal , endTemporal );
308+ e .add (d );
303309 }
304310 removeProperty (e , Property .DTEND );
305311 } else {
306312 // Non-recurring event. Android insists on an end date.
307313 if (!hasProperty (e , Property .DTEND )) {
308314 // Calculate end date from duration, set it and remove the duration.
309- e .getProperties ().add (e .getEndDate ());
315+ Optional <DtEnd <Temporal >> derivedEnd = e .getEndDate ();
316+ derivedEnd .ifPresent (e ::add );
310317 }
311318 removeProperty (e , Property .DURATION );
312319 }
@@ -331,8 +338,9 @@ private ContentValues convertToDB(VEvent e, Options options,
331338
332339 copyProperty (c , Events .EVENT_LOCATION , e , Property .LOCATION );
333340
334- if (hasProperty (e , Property .STATUS )) {
335- String status = e .getProperty (Property .STATUS ).getValue ();
341+ Optional <Property > statusProperty = e .getProperty (Property .STATUS );
342+ if (statusProperty .isPresent ()) {
343+ String status = statusProperty .get ().getValue ();
336344 switch (status ) {
337345 case "TENTATIVE" :
338346 c .put (Events .STATUS , Events .STATUS_TENTATIVE );
@@ -352,13 +360,14 @@ private ContentValues convertToDB(VEvent e, Options options,
352360 c .put (Events .ALL_DAY , 1 );
353361 }
354362
355- copyDateProperty (c , Events .DTSTART , Events .EVENT_TIMEZONE , e . getStartDate ( ));
363+ e .< Temporal > getStartDate (). ifPresent ( dtStart -> copyDateProperty (c , Events .DTSTART , Events .EVENT_TIMEZONE , dtStart ));
356364 if (hasProperty (e , Property .DTEND )) {
357- copyDateProperty (c , Events .DTEND , Events .EVENT_END_TIMEZONE , e . getEndDate ( ));
365+ e .< Temporal > getEndDate (). ifPresent ( dtEnd -> copyDateProperty (c , Events .DTEND , Events .EVENT_END_TIMEZONE , dtEnd ));
358366 }
359367
360- if (hasProperty (e , Property .CLASS )) {
361- String access = e .getProperty (Property .CLASS ).getValue ();
368+ Optional <Property > classProperty = e .getProperty (Property .CLASS );
369+ if (classProperty .isPresent ()) {
370+ String access = classProperty .get ().getValue ();
362371 int accessLevel = switch (access ) {
363372 case "CONFIDENTIAL" -> Events .ACCESS_CONFIDENTIAL ;
364373 case "PRIVATE" -> Events .ACCESS_PRIVATE ;
@@ -372,16 +381,17 @@ private ContentValues convertToDB(VEvent e, Options options,
372381 // Work out availability. This is confusing as FREEBUSY and TRANSP overlap.
373382 int availability = Events .AVAILABILITY_BUSY ;
374383 if (hasProperty (e , Property .TRANSP )) {
375- if (e .getTransparency () == Transp .TRANSPARENT ) {
384+ Optional <Transp > transparency = e .getTransparency ();
385+ if (transparency .isPresent () && Transp .VALUE_TRANSPARENT .equals (transparency .get ().getValue ())) {
376386 availability = Events .AVAILABILITY_FREE ;
377387 }
378388
379389 } else if (hasProperty (e , Property .FREEBUSY )) {
380- FreeBusy fb = e .getProperty (Property .FREEBUSY );
381- FbType fbType = fb .getParameter (Parameter .FBTYPE );
382- if (fbType != null && fbType == FbType .FREE ) {
390+ Optional < FreeBusy > fb = e .getProperty (Property .FREEBUSY );
391+ Optional < FbType > fbType = fb .isPresent () ? fb . get (). getParameter (Parameter .FBTYPE ) : Optional . empty ( );
392+ if (fbType . isPresent () && fbType . get (). equals ( FbType .FREE ) ) {
383393 availability = Events .AVAILABILITY_FREE ;
384- } else if (fbType != null && fbType == FbType .BUSY_TENTATIVE ) {
394+ } else if (fbType . isPresent () && fbType . get (). equals ( FbType .BUSY_TENTATIVE ) ) {
385395 availability = Events .AVAILABILITY_TENTATIVE ;
386396 }
387397 }
@@ -400,23 +410,31 @@ private ContentValues convertToDB(VEvent e, Options options,
400410
401411 for (VAlarm a : e .getAlarms ()) {
402412
403- if (a .getAction () != Action .AUDIO && a .getAction () != Action .DISPLAY ) {
413+ Optional <Action > action = a .getAction ();
414+ if (action .isEmpty () || (!ImmutableAction .AUDIO .equals (action .get ()) && !ImmutableAction .DISPLAY .equals (action .get ()))) {
404415 continue ; // Ignore email and procedure alarms
405416 }
406417
407- Trigger t = a .getTrigger ();
408- final long startMs = e .getStartDate ().getDate ().getTime ();
418+ Optional <Trigger > triggerProperty = a .getTrigger ();
419+ if (triggerProperty .isEmpty ()) {
420+ continue ;
421+ }
422+ Trigger t = triggerProperty .get ();
423+
424+ final long startMs = TemporalExtensionsKt .toEpochMilli (startTemporal );
409425 long alarmStartMs = startMs ;
410426 long alarmMs ;
411427
412428 // FIXME: - Support for repeating alarms
413429 // - Check the calendars max number of alarms
414- if (t .getDateTime () != null )
415- alarmMs = t .getDateTime ().getTime (); // Absolute
430+ if (t .isAbsolute () )
431+ alarmMs = t .getDate ().toEpochMilli (); // Absolute
416432 else if (t .getDuration () != null ) {
417- Related rel = t .getParameter (Parameter .RELATED );
418- if (rel != null && rel == Related .END )
419- alarmStartMs = e .getEndDate ().getDate ().getTime ();
433+ Optional <Related > rel = t .getParameter (Parameter .RELATED );
434+ if (rel .isPresent () && rel .get ().equals (Related .END )) {
435+ Temporal endTemporal = e .<Temporal >getEndDate ().map (DtEnd ::getDate ).orElse (null );
436+ alarmStartMs = TemporalExtensionsKt .toEpochMilli (endTemporal );
437+ }
420438 alarmMs = alarmStartMs + durationToMs (t .getDuration ());
421439 } else {
422440 continue ;
@@ -452,33 +470,31 @@ private static long durationToMs(TemporalAmount d) {
452470 }
453471
454472 private boolean hasProperty (VEvent e , String name ) {
455- return e .getProperty (name ) != null ;
473+ return e .getProperty (name ). isPresent () ;
456474 }
457475
458476 private void removeProperty (VEvent e , String name ) {
459- Property p = e .getProperty (name );
460- if (p != null ) {
461- e .getProperties ().remove (p );
462- }
477+ e .removeAll (name );
463478 }
464479
465480 private void copyProperty (ContentValues c , String dbName , VEvent e , String evName ) {
466481 if (dbName != null ) {
467- Property p = e .getProperty (evName );
468- if (p != null ) {
469- c .put (dbName , p .getValue ());
482+ Optional < Property > p = e .getProperty (evName );
483+ if (p . isPresent () ) {
484+ c .put (dbName , p .get (). getValue ());
470485 }
471486 }
472487 }
473488
474- private void copyDateProperty (ContentValues c , String dbName , String dbTzName , DateProperty date ) {
475- if (dbName != null && date .getDate () != null ) {
476- c .put (dbName , date .getDate ().getTime ()); // ms since epoc in GMT
489+ private void copyDateProperty (ContentValues c , String dbName , String dbTzName , DateProperty <? extends Temporal > date ) {
490+ Temporal temporal = date .getDate ();
491+ if (dbName != null && temporal != null ) {
492+ c .put (dbName , TemporalExtensionsKt .toEpochMilli (temporal )); // ms since epoc in GMT
477493 if (dbTzName != null ) {
478- if (date .isUtc () || date . getTimeZone () == null ) {
494+ if (date .isUtc () || !( temporal instanceof ZonedDateTime ) ) {
479495 c .put (dbTzName , "UTC" );
480496 } else {
481- c .put (dbTzName , date . getTimeZone ().getID ());
497+ c .put (dbTzName , (( ZonedDateTime ) temporal ). getZone ().getId ());
482498 }
483499 }
484500 }
@@ -566,10 +582,11 @@ private void checkTestValue(ContentValues c, String keyValue, String testName) {
566582
567583 private void processEventTests (VEvent e , ContentValues c , List <Integer > reminders ) {
568584
569- Property testName = e .getProperty ("X-TEST-NAME" );
570- if (testName == null ) {
585+ Optional < Property > testNameProperty = e .getProperty ("X-TEST-NAME" );
586+ if (testNameProperty . isEmpty () ) {
571587 return ; // Not a test case
572588 }
589+ Property testName = testNameProperty .get ();
573590
574591 // This is a test event. Verify it using the embedded meta data.
575592 Log_OC .i (TAG , "Processing test case " + testName .getValue () + "..." );
0 commit comments