I want to groupingBy a Collection based on the LocalDateTime, but I only want to get the Hour, not the minutes, seconds...
.collect(Collectors.groupingBy(cp -> getUpdateLocalDate())
public LocalDate getUpdateLocalDate() {
return getUpdateDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
but I need also the Date, something like: 2018-10-12T09:00:00, not only the hour
note that LocalDate doesn't have a time-zone. instead, return a LocalDateTime from the method getUpdateLocalDate (might be worth renaming also) then truncate the LocalDateTime instance.
.collect(Collectors.groupingBy(cp -> cp.getUpdateLocalDateTime().truncatedTo(ChronoUnit.HOURS));
As said by Aomine, a LocalDate does not contain any time information, therefore, you have to change the method to return a LocalDateTime instead.
public LocalDateTime getUpdateLocalDateTime() {
return getUpdateDate().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
Then, you may perform your operation as
.collect(Collectors.groupingBy(cp ->
cp.getUpdateLocalDateTime().truncatedTo(ChronoUnit.HOURS))
Related
Currently in my local. The API is expecting the same formart as in SQL Server database which is yyyy-mm-dd.
However when i am deploying the application into production server. The API is expecting the datetime format as yyyy-dd-mm.
Is there any way to configure my .net core web api to accept yyyy-mm-dd as default format in every enviroment?
Please show some code. You can try the following in the AddJsonOptions() pipeline in ConfigureServices()
services
.AddMvc()
.AddJsonOptions(options =>
{
//Set date configurations
//options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
options.SerializerSettings.DateFormatString = "yyyy-MM-dd"; // month must be capital. otherwise it gives minutes.
});
100% of the time, If I am using DateTime, I create an interface for it. It just makes life a lot easier when it's time for testing. I believe this would work for you as well.
There's a couple of reasons for this method.
It's testable.
It abstracts the dependency of DateTime out of your business logic.
If other systems in your app may need a different format, just create a new MyAppDateTimeProvider
public interface IDateTimeProvider
{
DateTime Now { get; }
string GetDateString(int year, int month, int day);
DateTime TryParse(string sqlDateString);
}
public class SqlDateTimeProvider : IDateTimeProvider
{
public DateTime Now => DateTime.UtcNow;
public string GetDateString(int year, int month, int day)
{
return new DateTime(year, month, day).ToString("yyyy-MM-dd");
}
public DateTime TryParse(string sqlDateString)
{
var result = new DateTime();
DateTime.TryParse(sqlDateString, out result);
return result;
}
}
I call a webservice from a Spring Boot App, using jackson-jsr-310 as maven dependency for being able to make use of LocalDateTime:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = this.createHeaders();
ResponseEntity<String> response;
response = restTemplate.exchange(uri,HttpMethod.GET,new HttpEntity<Object>(httpHeaders),String.class);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
BusinessPartner test = mapper.readValue(response.getBody(), BusinessPartner.class);
My problem is in the last line, the code produces this error:
java.time.format.DateTimeParseException: Text '/Date(591321600000)/' could not be parsed at index 0
The resulting JSON in response.getBody() looks like this:
{
"d":{
...
"Address":{...},
"FirstName":"asd",
"LastName":"asd",
"BirthDate":"\/Date(591321600000)\/",
}
}
And in my model class, I have the following member:
#JsonProperty("BirthDate")
private LocalDateTime birthDate;
So, after a bit of searching here I found out that this /Date(...)/ seems to be a Microsoft-proprietary Dateformat, which Jackson cannot deserialize into an object per default.
Some questions advise to create a custom SimpleDateFormat and apply it to the opbject mapper, which I tried to do, but then I think I miss the right syntax for mapper.setDateFormat(new SimpleDateFormat("..."));
I tried with e.g. mapper.setDateFormat(new SimpleDateFormat("/Date(S)/"));
or at the end even mapper.setDateFormat(new SimpleDateFormat("SSSSSSSSSSSS)"));
but it seems this does not work, too, so I am out of ideas for now and hope some people here could help me out.
edit 1:
further investigated, it seems one way to go is to write a custom DateDeSerializer for jackson. So I tried this:
#Component
public class JsonDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private DateTimeFormatter formatter;
private JsonDateTimeDeserializer() {
this(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
public JsonDateTimeDeserializer(DateTimeFormatter formatter) {
this.formatter = formatter;
}
#Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
String unixEpochString = parser.getText().trim();
unixEpochString = unixEpochString.replaceAll("[^\\d.]", "");
long unixTime = Long.valueOf(unixEpochString);
if (unixEpochString.length() == 0) {
return null;
}
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
localDateTime.format(formatter);
return localDateTime;
}
return null;
}
}
which actually returns nearly what I want, annotating my fields in the model using
#JsonDeserialize(using = JsonDateTimeDeserializer.class)
but not exactly:
This code returns a LocalDateTime of value: 1988-09-27T01:00.
But in the thirdparty system, the xmlvalue is 1988-09-27T00:00:00.
As it is obvious, the ZoneId here:
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
is the Problem, apart from a wrong dateformat.
So could someone here please help me out in how to switch to always use zeros for the time-part and to get my dateformat right? Would be great!
I'm assuming that the number 591321600000 is the epoch milli (number of milliseconds from 1970-01-01T00:00:00Z).
If that's the case, I think that SimpleDateFormat can't help you (at least I couldn't find a way to parse a date from the epoch milli using this class). The pattern S (according to javadoc) is used to format or parse the milliseconds field of a time (so its maximum value is 999) and won't work for your case.
The only way I could make it work is creating a custom deserializer.
First, I created this class:
public class SimpleDateTest {
#JsonProperty("BirthDate")
private LocalDateTime birthDate;
// getter and setter
}
Then I created the custom deserializer and added it to a custom module:
// I'll explain all the details below
public class CustomDateDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String s = p.getText(); // s is "/Date(591321600000)/"
// assuming the format is always /Date(number)/
long millis = Long.parseLong(s.replaceAll("\\/Date\\((\\d+)\\)\\/", "$1"));
Instant instant = Instant.ofEpochMilli(millis); // 1988-09-27T00:00:00Z
// instant is in UTC (no timezone assigned to it)
// to get the local datetime, you must provide a timezone
// I'm just using system's default, but you must use whatever timezone your system uses
return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
public class CustomDateModule extends SimpleModule {
public CustomDateModule() {
addDeserializer(LocalDateTime.class, new CustomDateDeserializer());
}
}
Then I added this module to my mapper and it worked:
// using reduced JSON with only the relevant field
String json = "{ \"BirthDate\": \"\\/Date(591321600000)\\/\" }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// add my custom module
mapper.registerModule(new CustomDateModule());
SimpleDateTest value = mapper.readValue(json, SimpleDateTest.class);
System.out.println(value.getBirthDate()); // 1988-09-26T21:00
Now some comments about the deserializer method.
First I converted the millis 591321600000 to an Instant (a class that represents a UTC instant). 591321600000 in millis is equivalent to 1988-09-27T00:00:00Z.
But that's the UTC date/time. To get the local date and time, you must know in what timezone you are, because in every timezone it's a different date and time (everybody in the world are at the same instant, but their local date/time might be different, depending on where they are).
In my example, I just used ZoneId.systemDefault(), which gets the default timezone of my system. But if you don't want to depend on the default and want to use a specific timezone, use the ZoneId.of("timezone name") method (you can get the list of all available timezones names with ZoneId.getAvailableZoneIds() - this method returns all valid names accepted by the ZoneId.of() method).
As my default timezone is America/Sao_Paulo, this code sets the birthDate to 1988-09-26T21:00.
If you don't want to convert to a specific timezone, you can use the ZoneOffset.UTC. So, in the deserializer method, the last line will be:
return instant.atZone(ZoneOffset.UTC).toLocalDateTime();
Now the local date will be 1988-09-27T00:00 - as we're using UTC offset, there's no timezone conversion and the local date/time is not changed.
PS: if you need to convert the birthDate back to MS's custom format, you can write a custom serializer and add to the custom module as well. To convert a LocalDateTime to that format, you can do:
LocalDateTime birthDate = value.getBirthDate();
// you must know in what zone you are to convert it to epoch milli (using default as an example)
Instant instant = birthDate.atZone(ZoneId.systemDefault()).toInstant();
String msFormat = "/Date(" + instant.toEpochMilli() + ")/";
System.out.println(msFormat); // /Date(591321600000)/
Note that, to convert a LocalDateTime to Instant, you must know in what timezone you are. In this case, I recommend to use the same timezone for serializing and deserializing (in your case, you can use ZoneOffset.UTC instead of ZoneId.systemDefault().
Here's some Groovy code I wrote that also handles the timezone offset: https://gist.github.com/jeffsheets/938733963c03208afd74927fb6130884
class JsonDotNetLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
LocalDateTime deserialize(JsonParser parser, DeserializationContext ctxt) {
convertDotNetDateToJava(parser.text.trim())
}
/**
* Returns a Java LocalDateTime when given a .Net Date String
* /Date(1535491858840-0500)/
*/
static LocalDateTime convertDotNetDateToJava(String dotNetDate) {
// Strip the prefix and suffix to just 1535491858840-0500
String epochAndOffset = dotNetDate[6..-3]
// 1535491858840
String epoch = epochAndOffset[0..-6]
// -0500 Note, keep the negative/positive indicator
String offset = epochAndOffset[-5..-1]
ZoneId zoneId = ZoneId.of("UTC${offset}")
LocalDateTime.ofInstant(Instant.ofEpochMilli(epoch.toLong()), zoneId)
}
}
We have a very massive system where reports are run off dates from specific days through to today's date using various definitions of "GenerateSalesReport(DateStart, Date.Now)".
For debugging purposes, I want to simulate reports that occurred in the past so I need to change the object "Date.Now" to a specific date from the past on my development environment. Is it possible to override date.Now?
That is one of the failings of DateTime.Now and related functions.
You should be passing in a DateTime to any function that relies on it. Any place you use DateTime.Now or DateTime.Today (and such) is a place where you should be passing in the date.
This allows you to test for different DateTime values, will make your code more testable and remove the temporal dependency.
A good solution is to abstract away external dependencies to be able to be able to stub them during test. To virtualize time I often use something like this:
public interface ITimeService {
DateTime Now { get; }
void Sleep(TimeSpan timeSpan);
}
In your case you don't need the Sleep part since you only depend on the current time, and of course you need to modify your code to use an externally supplied ITimeService when the current time is required.
Normally you would use this implementation:
public class TimeService : ITimeService {
public DateTime Now { get { return DateTime.Now; }
public void Sleep(TimeSpan timeSpan) { Thread.Sleep(timeSpan); }
}
For testing purposes you can instead use this stub:
public class TimeServiceStub : ITimeService {
public TimeServiceStub(DateTime startTime) { Now = startTime; }
public DateTime Now { get; private set; }
public void Sleep(TimeSpan timeSpan) { Now += timeSpan; }
}
Even if this were possible, you'd probably be better off changing the system time. Otherwise you've created a whole new test case (where your system is running under a different time than every other process on the system). Who knows what kind of problems you could run into.
I am using Entity Framework Code First, and I have an entity defined with a StartTime property, an EndTime property and a Duration property (along with some others). The Duration property is a calculated field and it is the duration in minutes between the start and end time. My property declarations are shown below:
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public int Duration
{
get
{
return (int)this.EndTime.Subtract(this.StartTime).TotalMinutes;
}
}
I would like to run this calculation in code, but have the duration value persisted to the database along with the start and end time values (to make it easier later down the line when I come to run reports against these figures).
My question is, how can I get this read-only property to map back and save to the database using code first?
Inspired by Slauma's answer, I was able to achieve what I was aiming for by using a protected setter. This persisted the value back to the database but didn't allow the value to be modified elsewhere.
My property now looks like this:
public int Duration
{
get
{
return (int)this.EndTime.Subtract(this.StartTime).TotalMinutes;
}
protected set {}
}
Supplying an empty setter might be a possible solution (although then the property isn't readonly anymore, of course):
public int Duration
{
get
{
return (int)this.EndTime.Subtract(this.StartTime).TotalMinutes;
}
set { }
}
As far as I know, readonly properties are not mappable to a column in the database.
Are there any ways to easy format Joda DateTime objects in Freemarker? For example with Java dates, we could use the ?string and other directives.
I know I could call toDate and get a Java Date, but I was hoping there was a better way.
You should be able to call the toString(String pattern) method directly from Freemarker:
${dateTime.toString('MM-dd-yy')}
(not tested)
There's even a simpler way of doing this, if you don't want to splatter toString('MM-dd-yy') all over your templates.
Simply extend Freemarker's DefaultObjectWrapper, so that it understands Joda Time out of the box:
public class JodaAwareObjectWrapper extends DefaultObjectWrapper {
#Override
public TemplateModel wrap(final Object obj) throws TemplateModelException {
if (obj == null) { return super.wrap(obj); }
if (obj instanceof LocalDate) { return new SimpleDate(((LocalDate) obj).toDate(), TemplateDateModel.DATE); }
// add support for all desired types here...
return super.wrap(obj);
}
}
and feed this object wrapper to the FreeMarker config when you fire up your FreeMarker engine
Configuration config = // ...
config.setObjectWrapper(new JodaAwareObjectWrapper());
You can then use FreeMarkers standard date built ins, such as ${dateTime?date} in your templates
I do not believe at this time there is any integration in Freemarker for JodaTime. It is pretty easy to put an object in your model for formatting, i.e.
Write a class "MyCustomJodaFormatterBean", with a format(String pattern, DateTime dateTime) method. Put an instance of this in the root.
root.put("joda", new
MyCustomJodaFormatterBean());
Then in freemarker,
${joda.format("MM-dd-yyy", dateTime)}
During parsing of FTL files freemarker builds its internal model of objects. For example java.util.Date expressions are wrapped into freemarker.template.SimpleDate. If expression value of your model is of type org.joda.time.DateTime - which is unknown for freemarker, it will be wrapped by default into freemarker.ext.beans.StringModel, converting your DateTime to string using toString() method.
For example, assume we have in FTL expression like:
med.expiryDate?date <= today?date
Where "med.expiryDate" is of type DateTime.
"med.expiryDate" will be wrapped into freemarker.ext.beans.StringModel and after this "med.expiryDate?date" will be parsed used freemarker.template.Configuration dateFormat. Which can leed to exception if this dateFormat is different then default format of DateTime.toString().
To fix this you need to make Freemarker understand that DateTime is also a date, not a string. Write your custom object wraper:
/**
* Wrapper to make freemarker identify org.joda.time.DateTime objects as date.
* User: evvo
* Date: 5/26/2016
* Time: 18:21
*/
public class DateTimeAwareObjectWrapper extends DefaultObjectWrapper {
#Override
public TemplateModel wrap(Object obj) throws TemplateModelException {
if (obj instanceof DateTime) {
return new SimpleDate(((DateTime) obj).toDate(), getDefaultDateType());
}
return super.wrap(obj);
}
}
And set it into freemarker configuration
configuration.setObjectWrapper(new DateTimeAwareObjectWrapper());
After such change I belive ?string suffix will also work on DateTime expression.