se.toxbee.sleepfighter.text.DateTextUtils.java Source code

Java tutorial

Introduction

Here is the source code for se.toxbee.sleepfighter.text.DateTextUtils.java

Source

/*******************************************************************************
 * Copyright (c) 2013 See AUTHORS file.
 * 
 * This file is part of SleepFighter.
 * 
 * SleepFighter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * SleepFighter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SleepFighter. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package se.toxbee.sleepfighter.text;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Locale;

import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.LocalDate;
import org.joda.time.MutableDateTime;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import se.toxbee.sleepfighter.R;
import se.toxbee.sleepfighter.SFApplication;
import se.toxbee.sleepfighter.model.Alarm;
import se.toxbee.sleepfighter.model.AlarmTimestamp;
import se.toxbee.sleepfighter.utils.string.StringUtils;
import android.content.res.Resources;
import android.graphics.Color;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;

import com.google.common.base.Strings;

/**
 * String/text specific date utility methods.
 *
 * @author Centril<twingoow@gmail.com> / Mazdak Farrokhzad.
 * @version 1.0
 * @since Sep 19, 2013
 */
public class DateTextUtils {
    private final static String TAG = DateTextUtils.class.getSimpleName();

    private static final int ENABLED_DAYS_INDICE_LENGTH = 2;

    /**
     * Builds and returns text for the "exact" time an alarm occurs as opposed to the period left for it to occur.<br/>
     * In English, 12:00 today would become "Today 12:00", tomorrow would be come "Tomorrow 12:00", and on Monday it would become
     *
     * @param res resources bundle
     * @param now current time in Unix epoch timestamp.
     * @param ats an AlarmTimestamp: the information about the alarm & its timestamp.
     * @param locale the locale to use for weekdays.
     * @return the built time-to string.
     */
    public static final String getTime(Resources res, long now, AlarmTimestamp ats, Locale locale) {
        String noActive = res.getStringArray(R.array.earliest_time_formats)[0];
        String[] formats = res.getStringArray(R.array.exact_time_formats);
        return getTime(formats, noActive, now, ats, locale);
    }

    /**
     * Builds and returns text for the "exact" time an alarm occurs as opposed to the period left for it to occur.<br/>
     * In English, 12:00 today would become "Today 12:00", tomorrow would be come "Tomorrow 12:00", and on Monday it would become
     *
     * @param formats the formats to use, e.g: [Today %1$s, Tomorrow %1$s, %2$s %1$s].
     * @param noActive if no alarm was active, this is used.
     * @param now current time in Unix epoch timestamp.
     * @param ats an AlarmTimestamp: the information about the alarm & its timestamp.
     * @param locale the locale to use for weekdays.
     * @return the built time-to string.
     */
    public static final String getTime(String[] formats, String noActive, long now, AlarmTimestamp ats,
            Locale locale) {

        if (ats == AlarmTimestamp.INVALID) {
            return noActive;
        } else {
            if (ats.getMillis() < now) {
                throw new RuntimeException("Time given is before now.");
            }

            // Prepare replacements.
            Alarm alarm = ats.getAlarm();
            String timeReplacement = StringUtils.joinTime(alarm.getHour(), alarm.getMinute());

            // Calculate start of tomorrow.
            DateTime nowTime = new DateTime(now);
            DateTime time = new DateTime(ats.getMillis());
            LocalDate tomorrow = new LocalDate(nowTime).plusDays(1);
            DateTime startOfTomorrow = tomorrow.toDateTimeAtStartOfDay(nowTime.getZone());

            if (time.isBefore(startOfTomorrow)) {
                // Alarm is today.
                Log.d(TAG, "today");
                return String.format(formats[0], timeReplacement);
            }

            // Calculate start of the day after tomorrow.
            LocalDate afterTomorrow = tomorrow.plusDays(1);
            DateTime startOfAfterTomorrow = afterTomorrow.toDateTimeAtStartOfDay(nowTime.getZone());

            if (time.isBefore(startOfAfterTomorrow)) {
                Log.d(TAG, "tomorrow");
                // Alarm is tomorrow.
                return String.format(formats[1], timeReplacement);
            }

            // Alarm is after tomorrow.
            Log.d(TAG, "after tomorrow");
            String weekday = new DateTime(ats.getMillis()).dayOfWeek().getAsText(locale);
            return String.format(formats[2], timeReplacement, weekday);
        }
    }

