I have a string date format 01/01/2017 6:54 PM and want to convert it to 2017-01-01T00:00:05.383+0100 ISOFormat in scala - scala

def cleantz( time : String ) : String = {
var sign_builder= new StringBuilder ++= time
println(sign_builder)
var clean_sign = ""
if (sign_builder.charAt(23).toString == "-"){
clean_sign= sign_builder.replace(23,24,"-").toString()
}else{
clean_sign = sign_builder.replace(23,24,"+").toString()
}
var time_builder= new StringBuilder ++= clean_sign
if (time_builder.charAt(26).toString == ":"){
val cleanz = time_builder.deleteCharAt(26)
cleanz.toString()
}else{
time_builder.toString()
}
}
val start = ISO8601Format.parse(cleantz(01/01/2017 6:54 PM))
I get this error:
java.lang.StringIndexOutOfBoundsException: String index out of range: 23

java.time
For the sake of completeness I should like to contribute the modern answer. It’s quite simple and straightforward.
I am sorry that I can neither write Scala code nor test it on my computer. I have to trust you to translate from Java.
private static DateTimeFormatter inputFormatter
= DateTimeFormatter.ofPattern("MM/dd/yyyy h:mm a", Locale.US);
public static String cleantz(String time) {
return LocalDateTime.parse(time, inputFormatter)
.atOffset(ZoneOffset.ofHours(1))
.toString();
}
Now cleantz("01/01/2017 6:54 PM") returns 2017-01-01T18:54+01:00, which is in ISO 8601 format. I would immediately suppose that you’re set. If for some reason you want or need the seconds too, replace .toString(); with:
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
Now the result is 2017-01-01T18:54:00+01:00. In both cases the milliseconds would have been printed if there were any.
Since AM and PM are hardly used in other languages than English, I suggest you give an English-speaking locale to DateTimeFormatter.ofPattern() (in my example I used Locale.US). Failing to provide a locale will cause the code to fail on many computers with non-English language settings.
Why java.time?
SimpleDateFormat and friends are long outdated and notoriously troublesome. I cannot count the questions asked on Stack Overflow because SimpleDateFormat behaved differently from what every sane programmer would have expected, or offered no help to debug the simple errors we all make from time to time.
Joda-Time was good for a long time. Today the Joda-Time homepage says:
Note that Joda-Time is considered to be a largely “finished” project.
No major enhancements are planned. If using Java SE 8, please migrate
to java.time (JSR-310).
java.time is the modern Java date & time API built using the experience from Joda-Time and under the same lead developer, Stephen Colebourne. It is built into Java 8 and later, and a backport exists for Java 6 and 7, so you can use the same classes there too.

Assuming that your input string is 01/01/2017 6:54 PM: it has 18 characters. When you call charAt(23), it tries to get the character at position 23, which doesn't exist: the string has positions from zero (the first 0) to 17 (the M). If you try to get a position greater than that, it throws a StringIndexOutOfBoundsException.
But you don't need to do all this string manipulation. If you have a string that represents a date in some format, and want to convert it to another format, all you need is:
parse the original string to a date
format this date to another format
So you need 2 different Joda formatter's (one for each step). But there's one additional detail.
The input has a date (01/01/2017) and a time (6:54 PM), and the output has a date (2017-01-01), a time (18:54:00.000) and the UTC offset (+0100). So you'll have an additional step:
parse the original string to a date
add the +0100 offset to the parsed date
format this date to another format
With Joda-Time, this can be achieved with the following code:
import org.joda.time.DateTimeZone
import org.joda.time.LocalDateTime
import org.joda.time.format.DateTimeFormat
import org.joda.time.format.ISODateTimeFormat
val fmt = DateTimeFormat.forPattern("dd/MM/yyyy h:mm a")
// parse the date
val localDate = LocalDateTime.parse("01/01/2017 6:54 PM", fmt)
// add the +01:00 offset
val dt = localDate.toDateTime(DateTimeZone.forOffsetHours(1))
// format to ISO8601
print(ISODateTimeFormat.dateTime().print(dt))
The output will be:
2017-01-01T18:54:00.000+01:00
Note that the offset is printed as +01:00. If you want exactly +0100 (without the :), you'll need to create another formatter:
val formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
println(formatter.print(dt))
The output will be:
2017-01-01T18:54:00.000+0100

