How do you store Date ranges, which are actually timestamps - java

Java & Oracle both have a timestamp type called Date. Developers tend to manipulate these as if they were calendar dates, which I've seen cause nasty one-off bugs.
For a basic date quantity you can simply chop off the time portion upon input, i.e., reduce the precision. But if you do that with a date range, (e.g.: 9/29-9/30), the difference between these two values is 1 day, rather than 2. Also, range comparisons require either 1) a truncate operation: start < trunc(now) <= end, or 2) arithmetic: start < now < (end + 24hrs). Not horrible, but not DRY.
An alternative is to use true timestamps: 9/29 00:00:00 - 10/1 00:00:00. (midnight-to-midnight, so does not include any part of Oct). Now durations are intrinsically correct, and range comparisons are simpler: start <= now < end. Certainly cleaner for internal processing, however end dates do need to be converted upon initial input (+1), and for output (-1), presuming a calendar date metaphor at the user level.
How do you handle date ranges on your project? Are there other alternatives? I am particularly interested in how you handle this on both the Java and the Oracle sides of the equation.

Here's how we do it.
Use timestamps.
Use Half-open intervals for comparison: start <= now < end.
Ignore the whiners who insist that BETWEEN is somehow essential to successful SQL.
With this a series of date ranges is really easy to audit. The database value for 9/30 to 10/1 encompass one day (9/30). The next interval's start must equal the previous interval's end. That interval[n-1].end == interval[n].start rule is handy for audit.
When you display, if you want, you can display the formatted start and end-1. Turns out, you can educate people to understand that the "end" is actually the first day the rule is no longer true. So "9/30 to 10/1" means "valid starting 9/30, no longer valid starting 10/1".

Oracle has the TIMESTAMP datatype. It stores the year, month, and day of the DATE datatype, plus hour, minute, second and fractional second values.
Here is a thread on asktom.oracle.com about date arithmetic.

I second what S.Lott explained. We have a product suite which makes extensive use of date time ranges and it has been one of our lessons-learned to work with ranges like that. By the way, we call the end date exclusive end date if it is not part of the range anymore (IOW, a half open interval). In contrast, it is an inclusive end date if it counts as part of the range which only makes sense if there is no time portion.
Users typically expect input/output of inclusive date ranges. At any rate, convert user input as soon as possible to exclusive end date ranges, and convert any date range as late as possible when it has to be shown to the user.
On the database, always store exclusive end date ranges. If there is legacy data with inclusive end date ranges, either migrate them on the DB if possible or convert to exclusive end date range as soon as possible when the data is read.

I use Oracle's date data type and educate developers on the issue of time components affecting boundary conditions.
A database constraint will also prevent the accidental specification of a time component in a column that should have none and also tells the optimizer that none of the values have a time component.
For example, the constraint CHECK (MY_DATE=TRUNC(MY_DATE)) prevents a value with a time other than 00:00:00 being placed into the my_date column, and also allows Oracle to infer that a predicate such as MY_DATE = TO_DATE('2008-09-12 15:00:00') will never be true, and hence no rows will be returned from the table because it can be expanded to:
MY_DATE = TO_DATE('2008-09-12 15:00:00') AND
TO_DATE('2008-09-12 15:00:00') = TRUNC(TO_DATE('2008-09-12 15:00:00'))
This is automatically false of course.
Although it is sometimes tempting to store dates as numbers such as 20080915 this can cause query optimization problems. For example, how many legal values are there between 20,071,231 and 20,070,101? How about between the dates 31-Dec-2007 abnd 01-Jan-2008? It also allows illegal values to be entered, such as 20070100.
So, if you have dates without time components then defining a range becomes easy:
select ...
from ...
where my_date Between date '2008-01-01' and date '2008-01-05'
When there is a time component you can do one of the following:
select ...
from ...
where my_date >= date '2008-01-01' and
my_date < date '2008-01-06'
or
select ...
from ...
where my_date Between date '2008-01-01'
and date '2008-01-05'-(1/24/60/60)
Note the use of (1/24/60/60) instead of a magic number. It's pretty common in Oracle to perform date arithmetic by adding defined fractions of a day ... 3/24 for three hours, 27/24/60 for 27 minutes. Oracle math of this type is exact and doesn't suffer rounding errors, so:
select 27/24/60 from dual;
... gives 0.01875, not 0.01874999999999 or whatever.

I don't see the Interval datatypes posted yet.
Oracle also has datatypes for your exact scenario. There are INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND datatypes in Oracle as well.
From the 10gR2 docs.
INTERVAL YEAR TO MONTH stores a period
of time using the YEAR and MONTH
datetime fields. This datatype is
useful for representing the difference
between two datetime values when only
the year and month values are
significant.
INTERVAL YEAR [(year_precision)] TO
MONTH
where year_precision is the number of
digits in the YEAR datetime field. The
default value of year_precision is 2.
INTERVAL DAY TO SECOND Datatype
INTERVAL DAY TO SECOND stores a period
of time in terms of days, hours,
minutes, and seconds. This datatype is
useful for representing the precise
difference between two datetime
values.
Specify this datatype as follows:
INTERVAL DAY [(day_precision)] TO
SECOND
[(fractional_seconds_precision)]
where
day_precision is the number of digits
in the DAY datetime field. Accepted
values are 0 to 9. The default is 2.
fractional_seconds_precision is the
number of digits in the fractional
part of the SECOND datetime field.
Accepted values are 0 to 9. The
default is 6.
You have a great deal of flexibility
when specifying interval values as
literals. Please refer to "Interval
Literals" for detailed information on
specify interval values as literals.
Also see "Datetime and Interval
Examples" for an example using
intervals.

Based on my experiences, there are four main ways to do it:
1) Convert the date to an epoch integer (seconds since 1st Jan 1970) and store it in the database as an integer.
2) Convert the date to a YYYYMMDDHHMMSS integer and store it in the database as an integer.
3) Store it as a date
4) Store it as a string
I've always stuck with 1 and 2, because it enables you to perform quick and simple arithmetic with the date and not rely on the underlying database functionality.

Based upon your first sentence, you're stumbling upon one of the hidden "features" (i.e. bugs) of Java: java.util.Date should have been immutable but it ain't. (Java 7 promises to fix this with a new date/time API.) Almost every enterprise app counts on various temporal patterns, and at some point you will need to do arithmetic on date and time.
Ideally, you could use Joda time, which is used by Google Calendar. If you can't do this, I guess an API that consists of a wrapper around java.util.Date with computational methods similar to Grails/Rails, and of a range of your wrapper (i.e. an ordered pair indicating the start and end of a time period) will be sufficient.
On my current project (an HR timekeeping application) we try to normalize all our Dates to the same timezone for both Oracle and Java. Fortunately, our localization requirements are lightweight (= 1 timezone is enough). When a persistent object doesn't need finer precision than a day, we use the timestamp as of midnight. I would go further and insist upon throwing away the extra milli-seconds to the coarsest granularity that a persistent object can tolerate (it will make your processing simpler).

All dates can be unambiguously stored as GMT timestamps (i.e. no timezone or daylight saving headaches) by storing the result of getTime() as a long integer.
In cases where day, week, month, etc. manipulations are needed in database queries, and when query performance is paramount, the timestamps (normalized to a higher granularity than milliseconds) can be linked to a date breakdown table that has columns for the day, week, month, etc. values so that costly date/time functions don't have to be used in queries.

Alan is right- Joda time is great. java.util.Date and Calendar are just a shame.
If you need timestamps use the oracle date type with the time, name the column with some kind of suffix like _tmst. When you read the data into java get it into a joda time DateTime object. to make sure the timezone is right consider that there are specific data types in oracle that will store the timestamps with the timezone. Or you can create another column in the table to store the timezone ID. Values for the timezone ID should be standard full name ID for Timezones see http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String%29 . If you use another column for the TZ dta then when you read the data into java use DateTime object but set the timezone on the DateTime object using the .withZoneRetainFields to set the timezone.
If you only need the date data (no timestamp) then use the date type in the database with no time. again name it well. in this case use DateMidnight object from jodatime.
bottom line: leverage the type system of the database and the language you are using. Learn them and reap the benefits of having expressive api and language syntax to deal with your problem.

