This code:
DateTimeParser[] parsers = { DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss zzz").getParser(),
DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss").getParser(), DateTimeFormat.forPattern("dd/MM/yyyy HH:mm").getParser(),
DateTimeFormat.forPattern("HH:mm").getParser() };
DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(null, parsers).toFormatter();
Session session;
DateTime dTime = null;
Calendar calendar;
try{
if (completedTime != null && !completedTime.equalsIgnoreCase("")){
LocalDateTime jt = LocalDateTime.parse(completedTime, formatter);
LocalDateTime dt;
LocalDateTime retDate;
produces the error:
java.lang.IllegalArgumentException: Invalid format: "09/05/2015 04:00:00 GDT" is malformed at " GDT"
at the LocalDateTime jt = LocalDateTime.parse(completedTime, formatter); line
I can't for the life of me work out why it is failing. I am pretty sure it is something simple, but I haven't spotted it.
You may want to refer to this thread (or one of the many others like it). My best advice would be to try cutting to only one "z" in your parser.
You need to manually specify a mapping from timezone abbreviation to timezone. For example:
return new DateTimeFormatterBuilder()
.appendPattern("dd/MM/yyyy HH:mm:ss ")
.appendTimeZoneShortName(UK_TIMEZONE_SYMBOLS)
.toFormatter();
Here UK_TIMEZONE_SYMBOLS is a Map<String,DateTimeZone> which contains our view of timezone names (so BST is British summer time, not Bangladesh standard time)
Here's how we build ours:
public static Map<String, String> buildTimeZoneSymbolMap(Locale locale) {
Map<String, String> timeZoneSymbols = Maps.newLinkedHashMap();
for (String[] zoneInfo : DateFormatSymbols.getInstance(locale).getZoneStrings()) {
String timeZone = zoneInfo[0];
if (!timeZoneSymbols.containsKey(zoneInfo[2])) {
timeZoneSymbols.put(zoneInfo[2], timeZone);
}
if (zoneInfo[4] != null && !timeZoneSymbols.containsKey(zoneInfo[4])) {
timeZoneSymbols.put(zoneInfo[4], timeZone);
}
}
timeZoneSymbols.put("UTC", "GMT");
return timeZoneSymbols;
}
public static Map<String, DateTimeZone> buildDateTimeZoneSymbolMap(Locale locale) {
return Maps.transformValues(buildTimeZoneSymbolMap(locale), input -> DateTimeZone.forTimeZone(TimeZone.getTimeZone(input)));
}
public static final Map<String, DateTimeZone> UK_TIMEZONE_SYMBOLS = ImmutableMap.copyOf(buildDateTimeZoneSymbolMap(Locale.UK));
First thing to note:
What is "GDT"? The website http://www.timeanddate.com/time/zones/ does not yield an answer. So if it really exists and is not a typo then what is your locale? Remember that time zone names and abbreviations are highly localized.
Second: The count of pattern symbols "z" is okay - for classes like SimpleDateFormat etc. - see its documentation. Either four letters for the full name or less than four letters for the abbreviation:
General time zone: Time zones are interpreted as text if they have
names. Text: For formatting, if the number of pattern letters is 4 or
more, the full form is used; otherwise a short or abbreviated form is
used if available. For parsing, both forms are accepted, independent
of the number of pattern letters.
But you use Joda-Time. Its documentation clearly states:
Zone names: Time zone names ('z') cannot be parsed.
I have verified this non-support using the newest Joda-Time version 2.7 by following code:
DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss z").withLocale(Locale.GERMANY);
DateTime dt = formatter.parseDateTime("09/05/2015 04:00:00 MESZ");
System.out.println("Joda-Time: " + dt);
// Exception in thread "main" java.lang.IllegalArgumentException: Invalid format: "09/05/2015 04:00:00 MESZ" is malformed at "MESZ"
Of course, "MESZ" is correct and must be interpreted as Europe/Berlin in context of given german locale.
However, since version update (2.2) the same code set to Locale.US works for some timezones names like "EDT", "PST" etc., see also this commit. So we can finally say, the parsing support of Joda-Time for timezone names and abbreviations is best to say very limited. Once again, what is your Locale? If it is not US then I can understand why you get the exception. And you will also get an exception for the input "GDT" even if we consider it as valid due to the limited capabilities of Joda-Time-parser.
Related
I am currently getting two version of timestamp format eg '2017-04-17 20:33:45.223+05:30' and '2017-04-17 20:33:45+05:30'.My parsing is failing due to dynamic timestamp .Is it possible to handle both of these time stamp with one DateTimeFormatter Pattern .Below is the example code what i tried
val myDate=LocalDateTime.parse("2017-04-17 20:33:45.223+05:30", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSZ")).toDateTime(DateTimeZone.UTC)//this will fail if time stamp comes with '2017-04-17 20:33:45+05:30
I had seen one way to achieve the same using optional part however I canot make it work
val pattern = "MM/dd/yyyy HH:mm:ss[.SSS]Z"
val fmt = DateTimeFormatter.ofPattern(pattern)
val temporalAccessor = fmt.parse("2017-04-17 20:33:45.223+05:30")
Ant help on this or any suggestion how to handle such cases will be helpful .Thanks in advance .
uuuu-MM-dd
Edit: This fixes it. I am using java.time, the modern Java date and time API, and Java syntax.
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss[.SSS]xxx", Locale.ROOT);
Trying it out:
String[] variants = {
"2017-04-17 20:33:45.223+05:30",
"2017-04-17 20:33:45+05:30",
// Variants we don’t want to accept
"2017-04-17 20:00+05:30",
"2017-04-17 20:00:00.000000+05:30" };
for (String inputString : variants) {
try {
OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
System.out.println("Parsed: " + dateTime);
} catch (DateTimeParseException dtpe) {
System.out.println("Invalid: " + inputString);
}
}
Output:
Parsed: 2017-04-17T20:33:45.223+05:30
Parsed: 2017-04-17T20:33:45+05:30
Invalid: 2017-04-17 20:00+05:30
Invalid: 2017-04-17 20:00:00.000000+05:30
What went wrong in your code?
You had the right idea for your purpose.
You attempted using the outmoded Joda-Time library. Joda-Time can support optional parts when parsing, but not through the square bracket syntax. Instead its DateTimeFormatterBuilder has got an appendOptional method.
In your java.time code this part of your format pattern string doesn’t match any of your inputs: MM/dd/yyyy. Java parsed 20 as a 2 digit month number (postponing validation of the number) and threw the exception because no slash was found after 20.
Edit 2: why xxx works but Z doesn't:
With Joda-Time’s DateTimeFormat one Z is for offset without colon, for example +0530. ZZ should have worked for +05:30 with colon.
With java.time both x and Z (and also upper case X) are for zone offset. Here too Z is for offset without colon. Either xxx or ZZZZZ works for +05:30.
Use the built-in formatters
Original answer, likely useful for others: This one does it (using Java syntax):
private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendOffsetId()
.toFormatter();
Let’s try it out:
String[] variants = {
"2017-04-17 20:33:45.223+05:30",
"2017-04-17 20:33:45+05:30",
"2017-04-17 20:00+05:30",
"2017-04-17 20:00:00.000000+05:30" };
for (String inputString : variants) {
OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
System.out.println(dateTime);
}
Output:
2017-04-17T20:33:45.223+05:30
2017-04-17T20:33:45+05:30
2017-04-17T20:00+05:30
2017-04-17T20:00+05:30
I am exploiting the fact that the built-in DateTimeFormatter.ISO_LOCAL_TIME accepts a time both with and without decimals on the seconds. We can reuse existing formatters in our own formatter through a DateTimeFormatterBuilder.
parseBest looks a good fit for this
public TemporalAccessor parseBest(CharSequence text,
TemporalQuery<?>... queries)
Fully parses the text producing an object of one of the specified
types.
This parse method is convenient for use when the parser can handle optional elements. For example, a pattern of 'uuuu-MM-dd HH.mm[ VV]'
can be fully parsed to a ZonedDateTime, or partially parsed to a
LocalDateTime. The queries must be specified in order, starting from
the best matching full-parse option and ending with the worst matching
minimal parse option. The query is typically a method reference to a
from(TemporalAccessor) method.
The result is associated with the first type that successfully parses
I have a small block of code which parses response generation time from the response itself and turns it into a date for future purposes. It goes like this:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
Date responseTime = sdf.parse(RStime);
And it almost works like a charm. To be precise, it works 99.9% of the time, with the exception of one case: When the millisecond part is 000 then the Server doesn't append the .000 milliseconds at all, hence we have a problem.
Now, according to SimpleDateFormat docs if parsing fails, the function returns null. However, I probably misinterpreted it as it just throws an exception.
I am very new to Java and try-catch mechanisms, so could anyone please provide an elegant good-practice solution for handling such cases?
Thanks!
java.time
String rsTime = "2018-04-09T10:47:16.999-02:00";
OffsetDateTime responseTime = OffsetDateTime.parse(rsTime);
System.out.println("Parsed date and time: " + responseTime);
Output from this snippet is:
Parsed date and time: 2018-04-09T10:47:16.999-02:00
It works just as well for the version with the 000 milliseconds omitted:
String rsTime = "2018-04-09T10:47:16-02:00";
Parsed date and time: 2018-04-09T10:47:16-02:00
The classes you used, SimpleDateFormat and Date, are poorly designed and long outdated (the former in particular notoriously troublesome). So it is not only in this particular case I recommend using java.time, the modern Java date and time API, instead. However, the strings from your server are in ISO 8601 format, and OffsetDateTime and the other classes of java.time parse this format as their default, that is, without any explicit formatter, which already makes the task remarkably easier. Furthermore, in the standard the fractional seconds are optional, which is why both the variants of the string are parsed without any problems. OffsetDateTime also prints ISO 8601 back from it’s toString method, which is why in both cases a string identical to the parsed one is printed.
Only in case you indispensably need an old-fashioned Date object for a legacy API that you cannot change just now, convert like this:
Instant responseInstant = responseTime.toInstant();
Date oldfashionedDateObject = Date.from(responseInstant);
System.out.println("Converted to old-fashioned Date: " + oldfashionedDateObject);
Output on my computer in Europe/Copenhagen time zone is:
Converted to old-fashioned Date: Mon Apr 09 14:47:16 CEST 2018
Link: Oracle tutorial: Date Time explaining how to use java.time.
According to the SimpleDateFormat doc that you mentioned the parse method:
public Date parse(String text, ParsePosition pos)
Throws:
NullPointerException - if text or pos is null.
So one option is to catch that exception and do what you need inside the catch, for example:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
try {
Date responseTime = sdf.parse(RStime, position);
} catch (NullPointerException e) {
e.printStackTrace();
//... Do extra stuff if needed
}
Or the inherited method from DateFormat:
public Date parse(String source)
Throws:
ParseException - if the beginning of the specified string cannot be
parsed.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
try {
Date responseTime = sdf.parse(RStime);
} catch (ParseException e) {
e.printStackTrace();
//... Do extra stuff if needed
}
Is it actually an exceptional situation? If it is not then you probably shouldn't use exceptions in that case. In my opinion it is normal that time can end with .000ms. In this case you can check if the string contains . (dot) and if not append .000 to the end.
if(!RStime.contains(".")){
RStime+=".000";
}
Edit: I've forgot about time zone in the time String. You probably need something a little bit more complicated for that. Something like this should do it:
if(!RStime.contains(".")){
String firstPart = RStime.substring(0, 21);
String secondPart = RStime.substring(21);
RStime = firstPart + ".000" + secondPart;
}
You can check for a dot and then use the first or second format:
String timeString = "2018-04-09T10:47:16.999-02:00";
//String timeString = "2018-04-09T10:47:16-02:00";
String format = timeString.contains(".") ? "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" : "yyyy-MM-dd'T'HH:mm:ssXXX";
Date responseTime = new SimpleDateFormat(format).parse(timeString);
System.out.println("responseTime: " + responseTime);
If you comment-out the first line and comment-in the second and run it again, it will both print out:
responseTime: Mon Apr 09 14:47:16 CEST 2018
By the way:
Java 7 (the version you use obviously) returns a java.text.ParseException: Unparseable date: "2018-04-09T10:47:16-02:00"
Optionals are supported since Java 8.
I am working on a REST API which supports Date as a query param. Since it is Query param it will be String. Now the Date can be sent in the following formats in the QueryParams:
yyyy-mm-dd[(T| )HH:MM:SS[.fff]][(+|-)NNNN]
It means following are valid dates:
2017-05-05 00:00:00.000+0000
2017-05-05 00:00:00.000
2017-05-05T00:00:00
2017-05-05+0000
2017-05-05
Now to parse all these different date-times i am using Java8 datetime api. The code is as shown below:
DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd[[ ][['T'][ ]HH:mm:ss[.SSS]][Z]"))
.toFormatter();
LocalDateTime localDateTime = null;
LocalDate localDate = null;
ZoneId zoneId = ZoneId.of(ZoneOffset.UTC.getId());
Date date = null;
try {
localDateTime = LocalDateTime.parse(datetime, formatter);
date = Date.from(localDateTime.atZone(zoneId).toInstant());
} catch (Exception exception) {
System.out.println("Inside Excpetion");
localDate = LocalDate.parse(datetime, formatter);
date = Date.from(localDate.atStartOfDay(zoneId).toInstant());
}
As can be seens from the code I am using DateTimeFormatter and appending a pattern. Now I am first trying to parse date as LocalDateTime in the try-block and if it throws an exception for cases like 2017-05-05 as no time is passed, I am using a LocalDate in the catch block.
The above approach is giving me the solution I am looking for but my questions are that is this the standard way to deal with date sent as String and is my approach is in line with those standards?
Also, If possible what is the other way I can parse the different kinds of date (shown as the Valid dates above) except some other straightforward solutions like using an Array list and putting all the possible formats and then using for-loop trying to parse the date?
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
// time is optional
.optionalStart()
.parseCaseInsensitive()
.appendPattern("[ ]['T']")
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalEnd()
// offset is optional
.appendPattern("[xx]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter();
for (String queryParam : new String[] {
"2017-05-05 00:00:00.000+0000",
"2017-05-05 00:00:00.000",
"2017-05-05T00:00:00",
"2017-05-05+0000",
"2017-05-05",
"2017-05-05T11:20:30.643+0000",
"2017-05-05 16:25:09.897+0000",
"2017-05-05 22:13:55.996",
"2017-05-05t02:24:01"
}) {
Instant inst = OffsetDateTime.parse(queryParam, formatter).toInstant();
System.out.println(inst);
}
The output from this snippet is:
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T11:20:30.643Z
2017-05-05T16:25:09.897Z
2017-05-05T22:13:55.996Z
2017-05-05T02:24:01Z
The tricks I am using include:
Optional parts may be included in either optionalStart/optionalEnd or in [] in a pattern. I use both, each where I find it easier to read, and you may prefer differently.
There are already predefined formatters for date and time of day, so I reuse those. In particular I take advantage of the fact that DateTimeFormatter.ISO_LOCAL_TIME already handles optional seconds and fraction of second.
For parsing into an OffsetDateTime to work we need to supply default values for the parts that may be missing in the query parameter. parseDefaulting does this.
In your code you are converting to a Date. The java.util.Date class is long outdated and has a number of design problems, so avoid it if you can. Instant will do fine. If you do need a Date for a legacy API that you cannot change or don’t want to change just now, convert in the same way as you do in the question.
EDIT: Now defaulting HOUR_OF_DAY, not MILLI_OF_DAY. The latter caused a conflict when only the millis were missing, but it seems the formatter is happy with just default hour of day when the time is missing.
I usually use the DateUtils.parseDate which belongs to commons-lang.
This method looks like this:
public static Date parseDate(String str,
String... parsePatterns)
throws ParseException
Here is the description:
Parses a string representing a date by trying a variety of different parsers.
The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match, a ParseException is thrown.
The parser will be lenient toward the parsed date.
#Configuration
public class DateTimeConfig extends WebMvcConfigurationSupport {
/**
* https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#format-configuring-formatting-globaldatetimeformat
* #return
*/
#Bean
#Override
public FormattingConversionService mvcConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));
dateTimeRegistrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd"));
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd'T'HH:mm:ss'Z'"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
}
I know there are similar questions like this, but I wasn't quite able to find the example similar to mine. I learned about LocalDateTime and ZonedDateTime but I don't know how to tell my ZonedDateTime what's assumed timezone of parsed date.
I'm migrating from Java 7 to Java 8. In my code, I have a method like this:
public String changeTZ(String tzFrom, String tzTo, String dateToChange) {
ZoneId zoneFrom = ZoneId.of(tzFrom);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat.toPattern());
LocalDateTime localtDateAndTime = LocalDateTime.parse(dateToChange, formatter);
ZonedDateTime dateAndTimeINeed = ZonedDateTime.of(localtDateAndTime, zoneFrom );
ZonedDateTime rezDate = dateAndTimeINeed.withZoneSameInstant(ZoneId.of(tzTo));
return formatter.format(rezDate);
}
Example usage is:
String rez = changeTZ("CEST", "UTC", "2017-08-10 14:23:58");
As you can see it receives datetime in form of string, timezone date is in tzFrom variable and TZ I need (toTo variable).
Code which does that in Java 7 is absurd and complex so I won't enlist it here. Can you please tell me how to achieve the same in Java 8 without changing method interface (arguments and return type)? What about DST? Is it handled automatically in Java 8?
Note that method also supports timezones in form of "CEST", "CET", "UTC" as well as standard ones like "Europe/London".
Java 8 uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the short abbreviations (like CEST or PST) because they are ambiguous and not standard.
Actually, those names don't work with ZoneId mainly because of this ambiguity (CST, for example, can be "Central Standard Time", "Cuba Standard Time" or "China Standard Time"). Actually, some of them might work due to retro-compatibility reasons, but it's not guaranteed to work with all of them.
I'm assuming that CEST is the Central European Summer Time. There are lots of different countries (and timezones) that are currently in CEST, so the API can't decide which timezone to choose if you just pass "CEST" to it.
That's because a timezone contains all the different offsets a region had during its history. There may be lots of countries using CEST today, but their history differs in the past (some might had DST in different years, or used a different offset and then changed, etc), and that's why they have one timezone for each.
To use such short names (like CEST), though, you can define some defaults for each one (which will be an arbitrary choice) and put these choices in a map:
// map of custom zone names
Map<String, String> map = new HashMap<>();
// setting my arbitrary choices for each name
map.put("CEST", "Europe/Berlin"); // Berlin during DST period
map.put("CET", "Europe/Berlin"); // Berlin during non-DST period
// ... and so on
Then you can use this map to create the ZoneId:
// use the custom map to create the ZoneId
ZoneId zoneFrom = ZoneId.of(tzFrom, map);
...
// use the custom map to create the ZoneId
ZonedDateTime rezDate = dateAndTimeINeed.withZoneSameInstant(ZoneId.of(tzTo, map));
I've chosen Europe/Berlin, but of course you can change it to whatever timezone you need. You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
Using the map above:
System.out.println(changeTZ("CEST", "UTC", "2017-08-10 14:23:58"));
This code outputs:
2017-08-10 12:23:58
Note that 14:23 in CEST (which I chose to be Europe/Berlin) is 12:23 in UTC, which is correct because in August Berlin is in DST (offset is +02:00).
ZoneId and ZonedDateTime classes handle DST effects automatically. You can check this by choosing a date in January (when DST is not in effect in Berlin):
// January is not DST, so use CET
System.out.println(changeTZ("CET", "UTC", "2017-01-10 14:23:58"));
The output is:
2017-01-10 13:23:58
In January Berlin is not in DST, so the offset is +01:00, then 14:23 in Berlin becomes 13:23 in UTC.
Of course the ideal is to always use the full names (like Europe/Berlin), but the custom map is an alternative if you don't have control over the inputs.
Java 8 also has a built-in predefined map, but as any other predefined stuff, the choices are arbitrary and not necessarily the ones you need.
This solution uses the IANA timezones names mentioned by Hugo in the comments to get the ZoneId (more details here). Will throw an exception if you use it with CEST.
public static String changeTZ(String tzFrom, String tzTo, String dateToChange){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of(tzFrom));
ZonedDateTime zdt = ZonedDateTime.parse(dateToChange, dtf);
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of(tzTo));
return zdt.format(dtf2);
}
Use like:
String rez = changeTZ("US/Alaska", "Europe/Berlin", "2017-08-10 14:23:58");
Here is a class that does the job.
public class TimeZoneConverter {
private static String datePattern = "yyyy-MM-dd HH:mm:ss";
public static void main(String[] args) throws ParseException {
String sourceDate = "2017-02-27 16:00:00";
String resultDate = convertTimeZone("EET", "UTC", sourceDate);
System.out.println("EET: "+ sourceDate);
System.out.println("UTC: "+ resultDate);
}
public static String convertTimeZone(String timeZoneFrom, String timeZoneTo, String date) throws ParseException {
long timestamp = stringToTimestamp(date, timeZoneFrom);
String result = timestampToString(timestamp, timeZoneTo);
return result;
}
public static long stringToTimestamp(String time, String timeZone) throws ParseException {
DateFormat format = getDateFormat(timeZone);
return format.parse(time).getTime();
}
public static String timestampToString(long timestamp, String timeZone) {
DateFormat format = getDateFormat(timeZone);
return format.format(new Date(timestamp));
}
private static DateFormat getDateFormat(String timeZone) {
DateFormat format = new SimpleDateFormat(datePattern);
format.setTimeZone(TimeZone.getTimeZone(timeZone));
return format;
}
}
I am getting a parse exception when trying to parse the time string 02:22 p.m..
I have the following conversion function:
public static long convertdatetotimestamp(String datestring, String newdateformat, String olddateformat){
SimpleDateFormat originalFormat = new SimpleDateFormat(olddateformat,Locale.ROOT);
SimpleDateFormat targetFormat = new SimpleDateFormat(newdateformat,Locale.ROOT);
Date date = null;
try {
date = originalFormat.parse(datestring);
String formattedDate = targetFormat.format(date);
Date parsedDate = targetFormat.parse(formattedDate);
long nowMilliseconds = parsedDate.getTime();
return nowMilliseconds;
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
The method is called in another activity with a time format "02:22 p.m.". olddateformat and newdateformat are the same: hh:mm a.
It causes following error in log:
java.text.ParseException: Unparseable date: "02:22 p.m." (at offset 6)
How to resolve this issue? Time is in exactly above mentioned format.
It so happens that a.m. and p.m. are called just this in Gaelic locale. At least on my Java 8. I am far from sure that it will be the case on (all) Android phones, but you may do some experiments with it.
String datestring = "02:22 p.m.";
Locale parseLocale = Locale.forLanguageTag("ga");
DateTimeFormatter originalFormat = DateTimeFormatter.ofPattern("hh:mm a", parseLocale);
System.out.println(LocalTime.parse(datestring, originalFormat));
This prints
14:22
As Hugo so warmly and rightly recommends is his answer, I am using the modern Java date and time API, so you will need ThreeTenABP for the above code. See How to use ThreeTenABP in Android Project. Alternatively you may want to try the same locale with your otherwise outdated SimpleDateFormat.
US Spanish locale shows the same behaviour on my Java 8, so you may try that too: Locale.forLanguageTag("es-US").
I believe that SimpleDateFormat can't be customized to parse the p.m. part (it only recognizes AM or PM).
So one alternative is to remove the dots:
String time = "02:22 p.m.";
SimpleDateFormat format = new SimpleDateFormat("hh:mm a", Locale.ROOT);
date = format.parse(time.replaceAll("\\.", ""));
One detail: to get the nowMilliseconds value, you need all the date fields (day/month/year) and a timezone. As those fields are not in the input String, SimpleDateFormat sets them to January 1st of 1970 (and also set the seconds and milliseconds to zero), and use the system's default timezone.
I'm not sure if this behaviour of getting January 1970 is consistent among all Java versions, which is another problem because you can get different values depending on the environment/device the code is running. Actually, you might have a different result anyway because it uses the system's default timezone and this can vary among different environments.
If I run this code in my machine, it uses my system's default timezone (America/Sao_Paulo), and the result is 62520000. But if I change the timezone to another (let's say, Asia/Kolkata), the result is 31920000. You must be aware of this variation and check if that's what you really need.
Another detail is that, if olddateformat and newdateformat are the same, there's no need to create 2 different formatters.
Java's new Date/Time API
The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.
In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. You'll also need the ThreeTenABP (more on how to use it here).
All the relevant classes are in the org.threeten.bp package.
With this new API, you can customize the text that corresponds to AM/PM using a org.threeten.bp.format.DateTimeFormatterBuilder (so no need to remove the dots manually). And there are specific classes to each case - in this case, the input has only the time fields (hour and minutes), so I'm going to use the org.threeten.bp.LocalTime class (which represents only a time - hour/minute/second/nanosecond - without a date):
String time = "02:22 p.m.";
// map AM and PM values to strings "a.m." and "p.m."
Map<Long, String> map = new HashMap<Long, String>();
map.put(0L, "a.m.");
map.put(1L, "p.m.");
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// hour and minute
.appendPattern("hh:mm ")
// use custom values for AM/PM
.appendText(ChronoField.AMPM_OF_DAY, map)
// create formatter
.toFormatter(Locale.ROOT);
// parse the time
LocalTime parsedTime = LocalTime.parse(time, fmt);
The parsedTime variable will contain the values corresponding to 02:22 PM (and only this value, it has no date fields (day/month/year) nor a timezone).
To get the milliseconds value (number of milliseconds since 1970-01-01T00:00Z), you also need a date (day/month/year) and a timezone. As I said previously, those fields can affect the final value.
In the old API, SimpleDateFormat tries to be "smart" and sets default values for those fields (January 1st of 1970 in the system's default timezone), but the new API is more strict about that and you must tell explicity what date and timezone you want.
In this example, I'm using the Asia/Kolkata timezone but you can change it according to your needs (more on that below):
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
// timezone for Asia/Kolkata
ZoneId zone = ZoneId.of("Asia/Kolkata");
// current date in Kolkata timezone
LocalDate now = LocalDate.now(zone);
// get the parsed time at the specified date, at the specified zone
ZonedDateTime zdt = parsedTime.atDate(now).atZone(zone);
// get the millis value
long millis = zdt.toInstant().toEpochMilli();
If you want a specific date instead of the current date, you can use LocalDate.of(2017, 5, 20) - this will get May 20th, 2017, for example. With this, you can set the code above to the date and timezone you need.
Note that the API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Asia/Kolkata).
Avoid using the 3-letter abbreviations (like IST or PST) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
If you want to emulate exactly what SimpleDateFormat does, you can use LocalDate.of(1970, 1, 1) and use the default timezone with ZoneId.systemDefault() - but this is not recommended, because the system's default can be changed without notice, even at runtime. It's better to explicit what timezone you're using.
Or you can create a formatter that always sets default values for the date (using the org.threeten.bp.temporal.ChronoField class) and always uses the same timezone. So you can parse it directly to a org.threeten.bp.Instant and get the millis value:
String time = "02:22 p.m.";
ZoneId zone = ZoneId.of("Asia/Kolkata");
DateTimeFormatter fmt2 = new DateTimeFormatterBuilder()
// hour and minute
.appendPattern("hh:mm ")
// use custom values for AM/PM (use the same map from previous example)
.appendText(ChronoField.AMPM_OF_DAY, map)
// default value for day: 1
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
// default value for month: January
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
// default value for year: 1970
.parseDefaulting(ChronoField.YEAR, 1970)
// create formatter at my specific timezone
.toFormatter(Locale.ROOT).withZone(zone);
// get the millis value
long millis = Instant.from(fmt2.parse(time)).toEpochMilli();
Following changes that i've made works fine for me.
public static long convertdatetotimestamp(String datestring, String newdateformat, String olddateformat){
DateFormat originalFormat = new SimpleDateFormat(olddateformat,Locale.ENGLISH);
DateFormat targetFormat = new
SimpleDateFormat(newdateformat,Locale.ENGLISH);
Date date = null;
try {
date = originalFormat.parse(datestring.replaceAll("\\.", ""));
String formattedDate = targetFormat.format(date);
Date parsedDate = targetFormat.parse(formattedDate);
long nowMilliseconds = parsedDate.getTime();
return nowMilliseconds;
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
Locale.ENGLISH you can use your locale, english solved my issue. Reference.
Thanks for responses and references.