Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.deskclock.data;
     18 
     19 import android.content.Context;
     20 import android.support.annotation.VisibleForTesting;
     21 import android.util.ArrayMap;
     22 
     23 import com.android.deskclock.R;
     24 
     25 import java.text.DateFormatSymbols;
     26 import java.util.Arrays;
     27 import java.util.Calendar;
     28 import java.util.Collections;
     29 import java.util.List;
     30 import java.util.Map;
     31 
     32 import static java.util.Calendar.DAY_OF_WEEK;
     33 import static java.util.Calendar.FRIDAY;
     34 import static java.util.Calendar.MONDAY;
     35 import static java.util.Calendar.SATURDAY;
     36 import static java.util.Calendar.SUNDAY;
     37 import static java.util.Calendar.THURSDAY;
     38 import static java.util.Calendar.TUESDAY;
     39 import static java.util.Calendar.WEDNESDAY;
     40 
     41 /**
     42  * This class is responsible for encoding a weekly repeat cycle in a {@link #getBits bitset}. It
     43  * also converts between those bits and the {@link Calendar#DAY_OF_WEEK} values for easier mutation
     44  * and querying.
     45  */
     46 public final class Weekdays {
     47 
     48     /**
     49      * The preferred starting day of the week can differ by locale. This enumerated value is used to
     50      * describe the preferred ordering.
     51      */
     52     public enum Order {
     53         SAT_TO_FRI(SATURDAY, SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY),
     54         SUN_TO_SAT(SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY),
     55         MON_TO_SUN(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY);
     56 
     57         private final List<Integer> mCalendarDays;
     58 
     59         Order(Integer... calendarDays) {
     60             mCalendarDays = Arrays.asList(calendarDays);
     61         }
     62 
     63         public List<Integer> getCalendarDays() {
     64             return mCalendarDays;
     65         }
     66     }
     67 
     68     /** All valid bits set. */
     69     private static final int ALL_DAYS = 0x7F;
     70 
     71     /** An instance with all weekdays in the weekly repeat cycle. */
     72     public static final Weekdays ALL = Weekdays.fromBits(ALL_DAYS);
     73 
     74     /** An instance with no weekdays in the weekly repeat cycle. */
     75     public static final Weekdays NONE = Weekdays.fromBits(0);
     76 
     77     /** Maps calendar weekdays to the bit masks that represent them in this class. */
     78     private static final Map<Integer, Integer> sCalendarDayToBit;
     79     static {
     80         final Map<Integer, Integer> map = new ArrayMap<>(7);
     81         map.put(MONDAY,    0x01);
     82         map.put(TUESDAY,   0x02);
     83         map.put(WEDNESDAY, 0x04);
     84         map.put(THURSDAY,  0x08);
     85         map.put(FRIDAY,    0x10);
     86         map.put(SATURDAY,  0x20);
     87         map.put(SUNDAY,    0x40);
     88         sCalendarDayToBit = Collections.unmodifiableMap(map);
     89     }
     90 
     91     /** An encoded form of a weekly repeat schedule. */
     92     private final int mBits;
     93 
     94     private Weekdays(int bits) {
     95         // Mask off the unused bits.
     96         mBits = ALL_DAYS & bits;
     97     }
     98 
     99     /**
    100      * @param bits {@link #getBits bits} representing the encoded weekly repeat schedule
    101      * @return a Weekdays instance representing the same repeat schedule as the {@code bits}
    102      */
    103     public static Weekdays fromBits(int bits) {
    104         return new Weekdays(bits);
    105     }
    106 
    107     /**
    108      * @param calendarDays an array containing any or all of the following values
    109      *                     <ul>
    110      *                     <li>{@link Calendar#SUNDAY}</li>
    111      *                     <li>{@link Calendar#MONDAY}</li>
    112      *                     <li>{@link Calendar#TUESDAY}</li>
    113      *                     <li>{@link Calendar#WEDNESDAY}</li>
    114      *                     <li>{@link Calendar#THURSDAY}</li>
    115      *                     <li>{@link Calendar#FRIDAY}</li>
    116      *                     <li>{@link Calendar#SATURDAY}</li>
    117      *                     </ul>
    118      * @return a Weekdays instance representing the given {@code calendarDays}
    119      */
    120     public static Weekdays fromCalendarDays(int... calendarDays) {
    121         int bits = 0;
    122         for (int calendarDay : calendarDays) {
    123             final Integer bit = sCalendarDayToBit.get(calendarDay);
    124             if (bit != null) {
    125                 bits = bits | bit;
    126             }
    127         }
    128         return new Weekdays(bits);
    129     }
    130 
    131     /**
    132      * @param calendarDay any of the following values
    133      *                     <ul>
    134      *                     <li>{@link Calendar#SUNDAY}</li>
    135      *                     <li>{@link Calendar#MONDAY}</li>
    136      *                     <li>{@link Calendar#TUESDAY}</li>
    137      *                     <li>{@link Calendar#WEDNESDAY}</li>
    138      *                     <li>{@link Calendar#THURSDAY}</li>
    139      *                     <li>{@link Calendar#FRIDAY}</li>
    140      *                     <li>{@link Calendar#SATURDAY}</li>
    141      *                     </ul>
    142      * @param on {@code true} if the {@code calendarDay} is on; {@code false} otherwise
    143      * @return a WeekDays instance with the {@code calendarDay} mutated
    144      */
    145     public Weekdays setBit(int calendarDay, boolean on) {
    146         final Integer bit = sCalendarDayToBit.get(calendarDay);
    147         if (bit == null) {
    148             return this;
    149         }
    150         return new Weekdays(on ? (mBits | bit) : (mBits & ~bit));
    151     }
    152 
    153     /**
    154      * @param calendarDay any of the following values
    155      *                     <ul>
    156      *                     <li>{@link Calendar#SUNDAY}</li>
    157      *                     <li>{@link Calendar#MONDAY}</li>
    158      *                     <li>{@link Calendar#TUESDAY}</li>
    159      *                     <li>{@link Calendar#WEDNESDAY}</li>
    160      *                     <li>{@link Calendar#THURSDAY}</li>
    161      *                     <li>{@link Calendar#FRIDAY}</li>
    162      *                     <li>{@link Calendar#SATURDAY}</li>
    163      *                     </ul>
    164      * @return {@code true} if the given {@code calendarDay}
    165      */
    166     public boolean isBitOn(int calendarDay) {
    167         final Integer bit = sCalendarDayToBit.get(calendarDay);
    168         if (bit == null) {
    169             throw new IllegalArgumentException(calendarDay + " is not a valid weekday");
    170         }
    171         return (mBits & bit) > 0;
    172     }
    173 
    174     /**
    175      * @return the weekly repeat schedule encoded as an integer
    176      */
    177     public int getBits() { return mBits; }
    178 
    179     /**
    180      * @return {@code true} iff at least one weekday is enabled in the repeat schedule
    181      */
    182     public boolean isRepeating() { return mBits != 0; }
    183 
    184     /**
    185      * Note: only the day-of-week is read from the {@code time}. The time fields
    186      * are not considered in this computation.
    187      *
    188      * @param time a timestamp relative to which the answer is given
    189      * @return the number of days between the given {@code time} and the previous enabled weekday
    190      *      which is always between 1 and 7 inclusive; {@code -1} if no weekdays are enabled
    191      */
    192     public int getDistanceToPreviousDay(Calendar time) {
    193         int calendarDay = time.get(DAY_OF_WEEK);
    194         for (int count = 1; count <= 7; count++) {
    195             calendarDay--;
    196             if (calendarDay < Calendar.SUNDAY) {
    197                 calendarDay = Calendar.SATURDAY;
    198             }
    199             if (isBitOn(calendarDay)) {
    200                 return count;
    201             }
    202         }
    203 
    204         return -1;
    205     }
    206 
    207     /**
    208      * Note: only the day-of-week is read from the {@code time}. The time fields
    209      * are not considered in this computation.
    210      *
    211      * @param time a timestamp relative to which the answer is given
    212      * @return the number of days between the given {@code time} and the next enabled weekday which
    213      *      is always between 0 and 6 inclusive; {@code -1} if no weekdays are enabled
    214      */
    215     public int getDistanceToNextDay(Calendar time) {
    216         int calendarDay = time.get(DAY_OF_WEEK);
    217         for (int count = 0; count < 7; count++) {
    218             if (isBitOn(calendarDay)) {
    219                 return count;
    220             }
    221 
    222             calendarDay++;
    223             if (calendarDay > Calendar.SATURDAY) {
    224                 calendarDay = Calendar.SUNDAY;
    225             }
    226         }
    227 
    228         return -1;
    229     }
    230 
    231     @Override
    232     public boolean equals(Object o) {
    233         if (this == o) return true;
    234         if (o == null || getClass() != o.getClass()) return false;
    235 
    236         final Weekdays weekdays = (Weekdays) o;
    237         return mBits == weekdays.mBits;
    238     }
    239 
    240     @Override
    241     public int hashCode() {
    242         return mBits;
    243     }
    244 
    245     @Override
    246     public String toString() {
    247         final StringBuilder builder = new StringBuilder(19);
    248         builder.append("[");
    249         if (isBitOn(MONDAY)) {
    250             builder.append(builder.length() > 1 ? " M" : "M");
    251         }
    252         if (isBitOn(TUESDAY)) {
    253             builder.append(builder.length() > 1 ? " T" : "T");
    254         }
    255         if (isBitOn(WEDNESDAY)) {
    256             builder.append(builder.length() > 1 ? " W" : "W");
    257         }
    258         if (isBitOn(THURSDAY)) {
    259             builder.append(builder.length() > 1 ? " Th" : "Th");
    260         }
    261         if (isBitOn(FRIDAY)) {
    262             builder.append(builder.length() > 1 ? " F" : "F");
    263         }
    264         if (isBitOn(SATURDAY)) {
    265             builder.append(builder.length() > 1 ? " Sa" : "Sa");
    266         }
    267         if (isBitOn(SUNDAY)) {
    268             builder.append(builder.length() > 1 ? " Su" : "Su");
    269         }
    270         builder.append("]");
    271         return builder.toString();
    272     }
    273 
    274     /**
    275      * @param context for accessing resources
    276      * @param order the order in which to present the weekdays
    277      * @return the enabled weekdays in the given {@code order}
    278      */
    279     public String toString(Context context, Order order) {
    280         return toString(context, order, false /* forceLongNames */);
    281     }
    282 
    283     /**
    284      * @param context for accessing resources
    285      * @param order the order in which to present the weekdays
    286      * @return the enabled weekdays in the given {@code order} in a manner that
    287      *      is most appropriate for talk-back
    288      */
    289     public String toAccessibilityString(Context context, Order order) {
    290         return toString(context, order, true /* forceLongNames */);
    291     }
    292 
    293     @VisibleForTesting
    294     int getCount() {
    295         int count = 0;
    296         for (int calendarDay = SUNDAY; calendarDay <= SATURDAY; calendarDay++) {
    297             if (isBitOn(calendarDay)) {
    298                 count++;
    299             }
    300         }
    301         return count;
    302     }
    303 
    304     /**
    305      * @param context for accessing resources
    306      * @param order the order in which to present the weekdays
    307      * @param forceLongNames if {@code true} the un-abbreviated weekdays are used
    308      * @return the enabled weekdays in the given {@code order}
    309      */
    310     private String toString(Context context, Order order, boolean forceLongNames) {
    311         if (!isRepeating()) {
    312             return "";
    313         }
    314 
    315         if (mBits == ALL_DAYS) {
    316             return context.getString(R.string.every_day);
    317         }
    318 
    319         final boolean longNames = forceLongNames || getCount() <= 1;
    320         final DateFormatSymbols dfs = new DateFormatSymbols();
    321         final String[] weekdays = longNames ? dfs.getWeekdays() : dfs.getShortWeekdays();
    322 
    323         final String separator = context.getString(R.string.day_concat);
    324 
    325         final StringBuilder builder = new StringBuilder(40);
    326         for (int calendarDay : order.getCalendarDays()) {
    327             if (isBitOn(calendarDay)) {
    328                 if (builder.length() > 0) {
    329                     builder.append(separator);
    330                 }
    331                 builder.append(weekdays[calendarDay]);
    332             }
    333         }
    334         return builder.toString();
    335     }
    336 }