UPDATE: The Joda-Time project is now in maintenance mode. Its team advises migration to the java.time classes built into Java.
Joda-Time
Joda-Time offers 3 classes for representing a span of time: Interval, Duration, and Period.
The ISO 8601 standard specifies how to format strings representing a Duration and an Interval. Joda-Time both parses and generates such strings.
Time zone is a crucial consideration. Your database should be storing its date-time values in UTC. But your business logic may need to consider time zones. The beginning of a "day" depends on time zone. By the way, use proper time zone names rather than 3 or 4 letter codes.
The correct answer by S.Lott wisely advises to use Half-Open logic, as that usually works best for date-time work. The beginning of a span of time is inclusive while the ending is exclusive. Joda-Time uses half-open logic in its methods.
DateTimeZone timeZone_NewYork = DateTimeZone.forID( "America/New_York" );
DateTime start = new DateTime( 2014, 9, 29, 15, 16, 17, timeZone_NewYork );
DateTime stop = new DateTime( 2014, 9, 30, 1, 2, 3, timeZone_NewYork );
int daysBetween = Days.daysBetween( start, stop ).getDays();
Period period = new Period( start, stop );
Interval interval = new Interval( start, stop );
Interval intervalWholeDays = new Interval( start.withTimeAtStartOfDay(), stop.plusDays( 1 ).withTimeAtStartOfDay() );
DateTime lateNight29th = new DateTime( 2014, 9, 29, 23, 0, 0, timeZone_NewYork );
boolean containsLateNight29th = interval.contains( lateNight29th );
Dump to console…
System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "daysBetween: " + daysBetween );
System.out.println( "period: " + period ); // Uses format: PnYnMnDTnHnMnS
System.out.println( "interval: " + interval );
System.out.println( "intervalWholeDays: " + intervalWholeDays );
System.out.println( "lateNight29th: " + lateNight29th );
System.out.println( "containsLateNight29th: " + containsLateNight29th );
When run…
start: 2014-09-29T15:16:17.000-04:00
stop: 2014-09-30T01:02:03.000-04:00
daysBetween: 0
period: PT9H45M46S
interval: 2014-09-29T15:16:17.000-04:00/2014-09-30T01:02:03.000-04:00
intervalWholeDays: 2014-09-29T00:00:00.000-04:00/2014-10-01T00:00:00.000-04:00
lateNight29th: 2014-09-29T23:00:00.000-04:00
containsLateNight29th: true

Im storing all dates in milliseconds. I do not use timestamps/datetime fields at all.
So, i have to manipulate it as longs. It means i do not use 'before', 'after', 'now' keywords in my sql queries.

Related

How can I take into account the TimeZone of java.sql.Date when I read it from database?

