Reputation: 11996
I have a function that takes a custom string and converts it into a Date. My goal is to store today's date but with the custom Hours:Minutes supplied by the string.
For some reason, the debugger shows that the AM/PM are switched at the end (but the flow is correct). When I pass in 12:05am
the Date object is stored as the PM value, whereas if I pass in 12:05pm
the Date object is stored as the AM value. It should be the opposite.
Code:
public class DateUtils {
private static final String AM_LOWERCASE = "am";
private static final String AM_UPPERCASE = "AM";
public static Date getDateFromTimeString(String timeStr) {
Calendar calendar = Calendar.getInstance();
if (StringUtils.hasText(timeStr)) {
if (timeStr.indexOf(AM_LOWERCASE) != -1 || timeStr.indexOf(AM_UPPERCASE) != -1) {
calendar.set(Calendar.AM_PM, Calendar.AM);
} else {
calendar.set(Calendar.AM_PM, Calendar.PM);
}
// Set custom Hours:Minutes on today's date, based on timeStr
String[] timeStrParts = timeStr.replaceAll("[a-zA-Z]", "").split(":");
calendar.set(Calendar.HOUR, Integer.valueOf(timeStrParts[0]));
calendar.set(Calendar.MINUTE, Integer.valueOf(timeStrParts[1]));
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
}
return calendar.getTime();
}
}
The debugger shows that:
Input: 12:05am -> Sun Dec 17 12:05:00 EST 2017
Input: 12:05pm -> Mon Dec 18 00:05:00 EST 2017
It should be the opposite of that. If I were to write out these strings back using SimpleDateFormat I would see that Input 1 comes back as 12:05PM and Input 2 comes back as 12:05AM.
Also, for #2 the date shouldn't cycle forward a day. The Dates that should be stored are today's date in both cases, with either 12:05 AM or 12:05 PM.
Am I missing something? Goal:
12:05am -> Sun Dec 17 00:05:00 EST 2017
12:05pm -> Sun Dec 17 12:05:00 EST 2017
Upvotes: 2
Views: 1666
Reputation: 79175
The java.util
Date-Time API 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*.
String
operationsPerforming String
operations (e.g. regex match, to-upper-case, to-lower-case etc.) can be error-prone and incomplete. Instead use DateTimeFormatterBuilder
which allows a rich set of options (e.g. case-insensitive parsing, specifying optional patterns, parsing with default values etc.).
Demo:
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Locale;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = new DateTimeFormatterBuilder()
.parseCaseInsensitive() //To parse in case-insensitive way e.g. AM, am
.appendPattern("h:m[ ]a") // Notice single h and m and optional space in square bracket
.toFormatter(Locale.ENGLISH);
// Test
Stream.of(
"08:20 am",
"08:20 pm",
"08:20 AM",
"08:20 PM",
"8:20 am",
"08:5 pm",
"08:5pm",
"8:5am"
).forEach(s -> System.out.println(LocalTime.parse(s, dtf)));
}
}
Output:
08:20
20:20
08:20
20:20
08:20
20:05
20:05
08:05
Notice the single h
and m
which cater for both one-digit as well as two-digit hour and minute. Another thing to notice is the optional space specified using a square bracket. You can specify many optional patterns in a single DateTimeFormatter
. Check this answer demonstrating some more powerful features of DateTimeFormatter
.
Use ZonedDateTime#of(LocalDate, LocalTime, ZoneId)
to create one.
String strTime = "08:20 am";
ZoneId tz = ZoneId.of("America/Los_Angeles");
System.out.println(ZonedDateTime.of(LocalDate.now(tz), LocalTime.parse(strTime, dtf), tz));
Learn more about java.time
, the modern Date-Time API* from Trail: Date Time.
* 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.
Upvotes: 1
Reputation: 339043
ZonedDateTime.of(
LocalDate.now( ZoneId.of( "Africa/Casablanca" ) ) ,
LocalTime.parse( "12:05am".toUppercase() , DateTimeFormatter.ofPattern( "hh:mma" , Locale.US ) ) ,
ZoneId.of( "Africa/Casablanca" )
)
You are using troublesome old date-time classes that are now legacy, supplanted by java.time classes.
java.time.LocalTime
Your input strings use lowercase "am"/"pm" which are incorrect. Those letters are actually initial-letter abbreviations and so should be uppercase. We must force the uppercase to facilitate parsing.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "hh:mma" , Locale.US ) ;
LocalTime lt = LocalTime.parse( "12:05am".toUppercase() , f ) ;
You are inappropriately trying to represent a time-of-day with a date-time class. Instead use LocalTime
class as it is meant for a time-of-day with no date and no zone/offset.
Getting the current date requires a time zone.
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
LocalDate ld = LocalDate.now( z );
Combine to get a ZonedDateTime
.
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
If that time-of-day on that date in that zone is not valid because of anomalies such as Daylight Saving Time, java.time automatically adjusts. Read the doc to be sure you understand and agree with the adjustment algorithm.
Upvotes: 2
Reputation: 86774
The problem is that the values for Calendar.HOUR
range from 0
to 11
, not 1
to 12
. When you set the hour to 12
the calendar normalizes this to the opposite half of the day... i.e. you "overflowed" to the next day half.
public static void main(String[] args)
{
Calendar c1 = Calendar.getInstance();
System.out.printf("Initial: %s\n",c1.getTime().toString());
c1.set(Calendar.AM_PM, Calendar.AM);
System.out.printf("Set(AM): %s\n",c1.getTime().toString());
c1.set(Calendar.HOUR, 12);
System.out.printf("Set(12): %s\n\n",c1.getTime().toString());
Calendar c2 = Calendar.getInstance();
System.out.printf("Initial: %s\n",c2.getTime().toString());
c2.set(Calendar.AM_PM, Calendar.PM);
System.out.printf("Set(PM): %s\n",c2.getTime().toString());
c2.set(Calendar.HOUR, 12);
System.out.printf("Set(12): %s\n\n",c2.getTime().toString());
}
Output
Initial: Sun Dec 17 17:53:52 PST 2017
Set(AM): Sun Dec 17 05:53:52 PST 2017
Set(12): Sun Dec 17 12:53:52 PST 2017
Initial: Sun Dec 17 17:53:52 PST 2017
Set(PM): Sun Dec 17 17:53:52 PST 2017
Set(12): Mon Dec 18 00:53:52 PST 2017
All this aside, you should be using the new Time classes that have been part of Java since Java 8. They supersede the legacy classes (i.e. Calendar, Date, etc) that have been known to be sub-optimal for many years.
As suggested by @Andreas, here's how to parse it the modern way:
LocalTime.parse(
"12:05am",
new DateTimeFormatterBuilder().
parseCaseInsensitive().
appendPattern("hh:mma").
toFormatter(Locale.US));
Upvotes: 5