How can I ensure NodaTime objects always get 'stringified' to ISO formats? - nodatime

When we use the NodaTime objects, it's a bit too easy to get the wrong format.
For example, we use string interpolation to construct uri's, but we really want the yyyy-MM-dd format. Same goes for logging, we don't really want any other format.
LocalDate date = new LocalDate(2020, 8, 10);
string toString = $"{date}"; // "den 10 augusti 2020"
logger.LogInformation("Date: {Date}", date); // "Date: Monday, 10 August 2020"
The documentation for ToString (which is used for the 2nd line above) states:
"The value of the current instance in the default format pattern ("D"), using
the current thread's culture to obtain a format provider."
If I change the current culture to InvariantCulture, I now get both of the above lines to show "Monday, 10 August 2020", which is better because they are consistent but neither is the yyyy-MM-dd format.
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
Ideally though, I would want to only customize how NodaTime objects are "stringified" to avoid any other undesired side effects of changing culture. Is there any help to get here or am I stuck?
edit:
I made a console application to have a minimal reproducible example
Console.WriteLine(new LocalDate(2020,8,13));
Console.WriteLine(ZonedDateTime.FromDateTimeOffset(DateTimeOffset.Now));
Console.WriteLine(DateTime.Now);
Console.WriteLine(DateTimeOffset.Now);
and with that I got the following output:
den 13 augusti 2020
2020-08-13T08:39:16 UTC+02 (+02)
2020-08-13 08:39:16
2020-08-13 08:39:16 +02:00
I would have liked if LocalDate had a default output of 2020-08-13 which is more useful in logs as well as string interpolation, for example: var uri = $"api/orders?date={localDate}"

The simplest way of achieving this is to use a CultureInfo that defaults to ISO-8601 formatting. It's reasonably easy to create that, starting with the invariant culture:
using NodaTime;
using System;
using System.Globalization;
using System.Threading;
class Program
{
static void Main(string[] args)
{
var isoCulture = (CultureInfo) CultureInfo.InvariantCulture.Clone();
var format = isoCulture.DateTimeFormat;
format.ShortDatePattern = "yyyy-MM-dd";
format.ShortTimePattern = "HH:mm:ss";
format.LongTimePattern = "HH:mm:ss.FFFFFFF";
format.FullDateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.FFFFFFF";
format.LongDatePattern = format.ShortDatePattern;
Thread.CurrentThread.CurrentCulture = isoCulture;
Console.WriteLine(new LocalDate(2020, 8, 13));
Console.WriteLine(ZonedDateTime.FromDateTimeOffset(DateTimeOffset.Now));
Console.WriteLine(DateTime.Now);
Console.WriteLine(DateTimeOffset.Now);
}
}
Output:
2020-08-13
2020-08-13T09:52:18 UTC+01 (+01)
2020-08-13 09:52:18.7351962
2020-08-13 09:52:18.7356716 +01:00
I believe .NET just uses "date pattern" {space} "time pattern" when formatting a DateTime, so I don't think there's a way of getting a T in there. But hey, the LocalDate output is what you wanted :)

Related

Importing date without local time transformation

