/* * [Holidays.java] * * Summary: Calculate when various holidays occur. * * Copyright: (c) 1999-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 1999-09-09 to show off the Holidays class * 1.1 1999-09-10 add St Patrick's Day and Commonwealth Day * 1.2 1999-09-17 reorg so each holiday calculator is a separate class * add equinox and solstice calculators. * 1.3 1999-10-24 add Robbie Burns Day * 1.4 2000-05-08 more accurate Victoria Day calculation. * 1.5 2001-01-14 configurable default year * 1.6 2001-04-07 add inauguration day * 1.7 2002-01-08 change default date to 2003 * fix bug in Victoria Day calc. * 1.8 2002-03-16 make fromYear and toYear protected to make it easier to override * in isHoliday.java. * 1.9 2002-03-18 improve Javadoc * improve constructor for IsHoliday class * 2.0 2004-04-24 add Earth Day * 2.1 2005-02-12 change default year to 2005 * 2.2 2005-06-18 consistent bat files. * 2.3 2005-06-18 about box * 2.4 2005-07-27 added Parents' Day * added extensive Javadoc * 2.5 2005-12-01 added Human Rights Day * 2.6 2006-03-05 reformat with IntelliJ, add Javadoc * 2.7 2006-03-05 * 2.8 2006-03-05 * 2.9 2006-03-05 * 3.0 2006-03-05 * 3.1 2007-05-22 add PAD and icon, more IntelliJ inspect tidying. * 3.2 2007-10-10 additional notes. * 3.3 2007-10-13 UK vs US Christmas, calculate when get day off. * 3.4 2007-12-19 display year to ensure have correct one. * 3.5 2008-01-25 add Australia Day * 3.6 2008-02-02 add ANZAC day, Australian Queen's Birthday * 3.7 2008-02-07 add Mardi Gras * 3.8 2008-03-20 convert to JDK 1.5, sort holidays, tidy word wraps, use of rsquo * 3.9 2008-06-21 add Aboriginal Day * 4.0 2008-10-19 add Daylight Saving Start and Daylight Saving End Day * 4.1 2008-11-01 add St. Andrews day * 4.2 2008-12-03 add World AIDS day * 4.3 2009-09-15 add Change Your Password Day * 4.4 2009-11-23 add Black Friday * 4.5 2010-03-30 add Earth Hour * 4.6 2010-10-23 add Creationism Day * 4.7 2011-02-16 add ExportHolidaysAsCSV * 4.8 2013-01-29 add Darwin Day * 4.9 2013-02-10 add BC Family Day * 5.0 2014-03-17 add No Bag day * 5.1 2014-04-16 add Sweater Day * 5.2 2014-10-05 add Orange shirt day and Sorry Day */ package com.mindprod.holidays; import com.mindprod.common18.BigDate; import com.mindprod.common18.Build; import com.mindprod.common18.CMPAboutBox; import com.mindprod.common18.Common18; import com.mindprod.common18.FontFactory; import com.mindprod.common18.HybridJ; import com.mindprod.common18.JEButton; import com.mindprod.common18.Misc; import com.mindprod.common18.VersionCheck; import com.mindprod.fastcat.FastCat; import javax.swing.JApplet; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import static java.lang.System.*; // todo: display as printable calendar for month /** * Calculate when various holidays occur. *

* HolidayCalculator.java CMP Holiday Calculator HolidayCalculator Enter a year, and it displays the dates of all the * holidays in that year. If a holiday was not observed in a given year, it will not show. The classes it uses for * calculating holidays can be easily cannibalised for your own applications. This calculator does not demonstrate the * use of the IsHoliday class to rapidly determine if a given day is a holiday. *

* Major classes to look at: HolidayCalculator HolInfo IsHoliday Maker The rest are classes about * individual holidays. In a future version the individual holidays will be put into a separate * package to make it easier to find the generic stuff. *

