st precision is nanosecond, not millisecond: DateTime dateAndTime = new DateTime("2010-01-19 23:59:59.123456789"); DateTime dateOnly = new DateTime("2010-01-19"); DateTime timeOnly = new DateTime("23:59:59"); DateTime dateOnly = DateTime.forDateOnly(2010,01,19); DateTime timeOnly = DateTime.forTimeOnly(23,59,59,0); DateTime dt = new DateTime("2010-01-15 13:59:15"); boolean leap = dt.isLeapYear(); //false dt.getNumDaysInMonth(); //31 dt.getStartOfMonth(); //2010-01-01, 00:00:00 dt.getEndOfDay(); //2010-01-15, 23:59:59 dt.format("YYYY-MM-DD"); //formats as '2010-01-15' dt.plusDays(30); //30 days after Jan 15 dt.numDaysFrom(someDate); //returns an int dueDate.lt(someDate); //less-than dueDate.lteq(someDate); //less-than-or-equal-to // {@link ActionImpl#getTimeZone()} is readily available in most ActionsDateTime.now(getTimeZone()); DateTime.today(getTimeZone()); DateTime fromMilliseconds = DateTime.forInstant(31313121L, getTimeZone()); birthday.isInFuture(getTimeZone());
Justification For This Class
The fundamental reasons why this class exists are :
- to avoid the embarrassing number of distasteful inadequacies in the JDK's date classes
- to oppose the very "mental model" of the JDK's date-time classes with something significantly simpler
There are 2 distinct mental models for date-times, and they don't play well together :
- timeline - an instant on the timeline, as a physicist would picture it, representing the number of seconds from some epoch. In this picture, such a date-time can have many, many different representations according to calendar and time zone. That is, the date-time, as seen and understood by the end user, can change according to "who's looking at it". It's important to understand that a timeline instant, before being presented to the user, must always have an associated time zone - even in the case of a date only, with no time.
- everyday - a date-time in the Gregorian calendar, such as '2009-05-25 18:25:00', which never changes according to "who's looking at it". Here, the time zone is always both implicit and immutable.
The problem is that java.util. {@link java.util.Date} uses only the timeline style, while most users, most of the time, think in terms of the other mental model - the 'everday' style. In particular, there are a large number of applications which experience problems with time zones, because the timeline model is used instead of the everday model. Such problems are often seen by end users as serious bugs, because telling people the wrong date or time is often a serious issue. These problems make you look stupid.
Date Classes in the JDK are Mediocre
The JDK's classes related to dates are widely regarded as frustrating to work with, for various reasons:
- mistakes regarding time zones are very common
- month indexes are 0-based, leading to off-by-one errors
- difficulty of calculating simple time intervals
- java.util.Date is mutable, but 'building block' classes should be immutable
- numerous other minor nuisances
Joda Time Has Drawbacks As Well
The
Joda Time library is used by some programmers as an alternative to the JDK classes. Joda Time has the following drawbacks :
- it limits precision to milliseconds. Database timestamp values almost always have a precision of microseconds or even nanoseconds. This is a serious defect: a library should never truncate your data, for any reason.
- it's large, with well over 100 items in its javadoc
- in order to stay current, it needs to be manually updated occasionally with fresh time zone data
- it has mutable versions of classes
- it always coerces March 31 + 1 Month to April 30 (for example), without giving you any choice in the matter
- some databases allow invalid date values such as '0000-00-00', but Joda Time doesn't seem to be able to handle them
Dates and Times in General
Civil Timekeeping Is Complex
Civil timekeeping is a byzantine hodge-podge of arcane and arbitrary rules. Consider the following :
- months have varying numbers of days
- one month (February) has a length which depends on the year
- not all years have the same number of days
- time zone rules spring forth arbitrarily from the fecund imaginations of legislators
- summer hours mean that an hour is 'lost' in the spring, while another hour must repeat itself in the autumn, during the switch back to normal time
- summer hour logic varies widely across various jurisdictions
- the cutover from the Julian calendar to the Gregorian calendar happened at different times in different places, which causes a varying number of days to be 'lost' during the cutover
- occasional insertion of leap seconds are used to ensure synchronization with the rotating Earth (whose speed of rotation is gradually slowing down, in an irregular way)
- there is no year 0 (1 BC is followed by 1 AD), except in the reckoning used by astronomers
How Databases Treat Dates
Most databases model dates and times using the Gregorian Calendar in an aggressively simplified form, in which :
- the Gregorian calendar is extended back in time as if it was in use previous to its inception (the 'proleptic' Gregorian calendar)
- the transition between Julian and Gregorian calendars is entirely ignored
- leap seconds are entirely ignored
- summer hours are entirely ignored
- often, even time zones are ignored, in the sense that the underlying database column doesn't usually explicitly store any time zone information.
The final point requires elaboration. Some may doubt its veracity, since they have seen date-time information "change time zone" when retrieved from a database. But this sort of change is usually applied using logic which is external to the data stored in the particular column.
For example, the following items might be used in the calculation of a time zone difference :
- time zone setting for the client (or JDBC driver)
- time zone setting for the client's connection to the database server
- time zone setting of the database server
- time zone setting of the host where the database server resides
(Note as well what's missing from the above list: your own application's logic, and the user's time zone preference.)
When an end user sees such changes to a date-time, all they will say to you is "Why did you change it? That's not what I entered" - and this is a completely valid question. Why did you change it? Because you're using the timeline model instead of the everyday model. Perhaps you're using a inappropriate abstraction for what the user really wants.
The Approach Used By This Class
This class takes the following design approach :
- it models time in the "everyday" style, not in the "timeline" style (see above)
- its precision matches the highest precision used by databases (nanosecond)
- it uses only the proleptic Gregorian Calendar, over the years 1..9999
- it ignores all non-linearities: summer-hours, leap seconds, and the cutover from Julian to Gregorian calendars
- it ignores time zones. Most date-times are stored in columns whose type does not include time zone information (see note above).
- it has (very basic) support for wonky dates, such as the magic value 0000-00-00 used by MySQL
- it's immutable
- it lets you choose among 4 policies for 'day overflow' conditions during calculations
- it talks to your {@link TimeSource} implementation when returning the current moment, allowing you to customise dates during testing
Even though the above list may appear restrictive, it's very likely true that DateTime can handle the dates and times you're currently storing in your database.
Two Sets Of Operations
This class allows for 2 sets of operations: a few "basic" operations, and many "computational" ones.
Basic operations model the date-time as a simple, dumb String, with absolutely no parsing or substructure. This will always allow your application to reflect exactly what is in a ResultSet, with absolutely no modification for time zone, locale, or for anything else.
This is meant as a back-up, to ensure that your application will always be able to, at the very least, display a date-time exactly as it appears in your ResultSet from the database. This style is particularly useful for handling invalid dates such as 2009-00-00, which can in fact be stored by some databases (MySQL, for example). It can also be used to handle unusual items, such as MySQL's TIME datatype.
The basic operations are represented by {@link #DateTime(String)}, {@link #toString()}, and {@link #getRawDateString()}.
Computational operations allow for calculations and formatting. If a computational operation is performed by this class (for example, if the caller asks for the month), then any underlying date-time String must be parseable by this class into its components - year, month, day, and so on. Computational operations require such parsing, while the basic operations do not. Almost all methods in this class are categorized as computational operations.
Parsing DateTime - Accepted Formats
The {@link #DateTime(String)} constructor accepts a
String representation of a date-time.The format of the String can take a number of forms. When retrieving date-times from a database, the majority of cases will have little problem in conforming to these formats. If necessary, your SQL statements can almost always use database formatting functions to generate a String whose format conforms to one of the many formats accepted by the {@link #DateTime(String)} constructor.
Mini-Language for Formatting
This class defines a simple mini-language for formatting a
DateTime, used by the various
format methods.
The following table defines the symbols used by this mini-language, and the corresponding text they would generate given the date:
1958-04-09 Wednesday, 03:05:06.123456789 AM
in an English Locale. (Items related to date are in upper case, and items related to time are in lower case.)
Format | Output | Description | Needs Locale? |
YYYY | 1958 | Year | ... |
YY | 58 | Year without century | ... |
M | 4 | Month 1..12 | ... |
MM | 04 | Month 01..12 | ... |
MMM | Apr | Month Jan..Dec | Yes |
MMMM | April | Month January..December | Yes |
DD | 09 | Day 01..31 | ... |
D | 9 | Day 1..31 | ... |
WWWW | Wednesday | Weekday Sunday..Saturday | Yes |
WWW | Wed | Weekday Sun..Sat | Yes |
hh | 03 | Hour 01..23 | ... |
h | 3 | Hour 1..23 | ... |
hh12 | 03 | Hour 01..12 | ... |
h12 | 3 | Hour 1..12 | ... |
a | AM | AM/PM Indicator | Yes |
mm | 05 | Minutes 01..59 | ... |
m | 5 | Minutes 1..59 | ... |
ss | 06 | Seconds 01..59 | ... |
s | 6 | Seconds 1..59 | ... |
f | 1 | Fractional Seconds, 1 decimal | ... |
ff | 12 | Fractional Seconds, 2 decimals | ... |
fff | 123 | Fractional Seconds, 3 decimals | ... |
ffff | 1234 | Fractional Seconds, 4 decimals | ... |
fffff | 12345 | Fractional Seconds, 5 decimals | ... |
ffffff | 123456 | Fractional Seconds, 6 decimals | ... |
fffffff | 1234567 | Fractional Seconds, 7 decimals | ... |
ffffffff | 12345678 | Fractional Seconds, 8 decimals | ... |
fffffffff | 123456789 | Fractional Seconds, 9 decimals | ... |
| | (no example) | Escape character | ... |
As indicated above, some of these symbols can only be used with an accompanying Locale. In general, if the output is text, not a number, then a Locale will be needed. For example, 'September' is localizable text, while '09' is a numeric representation, which doesn't require a Locale. Thus, the symbol 'MM' can be used without a Locale, while 'MMMM' and 'MMM' both require a Locale, since they generate text, not a number.
The fractional seconds 'f' does not perform any rounding. The escape character '|' allows you to insert arbitrary text.
Examples :
Format | Output |
YYYY-MM-DD hh:mm:ss.fffffffff a | 1958-04-09 03:05:06.123456789 AM |
YYYY-MM-DD hh:mm:ss.fff a | 1958-04-09 03:05:06.123 AM |
YYYY-MM-DD | 1958-04-09 |
hh:mm:ss.fffffffff | 03:05:06.123456789 |
hh:mm:ss | 03:05:06 |
YYYY-M-D h:m:s | 1958-4-9 3:5:6 |
WWWW, MMMM D, YYYY | Wednesday, April 9, 1958 |
WWWW, MMMM D, YYYY |at| D a | Wednesday, April 9, 1958 at 3 AM |
Interaction with {@link TimeSource}
The methods of this class related to the current date interact with your configured implementation of {@link hirondelle.web4j.util.TimeSource}. That is, {@link #now(TimeZone)} and {@link #today(TimeZone)}will return values derived from {@link TimeSource}. Thus, callers of this class automatically use any 'fake' system clock that you may want to define.
Passing DateTime Objects to the Database
When a
DateTime is passed as a parameter to an SQL statement, the
DateTime is formatted into a
String of a form accepted by the database. There are two mechanisms to accomplish this
- in your DAO code, format the DateTime explicitly as a String, using one of the format methods, and pass the String as the parameter to the SQL statement, and not the actual DateTime
- pass the DateTime itself. In this case, WEB4J will use the setting in web.xml named DateTimeFormatForPassingParamsToDb to perform the formatting for you. If the formats defined by this setting are not appropriate for a given case, you will need to format the DateTime as a String explicitly instead.