Java tutorial
/** * Copyright 2014-2016 Smart Society Services B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ package com.alliander.osgp.adapter.protocol.iec61850.domain.valueobjects; import java.util.Objects; import org.joda.time.DateTime; import org.joda.time.DateTimeConstants; import org.joda.time.DateTimeZone; /** * Class for handling representations of the start (when DST goes into effect) * or end time (when time goes back to standard time) for Daylight Savings Time * according to time zone specifications in POSIX systems. * <p> * Of the formats specified only the Julian day and the day of week of month * variations are supported. * <p> * This implementation considers any time based offsets in hours to be based on * the locale for "Europe/Amsterdam", unless another timezone is provided * explicitly. * * @see http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html */ public class DaylightSavingTimeTransition { public enum DstTransitionFormat { /** * <dl> * <dt>Jn</dt> * <dd>Julian day, with {@code n} between {@code 1} and {@code 365}. * February 29 is never counted.</dd> * </dl> */ JULIAN_DAY_IGNORING_FEBRUARY_29 { @Override public boolean isValid(final String transition) { if (transition == null || 'J' != transition.charAt(0)) { return false; } final int timeSeparatorPos = transition.indexOf('/'); final int n; try { if (timeSeparatorPos == -1) { n = Integer.parseInt(transition.substring(1)); } else { n = Integer.parseInt(transition.substring(1, timeSeparatorPos)); } } catch (final NumberFormatException nfe) { return false; } if (n < 1 || n > 365) { return false; } if (timeSeparatorPos == -1) { return true; } return transition.length() > timeSeparatorPos && this.isValidTime(transition.substring(timeSeparatorPos + 1)); } @Override public DaylightSavingTimeTransition getDaylightSavingTimeTransition(final DateTime dateTime) { final int dayOfYear = dateTime.getDayOfYear(); final int n; if (dateTime.getMonthOfYear() < DateTimeConstants.MARCH || !dateTime.toGregorianCalendar().isLeapYear(dateTime.getYear())) { n = dayOfYear; } else { /* * Leap year, date on or after February 29th, ignore leap * day by subtracting 1. */ n = dayOfYear - 1; } final int hours = dateTime.getHourOfDay(); final String transition; if (hours == 0) { transition = "J" + n; } else { transition = "J" + n + "/" + hours; } return new DaylightSavingTimeTransition(dateTime.getZone(), transition); } @Override public DateTime getDateTime(final DateTimeZone dateTimeZone, final String transition, final int year) { final int timeSeparatorPos = transition.indexOf('/'); final int n; if (timeSeparatorPos == -1) { n = Integer.parseInt(transition.substring(1)); } else { n = Integer.parseInt(transition.substring(1, timeSeparatorPos)); } final DateTime candidate = new DateTime(year, 1, 1, 0, 0, 0, 0, dateTimeZone).plusDays(n); final boolean subtractOne = !candidate.toGregorianCalendar().isLeapYear(year) || candidate.getMonthOfYear() < DateTimeConstants.MARCH; if (subtractOne) { return candidate.minusDays(1).plusHours(this.getTime(transition)); } return candidate.plusHours(this.getTime(transition)); } }, /** * <dl> * <dt>n</dt> * <dd>Julian day, with {@code n} between {@code 0} and {@code 365}. * February 29 is counted in leap years.</dd> * </dl> */ JULIAN_DAY_COUNTING_FEBRUARY_29 { @Override public boolean isValid(final String transition) { if (transition == null) { return false; } final int timeSeparatorPos = transition.indexOf('/'); final int n; try { if (timeSeparatorPos == -1) { n = Integer.parseInt(transition); } else { n = Integer.parseInt(transition.substring(0, timeSeparatorPos)); } } catch (final NumberFormatException nfe) { return false; } if (n < 0 || n > 365) { return false; } if (timeSeparatorPos == -1) { return true; } return transition.length() > timeSeparatorPos && this.isValidTime(transition.substring(timeSeparatorPos + 1)); } @Override public DaylightSavingTimeTransition getDaylightSavingTimeTransition(final DateTime dateTime) { final int n = dateTime.getDayOfYear() - 1; final int hours = dateTime.getHourOfDay(); final String transition; if (hours == 0) { transition = String.valueOf(n); } else { transition = n + "/" + hours; } return new DaylightSavingTimeTransition(dateTime.getZone(), transition); } @Override public DateTime getDateTime(final DateTimeZone dateTimeZone, final String transition, final int year) { final int timeSeparatorPos = transition.indexOf('/'); final int n; if (timeSeparatorPos == -1) { n = Integer.parseInt(transition); } else { n = Integer.parseInt(transition.substring(0, timeSeparatorPos)); } return new DateTime(year, 1, 1, 0, 0, 0, 0, dateTimeZone).plusDays(n) .plusHours(this.getTime(transition)); } }, /** * <dl> * <dt>Mm.w.d</dt> * <dd>Day {@code d} of week {@code w} of month {@code m}. Day {@code d} * from {@code 0} (Sunday) to {@code 6}. Week {@code w} between * {@code 1} (first week in which day {@code d} occurs) and {@code 5} * (last day {@code d} of the month). Month {@code m} from {@code 1} to * {@code 12}.</dd> * </dl> */ DAY_OF_WEEK_OF_MONTH { @Override public boolean isValid(final String transition) { if (transition == null || 'M' != transition.charAt(0)) { return false; } final int timeSeparatorPos = transition.indexOf('/'); final String mwd; if (timeSeparatorPos == -1) { mwd = transition.substring(1); } else { mwd = transition.substring(1, timeSeparatorPos); } final String[] mwdParts = mwd.split("\\."); if (mwdParts.length != 3) { return false; } final int m; final int w; final int d; try { m = Integer.parseInt(mwdParts[0]); w = Integer.parseInt(mwdParts[1]); d = Integer.parseInt(mwdParts[2]); } catch (final NumberFormatException nfe) { return false; } if (m < 1 || m > 12 || w < 1 || w > 5 || d < 0 || d > 6) { return false; } if (timeSeparatorPos == -1) { return true; } return transition.length() > timeSeparatorPos && this.isValidTime(transition.substring(timeSeparatorPos + 1)); } @Override public DaylightSavingTimeTransition getDaylightSavingTimeTransition(final DateTime dateTime) { final int m = dateTime.getMonthOfYear(); final boolean lastDayOfWeekForTheMonth = dateTime.plusDays(7).getMonthOfYear() > m; final int w = lastDayOfWeekForTheMonth ? 5 : 1 + ((dateTime.getDayOfMonth() - 1) / 7); final int d = dateTime.getDayOfWeek() % 7; final int time = dateTime.getHourOfDay(); final String transition = "M" + m + "." + w + "." + d + (time == 0 ? "" : "/" + time); return new DaylightSavingTimeTransition(dateTime.getZone(), transition); } @Override public DateTime getDateTime(final DateTimeZone dateTimeZone, final String transition, final int year) { final int dotAfterM = transition.indexOf('.', 1); final int m = Integer.parseInt(transition.substring(1, dotAfterM)); final int dotAfterW = transition.indexOf('.', dotAfterM + 1); final int w = Integer.parseInt(transition.substring(dotAfterM + 1, dotAfterW)); final int timeSeparatorPos = transition.indexOf('/'); final int d; if (timeSeparatorPos == -1) { d = Integer.parseInt(transition.substring(dotAfterW + 1)); } else { d = Integer.parseInt(transition.substring(dotAfterW + 1, timeSeparatorPos)); } final int dayOfWeek = d == 0 ? DateTimeConstants.SUNDAY : d; final int startAtDate = w == 5 ? 22 : (w - 1) * 7 + 1; final DateTime firstAttempt = new DateTime(year, m, startAtDate, 0, 0, 0, 0, dateTimeZone); final int dayDiff = dayOfWeek - firstAttempt.getDayOfWeek(); final DateTime secondAttempt; if (dayDiff == 0) { secondAttempt = firstAttempt; } else { secondAttempt = firstAttempt.plusDays(dayDiff > 0 ? dayDiff : 7 + dayDiff); } if (w < 5) { return secondAttempt.plusHours(this.getTime(transition)); } final DateTime thirdAttempt = secondAttempt.plusDays(7); if (thirdAttempt.getMonthOfYear() > secondAttempt.getMonthOfYear()) { return secondAttempt.plusHours(this.getTime(transition)); } return thirdAttempt.plusHours(this.getTime(transition)); } }; public abstract boolean isValid(String transition); public abstract DaylightSavingTimeTransition getDaylightSavingTimeTransition(DateTime dateTime); public abstract DateTime getDateTime(DateTimeZone dateTimeZone, String transition, int year); public boolean isValidTime(final String time) { if (time == null) { return false; } final int hours; if (time.indexOf(':') == -1) { try { hours = Integer.parseInt(time); } catch (final NumberFormatException nfe) { return false; } } else { final String[] timeParts = time.split(":"); try { if (timeParts.length > 3) { return false; } hours = Integer.parseInt(timeParts[0]); for (int i = 1; i < timeParts.length; i++) { final int minutesOrSeconds = Integer.parseInt(timeParts[i]); if (minutesOrSeconds < 0 || minutesOrSeconds > 59) { return false; } } } catch (final NumberFormatException nfe) { return false; } } return hours >= -167 && hours <= 167; } public int getTime(final String transition) { final int timeSeparatorPos = transition.indexOf('/'); if (timeSeparatorPos == -1) { return 0; } final int hourSeparatorPos = transition.indexOf(':', timeSeparatorPos + 1); if (hourSeparatorPos == -1) { return Integer.parseInt(transition.substring(timeSeparatorPos + 1)); } return Integer.parseInt(transition.substring(timeSeparatorPos + 1, hourSeparatorPos)); } } private static final DateTimeZone TIME_ZONE_AMSTERDAM = DateTimeZone.forID("Europe/Amsterdam"); private final DstTransitionFormat format; private final String transition; private final DateTimeZone dateTimeZone; public static DaylightSavingTimeTransition forDateTimeAccordingToFormat(final DateTime dateTime, final DstTransitionFormat format) { Objects.requireNonNull(dateTime, "dateTime must not be null"); Objects.requireNonNull(format, "format must not be null"); return format.getDaylightSavingTimeTransition(dateTime); } /** * Creates a {@link DaylightSavingTimeTransition} for the given textual * representation. The {@code transition} must be a {@link String} according * to one of the formats described by {@link DstTransitionFormat}. * * @param dateTimeZone * the time zone used for local time in hours * @param transition * the formatted representation of when Daylight Saving Time goes * into effect or when the change is made back to standard time. */ public DaylightSavingTimeTransition(final DateTimeZone dateTimeZone, final String transition) { Objects.requireNonNull(dateTimeZone, "dateTimeZone must not be null"); Objects.requireNonNull(transition, "transition must not be null"); this.dateTimeZone = dateTimeZone; if (transition.startsWith("J")) { this.format = DstTransitionFormat.JULIAN_DAY_IGNORING_FEBRUARY_29; } else if (transition.startsWith("M")) { this.format = DstTransitionFormat.DAY_OF_WEEK_OF_MONTH; } else { this.format = DstTransitionFormat.JULIAN_DAY_COUNTING_FEBRUARY_29; } if (this.format.isValid(transition)) { this.transition = transition; } else { throw new IllegalArgumentException( "Transition is not a supported textual representation: " + transition); } } /** * Creates a {@link DaylightSavingTimeTransition} for time zone * "Europe/Amsterdam". * * @see #DaylightSavingTimeTransition(DateTimeZone, String) */ public DaylightSavingTimeTransition(final String transition) { this(TIME_ZONE_AMSTERDAM, transition); } public DateTime getDateTimeForYear(final int year) { return this.format.getDateTime(this.dateTimeZone, this.transition, year); } public DateTime getDateTimeForCurrentYear() { return this.getDateTimeForYear(DateTime.now(this.dateTimeZone).getYear()); } public DateTime getDateTimeForNextTransition() { final DateTime now = DateTime.now(this.dateTimeZone); final DateTime thisYearsTransition = this.getDateTimeForYear(now.getYear()); if (now.isAfter(thisYearsTransition)) { return this.getDateTimeForYear(now.getYear() + 1); } return thisYearsTransition; } public DstTransitionFormat getFormat() { return this.format; } public String getTransition() { return this.transition; } public DateTimeZone getDateTimeZone() { return this.dateTimeZone; } public int getTime() { return this.format.getTime(this.transition); } @Override public String toString() { return String.format("%s (%s)", this.transition, this.dateTimeZone); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!(obj instanceof DaylightSavingTimeTransition)) { return false; } final DaylightSavingTimeTransition o = (DaylightSavingTimeTransition) obj; return Objects.equals(this.transition, o.transition) && Objects.equals(this.dateTimeZone, o.dateTimeZone); } @Override public int hashCode() { return Objects.hash(this.transition, this.dateTimeZone); } }