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.
Related
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.
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)
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();
I am trying to get only the records that was created in the last 5 minutes. I found code that uses between("date", now-1, now)but that doesn't work in my case because the 1 there represents a day and that would mean I would have to write now-0.00347 or now-(5/1440) coz that's what 5 minutes are. The problem with that is that the minus method can apparently not be applied to any datatype except integer. So if anyone knows any better way to get the records created in the last 5 minutes please let me know, I would really appreciate it. Here's what I got:
def activeUsers = User.withCriteria{
def now = new Date()
between("date", now-(5/1440), now
}
Use time categories to make date manipulation easier in Groovy. Such as this:
import groovy.time.*
import org.codehaus.groovy.runtime.TimeCategory
def now = new Date()
def beforeNow = now
use (TimeCategory) {
beforeNow = now - 5.minutes
}
def activeUsers = User.withCriteria{
def now = new Date()
between("date", beforeNow, now)
}
I have a string which is:
"may 2013"
This refers to the second of May. I am trying to get all instances of PostOrder where the date is the second of May.
What I have tried so far:
def t = new Date(new Integer(sp[1]), new Integer(month), 01)
def results = PostOrder.createCriteria().list() {
ilike('dateCreated', t )
}
Please note I have split the string so sp[1] is 2013 and I have parsed the May so the month variable is not 05.
The above attempt does not work.
The only way I know will work is grabbing all objects and doing a for each on them. parsing the dateCreated to a string then doing a .contains(). But this will get very slow and messy.
First point. According to the documentation, ilike() is a case-insensitive 'like' expression - see SQL LIKE Operator. So, it cannot accept a date. What you need is just eq().
def t = new Date(new Integer(sp[1]), new Integer(month), 1)
def results = PostOrder.createCriteria().list() {
eq('dateCreated', t)
}
Another point. The constructor Date() accepts the year minus 1900 as the first argument. So, you probably need to subtract 1900 from new Integer(sp[1]).
Also, that constructor is deprecated; I would suggest to use GregorianCalendar(new Integer(sp[1]), new Integer(month), 1).time.