0

When parsing time strings using SimpleDateFormat, I'm seeing some output that I find confusing. So, given the following:

public class NewMain 
{
    public static void main( String[] args )
    {
        String str1 = "00:00:03";
        parseStr(str1);

        String str2 = "-00:00:03";
        parseStr(str2);

        String str3 = "00:59:59";
        parseStr(str3);

        String str4 = "-00:59:59";
        parseStr(str4);

        String str5 = "01:00:00";
        parseStr(str5);

        String str6 = "-01:00:00";
        parseStr(str6);
    }
    
    private static void parseStr(String aStr)
    {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        Calendar cal = sdf.getCalendar();
        Date date = cal.getTime();
        System.out.println("SimpleDateFormat.getCalendar.getTime: " + date.toString());
        
        try
        {
            Date dee = sdf.parse(aStr);
            String dStr = dee.toString();
            System.out.println("SimpleDateFormat.parse("+aStr+") = " + dStr);
        }
        catch (ParseException ex)
        {
            ex.printStackTrace();
        }
        System.out.println("");
    }
}

And Output:

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(00:00:03) = Thu Jan 01 00:00:03 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(-00:00:03) = Thu Jan 01 00:00:03 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(00:59:59) = Thu Jan 01 00:59:59 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(-00:59:59) = Thu Jan 01 00:59:59 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(01:00:00) = Thu Jan 01 01:00:00 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(-01:00:00) = Wed Dec 31 23:00:00 CST 1969

The first 2 negative times parse out to the given time without the "-". The string "-01:00:00", however, parses out to a completely different time. It looks like the parser is subtracting it from Jan 1st 1970. I'm so confused by this, because "-00:00:03" and "-00:59:59" did NOT have that effect!

Any help would be appreciated!

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • 1
    `SimpleDateFormat` is meant to parse/format dates, not durations nor timers. – Sotirios Delimanolis Dec 08 '20 at 16:34
  • 1
    More precisely, @SotiriosDelimanolis, 10 years ago `SimpleDateFormat` was meant for dates and times of day. Today it’s not meant to be used anymore, and it was always notoriously troublesome. I recommend we use [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Dec 09 '20 at 02:31

2 Answers2

2

A negative time e.g. -00:59:59 does not make sense. If your API is getting such a time from the client application, either discard it or educate the owner of the client application to correct it. Another option is to parse the string after removing the negative sign.

Note that the date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.

import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(parseStr("00:00:03"));
        System.out.println(parseStr("00:59:59"));
        System.out.println(parseStr("01:00:00"));
    }

    static LocalTime parseStr(String str) {
        return LocalTime.parse(str);
    }
}

Output:

00:00:03
00:59:59
01:00

Learn more about the modern date-time API at Trail: Date Time.

Note: For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7.

If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Given below is how you can parse the string after removing the negative sign:

import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(parseStr("00:00:03"));
        System.out.println(parseStr("00:59:59"));
        System.out.println(parseStr("01:00:00"));
        System.out.println(parseStr("-00:00:03"));
        System.out.println(parseStr("-00:59:59"));
        System.out.println(parseStr("-01:00:00"));
    }

    static LocalTime parseStr(String str) {
        if (str.charAt(0) == '-') {
            str = str.substring(1);
        }
        return LocalTime.parse(str);
    }
}

Output:

00:00:03
00:59:59
01:00
00:00:03
00:59:59
01:00

If you mean Duration (which can be negative) and not time, check Duration#toString to understand how duration is represented.

