All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.icalendarfx.properties.component.recurrence.rrule.byxxx.ByMonthDay Maven / Gradle / Ivy

The newest version!
package jfxtras.icalendarfx.properties.component.recurrence.rrule.byxxx;

import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

import jfxtras.icalendarfx.properties.component.recurrence.rrule.byxxx.ByMonthDay;
import jfxtras.icalendarfx.properties.component.recurrence.rrule.byxxx.ByRuleIntegerAbstract;
import jfxtras.icalendarfx.utilities.DateTimeUtilities;

/**
 * By Month Day
 * BYMONTHDAY
 * RFC 5545, iCalendar 3.3.10, page 42
 * 
 * The BYMONTHDAY rule part specifies a COMMA-separated list of days
      of the month.  Valid values are 1 to 31 or -31 to -1.  For
      example, -10 represents the tenth to the last day of the month.
      The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule
      part is set to WEEKLY.
  *
  * @author David Bal
  * 
 * */
public class ByMonthDay extends ByRuleIntegerAbstract
{
    /** sorted array of days of month
     * (i.e. 5, 10 = 5th and 10th days of the month, -3 = 3rd from last day of month)
     * Uses a varargs parameter to allow any number of days
     */
    
    /*
     * CONSTRUCTORS
     */
    
    public ByMonthDay()
    {
        super();
    }

    public ByMonthDay(Integer... daysOfMonth)
    {
        super(daysOfMonth);
    }
    
    public ByMonthDay(ByMonthDay source)
    {
        super(source);
    }
    
    @Override
    Predicate isValidValue()
    {
        return (value) -> (value >= -31) && (value <= 31) && (value != 0);
    }
    
    /**
     * Return stream of valid dates made by rule (infinite if COUNT or UNTIL not present)
     */
    @Override
    public Stream streamRecurrences(Stream inStream, ChronoUnit chronoUnit, Temporal dateTimeStart)
    {
        switch (chronoUnit)
        {
        case HOURS:
        case MINUTES:
        case SECONDS:
        case DAYS:
            return inStream.filter(d ->
                    { // filter out all but qualifying days
                        int myDay = d.get(ChronoField.DAY_OF_MONTH);
                        int myDaysInMonth = LocalDate.from(d).lengthOfMonth();
                        for (int day : getValue())
                        {
                            if (myDay == day) return true;
                            // negative daysOfMonth (-3 = 3rd to last day of month)
                            if ((day < 0) && (myDay == myDaysInMonth + day + 1)) return true;
                        }
                        return false;
                    });
        case YEARS:
            return inStream.flatMap(d -> 
            { // Expand to be daysOfMonth days in current month
                List dates = new ArrayList<>();
                for (Month month : Month.values())
                {
                    Temporal monthAdjustedTemporal = d.with(ChronoField.MONTH_OF_YEAR, month.getValue());
                    dates.addAll(extracted(monthAdjustedTemporal, dateTimeStart));
                }
                return dates.stream().sorted(DateTimeUtilities.TEMPORAL_COMPARATOR);
            });
        case MONTHS:
            return inStream.flatMap(d -> 
            { // Expand to be daysOfMonth days in current month
                List dates = new ArrayList<>();
                dates.addAll(extracted(d, dateTimeStart));
                return dates.stream().sorted(DateTimeUtilities.TEMPORAL_COMPARATOR);
            });
        case WEEKS:
            throw new IllegalArgumentException(name().toString() + " is not available for " + chronoUnit + " frequency."); // Not available
        default:
            throw new IllegalArgumentException("Not implemented: " + chronoUnit);
        }
    }

    /* process dayOfMonth for YEARS and MONTHS */
    private List extracted(Temporal initialTemporal, Temporal dateTimeStart)
    {
        List dates = new ArrayList<>();
        for (int dayOfMonth : getValue())
        {           
            final Temporal correctMonthTemporal = (dayOfMonth > 0) ? initialTemporal : initialTemporal.minus(1, ChronoUnit.MONTHS);
            int daysInMonth = (int) ChronoUnit.DAYS.between(correctMonthTemporal.with(TemporalAdjusters.firstDayOfMonth()),
                                                            correctMonthTemporal.with(TemporalAdjusters.firstDayOfNextMonth()));
            int finalDayOfMonth = 0;
            if (dayOfMonth > 0)
            {
                if (dayOfMonth <= daysInMonth)
                {
                    finalDayOfMonth = dayOfMonth;
                }
            } else if (dayOfMonth < 0)
            {
                int newDayOfMonth = daysInMonth + dayOfMonth + 1;
                if (newDayOfMonth > 0)
                {
                    finalDayOfMonth = newDayOfMonth;
                }
            } else
            {
                throw new IllegalArgumentException(name().toString() + " can't have a value of zero");
            }
            Temporal newTemporal = (finalDayOfMonth != 0) ? correctMonthTemporal.with(ChronoField.DAY_OF_MONTH, finalDayOfMonth) : null;
            
            // ensure day of month hasn't changed.  If it changed the date was invalid and should be ignored.
            if (newTemporal != null)
            {
                dates.add(newTemporal);
            }
        }
        return dates;
    }
    
    public static ByMonthDay parse(String content)
    {
    	return ByMonthDay.parse(new ByMonthDay(), content);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy