com.tkmtwo.timex.WallClock.java Source code

Java tutorial

Introduction

Here is the source code for com.tkmtwo.timex.WallClock.java

Source

/*
 *
 * Copyright 2014 Tom Mahaffey
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.tkmtwo.timex;

import java.math.RoundingMode;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.math.IntMath;
import com.google.common.math.LongMath;
import com.google.common.primitives.Ints;

import org.joda.time.Duration;
import org.joda.time.Period;

/**
 * <code>WallClock</code> is an immutable class representing the current
 * "wall clock" time.  The "wall clock" time is the time seen by an observer
 * on their clock, on their wall.  There is no concept of timezone in 
 * this class.
 * <p>
 *
 * Internally, the class holds values for the hours, minutes, and seconds
 * field as observed on "the wall clock" as well as the number of seconds
 * since the beginning of the day.  <code>WallClock</code> only supports
 * precision to the second (no milliseconds).
 *
 *
 *
 *
 * @author Tom Mahaffey
 */
public final class WallClock implements Comparable<WallClock> {

    private static final int SECONDS_PER_MINUTE = 60;
    private static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60;
    private static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * 24;

    private final int hour;
    private final int minute;
    private final int second;
    private final int seconds;

    private WallClock(int sod) {
        checkArgument(sod >= 0 && sod <= 86400, "Seconds of day must be in [0...86400].");

        int secs = sod;

        int hs = secs / SECONDS_PER_HOUR;
        secs = secs - (hs * SECONDS_PER_HOUR);

        int ms = secs / SECONDS_PER_MINUTE;
        secs = secs - (ms * SECONDS_PER_MINUTE);

        int ss = secs;

        this.hour = hs;
        this.minute = ms;
        this.second = ss;
        this.seconds = sod;
    }

    /**
     * Gets the hour of the day.
     * <p>
     *
     * @return an <code>int</code> value
     */
    public int getHour() {
        return hour;
    }

    /**
     * Gets the minute of the hour.
     * <p>
     *
     * @return an <code>int</code> value
     */
    public int getMinute() {
        return minute;
    }

    /**
     * Gets the second of the minute.
     * <p>
     *
     * @return an <code>int</code> value
     */
    public int getSecond() {
        return second;
    }

    /**
     * Gets the seconds since the beginning of the day.
     * <p>
     *
     * @return an <code>int</code> value
     */
    public int getSeconds() {
        return seconds;
    }

    private static int sumFields(int hrs, int mins, int secs) {
        int sumSecs = 0;
        sumSecs = IntMath.checkedAdd(sumSecs, IntMath.checkedMultiply(hrs, SECONDS_PER_HOUR));
        sumSecs = IntMath.checkedAdd(sumSecs, IntMath.checkedMultiply(mins, SECONDS_PER_MINUTE));
        sumSecs = IntMath.checkedAdd(sumSecs, secs);
        return sumSecs;
    }

    /**
     * Creates a <code>WallClock</code> based on the number of seconds
     * since the start of the day.
     * <p>
     *
     * Acceptable values are between 0 and 86400 seconds.
     * <p>
     *
     * @param sod an <code>int</code> representing the seconds since the start of the day
     * @return a <code>WallClock</code> value
     */
    public static WallClock valueOf(int sod) {
        return new WallClock(sod);
    }

    /**
     * Creates a <code>WallClock</code> based on the hours, minutes, and
     * seconds since the start of the day.
     * <p>
     *
     * There are no limitations on values, so long as the field
     * arithmetic results in less thab a day's worth of seconds.  For
     * example:
     * <p>
     *
     * <pre>
     * WallClock.valueOf(0, 1, 0) == WallClock.valueOf(0, 0, 60) == WallClock.valueOf(0, 2, -60)
     * </pre>
     *
     *
     * @param hrs an <code>int</code> representing the number of hours
     * @param mins an <code>int</code> representing the number of minutes
     * @param secs an <code>int</code> representing the number of seconds
     * @return a <code>WallClock</code> value
     */
    public static WallClock valueOf(int hrs, int mins, int secs) {
        int sod = sumFields(hrs, mins, secs);
        return valueOf(sod);
    }