This is the code I used to achieve the same result. The error occurred because I was trying to parse the wrong date format.
val inputForm = new SimpleDateFormat("MM/dd/yyyy h:mm a")
val outputForm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
val dateFormat1 = start_iso
val dateFormat2 = stop_iso
val start = outputForm.format(inputForm.parse(start_iso))
val stop = outputForm.format(inputForm.parse(stop_iso))
println(start)
println(stop)

Related

Date Format For Google Classroom API ScheduledTime

I'm trying to set the scheduled time when creating an assignment using the Google Classroom API. However, I'm confused about which date format is needed. By the error messages, it seems to accept a string which holds a timestamp and a timezone or Z at the end. Among others, I've tried using System.currentTimeMillis() + "Z", as well as googleDate.getValue() + "Z", googleDate.getValue() since Google Date format seems to be the way to go based on this doc but none of them seem to work.
Any ideas perhaps?
Thank you.
String timezone = timestamp + offset + "";
System.currentTimeMillis()
com.google.api.client.util.DateTime googleDate =
new com.google.api.client.util.DateTime(new java.util.Date());
// Date javaDate = new Date(googleDate.getValue());
CourseWork courseWork = new CourseWork()
.setCourseId(course.getId())
.setTitle("title PUBLISHED 2")
.setDescription("desc")
.setScheduledTime(googleDate.getValue() + "Z")
.setMaxPoints(100.0)
.setDueDate(date)
.setDueTime(timeOfDay)
.setWorkType("ASSIGNMENT")
.setState("PUBLISHED")
;
This is what I get when I manually add a timestamp and turn it into a string.
And this using the Google date instead.
And this with the new Java 8 apis
java.time
I recommend that you use java.time, the modern Java date and time API, for your date and time work. The following code gives the same result as the code from your answer.
LocalDate localDate = LocalDate.now().plusDays(7);
String s = localDate.atStartOfDay(ZoneId.systemDefault())
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
System.out.println(s);
Output in my time zone today:
2021-10-20T00:00:00+02:00
Compared to your own answer you have fewer conversions, and you are freed from writing your own format pattern string since the formatter we need is built in.
This worked:
LocalDate localDate = LocalDate.now().plusDays(7);
java.util.Date date1 = java.util.Date.from(localDate.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
String s = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(date1);
It seems the imports were using the Google Date class instead of java.util.date.

Convert milliseconds to date string back and forth using java 7

I have the following code which uses all recommendations discussed in similar questions.
public class DateUtils {
static String secondsToDate(String seconds) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(Long.parseLong(seconds) * 1000);
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
return String.format("%d-%d-%d", year, month, day);
}
static String dateToSeconds(String date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
try {
Date parsed = format.parse(date);
long timeInMillis = parsed.getTime();
return Long.toString(timeInMillis / 1000);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String timestamp = "1409515200";
String date = secondsToDate(timestamp);
String timestamp2 = dateToSeconds(date);
System.out.printf("%s %s", timestamp, timestamp2);
}
}
The result of the code:
1409515200 1406836800
As you can see the conversion back and forth doesn't work. What's wrong?
Your problem here is the rounding. In the first method, you are converting your timestamp (which is the number of milliseconds from 1970) into a date. You are now getting only the date, discarding hours, minutes, seconds and milliseconds and converting it back. This means that you will always have a difference of the amount you are discarding (between 0 at 00:00:00:000 and 86400000 at 23:59:59:999). To fix it, simply change your date format to include the hours with milliseconds precision.
The answer by Aurasphere is correct and should be accepted.
Some further tips…
Use date-time classes for date-time values, rather than strings. Perform your business logic using date-time objects, and pass around such objects amongst your code rather than strings.
Avoid tracking date-times as a count-from-epoch. When you do need to serialize to text, use the unambiguous and easy-to-read formats defined by the ISO 8601 standard such as 2016-05-09T16:47:54Z.
You are using old troublesome legacy classes that have been supplanted by the java.time framework built into Java 8 and later. Much of that functionality has been back-ported to Java 6 & 7 in the ThreeTen-Backport project, and further adapted for Android in the ThreeTenABP project.
Using java.time classes will make your work easier and your code easier to comprehend, less likely to encounter the confusion seen in the Question.
An Instant is a moment on the timeline in UTC, with a resolution of nanoseconds. That class offers a convenient factory method ofEpochSecond, so no need to multiply by a thousand for milliseconds.
String input = "1409515200";
long seconds = Long.parseLong( input );
Instant instant = Instant.ofEpochSecond( seconds );
To get the wall-clock time for some locality, assign a time zone to get a ZonedDateTime.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
To generate a string in standard ISO 8601 format, call toString. Note that this method extends that format to append the name of the time zone in square brackets. For example, 2007-12-03T10:15:30+01:00[Europe/Paris].
String output = zdt.toString();
To get the date-only as in the Question, extract a LocalDate object.
LocalDate localDate = zdt.toLocalDate();
From there you can determine the first moment of the day. The first moment is not always the time-of-day 00:00:00.0, so let java.time determine that.
ZonedDateTime zdtStartOfDay = localDate.atStartOfDay( zoneId );
To get the two long integer counts of seconds seen in the Question, extract an Instant from each of our ZonedDateTime objects, and ask for the seconds-since-epoch. Note that you might be losing data as the ZonedDateTime/Instant objects can store values with a resolution up to nanoseconds. The call asking for whole seconds from epoch means any fraction of a second is truncated.
long seconds1 = zdt.toInstant().getEpochSecond();
long seconds2 = zdtStartOfDay.toInstant().getEpochSecond();

Multi-locale date parsing

I'm trying to write a class, which able to parse multi-format and multi-locale strings into DateTime.
multi-format means that date might be: dd/MM/yyyy, MMM dd yyyy, ... (up to 10 formats)
multi-locale means that date might be: 29 Dec 2015, 29 Dez 2015, dice 29 2015 ... (up to 10 locales, like en, gr, it, jp )
Using the answer Using Joda Date & Time API to parse multiple formats I wrote:
val locales = List(
Locale.ENGLISH,
Locale.GERMAN,
...
)
val patterns = List(
"yyyy/MM/dd",
"yyyy-MM-dd",
"MMMM dd, yyyy",
"dd MMMM yyyy",
"dd MMM yyyy"
)
val parsers = patterns.flatMap(patt => locales.map(locale => DateTimeFormat.forPattern(patt).withLocale(locale).getParser)).toArray
val birthDateFormatter = new DateTimeFormatterBuilder().append(null, parsers).toFormatter
but it doesn't work:
birthDateFormatter.parseDateTime("29 Dec 2015") // ok
birthDateFormatter.parseDateTime("29 Dez 2015") // exception below
Invalid format: "29 Dez 2015" is malformed at "Dez 2015"
java.lang.IllegalArgumentException: Invalid format: "29 Dez 2015" is
malformed at "Dez 2015"
I found what all parsers: List[DateTimeParser] had "lost" their locales after an appending into birthDateFormatter: DateTimeFormatter. And birthDateFormatter has only one locale - en.
I can write:
val birthDateFormatter = locales.map(new DateTimeFormatterBuilder().append(null, parsers).toFormatter.withLocale(_))
and use it like:
birthDateFormatter.map(_.parseDateTime(stringDate))
but it will throw a lots of exceptions. It's terrible.
How can I parse multi-format and multi-locale strings using joda-time?
How can I do it any other way?
That was interesting to investigate. This is a test suite that helped me (in Java, but I hope you'll get the idea):
import java.util.*;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
import org.joda.time.format.*;
import org.junit.Test;
import static org.assertj.core.api.Assertions.*;
public class JodaTimeLocaleTest {
#Test // fails on both assertions
public void testTwoLocales() {
List<Locale> locales = Arrays.asList(Locale.FRENCH, Locale.GERMAN);
DateTimeParser[] parsers = locales.stream()
.map(locale -> DateTimeFormat.forPattern("dd MMM yyyy").withLocale(locale).getParser())
.collect(Collectors.toList())
.toArray(new DateTimeParser[0]);
DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(null, parsers).toFormatter();
DateTime dateTime1 = formatter.parseDateTime("29 déc. 2015");
DateTime dateTime2 = formatter.parseDateTime("29 Dez 2015");
assertThat(dateTime1).isEqualTo(new DateTime("2015-12-29T00:00:00"));
assertThat(dateTime2).isEqualTo(new DateTime("2015-12-29T00:00:00"));
}
#Test // passes
public void testFrench() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("dd MMM yyyy").withLocale(Locale.FRENCH);
DateTime dateTime = formatter.parseDateTime("29 déc. 2015");
assertThat(dateTime).isEqualTo(new DateTime("2015-12-29T00:00:00"));
}
#Test // passes
public void testGerman() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("dd MMM yyyy").withLocale(Locale.GERMAN);
DateTime dateTime = formatter.parseDateTime("29 Dez 2015");
assertThat(dateTime).isEqualTo(new DateTime("2015-12-29T00:00:00"));
}
}
First of all, your first example
birthDateFormatter.parseDateTime("29 Dec 2015")
passes only because your machine's default locale is English. If it was different, also this case would have failed. That's why I'm using French and German when running on a machine with English locale. In my case, both assertions fail.
It turns out that the locale is not stored in the parser, but in the formatter only. So when you do
DateTimeFormat.forPattern("dd MMM yyyy").withLocale(locale).getParser()
the locale is set on the formatter, but is then lost when creating the parser:
// DateTimeFormatter#withLocale:
public DateTimeFormatter withLocale(Locale locale) {
if (locale == getLocale() || (locale != null && locale.equals(getLocale()))) {
return this;
}
// Notice how locale does not affect the parser
return new DateTimeFormatter(iPrinter, iParser, locale,
iOffsetParsed, iChrono, iZone, iPivotYear, iDefaultYear);
}
Next, when you create a new formatter
new DateTimeFormatterBuilder().append(null, parsers).toFormatter()
it's created with the system's default locale (unless you override it with withLocale()). And that locale is used during parsing:
// DateTimeFormatter#parseDateTime
public DateTime parseDateTime(String text) {
InternalParser parser = requireParser();
Chronology chrono = selectChronology(null);
// Notice how the formatter's locale is used
DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear, iDefaultYear);
int newPos = parser.parseInto(bucket, text, 0);
// ... snipped
}
So it turns out that although you can have multiple parsers to support multiple formats, still only a single locale can be used per formatter instance.
Answer to question 1 (How can I parse multi-format and multi-locale strings using joda-time?):
No this is not possible the way you want, see also the good answer of #Adam Michalik. So the only way is just to write a list of multiple Joda-formatters and to try each one for a given input - possibly catching exceptions. You have already found the right workaround so I don't describe the details here.
Answer to question 2 (How can I do it any other way?):
My library Time4J has got a new MultiFormatParser-class since v4.11. However, I discovered some performance issues with its format engine in general (mainly due to autoboxing feature of Java) so I decided to wait with this answer until release v4.12 where I have improved the performance. According to my first benchmarks Time4J-4.12 seems to be quicker than Joda-Time (v2.9.1) because internal exceptions are strongly reduced. So I think you can give that latest version of Time4J a try and report then some feedback if it works for you.
private static final MultiFormatParser<PlainDate> TIME4J;
static {
ChronoFormatter<PlainDate> f1 =
ChronoFormatter.ofDatePattern("dd.MM.uuuu", PatternType.CLDR, Locale.ROOT);
ChronoFormatter<PlainDate> f2 =
ChronoFormatter.ofDatePattern("MM/dd/uuuu", PatternType.CLDR, Locale.ROOT);
ChronoFormatter<PlainDate> f3 =
ChronoFormatter.ofDatePattern("uuuu-MM-dd", PatternType.CLDR, Locale.ROOT);
ChronoFormatter<PlainDate> f4 =
ChronoFormatter.ofDatePattern("uuuuMMdd", PatternType.CLDR, Locale.ROOT);
ChronoFormatter<PlainDate> f5 =
ChronoFormatter.ofDatePattern("d. MMMM uuuu", PatternType.CLDR, Locale.GERMAN);
ChronoFormatter<PlainDate> f6 =
ChronoFormatter.ofDatePattern("d. MMMM uuuu", PatternType.CLDR, Locale.FRENCH);
ChronoFormatter<PlainDate> f7 =
ChronoFormatter.ofDatePattern("MMMM d, uuuu", PatternType.CLDR, Locale.US);
TIME4J = MultiFormatParser.of(f1, f2, f3, f4, f5, f6, f7);
}
...
static List<PlainDate> parse(List<String> input) {
ParseLog plog = new ParseLog();
int n = input.size();
List<PlainDate> result = new ArrayList<>(n);
for (int i = 0; i < n; i++){
String s = input.get(i);
plog.reset();
PlainDate date = TIME4J.parse(s, plog);
if (!plog.isError()) {
result.add(date);
} else {
// log or report error
}
}
return result;
}
Every single parser within MultiFormatParser keeps its own locale.
The order of parser components matters in terms of performance. Prefer those patterns and locales for first positions which are most common in your input.
I strongly recommend to use a static constant for the MultiFormatParser because a) it is immutable and b) constructing formatters is expensive in every library (and Time4J is no exception about this detail).
For interoperability with Joda-Time you can consider this conversion: LocalDate joda = new LocalDate(plainDate.getYear(), plainDate.getMonth(), plainDate.getDayOfMonth()); But keep in mind that every conversion has some extra costs. On the other side, Joda-Time offers less features than Time4J so latter one can do the full job of all date-time-zone relevant tasks, too.
I am not a scala guy but assume that following scala code might compile:
val parser = MultiFormatParser.of(patterns.flatMap(patt => locales.map(locale => ChronoFormatter.ofDatePattern(patt, PatternType.CLDR, locale))).toArray)
By the way: The performance of Joda-Time is not so bad since it was a tough task for me to make it better in Time4J-v4.12. Parsing so different patterns and locales is always a complex task. Surprising for me: The new time library built in Java-8 (package java.time) is the worst in terms of performance according to my own experiments (obviously due to internal exception handling).
If you don't work on Java-8-platforms then you can use Time4J-v3.15 (backport to Java-6-platforms).