My application reads java.sql.Date from database witch contains these dates in America/New_York time zone (-4 UTC). After a fetching of data Hibernate creates objects java.sql.Date and represents them in my local time zone. So, I need to convert date from database in UTC directly. How can I do that?
I need something like this
Instant.ofEpochMilli(((java.sql.Date) value).getTime()).atOffset(offset);
But offset doesn't do what I want. For example:
time in database: 01-02-2020 22:00 (in America/New_York -> it's UTC-4 and I need to add extra 4 hours)
time in my application: 01-02-2020 22:00 +4 (because my time zone is UTC+4). When I set ZoneOffset.UTC
Instant.ofEpochMilli(((java.sql.Date) value).getTime()).atOffset(ZoneOffset.UTC)
it removes 4 hours ans toString() result = 01-02-2020T16:00Z
How can I add 4 hour to date (java.sql.Date) in database so that it would be 02-02-2020 02:00 UTC ?
For a point in time with a time zone such as 2020-02-01T22:00-04:00[America/New_York], do not use java.sql.Date. For two reasons:
java.sql.Date is a poorly designed class, a true hack, indeed, on top if the already poorly designed java.util.Date class. Fortunately both Date classes are also long outdated.
java.sql.Date was designed for a date without time of day.
Instead:
In your SQL database use timestamp with time zone and store times consistently in UTC. So the time stored in your database should be 2020-02-02T02:00Z (Z for UTC).
In Java retrieve your time into an OffsetDateTime (since JDBC 4.2 we can do that, bypassing java.sql.Date and java.sql.Timestamp completely). Then if needed convert to a ZonedDateTime in your time zone. Use a proper time zone ID in the region/city format (not just what you think the UTC offset is).
For a demonstration:
ZoneId zone = ZoneId.of("Asia/Tbilisi");
OffsetDateTime dateTimeFromDatabase
= OffsetDateTime.of(2020, 2, 2, 2, 0, 0, 0, ZoneOffset.UTC);
ZonedDateTime dateTimeInYourTimeZone
= dateTimeFromDatabase.atZoneSameInstant(zone);
System.out.println(dateTimeInYourTimeZone);
Output:
2020-02-02T06:00+04:00[Asia/Tbilisi]
Edit 1: You said:
I understand that this is bad to use outdated java.sql.Date, but I
have no choice. "java.sql.Date was designed for a date without time of
day." - but I thought I can anyway get time of day by calling
(java.sql.Date) value).getTime() (because it returns timestamp)
From the documentation:
To conform with the definition of SQL DATE, the millisecond values
wrapped by a java.sql.Date instance must be 'normalized' by setting
the hours, minutes, seconds, and milliseconds to zero in the
particular time zone with which the instance is associated.
So it seems to me that you’re breaking the contract. What the consequences are, I don’t know. They probably depend on your JDBC driver. That is, behaviour might change with the next version of that JDBC driver.
Edit 2: I took a closer look at your data. I agree with you that they are wrong; but the problem is not in the code you have presented, it’s in the java.sql.Date object that you seem to have received somehow.
For my investigation I did:
// time in database: 01-02-2020 22:00
// (in America/New_York -> it's UTC-4 and I need to add extra 4 hours)
ZonedDateTime dateTimeInDatebase = ZonedDateTime
.of(2020, 2, 1, 22, 0, 0, 0, ZoneId.of("America/New_York"));
System.out.println("In database: " + dateTimeInDatebase);
long correctEpochMillis = dateTimeInDatebase.toInstant().toEpochMilli();
System.out.println("Correct millis: " + correctEpochMillis);
// toString() result = 01-02-2020T16:00Z
OffsetDateTime observedDateTime
= OffsetDateTime.of(2020, 2, 1, 16, 0, 0, 0, ZoneOffset.UTC);
long observedEpochMilli = observedDateTime.toInstant().toEpochMilli();
System.out.println("Observed millis: " + observedEpochMilli);
Duration error = Duration.between(dateTimeInDatebase, observedDateTime);
System.out.println("Error: " + error);
The output is:
In database: 2020-02-01T22:00-05:00[America/New_York]
Correct millis: 1580612400000
Observed millis: 1580572800000
Error: PT-11H
Observations:
The UTC offset in New York in February is not -04:00 but -05:00 (-04:00 is the correct offset during summer time/DST).
The millisecond value that you have retrieved from your java.sql.Date does not denote the point in time that it should. There is nothing in your code that changes the point in time. So you are not only getting an incorrect type, you are also getting an incorrect value.
Read the error printed in the last output line as a period of time of minus 11 hours. The millisecond value in your java.sql.Date is 11 hours too early.
You have yourself explained some of the discrepancy with the time zone difference, and I believe that this is true. We have not yet verified that this is the whole story. So I also cannot tell you what the solution is. Other than filing a ticket to the provider of your incorrect type and value so you get correct data instead. A possible hack is to add 11 hours, of course, but whether you then should add only 10 hours in the summer time part of the year — I am not the correct person to ask.
Edit 3:
I just came up with an idea to fix twice value of timestamp. Like the
first time - add offset of local zone (fix the influence of jdbc
driver), and the second - handle offset of dates stored in database.
We can do that if we want:
Instant observedResult = Instant.parse("2020-02-01T16:00:00Z");
Object receivedValue = new java.sql.Date(observedResult.toEpochMilli());
long receivedEpochMillis = ((java.sql.Date) receivedValue).getTime();
ZonedDateTime adjustedDateTime = Instant.ofEpochMilli(receivedEpochMillis)
.atZone(ZoneId.systemDefault())
.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println(adjustedDateTime);
Output when run in Asia/Tbilisi time zone (so this is what ZoneId.systemDefault() returned; it’s at offset +04:00 all year):
2020-02-01T20:00-05:00[America/New_York]
It brings us closer to what you say was in the database, but it’s still a couple of hours too early. I am sorry.
Links
My answer to a related question: Getting the date from a ResultSet for use with java.time classes
Documentation of java.sql.Date

How to check if 2 dates are on the same day in Java

