Using JsonConverter to keep all datetime values formatted the same - datetime

I have a use case in which ALL date/time values must be saved in JSON documents using the following format:
yyyy-MM-ddTHH:mm:ss.fffffff
In other words, all date/time values will be exactly 27 characters in length regardless of the date/time value, for example:
System.DateTime.MinValue = "0001-01-01T00:00:00.0000000"
System.DateTime.MaxValue = "9999-12-31T23:59:59.9999999"
19-May-2018 10:35:12 am = "2018-05-19T10:35:12.0000000"
Unfortunately, Json.Net decides to format date/times in various formats depending on the date/time value. I have tried to use the JsonConverter attribute on date/time properties, ie:
[JsonConverter( typeof( datetime_iso8601_converter ) )]
public System.DateTime created_utc;
My converter class is as follows:
public class datetime_iso8601_converter : Newtonsoft.Json.Converters.IsoDateTimeConverter
{
public datetime_iso8601_converter()
{
// Set format that all DateTime values will use ...
base.DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffff";
}
}
If I run a test using the debugger, for example:
created_utc = System.DateTime.MinValue;
I can see the following line is executed:
base.DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffff";
However, the resulting JSON date/time value that gets written is:
"0001-01-01T00:00:00"
SECOND ATTEMPT:
I tried the following converter ...
public class datetime_iso8601_converter : Newtonsoft.Json.Converters.DateTimeConverterBase
{
// The format that all datetime values will use ...
private const String k_fmt_datetime_iso8601 = "yyyy-MM-ddTHH:mm:ss.fffffff";
// Write the given datetime value using the format above ...
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
if ( value is System.DateTime )
{
String dt_str;
System.DateTime dt;
dt = ( System.DateTime ) value;
dt_str = dt.ToString( k_fmt_datetime_iso8601 );
writer.WriteRawValue( dt_str );
}
else
{
throw new ArgumentException( "value is not System.DateTime" );
}
}
// Return a datetime value ...
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
if ( reader.TokenType != Newtonsoft.Json.JsonToken.Date )
{
throw new Exception( "Invalid token. Expected Date" );
}
return reader.Value;
}
}
Again using the debugger I can see that dt_str is set to a value of:
"0001-01-01T00:00:00.0000000"
but the value that actually gets written is:
"0001-01-01T00:00:00"
I do not understand why.
HOW can I get all date/time values saved in the format required?
Thanks in advance.

The above issue was present in an environment that was using Azure Functions. The current release of Azure functions 1.0.13 does not seem to support the binding redirects, i.e.:
[JsonConverter( typeof( datetime_iso8601_converter ) )]
However, release 1.22.0 will support JsonSettings and binding redirects (hopefully).

Just ran into the same thing. The solution to is to override Newtonsoft's time format string and have it use fffffff instead of FFFFFFF 🙂
See https://github.com/JamesNK/Newtonsoft.Json/issues/2780#issuecomment-1363704872 for sample code and details.

Related

Database DateTime milli and nano seconds are truncated by default if they are 0s, while using it in Java 11 using ZonedDateTime