    /**
     * Creates a <code>WallClock</code> based on the hours, minutes, and
     * seconds since the start of the day.
     *
     *
     * @param hrs a <code>String</code> representing the number of hours
     * @param mins a <code>String</code> representing the number of minutes
     * @param secs a <code>String</code> representing the number of seconds
     * @return a <code>WallClock</code> value
     * @see #valueOf(int, int, int)
     */
    public static WallClock valueOf(String hrs, String mins, String secs) {
        int hInt = checkNotNull(Ints.tryParse(hrs), "Hours '%s' is not a valid integer.", hrs).intValue();
        int mInt = checkNotNull(Ints.tryParse(mins), "Minutes '%s' is not a valid integer.", mins).intValue();
        int sInt = checkNotNull(Ints.tryParse(secs), "Seconds '%s' is not a valid integer.", secs).intValue();

        return valueOf(hInt, mInt, sInt);
    }

    /**
     * Creates a <code>WallClock</code> based on a formatted
     * <code>String</code> representing the hours, minutes, and seconds
     * since the start of the day.
     * <p>
     *
     * The format may be either basic (HHmmss) or extended (HH:mm:ss).
     * <p>
     *
     * @param s a <code>String</code> representing the 'clock' time
     * @return a <code>WallClock</code> value
     * @see #valueOf(int, int, int)
     */
    public static WallClock valueOf(String s) {
        return parse(s);
    }

    /**
     * Creates copy of a <code>WallClock</code>.
     * <p>
     *
     *
     * @param wc a <code>WallClock</code> value
     * @return a <code>WallClock</code> value
     * @see #valueOf(int)
     */
    public static WallClock valueOf(WallClock wc) {
        checkNotNull(wc, "Need a WallClock.");
        return valueOf(wc.getSeconds());
    }

    /**
     * Creates a <code>WallClock</code> based on a formatted <code>String</code>
     * representing the hours, minutes, and seconds since the start of the day.
     * <p>
     *
     * The format may be either basic (HHmmss) or extended (HH:mm:ss).
     * <p>
     *
     * @param s a <code>String</code> representing the 'clock' time
     * @return a <code>WallClock</code> value
     * @see #valueOf(int, int, int)
     */
    public static WallClock parse(String s) {
        checkNotNull(s, "Need a string.");
        if (s.length() == 6) {
            return parseBasic(s);
        }
        if (s.length() == 8) {
            return parseExtended(s);
        }
        throw new IllegalArgumentException("Expecting 6 (HHmmss) or 8 (HH:mm:ss) characters.");
    }

    /**
     * Creates a <code>WallClock</code> on a basic-formatted <code>String</code>.
     * <p>
     *
     * This method expects 'HHmmss' (six characters) exactly.  No effort
     * is made to resolve negative field values.
     * <p>
     *
     * @param s a <code>String</code> representing the 'clock' time
     * @return a <code>WallClock</code> value
     * @see #valueOf(int, int, int)
     */
    public static WallClock parseBasic(String s) {
        checkNotNull(s, "Need a string.");
        checkArgument(s.length() == 6, "Expecting 6 characters (HHmmss).");

        return valueOf(s.substring(0, 2), s.substring(2, 4), s.substring(4, 6));
    }

    /**
     * Creates a <code>WallClock</code> on an extended-formatted <code>String</code>.
     * <p>
     *
     * This method expects 'HH:mm:ss' (eight characters) exactly.  No effort is made to
     * resolve negative field values.
     *
     * <p>
     *
     * @param s a <code>String</code> representing the 'clock' time
     * @return a <code>WallClock</code> value
     * @see #valueOf(int, int, int)
     */
    public static WallClock parseExtended(String s) {
        checkNotNull(s, "Need a string.");
        checkArgument(s.length() == 8, "Expecting 8 characters (HH:mm:ss).");

        return valueOf(s.substring(0, 2), s.substring(3, 5), s.substring(6, 8));
    }

    /**
     * Compares this <code>WallClock</code> with another.
     * <p>
     *
     *
     * @param wc a <code>WallClock</code> value
     * @return a <code>boolean</code> value
     */
    public boolean isEqual(WallClock wc) {
        if (wc == null) {
            return false;
        }
        return getSeconds() == wc.getSeconds();
    }

    /**
     * Compares this <code>WallClock</code> with another.
     * <p>
     *
     *
     * @param wc a <code>WallClock</code> value
     * @return a <code>boolean</code> value
     */
    public boolean isBefore(WallClock wc) {
        if (wc == null) {
            return false;
        }
        return getSeconds() < wc.getSeconds();
    }

