DaylightSavingUtils.java

  1. /*
  2.  * GovWay - A customizable API Gateway
  3.  * https://govway.org
  4.  *
  5.  * Copyright (c) 2005-2025 Link.it srl (https://link.it).
  6.  *
  7.  * This program is free software: you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License version 3, as published by
  9.  * the Free Software Foundation.
  10.  *
  11.  * This program is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  *
  19.  */

  20. package org.openspcoop2.utils.date;

  21. import java.time.*;
  22. import java.time.format.DateTimeFormatter;
  23. import java.time.zone.ZoneOffsetTransition;
  24. import java.time.zone.ZoneRules;

  25. /**    
  26.  * DaylightSavingUtils
  27.  *
  28.  * @author Poli Andrea (poli@link.it)
  29.  * @author $Author$
  30.  * @version $Rev$, $Date$
  31.  */
  32. public class DaylightSavingUtils {
  33.    
  34.     /**
  35.      * Ora Solare (CET - Central European Time): L'ora solare è il tempo standard utilizzato durante l'inverno. In Europa, è UTC+1, il che significa che è un'ora avanti rispetto al Coordinated Universal Time (UTC). L'ora solare è usata dal tardo autunno all'inizio della primavera, solitamente da fine ottobre a fine marzo.

  36.        Ora Legale (CEST - Central European Summer Time): L'ora legale viene utilizzata durante l'estate per risparmiare energia, spostando le lancette avanti di un'ora. In Europa, è UTC+2, ovvero due ore avanti rispetto a UTC. L'ora legale è in vigore da fine marzo a fine ottobre.

  37.        Passaggio da Ora Legale a Ora Solare: In autunno (solitamente l'ultima domenica di ottobre), l'orologio viene spostato indietro di un'ora alle 03:00 AM CEST, tornando alle 02:00 AM CET (ora solare).
  38.        Passaggio da Ora Solare a Ora Legale: In primavera (solitamente l'ultima domenica di marzo), l'orologio viene spostato avanti di un'ora alle 02:00 AM CET, diventando le 03:00 AM CEST (ora legale).
  39.      */
  40.    
  41.     private DaylightSavingUtils() {}

  42.     // Formato per il parsing delle date con offset
  43.     private static final String OFFSET_FORMAT = "yyyy-MM-dd HH:mm:ssXXX";
  44.     public static String getOffsetFormat() {
  45.         return OFFSET_FORMAT;
  46.     }
  47.     private static final DateTimeFormatter OFFSET_FORMATTER = DateTimeFormatter.ofPattern(OFFSET_FORMAT);
  48.     public static DateTimeFormatter getOffsetFormatter() {
  49.         return OFFSET_FORMATTER;
  50.     }

  51.     // Formato per il parsing delle date senza offset
  52.     private static final String BASIC_FORMAT = "yyyy-MM-dd HH:mm:ss";
  53.     public static String getBasicFormat() {
  54.         return BASIC_FORMAT;
  55.     }
  56.     private static final DateTimeFormatter BASIC_FORMATTER = DateTimeFormatter.ofPattern(BASIC_FORMAT);
  57.     public static DateTimeFormatter getBasicFormatter() {
  58.         return BASIC_FORMATTER;
  59.     }

  60.     // Zona di riferimento, ad esempio Europe/Rome
  61.     /**private static final ZoneId ZONE_ID = ZoneId.of("Europe/Rome");*/
  62.     private static final ZoneId ZONE_ID_DEFAULT = ZoneId.systemDefault();
  63.     public static ZoneId getZoneIdDefault() {
  64.         return ZONE_ID_DEFAULT;
  65.     }
  66.    
  67.     /**
  68.      * Calcola quanti minuti mancano al prossimo cambio di ora legale o solare
  69.      * per una data con offset di fuso orario.
  70.      *
  71.      * @param inputDate La data in formato "yyyy-MM-dd HH:mm:ss+01"
  72.      * @return Minuti mancanti al cambio di ora
  73.      */
  74.     public static long minutesUntilNextTransition(String inputDate) {
  75.         return minutesUntilNextTransition(inputDate, ZONE_ID_DEFAULT);
  76.     }
  77.     public static long minutesUntilNextTransition(String inputDate, ZoneId zoneId) {
  78.         return minutesUntilNextTransition(inputDate, zoneId, null, null);
  79.     }
  80.     public static long minutesUntilNextTransition(String inputDate, String format) {
  81.         return minutesUntilNextTransition(inputDate, ZONE_ID_DEFAULT, format);
  82.     }
  83.     public static long minutesUntilNextTransition(String inputDate, ZoneId zoneId, String format) {
  84.         return minutesUntilNextTransition(inputDate, zoneId, format, null);
  85.     }
  86.     public static long minutesUntilNextTransition(String inputDate, DateTimeFormatter formatter) {
  87.         return minutesUntilNextTransition(inputDate, ZONE_ID_DEFAULT, formatter);
  88.     }
  89.     public static long minutesUntilNextTransition(String inputDate, ZoneId zoneId, DateTimeFormatter formatter) {
  90.         return minutesUntilNextTransition(inputDate, zoneId, null, formatter);
  91.     }
  92.     private static long minutesUntilNextTransition(String inputDate, ZoneId zoneId, String format, DateTimeFormatter formatter) {
  93.         OffsetDateTime offsetDateTime = formatter!=null ? parseOffsetDateTime(inputDate, formatter) : parseOffsetDateTime(inputDate, format);
  94.         ZonedDateTime zonedDateTime = offsetDateTime.atZoneSameInstant(zoneId);

  95.         ZoneRules zoneRules = zoneId.getRules();
  96.         ZoneOffsetTransition nextTransition = zoneRules.nextTransition(zonedDateTime.toInstant());

  97.         if (nextTransition != null) {
  98.             ZonedDateTime transitionDateTime = ZonedDateTime.ofInstant(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore(), zoneId);
  99.             return Duration.between(zonedDateTime, transitionDateTime).toMinutes();
  100.         }
  101.         return -1; // Nessuna transizione trovata
  102.     }

  103.     /**
  104.      * Calcola quanti minuti mancano al prossimo cambio di ora legale o solare
  105.      * per una data senza offset di fuso orario.
  106.      *
  107.      * @param inputDate La data in formato "yyyy-MM-dd HH:mm:ss"
  108.      * @return Minuti mancanti al cambio di ora
  109.      */
  110.     public static long minutesUntilNextTransitionWithoutOffset(String inputDate) {
  111.         return minutesUntilNextTransitionWithoutOffset(inputDate, ZONE_ID_DEFAULT);
  112.     }
  113.     public static long minutesUntilNextTransitionWithoutOffset(String inputDate, ZoneId zoneId) {
  114.         return minutesUntilNextTransitionWithoutOffset(inputDate, zoneId, null, null);
  115.     }
  116.     public static long minutesUntilNextTransitionWithoutOffset(String inputDate, String format) {
  117.         return minutesUntilNextTransitionWithoutOffset(inputDate, ZONE_ID_DEFAULT, format);
  118.     }
  119.     public static long minutesUntilNextTransitionWithoutOffset(String inputDate, ZoneId zoneId, String format) {
  120.         return minutesUntilNextTransitionWithoutOffset(inputDate, zoneId, format, null);
  121.     }
  122.     public static long minutesUntilNextTransitionWithoutOffset(String inputDate, DateTimeFormatter formatter) {
  123.         return minutesUntilNextTransitionWithoutOffset(inputDate, ZONE_ID_DEFAULT, formatter);
  124.     }
  125.     public static long minutesUntilNextTransitionWithoutOffset(String inputDate, ZoneId zoneId, DateTimeFormatter formatter) {
  126.         return minutesUntilNextTransitionWithoutOffset(inputDate, zoneId, null, formatter);
  127.     }
  128.     private static long minutesUntilNextTransitionWithoutOffset(String inputDate, ZoneId zoneId, String format, DateTimeFormatter formatter) {
  129.         LocalDateTime localDateTime = formatter!=null ? parseLocalDateTime(inputDate, formatter) : parseLocalDateTime(inputDate, format);
  130.         ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);

  131.         ZoneRules zoneRules = zoneId.getRules();
  132.         ZoneOffsetTransition nextTransition = zoneRules.nextTransition(zonedDateTime.toInstant());

  133.         if (nextTransition != null) {
  134.             ZonedDateTime transitionDateTime = ZonedDateTime.ofInstant(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore(), zoneId);
  135.             return Duration.between(zonedDateTime, transitionDateTime).toMinutes();
  136.         }
  137.         return -1; // Nessuna transizione trovata
  138.     }
  139.    
  140.    
  141.     /**
  142.      * Determina se una certa data si trova in ora legale o ora solare.
  143.      *
  144.      * @param inputDate La data in formato "yyyy-MM-dd HH:mm:ss+01"
  145.      * @return "ora legale" o "ora solare" a seconda della data
  146.      */
  147.     public static TimeType getTimeType(String inputDate) {
  148.         return getTimeType(inputDate, ZONE_ID_DEFAULT);
  149.     }
  150.     public static TimeType getTimeType(String inputDate, ZoneId zoneId) {
  151.         return getTimeType(inputDate, zoneId, null, null);
  152.     }
  153.     public static TimeType getTimeType(String inputDate, String format) {
  154.         return getTimeType(inputDate, ZONE_ID_DEFAULT, format);
  155.     }
  156.     public static TimeType getTimeType(String inputDate, ZoneId zoneId, String format) {
  157.         return getTimeType(inputDate, zoneId, format, null);
  158.     }
  159.     public static TimeType getTimeType(String inputDate, DateTimeFormatter formatter) {
  160.         return getTimeType(inputDate, ZONE_ID_DEFAULT, formatter);
  161.     }
  162.     public static TimeType getTimeType(String inputDate, ZoneId zoneId, DateTimeFormatter formatter) {
  163.         return getTimeType(inputDate, zoneId, null, formatter);
  164.     }
  165.     private static TimeType getTimeType(String inputDate, ZoneId zoneId, String format, DateTimeFormatter formatter) {
  166.         OffsetDateTime offsetDateTime = formatter!=null ? parseOffsetDateTime(inputDate, formatter) : parseOffsetDateTime(inputDate, format);
  167.         ZonedDateTime zonedDateTime = offsetDateTime.atZoneSameInstant(zoneId);

  168.         boolean isDaylightSavings = zonedDateTime.getZone().getRules().isDaylightSavings(zonedDateTime.toInstant());
  169.         return isDaylightSavings ? TimeType.DAYLIGHT_SAVING_TIME : TimeType.STANDARD_TIME;
  170.     }

  171.     /**
  172.      * Converte una data in formato "yyyy-MM-dd HH:mm:ss+01" in un ZonedDateTime
  173.      * per il fuso orario specificato (es. Europe/Rome).
  174.      *
  175.      * @param inputDate La data in formato "yyyy-MM-dd HH:mm:ss+01"
  176.      * @return ZonedDateTime corrispondente
  177.      */
  178.     public static ZonedDateTime convertToZonedDateTime(String inputDate) {
  179.         return convertToZonedDateTime(inputDate, ZONE_ID_DEFAULT);
  180.     }
  181.     public static ZonedDateTime convertToZonedDateTime(String inputDate, ZoneId zoneId) {
  182.         return convertToZonedDateTime(inputDate, zoneId, null, null);
  183.     }
  184.     public static ZonedDateTime convertToZonedDateTime(String inputDate, String format) {
  185.         return convertToZonedDateTime(inputDate, ZONE_ID_DEFAULT, format);
  186.     }
  187.     public static ZonedDateTime convertToZonedDateTime(String inputDate, ZoneId zoneId, String format) {
  188.         return convertToZonedDateTime(inputDate, zoneId, format, null);
  189.     }
  190.     public static ZonedDateTime convertToZonedDateTime(String inputDate, DateTimeFormatter formatter) {
  191.         return convertToZonedDateTime(inputDate, ZONE_ID_DEFAULT, formatter);
  192.     }
  193.     public static ZonedDateTime convertToZonedDateTime(String inputDate, ZoneId zoneId, DateTimeFormatter formatter) {
  194.         return convertToZonedDateTime(inputDate, zoneId, null, formatter);
  195.     }
  196.     private static ZonedDateTime convertToZonedDateTime(String inputDate, ZoneId zoneId, String format, DateTimeFormatter formatter) {
  197.         OffsetDateTime offsetDateTime = formatter!=null ? parseOffsetDateTime(inputDate, formatter) : parseOffsetDateTime(inputDate, format);
  198.         return offsetDateTime.atZoneSameInstant(zoneId);
  199.     }

  200.    
  201.     /**
  202.      * Determina se una data è nel giorno in cui avviene il cambio tra ora legale e ora solare (o viceversa).
  203.      *
  204.      * @param inputDate La data in formato "yyyy-MM-dd HH:mm:ss+01:00"
  205.      * @return true se la data è nel giorno del cambio di ora, false altrimenti
  206.      */
  207.     public static TimeTransitionType getTimeChangePendingToday(String inputDate) {
  208.         return getTimeChangePendingToday(inputDate, ZONE_ID_DEFAULT);
  209.     }
  210.     public static TimeTransitionType getTimeChangePendingToday(String inputDate, ZoneId zoneId) {
  211.         return getTimeChangePendingToday(inputDate, zoneId, null, null);
  212.     }
  213.     public static TimeTransitionType getTimeChangePendingToday(String inputDate, String format) {
  214.         return getTimeChangePendingToday(inputDate, ZONE_ID_DEFAULT, format);
  215.     }
  216.     public static TimeTransitionType getTimeChangePendingToday(String inputDate, ZoneId zoneId, String format) {
  217.         return getTimeChangePendingToday(inputDate, zoneId, format, null);
  218.     }
  219.     public static TimeTransitionType getTimeChangePendingToday(String inputDate, DateTimeFormatter formatter) {
  220.         return getTimeChangePendingToday(inputDate, ZONE_ID_DEFAULT, formatter);
  221.     }
  222.     public static TimeTransitionType getTimeChangePendingToday(String inputDate, ZoneId zoneId, DateTimeFormatter formatter) {
  223.         return getTimeChangePendingToday(inputDate, zoneId, null, formatter);
  224.     }
  225.     private static TimeTransitionType getTimeChangePendingToday(String inputDate, ZoneId zoneId, String format, DateTimeFormatter formatter) {
  226.         // Parse della stringa in OffsetDateTime
  227.         OffsetDateTime dateTime = formatter!=null ? parseOffsetDateTime(inputDate, formatter) : parseOffsetDateTime(inputDate, format);

  228.         // Ottieni il fuso orario dell'ambiente (ad esempio Europe/Rome)
  229.          ZoneRules zoneRules = zoneId.getRules();

  230.         // Ottieni la transizione successiva
  231.         ZoneOffsetTransition nextTransition = zoneRules.nextTransition(dateTime.toInstant());

  232.         if (nextTransition != null) {
  233.             // Ottieni la data e l'ora del giorno della transizione
  234.             LocalDate transitionDate = nextTransition.getDateTimeBefore().toLocalDate();

  235.             // Verifica se la data fornita è nello stesso giorno della transizione
  236.             if( dateTime.toLocalDate().equals(transitionDate) ) {
  237.                  // Ottieni gli offset prima e dopo la transizione
  238.                 ZoneOffset offsetBefore = nextTransition.getOffsetBefore();
  239.                 ZoneOffset offsetAfter = nextTransition.getOffsetAfter();

  240.                 // Determina il tipo di transizione
  241.                 if (offsetBefore.getTotalSeconds() > offsetAfter.getTotalSeconds()) {
  242.                     return TimeTransitionType.FROM_DAYLIGHT_SAVING_TO_STANDARD_TIME;  // Ora legale a ora solare
  243.                 } else {
  244.                     return TimeTransitionType.FROM_STANDARD_TO_DAYLIGHT_SAVING_TIME;  // Ora solare a ora legale
  245.                 }

  246.             }
  247.         }
  248.        
  249.         return null;  // Nessuna transizione trovata
  250.     }
  251.    
  252.    
  253.     /**
  254.      * Determina se una data è nel giorno in cui avviene il cambio tra ora legale e ora solare (o viceversa) per una data senza offset di fuso orario.
  255.      *
  256.      * @param inputDate La data in formato "yyyy-MM-dd HH:mm:ss"
  257.      * @return true se la data è nel giorno del cambio di ora, false altrimenti
  258.      */
  259.     public static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate) {
  260.         return getTimeChangePendingTodayWithoutOffset(inputDate, ZONE_ID_DEFAULT);
  261.     }
  262.     public static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate, ZoneId zoneId) {
  263.         return getTimeChangePendingTodayWithoutOffset(inputDate, zoneId, null, null);
  264.     }
  265.     public static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate, String format) {
  266.         return getTimeChangePendingTodayWithoutOffset(inputDate, ZONE_ID_DEFAULT, format);
  267.     }
  268.     public static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate, ZoneId zoneId, String format) {
  269.         return getTimeChangePendingTodayWithoutOffset(inputDate, zoneId, format, null);
  270.     }
  271.     public static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate, DateTimeFormatter formatter) {
  272.         return getTimeChangePendingTodayWithoutOffset(inputDate, ZONE_ID_DEFAULT, formatter);
  273.     }
  274.     public static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate, ZoneId zoneId, DateTimeFormatter formatter) {
  275.         return getTimeChangePendingTodayWithoutOffset(inputDate, zoneId, null, formatter);
  276.     }
  277.     private static TimeTransitionType getTimeChangePendingTodayWithoutOffset(String inputDate, ZoneId zoneId, String format, DateTimeFormatter formatter) {
  278.        
  279.         LocalDateTime localDateTime = formatter!=null ? parseLocalDateTime(inputDate, formatter) : parseLocalDateTime(inputDate, format);
  280.         ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);

  281.         ZoneRules zoneRules = zoneId.getRules();
  282.         ZoneOffsetTransition nextTransition = zoneRules.nextTransition(zonedDateTime.toInstant());

  283.         if (nextTransition != null) {
  284.             ZonedDateTime transitionDateTime = ZonedDateTime.ofInstant(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore(), zoneId);
  285.            
  286.               // Verifica se la data fornita è nello stesso giorno della transizione
  287.             if( localDateTime.toLocalDate().equals(transitionDateTime.toLocalDate()) ) {
  288.                  // Ottieni gli offset prima e dopo la transizione
  289.                 ZoneOffset offsetBefore = nextTransition.getOffsetBefore();
  290.                 ZoneOffset offsetAfter = nextTransition.getOffsetAfter();

  291.                 // Determina il tipo di transizione
  292.                 if (offsetBefore.getTotalSeconds() > offsetAfter.getTotalSeconds()) {
  293.                     return TimeTransitionType.FROM_DAYLIGHT_SAVING_TO_STANDARD_TIME;  // Ora legale a ora solare
  294.                 } else {
  295.                     return TimeTransitionType.FROM_STANDARD_TO_DAYLIGHT_SAVING_TIME;  // Ora solare a ora legale
  296.                 }

  297.             }
  298.         }
  299.        
  300.         return null;  // Nessuna transizione trovata
  301.     }
  302.    
  303.    
  304.    
  305.     /** Parser */
  306.    
  307.     public static LocalDateTime parseLocalDateTime(String inputDate) {
  308.         return parseLocalDateTime(inputDate, BASIC_FORMATTER);
  309.     }
  310.     public static LocalDateTime parseLocalDateTime(String inputDate, String format) {
  311.         if(format==null) {
  312.             return parseLocalDateTime(inputDate);
  313.         }
  314.         return parseLocalDateTime(inputDate, DateTimeFormatter.ofPattern(format));
  315.     }
  316.     public static LocalDateTime parseLocalDateTime(String inputDate, DateTimeFormatter formatter) {
  317.         if(formatter==null) {
  318.             return parseLocalDateTime(inputDate);
  319.         }
  320.         return LocalDateTime.parse(inputDate, formatter);
  321.     }
  322.    
  323.     public static OffsetDateTime parseOffsetDateTime(String inputDate) {
  324.         return parseOffsetDateTime(inputDate, OFFSET_FORMATTER);
  325.     }
  326.     public static OffsetDateTime parseOffsetDateTime(String inputDate, String format) {
  327.         if(format==null) {
  328.             return parseOffsetDateTime(inputDate);
  329.         }
  330.         return parseOffsetDateTime(inputDate, DateTimeFormatter.ofPattern(format));
  331.     }
  332.     public static OffsetDateTime parseOffsetDateTime(String inputDate, DateTimeFormatter formatter) {
  333.         if(formatter==null) {
  334.             return parseOffsetDateTime(inputDate);
  335.         }
  336.         if (inputDate.matches(".*[+-]\\d{2}$")) {
  337.             inputDate += ":00";
  338.         }
  339.         return OffsetDateTime.parse(inputDate, formatter);
  340.     }
  341. }