I am fetching datetime from an Oracle database and parsing in Java 11 using ZonedDateTime as below:
Oracle --> 1/19/2020 06:09:46.038631 PM
Java ZonedDateTime output --> 2020-01-19T18:09:46.038631Z[UTC]
Oracle --> 1/19/2011 4:00:00.000000 AM
Java ZonedDateTime output --> 2011-01-19T04:00Z[UTC] (So, here the 0s are truncated by default.
However, my requirement is to have consistent fixed length output like #1.)
Expected Java ZonedDateTime output --> 2011-01-19T04:00:00.000000Z[UTC]
However, I didn’t find any date API methods to achieve above expected output. Instead of manipulating a string, is there a way to preserve the trailing 0s with fixed length?
We have consistent ZonedDateTime types in the application, so we do not prefer to change that.
We have consistent ZonedDateTime type in application, so we do not
prefer to change that.
Why do you think 2011-01-19T04:00Z[UTC] is inconsistent? A date-time object is supposed to hold (and provide methods/functions to operate with) only the date, time, and time-zone information. It is not supposed to store any formatting information; otherwise, it will violate the Single-responsibility principle. The formatting should be handled by a formating class e.g. DateTimeFormatter (for modern date-time API), DateFormat (for legacy java.util date-time API) etc.
Every class is supposed to override the toString() function; otherwise, Object#toString will be returned when its object will be printed. A ZonedDateTime has date, time and time-zone information. Given below is how its toString() for time-part has been implemented:
#Override
public String toString() {
StringBuilder buf = new StringBuilder(18);
int hourValue = hour;
int minuteValue = minute;
int secondValue = second;
int nanoValue = nano;
buf.append(hourValue < 10 ? "0" : "").append(hourValue)
.append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
if (secondValue > 0 || nanoValue > 0) {
buf.append(secondValue < 10 ? ":0" : ":").append(secondValue);
if (nanoValue > 0) {
buf.append('.');
if (nanoValue % 1000_000 == 0) {
buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1));
} else if (nanoValue % 1000 == 0) {
buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1));
} else {
buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1));
}
}
}
return buf.toString();
}
As you can see, the second and nano parts are included in the returned string only when they are greater than 0. It means that you need to use a formatting class if you want these (second and nano) zeros in the output string. Given below is an example:
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
String input = "1/19/2011 4:00:00.000000 AM";
// Formatter for input string
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("M/d/u H:m:s.n a")
.toFormatter(Locale.ENGLISH);
ZonedDateTime zdt = LocalDateTime.parse(input, inputFormatter).atZone(ZoneOffset.UTC);
// Print `zdt` in default format i.e. the string returned by `zdt.toString()`
System.out.println(zdt);
// Formatter for input string
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.nnnnnnz");
String output = zdt.format(outputFormatter);
System.out.println(output);
}
}
Output:
2011-01-19T04:00Z
2011-01-19T04:00:00.000000Z
Food for thought:
public class Main {
public static void main(String[] args) {
double d = 5.0000;
System.out.println(d);
}
}
What output do you expect from the code given above? Does 5.0 represent a value different from 5.0000? How will you print 5.0000? [Hint: Check String#format, NumberFormat, BigDecimal etc.]

java.time.LocalDateTime conversion issue if seconds are 00

My web application is using Apache CXF and JAVA8, and facing below error in response if user send xs:datetime input(seconds 00) as
<urn1:dateTimeVal>2016-04-29T20:00:00</urn1:dateTimeVal>
ERROR :
org.apache.cxf.interceptor.Fault: Marshalling Error:
cvc-datatype-valid.1.2.1: '2016-04-29T20:00' is not a valid value for
'dateTime'.
I debug and analysed that if user send dateTimeVal as 2016-04-29T20:00:00 then CXF validations for input are passed and XML value is UnMarshaled to java.time.LocalDateTime as 2016-05-05T20:00 , and at the time of returning the response, the Marshaling error occurs due to loss of seconds part(00).
Any help/hint are appreciated.
P.S : You can try with below snippet :
java.time.LocalDateTime dt= java.time.LocalDateTime.of(2016, Month.MAY, 5, 20, 00, 00);
System.out.println(dt);
Note : Above code sample is just for understanding to print datetime value. But actual return type expected in web application is java.time.LocalDateTime
OUTPUT EXPECTED : 2016-05-05T20:00:00
OUTPUT ACTUAL : 2016-05-05T20:00
EDIT : The binding (JAXB) content for the field is :
#XmlElement(required = true, type = String.class)
#XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
#XmlSchemaType(name = "dateTime")
#Generated(value = "com.sun.tools.xjc.Driver", date = "2016-05-03T05:28:57+05:30", comments = "JAXB RI v2.2.11")
#NotNull
protected LocalDateTime dateTimeVal;
AND LocalDateTimeAdapter File is
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class LocalDateTimeAdapter
extends XmlAdapter<String, LocalDateTime>
{
public static LocalDateTime parse(String value)
{
DateTimeFormatter dateTimeAndZoneformatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
DateTimeFormatter dateTimeformatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
TemporalAccessor ta = null;
try
{
ta = dateTimeformatter.parse(value);
}
catch (DateTimeParseException ex)
{
ta = dateTimeAndZoneformatter.parse(value);
}
return LocalDateTime.from(ta);
}
public static String print(LocalDateTime value)
{
return value.toString();
}
public LocalDateTime unmarshal(String value)
{
return parse(value);
}
public String marshal(LocalDateTime value)
{
return print(value);
}
}
The problem appears to be in LocalDateTimeAdapter.print(). LocalDateTime.toString() omits the seconds when the seconds value is 0.
If you change it to
public static String print(LocalDateTime value)
{
return value.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
it will provide the seconds as well when marshaling.
To see a quick example, note the results of the following snippet:
System.out.println(LocalDateTime.of(2016,1,1,0,0,0,0).toString());
System.out.println(LocalDateTime.of(2016,1,1,0,0,0,0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
The output it gives is
2016-01-01T00:00
2016-01-01T00:00:00
In the documentation for LocalDateTime.toString() it explains this behavior:
The output will be one of the following ISO-8601 formats:
- uuuu-MM-dd'T'HH:mm
- uuuu-MM-dd'T'HH:mm:ss
- uuuu-MM-dd'T'HH:mm:ss.SSS
- uuuu-MM-dd'T'HH:mm:ss.SSSSSS
- uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS
The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero.
You may want to use
System.out.println (DateTimeFormatter.ISO_LOCAL_DATE_TIME.format (dt));
It gives:
2016-05-05T20:00:00

Correctly convert DateTime property with Dapper on SQLite

I'm using Dapper to insert and get objects to/from SQLite: one object have a property of type DateTime (and DateTimeOffset) that I have to persist and retrieve with milliseconds precision. I can't find a way to correctly retrieve the value because Dapper fail with:
System.FormatException : String was not recognized as a valid DateTime.
in System.DateTimeParse.ParseExactMultiple(String s, String[] formats, DateTimeFormatInfo dtfi, DateTimeStyles style)
in System.DateTime.ParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style)
in System.Data.SQLite.SQLiteConvert.ToDateTime(String dateText, SQLiteDateFormats format, DateTimeKind kind, String formatString)
in System.Data.SQLite.SQLite3.GetDateTime(SQLiteStatement stmt, Int32 index)
in System.Data.SQLite.SQLite3.GetValue(SQLiteStatement stmt, SQLiteConnectionFlags flags, Int32 index, SQLiteType typ)
in System.Data.SQLite.SQLiteDataReader.GetValue(Int32 i)
in System.Data.SQLite.SQLiteDataReader.GetValues(Object[] values)
in Dapper.SqlMapper.<>c__DisplayClass5d.<GetDapperRowDeserializer>b__5c(IDataReader r) in SqlMapper.cs: line 2587
in Dapper.SqlMapper.<QueryImpl>d__11`1.MoveNext() in SqlMapper.cs: line 1572
in System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
in System.Linq.Enumerable.ToList(IEnumerable`1 source)
in Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1443
in Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1382
What do I have to try? Column is of type DATETIME.
Do I have to create a custom TypeHandler and convert DateTime to and from a SQLite string in format "o"?
Dapper version 1.38
I know it's old, but I have found the solution.
After a lot of digging and analyzing Dapper code I came up with this (notice that this is 2019 year):
First you will have to create date time handler:
public class DateTimeHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
private readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;
public static readonly DateTimeHandler Default = new DateTimeHandler();
public DateTimeHandler()
{
}
public override DateTimeOffset Parse(object value)
{
DateTime storedDateTime;
if (value == null)
storedDateTime = DateTime.MinValue;
else
storedDateTime = (DateTime)value;
if (storedDateTime.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime)
return DateTimeOffset.MinValue;
else
return new DateTimeOffset(storedDateTime, databaseTimeZone.BaseUtcOffset);
}
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
DateTime paramVal = value.ToOffset(this.databaseTimeZone.BaseUtcOffset).DateTime;
parameter.Value = paramVal;
}
}
Now, notice that Dapper translates .Net's type DateTimeOffset to dbType - DateTimeOffset. You need to remove this mapping and add your own like this:
SqlMapper.RemoveTypeMap(typeof(DateTimeOffset));
SqlMapper.AddTypeHandler(DateTimeHandler.Default);
That's all. Now everytime Dapper will see DateTimeOffset property in your model, it will run your DateTimeHandler to manage this.
I have found that custom TypeHandler for base types can't be used because of default typeMap that is choosen before looking for TypeHandler.
I have opened an issue dapper-dot-net but in the mean time I have solved replacing via reflection the default typeMap with a new one like the previous minus the four key DateTime, DateTime?, DateTimeOffset, DateTimeOffset?
I've made a slight modification to Adam Jachocki's solution as it didn't work for me. I am storing a date as TEXT in Sqlite and Dapper was giving me a string instead of a DateTime as the object value to parse. Apparently, Sqlite stores datetime values using three different data types: INTEGER (unix epoch), TEXT (ISO 8601 YYYY-MM-DD HH:MM:SS.SSS), and REAL ("number of days since noon in Greenwich on November 24, 4741 B.C."). That last one is really out there, so it isn't supported in the code below.
See the sqlite docs and this page for more info.
Below is my implementation of the DateTimeOffset TypeHandler. The rest of Adam's solution remains the same.
internal class DateTimeOffsetHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
private static readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;
private static readonly DateTime unixOrigin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
public static DateTimeOffsetHandler Default { get; } = new DateTimeOffsetHandler();
public DateTimeOffsetHandler() {}
public override DateTimeOffset Parse(object value)
{
if (!TryGetDateTime(value, out DateTime storedDateValue))
{
throw new InvalidOperationException($"Unable to parse value {value} as DateTimeOffset");
}
if (storedDateValue.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime)
{
return DateTimeOffset.MinValue;
}
else
{
return new DateTimeOffset(storedDateValue, databaseTimeZone.BaseUtcOffset);
}
}
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
DateTime paramVal = value.ToOffset(databaseTimeZone.BaseUtcOffset).DateTime;
parameter.Value = paramVal;
}
private bool TryGetDateTime(object value, out DateTime dateTimeValue)
{
dateTimeValue = default;
if (value is DateTime d)
{
dateTimeValue = d;
return true;
}
if (value is string v)
{
dateTimeValue = DateTime.Parse(v);
return true;
}
if (long.TryParse(value?.ToString() ?? string.Empty, out long l))
{
dateTimeValue = unixOrigin.AddSeconds(l);
return true;
}
if (float.TryParse(value?.ToString() ?? string.Empty, out float f))
{
throw new InvalidOperationException("Unsupported Sqlite datetime type, REAL.");
}
return false;
}
}