    /**
     * Compares this <code>WallClock</code> with another.
     * <p>
     *
     *
     * @param wc a <code>WallClock</code> value
     * @return a <code>boolean</code> value
     */
    public boolean isAfter(WallClock wc) {
        if (wc == null) {
            return false;
        }
        return getSeconds() > wc.getSeconds();
    }

    /**
     * Compares this <code>WallClock</code> with another.
     * <p>
     *
     *
     * @param wc a <code>WallClock</code> value
     * @return a <code>boolean</code> value
     */
    public boolean isBeforeOrEquals(WallClock wc) {
        return isBefore(wc) || isEqual(wc);
    }

    /**
     * Compares this <code>WallClock</code> with another.
     * <p>
     *
     *
     * @param wc a <code>WallClock</code> value
     * @return a <code>boolean</code> value
     */
    public boolean isAfterOrEquals(WallClock wc) {
        return isAfter(wc) || isEqual(wc);
    }

    /**
     * Prints the wall clock time with format 'HHmmss'.
     * <p>
     *
     *
     * @return a <code>boolean</code> value
     */
    public String print() {
        return printBasic();
    }

    /**
     * Prints the wall clock time with format 'HHmmss'.
     * <p>
     *
     *
     * @return a <code>boolean</code> value
     */
    public String printBasic() {
        return String.format("%02d%02d%02d", getHour(), getMinute(), getSecond());
    }

    /**
     * Prints the wall clock time with format 'HH:mm:ss'.
     * <p>
     *
     *
     * @return a <code>boolean</code> value
     */
    public String printExtended() {
        return String.format("%02d:%02d:%02d", getHour(), getMinute(), getSecond());
    }

    /**
     * Returns a copy of this <code>WallClock</code> plus
     * the specified number of seconds.
     * <p>
     *
     * @param secs an <code>int</code> representing the seconds
     * @return a <code>WallClock</code> value
     */
    public WallClock plus(int secs) {
        return valueOf(IntMath.checkedAdd(getSeconds(), secs));
    }

    /**
     * Returns a copy of this <code>WallClock</code> plus
     * the specified <code>Period</code>.
     * <p>
     *
     * <code>Period.toStandardDuration()</code> is used
     * to calculate seconds.
     * <p>
     *
     * @param p an <code>Period</code> representing the amount of time
     * @return a <code>WallClock</code> value
     */
    public WallClock plus(Period p) {
        checkNotNull(p, "Need a Period.");
        return plus(p.toStandardDuration());
    }

    /**
     * Returns a copy of this <code>WallClock</code> plus
     * the specified <code>Duration</code>.
     * <p>
     *
     * Uses <code>RoundingMode.FLOOR</code> to truncate the milliseconds
     * during conversion of milliseconds in the <code>Duration</code> to
     * seconds.
     * <p>
     *
     * @param d an <code>Duration</code> representing the amount of time
     * @return a <code>WallClock</code> value
     */
    public WallClock plus(Duration d) {
        return plus(d, RoundingMode.FLOOR);
    }

    /**
     * Returns a copy of this <code>WallClock</code> plus
     * the specified <code>Duration</code>.
     * <p>
     *
     *
     * @param d an <code>Duration</code> representing the amount of time
     * @param rm a <code>RoundingMode</code> used to deal with long to int division
     * @return a <code>WallClock</code> value
     */
    public WallClock plus(Duration d, RoundingMode rm) {
        checkNotNull(d, "Need a Duration.");
        checkNotNull(rm, "Need a RoundingMode.");
        return plus(Ints.checkedCast(LongMath.divide(d.getMillis(), 1000L, rm)));
    }

    /**
     * Returns a copy of this <code>WallClock</code> plus
     * the specified number of hours, minutes, and seconds.
     * <p>
     *
     *
     * @param hrs an <code>int</code> representing the number of hours
     * @param mins an <code>int</code> representing the number of minutes
     * @param secs an <code>int</code> representing the number of seconds
     * @return a <code>WallClock</code> value
     */
    public WallClock plus(int hrs, int mins, int secs) {
        return plus(sumFields(hrs, mins, secs));
    }

