diff --git a/CODEOWNERS b/CODEOWNERS
index a82897a8b9633..7c798cf0bf194 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -102,6 +102,7 @@
/bundles/org.openhab.binding.enocean/ @fruggy83
/bundles/org.openhab.binding.enphase/ @Hilbrand
/bundles/org.openhab.binding.enturno/ @klocsson
+/bundles/org.openhab.binding.ephemeris/ @clinique
/bundles/org.openhab.binding.epsonprojector/ @mlobstein
/bundles/org.openhab.binding.etherrain/ @dfad1469
/bundles/org.openhab.binding.evcc/ @florian-h05
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 71a3e8dc36a1f..4feefb764119d 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -501,6 +501,11 @@
org.openhab.binding.enturno${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.ephemeris
+ ${project.version}
+ org.openhab.addons.bundlesorg.openhab.binding.epsonprojector
diff --git a/bundles/org.openhab.binding.ephemeris/NOTICE b/bundles/org.openhab.binding.ephemeris/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.ephemeris/README.md b/bundles/org.openhab.binding.ephemeris/README.md
new file mode 100644
index 0000000000000..238e9056f17ec
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/README.md
@@ -0,0 +1,105 @@
+# Ephemeris Binding
+
+The Ephemeris Binding makes the bridge with Ephemeris core actions.
+It provides access to Ephemeris data via Items without requiring usage of a scripting language.
+
+The binding will search your Jollyday event definition files in the sub folder `/misc/ephemeris` located in the configuration folder of openHAB (e.g. for a linux system : /etc/openhab/misc/ephemeris/)
+
+## Supported Things
+
+The binding handles the following Things:
+
+* default holiday data (`holiday`)
+* custom holiday file (`custom`)
+* daysets (`dayset`)
+* weekend (`weekend`)
+
+## Discovery
+
+The binding discovers `weekend` and `holiday` things.
+
+## Binding Configuration
+
+There is no configuration at binding level.
+
+## Thing Configuration
+
+
+### `custom` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|---------------------------------------------------|---------|----------|----------|
+| fileName | text | Name of the XML file in the configuration folder | N/A | yes | no |
+
+The file has to use the syntax described here : https://www.openhab.org/docs/configuration/actions.html#custom-bank-holidays
+
+### `dayset` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|---------------------------|---------|----------|----------|
+| name | text | Name of the dayset used | N/A | yes | no |
+
+
+## Channels
+
+### `weekend` Channels
+
+| Name | Type | Description |
+|----------|--------|---------------------------------------------------------------|
+| today | Switch | Set to ON if today is a weekend day, OFF in the other case |
+| tomorrow | Switch | Set to ON if tomorrow is a weekend day, OFF in the other case |
+
+### `dayset` Channels
+
+| Name | Type | Description |
+|----------|--------|---------------------------------------------------------------------|
+| today | Switch | Set to ON if today is in the given dayset, OFF in the other case |
+| tomorrow | Switch | Set to ON if tomorrow is in the given dayset, OFF in the other case |
+
+### `holiday` Channels
+
+| Name | Type | Description |
+|------------------|-------------|------------------------------------------------|
+| title-today | String | Name of today's holiday if any, NULL otherwise |
+| holiday-today | Switch | Set to ON if today is a holiday |
+| holiday-tomorrow | Switch | Set to ON if tomorrow is a holiday |
+| next-title | String | Name of the next coming holiday |
+| next-start | DateTime | Start date of the next coming holiday |
+| days-remaining | Number:Time | Remaining days until next holiday |
+
+### `custom` Channels
+
+| Name | Type | Description |
+|----------------|-------------|----------------------------------------|
+| title-today | String | Title of the currently present event |
+| event-today | Switch | Set to ON if an event exists today |
+| event-tomorrow | Switch | Set to ON if an event exists tomorrow |
+| next-title | String | Title of the next starting event |
+| next-start | DateTime | Start date of the next coming event |
+| days-remaining | Number:Time | Remaining days until next event |
+
+## Full Example
+
+### Thing Configuration
+
+```java
+Thing ephemeris:holiday:local "Holidays"
+Thing ephemeris:weekend:local "Week-end"
+Thing ephemeris:custom:events "Event" [fileName="events.xml"]
+```
+
+### Item Configuration
+
+```java
+String ToD_Event_Current "Event Today" (gEvents) {channel="ephemeris:custom:events:title-today"}
+String ToD_Event_Next "Event Next" (gEvents) {channel="ephemeris:custom:events:next-title"}
+Number:Time ToD_Event_Next_Left "Event In" (gEvents) ["Measurement","Duration"] {channel="ephemeris:custom:events:days-remaining", unit="day"}
+
+Switch ToD_Week_End_Current "Week-End" (gWeekEnd) {channel="ephemeris:weekend:local:today"}
+Switch ToD_Week_End_Tomorrow "Week-End Tomorrow" (gWeekEnd) {channel="ephemeris:weekend:local:tomorrow"}
+
+String ToD_Holiday_Current "Holiday Today" (gHoliday) {channel="ephemeris:holiday:local:title-today"}
+String ToD_Holiday_Next "Holiday Next" (gHoliday) {channel="ephemeris:holiday:local:next-title"}
+Number:Time ToD_Holiday_Next_Left "Holiday In" (gHoliday) ["Measurement","Duration"] {channel="ephemeris:holiday:local:days-remaining", unit="day"}
+
+```
diff --git a/bundles/org.openhab.binding.ephemeris/pom.xml b/bundles/org.openhab.binding.ephemeris/pom.xml
new file mode 100644
index 0000000000000..f8c582d38b0f9
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.2.0-SNAPSHOT
+
+
+ org.openhab.binding.ephemeris
+
+ openHAB Add-ons :: Bundles :: Ephemeris Binding
+
+
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/feature/feature.xml b/bundles/org.openhab.binding.ephemeris/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..0d647a0847776
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.ephemeris/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisBindingConstants.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisBindingConstants.java
new file mode 100644
index 0000000000000..4e2c7af9399bb
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisBindingConstants.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal;
+
+import java.io.File;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.OpenHAB;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link EphemerisBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class EphemerisBindingConstants {
+
+ public static final String BINDING_ID = "ephemeris";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_CUSTOM = new ThingTypeUID(BINDING_ID, "custom");
+ public static final ThingTypeUID THING_TYPE_HOLIDAY = new ThingTypeUID(BINDING_ID, "holiday");
+ public static final ThingTypeUID THING_TYPE_DAYSET = new ThingTypeUID(BINDING_ID, "dayset");
+ public static final ThingTypeUID THING_TYPE_WEEKEND = new ThingTypeUID(BINDING_ID, "weekend");
+
+ // List of all Channel ids
+ public static final String CHANNEL_CURRENT_EVENT = "title-today";
+ public static final String CHANNEL_NEXT_EVENT = "next-title";
+ public static final String CHANNEL_NEXT_START = "next-start";
+ public static final String CHANNEL_NEXT_REMAINING = "days-remaining";
+ public static final String CHANNEL_TODAY = "today";
+ public static final String CHANNEL_TOMORROW = "tomorrow";
+ public static final String CHANNEL_HOLIDAY_TODAY = "holiday-today";
+ public static final String CHANNEL_HOLIDAY_TOMORROW = "holiday-tomorrow";
+ public static final String CHANNEL_EVENT_TODAY = "event-today";
+ public static final String CHANNEL_EVENT_TOMORROW = "event-tomorrow";
+
+ // Folder for xml storage eg: /etc/openhab/misc/ephemeris
+ public static final String BINDING_DATA_PATH = "%s%smisc%s%s".formatted(OpenHAB.getConfigFolder(), File.separator,
+ File.separator, BINDING_ID);
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisException.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisException.java
new file mode 100644
index 0000000000000..67cab6f75a71c
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisException.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingStatusDetail;
+
+/**
+ * Exception raised by Ephemeris Handlers
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class EphemerisException extends Exception {
+ private static final long serialVersionUID = -8813754360966576513L;
+ private final ThingStatusDetail statusDetail;
+
+ public EphemerisException(String message, ThingStatusDetail statusDetail) {
+ super(message);
+ this.statusDetail = statusDetail;
+ }
+
+ public ThingStatusDetail getStatusDetail() {
+ return statusDetail;
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisHandlerFactory.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisHandlerFactory.java
new file mode 100644
index 0000000000000..695c677208e1b
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/EphemerisHandlerFactory.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.io.File;
+import java.time.ZoneId;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ephemeris.internal.handler.CustomHandler;
+import org.openhab.binding.ephemeris.internal.handler.DaysetHandler;
+import org.openhab.binding.ephemeris.internal.handler.HolidayHandler;
+import org.openhab.binding.ephemeris.internal.handler.WeekendHandler;
+import org.openhab.binding.ephemeris.internal.providers.EphemerisDescriptionProvider;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EphemerisHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.ephemeris", service = ThingHandlerFactory.class)
+public class EphemerisHandlerFactory extends BaseThingHandlerFactory {
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CUSTOM, THING_TYPE_HOLIDAY,
+ THING_TYPE_DAYSET, THING_TYPE_WEEKEND);
+
+ private final Logger logger = LoggerFactory.getLogger(EphemerisHandlerFactory.class);
+ private final EphemerisManager ephemerisManager;
+ private final ZoneId zoneId;
+ private final EphemerisDescriptionProvider descriptionProvider;
+
+ @Activate
+ public EphemerisHandlerFactory(final @Reference EphemerisManager ephemerisManager,
+ final @Reference TimeZoneProvider timeZoneProvider,
+ final @Reference EphemerisDescriptionProvider descriptionProvider) {
+ this.ephemerisManager = ephemerisManager;
+ this.zoneId = timeZoneProvider.getTimeZone();
+ this.descriptionProvider = descriptionProvider;
+ File folder = new File(BINDING_DATA_PATH);
+ if (!folder.exists()) {
+ logger.info("Please, create the folder '{}' to store your custom Jollyday definition files.",
+ BINDING_DATA_PATH);
+ }
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_CUSTOM.equals(thingTypeUID)) {
+ return new CustomHandler(thing, ephemerisManager, zoneId);
+ } else if (THING_TYPE_HOLIDAY.equals(thingTypeUID)) {
+ return new HolidayHandler(thing, ephemerisManager, zoneId, descriptionProvider);
+ } else if (THING_TYPE_DAYSET.equals(thingTypeUID)) {
+ return new DaysetHandler(thing, ephemerisManager, zoneId);
+ } else if (THING_TYPE_WEEKEND.equals(thingTypeUID)) {
+ return new WeekendHandler(thing, ephemerisManager, zoneId);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/configuration/DaysetConfiguration.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/configuration/DaysetConfiguration.java
new file mode 100644
index 0000000000000..71d5538f73430
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/configuration/DaysetConfiguration.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.configuration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link DaysetConfiguration} class contains fields mapping Dayset Thing configuration parameters.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class DaysetConfiguration {
+ public String name = "";
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/configuration/FileConfiguration.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/configuration/FileConfiguration.java
new file mode 100644
index 0000000000000..7773f3f94dc2d
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/configuration/FileConfiguration.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.configuration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link FileConfiguration} class contains fields mapping File Thing configuration parameters.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class FileConfiguration {
+ public String fileName = "";
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/discovery/EphemerisDiscoveryService.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/discovery/EphemerisDiscoveryService.java
new file mode 100644
index 0000000000000..152a80244d76b
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/discovery/EphemerisDiscoveryService.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.discovery;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EphemerisDiscoveryService} creates default available Ephemeris Things.
+ *
+ * @author Gaël L'hopital - Initial Contribution
+ */
+@NonNullByDefault
+@Component(service = DiscoveryService.class, configurationPid = "discovery.ephemeris")
+public class EphemerisDiscoveryService extends AbstractDiscoveryService {
+ private static final int DISCOVER_TIMEOUT_SECONDS = 2;
+ private static final String LOCAL = "local";
+ private static final ThingUID HOLIDAY_THING = new ThingUID(THING_TYPE_HOLIDAY, LOCAL);
+ private static final ThingUID WEEKEND_THING = new ThingUID(THING_TYPE_WEEKEND, LOCAL);
+
+ private final Logger logger = LoggerFactory.getLogger(EphemerisDiscoveryService.class);
+
+ private Optional> discoveryJob = Optional.empty();
+
+ @Activate
+ public EphemerisDiscoveryService(final @Reference LocaleProvider localeProvider,
+ final @Reference TranslationProvider i18nProvider, @Nullable Map configProperties) {
+ super(Set.of(THING_TYPE_HOLIDAY, THING_TYPE_WEEKEND), DISCOVER_TIMEOUT_SECONDS, true);
+ this.localeProvider = localeProvider;
+ this.i18nProvider = i18nProvider;
+ activate(configProperties);
+ }
+
+ @Override
+ protected void startScan() {
+ logger.debug("Starting Ephemeris discovery scan");
+ createResults();
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ discoveryJob = Optional.of(scheduler.schedule(this::createResults, 2, TimeUnit.SECONDS));
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ logger.debug("Stopping Ephemeris device background discovery");
+ discoveryJob.ifPresent(job -> job.cancel(true));
+ discoveryJob = Optional.empty();
+ }
+
+ public void createResults() {
+ thingDiscovered(DiscoveryResultBuilder.create(HOLIDAY_THING).build());
+ thingDiscovered(DiscoveryResultBuilder.create(WEEKEND_THING).build());
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/BaseEphemerisHandler.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/BaseEphemerisHandler.java
new file mode 100644
index 0000000000000..d4019ac50f674
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/BaseEphemerisHandler.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.handler;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Optional;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.ephemeris.internal.EphemerisException;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link BaseEphemerisHandler} is the base class for Ephemeris Things. It takes care of
+ * update logic and update scheduling once a day.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public abstract class BaseEphemerisHandler extends BaseThingHandler {
+ private static final int REFRESH_FIRST_HOUR_OF_DAY = 0;
+ private static final int REFRESH_FIRST_MINUTE_OF_DAY = 1;
+
+ private final Logger logger = LoggerFactory.getLogger(BaseEphemerisHandler.class);
+ private final ZoneId zoneId;
+ private Optional> refreshJob = Optional.empty();
+
+ protected final EphemerisManager ephemeris;
+
+ public BaseEphemerisHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
+ super(thing);
+ this.zoneId = zoneId;
+ this.ephemeris = ephemerisManager;
+ }
+
+ @Override
+ public void initialize() {
+ updateStatus(ThingStatus.UNKNOWN);
+ refreshJob = Optional.of(scheduler.schedule(this::updateData, 1, TimeUnit.SECONDS));
+ }
+
+ @Override
+ public void dispose() {
+ refreshJob.ifPresent(job -> job.cancel(true));
+ refreshJob = Optional.empty();
+ super.dispose();
+ }
+
+ private void updateData() {
+ ZonedDateTime now = ZonedDateTime.now().withZoneSameLocal(zoneId);
+
+ logger.debug("Updating {} channels", getThing().getUID());
+ try {
+ internalUpdate(now.truncatedTo(ChronoUnit.DAYS));
+
+ updateStatus(ThingStatus.ONLINE);
+ ZonedDateTime nextUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
+ .withMinute(REFRESH_FIRST_MINUTE_OF_DAY).truncatedTo(ChronoUnit.MINUTES);
+ long delay = ChronoUnit.MINUTES.between(now, nextUpdate);
+ logger.debug("Scheduling next {} update in {} minutes", getThing().getUID(), delay);
+ refreshJob = Optional.of(scheduler.schedule(this::updateData, delay, TimeUnit.MINUTES));
+ } catch (EphemerisException e) {
+ updateStatus(ThingStatus.OFFLINE, e.getStatusDetail(), e.getMessage());
+ }
+ }
+
+ protected abstract void internalUpdate(ZonedDateTime today) throws EphemerisException;
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (RefreshType.REFRESH.equals(command)) {
+ updateData();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/CustomHandler.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/CustomHandler.java
new file mode 100644
index 0000000000000..b204f96fe6822
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/CustomHandler.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.handler;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ephemeris.internal.EphemerisException;
+import org.openhab.binding.ephemeris.internal.configuration.FileConfiguration;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+
+/**
+ * The {@link CustomHandler} delivers user defined Jollyday definition events.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class CustomHandler extends JollydayHandler {
+ private Optional definitionFile = Optional.empty();
+
+ public CustomHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
+ super(thing, ephemerisManager, zoneId);
+ }
+
+ @Override
+ public void initialize() {
+ String fileName = getConfigAs(FileConfiguration.class).fileName;
+
+ if (fileName.isBlank()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "'fileName' can not be blank or empty");
+ return;
+ }
+
+ File file = new File(BINDING_DATA_PATH, fileName);
+ if (!file.exists()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Missing file: %s".formatted(file.getAbsolutePath()));
+ return;
+ }
+
+ definitionFile = Optional.of(file);
+ super.initialize();
+ }
+
+ @Override
+ protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
+ String event = getEvent(today);
+ updateState(CHANNEL_EVENT_TODAY, OnOffType.from(event != null));
+
+ event = getEvent(today.plusDays(1));
+ updateState(CHANNEL_EVENT_TOMORROW, OnOffType.from(event != null));
+
+ super.internalUpdate(today);
+ }
+
+ @Override
+ protected @Nullable String getEvent(ZonedDateTime day) throws EphemerisException {
+ String path = definitionFile.get().getAbsolutePath();
+ try {
+ return ephemeris.getBankHolidayName(day, path);
+ } catch (IllegalStateException e) {
+ throw new EphemerisException("Incorrect syntax", ThingStatusDetail.NONE);
+ } catch (FileNotFoundException e) {
+ throw new EphemerisException("File is absent: " + path, ThingStatusDetail.CONFIGURATION_ERROR);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/DaysetHandler.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/DaysetHandler.java
new file mode 100644
index 0000000000000..343a31adfe57e
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/DaysetHandler.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.handler;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.ephemeris.internal.EphemerisException;
+import org.openhab.binding.ephemeris.internal.configuration.DaysetConfiguration;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link DaysetHandler} delivers system default dayset data.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class DaysetHandler extends BaseEphemerisHandler {
+ private String dayset = "";
+
+ public DaysetHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
+ super(thing, ephemerisManager, zoneId);
+ }
+
+ @Override
+ public void initialize() {
+ DaysetConfiguration config = getConfigAs(DaysetConfiguration.class);
+ dayset = config.name;
+ super.initialize();
+ }
+
+ @Override
+ protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
+ updateState(CHANNEL_TODAY, OnOffType.from(ephemeris.isInDayset(dayset, today)));
+ updateState(CHANNEL_TOMORROW, OnOffType.from(ephemeris.isInDayset(dayset, today.plusDays(1))));
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/HolidayHandler.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/HolidayHandler.java
new file mode 100644
index 0000000000000..83e546e10ec50
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/HolidayHandler.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.handler;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ephemeris.internal.EphemerisException;
+import org.openhab.binding.ephemeris.internal.providers.EphemerisDescriptionProvider;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.StateOption;
+
+/**
+ * The {@link HolidayHandler} delivers system default Holidays data.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class HolidayHandler extends JollydayHandler {
+
+ public HolidayHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId,
+ EphemerisDescriptionProvider descriptionProvider) {
+ super(thing, ephemerisManager, zoneId);
+
+ // Search all holidays in the coming year, using a map to avoid duplicates
+ Map events = new HashMap<>();
+ ZonedDateTime now = ZonedDateTime.now();
+ // Scans 13 monthes to be sure to catch mobile holidays
+ for (int offset = 0; offset < 398; offset++) {
+ String event = getEvent(now.plusDays(offset));
+ if (event != null) {
+ String description = ephemeris.getHolidayDescription(event);
+ events.put(event, new StateOption(event, description == null ? event : description));
+ }
+ }
+
+ // Set descriptions for these events
+ List stateOptions = events.values().stream().toList();
+ descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_CURRENT_EVENT), stateOptions);
+ descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_NEXT_EVENT), stateOptions);
+ }
+
+ @Override
+ protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
+ updateState(CHANNEL_HOLIDAY_TODAY, OnOffType.from(getEvent(today) != null));
+ updateState(CHANNEL_HOLIDAY_TOMORROW, OnOffType.from(getEvent(today.plusDays(1)) != null));
+ super.internalUpdate(today);
+ }
+
+ @Override
+ protected @Nullable String getEvent(ZonedDateTime day) {
+ return ephemeris.getBankHolidayName(day);
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/JollydayHandler.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/JollydayHandler.java
new file mode 100644
index 0000000000000..8f5cee527778d
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/JollydayHandler.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.handler;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.ephemeris.internal.EphemerisException;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link JollydayHandler} handles common parts for Jollyday file based events
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public abstract class JollydayHandler extends BaseEphemerisHandler {
+
+ public JollydayHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
+ super(thing, ephemerisManager, zoneId);
+ }
+
+ @Override
+ protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
+ String todayEvent = getEvent(today);
+ updateState(CHANNEL_CURRENT_EVENT, toStringType(todayEvent));
+
+ String nextEvent = null;
+ ZonedDateTime nextDay = today;
+
+ for (int offset = 1; offset < 366 && (nextEvent == null || nextEvent.isEmpty()); offset++) {
+ nextDay = today.plusDays(offset);
+ nextEvent = getEvent(nextDay);
+ }
+
+ updateState(CHANNEL_NEXT_EVENT, toStringType(nextEvent));
+ updateState(CHANNEL_NEXT_REMAINING,
+ nextEvent != null ? new QuantityType<>(Duration.between(today, nextDay).toDays(), Units.DAY)
+ : UnDefType.UNDEF);
+ updateState(CHANNEL_NEXT_START, nextEvent != null ? new DateTimeType(nextDay) : UnDefType.UNDEF);
+ }
+
+ protected abstract @Nullable String getEvent(ZonedDateTime day) throws EphemerisException;
+
+ protected State toStringType(@Nullable String event) {
+ return event == null || event.isEmpty() ? UnDefType.NULL : new StringType(event);
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/WeekendHandler.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/WeekendHandler.java
new file mode 100644
index 0000000000000..a2a5aea2d48ee
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/handler/WeekendHandler.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.handler;
+
+import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.ephemeris.internal.EphemerisException;
+import org.openhab.core.ephemeris.EphemerisManager;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link WeekendHandler} delivers system default Weekend data.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class WeekendHandler extends BaseEphemerisHandler {
+
+ public WeekendHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
+ super(thing, ephemerisManager, zoneId);
+ }
+
+ @Override
+ protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
+ updateState(CHANNEL_TODAY, OnOffType.from(ephemeris.isWeekend(today)));
+ updateState(CHANNEL_TOMORROW, OnOffType.from(ephemeris.isWeekend(today.plusDays(1))));
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/providers/EphemerisDescriptionProvider.java b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/providers/EphemerisDescriptionProvider.java
new file mode 100644
index 0000000000000..f249faa739b97
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/java/org/openhab/binding/ephemeris/internal/providers/EphemerisDescriptionProvider.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ephemeris.internal.providers;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Dynamic provider of state options while leaving other state description fields as original.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, EphemerisDescriptionProvider.class })
+@NonNullByDefault
+public class EphemerisDescriptionProvider extends BaseDynamicStateDescriptionProvider {
+
+ @Activate
+ public EphemerisDescriptionProvider(final @Reference EventPublisher eventPublisher, //
+ final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
+ final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+ this.eventPublisher = eventPublisher;
+ this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+ this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
+ }
+}
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 0000000000000..a4173aa28456f
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,11 @@
+
+
+
+ binding
+ Ephemeris Binding
+ The Ephemeris Binding makes Ephemeris core actions accessible to Items
+ none
+
+
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/i18n/ephemeris.properties b/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/i18n/ephemeris.properties
new file mode 100644
index 0000000000000..d64ac7268dfba
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/i18n/ephemeris.properties
@@ -0,0 +1,86 @@
+# add-on
+
+addon.ephemeris.name = Ephemeris Binding
+addon.ephemeris.description = The Ephemeris Binding makes Ephemeris core actions accessible to Items
+
+# thing types
+
+thing-type.ephemeris.custom.label = Custom Jollyday File
+thing-type.ephemeris.custom.description = Events defined in a Jollyday file
+thing-type.ephemeris.custom.channel.event-today.label = Event Today
+thing-type.ephemeris.custom.channel.event-today.description = Set to ON if an event exists today
+thing-type.ephemeris.custom.channel.event-tomorrow.label = Event Tomorrow
+thing-type.ephemeris.custom.channel.event-tomorrow.description = Set to ON if an event exists tomorrow
+thing-type.ephemeris.dayset.label = Dayset
+thing-type.ephemeris.dayset.description = Events based on a given dayset
+thing-type.ephemeris.dayset.channel.today.label = Today in Dayset
+thing-type.ephemeris.dayset.channel.today.description = Set to ON if today is in the given dayset, OFF in the other case
+thing-type.ephemeris.dayset.channel.tomorrow.label = Tomorrow in Dayset
+thing-type.ephemeris.dayset.channel.tomorrow.description = Set to ON if tomorrow is in the given dayset, OFF in the other case
+thing-type.ephemeris.holiday.label = Ephemeris Holidays
+thing-type.ephemeris.holiday.description = Holidays based on system default Ephemeris configuration
+thing-type.ephemeris.holiday.channel.holiday-today.label = Holiday Today
+thing-type.ephemeris.holiday.channel.holiday-today.description = Set to ON if today is a holiday
+thing-type.ephemeris.holiday.channel.holiday-tomorrow.label = Holiday Tomorrow
+thing-type.ephemeris.holiday.channel.holiday-tomorrow.description = Set to ON if tomorrow is a holiday
+thing-type.ephemeris.holiday.channel.next-title.description = Name of the next coming holiday
+thing-type.ephemeris.holiday.channel.title-today.description = Name of today's holiday if any, NULL otherwise
+thing-type.ephemeris.weekend.label = Weekend
+thing-type.ephemeris.weekend.description = Events based on the system default week-end dayset
+thing-type.ephemeris.weekend.channel.today.label = Weekend Today
+thing-type.ephemeris.weekend.channel.today.description = Set to ON if today is a weekend day, OFF in the other case
+thing-type.ephemeris.weekend.channel.tomorrow.label = Weekend Tomorrow
+thing-type.ephemeris.weekend.channel.tomorrow.description = Set to ON if tomorrow is a weekend day, OFF in the other case
+
+# thing types config
+
+thing-type.config.ephemeris.custom.fileName.label = File Name
+thing-type.config.ephemeris.custom.fileName.description = Name of a Jollyday XML file in the configuration folder.
+thing-type.config.ephemeris.dayset.name.label = Name
+thing-type.config.ephemeris.dayset.name.description = Name of the dayset.
+
+# channel types
+
+channel-type.ephemeris.days-remaining.label = Remaining Days
+channel-type.ephemeris.days-remaining.description = Remaining days until next event
+channel-type.ephemeris.event-current-title.label = Current Event Title
+channel-type.ephemeris.event-current-title.description = Title of the currently present event
+channel-type.ephemeris.event-next-start.label = Next Event Start
+channel-type.ephemeris.event-next-start.description = Start date of the next coming event
+channel-type.ephemeris.event-next-start.state.pattern = %1$tY-%1$tm-%1$td
+channel-type.ephemeris.event-next-title.label = Next Event Title
+channel-type.ephemeris.event-next-title.description = Title of the next starting event
+channel-type.ephemeris.in-dayset.label = In Dayset
+
+# channel types
+
+channel-type.ephemeris.in-dayset.description = Set to ON if the day is in the dayset, OFF in the other case
+
+# thing types
+
+thing-type.ephemeris.file.label = Ephemeris File
+thing-type.ephemeris.file.description = Events defined in a Jollyday file
+thing-type.ephemeris.file.channel.event-today.label = Event Today
+thing-type.ephemeris.file.channel.event-today.description = Set to ON if an event exists today
+thing-type.ephemeris.file.channel.event-tomorrow.label = Event Tomorrow
+thing-type.ephemeris.file.channel.event-tomorrow.description = Set to ON if an event exists tomorrow
+
+# thing types config
+
+thing-type.config.ephemeris.file.fileName.label = File Name
+thing-type.config.ephemeris.file.fileName.description = Name of a Jollyday XML file in the binding folder.
+
+# channel types
+
+channel-type.ephemeris.remaining-days.label = Remaining Days
+channel-type.ephemeris.remaining-days.description = Days until next event
+
+# thing types
+
+thing-type.ephemeris.default.label = Ephemeris
+thing-type.ephemeris.default.description = Events based on system default Ephemeris configuration
+
+# discovery result
+
+discovery.ephemeris.holiday.local.label = Local Holiday
+discovery.ephemeris.weekend.local.label = Local Weekend
diff --git a/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..b0bb17f433029
--- /dev/null
+++ b/bundles/org.openhab.binding.ephemeris/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+ Events defined in a Jollyday file
+
+
+
+
+
+ Set to ON if an event exists today
+
+
+
+ Set to ON if an event exists tomorrow
+
+
+
+
+
+
+
+
+
+ Name of a Jollyday XML file in the configuration folder.
+
+
+
+
+
+
+
+ Holidays based on system default Ephemeris configuration
+
+
+
+ Name of today's holiday if any, NULL otherwise
+
+
+
+ Set to ON if today is a holiday
+
+
+
+ Set to ON if tomorrow is a holiday
+
+
+ Name of the next coming holiday
+
+
+
+
+
+
+
+
+ Events based on a given dayset
+
+
+
+
+ Set to ON if today is in the given dayset, OFF in the other case
+
+
+
+ Set to ON if tomorrow is in the given dayset, OFF in the other case
+
+
+
+
+
+
+ Name of the dayset.
+
+
+
+
+
+
+
+ Events based on the system default weekend dayset
+
+
+
+
+ Set to ON if today is a weekend day, OFF in the other case
+
+
+
+ Set to ON if tomorrow is a weekend day, OFF in the other case
+
+
+
+
+
+ Switch
+
+
+
+
+
+ String
+
+ Title of the currently present event
+
+
+
+
+ String
+
+ Title of the next starting event
+
+
+
+
+ DateTime
+
+ Start date of the next coming event
+
+
+
+
+ Number:Time
+
+ Remaining days until next event
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 2c9f08d17a928..4dd01018775dd 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -134,6 +134,7 @@
org.openhab.binding.enoceanorg.openhab.binding.enphaseorg.openhab.binding.enturno
+ org.openhab.binding.ephemerisorg.openhab.binding.epsonprojectororg.openhab.binding.etherrainorg.openhab.binding.evcc