How to serialize dynamic field names using JSON parser

I am using JSON.Net to serialize my objects. For eg, if this is my object
Class MainData
{
[JsonProperty("keyValues")]
string val;
}
the data for 'val' is a key value pair string like this key1:value1.
I have a scenario where I should not get the above 'keyValues' name in my final serialized string and instead get a serialized string which looks like this
{
"key1":"value1"
}
Currently with my serializer I am getting this, which is not what I need
{
"keyValues":"key:value1"
}
Can somebody guide me to any documentation/solution to dynamically assign the name of the field instead of using the default variable name/JSONProperty Name defined inside the object?
Thanks a lot in advance.
I've been struggling with this all day, what I've done is used a dictionary object and serialised this
however I had an error message that was "cannot serialise dictionary", should have read the whole message, "cannot serialise dictionary when the key is not a string or object"
this now works for me and gives me a key/value pair
i have the following objects
public class Meal {
public int mealId;
public int value;
public Meal(int MealId, int Value) {
mealId = MealId;
value = Value;
} }
public class Crew
{
public Meal[] AllocatedMeals {
get {
return new Meal[]{
new Meal(1085, 2),
new Meal(1086, 1) }; } }
public int AllocatedMealTotal {
get {
return this.AllocatedMeals.Sum(x => x.value); } }
}
then the following code
Dictionary<string,string> MealsAllocated = crew.AllocatedMeals.ToDictionary(x => x.mealId.ToString(), x => x.value.ToString());
return new JavaScriptSerializer().Serialize(
new {
Allocated = new {
Total = crew.AllocatedMealTotal,
Values = MealsAllocated } )
to get
"Allocated":{"Total":3,"Values":{"1085":"2","1086":"1"}}

How to work with several fields in DateTime?

public DateTime EnterDeparture()
{
Console.WriteLine("Enter Year:");
return new DateTime().AddYears(int.Parse(Console.ReadLine()));
}
// This will return new DateTime(Without assigned Year) Cause DateTime is value type.
public DateTime EnterDeparture()
{
DateTime EnterDeparture = new DateTime();
Console.WriteLine("Enter Year:");
EnterDeparture.AddYears(int.Parse(Console.ReadLine()));
return EnterDeparture;
}
How to work with several fields in DateTime ? (Year,Days for example) Default constructors aren't suitable.
The DateTime.AddXXX methods return new DateTime instances, the existing struct does not change. Since each method returns a new instance, you can chain the method calls together. At the very least, you want to capture each return value into a variable. For example:
DateTime myDate = DateTime.Today;
DateTime tomorrowAtNoon = myDate.AddDays(1).AddHours(12);
You could have also written it like
DateTime tomorrow = myDate.AddDays(1);
DateTime tomorrowAtNoon = tomorrow.AddHours(12);
Follow?

Resources