    /**
     * Returns a copy of this <code>WallClock</code> plus
     * the specified number of hours, minutes, and seconds.
     * <p>
     *
     *
     * @param hrs a <code>String</code> representing the number of hours
     * @param mins a <code>String</code> representing the number of minutes
     * @param secs a <code>String</code> representing the number of seconds
     * @return a <code>WallClock</code> value
     */
    public WallClock plus(String hrs, String mins, String secs) {
        return plus(checkNotNull(Ints.tryParse(hrs), "Hours '%s' is not a valid integer.", hrs).intValue(),
                checkNotNull(Ints.tryParse(mins), "Minutes '%s' is not a valid integer.", mins).intValue(),
                checkNotNull(Ints.tryParse(secs), "Seconds '%s' is not a valid integer.", secs).intValue());
    }

    /**
     * Returns a copy of this <code>WallClock</code> minus
     * the specified number of seconds.
     * <p>
     *
     * @param secs an <code>int</code> representing the seconds
     * @return a <code>WallClock</code> value
     */
    public WallClock minus(int secs) {
        return valueOf(IntMath.checkedSubtract(getSeconds(), secs));
    }

    /**
     * Returns a copy of this <code>WallClock</code> minus
     * the specified <code>Period</code>.
     * <p>
     *
     * <code>Period.toStandardDuration()</code> is used
     * to calculate seconds.
     * <p>
     *
     * @param p an <code>Period</code> representing the amount of time
     * @return a <code>WallClock</code> value
     */
    public WallClock minus(Period p) {
        checkNotNull(p, "Need a Period.");
        return minus(p.toStandardDuration());
    }

    /**
     * Returns a copy of this <code>WallClock</code> minus
     * the specified <code>Duration</code>.
     * <p>
     *
     * Uses <code>RoundingMode.FLOOR</code> to truncate the milliseconds
     * during conversion of milliseconds in the <code>Duration</code> to
     * seconds.
     * <p>
     *
     * @param d an <code>Duration</code> representing the amount of time
     * @return a <code>WallClock</code> value
     */
    public WallClock minus(Duration d) {
        return minus(d, RoundingMode.FLOOR);
    }

    /**
     * Returns a copy of this <code>WallClock</code> minus
     * the specified <code>Duration</code>.
     * <p>
     *
     *
     * @param d an <code>Duration</code> representing the amount of time
     * @param rm a <code>RoundingMode</code> used to deal with long to int division
     * @return a <code>WallClock</code> value
     */
    public WallClock minus(Duration d, RoundingMode rm) {
        checkNotNull(d, "Need a Duration.");
        checkNotNull(rm, "Need a RoundingMode.");
        return minus(Ints.checkedCast(LongMath.divide(d.getMillis(), 1000L, rm)));
    }

    /**
     * Returns a copy of this <code>WallClock</code> minus
     * the specified number of hours, minutes, and seconds.
     * <p>
     *
     *
     * @param hrs an <code>int</code> representing the number of hours
     * @param mins an <code>int</code> representing the number of minutes
     * @param secs an <code>int</code> representing the number of seconds
     * @return a <code>WallClock</code> value
     */
    public WallClock minus(int hrs, int mins, int secs) {
        return minus(sumFields(hrs, mins, secs));
    }

    /**
     * Returns a copy of this <code>WallClock</code> minus
     * the specified number of hours, minutes, and seconds.
     * <p>
     *
     *
     * @param hrs a <code>String</code> representing the number of hours
     * @param mins a <code>String</code> representing the number of minutes
     * @param secs a <code>String</code> representing the number of seconds
     * @return a <code>WallClock</code> value
     */
    public WallClock minus(String hrs, String mins, String secs) {
        return minus(checkNotNull(Ints.tryParse(hrs), "Hours '%s' is not a valid integer.", hrs).intValue(),
                checkNotNull(Ints.tryParse(mins), "Minutes '%s' is not a valid integer.", mins).intValue(),
                checkNotNull(Ints.tryParse(secs), "Seconds '%s' is not a valid integer.", secs).intValue());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }

        final WallClock impl = (WallClock) obj;
        return getSeconds() == impl.getSeconds();
    }

    @Override
    public int hashCode() {
        return getSeconds();
    }

    @Override
    public int compareTo(WallClock wc) {
        if (wc == null) {
            return 0;
        }
        return IntMath.checkedSubtract(getSeconds(), wc.getSeconds());
    }

}