I have 2 Date variables, Date1 and Date2.
I want to check if Date 1 fall on the same date as Date2 (but they are allowed to have different times).
How do i do this?
It looks like a really easy thing to do, but i'm struggling.
EDIT: I want to avoid external libraries and stuff
EDIT:
My orgional idea was to remove the hour, min, sec but those features are marked as depreciated in Java. So what should I use????
Although given answers based on date component parts of a java.util.Date are sufficient in many parts, I would stress the point that a java.util.Date is NOT a date but a kind of UNIX-timestamp measured in milliseconds. What is the consequence of that?
Date-only comparisons of Date-timestamps will depend on the time zone of the context. For example in UTC time zone the date-only comparison is straight-forward and will finally just compare year, month and day component, see other answers (I don't need to repeat).
But consider for example the case of Western Samoa crossing the international dateline in 2011. You can have valid timestamps of type java.util.Date, but if you consider their date parts in Samoa you can even get an invalid date (2011-12-30 never existed in Samoa locally) so a comparison of just the date part can fail. Furthermore, depending on the time zone the date component can generally differ from local date in UTC zone by one day, ahead or behind, in worst case there are even two days difference.
So following extension of solution is slightly more precise:
SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
fmt.setTimeZone(...); // your time zone
return fmt.format(date1).equals(fmt.format(date2));
Similar extension also exists for the more programmatic approach to first convert the j.u.Date-timestamp into a java.util.GregorianCalendar, then setting the time zone and then compare the date components.
Why don't you simply compare the year, month and day? You can write your method for doing it something like:
private boolean isDateSame(Calendar c1, Calendar c2) {
return (c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) &&
c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH) &&
c1.get(Calendar.DAY_OF_MONTH) == c2.get(Calendar.DAY_OF_MONTH));
}
Today = Span Of Time
While the other answers may be correct, I prefer the approach where we recognize that "today" is actually a span of time.
Because of anomalies such as Daylight Saving Time (DST), days vary in length, not always 24 hours long. Here in the United States, some days are 23 hours long, some 25.
Half-Open
Commonly in data-time work, we use the "Half-Open" strategy where the beginning of a span is inclusive and the ending is exclusive. So that means "today" spans from the first moment of today up to, but not including, the first moment of tomorrow.
Time Zones
Time zones are critical, as explained in the correct answer by Meno Hochschild. The first moment of a day depends on its time zone rules.
Joda-Time
The Joda-Time library has nice classes for handling spans of time: Period, Duration, and Interval.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime now = new DateTime( timeZone );
Interval today = new Interval( now.withTimeAtStartOfDay(), now.plusDays(1).withTimeAtStartOfDay() );
DateTime dateTimeInQuestion = new DateTime( date ); // Convert java.util.Date.
boolean happensToday = today.contains( dateTimeInQuestion );
Benefits
This approach using a span of time has multiple benefits:
Avoids Daylight Saving Time (DST) issues
Lets you compare date-time values from other time zones
Flexible, so you can use the same kind of code for other spans (multiple days, months, etc.)
Gets your mind shifted away from calendar dates (a layered abstraction) and onto date-times as points on a flowing timeline (the underlying truth).
Java 8 has a new java.time package built-in. These new classes are modeled after Joda-Time but are entirely re-architected. This same kind of code can be written using java.time.
When you use the toString() method what do you get? Is it only the year/month/day or time too? If it is then you could simply compare the strings of the two objects. (date1.toString().equals(date2.toString()));

Java: How do I store a timeline/schedule, location full or empty from time x to y (interval)?