I am getting a date in DD-MMM-YY format, and am using dayjs to convert it to standard date format in nodejs.
However, the returned date is 1 day earlier than the input date.
I was told this is probably due to some difference in the server local time.
I can easily just add a day to the date, but as this will be a global function that works in multiple time zones, I just want to get the date "as is" without any automatic adjustments.
This is my function:
const convertDate = (date,format,zone) => {
dayjs.tz.setDefault(zone);
console.log(date);
console.log(dayjs(date));
console.log(dayjs.utc(date).$d);
console.log(dayjs.tz(date,format,zone).$d);
var newDate = dayjs.tz(date,zone);
//newDate.setDate(newDate.getDate());
return newDate;
}
No matter which methods I use or which zone I set, the date comes out as one day earlier than the input date.
For example, for a date of 01-APR-03 I get:
2003-03-31T21:00:00.000Z
I want the time to just be 2003-04-01T00:00:00.000Z.
Following comments, I have tried the following approach, but the result is the same:
const fixMonthName = (s) => s.replace(/[A-Z]{2}-/, (m) => m.toLowerCase());
const d = dayjs.utc(fixMonthName("22-FEB-02"), "DD-MMM-YY");
console.log(d);
const s = d.toISOString();
console.log(s); //{result:
M {
'$L': 'en',
'$u': true,
'$d': 2002-02-21T22:00:00.000Z,
'$x': {},
'$y': 2002,
'$M': 1,
'$D': 21,
'$W': 4,
'$H': 22,
'$m': 0,
'$s': 0,
'$ms': 0
}}
2002-02-21T22:00:00.000Z
Let's recap the problem:
You have a date-only string value of 01-APR-03 (equivalent to 2003-04-01).
You're then parsing it as timestamp, treating it as if it were 2003-04-01T00:00:00.000 (local time). This is the cause of the logical error.
Then you're looking at a UTC representation (2003-03-31T21:00:00.000Z in your example), and wondering why it's been shifted. (Z means UTC)
Fundamentally, a date and a timestamp are two different concepts. If you conflate them, you will have complications in your code such as the one you described.
A date can be thought of as a half-open range of timestamps (from the start of one day, to just before the start of the next). In other words, logically the following is true:
'2003-04-01' == ['2003-04-01T00:00:00.000', '2003-04-02T00:00:00.000')
If you parse a date-only value to an object that represents a timestamp, you are choosing to assign a point-in-time within that range. Thus, if you pick the very start of the range, you can easily shift into a different day when viewing that from another time zone.
Note that the JavaScript Date object is misnamed. It isn't a date, it's a timestamp.
A day.js object also represents a timestamp, as do most other libraries including Moment, Luxon, date-fns, and many others.
There are a few different solutions to this problem:
You can pick a time in the middle of the range which is less likely to be shifted to a different date when viewed from another time zone. For example, 12:00:00 noon. (Though this isn't perfect, as there are some time zones that go up to UTC+14.)
You can avoid treating a date as a timestamp, by keeping it in an object or string that represents it as a whole date.
Unfortunately, this isn't a concept that has caught on well in JavaScript yet. The language and most libraries do not handle it this way. (One notable exception is js-joda, which has a LocalDate data type.) However, this will eventually be coming to the JavaScript language itself via the Temporal proposal, which adds Temporal.PlainDate.
You can ignore the time portion of a timestamp and only look at the date part, but this only works if you lock all your operations to UTC rather than local time. In other words, treat '2003-04-01' as if it were '2003-04-01T00:00:00.000Z' and never convert it to local time or another time zone.
If you were using just the JavaScript Date object, then you would do:
const d = new Date('2003-04-01T00:00:00.000Z'); // the Z parses as UTC
const s = d.toISOString(); // this always emits UTC
But since you have a custom date format to parse and want to use day.js, you can do something like the following:
Define a function to work around day.js's parsing case sensitivity issue. (You need Apr, not APR.)
const fixMonthName = (s) => s.replace(/[A-Z]{2}-/, (m) => m.toLowerCase());
Parse the input string using day.js's UTC mode
const d = dayjs.utc(fixMonthName('01-APR-03'), 'DD-MMM-YY');
Get the output as a string however you would like, using any of day.js's display functions:
const s = d.toISOString(); // "2003-04-01T00:00:00.000Z"
// or
const s = d.format('YYYY-MM-DD'); // "2003-04-01"
Note that if you need a JavaScript Date object, do not use $d but instead call .toDate(). From there, make sure you are only using the UTC representation of the Date object. Keep in mind that while some environments will emit UTC when logging a Date object to the console (as if you called .toISOString(), other environments will emit the local time equivalent (as if you called .toString().

Can't override Date toString in node.js

In node.js:
Date.prototype.toString = function dateToString() {
return `${this.getMonth()}/${this.getDate()} of ${this.getFullYear()}`
};
console.log("====>", new Date(2019, 0, 1))
I would expect "2/11 of 2019", instead I get "2019-01-01T02:00:00.000Z".
Is node.js broken?
You may think that logging a Date calls the Date object's toString function, so you could just override it - but that's not necessarily true. Some implementations will give you output similar to toISOString instead of toString. Nowhere in the ECMAScript spec does it define how console.log should behave. Even in the WhatWG Console Standard, it doesn't describe how to log Date objects - so you're in implementation dependent territory.
So, instead of overriding a function on the Date prototype, you'd have to override the console.log function, check if an argument passed to it is a Date, and if so convert it to a string instead, and then pass it along to the original console.log function. I'll leave that up to you (or someone else) to implement.
Or just remember to call .toString(), as ChuongTran showed in their answer.
I think node.js not broken. But you need to call toString() to get a string in console.log
Date.prototype.toString = function dateToString() {
return `${this.getMonth()}/${this.getDate()} of ${this.getFullYear()}`
};
var date = new Date(2019, 0, 1);
console.log("====>", date.toString());
console.log("====>", date.toDateString());
Output:
====> 0/1 of 2019
====> Tue Jan 01 2019

Unable to render date from model template

I'm working on a Grails project with Freemarker and am having trouble rendering a date from the data model. I start put placing a date into the model
def dataModel = [:]
def dataDate = new Date().parse("yyyy-MM-dd","2015-08-20")
dataModel.put("someDate",dataDate)
I then loop through the dataModel to verify the data types
dataModel.each { name, value ->
println "${name} : ${value} (Value is type: ${value.getClass()})"
}
For this my output is: someDate : Thu Aug 20 00:00:00 CDT 2015 (Value is type: class java.util.Date)
Next I set up my confiuration and try to process the template
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22)
cfg.setDefaultEncoding("UTF-8")
def ftlTemplate = new Template("name", new StringReader("${someDate}"), cfg)
Writer out = new StringWriter()
ftlTemplate.process(dataModel, out)
output = out.toString()
At this point I receive the following error
ERROR freemarker.runtime - Error executing FreeMarker template
Message: Can't convert the date-like value to string because it isn't known if it's a date (no time part), time or date-time value.
The blamed expression:
==> someDate [in template "name" at line 1, column 3]
I've found a Freemarker engine online here: http://freemarker-online.kenshoo.com/ and if I run the same datamodel and template through that the ouput I receive is: Aug 20, 2015
Can anyone point out where I am going wrong? I would like to render the output like that online engine does.
Thanks
The problem is (as the error message states) is that it's technically impossible to decide in general, if a java.util.Date stands for a date-only, date-time or time-only value. This is a technical fact that's outside FreeMarker. FreeMarker offers these ways to handle this problem:
Use ?date, ?time and ?datetime operators to give FreeMarker a hint in the template. See: http://freemarker.org/docs/ref_builtins_date.html#ref_builtin_date_datetype
Use java.sql.Date and java.sql.Timestamp and java.sql.Timestamp, where there's no such ambiguity. (Beware with Timestamp's funny fraction second handling though.)
Wrap the values manually into TemplateDateModel, where you can specify which kind of value it is. Then drop the TemplateDateModel into the data-model.
Change
new StringReader("${someDate}")
To
new StringReader('${someDate}')
The double quotes are probably causing double tempering
hi,look! you can try like these:
<td name="">${value.startTime?string('dd.MM.yyyy HH:mm:ss')}</td>
the you can replace value.startTime with the target varibale which is date or timestamp type

How to get today's day and transform it to a string using vala / genie?

I have this code, but it fails. Why?
// tomar la fecha
fecha = GLib.Date();
print "cogiendo"
var s = new StringBuilder("Fecha:");
dia:DateDay= fecha.get_day();
s.append_printf ("%u",dia);
print s.str;
fecha_str=s.str;
Glib returns:
g_date_get_day: assertion 'g_date_valid (d)' failed
I would suggest to use the GLib.DateTime class for this purpose.
You didn't write if you want the current date as a locale dependent or independent format.
Locale dependent, using format ():
var dt = new DateTime.now_local ();
stdout.puts (dt.format ("%x"));
Locale independent, using to_string () (beware that this will also include the time):
var dt = new DateTime.now_local ();
stdout.puts (#"$dt");
In a custom format using format ():
var dt = new DateTime.now_local ();
stdout.puts (dt.format ("%d/%m/%Y")); // 29/09/2014
I took the custom format from your own answer, but I wouldn't use it as it's confusing, because usually with / delimited dates have the format "%m/%d/%Y" or "%m/%d/%y".
I would prefer either the locale default format (which is what the user expects) or the ISO 8601 format which you get with to_string () or without the time with format ("%F").
From the documentation:
If the Date-struct is obtained from g_date_new, it will be safe to mutate but invalid and thus not safe for calendrical computations.
Perhaps you want to set_time_t, or set_time_val. If you want today's date, fecha.set_time_t(time_t());
This code goes well!! Searched in gnome's vala samples:
var now = new DateTime.now_local ();
fecha_str = now.get_day_of_month ().to_string()+"/"+now.get_month ().to_string()+"/"+now.get_year ().to_string()

Converting String to datefield / Java Microedition

Using J2ME, netbeans 7.2, Developing a mobile app..
I have converted the Datefield value to a String and Now want to put it back to a Datefield. To do this I need to convert the String back to Datefield, I am using the following code but its not happening.
long myFileTime = dateField.getDate().getTime(); // getting current/set date from the datefield into long
String date = String.valueOf(myFileTime); // converting it to a String to put it back into a different datefield
Date updatedate= stringToDate(date); // passing the string 'date' to the Method stringToDate() to convert it back to date.
dateField1.setDate(updatedate); // updating the date into the new datefield1
public Date stringToDate(String s)
{
Calendar c = Calendar.getInstance();
c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(s.substring(0, 2)));
c.set(Calendar.MONTH, Integer.parseInt(s.substring(3, 5)) - 1);
c.set(Calendar.YEAR, Integer.parseInt(s.substring(6, 10)));
return c.getTime();
}
Since you have mentioned that you have the long myFileTime around, you should be able to use:
Date updatedate=new Date(myFileTime);
To convert back to your date. If only your String is available, you should modify your function to this:
public Date stringToDate(String s){
Calendar c = Calendar.getInstance();
c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(s.substring(0, 2)));
c.set(Calendar.MONTH, Integer.parseInt(s.substring(2, 4))-1 );
c.set(Calendar.YEAR, Integer.parseInt(s.substring(4, 8)));
return c.getTime();
}
Note the changed indexes.
In Java SE, you should be able to use the following line, instead of setting each fields separately:
c.setTimeInMillis(Long.parseLong(s));
Since in s you have the dateField.getDate().getTime() that is equal to myFileTime, the number of seconds starting with January 1, 1970, based on your provided code.
Your stringToDate should work only if your string will have the following format: ddMMyyyy. Also note that in this case you should use a SimpleDateFormat to parse, something like:
Date updatedate = new java.text.SimpleDateFormat("ddMMyyyy HH:mm:ss").parse(date);

Resources