* * @author Roedy Green, Canadian Mind Products * @version 5.2 2014-10-05 add Orange shirt day and Sorry Day * @noinspection FieldCanBeLocal * @see com.mindprod.holidays.ExportHolidaysToCSV * @since 1999-09-09 */ public final class Holidays extends JApplet { private static final Font FONT_FOR_LABELS = FontFactory.build( "Dialog", Font.BOLD, 15 ); /** * height of Applet box in pixels. Does not include surrounding frame. */ private static final int APPLET_HEIGHT = 480; /** * Width of Applet box in pixels. */ private static final int APPLET_WIDTH = 720; /** * Default year to display a list of holidays for. */ private static final int defaultYear = Misc.thisYear(); private static final int FIRST_COPYRIGHT_YEAR = 1999; /** * undisplayed copyright notice * * @noinspection UnusedDeclaration */ private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; private static final String RELEASE_DATE = "2014-10-05"; private static final String TITLE_STRING = "CMP Holiday Calculator"; private static final String VERSION_STRING = "5.2"; /** * the colour white. */ private static final Color BACKGROUND_FOR_APPLET = Color.WHITE; /** * the colour black. */ private static final Color FOREGROUND_FOR_DISPLAY = Color.BLACK; /** * the colour dark green. */ private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x008000 ); private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 ); /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 ); /** * data entry text field colour */ private static final Color FOREGROUND_FOR_ENTER = Color.BLACK; /** * background where for data entry where user enters a value. */ private static final Color BACKGROUND_FOR_EDITABLE = Color.WHITE; /** * formats output of saleAmount spinner. */ private JSpinner.NumberEditor yearNumberEditor; /** * dollar amount of sale with a spinner to increment/decrement by pennies * setValue ( double ); (Double) getValue. */ private JSpinner yearSpinner; /** * A delegate object that implements the HolInfo abstract class. That lets us execute code for a number of different * holidays given only the name of the class that computes them. We dynamically load the HolInfo classes with * Class.forName. */ private HolInfo[] holidayDelegate; /** * tracks value of the sale amount spinner. */ private SpinnerNumberModel yearSpinnerModel; /** * ACTUAL if you want the actual date of the holiday. SHIFTED if you want the date taken off work, usually the * nearest weekday. */ private JComboBox shift; /** * choice of terse or verbose calendar. */ private JComboBox terse; /** * about box */ private JEButton about; /** * prompt on how to use the Applet. */ private JLabel instructions; /** * Label for year field */ private JLabel theYearLabel; /** * Applet title */ private JLabel title; /** * title, second line */ private JLabel title2; /** * scroll results */ private JScrollPane scroller; /** * The region where we display the list of holidays */ private JTextArea display; /** * Prepare the year in a form suitable for display with BC instead of -ve sign. * * @param year (-ve for BCE) * * @return String display of year BCE for negative years */ private static String yearToDisplay( int year ) { if ( year < 0 ) { return Integer.toString( -year ) + " BCE"; } else if ( year == 0 ) { return ""; } else { return Integer.toString( year ); } } /** * allocate the components */ private void buildComponents() { title = new JLabel( TITLE_STRING + " " + VERSION_STRING ); title.setFont( FONT_FOR_TITLE ); title.setForeground( FOREGROUND_FOR_TITLE ); title.setFont( FONT_FOR_TITLE ); title.setForeground( FOREGROUND_FOR_TITLE ); title2 = new JLabel( "released:" + RELEASE_DATE + " build:" + Build.BUILD_NUMBER ); title2.setFont( FONT_FOR_TITLE2 ); title2.setForeground( FOREGROUND_FOR_TITLE ); about = new JEButton( "about" ); about.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { // open aboutbox frame new CMPAboutBox( TITLE_STRING, VERSION_STRING, "Calculate when holidays occur", "in any given year.", "freeware", RELEASE_DATE, FIRST_COPYRIGHT_YEAR, "Roedy Green", "HOLIDAYS", "1.8" ); } } ); shift = new JComboBox<>(); shift.addItem( "actual" ); shift.addItem( "nearest weekday" ); shift.setSelectedIndex( 0 ); terse = new JComboBox<>(); terse.addItem( "terse" ); terse.addItem( "verbose" ); terse.setSelectedIndex( 0 ); yearSpinner = new JSpinner(); yearSpinner.setFont( FONT_FOR_LABELS ); yearSpinner.setForeground( FOREGROUND_FOR_ENTER ); yearSpinner.setBackground( BACKGROUND_FOR_EDITABLE ); yearSpinner.setValue( defaultYear ); yearSpinnerModel = new SpinnerNumberModel( /* initial value */ defaultYear, /* min */ 1, /* max */ 9999, /* step */ 1 ); yearNumberEditor = new JSpinner.NumberEditor( yearSpinner, "####" ); yearSpinner.setModel( yearSpinnerModel ); yearSpinner.setEditor( yearNumberEditor ); theYearLabel = new JLabel( "year", JLabel.CENTER ); theYearLabel.setFont( FontFactory.build( "Dialog", Font.BOLD, 15 ) ); theYearLabel.setForeground( FOREGROUND_FOR_LABEL ); display = new JTextArea( "", 0, 0 ); display.setEditable( false ); display.setMargin( new Insets( 4, 4, 4, 4 ) ); // Serif has best chance of supporting Unicode. // So far only NT+Internet Explorer works display.setFont( FontFactory.build( "Serif", Font.PLAIN, 15 ) ); display.setForeground( FOREGROUND_FOR_DISPLAY ); scroller = new JScrollPane( display, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); instructions = new JLabel( "Select options, enter year (negative for BCE) and hit enter.", JLabel.CENTER ); instructions.setForeground( FOREGROUND_FOR_INSTRUCTIONS ); } /** * prepare list of all holidays in given year * * @param year Year for which you want a calendar prepared. * @param shifted ACTUAL = false if you want the actual date of the holiday. SHIFTED = true if you want the date * taken off work, usually the nearest weekday. * @param verbose true if you want want the full story about each holiday, false if just the summary. * * @return list of holidays and information about them occurring in that year -- not a calendar per se. */ private String displayHolidays( int year, boolean shifted, boolean verbose ) { final FastCat sb = new FastCat( holidayDelegate.length * ( verbose ? 25 : 11 ) + 5 ); sb.append( "Holidays in year " ); sb.append( year ); sb.append( "." ); if ( shifted ) { sb.append( " nearest weekday" ); } sb.append( "\n\n" ); for ( HolInfo h : holidayDelegate ) { if ( h == null ) { continue; } BigDate d = new BigDate( h.when( year, shifted ) ); if ( d.getOrdinal() == BigDate.NULL_ORDINAL ) { continue; } sb.append( d.toString() ); // yyyyy-mm-dd sb.append( " " ); sb.append( BigDate.dayName( d.getDayOfWeek() ) ); sb.append( " " ); sb.append( BigDate.monthName( d.getMM() ) ); sb.append( " " ); sb.append( d.getDD() ); sb.append( " is " ); sb.append( shifted ? " nearest weekday to " : "" ); sb.append( h.getName() ); sb.append( ".\n" ); if ( verbose ) { sb.append( h.getRule() ); sb.append( "\n" ); int yearFirstObserved = h.getFirstYear( HolInfo.OBSERVED ); int yearFirstProclaimed = h.getFirstYear( HolInfo.PROCLAIMED ); sb.append( "first observed " ); sb.append( yearToDisplay( yearFirstObserved ) ); sb.append( ". " ); if ( yearFirstObserved != yearFirstProclaimed ) { sb.append( "first proclaimed " ); sb.append( yearToDisplay( yearFirstProclaimed ) ); sb.append( "." ); } sb.append( "\n" ); String authority = h.getAuthority(); if ( authority.length() > 0 ) { sb.append( "authority: " ); sb.append( authority ); sb.append( "\n" ); } sb.append( "\n" ); } } // end for return sb.toString(); } /** * get list of holidays from properties file: com.mindprod.holidays.Holiday.properties which lives in the jar. class * names are case-sensitive. file looks like this:
Christmas=yes
GroundhogDay=no
...
where * yes=include no=ignore */ private void findHolidays() { String[][] result = null; try { // look in jar, and on classpath for // com.mindprod.holidays.Holiday.properties final InputStream fis = Holidays.class.getResourceAsStream( "Holiday.properties" ); result = Misc.loadProperties( fis ); // we now key=value pairs } catch ( IOException oops ) { out.println( oops + " Problem accessing Holiday.properties file." ); System.exit( 1 ); } // in pairs className=yes/no int length = result[ 0 ].length; int j = 0; // keep just the holidays marked yes. // in the holidayDelegate array. holidayDelegate = new HolInfo[ length ]; for ( int i = 0; i < length; i++ ) { try { if ( result[ 1 ][ i ].equalsIgnoreCase( "yes" ) ) { holidayDelegate[ j++ ] = ( HolInfo ) ( Class .forName( "com.mindprod.holidays." + result[ 0 ][ i ] ) .newInstance() ); } } catch ( Exception oops ) { out.println( oops + " Bug in Holiday.properties or class file for " + result[ 0 ][ i ] ); System.exit( 1 ); } } // end for } /** * layout components in a GridBag * * @param contentPane where to place the components. */ private void layoutComponents( Container contentPane ) { // Layout looks like this: // 0 --0------1-------- 2 // 0 -title1--title2-----about // 1 shift terse 2001 // 2 year // 3 -------results--------- // 4 -------instructions---- // 0 --0-------1 ----------2 // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title, new GridBagConstraints( 0, 0, 1, 1, 30.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 10, 10, 5, 5 ), 0, 0 ) ); contentPane.add( title2, new GridBagConstraints( 1, 0, 1, 1, 30.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 10, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( about, new GridBagConstraints( 2, 0, 1, 1, 20.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 10, 5, 10, 10 ), 10, 2 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( shift, new GridBagConstraints( 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 5 ), 150, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( terse, new GridBagConstraints( 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 0, 5 ), 150, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( yearSpinner, new GridBagConstraints( 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 0, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( theYearLabel, new GridBagConstraints( 2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 5, 10, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( scroller, new GridBagConstraints( 0, 3, 3, 1, 0.0, 100.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 10, 10, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( instructions, new GridBagConstraints( 0, 4, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 10, 10, 10 ), 0, 0 ) ); } /** * Refresh screen after shift or number change. */ private void refresh() { int year = defaultYear; try { year = yearSpinnerModel.getNumber().intValue(); } catch ( NumberFormatException e ) { // leave year at default } // avoid illegal year 0 if ( year == 0 ) { year = defaultYear; } final boolean shifted = shift.getSelectedIndex() != 0; final boolean verbose = terse.getSelectedIndex() != 0; // sort holidays in order. Arrays.sort( holidayDelegate, new HolidaySort( year, shifted ) ); // display holidays in date order display.setText( displayHolidays( year, shifted, verbose ) ); // wind back to the top to read, setValue has no effect // scroller.getVerticalScrollBar().setValue( 0 ); display.setCaretPosition( 0 ); } /** * Allow this Applet to run as as application as well. * * @param args command line arguments ignored. */ public static void main( String args[] ) { HybridJ.fireup( new Holidays(), TITLE_STRING + " " + VERSION_STRING, APPLET_WIDTH, APPLET_HEIGHT ); } // end main /** * Called by the browser or Applet viewer to inform * this Applet that it is being reclaimed and that it should destroy * any resources that it has allocated. */ public void destroy() { about = null; display = null; holidayDelegate = null; instructions = null; scroller = null; shift = null; terse = null; yearSpinner = null; theYearLabel = null; title = null; title2 = null; } /** * Called by the browser or Applet viewer to inform * this Applet that it has been loaded into the system. */ @Override public void init() { if ( !VersionCheck.isJavaVersionOK( 1, 8, 0, this ) ) { return; } Common18.setLaf(); final Container contentPane = this.getContentPane(); contentPane.setBackground( BACKGROUND_FOR_APPLET ); contentPane.setLayout( new GridBagLayout() ); buildComponents(); findHolidays(); refresh(); layoutComponents( contentPane ); // hook up listeners TheListener theListener = new TheListener(); yearSpinnerModel.addChangeListener( theListener ); shift.addItemListener( theListener ); terse.addItemListener( theListener ); this.validate(); this.setVisible( true ); } // end init /** * Inner class to act as listener to respond to all end-user actions. */ private class TheListener implements ItemListener, ChangeListener { /** * Notice any change to shift/terse choice. * * @param event details of just what the user clicked. */ public void itemStateChanged( ItemEvent event ) { final Object object = event.getSource(); if ( object == shift || object == terse ) { refresh(); } // end if } // end itemStateChanged /** * spinner changed value * * @param e event from spinner */ public void stateChanged( ChangeEvent e ) { final Object object = e.getSource(); if ( object == yearSpinnerModel ) { refresh(); } } // end actionPerformed } // end TheListener }