Some brief background:
I have a java app that's used to see when certain locations (classrooms) are in use or not. The user puts a location identifier into a search box and the program displays any events (classes) matching. The app will display all the pertinent information (class name, room #, professor name, day of week, time of class), classes that are in-session (or soon to be) are color-coded so you can tell at-a-glance if there's anything coming up. The data comes from an html page I'm scraping from (I don't have SQL access).
Everything to this point works, I'm using JavaSwing for the UI. The events are stored as a basic object I made to hold it, the only part that matters for my question is that it stores to Java Date objects for the start and end time of each event.
What I'm trying to do now is add a way to check for and display the gaps between events (when a classroom is empty). I have all the data I need (all the start and end times) but I'm having trouble coming up with a way to actually to actually code the program to see the gaps.
And if that's not challenging enough: I want to implement it as simply as possible, not using any extra libraries. I don't want to re-create the wheel, but I'd much rather hash out the code by hand than just throw in an import and a couple method calls; half the point of this project is to challenge myself.
For now I'm not looking for advice on how to actually display the data, for now I'm just trying to logically organize it in a usable way. It will end up being displayed in a JTable, but I'm not working on that implementation yet.
My scraper pulls from an html table something like <td id=time>10:00AM - 11:15AM</td> and parses it down to two strings 10:00AM and 11:15AM. These get passed with other data to my Course object.
My Course object has a DateFormat set so it can interpret the incoming Strings into Dates.
public class Course {
static DateFormat tf = new SimpleDateFormat("h:mma");
private String name;
private String room;
private String instructor;
private Date start;
private Date end;
private String days;
public Course(String n, String r, String in, String st, String ed, String d) throws ParseException{
name=n;
room=r;
instructor=in;
start=tf.parse(st);
end=tf.parse(ed);
days=d;
}
//...basic getters, setters, and a toString()
The idea is that I'll set two times, setting the window in which I want to check for gaps. I want to pick an interval, say 15 minutes, and find all the gaps of 15 or more minutes during which a classroom is empty, i.e. no classes meeting.
I don't have a complete answer, but I have some suggestions.
Time Zone
A Date represents a date-time in UTC/GMT, that is, no time zone offset. Your values are in a time zone. In the long run, you'll be better off by not ignoring that time zone. Adjust your values. Generally think, work, and store date-times in UTC/GMT. Convert to local time as needed for presentation in the user interface.
While a java.util.Date has no time zone, a Joda-Time DateTime (discussed below) does indeed know its own time zone and time zone offset.
Avoid java.util.Date/Calendar
The java.util.Date and Calendar classes bundled with Java are notoriously troublesome. Avoid them.
Instead, use either:
Joda-Time
The new java.time.* package in Java 8.That package is inspired by Joda-Time, defined by JSR 310, and supplants the old bundled classes.
Don't think of Joda-Time as any old "extra library". The first thing I do when creating a new project in my IDE is add the Joda-Time library. It is just that good. Or j.u.Date/Calendar is just that bad, depending on your perspective.
Start Inclusive, End Exclusive
The best way to handle a span of time is to make the beginning inclusive and the ending exclusive. Write logic where you check for GREATER THAN OR EQUALS to the beginning and LESS THAN the ending (not testing for equals on the ending). I discuss this more in another answer along with a diagram.
Joda-Time
Joda-Time offers these classes for defining a span of time: Period, Duration, and Internal. The Hours and similar classes offer some handy utility methods.
Find Gaps
If your goal is to make a list of gaps, of spans of time where a classroom is not assigned to a class, then the obvious way it seems to me is to sort the classes chronologically (and by classroom) as in the answer by Rob Whiteside. Define an "available" span of time where its beginning is the prior class’ ending, and its ending is the next class’ beginning. I've no experience in this arena, so there may be a more clever way.
See this question about sorting a List of Intervals.
Example Code
Here is some example code using Joda-Time 2.3.
Specify a time zone rather than rely on default…
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
Create three classes as sample data, in arbitrary order…
Interval class_math = new Interval( new DateTime( 2014, 1, 24, 10, 0, 0, timeZone ), new DateTime( 2014, 1, 24, 11, 15, 0, timeZone ) );
Interval class_chemistry = new Interval( new DateTime( 2014, 1, 24, 8, 0, 0, timeZone ), new DateTime( 2014, 1, 24, 9, 15, 0, timeZone ) );
Interval class_french_lit = new Interval( new DateTime( 2014, 1, 24, 13, 0, 0, timeZone ), new DateTime( 2014, 1, 24, 14, 15, 0, timeZone ) );
Collect the sample data into a List…
java.util.List<Interval> classes = new java.util.ArrayList<Interval>( 3 );
classes.add( class_math );
classes.add( class_chemistry );
classes.add( class_french_lit );
System.out.println( "classes unsorted: " + classes );
Sort the List using a custom Comparator (see class definition below)…
java.util.Collections.sort( classes, new IntervalStartComparator() );
System.out.println( "classes sorted: " + classes );
Make a collection of objects representing each gap between classes we find…
java.util.List<Interval> gaps = new java.util.ArrayList<Interval>();
DateTime gapStart = null, gapStop = null;
for ( int i = 0; i < classes.size(); i++ ) {
// For each class, take the prior class' end as the gap's beginning, and the next class' start as the gap's ending.
Interval session = classes.get( i ); // Cannot name the var "class" because that is a keyword in Java.
if ( i == 0 ) { // If first time through, grab the end of the first class as our first gap's start.
gapStart = session.getEnd();
continue;
}
gapStop = session.getStart();
Interval gap = new Interval( gapStart, gapStop );
gaps.add( gap );
gapStart = session.getEnd();
}
System.out.println( "gaps: " + gaps );
The class definition for the Comparator used in code above and lifted from this answer by Jon Skeet…
class IntervalStartComparator implements java.util.Comparator<Interval> {
#Override
public int compare( Interval x, Interval y ) {
return x.getStart().compareTo( y.getStart() );
}
}
When run…
classes unsorted: [2014-01-24T10:00:00.000+01:00/2014-01-24T11:15:00.000+01:00, 2014-01-24T08:00:00.000+01:00/2014-01-24T09:15:00.000+01:00, 2014-01-24T13:00:00.000+01:00/2014-01-24T14:15:00.000+01:00]
classes sorted: [2014-01-24T08:00:00.000+01:00/2014-01-24T09:15:00.000+01:00, 2014-01-24T10:00:00.000+01:00/2014-01-24T11:15:00.000+01:00, 2014-01-24T13:00:00.000+01:00/2014-01-24T14:15:00.000+01:00]
gaps: [2014-01-24T09:15:00.000+01:00/2014-01-24T10:00:00.000+01:00, 2014-01-24T11:15:00.000+01:00/2014-01-24T13:00:00.000+01:00]
Duration
You said you only care about gaps at least 15 minutes long. A Duration instance in Joda-Time represents the milliseconds between the start and stop points of an Interval.
Here is some untested off-the-top-of-my-head code.
Renamed "gap" var from above to "gapInterval" to remind you it is an Interval instance.
Note that Minutes is a class. The "minutes" var seen below is an instance, not an integer primitive ("int"). Calling the getMinutes method renders an int primitive.
Duration duration = gapInterval.toDuration();
Minutes minutes = duration.toStandardMinutes(); // "Standard" means ignoring time zone anomalies such as Daylight Saving Time (DST).
int mins = minutes.getMinutes();
boolean isGapSignificant = ( mins >= 15 );
ISO 8601
The string outputs you see there are not arbitrary. Those are ISO 8601 formats. That handy standard defines string representations of single date-time values as well as the <start>/<end> time interval.
That standard also defines a string representation of durations that may prove useful to you, in the format of PnYnMnDTnHnMnS such as "P3Y6M4DT12H30M5S" meaning "three years, six months, four days, twelve hours, thirty minutes, and five seconds". Or shorter, as in your case, a class may be PT1H15M for one and a quarter hours.
Joda-Time uses ISO 8601 as its default for most everything, as both inputs and outputs.
Sorting is probably the key to your problem here.
Presumably you have a big collection of all the courses you've scraped. First you'll need to be able to pull out all the courses given a particular location into a new list. You'll want this list to be sorted by the course date. Check out the various sorted collections (e.g. TreeSet). You'll probably also need to make use of a "comparator" to make sure your courses get sorted by their date.
Once you have that list, it's just a matter of iterating over it and finding the gaps.

How to sum 2 joda-time DateTime values (one containing a date and a zero time and another containing a time and a zero date)?

In a Scala 2.8 program of mine I use joda-time with its scala-time wrapper. I've got 2 DateTime values, one for a date (with zero time fields) and one for time (with zero date fields) (the reason of separation is a storage architecture).
How do I get another DateTime value with both date and time parts set from a source pair?
You should be using LocalDate for the date and LocalTime for the time. Those are the appropriate types for dates and times respectively. You can get each from a DateTime with DateTime.toLocalDate() and DateTime.toLocalTime() if you have to have them as DateTime values to start with. Ideally you wouldn't build a DateTime at all until you've got both bits separately though :)
Then you can LocalDate.toDateTime(LocalTime, DateTimeZone).