    /**
     * Builds and returns text for the time to an alarm given resources, current time, and AlarmTimestamp when given a resources bundle.
     *
     * @param res Android resources.
     * @param now current time in unix epoch timestamp.
     * @param ats an AlarmTimestamp: the information about the alarm & its timestamp.
     * @return the built time-to string.
     */
    public static final String getTimeToText(Resources res, long now, AlarmTimestamp ats) {
        Period diff = null;

        // ats is invalid when all the alarms have been turned off. INVALID has the value null. 
        // therefore, we must do a nullcheck, otherwise we get an exception. 
        if (ats != AlarmTimestamp.INVALID) {
            diff = new Period(now, ats.getMillis());
        }

        String[] formats = res.getStringArray(R.array.earliest_time_formats);
        String[] partFormats = res.getStringArray(R.array.earliest_time_formats_parts);

        return getTimeToText(formats, partFormats, diff, ats);
    }

    /**
     * Builds and returns earliest text when given a resources bundle.
     *
     * @param formats an array of strings containing formats for [no-alarm-active, < minute, xx-yy-zz, xx-yy, xx]
     *       where xx, yy, zz can be either day, hours, minutes (non-respectively).
     * @param partsFormat an array of strings containing part formats for [day, month, hour].
     * @param diff difference between now and ats.
     * @param ats an AlarmTimestamp: the information about the alarm & its timestamp.
     * @return the built time-to string.
     */
    public static final String getTimeToText(String[] formats, String[] partFormats, Period diff,
            AlarmTimestamp ats) {
        String earliestText;

        // Not real? = we don't have any alarms active.
        if (ats == AlarmTimestamp.INVALID) {
            earliestText = formats[0];
        } else {
            int[] diffVal = { Math.abs(diff.getDays()), Math.abs(diff.getHours()), Math.abs(diff.getMinutes()) };

            // What fields are set?
            BitSet setFields = new BitSet(3);
            setFields.set(0, diffVal[0] != 0);
            setFields.set(1, diffVal[1] != 0);
            setFields.set(2, diffVal[2] != 0);
            int cardinality = setFields.cardinality();

            earliestText = formats[cardinality + 1];

            if (cardinality > 0) {
                List<String> args = new ArrayList<String>(3);

                for (int i = setFields.nextSetBit(0); i >= 0; i = setFields.nextSetBit(i + 1)) {
                    args.add(partFormats[i]);
                }

                // Finally format everything.
                earliestText = String.format(earliestText, args.toArray());

                earliestText = String.format(earliestText, diffVal[0], diffVal[1], diffVal[2]);
            } else {
                // only seconds remains until it goes off.
                earliestText = formats[1];
            }
        }

        return earliestText;
    }

    /**
     * Returns an array of strings with weekday names.
     *
     * @param indiceLength how long each string should be.
     * @param locale the desired locale.
     * @return the array of strings.
     */
    public static final String[] getWeekdayNames(int indiceLength, Locale locale) {
        DateTimeFormatter fmt = DateTimeFormat.forPattern(Strings.repeat("E", indiceLength)).withLocale(locale);

        MutableDateTime time = new MutableDateTime();
        time.setDayOfWeek(DateTimeConstants.MONDAY);

        String[] names = new String[DateTimeConstants.DAYS_PER_WEEK];

        for (int day = 0; day < DateTimeConstants.DAYS_PER_WEEK; day++) {
            String name = fmt.print(time);

            if (name.length() > indiceLength) {
                name = name.substring(0, indiceLength);
            }

            names[day] = name;

            time.addDays(1);
        }

        return names;
    }

    /**
     * Returns a SpannableString of the enabled days in an alarm,<br/>
     * where the days are differently colored if enabled/disabled.
     *
     * @param alarm the alarm to make text for.
     * @return the string.
     */
    public static final SpannableString makeEnabledDaysText(final Alarm alarm) {
        // Compute weekday names & join.
        String[] names = DateTextUtils.getWeekdayNames(ENABLED_DAYS_INDICE_LENGTH, Locale.getDefault());

        SpannableString text = new SpannableString(StringUtils.WS_JOINER.join(names));

        // Create spans for enabled days.
        boolean[] enabledDays = alarm.getEnabledDays();
        if (enabledDays.length != names.length || names.length != 7) {
            throw new AssertionError("A week has 7 days, wrong array lengths!");
        }

        int enabledColor = Color.WHITE;
        int disabledColor = SFApplication.get().getResources().getColor(R.color.nearly_background_text);

        int start = 0;
        for (int i = 0; i < enabledDays.length; i++) {
            boolean enabled = enabledDays[i];
            int length = names[i].length();

            int color = enabled ? enabledColor : disabledColor;
            text.setSpan(new ForegroundColorSpan(color), start, start + length, 0);

            start += length + 1;
        }

        return text;
    }

    /**
     * Construction forbidden.
     */
    private DateTextUtils() {
    }
}