/*
* [Born.java]
*
* Summary: Displays birth and death dates and age at time of death, or birth and age for living person.
*
* Copyright: (c) 2008-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
*
* Licence: This software may be copied and used freely for any purpose but military.
* http://mindprod.com/contact/nonmil.html
*
* Requires: JDK 1.8+
*
* Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
*
* Version History:
* 1.0 2008-12-14 initial version
* 1.1 2009-04-25 add birthday cake, refactor code
* 1.2 2009-05-05 add deathdate. Use BigDate.isAnniversary method.
*/
package com.mindprod.htmlmacros.macro;
import com.mindprod.common18.BigDate;
import com.mindprod.common18.ST;
import com.mindprod.fastcat.FastCat;
import static java.lang.System.*;
/**
* Displays birth and death dates and age at time of death, or birth and age for living person.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.2 2009-05-05 add deathdate. Use BigDate.isAnniversary method.
* @see com.mindprod.stores.Book
* @since 2008-12-14
*/
@SuppressWarnings( { "UnusedDeclaration" } )
public final class Born extends Macro
{
// TODO: handle dates of form YYYY-MM
/**
* true if want debugging harness code included
*/
private static final boolean DEBUGGING = false;
/**
* warning message
*/
private static final String IMPROBABLE_AGE = "Warning: improbable age ";
/**
* generic error message. allows yyyy and yyyy-mm-dd but not yyyy-mm.
*/
private static final String USAGE = "\nBorn macro needs date birth-yyyy-mm-dd [death-yyyy-mm-dd] [checked]";
/**
* Make sure no future dates
*
* @param date birth or death date to check.
*
* @throws IllegalArgumentException
*/
private static void ensureNotFuture( BigDate date )
{
if ( date.compareTo( Global.TODAY ) > 0 )
{
throw new IllegalArgumentException( "Born date " + date.toString() + " is in the future." );
}
}
/**
* generate HTML to display the age now or at death.
*
* @param ageInYears how old the person is today or how old they were the day they died.
*
* @return HTML to display the age now or at death.
*/
private static String expandAge( final int ageInYears )
{
final FastCat sb = new FastCat( 3 );
sb.append( "age:" );
sb.append( ageInYears );
sb.append( "" );
return sb.toString();
}
/**
* generate HTML to display birthday
*
* @param birthDate person's birth date whose age you want expanded
* @param isBirthYearOnlyNoDay true if birth date should be displayed with year only.
* @param isBirthdayToday true if today is this person's birthday, even if they are dead.
*
* @return HTML to display the birthdate
*/
private static String expandBirthdate( final BigDate birthDate, final boolean isBirthYearOnlyNoDay, final boolean isBirthdayToday )
{
final FastCat sb = new FastCat( 5 );
sb.append( "(" );
if ( isBirthYearOnlyNoDay )
{
sb.append( birthDate.toYYYYString() );
}
else
{
sb.append( birthDate.toString() );
}
sb.append( "" );
return sb.toString();
}
/**
* generate HTML to display the date of death.
*
* @param deathDate person's death date
* @param deathYearOnly true if death date should be displayed with year only.
* @param isDeathday true if today is this person's birthday, even if they are dead.
*
* @return HTML to display the date of death.
*/
private static String expandDied( final BigDate deathDate, final boolean deathYearOnly, final boolean isDeathday )
{
final FastCat sb = new FastCat( 5 );
sb.append( "" );
if ( deathYearOnly )
{
sb.append( deathDate.toYYYYString() );
}
else
{
sb.append( deathDate.toString() );
}
sb.append( "" );
return sb.toString();
}
/**
* Trim birth and death string
*
* @param birthAndDeath string from Born macro
*
* @return Remove ()
*/
public static String trimBorn( String birthAndDeath )
{
assert !ST.isEmpty( birthAndDeath ) : "birthAndDeath string empty";
// (1921-01-19 1995-12-20 age:74)
// strip out "(" and ")"
final int offset1 = birthAndDeath.indexOf( '(' );
if ( offset1 >= 0 )
{
birthAndDeath = birthAndDeath.substring( 0, offset1 ) + birthAndDeath.substring( offset1 + 1 );
}
final int offset2 = birthAndDeath.lastIndexOf( ')' );
if ( offset2 >= 0 )
{
birthAndDeath = birthAndDeath.substring( 0, offset2 ) + birthAndDeath.substring( offset2 + 1 );
}
return birthAndDeath;
}// /method
/**
* guts of Born macro expansion, when no deathDate
*
* @param birthDate person's birth date whose age you want expanded
* @param isBirthYearOnlyNoDay true if birthDate date contains birth year only.
* @param isBirthdayToday true if today is this person's birthday, even if they are dead.
* @param checked true if we are sure the birthdate is correct, and this person has not died.
*
* @return age in years
*/
private String expandStillAlive( final BigDate birthDate,
final boolean isBirthYearOnlyNoDay,
final boolean isBirthdayToday,
final boolean checked
)
{
// compute age as of TimeZone where macros expanded at elapsedTime macros
// expanded.
final int[] ymd = BigDate.age( birthDate, Global.TODAY );
final int ageInYears = ymd[ 0 ];
// this person/organisation is ostensibly still alive
if ( DEBUGGING && !( ( 16 <= ageInYears && ageInYears <= 100 )
|| ageInYears > 115 || checked ) )
{
err.println( IMPROBABLE_AGE
+ ageInYears
+ " : "
+ birthDate.toString()
+ " : "
+ fileBeingProcessed.toString() );
}
final FastCat sb = new FastCat( 5 );
sb.append( "" );
sb.append( expandBirthdate( birthDate, isBirthYearOnlyNoDay, isBirthdayToday ) );
sb.append( " " );
sb.append( expandAge( ageInYears ) );
sb.append( ")" );
return sb.toString();
}
/**
* expand birth when dead, but death unknown
*
* @param birthDate person's birth date whose age you want expanded
* @param isBirthYearOnlyNoDay true if birth date should be displayed with year only.
* @param isBirthdayToday true if today is this person's birthday, even if they are dead.
*
* @return HTML string with birth death and age in years
*/
private String expandUnknownDead( final BigDate birthDate, final boolean isBirthYearOnlyNoDay, final boolean isBirthdayToday )
{
// this person/organisation is dead, but we do not know when
final FastCat sb = new FastCat( 3 );
sb.append( "" );
sb.append( expandBirthdate( birthDate, isBirthYearOnlyNoDay, isBirthdayToday ) );
sb.append( " age:?)" );
return sb.toString();
}
/**
* guts of Born macro expansion
*
* @param birthDate person's birth date whose age you want expanded
* @param isBirthYearOnlyNoDay true if birth date should be displayed with year only.
* @param deathDate person's death date
* @param deathYearOnly true if death date should be displayed with year only.
* @param isBirthdayToday true if today is this person's birthday, even if they are dead.
* @param isDeathday true if today is anniversary of this person's death, or today is this person's death.
* @param checked true if we are sure the birthdate and deathdate are correct.
*
* @return HTML string with birth death and age in years
*/
private String expandKnownDead( final BigDate birthDate,
final boolean isBirthYearOnlyNoDay,
final boolean isBirthdayToday,
final boolean isDeathday,
final BigDate deathDate,
final boolean deathYearOnly,
final boolean checked
)
{
final int[] ymd = BigDate.age( birthDate, deathDate );
final int ageInYears = ymd[ 0 ];
if ( DEBUGGING && !( ( 16 <= ageInYears && ageInYears <= 105 )
|| ageInYears > 115 || checked ) )
{
err.println( IMPROBABLE_AGE
+ ageInYears
+ " : "
+ birthDate.toString()
+ " : "
+ deathDate.toString()
+ " : "
+ fileBeingProcessed.toString() );
}
final FastCat sb = new FastCat( 9 );
sb.append( "" );
sb.append( expandBirthdate( birthDate, isBirthYearOnlyNoDay, isBirthdayToday ) );
sb.append( " " );
sb.append( expandDied( deathDate, deathYearOnly, isDeathday ) );
sb.append( " " );
sb.append( expandAge( ageInYears ) );
sb.append( ")" );
return sb.toString();
}
/**
* Born 1948-02-04 generates (born: 1948-02-04 age 60)
* Born 1900-01-01 2000-01-01 generates (born: 1900-01-01 died: 2000-01-01 age: 100)
*
* @param parms dates as ISO string.
* @param quiet true if want output suppressed.
* @param verbose @return expanded macro text
*/
public String expandMacro( final String[] parms,
final boolean quiet, final boolean verbose )
{
if ( !quiet )
{
out.print( "B" );
}
int parmsLength = parms.length;
if ( !( 1 <= parmsLength && parmsLength <= 3 ) )
{
throw new IllegalArgumentException( USAGE );
}
try
{
if ( parms[ 0 ].equals( "unknown" ) )
{
return ""; // empty expansion.
}
boolean checked;
if ( parms[ parmsLength - 1 ].equals( "checked" ) )
{
checked = true;
parmsLength--;
}
else
{
checked = false;
}
final boolean isBirthYearOnlyNoDay = parms[ 0 ].indexOf( '-' ) < 0;
BigDate birthDate;
final boolean isBirthdayToday;
if ( isBirthYearOnlyNoDay )
{
// yyyybc
birthDate = new BigDate( BigDate.parseYYYY( parms[ 0 ] ),
7,
1 ); // pick half way through year to maximize odds of being correct
isBirthdayToday = false;
}
else
{
// yyyy-mm-dd
birthDate = new BigDate( parms[ 0 ] );
// people with 02-29 birthdays are counted on the 28th in non leap years.
isBirthdayToday = BigDate.isAnniversary( birthDate, Global.TODAY );
}
if ( parmsLength == 1 )
{
// no death date
ensureNotFuture( birthDate );
return expandStillAlive( birthDate, isBirthYearOnlyNoDay, isBirthdayToday, checked );
}
else
{
// both birth and death date.
final boolean isDeathYearOnlyNoDay = !parms[ 1 ].equals( "unknown" ) && parms[ 1 ].indexOf( '-' ) < 0;
BigDate deathDate;
final boolean isDeathday;
if ( isDeathYearOnlyNoDay )
{
// yyyybc
deathDate = new BigDate( BigDate.parseYYYY( parms[ 1 ] ),
7,
1 ); // pick half way through year to maximize odds of being correct
isDeathday = false;
}
else
{
if ( parms[ 1 ].equals( "unknown" ) )
{
// person is dead, but we don't know when
ensureNotFuture( birthDate );
return expandUnknownDead( birthDate, isBirthYearOnlyNoDay, isBirthdayToday );
}
deathDate = new BigDate( parms[ 1 ] );
isDeathday = BigDate.isAnniversary( deathDate, Global.TODAY );
}
ensureNotFuture( birthDate );
ensureNotFuture( deathDate );
return expandKnownDead( birthDate,
isBirthYearOnlyNoDay,
isBirthdayToday,
isDeathday,
deathDate,
isDeathYearOnlyNoDay,
checked );
}
}
catch ( StringIndexOutOfBoundsException e )
{
throw new IllegalArgumentException( USAGE );
}
catch ( NumberFormatException e )
{
throw new IllegalArgumentException( USAGE );
}
}
}