Calculating difference in dates in Java

I find it funny that Java (or the java.util library) does not have a built-in function to calculate difference in dates. I want to subtract one date from another to get the elapsed time between them. What is the best way to do this?
I know the simple way is to take the difference of the time in milliseconds and then convert that into days. However, I wanted to know if this works in all cases (with daylight saving, etc.).
Java's not missing much, if you look at open source: try Joda-Time.
I know the simple way is to take the
difference of the time in milliseconds
and then convert that into days.
However, i wanted to know if this
works in all cases (with daylight
saving, etc.).
If your times are derived from UTC dates, or they are just the difference between two calls to System.getCurrentTimeMillis() measured on the same system, you will get a valid number of milliseconds as the difference, independent of any timezone issues. (which is why everything should be using UTC as a storage format -- it's much easier to go from UTC->local time; if you try to go the other way then you need to store the local timezone along with the local time -- or attempt to infer it, gack!)
As for turning this into a number of days, you should just be able to divide by 86400000... with the caveat that there is an occasional leap second every other year or so.
Use either Joda-Time or the new java.time package in Java 8.
Both frameworks use the Half-Open approach where the beginning is inclusive while the ending is exclusive. Sometimes notated as [). This is generally the best approach for defining spans of time.
java.time
The java.time framework built into Java 8 and later has a Period class to represent a span of time as a number of years, a number of months, and a number of days. But this class is limited to whole days, no representation of hours, minutes, and seconds.
Note that we specify a time zone, crucial for determining a date. For example, a new day dawns earlier in Paris than in Montréal.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate now = LocalDate.now( zoneId );
LocalDate then = LocalDate.of( 2001, 1, 1 );
Period period = Period.between( then, now );
Then: 2001-01-01. Now: 2015-09-07. Period: P14Y8M6D. Days: 5362
For whole days, then Daylight Saving Time (DST) is irrelevant.
If you want a count of total days, use the ChronoUnit enum which includes some calculation methods. Notice the calculations return a long.
long days = ChronoUnit.DAYS.between( then, now ); // "5362" seen above.
I have asked about doing a full period in java.time, including hours, minutes, seconds. Not possible as of Java 8. A surprising workaround using the bundled libraries was suggested by Meno Hochschild: Use a Duration class found in the javax.xml.datatype package.
Joda-Time
Here is some example code in Joda-Time 2.3.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime start = new DateTime( 2014, 1, 2, 3, 4, 5, timeZone );
DateTime stop = new DateTime( 2014, 5, 2, 3, 4, 5, timeZone );
Period period = new Period( start, stop );
Calling toString will get you a string representation in the form defined by the ISO 8601 standard, PnYnMnDTnHnMnS.
With the date4j library:
int numDaysBetween = oneDate.numDaysFrom(anotherDate);
There is simple way to implement it. We can use Calendar.add method with loop.
For example as below,
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date beginDate = dateFormat.parse("2013-11-29");
Date endDate = dateFormat.parse("2013-12-4");
Calendar beginCalendar = Calendar.getInstance();
beginCalendar.setTime(beginDate);
Calendar endCalendar = Calendar.getInstance();
endCalendar.setTime(endDate);
The minus days between beginDate and endDate, and the code as below,
int minusDays = 0;
while (true) {
minusDays++;
// Day increasing by 1
beginCalendar.add(Calendar.DAY_OF_MONTH, 1);
if (dateFormat.format(beginCalendar.getTime()).
equals(dateFormat.format(endCalendar).getTime())) {
break;
}
}
System.out.println("The substractation between two days is " + (minusDays + 1));
Have Fun! #.#
I disagree with the claim that Java doesn't have a mechanism for calculating the difference between dates.
Java was designed for global use. It was designed so that there isn't a concept of date, there is only a concept of "time in milliseconds". Any interpretation of such a universal time as the time-and-date in a specific location under a specific convention is merely a projection or a view.
The calendar class is used to turn this sort of absolute time into dates. You can also add or subtract date components, if you really need to. The only way to provide a difference in term of components between two times would be Calendar generated and specific. Thus, you could argue that the standard library does not include a smart enough Gregorian Calendar, and I would agree that it leaves some to be desired.
That being said, there are numerous implementations of this kind of functionality, I see others have provided examples.
Java's implementation of dates is poor. If you find Joda-Time too complicated, try my little contribution to open source:
http://calendardate.sourceforge.net/javadoc/index.html

Categories

Resources