From 78f03f7d82dbf9b68efa4d36104ccc951191b32a Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Tue, 4 Nov 2025 21:48:49 +0100
Subject: [PATCH] groupware: some fixes accordingly to the latest JMAP and
jscalendarbis RFCs
---
pkg/jscalendar/jscalendar_model.go | 78 +++----------------
pkg/jscalendar/jscalendar_model_test.go | 23 ++----
.../pkg/groupware/groupware_mock_calendars.go | 12 +--
.../pkg/groupware/groupware_mock_tasks.go | 6 +-
4 files changed, 21 insertions(+), 98 deletions(-)
diff --git a/pkg/jscalendar/jscalendar_model.go b/pkg/jscalendar/jscalendar_model.go
index acc6701c32..139fe89c16 100644
--- a/pkg/jscalendar/jscalendar_model.go
+++ b/pkg/jscalendar/jscalendar_model.go
@@ -17,6 +17,7 @@ type LocalDateTime struct {
}
*/
type LocalDateTime string
+type UTCDateTime string
type TypeOfRelation string
type TypeOfLink string
@@ -41,7 +42,6 @@ type Relationship string
type Display string
type Rel string
type LocationTypeOption string
-type LocationRelation string
type VirtualLocationFeature string
type Frequency string
type Skip string
@@ -301,9 +301,6 @@ const (
LocationTypeOptionWaterFacility = LocationTypeOption("water-facility")
LocationTypeOptionYouthCamp = LocationTypeOption("youth-camp")
- LocationRelationStart = LocationRelation("start")
- LocationRelationEnd = LocationRelation("end")
-
VirtualLocationFeatureAudio = VirtualLocationFeature("audio")
VirtualLocationFeatureChat = VirtualLocationFeature("chat")
VirtualLocationFeatureFeed = VirtualLocationFeature("feed")
@@ -611,11 +608,6 @@ var (
LocationTypeOptionYouthCamp,
}
- LocationRelations = []LocationRelation{
- LocationRelationStart,
- LocationRelationEnd,
- }
-
Frequencies = []Frequency{
FrequencyYearly,
FrequencyMonthly,
@@ -645,7 +637,6 @@ var (
"relatedTo",
"replyTo",
"sentBy",
- "timeZones",
"uid",
}
@@ -836,13 +827,6 @@ type Link struct {
// server to avoid embedding arbitrarily large data in JSCalendar object instances.
Href string `json:"href"`
- // This MUST be a valid content-id value according to the definition of Section 2 of [RFC2392].
- //
- // The value MUST be unique within this `Link` object but has no meaning beyond that.
- //
- // It MAY be different from the link id for this `Link` object.
- Cid string `json:"cid,omitempty"`
-
// This is the media type [RFC6838] of the resource, if known.
ContentType string `json:"contentType,omitempty"`
@@ -897,21 +881,6 @@ type Location struct {
// The value for each key in the map MUST be `true`.
LocationTypes map[LocationTypeOption]bool `json:"locationTypes,omitempty"`
- // This specifies the relation between this location and the time of the JSCalendar object.
- //
- // This is primarily to allow events representing travel to specify the location of departure (at the
- // start of the event) and location of arrival (at the end); this is particularly important if these
- // locations are in different time zones, as a client may wish to highlight this information for the user.
- //
- // This MUST be one of the following values; any value the client or server doesn't understand
- // should be treated the same as if this property is omitted:
- // !- `start`: The event/task described by this JSCalendar object occurs at this location at the time the event/task starts.
- // !- `end`: The event/task described by this JSCalendar object occurs at this location at the time the event/task ends.
- RelativeTo LocationRelation `json:"relativeTo,omitempty"`
-
- // This is a time zone for this location.
- TimeZone string `json:"timeZone,omitempty"`
-
// This is a geo: URI [RFC5870] for the location.
Coordinates string `json:"coordinates,omitempty"`
@@ -1645,15 +1614,11 @@ type CommonObject struct {
ProdId string `json:"prodId,omitempty"`
// This is the date and time this object was initially created.
- //
- // TODO serialize as UTCDateTime
- Created time.Time `json:"created,omitzero"`
+ Created UTCDateTime `json:"created,omitzero"`
// This is the date and time the data in this object was last modified (or its creation date/time
// if not modified since).
- //
- // TODO serialize as UTCDateTime
- Updated time.Time `json:"updated,omitzero"`
+ Updated UTCDateTime `json:"updated,omitzero"`
// This is a short summary of the object.
Title string `json:"title,omitempty"`
@@ -1719,35 +1684,6 @@ type CommonObject struct {
// [Section 4.3 of CSS Color Module Level 3]: https://www.w3.org/TR/css-color-3/#svg-color
// [Section 4.2.1 of CSS Color Module Level 3]: https://www.w3.org/TR/css-color-3/#rgb-color
Color string `json:"color,omitempty"`
-
- // This maps identifiers of custom time zones to their time zone definitions.
- //
- // The following restrictions apply for each key in the map:
- // !- To avoid conflict with names in the IANA Time Zone Database [TZDB], it MUST start with the `/` character.
- // !- It MUST be a valid `paramtext` value, as specified in Section 3.1 of [RFC5545].
- // !- At least one other property in the same JSCalendar object MUST reference a time zone using this identifier (i.e.,
- // orphaned time zones are not allowed).
- //
- // An identifier need only be unique to this JSCalendar object.
- //
- // It MAY differ from the tzId property value of the TimeZone object it maps to.
- //
- // A JSCalendar object may be part of a hierarchy of other JSCalendar objects (say, an `Event` is an entry in a `Group`).
- //
- // In this case, the set of time zones is the sum of the time zone definitions of this object and its parent objects.
- //
- // If multiple time zones with the same identifier exist, then the definition closest to the calendar object in relation
- // to its parents MUST be used.
- //
- // (In context of `Event`, a time zone definition in its `timeZones` property has precedence over a definition of the
- // same id in the `Group`).
- //
- // Time zone definitions in any children of the calendar object MUST be ignored.
- //
- // A `TimeZone` object maps a `VTIMEZONE` component from iCalendar, and the semantics are as defined in [RFC5545].
- //
- // A valid time zone MUST define at least one transition rule in the `standard` or `daylight` property.
- TimeZones map[string]TimeZone `json:"timeZones,omitempty"`
}
// TODO
@@ -2128,6 +2064,14 @@ type Event struct {
// the `Event`'s Location objects (see Section 4.2.5).
Duration Duration `json:"duration,omitempty"`
+ // This identifies the time zone in which this event ends, for cases where the start and time zones of the event differ
+ // (e.g., a transcontinental flight).
+ //
+ // If this property is not set, then the event starts and ends in the same time zone.
+ //
+ // This property MUST NOT be set if the timeZone property value is null or not set.
+ EndTimeZone string `json:"endTimeZone,omitempty"`
+
// This is the scheduling status (Section 4.4) of an Event.
//
// If set, it MUST be one of the following values, another value registered in the IANA
diff --git a/pkg/jscalendar/jscalendar_model_test.go b/pkg/jscalendar/jscalendar_model_test.go
index 1da7ac275d..be00c0d0ce 100644
--- a/pkg/jscalendar/jscalendar_model_test.go
+++ b/pkg/jscalendar/jscalendar_model_test.go
@@ -74,7 +74,6 @@ func TestLink(t *testing.T) {
}`, Link{
Type: LinkType,
Href: "https://opencloud.eu.example.com/f72ae875-40be-48a4-84ff-aea9aed3e085.png",
- Cid: "c1",
ContentType: "image/png",
Size: 128912,
Rel: RelIcon,
@@ -115,14 +114,11 @@ func TestLocation(t *testing.T) {
LocationTypeOptionLandmarkAddress: true,
LocationTypeOptionIndustrial: true,
},
- RelativeTo: LocationRelationStart,
- TimeZone: "Europe/Paris",
Coordinates: "geo:48.8559324,2.2932441",
Links: map[string]Link{
"l1": {
Type: LinkType,
Href: "https://upload.wikimedia.org/wikipedia/commons/f/fd/Eiffel_blue.PNG",
- Cid: "cl1",
ContentType: "image/png",
Size: 12345,
Rel: RelIcon,
@@ -340,7 +336,6 @@ func TestParticipant(t *testing.T) {
"l1": {
Type: LinkType,
Href: "https://opa.org/opa.png",
- Cid: "c1",
ContentType: "image/png",
Size: 182912,
Rel: RelIcon,
@@ -608,11 +603,13 @@ func TestTimeZone(t *testing.T) {
}
func TestEvent(t *testing.T) {
- ts1, err := time.Parse(time.RFC3339, "2025-09-25T18:26:14+02:00")
+ local1 := "2025-09-25T18:26:14"
+ ts1, err := time.Parse(time.RFC3339, local1+"+02:00")
require.NoError(t, err)
ts1 = ts1.UTC()
- ts2, err := time.Parse(time.RFC3339, "2025-09-29T15:53:01+02:00")
+ local2 := "2025-09-29T15:53:01"
+ ts2, err := time.Parse(time.RFC3339, local2+"+02:00")
require.NoError(t, err)
ts2 = ts2.UTC()
@@ -692,8 +689,8 @@ func TestEvent(t *testing.T) {
CommonObject: CommonObject{
Uid: "b422cfec-f7b4-4e04-8ec6-b794007f63f1",
ProdId: "OpenCloud 1.0",
- Created: ts1,
- Updated: ts2,
+ Created: UTCDateTime(local1),
+ Updated: UTCDateTime(local2),
Title: "End of year party",
Description: "It's the party at the end of the year.",
DescriptionContentType: "text/plain",
@@ -713,12 +710,6 @@ func TestEvent(t *testing.T) {
"cat": true,
},
Color: "oil",
- TimeZones: map[string]TimeZone{
- "cest": {
- Type: TimeZoneType,
- TzId: "cest",
- },
- },
},
RelatedTo: map[string]Relation{
"a": {
@@ -738,8 +729,6 @@ func TestEvent(t *testing.T) {
LocationTypes: map[LocationTypeOption]bool{
LocationTypeOptionBar: true,
},
- RelativeTo: LocationRelationStart,
- TimeZone: "cest",
Coordinates: "geo:16.7685657,-4.8629852",
Links: map[string]Link{
"l1": {
diff --git a/services/groupware/pkg/groupware/groupware_mock_calendars.go b/services/groupware/pkg/groupware/groupware_mock_calendars.go
index bbe396b9e7..f48439e411 100644
--- a/services/groupware/pkg/groupware/groupware_mock_calendars.go
+++ b/services/groupware/pkg/groupware/groupware_mock_calendars.go
@@ -80,8 +80,8 @@ var E1 = jmap.CalendarEvent{
CommonObject: jscalendar.CommonObject{
Uid: "9a7ab91a-edca-4988-886f-25e00743430d",
ProdId: "Mock 0.0",
- Created: mustParseTime("2025-09-29T16:17:18Z"),
- Updated: mustParseTime("2025-09-29T16:17:18Z"),
+ Created: "2025-09-29T16:17:18",
+ Updated: "2025-09-29T16:17:18",
Title: "Meeting of the Minds",
Description: "Internal meeting about the grand strategy for the future",
DescriptionContentType: "text/plain",
@@ -103,12 +103,6 @@ var E1 = jmap.CalendarEvent{
"internal": true,
},
Color: "purple",
- TimeZones: map[string]jscalendar.TimeZone{
- "airee8ai": {
- Type: jscalendar.TimeZoneType,
- TzId: "CEST",
- },
- },
},
RelatedTo: map[string]jscalendar.Relation{},
Sequence: 0,
@@ -121,8 +115,6 @@ var E1 = jmap.CalendarEvent{
LocationTypes: map[jscalendar.LocationTypeOption]bool{
jscalendar.LocationTypeOptionOffice: true,
},
- RelativeTo: jscalendar.LocationRelationStart,
- TimeZone: "CEST",
Coordinates: "geo:52.5334956,13.4079872",
Links: map[string]jscalendar.Link{
"eefe2pax": {
diff --git a/services/groupware/pkg/groupware/groupware_mock_tasks.go b/services/groupware/pkg/groupware/groupware_mock_tasks.go
index 37c3d18791..6c984ecade 100644
--- a/services/groupware/pkg/groupware/groupware_mock_tasks.go
+++ b/services/groupware/pkg/groupware/groupware_mock_tasks.go
@@ -82,8 +82,8 @@ var T1 = jmap.Task{
CommonObject: jscalendar.CommonObject{
Uid: "7da0d4a2-385c-430f-9022-61db302734d9",
ProdId: "Mock 0.0",
- Created: mustParseTime("2025-10-01T17:31:49Z"),
- Updated: mustParseTime("2025-10-01T17:35:12Z"),
+ Created: "2025-10-01T17:31:49",
+ Updated: "2025-10-01T17:35:12",
Title: "Crossing the Ring",
Description: "We need to cross the Ring the protomolecule opened.",
DescriptionContentType: "text/plain",
@@ -117,8 +117,6 @@ var T1 = jmap.Task{
LocationTypes: map[jscalendar.LocationTypeOption]bool{
jscalendar.LocationTypeOptionLandmarkAddress: true,
},
- RelativeTo: jscalendar.LocationRelationStart,
- TimeZone: "UTC",
Coordinates: "geo:40.4165583,-3.7063595",
Links: map[string]jscalendar.Link{
"jeeshei5": {