Why substracting two Dates gives me an extra hour?

I wanted to perform some benchmarks on some methods of my program so I wrote this little snippet
object ExecutionTime {
private val format = new java.text.SimpleDateFormat("hh:mm:ss")
private var timeList = scala.collection.mutable.MutableList[String]()
def startTimer() : Long = {
val start = System.currentTimeMillis()
start
}
def getExecutionTime(start : Long) {
val executionTime = System.currentTimeMillis() - start
timeList.+=(format.format(new Date(executionTime)))
}
def printResults() {
timeList.mkString(" /n ")
}
}
To test this, I ran this little piece of code :
object Test {
val begin = ExecutionTime.startTimer()
waitFor2Seconds()
ExecutionTime.getExecutionTime(begin)
ExecutionTime.printResults()
}
However, when I run this I am getting a strange output :
01:00:02 // Should be 00:00:02
The result I have is the one that I expect, but with 1 extra hour.
Anyone have an idea on this?
You are constructing a Date object using the time delta between clock measurements. Therefore the SimpleDateFormat is translating the Date to local time when constructing the String.
From the documentation (emphasis mine):
SimpleDateFormat is a concrete class for formatting and parsing dates
in a locale-sensitive manner.
Your constructed Date is epoch + 2 seconds, so your local time must be (epoch + 1 hour + 2 seconds); and that is what the formatter is printing.
To have SimpleDateFormat print without the 1 hour addition you need to set the locale to be GMT and change your hour format to be "HH":
private val format = new java.text.SimpleDateFormat("HH:mm:ss")
format.setTimeZone(java.util.TimeZone.getTimeZone("GMT"))
As another suggestion, look into the Joda time library (here's a scala wrapper). This library can be used to easily subtract and add periods of time and convert to time formats on the fly. I much prefer it to the SimpleDateFormat class.

How to parse string with date, but without time in local format to ZonedDateTime?

This question is similar to How to parse ZonedDateTime with default zone? but addinitional condition.
I have a string param that represent a date in UK format: "3/6/09". It doesn't contain time, only date. But may contain it and even time zone.
And I want to parse it to ZonedDateTime.
public static ZonedDateTime parse(String value) {
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(SHORT).withLocale(Locale.UK).withZone(ZoneId.systemDefault());
TemporalAccessor temporalAccessor = formatter.parseBest(value, ZonedDateTime::from, LocalDateTime::from, LocalDate::from);
if (temporalAccessor instanceof ZonedDateTime) {
return ((ZonedDateTime) temporalAccessor);
}
if (temporalAccessor instanceof LocalDateTime) {
return ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault());
}
return ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.systemDefault());
}
But, it fails with exception:
java.time.format.DateTimeParseException: Text '3/6/2009' could not be parsed at index 6
It's a bug for me, or isn't?
In my opinion is not a bug. Your approach is flawed.
First of all you are returning a ZonedDateTime so it is expected that the String contains full date, time and zone information. The string "3/6/09" should be parsed to a LocalDate.
Second, you are delegating a runtime detection of format to the library. Again, you should be parsing/formatting an expected format. Your application should know wether is expecting a full date & time or a partial (only date or only time).
Anyway you will have more luck detecting the format and then using different parsing methods.
Only local date:
DateTimeFormatter
.ofLocalizedDate(FormatStyle.SHORT)
.parse(value, LocalDate::from)`
Zoned date and time:
DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)
.parse(value, ZonedDateTime::from)`
The format used can be seen using the getLocalizedDateTimePattern() method:
String fmt = DateTimeFormatterBuilder.getLocalizedDateTimePattern(
FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.UK);
The result is "dd/MM/yy HH:mm".
As such, the format is expecting both a date and a time with a space separator, so that is what must be provided.
In addition, the format/parse expects there to be two digits for the day-of-month and two digits for the month-of-year. Thus, you would need to pass in "03/06/09 00:00" in order to get the result you expect, in which case you can parse directly to a LocalDateTime.
Alternatively, use ofLocalizedDate():
DateTimeFormatter formatter =
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.UK);
LocalDate date = LocalDate.parse("03/06/99", formatter);
Note that the input must still have two digits for the day and month.
Alternatively, parse using a specific pattern that can handle the missing leading zeroes:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yy");
LocalDate date = LocalDate.parse("3/6/99", formatter);
LocalDate date = LocalDate.parse("03/06/99", formatter);
// handles both "3/6/99" and "03/06/99"
Update: Lenient parsing also handles this case:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.parseLenient().appendPattern("dd/MM/yy").toFormatter();
LocalDate date = LocalDate.parse("3/6/99", formatter);
LocalDate date = LocalDate.parse("03/06/99", formatter);
// handles both "3/6/99" and "03/06/99"