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

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"

Related

Flutter DateTime error on string to DateTime [duplicate]

Say I have a string
"1974-03-20 00:00:00.000"
It is created using DateTime.now(),
how do I convert the string back to a DateTime object?
DateTime has a parse method
var parsedDate = DateTime.parse('1974-03-20 00:00:00.000');
https://api.dartlang.org/stable/dart-core/DateTime/parse.html
There seem to be a lot of questions about parsing timestamp strings into DateTime. I will try to give a more general answer so that future questions can be directed here.
Your timestamp is in an ISO format. Examples: 1999-04-23, 1999-04-23 13:45:56Z, 19990423T134556.789. In this case, you can use DateTime.parse or DateTime.tryParse. (See the DateTime.parse documentation for the precise set of allowed inputs.)
Your timestamp is in a standard HTTP format. Examples: Fri, 23 Apr 1999 13:45:56 GMT, Friday, 23-Apr-99 13:45:56 GMT, Fri Apr 23 13:45:56 1999. In this case, you can use dart:io's HttpDate.parse function.
Your timestamp is in some local format. Examples: 23/4/1999, 4/23/99, April 23, 1999. You can use package:intl's DateFormat class and provide a pattern specifying how to parse the string:
import 'package:intl/intl.dart';
...
var dmyString = '23/4/1999';
var dateTime1 = DateFormat('d/M/y').parse(dmyString);
var mdyString = '04/23/99';
var dateTime2 = DateFormat('MM/dd/yy').parse(mdyString);
var mdyFullString = 'April 23, 1999';
var dateTime3 = DateFormat('MMMM d, y', 'en_US').parse(mdyFullString));
See the DateFormat documentation for more information about the pattern syntax.
DateFormat limitations:
DateFormat cannot parse dates that lack explicit field separators. For such cases, you can resort to using regular expressions (see below).
Prior to version 0.17.0 of package:intl, yy did not follow the -80/+20 rule that the documentation describes for inferring the century, so if you use a 2-digit year, you might need to adjust the century afterward.
As of writing, DateFormat does not support time zones. If you need to deal with time zones, you will need to handle them separately.
Last resort: If your timestamps are in a fixed, known, numeric format, you always can use regular expressions to parse them manually:
var dmyString = '23/4/1999';
var re = RegExp(
r'^'
r'(?<day>[0-9]{1,2})'
r'/'
r'(?<month>[0-9]{1,2})'
r'/'
r'(?<year>[0-9]{4,})'
r'$',
);
var match = re.firstMatch(dmyString);
if (match == null) {
throw FormatException('Unrecognized date format');
}
var dateTime4 = DateTime(
int.parse(match.namedGroup('year')!),
int.parse(match.namedGroup('month')!),
int.parse(match.namedGroup('day')!),
);
See https://stackoverflow.com/a/63402975/ for another example.
(I mention using regular expressions for completeness. There are many more points for failure with this approach, so I do not recommend it unless there's no other choice. DateFormat usually should be sufficient.)
import 'package:intl/intl.dart';
DateTime brazilianDate = new DateFormat("dd/MM/yyyy").parse("11/11/2011");
you can just use : DateTime.parse("your date string");
for any extra formating, you can use "Intl" package.
void main() {
var dateValid = "30/08/2020";
print(convertDateTimePtBR(dateValid));
}
DateTime convertDateTimePtBR(String validade)
{
DateTime parsedDate = DateTime.parse('0001-11-30 00:00:00.000');
List<String> validadeSplit = validade.split('/');
if(validadeSplit.length > 1)
{
String day = validadeSplit[0].toString();
String month = validadeSplit[1].toString();
String year = validadeSplit[2].toString();
parsedDate = DateTime.parse('$year-$month-$day 00:00:00.000');
}
return parsedDate;
}
a string can be parsed to DateTime object using Dart default function DateTime.parse("string");
final parsedDate = DateTime.parse("1974-03-20 00:00:00.000");
Example on Dart Pad
String dateFormatter(date) {
date = date.split('-');
DateFormat dateFormat = DateFormat("yMMMd");
String format = dateFormat.format(DateTime(int.parse(date[0]), int.parse(date[1]), int.parse(date[2])));
return format;
}
I solved this by creating, on the C# server side, this attribute:
using Newtonsoft.Json.Converters;
public class DartDateTimeConverter : IsoDateTimeConverter
{
public DartDateTimeConverter()
{
DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFK";
}
}
and I use it like this:
[JsonConverter(converterType: typeof(DartDateTimeConverter))]
public DateTimeOffset CreatedOn { get; set; }
Internally, the precision is stored, but the Dart app consuming it gets an ISO8601 format with the right precision.
HTH

Extract the date format from react-intl

I have a component that uses a datepicker. The datepicker needs a dateFormat property that fits the momentjs pattern, for example 'DD.MM.YYYY' or 'MM/DD/YYYY'.
The date formatting is handled by react-intl. This works fine when converting from a date to a string (via formatDate). However, I need to retrieve the pattern as described above.
My goal is to do something like
dateFormat = this.props.intl.extractDateFormat() // returns 'DD.MM.YYYY'
I have found this similar question, but the only answer relies on parsing the string, which I cannot do, because I do not know whether Day or Month will come first in the formatted date.
If it is possible to convert this string to a date and somehow retrieve the format from momentjs, that would also be a good solution.
I was able to get the date format from react-intl. To do this, I defined an example date and had it formatted by react-intl, and then parsed the format by referring to the original string.
My component which is exported as injectIntl(Component) has this method:
deriveDateFormat = () => {
const isoString = '2018-09-25' // example date!
const intlString = this.formatDate(isoString) // generate a formatted date
const dateParts = isoString.split('-') // prepare to replace with pattern parts
return intlString
.replace(dateParts[2], 'DD')
.replace(dateParts[1], 'MM')
.replace(dateParts[0], 'YYYY')
}
The date will e.g. be formatted to '09/25/2018', and this function would return 'MM/DD/YYYY', a format which can be used by Moment.js.
This function only works if you know that the month and day will always be displayed with two digits. It would fail if the format is something like 9/25/2018.
I have not found a way to extract the date format from react-intl directly.

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

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)

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();

Passing date in Date data type only in groovy

I have been struggling with this even after doing so much of research on such a simple thing, so I need some help here.
I need to pass current date in date data type only in 'yyyy-MM-dd' format. SimpleDateFormat converts current date to string type and while trying to parse though it gets converted to Date type but changes the format.
I need currentDate in the format 'yyyy-MM-dd' of Date Type.
if(!session.dtfromDate && !session.dttoDate)
eq("startDate", currentDate)
I have managed to figure out a solutions to this. First got my desired format which obviously converted it to a String and then parsed this to a Date. It has worked perfectly fine.
SimpleDateFormat nsdf = new SimpleDateFormat('yyyy-MM-dd')
String currentDate = new SimpleDateFormat('yyyy-MM-dd').format(new Date());
Date newDate = (Date)nsdf.parse(currentDate)
if(!session.dtfromDate && !session.dttoDate)
eq("startDate", newDate)