Using legacy API:

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Main {
    public static void main(String[] args) throws ParseException {
        // Test
        System.out.println(parseStr("00:00:03"));
        System.out.println(parseStr("00:59:59"));
        System.out.println(parseStr("01:00:00"));
        System.out.println(parseStr("-00:00:03"));
        System.out.println(parseStr("-00:59:59"));
        System.out.println(parseStr("-01:00:00"));
    }

    static String parseStr(String str) throws ParseException {
        if (str.charAt(0) == '-') {
            str = str.substring(1);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(sdf.parse(str));
    }
}

Output:

00:00:03
00:59:59
01:00:00
00:00:03
00:59:59
01:00:00

Note that a date-time object does not store formatting information. If you want a date-time object to be printed in a specific format, you can do so using a formatting API (e.g. DateTimeFormatter in the modern API and SimpleDateFormat in the legacy API). The LocalTime#toString returns a string in ISO-8601 format which is HH:mm:ss and therefore, if it is the expected format, you do not need to use DateTimeFormatter.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • It isn't an API, rather user input into a field. Our tester is somewhat evil lol and decided to see what would happen if they entered a negative. So, highly unlikely, but now that he's brought it to light, we have to deal with it. Also, we will be staying at Java7 for the forseeable future. – Tracy Hiltbrand Dec 08 '20 at 18:01
  • @TracyHiltbrand - I've posted an update for Java-7. – Arvind Kumar Avinash Dec 08 '20 at 18:20
  • Thanks! I actually ended up checking for a "-" via the contains method (I **know** my tester lol, if I check only the first character, he's gonna try a string like "--1:00:00". Thanks for the update though! :-) – Tracy Hiltbrand Dec 08 '20 at 21:41
  • I do wish I knew exactly **why** the "-" has that effect... – Tracy Hiltbrand Dec 08 '20 at 21:42
1

java.time.Duration

I am sharing the assumption by Sotirios Delimanolis that your strings represent amounts of time such as durations, for example. Such can in many contexts be positive or negative. If this assumption is correct, use Duration in Java.

One limitation of Duration is that only a string in ISO 8601 format can be directly parsed into a Duration. So I am using a regular expression for converting your string into ISO 8601 format.

private static void parseStr(String aStr)
{
    // Convert string to ISO 8601 format
    String isoStr = aStr.replaceFirst(
            "^([+-]?)(\\d{2}):(\\d{2}):(\\d{2})$", "$1PT$2H$3M$4S");
    // Parse to Duration
    Duration dur = Duration.parse(isoStr);
    // Print
    System.out.format("Duration.parse(%s) = %s%n", aStr, dur);
    
    System.out.println();
}

With this method declaration your main method prints:

Duration.parse(00:00:03) = PT3S

Duration.parse(-00:00:03) = PT-3S

Duration.parse(00:59:59) = PT59M59S

Duration.parse(-00:59:59) = PT-59M-59S

Duration.parse(01:00:00) = PT1H

Duration.parse(-01:00:00) = PT-1H

Duration also prints ISO 8601 format back. The format is unusual to many, but straightforward. Read for example PT3S as a period of time of 3 seconds.

I carried the minus sign with me through the string conversion because it’s perfectly allowed in ISO 8601. So our parsed durations all have the expected sign.

The Duration class is part of java.time, the modern Java date and time API.

Edit: In case you prefer, the two System.out statements can be merged into one:

    System.out.format("Duration.parse(%s) = %s%n%n", aStr, dur);

I simply put a double %n there to make two newlines.

Objection: I can’t use java.time with my Java 7?

Also, we will be staying at Java7 for the forseeable future.

java.time works nicely with Java 7. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
  • In (non-Android) Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On older Android either use desugaring or the Android edition of ThreeTen Backport. It’s called ThreeTenABP. In the latter case make sure you import the date and time classes from org.threeten.bp with subpackages.

Do you have to deal with it?

Our tester is somewhat evil lol and decided to see what would happen if they entered a negative. So, highly unlikely, but now that he's brought it to light, we have to deal with it.

While I don’t know your organization, where I come from it’s fine to challenge the requirements. If a negative amount of time makes good sense and is a useful concept to your users, deal with it. If it doesn’t make good sense, revise the requirements to specify that no sign is allowed. And when your tester (who seems to be doing a good job) types one anyway, issue an error message rather than trying to make sense of the nonsensical.

What happened in your code?

I do wish I knew exactly why the "-" has that effect...

First, SimpleDateFormat (incorrectly, I suppose) takes your string to be a time of day, not a duration. Second, a SimpleDateFormat with standard settings confusingly accepts a negative hour of day. For example:

  • -00:00:03 is taken to mean hour of day = -0, minute of hour = 0 and second of minute = 3. Since -0 is the same as 0, you just get 00:00:03. The date defaults to January 1, 1970.
  • In -01:00:00 the hour of day is –1. –1 is the hour before hour 0, so the same as 23 of the previous evening. The date still defaults to January 1, 1970. So you get 23:00:00 on December 31, 1969.

In all cases any minus is taken to negate the hour of day only where I think that your tester meant to negate the entire duration.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161