classes/hirondelle/web4j/model/DateTime.java
changeset 0 3060119b1292
equal deleted inserted replaced
-1:000000000000 0:3060119b1292
       
     1 package hirondelle.web4j.model;
       
     2 
       
     3 import hirondelle.web4j.BuildImpl;
       
     4 import hirondelle.web4j.action.ActionImpl;
       
     5 import hirondelle.web4j.model.ModelUtil.NullsGo;
       
     6 import hirondelle.web4j.request.TimeZoneSource;
       
     7 import hirondelle.web4j.util.TimeSource;
       
     8 import hirondelle.web4j.util.Util;
       
     9 
       
    10 import java.io.IOException;
       
    11 import java.io.ObjectInputStream;
       
    12 import java.io.ObjectOutputStream;
       
    13 import java.io.Serializable;
       
    14 import java.util.Calendar;
       
    15 import java.util.GregorianCalendar;
       
    16 import java.util.List;
       
    17 import java.util.Locale;
       
    18 import java.util.TimeZone;
       
    19 
       
    20 /**
       
    21  Building block class for an immutable date-time, with no time zone. 
       
    22  
       
    23  <P>
       
    24  This class is provided as an alternative to java.util.{@link java.util.Date}. 
       
    25  You're strongly encouraged to use this class in your WEB4J applications, but you can still use java.util.{@link java.util.Date} if you wish.
       
    26 
       
    27 <P>This class can hold :
       
    28 <ul>
       
    29    <li>a date-and-time : <tt>1958-03-31 18:59:56.123456789</tt>
       
    30    <li>a date only : <tt>1958-03-31</tt>
       
    31    <li>a time only : <tt>18:59:56.123456789</tt>
       
    32 </ul>
       
    33 
       
    34  <P>
       
    35  <a href='#Examples'>Examples</a><br>
       
    36  <a href='#JustificationForThisClass'>Justification For This Class</a><br>
       
    37  <a href='#DatesAndTimesInGeneral'>Dates and Times In General</a><br>
       
    38  <a href='#TheApproachUsedByThisClass'>The Approach Used By This Class</a><br>
       
    39  <a href='#TwoSetsOfOperations'>Two Sets Of Operations</a><br>
       
    40  <a href='#ParsingDateTimeAcceptedFormats'>Parsing DateTime - Accepted Formats</a><br>
       
    41  <a href='#FormattingLanguage'>Mini-Language for Formatting</a><br>
       
    42  <a href='#InteractionWithTimeSource'>Interaction with {@link TimeSource}</a><br>
       
    43  <a href='#PassingDateTimeToTheDatabase'>Passing DateTime Objects to the Database</a>
       
    44 
       
    45  <a name='Examples'></a>
       
    46  <h3> Examples</h3>
       
    47  Some quick examples of using this class :
       
    48  <PRE>
       
    49   DateTime dateAndTime = new DateTime("2010-01-19 23:59:59");
       
    50   //highest precision is nanosecond, not millisecond:
       
    51   DateTime dateAndTime = new DateTime("2010-01-19 23:59:59.123456789");
       
    52   
       
    53   DateTime dateOnly = new DateTime("2010-01-19");
       
    54   DateTime timeOnly = new DateTime("23:59:59");
       
    55   
       
    56   DateTime dateOnly = DateTime.forDateOnly(2010,01,19);
       
    57   DateTime timeOnly = DateTime.forTimeOnly(23,59,59,0);
       
    58   
       
    59   DateTime dt = new DateTime("2010-01-15 13:59:15");
       
    60   boolean leap = dt.isLeapYear(); //false
       
    61   dt.getNumDaysInMonth(); //31
       
    62   dt.getStartOfMonth(); //2010-01-01, 00:00:00
       
    63   dt.getEndOfDay(); //2010-01-15, 23:59:59
       
    64   dt.format("YYYY-MM-DD"); //formats as '2010-01-15'
       
    65   dt.plusDays(30); //30 days after Jan 15
       
    66   dt.numDaysFrom(someDate); //returns an int
       
    67   dueDate.lt(someDate); //less-than
       
    68   dueDate.lteq(someDate); //less-than-or-equal-to
       
    69   
       
    70   //{@link ActionImpl#getTimeZone()} is readily available in most Actions
       
    71   DateTime.now(getTimeZone());
       
    72   DateTime.today(getTimeZone());
       
    73   DateTime fromMilliseconds = DateTime.forInstant(31313121L, getTimeZone());
       
    74   birthday.isInFuture(getTimeZone());
       
    75  </PRE>
       
    76  
       
    77  <a name='JustificationForThisClass'></a>
       
    78  <h3> Justification For This Class</h3>
       
    79  The fundamental reasons why this class exists are :
       
    80  <ul>
       
    81  <li>to avoid the embarrassing number of distasteful inadequacies in the JDK's date classes
       
    82  <li>to oppose the very "mental model" of the JDK's date-time classes with something significantly simpler 
       
    83  </ul>
       
    84  
       
    85  <a name='MentalModels'></a>
       
    86  <P><b>There are 2 distinct mental models for date-times, and they don't play well together</b> :
       
    87  <ul>
       
    88  <li><b>timeline</b> - an instant on the timeline, as a physicist would picture it, representing the number of 
       
    89  seconds from some epoch. In this picture, such a date-time can have many, many different 
       
    90  representations according to calendar and time zone. That is, the date-time, <i> as seen and understood by 
       
    91  the end user</i>, can change according to "who's looking at it". It's important to understand that a timeline instant, 
       
    92  before being presented to the user, <i>must always have an associated time zone - even in the case of 
       
    93  a date only, with no time.</i>
       
    94  <li><b>everyday</b> - a date-time in the Gregorian calendar, such as '2009-05-25 18:25:00', 
       
    95  which never changes according to "who's looking at it". Here, <i>the time zone is always both implicit and immutable</i>.
       
    96  </ul>
       
    97  
       
    98  <P>The problem is that java.util.{@link java.util.Date} uses <i>only</i> the timeline style, while <i>most</i> users, <i>most</i> 
       
    99  of the time, think in terms of the <i>other</i> mental model - the 'everday' style. 
       
   100 
       
   101  In particular, there are a large number of applications which experience 
       
   102  <a href='http://martinfowler.com/bliki/TimeZoneUncertainty.html'>problems with time zones</a>, because the timeline model 
       
   103  is used instead of the everday model.
       
   104  <i>Such problems are often seen by end users as serious bugs, because telling people the wrong date or time is often a serious issue.</i>
       
   105  <b>These problems make you look stupid.</b> 
       
   106  
       
   107  <a name='JDKDatesMediocre'></a>
       
   108  <h4>Date Classes in the JDK are Mediocre</h4>
       
   109  The JDK's classes related to dates are widely regarded as frustrating to work with, for various reasons:
       
   110  <ul>
       
   111  <li>mistakes regarding time zones are very common
       
   112  <li>month indexes are 0-based, leading to off-by-one errors
       
   113  <li>difficulty of calculating simple time intervals 
       
   114  <li><tt>java.util.Date</tt> is mutable, but 'building block' classes should be
       
   115  immutable
       
   116  <li>numerous other minor nuisances
       
   117  </ul>
       
   118  
       
   119  <a name='JodaTimeDrawbacks'></a>
       
   120  <h4>Joda Time Has Drawbacks As Well</h4>
       
   121  The <a href='http://joda-time.sourceforge.net/'>Joda Time</a> library is used by some programmers as an alternative 
       
   122  to the JDK classes. Joda Time has the following drawbacks :
       
   123  <ul>
       
   124  <li>it limits precision to milliseconds. Database timestamp values almost always have a precision of microseconds 
       
   125  or even nanoseconds. This is a serious defect: <b>a library should never truncate your data, for any reason.</b>
       
   126  <li>it's large, with well over 100 items in its <a href='http://joda-time.sourceforge.net/api-release/index.html'>javadoc</a>
       
   127  <li>in order to stay current, it needs to be manually updated occasionally with fresh time zone data 
       
   128  <li>it has mutable versions of classes
       
   129  <li>it always coerces March 31 + 1 Month to April 30 (for example), without giving you any choice in the matter
       
   130  <li>some databases allow invalid date values such as '0000-00-00', but Joda Time doesn't seem to be able to handle them   
       
   131  </ul>
       
   132  
       
   133  
       
   134  <a name='DatesAndTimesInGeneral'></a>
       
   135  <h3>Dates and Times in General</h3>
       
   136  
       
   137  <h4>Civil Timekeeping Is Complex</h4>
       
   138  Civil timekeeping is a byzantine hodge-podge of arcane and arbitrary rules. Consider the following :
       
   139  <ul>
       
   140  <li>months have varying numbers of days
       
   141  <li>one month (February) has a length which depends on the year
       
   142  <li>not all years have the same number of days
       
   143  <li>time zone rules spring forth arbitrarily from the fecund imaginations of legislators 
       
   144  <li>summer hours mean that an hour is 'lost' in the spring, while another hour must
       
   145  repeat itself in the autumn, during the switch back to normal time
       
   146  <li>summer hour logic varies widely across various jurisdictions
       
   147  <li>the cutover from the Julian calendar to the Gregorian calendar happened at different times in
       
   148  different places, which causes a varying number of days to be 'lost' during the cutover
       
   149  <li>occasional insertion of leap seconds are used to ensure synchronization with the
       
   150  rotating Earth (whose speed of rotation is gradually slowing down, in an irregular way)
       
   151  <li>there is no year 0 (1 BC is followed by 1 AD), except in the reckoning used by
       
   152  astronomers
       
   153  </ul>
       
   154  
       
   155  <h4>How Databases Treat Dates</h4>
       
   156  <b>Most databases model dates and times using the Gregorian Calendar in an aggressively simplified form</b>,
       
   157  in which :
       
   158  <ul>
       
   159  <li>the Gregorian calendar is extended back in time as if it was in use previous to its
       
   160  inception (the 'proleptic' Gregorian calendar)
       
   161  <li>the transition between Julian and Gregorian calendars is entirely ignored
       
   162  <li>leap seconds are entirely ignored
       
   163  <li>summer hours are entirely ignored
       
   164  <li>often, even time zones are ignored, in the sense that <i>the underlying database
       
   165  column doesn't usually explicitly store any time zone information</i>. 
       
   166  </ul>
       
   167  
       
   168  <P><a name='NoTimeZoneInDb'></a>The final point requires elaboration.
       
   169  Some may doubt its veracity, since they have seen date-time information "change time zone" when 
       
   170  retrieved from a database. But this sort of change is usually applied using logic which is <i>external</i> to the data 
       
   171  stored in the particular column.  
       
   172  
       
   173  <P> For example, the following items might be used in the calculation of a time zone difference :
       
   174  <ul>
       
   175  <li>time zone setting for the client (or JDBC driver) 
       
   176  <li>time zone setting for the client's connection to the database server
       
   177  <li>time zone setting of the database server
       
   178  <li>time zone setting of the host where the database server resides
       
   179  </ul>
       
   180 
       
   181  <P>(Note as well what's <i>missing</i> from the above list: your own application's logic, and the user's time zone preference.) 
       
   182 
       
   183  <P>When an end user sees such changes to a date-time, all they will say to you is 
       
   184  <i>"Why did you change it? That's not what I entered"</i> - and this is a completely valid question. 
       
   185  Why <i>did</i> you change it? Because you're using the timeline model instead of the everyday model. 
       
   186  Perhaps you're using a inappropriate abstraction for what the user really wants. 
       
   187  
       
   188 <a name='TheApproachUsedByThisClass'></a>
       
   189  <h3>The Approach Used By This Class</h3>
       
   190  
       
   191  This class takes the following design approach :
       
   192  <ul>
       
   193  <li>it models time in the "everyday" style, not in the "timeline" style (see <a href='#MentalModels'>above</a>)
       
   194  <li>its precision matches the highest precision used by databases (nanosecond)
       
   195  <li>it uses only the proleptic Gregorian Calendar, over the years <tt>1..9999</tt> 
       
   196  <li><i>it ignores all non-linearities</i>: summer-hours, leap seconds, and the cutover
       
   197  from Julian to Gregorian calendars
       
   198  <li><i>it ignores time zones</i>. Most date-times are stored in columns whose type
       
   199  does <i>not</i> include time zone information (see note <a href='#NoTimeZoneInDb'>above</a>).
       
   200  <li>it has (very basic) support for wonky dates, such as the magic value <tt>0000-00-00</tt> used by MySQL
       
   201  <li>it's immutable
       
   202  <li>it lets you choose among 4 policies for 'day overflow' conditions during calculations
       
   203  <li>it talks to your {@link TimeSource} implementation when returning the current moment, allowing you to customise dates during testing
       
   204  </ul>
       
   205  
       
   206  <P>Even though the above list may appear restrictive, it's very likely true that
       
   207  <tt>DateTime</tt> can handle the dates and times you're currently storing in your database.
       
   208  
       
   209 <a name='TwoSetsOfOperations'></a>
       
   210  <h3>Two Sets Of Operations</h3>
       
   211  This class allows for 2 sets of operations: a few "basic" operations, and many "computational" ones.
       
   212  
       
   213  <P><b>Basic operations</b> model the date-time as a simple, dumb String, with absolutely no parsing or substructure. 
       
   214  This will always allow your application to reflect exactly what is in a <tt>ResultSet</tt>, with
       
   215  absolutely no modification for time zone, locale, or for anything else. 
       
   216  
       
   217  <P>This is meant as a back-up, to ensure that <i>your application will always be able
       
   218  to, at the very least, display a date-time exactly as it appears in your
       
   219  <tt>ResultSet</tt> from the database</i>. This style is particularly useful for handling invalid
       
   220  dates such as <tt>2009-00-00</tt>, which can in fact be stored by some databases (MySQL, for
       
   221  example). It can also be used to handle unusual items, such as MySQL's 
       
   222  <a href='http://dev.mysql.com/doc/refman/5.1/en/time.html'>TIME</a> datatype.
       
   223  
       
   224  <P>The basic operations are represented by {@link #DateTime(String)}, {@link #toString()}, and {@link #getRawDateString()}.
       
   225  
       
   226  <P><b>Computational operations</b> allow for calculations and formatting. 
       
   227  If a computational operation is performed by this class (for example, if the caller asks for the month), 
       
   228  then any underlying date-time String must be parseable by this class into its components - year, month, day, and so on. 
       
   229  Computational operations require such parsing, while the basic operations do not. Almost all methods in this class 
       
   230  are categorized as computational operations.
       
   231  
       
   232  <a name="ParsingDateTimeAcceptedFormats"></a>
       
   233  <h3>Parsing DateTime - Accepted Formats</h3>
       
   234   The {@link #DateTime(String)} constructor accepts a <tt>String</tt> representation of a date-time.
       
   235  The format of the String can take a number of forms. When retrieving date-times from a database, the 
       
   236  majority of cases will have little problem in conforming to these formats. If necessary, your SQL statements 
       
   237  can almost always use database formatting functions to generate a String whose format conforms to one of the 
       
   238  many formats accepted by the {@link #DateTime(String)} constructor.  
       
   239  
       
   240  <a name="FormattingLanguage"></a>
       
   241  <h3>Mini-Language for Formatting</h3>
       
   242  This class defines a simple mini-language for formatting a <tt>DateTime</tt>, used by the various <tt>format</tt> methods. 
       
   243  
       
   244  <P>The following table defines the symbols used by this mini-language, and the corresponding text they 
       
   245  would generate given the date:
       
   246  <PRE>1958-04-09 Wednesday, 03:05:06.123456789 AM</PRE>
       
   247  in an English Locale. (Items related to date are in upper case, and items related to time are in lower case.)
       
   248  
       
   249  <P><table border='1' cellpadding='3' cellspacing='0'>
       
   250  <tr><th>Format</th><th>Output</th> <th>Description</th><th>Needs Locale?</th></tr>
       
   251  <tr><td>YYYY</td> <td>1958</td> <td>Year</td><td>...</td></tr>
       
   252  <tr><td>YY</td> <td>58</td> <td>Year without century</td><td>...</td></tr>
       
   253  <tr><td>M</td> <td>4</td> <td>Month 1..12</td><td>...</td></tr>
       
   254  <tr><td>MM</td> <td>04</td> <td>Month 01..12</td><td>...</td></tr>
       
   255  <tr><td>MMM</td> <td>Apr</td> <td>Month Jan..Dec</td><td>Yes</td></tr>
       
   256  <tr><td>MMMM</td> <td>April</td> <td>Month January..December</td><td>Yes</td></tr>
       
   257  <tr><td>DD</td> <td>09</td> <td>Day 01..31</td><td>...</td></tr>
       
   258  <tr><td>D</td> <td>9</td> <td>Day 1..31</td><td>...</td></tr>
       
   259  <tr><td>WWWW</td> <td>Wednesday</td> <td>Weekday Sunday..Saturday</td><td>Yes</td></tr>
       
   260  <tr><td>WWW</td> <td>Wed</td> <td>Weekday Sun..Sat</td><td>Yes</td></tr>
       
   261  <tr><td>hh</td> <td>03</td> <td>Hour 01..23</td><td>...</td></tr>
       
   262  <tr><td>h</td> <td>3</td> <td>Hour 1..23</td><td>...</td></tr>
       
   263  <tr><td>hh12</td> <td>03</td> <td>Hour 01..12</td><td>...</td></tr>
       
   264  <tr><td>h12</td> <td>3</td> <td>Hour 1..12</td><td>...</td></tr>
       
   265  <tr><td>a</td> <td>AM</td> <td>AM/PM Indicator</td><td>Yes</td></tr>
       
   266  <tr><td>mm</td> <td>05</td> <td>Minutes 01..59</td><td>...</td></tr>
       
   267  <tr><td>m</td> <td>5</td> <td>Minutes 1..59</td><td>...</td></tr>
       
   268  <tr><td>ss</td> <td>06</td> <td>Seconds 01..59</td><td>...</td></tr>
       
   269  <tr><td>s</td> <td>6</td> <td>Seconds 1..59</td><td>...</td></tr>
       
   270  <tr><td>f</td> <td>1</td> <td>Fractional Seconds, 1 decimal</td><td>...</td></tr>
       
   271  <tr><td>ff</td> <td>12</td> <td>Fractional Seconds, 2 decimals</td><td>...</td></tr>
       
   272  <tr><td>fff</td> <td>123</td> <td>Fractional Seconds, 3 decimals</td><td>...</td></tr>
       
   273  <tr><td>ffff</td> <td>1234</td> <td>Fractional Seconds, 4 decimals</td><td>...</td></tr>
       
   274  <tr><td>fffff</td> <td>12345</td> <td>Fractional Seconds, 5 decimals</td><td>...</td></tr>
       
   275  <tr><td>ffffff</td> <td>123456</td> <td>Fractional Seconds, 6 decimals</td><td>...</td></tr>
       
   276  <tr><td>fffffff</td> <td>1234567</td> <td>Fractional Seconds, 7 decimals</td><td>...</td></tr>
       
   277  <tr><td>ffffffff</td> <td>12345678</td> <td>Fractional Seconds, 8 decimals</td><td>...</td></tr>
       
   278  <tr><td>fffffffff</td> <td>123456789</td> <td>Fractional Seconds, 9 decimals</td><td>...</td></tr>
       
   279  <tr><td>|</td> <td>(no example)</td> <td>Escape character</td><td>...</td></tr>
       
   280  </table>
       
   281 
       
   282  <P>As indicated above, some of these symbols can only be used with an accompanying <tt>Locale</tt>.
       
   283  In general, if the output is text, not a number, then a <tt>Locale</tt> will be needed. 
       
   284  For example, 'September' is localizable text, while '09' is a numeric representation, which doesn't require a <tt>Locale</tt>.
       
   285  Thus, the symbol 'MM' can be used without a <tt>Locale</tt>, while 'MMMM' and 'MMM' both require a <tt>Locale</tt>, since they 
       
   286  generate text, not a number. 
       
   287 
       
   288  <P>The fractional seconds 'f' does not perform any rounding. 
       
   289  
       
   290 <P> The escape character '|' allows you to insert arbitrary text. 
       
   291  The escape character always appears in pairs; these pairs define a range of characters over which 
       
   292  the text will not be interpreted using the special format symbols defined above. 
       
   293  
       
   294  <P>Examples :
       
   295  <table border='1' cellpadding='3' cellspacing='0'>
       
   296  <tr><th>Format</th><th>Output</th></tr>
       
   297  <tr><td>YYYY-MM-DD hh:mm:ss.fffffffff a</td> <td>1958-04-09 03:05:06.123456789 AM</td></tr>
       
   298  <tr><td>YYYY-MM-DD hh:mm:ss.fff a</td> <td>1958-04-09 03:05:06.123 AM</td></tr>
       
   299  <tr><td>YYYY-MM-DD</td> <td>1958-04-09</td></tr>
       
   300  <tr><td>hh:mm:ss.fffffffff</td> <td>03:05:06.123456789</td></tr>
       
   301  <tr><td>hh:mm:ss</td> <td>03:05:06</td></tr>
       
   302  <tr><td>YYYY-M-D h:m:s</td> <td>1958-4-9 3:5:6</td></tr>
       
   303  <tr><td>WWWW, MMMM D, YYYY</td> <td>Wednesday, April 9, 1958</td></tr>
       
   304  <tr><td>WWWW, MMMM D, YYYY |at| D a</td> <td>Wednesday, April 9, 1958 at 3 AM</td></tr>
       
   305  </table>
       
   306  
       
   307  <P>In the last example, the escape characters are needed only because 'a', the formating symbol for am/pm, appears in the text. 
       
   308  
       
   309  <a name='InteractionWithTimeSource'></a>
       
   310  <h3>Interaction with {@link TimeSource}</h3>
       
   311  The methods of this class related to the current date interact with your configured implementation 
       
   312  of {@link hirondelle.web4j.util.TimeSource}. That is, {@link #now(TimeZone)} and {@link #today(TimeZone)}
       
   313  will return values derived from {@link TimeSource}. Thus, callers of this class automatically use any
       
   314  'fake' system clock that you may want to define.
       
   315  
       
   316  <a name='PassingDateTimeToTheDatabase'></a>
       
   317  <h3>Passing DateTime Objects to the Database</h3>
       
   318  When a <tt>DateTime</tt> is passed as a parameter to an SQL statement, the <tt>DateTime</tt> is 
       
   319  formatted into a <tt>String</tt> of a form accepted by the database. There are two mechanisms to 
       
   320  accomplish this
       
   321  <ul>
       
   322    <li>in your DAO code, format the <tt>DateTime</tt> explicitly as a String, using one of the <tt>format</tt> methods, 
       
   323    and pass the <tt>String</tt> as the parameter to the SQL statement, and not the actual <tt>DateTime</tt>
       
   324    <li>pass the <tt>DateTime</tt> itself. In this case, WEB4J will use the setting in <tt>web.xml</tt> named
       
   325    <tt>DateTimeFormatForPassingParamsToDb</tt> to perform the formatting for you. If the formats defined by this 
       
   326    setting are not appropriate for a given case, you will need to format the <tt>DateTime</tt> as a String explicitly instead. 
       
   327  </ul>
       
   328  */
       
   329 public final class DateTime implements Comparable<DateTime>, Serializable {
       
   330 
       
   331   /** The seven parts of a <tt>DateTime</tt> object. The <tt>DAY</tt> represents the day of the month (1..31), not the weekday. */
       
   332   public enum Unit {
       
   333     YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, NANOSECONDS;
       
   334   }
       
   335 
       
   336   /**
       
   337    Policy for treating 'day-of-the-month overflow' conditions encountered during some date calculations.
       
   338    
       
   339    <P>Months are different from other units of time, since the length of a month is not fixed, but rather varies with 
       
   340    both month and year. This leads to problems. Take the following simple calculation, for example :
       
   341    
       
   342    <PRE>May 31 + 1 month = ?</PRE>
       
   343    
       
   344    <P>What's the answer? Since there is no such thing as June 31, the result of this operation is inherently ambiguous. 
       
   345    This  <tt>DayOverflow</tt> enumeration lists the various policies for treating such situations, as supported by 
       
   346    <tt>DateTime</tt>.
       
   347    
       
   348    <P>This table illustrates how the policies behave :
       
   349    <P><table BORDER="1" CELLPADDING="3" CELLSPACING="0">
       
   350    <tr>
       
   351    <th>Date</th>
       
   352    <th>DayOverflow</th>
       
   353    <th>Result</th>
       
   354    </tr>    
       
   355    <tr>
       
   356    <td>May 31 + 1 Month</td>
       
   357    <td>LastDay</td>
       
   358    <td>June 30</td>
       
   359    </tr>    
       
   360    <tr>
       
   361    <td>May 31 + 1 Month</td>
       
   362    <td>FirstDay</td>
       
   363    <td>July 1</td>
       
   364    </tr>    
       
   365    <tr>
       
   366    <td>December 31, 2001 + 2 Months</td>
       
   367    <td>Spillover</td>
       
   368    <td>March 3</td>
       
   369    </tr>    
       
   370    <tr>
       
   371    <td>May 31 + 1 Month</td>
       
   372    <td>Abort</td>
       
   373    <td>RuntimeException</td>
       
   374    </tr>    
       
   375    </table> 
       
   376    */
       
   377   public enum DayOverflow {
       
   378     /** Coerce the day to the last day of the month. */
       
   379     LastDay,
       
   380     /** Coerce the day to the first day of the next month. */
       
   381     FirstDay,
       
   382     /** Spillover the day into the next month. */
       
   383     Spillover,
       
   384     /** Throw a RuntimeException. */
       
   385     Abort;
       
   386   }
       
   387 
       
   388   /**
       
   389    Constructor taking a date-time as a String. 
       
   390    
       
   391    <P>This constructor is called when WEB4J's data layer needs to translate a column in a <tt>ResultSet</tt>
       
   392    into a <tt>DateTime</tt>.
       
   393    
       
   394    <P> When this constructor is called, the underlying text can be in an absolutely arbitrary
       
   395    form, since it will not, initially, be parsed in any way. This policy of extreme
       
   396    leniency allows you to use dates in an arbitrary format, without concern over possible
       
   397    transformations of the date (time zone in particular), and without concerns over possibly bizarre content, such 
       
   398    as '2005-00-00', as seen in some databases, such as MySQL.
       
   399    
       
   400    <P><i>However</i>, the moment you attempt to call <a href='#TwoSetsOfOperations'>almost any method</a>
       
   401    in this class, an attempt will be made to parse 
       
   402    the given date-time string into its constituent parts. Then, if the date-time string does not match one of the 
       
   403    example formats listed below, a <tt>RuntimeException</tt> will be thrown.
       
   404    
       
   405    <P>Before calling this constructor, you may wish to call {@link #isParseable(String)} to explicitly test whether a 
       
   406    given String is parseable by this class.
       
   407    
       
   408    <P>The full date format expected by this class is <tt>'YYYY-MM-YY hh:mm:ss.fffffffff'</tt>. 
       
   409    All fields except for the fraction of a second have a fixed width.
       
   410    In addition, various portions of this format are also accepted by this class.
       
   411    
       
   412    <P>All of the following dates can be parsed by this class to make a <tt>DateTime</tt> :
       
   413    <ul>
       
   414    <li><tt>2009-12-31 00:00:00.123456789</tt>
       
   415    <li><tt>2009-12-31T00:00:00.123456789</tt>
       
   416    <li><tt>2009-12-31 00:00:00.12345678</tt>
       
   417    <li><tt>2009-12-31 00:00:00.1234567</tt>
       
   418    <li><tt>2009-12-31 00:00:00.123456</tt>
       
   419    <li><tt>2009-12-31 23:59:59.12345</tt>
       
   420    <li><tt>2009-01-31 16:01:01.1234</tt>
       
   421    <li><tt>2009-01-01 16:59:00.123</tt>
       
   422    <li><tt>2009-01-01 16:00:01.12</tt>
       
   423    <li><tt>2009-02-28 16:25:17.1</tt>
       
   424    <li><tt>2009-01-01 00:01:01</tt>
       
   425    <li><tt>2009-01-01 16:01</tt>
       
   426    <li><tt>2009-01-01 16</tt>
       
   427    <li><tt>2009-01-01</tt>
       
   428    <li><tt>2009-01</tt>
       
   429    <li><tt>2009</tt>
       
   430    <li><tt>0009</tt>
       
   431    <li><tt>9</tt>
       
   432    <li><tt>00:00:00.123456789</tt>
       
   433    <li><tt>00:00:00.12345678</tt>
       
   434    <li><tt>00:00:00.1234567</tt>
       
   435    <li><tt>00:00:00.123456</tt>
       
   436    <li><tt>23:59:59.12345</tt>
       
   437    <li><tt>01:59:59.1234</tt>
       
   438    <li><tt>23:01:59.123</tt>
       
   439    <li><tt>00:00:00.12</tt>
       
   440    <li><tt>00:59:59.1</tt>
       
   441    <li><tt>23:59:00</tt>
       
   442    <li><tt>23:00:10</tt>
       
   443    <li><tt>00:59</tt>
       
   444    </ul>
       
   445    
       
   446    <P>The range of each field is :
       
   447    <ul>
       
   448    <li>year: 1..9999 (leading zeroes are optional)
       
   449    <li>month: 01..12
       
   450    <li>day: 01..31
       
   451    <li>hour: 00..23
       
   452    <li>minute: 00..59
       
   453    <li>second: 00..59
       
   454    <li>nanosecond: 0..999999999
       
   455    </ul>
       
   456    
       
   457    <P>Note that <b>database format functions</b> are an option when dealing with date formats. 
       
   458    Since your application is always in control of the SQL used to talk to the database, you can, if needed, usually
       
   459     use database format functions to alter the format of dates returned in a <tt>ResultSet</tt>.
       
   460    */
       
   461   public DateTime(String aDateTime) {
       
   462     fIsAlreadyParsed = false;
       
   463     if (aDateTime == null) {
       
   464       throw new IllegalArgumentException("String passed to DateTime constructor is null. You can use an empty string, but not a null reference.");
       
   465     }
       
   466     fDateTime = aDateTime;
       
   467   }
       
   468   
       
   469   /**
       
   470    Return <tt>true</tt> only if the given String follows one of the formats documented by {@link #DateTime(String)}.
       
   471    <P>If the text is not from a trusted source, then the caller may use this method to validate whether the text 
       
   472    is in a form that's parseable by this class.    
       
   473   */
       
   474   public static boolean isParseable(String aCandidateDateTime){
       
   475     boolean result = true;
       
   476     try {
       
   477       DateTime dt = new DateTime(aCandidateDateTime);
       
   478       dt.ensureParsed();
       
   479     }
       
   480     catch (RuntimeException ex){
       
   481       result = false;
       
   482     }
       
   483     return result;
       
   484   }
       
   485   
       
   486 
       
   487   /**
       
   488    Constructor taking each time unit explicitly.
       
   489    
       
   490    <P>Although all parameters are optional, many operations on this class require year-month-day to be 
       
   491    present. 
       
   492    
       
   493    @param aYear 1..9999, optional 
       
   494    @param aMonth 1..12 , optional
       
   495    @param aDay 1..31, cannot exceed the number of days in the given month/year, optional
       
   496    @param aHour 0..23, optional
       
   497    @param aMinute 0..59, optional
       
   498    @param aSecond 0..59, optional
       
   499    @param aNanoseconds 0..999,999,999, optional (allows for databases that store timestamps up to
       
   500    nanosecond precision).
       
   501    */
       
   502   public DateTime(Integer aYear, Integer aMonth, Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanoseconds) {
       
   503     fIsAlreadyParsed = true;
       
   504     fYear = aYear;
       
   505     fMonth = aMonth;
       
   506     fDay = aDay;
       
   507     fHour = aHour;
       
   508     fMinute = aMinute;
       
   509     fSecond = aSecond;
       
   510     fNanosecond = aNanoseconds;
       
   511     validateState();
       
   512   }
       
   513 
       
   514   /**
       
   515    Factory method returns a <tt>DateTime</tt> having year-month-day only, with no time portion.
       
   516    <P>See {@link #DateTime(Integer, Integer, Integer, Integer, Integer, Integer, Integer)} for constraints on the parameters.
       
   517    */
       
   518   public static DateTime forDateOnly(Integer aYear, Integer aMonth, Integer aDay) {
       
   519     return new DateTime(aYear, aMonth, aDay, null, null, null, null);
       
   520   }
       
   521 
       
   522   /**
       
   523    Factory method returns a <tt>DateTime</tt> having hour-minute-second-nanosecond only, with no date portion.
       
   524    <P>See {@link #DateTime(Integer, Integer, Integer, Integer, Integer, Integer, Integer)} for constraints on the parameters.
       
   525    */
       
   526   public static DateTime forTimeOnly(Integer aHour, Integer aMinute, Integer aSecond, Integer aNanoseconds) {
       
   527     return new DateTime(null, null, null, aHour, aMinute, aSecond, aNanoseconds);
       
   528   }
       
   529 
       
   530   /** 
       
   531    Constructor taking a millisecond value and a {@link TimeZone}.
       
   532    This constructor may be use to convert a <tt>java.util.Date</tt> into a <tt>DateTime</tt>.
       
   533    
       
   534    <P>To use nanosecond precision, please use {@link #forInstantNanos(long, TimeZone)} instead.
       
   535    
       
   536    @param aMilliseconds must be in the range corresponding to the range of dates supported by this class (year 1..9999); corresponds 
       
   537    to a millisecond instant on the timeline, measured from the epoch used by {@link java.util.Date}.
       
   538    */
       
   539   public static DateTime forInstant(long aMilliseconds, TimeZone aTimeZone) {
       
   540     Calendar calendar = new GregorianCalendar(aTimeZone);
       
   541     calendar.setTimeInMillis(aMilliseconds);
       
   542     int year = calendar.get(Calendar.YEAR);
       
   543     int month = calendar.get(Calendar.MONTH) + 1; // 0-based
       
   544     int day = calendar.get(Calendar.DAY_OF_MONTH);
       
   545     int hour = calendar.get(Calendar.HOUR_OF_DAY); // 0..23
       
   546     int minute = calendar.get(Calendar.MINUTE);
       
   547     int second = calendar.get(Calendar.SECOND);
       
   548     int milliseconds = calendar.get(Calendar.MILLISECOND);
       
   549     int nanoseconds = milliseconds * 1000 * 1000;
       
   550     return new DateTime(year, month, day, hour, minute, second, nanoseconds);
       
   551   }
       
   552   
       
   553   /**
       
   554     For the given time zone,  return the corresponding time in milliseconds-since-epoch for this <tt>DateTime</tt>.
       
   555     
       
   556     <P>This method is meant to help you convert between a <tt>DateTime</tt> and the 
       
   557     JDK's date-time classes, which are based on the combination of a time zone and a 
       
   558     millisecond value from the Java epoch.
       
   559     <P>Since <tt>DateTime</tt> can go to nanosecond accuracy, the return value can 
       
   560     lose precision. The nanosecond value is truncated to milliseconds, not rounded.
       
   561     To retain nanosecond accuracy, please use {@link #getNanosecondsInstant(TimeZone)} instead.
       
   562    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   563   */
       
   564   public long getMilliseconds(TimeZone aTimeZone){
       
   565     Integer year = getYear();
       
   566     Integer month = getMonth();
       
   567     Integer day = getDay();
       
   568     //coerce missing times to 0:
       
   569     Integer hour = getHour() == null ? 0 : getHour();
       
   570     Integer minute = getMinute() == null ? 0 : getMinute();
       
   571     Integer second = getSecond() == null ? 0 : getSecond();
       
   572     Integer nanos = getNanoseconds() == null ? 0 : getNanoseconds();
       
   573     
       
   574     Calendar calendar = new GregorianCalendar(aTimeZone);
       
   575     calendar.set(Calendar.YEAR, year);
       
   576     calendar.set(Calendar.MONTH, month-1); // 0-based
       
   577     calendar.set(Calendar.DAY_OF_MONTH, day);
       
   578     calendar.set(Calendar.HOUR_OF_DAY, hour); // 0..23
       
   579     calendar.set(Calendar.MINUTE, minute);
       
   580     calendar.set(Calendar.SECOND, second);
       
   581     calendar.set(Calendar.MILLISECOND, nanos/1000000);
       
   582     
       
   583     return calendar.getTimeInMillis();
       
   584   }
       
   585   
       
   586   /** 
       
   587   Constructor taking a nanosecond value and a {@link TimeZone}.
       
   588  
       
   589   <P>To use milliseconds instead of nanoseconds, please use {@link #forInstant(long, TimeZone)}.
       
   590  
       
   591   @param aNanoseconds must be in the range corresponding to the range of dates supported by this class (year 1..9999); corresponds 
       
   592   to a nanosecond instant on the time-line, measured from the epoch used by {@link java.util.Date}.
       
   593  */
       
   594   public static DateTime forInstantNanos(long aNanoseconds, TimeZone aTimeZone) {
       
   595     //these items can be of either sign
       
   596     long millis = aNanoseconds / MILLION; //integer division truncates towards 0, doesn't round
       
   597     long nanosRemaining = aNanoseconds % MILLION; //size 0..999,999
       
   598     //when negative: go to the previous millis, and take the complement of nanosRemaining
       
   599     if(aNanoseconds < 0){
       
   600       millis = millis - 1;
       
   601       nanosRemaining = MILLION + nanosRemaining; //-1 remaining coerced to 999,999
       
   602     }
       
   603     
       
   604     //base calculation in millis
       
   605     Calendar calendar = new GregorianCalendar(aTimeZone);
       
   606     calendar.setTimeInMillis(millis);
       
   607     int year = calendar.get(Calendar.YEAR);
       
   608     int month = calendar.get(Calendar.MONTH) + 1; // 0-based
       
   609     int day = calendar.get(Calendar.DAY_OF_MONTH);
       
   610     int hour = calendar.get(Calendar.HOUR_OF_DAY); // 0..23
       
   611     int minute = calendar.get(Calendar.MINUTE);
       
   612     int second = calendar.get(Calendar.SECOND);
       
   613     int milliseconds = calendar.get(Calendar.MILLISECOND);
       
   614     
       
   615     DateTime withoutNanos = new DateTime(year, month, day, hour, minute, second, milliseconds * MILLION);
       
   616     //adjust for nanos - this cast is acceptable, because the value's range is 0..999,999:
       
   617     DateTime withNanos = withoutNanos.plus(0, 0,  0, 0, 0, 0, (int)nanosRemaining, DayOverflow.Spillover);
       
   618     return withNanos;
       
   619   }
       
   620   
       
   621   /**
       
   622    For the given time zone,  return the corresponding time in nanoseconds-since-epoch for this <tt>DateTime</tt>.
       
   623   
       
   624    <P>For conversion between a <tt>DateTime</tt> and the JDK's date-time classes, 
       
   625    you should likely use {@link #getMilliseconds(TimeZone)} instead. 
       
   626   <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   627  */
       
   628   public long getNanosecondsInstant(TimeZone aTimeZone){
       
   629     // these are always positive:
       
   630     Integer year = getYear();
       
   631     Integer month = getMonth();
       
   632     Integer day = getDay();
       
   633     //coerce missing times to 0:
       
   634     Integer hour = getHour() == null ? 0 : getHour();
       
   635     Integer minute = getMinute() == null ? 0 : getMinute();
       
   636     Integer second = getSecond() == null ? 0 : getSecond();
       
   637     Integer nanos = getNanoseconds() == null ? 0 : getNanoseconds();
       
   638     
       
   639     int millis = nanos / MILLION; //integer division truncates, doesn't round
       
   640     int nanosRemaining = nanos % MILLION; //0..999,999 - always positive
       
   641     
       
   642     //base calculation in millis
       
   643     Calendar calendar = new GregorianCalendar(aTimeZone);
       
   644     calendar.set(Calendar.YEAR, year);
       
   645     calendar.set(Calendar.MONTH, month-1); // 0-based
       
   646     calendar.set(Calendar.DAY_OF_MONTH, day);
       
   647     calendar.set(Calendar.HOUR_OF_DAY, hour); // 0..23
       
   648     calendar.set(Calendar.MINUTE, minute);
       
   649     calendar.set(Calendar.SECOND, second);
       
   650     calendar.set(Calendar.MILLISECOND, millis);
       
   651     
       
   652     long baseResult = calendar.getTimeInMillis() * MILLION; // either sign
       
   653     //the adjustment for nanos is always positive, toward the future:
       
   654     return baseResult + nanosRemaining;
       
   655   }
       
   656   
       
   657   /**
       
   658    Return the raw date-time String passed to the {@link #DateTime(String)} constructor.
       
   659    Returns <tt>null</tt> if that constructor was not called. See {@link #toString()} as well.
       
   660    */
       
   661   public String getRawDateString() {
       
   662     return fDateTime;
       
   663   }
       
   664 
       
   665   /** Return the year, 1..9999. */
       
   666   public Integer getYear() {
       
   667     ensureParsed();
       
   668     return fYear;
       
   669   }
       
   670 
       
   671   /** Return the Month, 1..12. */
       
   672   public Integer getMonth() {
       
   673     ensureParsed();
       
   674     return fMonth;
       
   675   }
       
   676 
       
   677   /** Return the Day of the Month, 1..31. */
       
   678   public Integer getDay() {
       
   679     ensureParsed();
       
   680     return fDay;
       
   681   }
       
   682 
       
   683   /** Return the Hour, 0..23. */
       
   684   public Integer getHour() {
       
   685     ensureParsed();
       
   686     return fHour;
       
   687   }
       
   688 
       
   689   /** Return the Minute, 0..59. */
       
   690   public Integer getMinute() {
       
   691     ensureParsed();
       
   692     return fMinute;
       
   693   }
       
   694 
       
   695   /** Return the Second, 0..59. */
       
   696   public Integer getSecond() {
       
   697     ensureParsed();
       
   698     return fSecond;
       
   699   }
       
   700 
       
   701   /** Return the Nanosecond, 0..999999999. */
       
   702   public Integer getNanoseconds() {
       
   703     ensureParsed();
       
   704     return fNanosecond;
       
   705   }
       
   706 
       
   707   /**
       
   708    Return the Modified Julian Day Number. 
       
   709    <P>The Modified Julian Day Number is defined by astronomers for simplifying the calculation of the number of days between 2 dates. 
       
   710    Returns a monotonically increasing sequence number. 
       
   711    Day 0 is November 17, 1858 00:00:00 (whose Julian Date was 2400000.5).
       
   712    
       
   713    <P>Using the Modified Julian Day Number instead of the Julian Date has 2 advantages:
       
   714    <ul>
       
   715    <li>it's a smaller number
       
   716    <li>it starts at midnight, not noon (Julian Date starts at noon)
       
   717    </ul>
       
   718    
       
   719    <P>Does not reflect any time portion, if present. 
       
   720    
       
   721    <P>(In spite of its name, this method, like all other methods in this class, uses the 
       
   722    proleptic Gregorian calendar - not the Julian calendar.)
       
   723    
       
   724    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   725    */
       
   726   public Integer getModifiedJulianDayNumber() {
       
   727     ensureHasYearMonthDay();
       
   728     int result = calculateJulianDayNumberAtNoon() - 1 - EPOCH_MODIFIED_JD;
       
   729     return result;
       
   730   }
       
   731 
       
   732   /**
       
   733    Return an index for the weekday for this <tt>DateTime</tt>.
       
   734    Returns 1..7 for Sunday..Saturday.
       
   735    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   736    */
       
   737   public Integer getWeekDay() {
       
   738     ensureHasYearMonthDay();
       
   739     int dayNumber = calculateJulianDayNumberAtNoon() + 1;
       
   740     int index = dayNumber % 7;
       
   741     return index + 1;
       
   742   }
       
   743 
       
   744   /**
       
   745    Return an integer in the range 1..366, representing a count of the number of days from the start of the year.
       
   746    January 1 is counted as day 1.
       
   747    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   748    */
       
   749   public Integer getDayOfYear() {
       
   750     ensureHasYearMonthDay();
       
   751     int k = isLeapYear() ? 1 : 2;
       
   752     Integer result = ((275 * fMonth) / 9) - k * ((fMonth + 9) / 12) + fDay - 30; // integer division
       
   753     return result;
       
   754   }
       
   755 
       
   756   /**
       
   757    Returns true only if the year is a leap year. 
       
   758    <P>Requires year to be present; if not, a runtime exception is thrown.
       
   759    */
       
   760   public Boolean isLeapYear() {
       
   761     ensureParsed();
       
   762     Boolean result = null;
       
   763     if (isPresent(fYear)) {
       
   764       result = isLeapYear(fYear);
       
   765     }
       
   766     else {
       
   767       throw new MissingItem("Year is absent. Cannot determine if leap year.");
       
   768     }
       
   769     return result;
       
   770   }
       
   771 
       
   772   /** 
       
   773    Return the number of days in the month which holds this <tt>DateTime</tt>. 
       
   774    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   775    */
       
   776   public int getNumDaysInMonth() {
       
   777     ensureHasYearMonthDay();
       
   778     return getNumDaysInMonth(fYear, fMonth);
       
   779   }
       
   780 
       
   781   /**
       
   782    Return The week index of this <tt>DateTime</tt> with respect to a given starting <tt>DateTime</tt>.
       
   783    <P>The single parameter to this method defines first day of week number 1.
       
   784    See {@link #getWeekIndex()} as well. 
       
   785    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   786    */
       
   787   public Integer getWeekIndex(DateTime aStartingFromDate) {
       
   788     ensureHasYearMonthDay();
       
   789     aStartingFromDate.ensureHasYearMonthDay();
       
   790     int diff = getModifiedJulianDayNumber() - aStartingFromDate.getModifiedJulianDayNumber();
       
   791     return (diff / 7) + 1; // integer division
       
   792   }
       
   793 
       
   794   /**
       
   795    Return The week index of this <tt>DateTime</tt>, taking day 1 of week 1 as Sunday, January 2, 2000. 
       
   796    <P>See {@link #getWeekIndex(DateTime)} as well, which takes an arbitrary date to define 
       
   797    day 1 of week 1. 
       
   798    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
   799    */
       
   800   public Integer getWeekIndex() {
       
   801     DateTime start = DateTime.forDateOnly(2000, 1, 2);
       
   802     return getWeekIndex(start);
       
   803   }
       
   804 
       
   805   /**
       
   806    Return <tt>true</tt> only if this <tt>DateTime</tt> has the same year-month-day as the given parameter.
       
   807    Time is ignored by this method.
       
   808    <P> Requires year-month-day to be present, both for this <tt>DateTime</tt> and for
       
   809    <tt>aThat</tt>; if not, a runtime exception is thrown.
       
   810    */
       
   811   public boolean isSameDayAs(DateTime aThat) {
       
   812     boolean result = false;
       
   813     ensureHasYearMonthDay();
       
   814     aThat.ensureHasYearMonthDay();
       
   815     result = (fYear.equals(aThat.fYear) && fMonth.equals(aThat.fMonth) && fDay.equals(aThat.fDay));
       
   816     return result;
       
   817   }
       
   818 
       
   819   /**  
       
   820    'Less than' comparison.
       
   821    Return <tt>true</tt> only if this <tt>DateTime</tt> comes before the given parameter, according to {@link #compareTo(DateTime)}.  
       
   822   */
       
   823   public boolean lt(DateTime aThat) {
       
   824     return compareTo(aThat) < EQUAL;
       
   825   }
       
   826 
       
   827   /**  
       
   828    'Less than or equal to' comparison.
       
   829    Return <tt>true</tt> only if this <tt>DateTime</tt> comes before the given parameter, according to {@link #compareTo(DateTime)}, 
       
   830    or this <tt>DateTime</tt> equals the given parameter.  
       
   831   */
       
   832   public boolean lteq(DateTime aThat) {
       
   833     return compareTo(aThat) < EQUAL || equals(aThat);
       
   834   }
       
   835 
       
   836   /**
       
   837    'Greater than' comparison.  
       
   838    Return <tt>true</tt> only if this <tt>DateTime</tt> comes after the given parameter, according to {@link #compareTo(DateTime)}. 
       
   839   */
       
   840   public boolean gt(DateTime aThat) {
       
   841     return compareTo(aThat) > EQUAL;
       
   842   }
       
   843   
       
   844   /**  
       
   845    'Greater than or equal to' comparison.
       
   846    Return <tt>true</tt> only if this <tt>DateTime</tt> comes after the given parameter, according to {@link #compareTo(DateTime)}, 
       
   847    or this <tt>DateTime</tt> equals the given parameter.  
       
   848   */
       
   849   public boolean gteq(DateTime aThat) {
       
   850     return compareTo(aThat) > EQUAL || equals(aThat);
       
   851   }
       
   852 
       
   853   /** Return the smallest non-null time unit encapsulated by this <tt>DateTime</tt>. */
       
   854   public Unit getPrecision() {
       
   855     ensureParsed();
       
   856     Unit result = null;
       
   857     if (isPresent(fNanosecond)) {
       
   858       result = Unit.NANOSECONDS;
       
   859     }
       
   860     else if (isPresent(fSecond)) {
       
   861       result = Unit.SECOND;
       
   862     }
       
   863     else if (isPresent(fMinute)) {
       
   864       result = Unit.MINUTE;
       
   865     }
       
   866     else if (isPresent(fHour)) {
       
   867       result = Unit.HOUR;
       
   868     }
       
   869     else if (isPresent(fDay)) {
       
   870       result = Unit.DAY;
       
   871     }
       
   872     else if (isPresent(fMonth)) {
       
   873       result = Unit.MONTH;
       
   874     }
       
   875     else if (isPresent(fYear)) {
       
   876       result = Unit.YEAR;
       
   877     }
       
   878     return result;
       
   879   }
       
   880 
       
   881   /**
       
   882    Truncate this <tt>DateTime</tt> to the given precision.
       
   883    <P>The return value will have all items lower than the given precision simply set to
       
   884    <tt>null</tt>. In addition, the return value will not include any date-time String passed to the 
       
   885    {@link #DateTime(String)} constructor.
       
   886    
       
   887    @param aPrecision takes any value <i>except</i> {@link Unit#NANOSECONDS} (since it makes no sense to truncate to the highest
       
   888    available precision).
       
   889    */
       
   890   public DateTime truncate(Unit aPrecision) {
       
   891     ensureParsed();
       
   892     DateTime result = null;
       
   893     if (Unit.NANOSECONDS == aPrecision) {
       
   894       throw new IllegalArgumentException("It makes no sense to truncate to nanosecond precision, since that's the highest precision available.");
       
   895     }
       
   896     else if (Unit.SECOND == aPrecision) {
       
   897       result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, fSecond, null);
       
   898     }
       
   899     else if (Unit.MINUTE == aPrecision) {
       
   900       result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, null, null);
       
   901     }
       
   902     else if (Unit.HOUR == aPrecision) {
       
   903       result = new DateTime(fYear, fMonth, fDay, fHour, null, null, null);
       
   904     }
       
   905     else if (Unit.DAY == aPrecision) {
       
   906       result = new DateTime(fYear, fMonth, fDay, null, null, null, null);
       
   907     }
       
   908     else if (Unit.MONTH == aPrecision) {
       
   909       result = new DateTime(fYear, fMonth, null, null, null, null, null);
       
   910     }
       
   911     else if (Unit.YEAR == aPrecision) {
       
   912       result = new DateTime(fYear, null, null, null, null, null, null);
       
   913     }
       
   914     return result;
       
   915   }
       
   916 
       
   917   /**
       
   918    Return <tt>true</tt> only if all of the given units are present in this <tt>DateTime</tt>.
       
   919    If a unit is <i>not</i> included in the argument list, then no test is made for its presence or absence
       
   920    in this <tt>DateTime</tt> by this method.
       
   921    */
       
   922   public boolean unitsAllPresent(Unit... aUnits) {
       
   923     boolean result = true;
       
   924     ensureParsed();
       
   925     for (Unit unit : aUnits) {
       
   926       if (Unit.NANOSECONDS == unit) {
       
   927         result = result && fNanosecond != null;
       
   928       }
       
   929       else if (Unit.SECOND == unit) {
       
   930         result = result && fSecond != null;
       
   931       }
       
   932       else if (Unit.MINUTE == unit) {
       
   933         result = result && fMinute != null;
       
   934       }
       
   935       else if (Unit.HOUR == unit) {
       
   936         result = result && fHour != null;
       
   937       }
       
   938       else if (Unit.DAY == unit) {
       
   939         result = result && fDay != null;
       
   940       }
       
   941       else if (Unit.MONTH == unit) {
       
   942         result = result && fMonth != null;
       
   943       }
       
   944       else if (Unit.YEAR == unit) {
       
   945         result = result && fYear != null;
       
   946       }
       
   947     }
       
   948     return result;
       
   949   }
       
   950 
       
   951   /**
       
   952    Return <tt>true</tt> only if this <tt>DateTime</tt> has a non-null values for year, month, and day.
       
   953   */
       
   954   public boolean hasYearMonthDay() {
       
   955     return unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY);
       
   956   }
       
   957 
       
   958   /**
       
   959    Return <tt>true</tt> only if this <tt>DateTime</tt> has a non-null values for hour, minute, and second.
       
   960   */
       
   961   public boolean hasHourMinuteSecond() {
       
   962     return unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND);
       
   963   }
       
   964 
       
   965   /**
       
   966    Return <tt>true</tt> only if all of the given units are absent from this <tt>DateTime</tt>.
       
   967    If a unit is <i>not</i> included in the argument list, then no test is made for its presence or absence
       
   968    in this <tt>DateTime</tt> by this method.
       
   969    */
       
   970   public boolean unitsAllAbsent(Unit... aUnits) {
       
   971     boolean result = true;
       
   972     ensureParsed();
       
   973     for (Unit unit : aUnits) {
       
   974       if (Unit.NANOSECONDS == unit) {
       
   975         result = result && fNanosecond == null;
       
   976       }
       
   977       else if (Unit.SECOND == unit) {
       
   978         result = result && fSecond == null;
       
   979       }
       
   980       else if (Unit.MINUTE == unit) {
       
   981         result = result && fMinute == null;
       
   982       }
       
   983       else if (Unit.HOUR == unit) {
       
   984         result = result && fHour == null;
       
   985       }
       
   986       else if (Unit.DAY == unit) {
       
   987         result = result && fDay == null;
       
   988       }
       
   989       else if (Unit.MONTH == unit) {
       
   990         result = result && fMonth == null;
       
   991       }
       
   992       else if (Unit.YEAR == unit) {
       
   993         result = result && fYear == null;
       
   994       }
       
   995     }
       
   996     return result;
       
   997   }
       
   998 
       
   999   /**
       
  1000    Return this <tt>DateTime</tt> with the time portion coerced to '00:00:00.000000000'.
       
  1001    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
  1002    */
       
  1003   public DateTime getStartOfDay() {
       
  1004     ensureHasYearMonthDay();
       
  1005     return getStartEndDateTime(fDay, 0, 0, 0, 0);
       
  1006   }
       
  1007 
       
  1008   /**
       
  1009    Return this <tt>DateTime</tt> with the time portion coerced to '23:59:59.999999999'. 
       
  1010    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
  1011    */
       
  1012   public DateTime getEndOfDay() {
       
  1013     ensureHasYearMonthDay();
       
  1014     return getStartEndDateTime(fDay, 23, 59, 59, 999999999);
       
  1015   }
       
  1016 
       
  1017   /**
       
  1018    Return this <tt>DateTime</tt> with the time portion coerced to '00:00:00.000000000', 
       
  1019    and the day coerced to 1.
       
  1020    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
  1021    */
       
  1022   public DateTime getStartOfMonth() {
       
  1023     ensureHasYearMonthDay();
       
  1024     return getStartEndDateTime(1, 0, 0, 0, 0);
       
  1025   }
       
  1026 
       
  1027   /**
       
  1028    Return this <tt>DateTime</tt> with the time portion coerced to '23:59:59.999999999', 
       
  1029    and the day coerced to the end of the month.
       
  1030    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
  1031    */
       
  1032   public DateTime getEndOfMonth() {
       
  1033     ensureHasYearMonthDay();
       
  1034     return getStartEndDateTime(getNumDaysInMonth(), 23, 59, 59, 999999999);
       
  1035   }
       
  1036 
       
  1037   /**
       
  1038    Create a new <tt>DateTime</tt> by adding an interval to this one.
       
  1039    
       
  1040    <P>See {@link #plusDays(Integer)} as well. 
       
  1041    
       
  1042    <P>Changes are always applied by this class <i>in order of decreasing units of time</i>: 
       
  1043    years first, then months, and so on. After changing both the year and month, a check on the month-day combination is made before 
       
  1044    any change is made to the day. If the day exceeds the number of days in the given month/year, then 
       
  1045    (and only then) the given {@link DayOverflow} policy applied, and the day-of-the-month is adusted accordingly.
       
  1046    
       
  1047    <P>Afterwards, the day is then changed in the usual way, followed by the remaining items (hour, minute, second, and nanosecond). 
       
  1048  
       
  1049    <P><em>The mental model for this method is very similar to that of a car's odometer.</em> When a limit is reach for one unit of time, 
       
  1050    then a rollover occurs for a neighbouring unit of time. 
       
  1051    
       
  1052    <P>The returned value cannot come after <tt>9999-12-13 23:59:59</tt>.
       
  1053    
       
  1054    <P>This class works with <tt>DateTime</tt>'s having the following items present :
       
  1055    <ul>
       
  1056    <li>year-month-day and hour-minute-second (and optional nanoseconds)
       
  1057    <li>year-month-day only. In this case, if a calculation with a time part is performed, that time part 
       
  1058    will be initialized by this class to 00:00:00.0, and the <tt>DateTime</tt> returned by this class will include a time part.
       
  1059    <li>hour-minute-second (and optional nanoseconds) only. In this case, the calculation is done starting with the   
       
  1060    the arbitrary date <tt>0001-01-01</tt> (in order to remain within a valid state space of <tt>DateTime</tt>). 
       
  1061    </ul>
       
  1062    
       
  1063    @param aNumYears positive, required, in range 0...9999 
       
  1064    @param aNumMonths positive, required, in range 0...9999 
       
  1065    @param aNumDays positive, required, in range 0...9999 
       
  1066    @param aNumHours positive, required, in range 0...9999 
       
  1067    @param aNumMinutes positive, required, in range 0...9999 
       
  1068    @param aNumSeconds positive, required, in range 0...9999 
       
  1069    @param aNumNanoseconds positive, required, in range 0...999999999 
       
  1070    */
       
  1071   public DateTime plus(Integer aNumYears, Integer aNumMonths, Integer aNumDays, Integer aNumHours, Integer aNumMinutes, Integer aNumSeconds, Integer aNumNanoseconds,  DayOverflow aDayOverflow) {
       
  1072     DateTimeInterval interval = new DateTimeInterval(this, aDayOverflow);
       
  1073     return interval.plus(aNumYears, aNumMonths, aNumDays, aNumHours, aNumMinutes, aNumSeconds, aNumNanoseconds);
       
  1074   }
       
  1075 
       
  1076   /**
       
  1077    Create a new <tt>DateTime</tt> by subtracting an interval to this one.
       
  1078    
       
  1079    <P>See {@link #minusDays(Integer)} as well. 
       
  1080    <P>This method has nearly the same behavior as {@link #plus(Integer, Integer, Integer, Integer, Integer, Integer, Integer, DayOverflow)},
       
  1081    except that the return value cannot come before <tt>0001-01-01 00:00:00</tt>.
       
  1082    */
       
  1083   public DateTime minus(Integer aNumYears, Integer aNumMonths, Integer aNumDays, Integer aNumHours, Integer aNumMinutes, Integer aNumSeconds, Integer aNumNanoseconds, DayOverflow aDayOverflow) {
       
  1084     DateTimeInterval interval = new DateTimeInterval(this, aDayOverflow);
       
  1085     return interval.minus(aNumYears, aNumMonths, aNumDays, aNumHours, aNumMinutes, aNumSeconds, aNumNanoseconds);
       
  1086   }
       
  1087 
       
  1088   /**
       
  1089    Return a new <tt>DateTime</tt> by adding an integral number of days to this one.
       
  1090    
       
  1091    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
  1092    @param aNumDays can be either sign; if negative, then the days are subtracted. 
       
  1093    */
       
  1094   public DateTime plusDays(Integer aNumDays) {
       
  1095     ensureHasYearMonthDay();
       
  1096     int thisJDAtNoon = getModifiedJulianDayNumber() + 1 + EPOCH_MODIFIED_JD;
       
  1097     int resultJD = thisJDAtNoon + aNumDays;
       
  1098     DateTime datePortion = fromJulianDayNumberAtNoon(resultJD);
       
  1099     return new DateTime(datePortion.getYear(), datePortion.getMonth(), datePortion.getDay(), fHour, fMinute, fSecond, fNanosecond);
       
  1100   }
       
  1101 
       
  1102   /**
       
  1103    Return a new <tt>DateTime</tt> by subtracting an integral number of days from this one.
       
  1104    
       
  1105    <P>Requires year-month-day to be present; if not, a runtime exception is thrown.
       
  1106    @param aNumDays can be either sign; if negative, then the days are added.
       
  1107    */
       
  1108   public DateTime minusDays(Integer aNumDays) {
       
  1109     return plusDays(-1 * aNumDays);
       
  1110   }
       
  1111 
       
  1112   /**  
       
  1113    The whole number of days between this <tt>DateTime</tt> and the given parameter. 
       
  1114    <P>Requires year-month-day to be present, both for this <tt>DateTime</tt> and for the <tt>aThat</tt> 
       
  1115    parameter; if not, a runtime exception is thrown.
       
  1116   */
       
  1117   public int numDaysFrom(DateTime aThat) {
       
  1118     return aThat.getModifiedJulianDayNumber() - this.getModifiedJulianDayNumber();
       
  1119   }
       
  1120 
       
  1121   /** 
       
  1122     The number of seconds between this <tt>DateTime</tt> and the given argument.
       
  1123     <P>If only time information is present in both this <tt>DateTime</tt> and <tt>aThat</tt>, then there are 
       
  1124     no restrictions on the values of the time units. 
       
  1125     <P>If any date information is present, in either this <tt>DateTime</tt> or <tt>aThat</tt>, 
       
  1126     then full year-month-day must be present in both; if not, then the date portion will be ignored, and only the 
       
  1127     time portion will contribute to the calculation.
       
  1128   */
       
  1129   public long numSecondsFrom(DateTime aThat) {
       
  1130     long result = 0;
       
  1131     if(hasYearMonthDay() && aThat.hasYearMonthDay()){
       
  1132       result = numDaysFrom(aThat) * 86400; //just the day portion
       
  1133     }
       
  1134     result = result - this.numSecondsInTimePortion() + aThat.numSecondsInTimePortion();
       
  1135     return result;
       
  1136   }
       
  1137 
       
  1138   /**
       
  1139    Output this <tt>DateTime</tt> as a formatted String using numbers, with no localizable text.
       
  1140    
       
  1141    <P>Example:
       
  1142    <PRE>dt.format("YYYY-MM-DD hh:mm:ss");</PRE>
       
  1143    would generate text of the form
       
  1144    <PRE>2009-09-09 18:23:59</PRE>
       
  1145    
       
  1146    <P>If months, weekdays, or AM/PM indicators are output as localizable text, you must use {@link #format(String, Locale)}.
       
  1147    @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment.
       
  1148    */
       
  1149   public String format(String aFormat) {
       
  1150     DateTimeFormatter format = new DateTimeFormatter(aFormat);
       
  1151     return format.format(this);
       
  1152   }
       
  1153 
       
  1154   /**
       
  1155    Output this <tt>DateTime</tt> as a formatted String using numbers and/or localizable text.
       
  1156    
       
  1157    <P>This method is intended for alphanumeric output, such as '<tt>Sunday, November 14, 1858 10:00 AM</tt>'.
       
  1158    <P>If months and weekdays are output as numbers, you are encouraged to use {@link #format(String)} instead.
       
  1159    
       
  1160    @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment.
       
  1161    @param aLocale used to generate text for Month, Weekday and AM/PM indicator; required only by patterns which return localized 
       
  1162    text, instead of numeric forms.
       
  1163    */
       
  1164   public String format(String aFormat, Locale aLocale) {
       
  1165     DateTimeFormatter format = new DateTimeFormatter(aFormat, aLocale);
       
  1166     return format.format(this);
       
  1167   }
       
  1168 
       
  1169   /**
       
  1170    Output this <tt>DateTime</tt> as a formatted String using numbers and explicit text for months, weekdays, and AM/PM indicator. 
       
  1171 
       
  1172    <P>Use of this method is likely relatively rare; it should be used only if the output of {@link #format(String, Locale)}  is 
       
  1173    inadequate. 
       
  1174    
       
  1175    @param aFormat uses the <a href="#FormattingLanguage">formatting mini-language</a> defined in the class comment.
       
  1176    @param aMonths contains text for all 12 months, starting with January; size must be 12. 
       
  1177    @param aWeekdays contains text for all 7 weekdays, starting with Sunday; size must be 7. 
       
  1178    @param aAmPmIndicators contains text for A.M and P.M. indicators (in that order); size must be 2. 
       
  1179    */
       
  1180   public String format(String aFormat, List<String> aMonths, List<String> aWeekdays, List<String> aAmPmIndicators) {
       
  1181     DateTimeFormatter format = new DateTimeFormatter(aFormat, aMonths, aWeekdays, aAmPmIndicators);
       
  1182     return format.format(this);
       
  1183   }
       
  1184 
       
  1185   /**
       
  1186    Return the current date-time.
       
  1187    <P>Combines the configured implementation of {@link TimeSource} with the given {@link TimeZone}.
       
  1188    The <tt>TimeZone</tt> will typically come from your implementation of {@link TimeZoneSource}. 
       
  1189    
       
  1190    <P>In an Action, the current date-time date can be referenced using 
       
  1191    <PRE>DateTime.now(getTimeZone())</PRE>
       
  1192    See {@link ActionImpl#getTimeZone()}.
       
  1193    
       
  1194    <P>Only millisecond precision is possible for this method.
       
  1195    */
       
  1196   public static DateTime now(TimeZone aTimeZone) {
       
  1197     TimeSource timesource = BuildImpl.forTimeSource();
       
  1198     return forInstant(timesource.currentTimeMillis(), aTimeZone);
       
  1199   }
       
  1200 
       
  1201   /** 
       
  1202    Return the current date.
       
  1203    <P>As in {@link #now(TimeZone)}, but truncates the time portion, leaving only year-month-day.
       
  1204    <P>In an Action, today's date can be referenced using 
       
  1205    <PRE>DateTime.today(getTimeZone())</PRE>
       
  1206    See {@link ActionImpl#getTimeZone()}. 
       
  1207    */
       
  1208   public static DateTime today(TimeZone aTimeZone) {
       
  1209     DateTime result = now(aTimeZone);
       
  1210     return result.truncate(Unit.DAY);
       
  1211   }
       
  1212 
       
  1213   /** Return <tt>true</tt> only if this date is in the future, with respect to {@link #now(TimeZone)}. */
       
  1214   public boolean isInTheFuture(TimeZone aTimeZone) {
       
  1215     return now(aTimeZone).lt(this);
       
  1216   }
       
  1217 
       
  1218   /** Return <tt>true</tt> only if this date is in the past, with respect to {@link #now(TimeZone)}. */
       
  1219   public boolean isInThePast(TimeZone aTimeZone) {
       
  1220     return now(aTimeZone).gt(this);
       
  1221   }
       
  1222   
       
  1223   /**
       
  1224     Return a <tt>DateTime</tt> corresponding to a change from one {@link TimeZone} to another.
       
  1225     
       
  1226     <P>A <tt>DateTime</tt> object has an implicit and immutable time zone. 
       
  1227     If you need to change the implicit time zone, you can use this method to do so.
       
  1228     
       
  1229     <P>Example :
       
  1230     <PRE>
       
  1231 TimeZone fromUK = TimeZone.getTimeZone("Europe/London");
       
  1232 TimeZone toIndonesia = TimeZone.getTimeZone("Asia/Jakarta");
       
  1233 DateTime newDt = oldDt.changeTimeZone(fromUK, toIndonesia);
       
  1234     </PRE>
       
  1235      
       
  1236    <P>Requires year-month-day-hour to be present; if not, a runtime exception is thrown.
       
  1237    @param aFromTimeZone the implicit time zone of this object.
       
  1238    @param aToTimeZone the implicit time zone of the <tt>DateTime</tt> returned by this method.
       
  1239    @return aDateTime corresponding to the change of time zone implied by the 2 parameters.
       
  1240    */
       
  1241   public DateTime changeTimeZone(TimeZone aFromTimeZone, TimeZone aToTimeZone){
       
  1242     DateTime result = null;
       
  1243     ensureHasYearMonthDay();
       
  1244     if (unitsAllAbsent(Unit.HOUR)){
       
  1245       throw new IllegalArgumentException("DateTime does not include the hour. Cannot change the time zone if no hour is present.");
       
  1246     }
       
  1247     Calendar fromDate = new GregorianCalendar(aFromTimeZone);
       
  1248     fromDate.set(Calendar.YEAR, getYear());
       
  1249     fromDate.set(Calendar.MONTH, getMonth()-1);
       
  1250     fromDate.set(Calendar.DAY_OF_MONTH, getDay());
       
  1251     fromDate.set(Calendar.HOUR_OF_DAY, getHour());
       
  1252     if(getMinute() != null) {
       
  1253       fromDate.set(Calendar.MINUTE, getMinute());
       
  1254     }
       
  1255     else {
       
  1256       fromDate.set(Calendar.MINUTE, 0);
       
  1257     }
       
  1258     //other items zeroed out here, since they don't matter for time zone calculations
       
  1259     fromDate.set(Calendar.SECOND, 0);
       
  1260     fromDate.set(Calendar.MILLISECOND, 0);
       
  1261 
       
  1262     //millisecond precision is OK here, since the seconds/nanoseconds are not part of the calc
       
  1263     Calendar toDate = new GregorianCalendar(aToTimeZone);
       
  1264     toDate.setTimeInMillis(fromDate.getTimeInMillis());
       
  1265     //needed if this date has hour, but no minute (bit of an oddball case) :
       
  1266     Integer minute = getMinute() != null ? toDate.get(Calendar.MINUTE) : null;
       
  1267     result = new DateTime(
       
  1268       toDate.get(Calendar.YEAR), toDate.get(Calendar.MONTH) + 1, toDate.get(Calendar.DAY_OF_MONTH), 
       
  1269       toDate.get(Calendar.HOUR_OF_DAY), minute, getSecond(), getNanoseconds() 
       
  1270     );
       
  1271     return result;
       
  1272   }
       
  1273 
       
  1274   /**
       
  1275    Compare this object to another, for ordering purposes.
       
  1276    <P> Uses the 7 date-time elements (year..nanosecond). The Year is considered the most
       
  1277    significant item, and the Nanosecond the least significant item. Null items are placed first in this comparison.
       
  1278    */
       
  1279   public int compareTo(DateTime aThat) {
       
  1280     if (this == aThat) return EQUAL;
       
  1281     ensureParsed();
       
  1282     aThat.ensureParsed();
       
  1283 
       
  1284     NullsGo nullsGo = NullsGo.FIRST;
       
  1285     int comparison = ModelUtil.comparePossiblyNull(this.fYear, aThat.fYear, nullsGo);
       
  1286     if (comparison != EQUAL)  return comparison;
       
  1287 
       
  1288     comparison = ModelUtil.comparePossiblyNull(this.fMonth, aThat.fMonth, nullsGo);
       
  1289     if (comparison != EQUAL)  return comparison;
       
  1290 
       
  1291     comparison = ModelUtil.comparePossiblyNull(this.fDay, aThat.fDay, nullsGo);
       
  1292     if (comparison != EQUAL)  return comparison;
       
  1293 
       
  1294     comparison = ModelUtil.comparePossiblyNull(this.fHour, aThat.fHour, nullsGo);
       
  1295     if (comparison != EQUAL)  return comparison;
       
  1296 
       
  1297     comparison = ModelUtil.comparePossiblyNull(this.fMinute, aThat.fMinute, nullsGo);
       
  1298     if (comparison != EQUAL)  return comparison;
       
  1299 
       
  1300     comparison = ModelUtil.comparePossiblyNull(this.fSecond, aThat.fSecond, nullsGo);
       
  1301     if (comparison != EQUAL)  return comparison;
       
  1302 
       
  1303     comparison = ModelUtil.comparePossiblyNull(this.fNanosecond, aThat.fNanosecond, nullsGo);
       
  1304     if (comparison != EQUAL)  return comparison;
       
  1305 
       
  1306     return EQUAL;
       
  1307   }
       
  1308 
       
  1309   /**
       
  1310    Equals method for this object.
       
  1311    
       
  1312    <P>Equality is determined by the 7 date-time elements (year..nanosecond).
       
  1313    */
       
  1314   @Override public boolean equals(Object aThat) {
       
  1315     /*
       
  1316      * Implementation note: it was considered branching this method, according to whether
       
  1317      * the objects are already parsed. That was rejected, since maintaining 'synchronicity'
       
  1318      * with hashCode would not then be possible, since hashCode is based only on one object,
       
  1319      * not two.
       
  1320      */
       
  1321     ensureParsed();
       
  1322     Boolean result = ModelUtil.quickEquals(this, aThat);
       
  1323     if (result == null) {
       
  1324       DateTime that = (DateTime)aThat;
       
  1325       that.ensureParsed();
       
  1326       result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
       
  1327     }
       
  1328     return result;
       
  1329   }
       
  1330 
       
  1331   /**
       
  1332    Hash code for this object.
       
  1333    
       
  1334    <P> Uses the same 7 date-time elements (year..nanosecond) as used by
       
  1335    {@link #equals(Object)}.
       
  1336    */
       
  1337   @Override public int hashCode() {
       
  1338     if (fHashCode == 0) {
       
  1339       ensureParsed();
       
  1340       fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
       
  1341     }
       
  1342     return fHashCode;
       
  1343   }
       
  1344 
       
  1345   /**
       
  1346   Intended for <i>debugging and logging</i> only.
       
  1347   
       
  1348   <P><b>To format this <tt>DateTime</tt> for presentation to the user, see the various <tt>format</tt> methods.</b>
       
  1349   
       
  1350   <P>If the {@link #DateTime(String)} constructor was called, then return that String. 
       
  1351   
       
  1352   <P>Otherwise, the return value is constructed from each date-time element, in a fixed format, depending 
       
  1353   on which time units are present. Example values :
       
  1354   <ul>
       
  1355    <li>2011-04-30 13:59:59.123456789
       
  1356    <li>2011-04-30 13:59:59
       
  1357    <li>2011-04-30
       
  1358    <li>2011-04-30 13:59
       
  1359    <li>13:59:59.123456789
       
  1360    <li>13:59:59
       
  1361    <li>and so on...
       
  1362   </ul>
       
  1363   
       
  1364   <P>In the great majority of cases, this will give reasonable output for debugging and logging statements.
       
  1365   
       
  1366   <P>In cases where a bizarre combinations of time units is present, the return value is presented in a verbose form.
       
  1367   For example, if all time units are present <i>except</i> for minutes, the return value has this form:
       
  1368   <PRE>Y:2001 M:1 D:31 h:13 m:null s:59 f:123456789</PRE> 
       
  1369  */
       
  1370   @Override public String toString() {
       
  1371     String result = "";
       
  1372     if (Util.textHasContent(fDateTime)) {
       
  1373       result = fDateTime;
       
  1374     }
       
  1375     else {
       
  1376       String format = calcToStringFormat();
       
  1377       if(format != null){
       
  1378         result = format(calcToStringFormat());
       
  1379       }
       
  1380       else {
       
  1381         StringBuilder builder = new StringBuilder();
       
  1382         addToString("Y", fYear, builder);
       
  1383         addToString("M", fMonth, builder);
       
  1384         addToString("D", fDay, builder);
       
  1385         addToString("h", fHour, builder);
       
  1386         addToString("m", fMinute, builder);
       
  1387         addToString("s", fSecond, builder);
       
  1388         addToString("f", fNanosecond, builder);
       
  1389         result = builder.toString().trim();
       
  1390       }
       
  1391     }
       
  1392     return result;
       
  1393   }
       
  1394 
       
  1395   // PACKAGE-PRIVATE (for unit testing, mostly)
       
  1396 
       
  1397   static final class ItemOutOfRange extends RuntimeException {
       
  1398     ItemOutOfRange(String aMessage) {
       
  1399       super(aMessage);
       
  1400     }
       
  1401   }
       
  1402 
       
  1403   static final class MissingItem extends RuntimeException {
       
  1404     MissingItem(String aMessage) {
       
  1405       super(aMessage);
       
  1406     }
       
  1407   }
       
  1408 
       
  1409   /** Intended as internal tool, for testing only. Not scope is not public! */
       
  1410   void ensureParsed() {
       
  1411     if (!fIsAlreadyParsed) {
       
  1412       parseDateTimeText();
       
  1413     }
       
  1414   }
       
  1415 
       
  1416   /**
       
  1417    Return the number of days in the given month. The returned value depends on the year as
       
  1418    well, because of leap years. Returns <tt>null</tt> if either year or month are
       
  1419    absent. WRONG - should be public??
       
  1420    Package-private, needed for interval calcs.
       
  1421    */
       
  1422   static Integer getNumDaysInMonth(Integer aYear, Integer aMonth) {
       
  1423     Integer result = null;
       
  1424     if (aYear != null && aMonth != null) {
       
  1425       if (aMonth == 1) {
       
  1426         result = 31;
       
  1427       }
       
  1428       else if (aMonth == 2) {
       
  1429         result = isLeapYear(aYear) ? 29 : 28;
       
  1430       }
       
  1431       else if (aMonth == 3) {
       
  1432         result = 31;
       
  1433       }
       
  1434       else if (aMonth == 4) {
       
  1435         result = 30;
       
  1436       }
       
  1437       else if (aMonth == 5) {
       
  1438         result = 31;
       
  1439       }
       
  1440       else if (aMonth == 6) {
       
  1441         result = 30;
       
  1442       }
       
  1443       else if (aMonth == 7) {
       
  1444         result = 31;
       
  1445       }
       
  1446       else if (aMonth == 8) {
       
  1447         result = 31;
       
  1448       }
       
  1449       else if (aMonth == 9) {
       
  1450         result = 30;
       
  1451       }
       
  1452       else if (aMonth == 10) {
       
  1453         result = 31;
       
  1454       }
       
  1455       else if (aMonth == 11) {
       
  1456         result = 30;
       
  1457       }
       
  1458       else if (aMonth == 12) {
       
  1459         result = 31;
       
  1460       }
       
  1461       else {
       
  1462         throw new AssertionError("Month is out of range 1..12:" + aMonth);
       
  1463       }
       
  1464     }
       
  1465     return result;
       
  1466   }
       
  1467 
       
  1468   static DateTime fromJulianDayNumberAtNoon(int aJDAtNoon) {
       
  1469     //http://www.hermetic.ch/cal_stud/jdn.htm
       
  1470     int l = aJDAtNoon + 68569;
       
  1471     int n = (4 * l) / 146097;
       
  1472     l = l - (146097 * n + 3) / 4;
       
  1473     int i = (4000 * (l + 1)) / 1461001;
       
  1474     l = l - (1461 * i) / 4 + 31;
       
  1475     int j = (80 * l) / 2447;
       
  1476     int d = l - (2447 * j) / 80;
       
  1477     l = j / 11;
       
  1478     int m = j + 2 - (12 * l);
       
  1479     int y = 100 * (n - 49) + i + l;
       
  1480     return DateTime.forDateOnly(y, m, d);
       
  1481   }
       
  1482 
       
  1483   // PRIVATE
       
  1484 
       
  1485   /*
       
  1486    There are 2 representations of a date - a text form, and a 'parsed' form, in which all
       
  1487    of the elements of the date are separated out. A DateTime starts out with one of these
       
  1488    forms, and may need to generate the other.
       
  1489    */
       
  1490 
       
  1491   /** The text form of a date. @serial */
       
  1492   private String fDateTime;
       
  1493 
       
  1494   /* The following 7 items represent the parsed form of a DateTime. */
       
  1495   /**  @serial */
       
  1496   private Integer fYear;
       
  1497   /**  @serial */
       
  1498   private Integer fMonth;
       
  1499   /**  @serial */
       
  1500   private Integer fDay;
       
  1501   /**  @serial */
       
  1502   private Integer fHour;
       
  1503   /**  @serial */
       
  1504   private Integer fMinute;
       
  1505   /**  @serial */
       
  1506   private Integer fSecond;
       
  1507   /**  @serial */
       
  1508   private Integer fNanosecond;
       
  1509 
       
  1510   /** Indicates if this DateTime has been parsed into its 7 constituents. @serial */
       
  1511   private boolean fIsAlreadyParsed;
       
  1512 
       
  1513   /** @serial */
       
  1514   private int fHashCode;
       
  1515   
       
  1516   private static final int EQUAL = 0;
       
  1517   
       
  1518   private static int EPOCH_MODIFIED_JD = 2400000;
       
  1519   
       
  1520   private static final int MILLION = 1000000;
       
  1521   
       
  1522   private static final long serialVersionUID =  -1300068157085493891L; 
       
  1523     
       
  1524   /**
       
  1525    Return a the whole number, with no fraction.
       
  1526    The JD at noon is 1 more than the JD at midnight. 
       
  1527    */
       
  1528   private int calculateJulianDayNumberAtNoon() {
       
  1529     //http://www.hermetic.ch/cal_stud/jdn.htm
       
  1530     int y = fYear;
       
  1531     int m = fMonth;
       
  1532     int d = fDay;
       
  1533     int result = (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 - (3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075;
       
  1534     return result;
       
  1535   }
       
  1536 
       
  1537   private void ensureHasYearMonthDay() {
       
  1538     ensureParsed();
       
  1539     if (!hasYearMonthDay()) {
       
  1540       throw new MissingItem("DateTime does not include year/month/day.");
       
  1541     }
       
  1542   }
       
  1543 
       
  1544   /** Return the number of seconds in any existing time portion of the date. */
       
  1545   private int numSecondsInTimePortion() {
       
  1546     int result = 0;
       
  1547     if (fSecond != null) {
       
  1548       result = result + fSecond;
       
  1549     }
       
  1550     if (fMinute != null) {
       
  1551       result = result + 60 * fMinute;
       
  1552     }
       
  1553     if (fHour != null) {
       
  1554       result = result + 3600 * fHour;
       
  1555     }
       
  1556     return result;
       
  1557   }
       
  1558 
       
  1559   private void validateState() {
       
  1560     checkRange(fYear, 1, 9999, "Year");
       
  1561     checkRange(fMonth, 1, 12, "Month");
       
  1562     checkRange(fDay, 1, 31, "Day");
       
  1563     checkRange(fHour, 0, 23, "Hour");
       
  1564     checkRange(fMinute, 0, 59, "Minute");
       
  1565     checkRange(fSecond, 0, 59, "Second");
       
  1566     checkRange(fNanosecond, 0, 999999999, "Nanosecond");
       
  1567     checkNumDaysInMonth(fYear, fMonth, fDay);
       
  1568   }
       
  1569 
       
  1570   private void checkRange(Integer aValue, int aMin, int aMax, String aName) {
       
  1571     if (!Check.optional(aValue, Check.range(aMin, aMax))) {
       
  1572       throw new ItemOutOfRange(aName + " is not in the range " + aMin + ".." + aMax + ". Value is:" + aValue);
       
  1573     }
       
  1574   }
       
  1575 
       
  1576   private void checkNumDaysInMonth(Integer aYear, Integer aMonth, Integer aDay) {
       
  1577     if (hasYearMonthDay(aYear, aMonth, aDay) && aDay > getNumDaysInMonth(aYear, aMonth)) {
       
  1578       throw new ItemOutOfRange("The day-of-the-month value '" + aDay + "' exceeds the number of days in the month: " + getNumDaysInMonth(aYear, aMonth));
       
  1579     }
       
  1580   }
       
  1581 
       
  1582   private void parseDateTimeText() {
       
  1583     DateTimeParser parser = new DateTimeParser();
       
  1584     DateTime dateTime = parser.parse(fDateTime);
       
  1585     /*
       
  1586      * This is unusual - we essentially copy from one object to another. This could be
       
  1587      * avoided by building another interface, But defining a top-level interface for this
       
  1588      * simple task is too high a price.
       
  1589      */
       
  1590     fYear = dateTime.fYear;
       
  1591     fMonth = dateTime.fMonth;
       
  1592     fDay = dateTime.fDay;
       
  1593     fHour = dateTime.fHour;
       
  1594     fMinute = dateTime.fMinute;
       
  1595     fSecond = dateTime.fSecond;
       
  1596     fNanosecond = dateTime.fNanosecond;
       
  1597     validateState();
       
  1598   }
       
  1599 
       
  1600   private boolean hasYearMonthDay(Integer aYear, Integer aMonth, Integer aDay) {
       
  1601     return isPresent(aYear, aMonth, aDay);
       
  1602   }
       
  1603 
       
  1604   private static boolean isLeapYear(Integer aYear) {
       
  1605     boolean result = false;
       
  1606     if (aYear % 100 == 0) {
       
  1607       // this is a century year
       
  1608       if (aYear % 400 == 0) {
       
  1609         result = true;
       
  1610       }
       
  1611     }
       
  1612     else if (aYear % 4 == 0) {
       
  1613       result = true;
       
  1614     }
       
  1615     return result;
       
  1616   }
       
  1617 
       
  1618   private Object[] getSignificantFields() {
       
  1619     return new Object[]{fYear, fMonth, fDay, fHour, fMinute, fSecond, fNanosecond};
       
  1620   }
       
  1621 
       
  1622   private void addToString(String aName, Object aValue, StringBuilder aBuilder) {
       
  1623     aBuilder.append(aName + ":" + String.valueOf(aValue) + " ");
       
  1624   }
       
  1625 
       
  1626   /** Return true only if all the given arguments are non-null. */
       
  1627   private boolean isPresent(Object... aItems) {
       
  1628     boolean result = true;
       
  1629     for (Object item : aItems) {
       
  1630       if (item == null) {
       
  1631         result = false;
       
  1632         break;
       
  1633       }
       
  1634     }
       
  1635     return result;
       
  1636   }
       
  1637 
       
  1638   private DateTime getStartEndDateTime(Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanosecond) {
       
  1639     ensureHasYearMonthDay();
       
  1640     return new DateTime(fYear, fMonth, aDay, aHour, aMinute, aSecond, aNanosecond);
       
  1641   }
       
  1642   
       
  1643   private String calcToStringFormat(){
       
  1644     String result = null; //caller will check for this; null means the set of units is bizarre
       
  1645     if(unitsAllPresent(Unit.YEAR) && unitsAllAbsent(Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
       
  1646       result = "YYYY";
       
  1647     }
       
  1648     else if (unitsAllPresent(Unit.YEAR, Unit.MONTH) && unitsAllAbsent(Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
       
  1649       result = "YYYY-MM";
       
  1650     }
       
  1651     else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY) && unitsAllAbsent(Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
       
  1652       result = "YYYY-MM-DD";
       
  1653     }
       
  1654     else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR) && unitsAllAbsent(Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
       
  1655       result = "YYYY-MM-DD hh";
       
  1656     }
       
  1657     else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE) && unitsAllAbsent(Unit.SECOND, Unit.NANOSECONDS)){
       
  1658       result = "YYYY-MM-DD hh:mm";
       
  1659     }
       
  1660     else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND) && unitsAllAbsent(Unit.NANOSECONDS)){
       
  1661       result = "YYYY-MM-DD hh:mm:ss";
       
  1662     }
       
  1663     else if (unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
       
  1664       result = "YYYY-MM-DD hh:mm:ss.fffffffff";
       
  1665     }
       
  1666     else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY) && unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND, Unit.NANOSECONDS)){
       
  1667       result = "hh:mm:ss.fffffffff";
       
  1668     }
       
  1669     else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.NANOSECONDS) && unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND)){
       
  1670       result = "hh:mm:ss";
       
  1671     }
       
  1672     else if (unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.SECOND, Unit.NANOSECONDS) && unitsAllPresent(Unit.HOUR, Unit.MINUTE)){
       
  1673       result = "hh:mm";
       
  1674     }
       
  1675     return result;
       
  1676   }
       
  1677   
       
  1678   /**
       
  1679     Always treat de-serialization as a full-blown constructor, by
       
  1680     validating the final state of the de-serialized object.
       
  1681   */
       
  1682   private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
       
  1683     //always perform the default de-serialization first
       
  1684     aInputStream.defaultReadObject();
       
  1685     //no mutable fields in this case
       
  1686     validateState();
       
  1687   }
       
  1688 
       
  1689    /**
       
  1690     This is the default implementation of writeObject.
       
  1691     Customise if necessary.
       
  1692   */
       
  1693   private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
       
  1694     //perform the default serialization for all non-transient, non-static fields
       
  1695     aOutputStream.defaultWriteObject();
       
  1696   }
       
  1697   
       
  1698 }