Few days ago I saw a post in StackoverFlow asking for finding date of birth from age in java. I thought that why don't I write a class which can manipulate Age. Here is what I came up with.
This class Age stores age in terms of
- Years
- Months
- Days
- Hours
- Minutes
- Seconds
Followings are the features of this class:
- It returns Age for a given Date
- It is Timezone specific
- It returns age as Seconds, Minutes, Hours, Days, Weeks, Months
- Prints Age in a pretty format
- This class is Serializable, Cloneable and Comparable
- It is written using java.time api
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.ValueRange;
import java.util.Date;
import java.util.Objects;
/**
* The class <code>Age</code> represents the length of time that a person has
* lived or a thing has existed.
*
* @author TapasB
*/
public class Age implements Serializable, Cloneable, Comparable<Age> {
private static final long serialVersionUID = 210032925430172448L;
private long years;
private long months;
private long days;
private long hours;
private long minutes;
private long seconds;
// Private Constructor
private Age() {
}
/**
* Creates and returns a new instance of <code>Age</code> object.
*
* @author TapasB
* @return a new instance of <code>Age</code>
*/
public static Age getDefault() {
return new Age();
}
/**
* Creates and returns a new instance of <code>Age</code> object after
* setting the given years.
*
* @author TapasB
* @param years
* - years to set
* @return a new instance of <code>Age</code>
*/
public static Age of(long years) {
return getDefault().setYears(years);
}
/**
* Creates and returns a new instance of <code>Age</code> object after
* setting given years and months to it.
*
* @author TapasB
* @param years
* - years to set
* @param months
* - months to set
* @return a new instance of <code>Age</code>
*/
public static Age of(long years, long months) {
return of(years).setMonths(months);
}
/**
* Creates and returns a new instance of <code>Age</code> object after
* setting given years, months and days to it.
*
* @author TapasB
* @param years
* - years to set
* @param months
* - months to set
* @param days
* - days to set
* @return a new instance of <code>Age</code>
*/
public static Age of(long years, long months, long days) {
return of(years, months).setDays(days);
}
/**
* Creates and returns a new instance of <code>Age</code> object after
* setting given years, months, days and hours to it.
*
* @author TapasB
* @param years
* - years to set
* @param months
* - months to set
* @param days
* - days to set
* @param hours
* - hours to set
* @return a new instance of <code>Age</code>
*/
public static Age of(long years, long months, long days, long hours) {
return of(years, months, days).setHours(hours);
}
/**
* Creates and returns a new instance of <code>Age</code> object after
* setting given years, months, days, hours and minutes to it.
*
* @author TapasB
* @param years
* - years to set
* @param months
* - months to set
* @param days
* - days to set
* @param hours
* - hours to set
* @param minutes
* - minutes to set
* @return a new instance of <code>Age</code>
*/
public static Age of(long years, long months, long days, long hours, long minutes) {
return of(years, months, days, hours).setMinutes(minutes);
}
/**
* Creates and returns a new instance of <code>Age</code> object after
* setting given years, months, days, hours, minutes and seconds to it.
*
* @author TapasB
* @param years
* - years to set
* @param months
* - months to set
* @param days
* - days to set
* @param hours
* - hours to set
* @param minutes
* - minutes to set
* @param seconds
* - seconds to set
* @return a new instance of <code>Age</code>
*/
public static Age of(long years, long months, long days, long hours, long minutes, long seconds) {
return of(years, months, days, hours, minutes).setSeconds(seconds);
}
/**
* Calculates and returns an instance <code>Age</code> for the given date
* string and date pattern. The <code>Age</code> will be calculated with
* respect to the system default time-zone.
*
* @author TapasB
* @param date
* - the birth date as string
* @param pattern
* - date pattern
* @return an instance of <code>Age</code>
* @throws ParseException
* if the parsing fails
*/
public static Age fromDateOfBirth(String date, String pattern) throws ParseException {
Objects.requireNonNull(date, "date");
Objects.requireNonNull(pattern, "pattern");
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
return fromDateOfBirth(dateFormat.parse(date));
}
/**
* Calculates and returns an instance of <code>Age</code> for the given
* {@link Date}. The <code>Age</code> will be calculated with respect to the
* system default time-zone.
*
* @author TapasB
* @param date
* - the birth date
* @return an instance of <code>Age</code>
*/
public static Age fromDateOfBirth(Date date) {
Objects.requireNonNull(date, "date");
return fromDateOfBirth(date, ZoneId.systemDefault());
}
/**
* Calculates and returns an instance of <code>Age</code> for the given
* {@link Date} with respect to the given {@link ZoneId}.
*
* @author TapasB
* @param date
* - the birth date
* @param zone
* - the zone ID to use
* @return an instance of <code>Age</code>
*/
public static Age fromDateOfBirth(Date date, ZoneId zone) {
Objects.requireNonNull(date, "date");
Objects.requireNonNull(zone, "zone");
Instant instant = Instant.ofEpochMilli(date.getTime());
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, zone);
return fromDateOfBirth(dateTime, zone);
}
/**
* Calculates and returns an instance of <code>Age</code> for the given
* {@link LocalDateTime}. The <code>Age</code> will be calculated with
* respect to the system default time-zone.
*
* @author TapasB
* @param date
* - the birth date
* @return an instance of <code>Age</code>
*/
public static Age fromDateOfBirth(LocalDateTime date) {
Objects.requireNonNull(date, "date");
return fromDateOfBirth(date, ZoneId.systemDefault());
}
/**
* Calculates and returns an instance of <code>Age</code> for the given
* {@link LocalDateTime} with respect to the given {@link ZoneId}.
*
* @author TapasB
* @param date
* - the birth date
* @param zone
* - the zone ID to use
* @return an instance of <code>Age</code>
*/
public static Age fromDateOfBirth(LocalDateTime date, ZoneId zone) {
Objects.requireNonNull(date, "date");
Objects.requireNonNull(zone, "zone");
ZonedDateTime zonedDate = date.atZone(zone);
return fromDateOfBirth(zonedDate);
}
/**
* Calculates and returns an instance of <code>Age</code> for the given
* {@link ZonedDateTime}.
*
* @author TapasB
* @param date
* - the birth date
* @return an instance of <code>Age</code>
*/
public static Age fromDateOfBirth(ZonedDateTime date) {
Objects.requireNonNull(date, "date");
ZonedDateTime now = ZonedDateTime.now(date.getZone());
ZonedDateTime tempDate = ZonedDateTime.from(date);
long years = tempDate.until(now, ChronoUnit.YEARS);
tempDate = tempDate.plusYears(years);
long months = tempDate.until(now, ChronoUnit.MONTHS);
tempDate = tempDate.plusMonths(months);
long days = tempDate.until(now, ChronoUnit.DAYS);
tempDate = tempDate.plusDays(days);
long hours = tempDate.until(now, ChronoUnit.HOURS);
tempDate = tempDate.plusHours(hours);
long minutes = tempDate.until(now, ChronoUnit.MINUTES);
tempDate = tempDate.plusMinutes(minutes);
long seconds = tempDate.until(now, ChronoUnit.SECONDS);
Age age = getDefault();
if (years > 0) {
age.years = years;
}
if (months > 0) {
age.months = months;
}
if (days > 0) {
age.days = days;
}
if (hours > 0) {
age.hours = hours;
}
if (minutes > 0) {
age.minutes = minutes;
}
if (seconds > 0) {
age.seconds = seconds;
}
return age;
}
/**
* Sets the given years to this object.
*
* @author TapasB
* @param years
* - years to set
* @return this instance for chain call
*/
public Age setYears(long years) {
checkValidValue("years",
ValueRange.of(0, ChronoField.YEAR.range().getMaximum()),
years);
this.years = years;
return this;
}
/**
* Sets the given months to this object.
*
* @author TapasB
* @param months
* - months to set
* @return this instance for chain call
*/
public Age setMonths(long months) {
checkValidValue("months",
ValueRange.of(0, ChronoField.MONTH_OF_YEAR.range().getMaximum()),
months);
this.months = months;
return this;
}
/**
* Sets the given days to this object.
*
* @author TapasB
* @param days
* - days to set
* @return this instance for chain call
*/
public Age setDays(long days) {
checkValidValue("days",
ValueRange.of(0, ChronoField.DAY_OF_MONTH.range().getMaximum()),
days);
this.days = days;
return this;
}
/**
* Sets the given hours to this object.
*
* @author TapasB
* @param hours
* - hours to set
* @return this instance for chain call
*/
public Age setHours(long hours) {
checkValidValue("hours",
ValueRange.of(0, ChronoField.HOUR_OF_DAY.range().getMaximum()),
hours);
this.hours = hours;
return this;
}
/**
* Sets the given minutes to this object.
*
* @author TapasB
* @param minutes
* - minutes to set
* @return this instance for chain call
*/
public Age setMinutes(long minutes) {
checkValidValue("minutes",
ValueRange.of(0, ChronoField.MINUTE_OF_HOUR.range().getMaximum()),
minutes);
this.minutes = minutes;
return this;
}
/**
* Sets the given seconds to this object.
*
* @author TapasB
* @param seconds
* - seconds to set
* @return this instance for chain call
*/
public Age setSeconds(long seconds) {
checkValidValue("seconds",
ValueRange.of(0, ChronoField.SECOND_OF_MINUTE.range().getMaximum()),
seconds);
this.seconds = seconds;
return this;
}
/**
* Sets the given value to this object to the given field defined by
* {@link ChronoUnit}.
*
* <p>
* The accepted list of values are:
* <ul>
* <li>ChronoUnit.YEARS</li>
* <li>ChronoUnit.MONTHS</li>
* <li>ChronoUnit.DAYS</li>
* <li>ChronoUnit.HOURS</li>
* <li>ChronoUnit.MINUTES</li>
* <li>ChronoUnit.SECONDS</li>
* </ul>
* </p>
*
* @author TapasB
* @param value
* - value to set
* @param unit
* - field to which the value needs to be set
* @return this instance for chain call
* @throws UnsupportedOperationException
* if the unit passed to it is not belongs to the allowed list
* of values
*/
public Age set(long value, ChronoUnit unit) throws UnsupportedOperationException {
Objects.requireNonNull(unit, "unit");
switch (unit) {
case YEARS:
return setYears(value);
case MONTHS:
return setMonths(value);
case DAYS:
return setDays(value);
case HOURS:
return setHours(value);
case MINUTES:
return setMinutes(value);
case SECONDS:
return setSeconds(value);
default:
throw new UnsupportedOperationException("Unit " + unit + " not supported");
}
}
/**
* Calculates the <code>Age</code> and returns the birth date as
* {@link LocalDateTime}.
*
* @author TapasB
* @return date of birth as <code>LocalDateTime</code>
*/
public LocalDateTime getDateOfBirth() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime dob = now.minus(this.years, ChronoUnit.YEARS).
minus(this.months, ChronoUnit.MONTHS).
minus(this.days, ChronoUnit.DAYS).
minus(this.hours, ChronoUnit.HOURS).
minus(this.minutes, ChronoUnit.MINUTES).
minus(this.seconds, ChronoUnit.SECONDS);
return dob;
}
/**
* Calculates the <code>Age</code> and returns the birth date as
* {@link ZonedDateTime} with respect to the given {@link ZoneId}.
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return date of birth as <code>ZonedDateTime</code>
*/
public ZonedDateTime getDateOfBirth(ZoneId zone) {
ZonedDateTime now = ZonedDateTime.now(zone);
ZonedDateTime dob = now.minus(this.years, ChronoUnit.YEARS).
minus(this.months, ChronoUnit.MONTHS).
minus(this.days, ChronoUnit.DAYS).
minus(this.hours, ChronoUnit.HOURS).
minus(this.minutes, ChronoUnit.MINUTES).
minus(this.seconds, ChronoUnit.SECONDS);
return dob;
}
/**
* Returns elapsed seconds from date of birth for this <code>Age</code>.
*
* @author TapasB
* @return seconds
*/
public long asSeconds() {
return asSeconds(ZoneId.systemDefault());
}
/**
* Returns elapsed seconds from date of birth for this <code>Age</code> for
* the given. zone
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return seconds
*/
public long asSeconds(ZoneId zone) {
return calculateDifference(ChronoUnit.SECONDS, zone);
}
/**
* Returns elapsed minutes from date of birth for this <code>Age</code>.
*
* @author TapasB
* @return minutes
*/
public long asMinutes() {
return asMinutes(ZoneId.systemDefault());
}
/**
* Returns elapsed minutes from date of birth for this <code>Age</code> for
* the given zone.
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return minutes
*/
public long asMinutes(ZoneId zone) {
return calculateDifference(ChronoUnit.MINUTES, zone);
}
/**
* Returns elapsed hours from date of birth for this <code>Age</code>.
*
* @author TapasB
* @return hours
*/
public long asHours() {
return asHours(ZoneId.systemDefault());
}
/**
* Returns elapsed hours from date of birth for this <code>Age</code> for
* the given zone.
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return hours
*/
public long asHours(ZoneId zone) {
return calculateDifference(ChronoUnit.HOURS, zone);
}
/**
* Returns elapsed days from date of birth for this <code>Age</code>.
*
* @author TapasB
* @return days
*/
public long asDays() {
return asDays(ZoneId.systemDefault());
}
/**
* Returns elapsed days from date of birth for this <code>Age</code> for the
* given zone.
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return days
*/
public long asDays(ZoneId zone) {
return calculateDifference(ChronoUnit.DAYS, zone);
}
/**
* Returns elapsed weeks from date of birth for this <code>Age</code>.
*
* @author TapasB
* @return weeks
*/
public long asWeeks() {
return asWeeks(ZoneId.systemDefault());
}
/**
* Returns elapsed weeks from date of birth for this <code>Age</code> for
* the given zone.
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return weeks
*/
public long asWeeks(ZoneId zone) {
return calculateDifference(ChronoUnit.WEEKS, zone);
}
/**
* Returns elapsed months from date of birth for this <code>Age</code>.
*
* @author TapasB
* @return months
*/
public long asMonths() {
return asMonths(ZoneId.systemDefault());
}
/**
* Returns elapsed months from date of birth for this <code>Age</code> for
* the given zone.
*
* @author TapasB
* @param zone
* - the zone ID to use
* @return months
*/
public long asMonths(ZoneId zone) {
return calculateDifference(ChronoUnit.MONTHS, zone);
}
/**
* Returns string representation of the <code>Age</code> in the following
* format: <br/>
* <code>A Years B Months C Days D Hours E Minutes F Seconds</code> <br/>
* Any field except Seconds will be omitted if the value is 0.
*
* @author TapasB
* @return the string representation
*/
public String getDisplayString() {
StringBuilder builder = new StringBuilder();
setString(years, builder, "Years");
setString(months, builder, "Months");
setString(days, builder, "Days");
setString(hours, builder, "Hours");
setString(minutes, builder, "Minutes");
builder.append(seconds);
builder.append(" Seconds.");
return builder.toString();
}
/**
* Returns string representation of the <code>Age</code> in the following
* format: <br/>
* <code>A Years B Months C Days D Hours E Minutes F Seconds</code> <br/>
* Any field except Seconds will be omitted if the value is 0.
*
* @author TapasB
* @return the string representation
*/
@Override
public String toString() {
return getDisplayString();
}
/**
* Compares two <code>Age</code> objects.
*
* @author TapasB
* @param age
* - the other <code>Age</code> to be compared
* @return the value 0 if the argument <code>Age</code> is equal to this
* <code>Age</code>; a value less than 0 if this <code>Age</code> is
* before the <code>Age</code> argument; and a value greater than 0
* if this <code>Age</code> is after the <code>Age</code> argument.
*/
@Override
public int compareTo(Age age) {
Objects.requireNonNull(age, "age");
long thisSeconds = asSeconds();
long otherSeconds = age.asSeconds();
return Long.compare(thisSeconds, otherSeconds);
}
/**
* Creates and returns a copy of this object with the same years, months,
* days, hours, minutes and seconds.
*
* @author TapasB
* @return a clone of this instance of <code>Age</code>
*/
@Override
protected Age clone() {
return of(years, months, days, hours, minutes, seconds);
}
/**
* Returns a hash code value for the <code>Age</code> object.
*
* @author TapasB
* @return a hash code value for this object
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
Long thisSeconds = asSeconds();
int intValue = thisSeconds.intValue();
result = prime * result + (int) (intValue ^ (intValue >>> 32));
return result;
}
/**
* Indicates whether some other <code>Age</code> object is same to this one.
*
* @author TapasB
* @param obj
* - the reference object with which to compare
* @return true if this object is the same as the obj argument; false
* otherwise.
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Age age = (Age) obj;
return compareTo(age) == 0;
}
// Checks the validity of the field value
private void checkValidValue(String field, ValueRange range, long value) {
if (!range.isValidValue(value)) {
throw new DateTimeException(getInvalidFieldMessage(field, range, value));
}
}
// Returns message in case of invalid value which is used by
// DateTimeException
private String getInvalidFieldMessage(String field, ValueRange range, long value) {
return "Invalid value " + value + " for " + field + ". Valid values: (" + range.getMinimum() + ", " + range.getMaximum() + ").";
}
// Appends the field value of Age to the StringBuilder with the token
private void setString(long value, StringBuilder builder, String token) {
if (value > 0) {
builder.append(value);
builder.append(" ").append(token).append(" ");
}
}
// Calculates the amount of time with respect to the given ChronoUnit
// and ZoneId between the birth date and now
private long calculateDifference(ChronoUnit chronoUnit, ZoneId zone) {
Objects.requireNonNull(chronoUnit);
Objects.requireNonNull(zone);
ZonedDateTime dob = getDateOfBirth(zone);
ZonedDateTime now = ZonedDateTime.now(zone);
return chronoUnit.between(dob, now);
}
}
Few example code to use:
public static void getDateOfBirthTest() {
Age age = Age.of(29, 8, 28, 12, 49, 0);
System.out.println(age.getDateOfBirth());
}
public static void getAgeFromDateTest() {
Age age = Age.fromDateOfBirth(LocalDateTime.of(1984, Month.DECEMBER, 16, 7, 45, 0));
System.out.println(age);
}
public static void ageAsUnitsTest() {
Age age = Age.fromDateOfBirth(LocalDateTime.of(1984, Month.DECEMBER, 16, 7, 45, 0));
System.out.println(age.asWeeks());
}
@SuppressWarnings("resource")
public static void serializationTest() throws FileNotFoundException, IOException, ClassNotFoundException {
Age age = Age.fromDateOfBirth(LocalDateTime.of(1984, Month.DECEMBER, 16, 7, 45));
System.out.println(age);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("C:/tmp")));
outputStream.writeObject(age);
outputStream.flush();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("C:/tmp")));
age = Age.class.cast(inputStream.readObject());
System.out.println(age);
}
public static void equalsTest() {
Age age = Age.of(29, 8, 28, 12, 49, 0);
Age clone = age.clone();
System.out.println(age.equals(clone));
}
While developing this class I was stuck to calculate the difference between to date-time and I got help from one SO user and I really appreciate that.
You comments and suggestion is appreciable to improve it.
Happy coding.