0

I can't seem to find a good way to manipulate Date objects from strings containing arbitrary modifiers such as "+3days", as described in http://www.php.net/manual/en/datetime.formats.relative.php. I couldn't find anything in the standard Java APIs, nor in Joda-Time.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Phillip
  • 5,366
  • 10
  • 43
  • 62
  • 1
    Can you post a concrete example of something you do in php that you can't with joda? Adding days is trivial and is do-able in joda – Durandal Feb 12 '14 at 06:55
  • 1
    It seems like OP wants someone to be able to input "next Saturday" or "last day of next month", not an explicit shift. Go read the [relative formats documentation](http://www.php.net/manual/en/datetime.formats.relative.php) linked by OP. – beerbajay Feb 12 '14 at 06:56
  • Just found this, which seems to be as close as it gets: http://stackoverflow.com/a/1268380/864358 – Phillip Feb 12 '14 at 07:19

3 Answers3

2

Time Shift Defined By Text

If you want to add or subtract a span of time defined by text, there is an official standard for that, ISO 8601. That standard defines a text format for Duration of PnYnMnDTnHnMnS where P means Period, followed by a number of years, months, days, hours, minutes, seconds. A T in the middle separates time portion from date portion.

Examples:

  • P4Y = 4 Years
  • P1DT12H = A day and a half
  • PT1H30M = An hour and a half

The Joda-Time library speaks ISO 8601 as its default, including this syntax. The Period class takes such a string in its constructor, parsing it automatically with a ISOPeriodFormat.

String input = "P3D"; // 3 days. Three hours would be "PT3H", as another example.

// Get current date-time.
DateTimeZone timeZone = DateTimeZone.forID( "Pacific/Honolulu" );
DateTime now = new DateTime( timeZone );

// Shift from current date-time by a Period specified by ISO "Duration" string.
// Yes, these date-time terms (period, duration, interval) are all mixed up. 
// An effort to standardize such terms is underway. But for now, get used to it and "translate".
Period period = new Period( input );
DateTime dateTime = now.plus( period );

Dump to console…

System.out.println( "now: " + now );
System.out.println( "period: " + period );
System.out.println( "dateTime: " + dateTime );

When run…

now: 2014-02-11T23:02:10.087-10:00
period: P3D
dateTime: 2014-02-14T23:02:10.087-10:00

First Of, Last Of, Next

Search StackOverflow to find many questions answered on using Joda-Time to find first-of-month, beginning and ending of week, and so on. Tip: Search using the shortened word "joda" as few people type the proper name, Joda-Time.

Some solutions are almost built-in, such as accessing the day-of-month property and calling withMinimumValue() to get first-of-month. Others take a few lines of code which you could put into a convenience method for yourself.

Ditto for "Next Saturday" and such.

Tips:

  • Do not forget/ignore time zones.
    The definition of when a day begins depends on time zone, for example.
  • Do not forget Locale.
    The first day of week, for example, depends on Locale.
  • Do not forget that DateTime has a time.
    When thinking about whole days but actually working with DateTime instances, remember to call withTimeAtStartOfDay method. Ignore the "Midnight"-related classes as the "withTimeAtStartOfDay" is meant to replace them.
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1

There is an analogous function in DateTime: DateTime#plusDays.

DateTime dateTime = DateTime.now();
dateTime.plusDays(3); // offset for three days from **now**

There's also a whole family of plus methods, such as plusMinutes, plusSeconds, plusYears...

EDIT: If you want to be semantically lazy...then you have to do more work. I've created a simple regex + switch statement that will perform the appropriate conversion given a verbal time range.

It would be far less painful if you were to stick to convention and used the appropriate method, as opposed to supplying words to another method to eventually call the appropriate method.

The below is only shown for illustration purposes. I don't recommend it since there are cases of millisecond drift (in which you could have a now() of .999 millseconds, and later it could be .000).

public static DateTime shiftDate(String shift) {
    Pattern pattern = Pattern.compile("([+-]\\d+)\\s*(millisecond[s]*|second[s]*|minute[s]*|hour[s]*|day[s]*|week[s]*|month[s]*|year[s]*)");
    Matcher matcher = pattern.matcher(shift);
    if(matcher.matches()) {
        Integer amount = Integer.valueOf(matcher.group(1));
        String duration = matcher.group(2);
        DateTime now = DateTime.now();
        switch(duration) {
            case "millisecond":
            case "milliseconds":
                return now.plusMillis(amount);
            case "second":
            case "seconds":
                return now.plusSeconds(amount);
            case "minute":
            case "minutes":
                return now.plusMinutes(amount);
            case "hour":
            case "hours":
                return now.plusHours(amount);
            case "day":
            case "days":
                return now.plusDays(amount);
            case "week":
            case "weeks":
                return now.plusWeeks(amount);
            case "month":
            case "months":
                return now.plusMonths(amount);
            case "year":
            case "years":
                return now.plusYears(amount);
            case "now":
                return now;
            default:
                throw new IllegalArgumentException("I'm not sure how you got past the guards, but you won't get past here.");
        }
    } else {
        throw new IllegalArgumentException("Invalid shift pattern: " + shift);
    }
}
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • I saw those, but date_modify allows you to pass in an arbitrary string without having to write a custom parser yourself. – Phillip Feb 12 '14 at 06:57
  • This isn't what OP is asking for. – beerbajay Feb 12 '14 at 06:58
  • Well, you're not really writing a custom parser per se; you're merely calling the appropriate methods when required. Java conventions work differently than PHP conventions in that you need to be more explicit in what you call, as opposed to merely passing strings around to do that. – Makoto Feb 12 '14 at 06:58
  • @Makoto You are doing unnecessary work in your answer. See [my answer](http://stackoverflow.com/a/21723442/642706) for info about (a) how the ISO 8601 standard already defines a string format for what it calls "Duration", and (b) how Joda-Time (Period class) already knows how to parse and generate such strings. – Basil Bourque Feb 12 '14 at 09:23
-1

Yeah. Check out Joda-Time It is much more elegant than what Java ships with.

Also be aware that Date objects are not designed to be used in that way. You're supposed to use Calendar objects if you want to manipulate temporal objects. Date objects are not supposed to be manipulated, although for some reason they are not immutable.

Anyway, get Joda-Time and your Dating problems will go away. :)

Mikkel Løkke
  • 3,710
  • 23
  • 37