2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.astro.internal.util;
15 import java.util.Calendar;
16 import java.util.regex.Pattern;
18 import org.openhab.binding.astro.internal.config.AstroChannelConfig;
19 import org.openhab.binding.astro.internal.model.Range;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
24 * Common used DateTime functions.
26 * @author Gerhard Riegler - Initial contribution
28 public class DateTimeUtils {
29 private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeUtils.class);
30 private static final Pattern HHMM_PATTERN = Pattern.compile("^([0-1][0-9]|2[0-3])(:[0-5][0-9])$");
32 private static final double J1970 = 2440588.0;
33 private static final double MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
36 private DateTimeUtils() {
37 throw new IllegalAccessError("Non-instantiable");
41 * Truncates the time from the calendar object.
43 public static Calendar truncateToSecond(Calendar calendar) {
44 Calendar cal = (Calendar) calendar.clone();
45 cal.set(Calendar.MILLISECOND, 0);
50 * Truncates the time from the calendar object.
52 private static Calendar truncateToMinute(Calendar calendar) {
53 Calendar cal = truncateToSecond(calendar);
54 cal.set(Calendar.SECOND, 0);
59 * Truncates the time from the calendar object.
61 public static Calendar truncateToMidnight(Calendar calendar) {
62 Calendar cal = truncateToMinute(calendar);
63 cal.set(Calendar.HOUR_OF_DAY, 0);
64 cal.set(Calendar.MINUTE, 0);
69 * Creates a Range object within the specified months and days. The start
70 * time is midnight, the end time is end of the day.
72 public static Range getRange(int startYear, int startMonth, int startDay, int endYear, int endMonth, int endDay) {
73 Calendar start = Calendar.getInstance();
74 start.set(Calendar.YEAR, startYear);
75 start.set(Calendar.MONTH, startMonth);
76 start.set(Calendar.DAY_OF_MONTH, startDay);
77 start = truncateToMidnight(start);
79 Calendar end = Calendar.getInstance();
80 end.set(Calendar.YEAR, endYear);
81 end.set(Calendar.MONTH, endMonth);
82 end.set(Calendar.DAY_OF_MONTH, endDay);
83 end.set(Calendar.HOUR_OF_DAY, 23);
84 end.set(Calendar.MINUTE, 59);
85 end.set(Calendar.SECOND, 59);
86 end.set(Calendar.MILLISECOND, 999);
88 return new Range(start, end);
92 * Returns a calendar object from a julian date.
94 public static Calendar toCalendar(double julianDate) {
95 if (Double.compare(julianDate, Double.NaN) == 0 || julianDate == 0) {
98 long millis = (long) ((julianDate + 0.5 - J1970) * MILLISECONDS_PER_DAY);
99 Calendar cal = Calendar.getInstance();
100 cal.setTimeInMillis(millis);
101 int second = cal.get(Calendar.SECOND);
103 cal.add(Calendar.MINUTE, 1);
105 return truncateToMinute(cal);
109 * Returns the julian date from the calendar object.
111 public static double dateToJulianDate(Calendar calendar) {
112 return calendar.getTimeInMillis() / MILLISECONDS_PER_DAY - 0.5 + J1970;
116 * Returns the midnight julian date from the calendar object.
118 public static double midnightDateToJulianDate(Calendar calendar) {
119 return dateToJulianDate(truncateToMidnight(calendar));
123 * Returns the end of day from the calendar object.
125 public static Calendar endOfDayDate(Calendar calendar) {
126 Calendar cal = truncateToMidnight(calendar);
127 cal.add(Calendar.DATE, 1);
128 cal.add(Calendar.MILLISECOND, -1);
133 * Returns the end of day julian date from the calendar object.
135 public static double endOfDayDateToJulianDate(Calendar calendar) {
136 return dateToJulianDate(endOfDayDate(calendar));
140 * Returns the year of the calendar object as a decimal value.
142 public static double getDecimalYear(Calendar calendar) {
143 return calendar.get(Calendar.YEAR)
144 + (double) calendar.get(Calendar.DAY_OF_YEAR) / calendar.getActualMaximum(Calendar.DAY_OF_YEAR);
148 * Converts the time (hour.minute) to a calendar object.
150 public static Calendar timeToCalendar(Calendar calendar, double time) {
154 Calendar cal = (Calendar) calendar.clone();
158 cal.add(Calendar.DAY_OF_MONTH, 1);
161 minute = (int) ((time * 100) - (hour * 100));
163 cal.set(Calendar.HOUR_OF_DAY, hour);
164 cal.set(Calendar.MINUTE, minute);
165 return truncateToMinute(cal);
169 * Returns true, if two calendar objects are on the same day ignoring time.
171 public static boolean isSameDay(Calendar cal1, Calendar cal2) {
172 return cal1 != null && cal2 != null && cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
173 && cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
174 && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
178 * Returns the next Calendar from today.
180 public static Calendar getNextFromToday(Calendar... calendars) {
181 return getNext(Calendar.getInstance(), calendars);
184 static Calendar getNext(Calendar now, Calendar... calendars) {
185 Calendar next = null;
186 Calendar firstSeasonOfYear = null;
187 for (Calendar calendar : calendars) {
188 if (firstSeasonOfYear == null || calendar.before(firstSeasonOfYear)) {
189 firstSeasonOfYear = calendar;
191 if (calendar.after(now) && (next == null || calendar.before(next))) {
196 final Calendar nextYearSeason = (Calendar) firstSeasonOfYear.clone();
198 nextYearSeason.add(Calendar.YEAR, 1);
199 return nextYearSeason;
206 * Returns true, if cal1 is greater or equal than cal2, ignoring seconds.
208 public static boolean isTimeGreaterEquals(Calendar cal1, Calendar cal2) {
209 Calendar truncCal1 = truncateToMinute(cal1);
210 Calendar truncCal2 = truncateToMinute(cal2);
211 return truncCal1.getTimeInMillis() >= truncCal2.getTimeInMillis();
214 public static Calendar getAdjustedEarliest(Calendar cal, AstroChannelConfig config) {
215 return adjustTime(cal, getMinutesFromTime(config.earliest));
218 public static Calendar getAdjustedLatest(Calendar cal, AstroChannelConfig config) {
219 return adjustTime(cal, getMinutesFromTime(config.latest));
223 * Applies the config to the given calendar.
225 public static Calendar applyConfig(Calendar cal, AstroChannelConfig config) {
227 if (config.offset != 0) {
228 Calendar cOffset = Calendar.getInstance();
229 cOffset.setTime(cCal.getTime());
230 cOffset.add(Calendar.MINUTE, config.offset);
234 Calendar cEarliest = getAdjustedEarliest(cCal, config);
235 if (cCal.before(cEarliest)) {
238 Calendar cLatest = getAdjustedLatest(cCal, config);
239 if (cCal.after(cLatest)) {
246 private static Calendar adjustTime(Calendar cal, int minutes) {
248 Calendar cTime = truncateToMidnight(cal);
249 cTime.add(Calendar.MINUTE, minutes);
255 public static Calendar createCalendarForToday(int hour, int minute) {
256 return DateTimeUtils.adjustTime(Calendar.getInstance(), hour * 60 + minute);
260 * Parses a HH:MM string and returns the minutes.
262 private static int getMinutesFromTime(String configTime) {
263 if (configTime != null) {
264 String time = configTime.trim();
265 if (!time.isEmpty()) {
267 if (!HHMM_PATTERN.matcher(time).matches()) {
268 throw new NumberFormatException();
270 String[] elements = time.split(":");
271 int hour = Integer.parseInt(elements[0]);
272 int minutes = Integer.parseInt(elements[1]);
273 return (hour * 60) + minutes;
275 } catch (NumberFormatException ex) {
277 "Can not parse astro channel configuration '{}' to hour and minutes, use pattern hh:mm, ignoring!",