I'm trying to put in some time stamps in a AnnotatedTimeLine (Google Chart), and its req. to be in the Datetime format.
When I reformat (to Timestamp format) the string that I receive from the class, it gives me this:
2013-06-28 10:08:35.0
What I want to do is to remove the .0 at the end. How can I do this?
The code looks like this:
public List<Survey> getAllSurvey(List<Status> statusList) {
String sqlValues = "SELECT * FROM status WHERE idCategory = (SELECT idCategory FROM category WHERE name = '"
+ statusList.get(0).getCategoryName() + "');";
List<Survey> survies = new ArrayList<Survey>();
try {
List<Map<String, Object>> rows = getJdbcTemplate().queryForList(
sqlValues);
for (Map row : rows) {
Survey survey = new Survey();
survey.setCategoryName(statusList.get(0).getCategoryName());
survey.setValue((String) (row.get("value")));
survey.setUnit((String) row.get("unit"));
survey.setTimestamp(row.get("timeStamp") + "");
survies.add(survey);
System.out.println(survey.toString());
}
} catch (BadSqlGrammarException e) {
e.printStackTrace();
} catch (Exception e) {
System.out.println(sqlValues);
e.printStackTrace();
}
return survies;
}
Thanks in advance!
You must convert the timestamp to the dateformat you want in your class. The default string representation of Timestamp is including the nanoseconds at the end. If you want to change that you can always do this:
Timestamp t = Your timestamp;
SimpleDateFormat df = new SimpleDateFormat("YYYY.MM.dd HH:mm:ss");
String s = df.format(t);
Your code contains the line row.get("timeStamp") + "", which will effectively call row.get("timeStamp").toString() + "". Assuming the row contains a java.sql.Timestamp object, it will return the timestamp formatted like yyyy-mm-dd hh:mm:ss.fffffffff.
If this is not desired, create a SimpleDateFormat object to represent the desired formatting, and then call SimpleDateFormat#format(row.get("timeStamp")) to have your value formatted properly.
The SimpleDateFormat class describes how to define your date and time patterns. Make sure to create the SimpleDateFormat object only once, since it's relatively expensive to create it. Many people define it as a private static final variable so it can be reused many times.
TL;DR
You seem to be mixing up two things that you should try to distinguish: the value of your timestamp and the format of it.
You should not wish to use the Timestamp class. It is poorly designed and long outdated. Instead use OffsetDateTime, Instant or LocalDatetime from java.time.
Also in your Survey class do not keep the timestamp as a String. Still use one of the java.time classes mentioned. Only when you need to give string output, format into a string in the desired format. For this purpose, depending on what you need the string for you can probably do better than just avoiding the .0 at the end.
Your problem line
The center of your problem is here:
survey.setTimestamp(row.get("timeStamp") + "");
You are concatenating a Timestamp from the query and a String. In order to do this Java converts the Timestamp to a String too by calling its toString method. Timestamp.toString() returns a string with at least one decimal on the seconds (and more, up to 9, if there are more non-zero decimals).
Solution: java.time
A timestamp means (a representation of) a point in time. The best class to use for a point in time is Instant from java.time. So in your Survey class define timestamp to be an Instant.
For a moment I assume that you are stuck with the List<Map<String,Object>> that Spring queryForList() returns, and that we cannot avoid the timestamp there being a java.sql.Timestamp. If so convert the Timestamp to an Instant as soon as you get hold of it:
survey.setTimestamp(((Timestamp) row.get("timeStamp")).toInstant());
With a more modern query tool you can get either an OffsetDateTime, an Instant or a LocalDateTIme directly from your database and avoid the Timestamp class completely.
How to format your Instant (or other java.time object) into a string for presentation to the user or for interchange with another system depends so much on the context and requirements. Here’s just one simple example:
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
String formattedTimestamp = survey.getTimetamp()
.atZone(ZoneId.systemDefault())
.format(formatter);
System.out.println(formattedTimestamp);
Example output:
Sep 13, 2020 2:26:40 PM
The output from the example code will depend not only on time zone, also on locale.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Questions and answers about using the java.time classes with your SQL database:
Getting the date from a ResultSet for use with java.time classes
Best way to Change java.sql.TimeStamp to UTC format in Java
Documentation of the JdbcTemplate.queryForList method that you used
try this one line solution
survey.setTimestamp(row.get("timeStamp").toString().replaceAll("\\.\\d+", ""));
if performance is critical then this solution is better
String ts = row.get("timeStamp").toString();
ts = ts.substring(0, str.indexOf('.'));
Related
This question already has answers here:
how to parse OffsetTime for format HHmmssZ
(2 answers)
Closed 1 year ago.
I am getting an parse error while parsing a date
java.text.ParseException: Unparseable date: "2021-06-17T05:49:41.174Z"
Unparseable date: "2021-06-17T05:49:41.174Z"
my code looks like this
private static String generateAndValidate(int count) {
Clock clock = Clock.systemUTC();
String clockTime=clock.instant().toString();
String result=clockTime;
SimpleDateFormat output = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.ENGLISH);
try {
output.parse(clockTime);
} catch (ParseException e) {
System.out.println("process date parse error. Going for retry.");
}
return result;
}
Also tried hard coding the value here
SimpleDateFormat output = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.ENGLISH);
try {
output.parse("2021-06-17T05:49:41.174Z");
} catch (ParseException e) {
System.out.println("process date parse error. Going for retry.");
}
What could be the problem?
EDIT: The reason for the failing of your code is in the answer given by #GS3!
My answer provides alternatives that are generally considered mroe up-to-date:
I would not recommend to use a java.text.SimpleDateFormat here because you are involving a very old and practically outdated API while you are receiving the time by the modern API utilizing a java.time.Clock.
A good move would be to use java.time.format.DateTimeFormatters for parsing, but I think you could even skip the clock and use OffsetDateTime.now(ZoneOffset.UTC).
However, this code definitely parses the String produced by your first lines:
public static void main(String[] args) {
// your first two lines
Clock clock = Clock.systemUTC();
String clockTime = clock.instant().toString();
// a hint to the problem
System.out.println(clockTime + " <--- 6 fractions of second");
// how to parse a String like that in plain java.time
OffsetDateTime odt = OffsetDateTime.parse(clockTime);
System.out.println(odt.format(
DateTimeFormatter.ISO_OFFSET_DATE_TIME
)
);
}
The output of that will look like the following (obviously having different values):
2021-06-17T06:34:55.490370Z <--- 6 fractions of second
2021-06-17T06:34:55.49037Z
The output that uses a DateTimeFormatter.ISO_OFFSET_DATE_TIME is just one option, you can still define your own pattern using a DateTimeFormatter.ofPattern(yourPatternString), a DateTimeFormatterBuilder in order to handle optional parts or one of the other built-in formatters.
If you just want to get the current moment and store it in a some datetime class, you can use the now() method the datetime classes in java.time have:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
looks suitable here, but there's a ZonedDateTime, too.
Just have a look at java.time...
In SimpleDateFormat, Z represents a restricted subset of the RFC-822 time zone syntax. Instant::toString() provides a timestamp in the ISO-8601 format. You can fix this by using X instead of Z.
SimpleDateFormat output = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX",Locale.ENGLISH);
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;
}
}
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.
Here is my problem:
I have a user input a date like: 2012-12-24 (string)
I concatenate a time to that string, and convert to java.util.Date
My code looks like:
String tempstartdate = startdte; //startdte is the string value from a txtfield
tempstartdate += " 00:01:00";
String tempenddate = startdte;
tempenddate += " 23:59:59";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
java.util.Date validstartdte = null;
java.util.Date validenddte = null;
validstartdte = df.parse(tempstartdate); //validstartdte is a util.Date (and works)
validenddte = df.parse(tempenddate);
My problem is, when I take that util.Date and want to make it an sql.Date:
java.sql.Date sqlstartDate = new java.sql.Date(validstartdte.getTime());
java.sql.Date sqlendDate = new java.sql.Date(validenddte.getTime());
It will not give me the timestamp I assigned, it will only return the date in the form yyyy-MM-dd (such as 2012-12-23).
WHY!? I'm so frustrated.
Note: I noticed that when I used breakpoints, I was able to expand sqlendDate and see there is a value in there called cdate that returns: 2012-12-12T23:59:59.000-0500
The database I'm using is PostgreSQL.
Please help! Much appreciated.
java.sql.Date doesn't have the time.
Use java.sql.Timestamp instead.
I might be very late to answer this question but I think it might be helpful.
As stated by 'Felipe Fonseca', I converted the util date to sql date as follows:
public static java.sql.Timestamp convertToSqlDateTime(Date utilDate){
return new java.sql.Timestamp(utilDate.getTime());
}
Normally, java.sql.Date only returns Date value and time will be discarded. So, in order to get time also, java.sql.TimeStamp must be used.
TimeStamp Constructs a Timestamp object using a milliseconds time value. The integral seconds are stored in the underlying date value; the fractional seconds are stored in the nanos field of the Timestamp object.
For this purpose, utilDate.getTime() is used to return the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date Object.
If we want only java.sql.Date, we can do:
public static java.sql.Date convertToSqlDate(Date utilDate){
return new java.sql.Date(utilDate.getTime());
}
I have completely given up on using Java's standard Date classes, for exactly the reasons you list.
I've been using Joda Time for a while now, and have found it a lot simpler.