Sorting dates with moment.js does not work when date is a link - datatables

I'm using DataTables 1.10.5. When I'm trying to sort on dates using the recommended moment.js (as per http://datatables.net/blog/2014-12-18), thinks work fine:
http://jsfiddle.net/9gohzd9t/1/
However, when I add a link (a href) to that date, it sorts on the link instead of the date:
http://jsfiddle.net/dnsL2oc4/1/
Any idea on how to properly fix this without too much hacking around?

The problem lies in the unshift method of datetime-moment.js. Moment tries to convert 12-01-2001 to a valid date in the given "DD-MM-YYYY"-Format, which it can't obviously. So you have to strip the html away from the date, probably with a function like this:
function strip(html) {
var tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
}
And then strip the string in the unshift method (Replace datetime-moment.js with the code below):
$.fn.dataTable.moment = function (format, locale) {
var types = $.fn.dataTable.ext.type;
// Add type detection
types.detect.unshift(function (d) {
return moment(strip(d), format, locale, true).isValid() ?
'moment-' + format :
null;
});
// Add sorting method - use an integer for the sorting
types.order['moment-' + format + '-pre'] = function (d) {
return moment(strip(d), format, locale, true).unix();
};
};

Related

How can I save part of a string in an alias using Cypress?

I'm trying to save just a number from a string I get from a paragraph but when I try to asign an alias to it and then check the value it returns undefined. I've tried a few solutions I found but none of those seem to work for me. These are two ways I tried (I tried another one similar to the second one but using split, had same result). The console.log inside of the 'then' doesn't show in the console, and when I try the alias after the code is when I get undefined.
cy.get('p')
.eq(1)
.should('have.text', '/[0-9]+/g')
.as('solNumber')
cy.get('p')
.eq(1)
.invoke('text')
.then((text)=>{
var fullText = text;
var pattern = /[0-9]+/g;
var number = fullText.match(pattern);
console.log(number);
})
.as('solNumber')
Please convert with + operator and return the numeric value if you want numeric type to be stored.
cy.get('p').eq(1)
.invoke('text')
.then(fullText => {
const number = fullText.match(/[0-9]+/);
return +number // text to numeric
})
.as('solNumber')
cy.get('#solNumber')
.should('eq', 42) // numeric type
});
Running your 2nd code on this,
<p>21</p>
<p>42</p>
gives the correct outcome
cy.get('p')
.eq(1)
.invoke('text')
.then((text)=>{
var fullText = text;
var pattern = /[0-9]+/g;
var number = fullText.match(pattern);
console.log(number); // logs 42
})
.as('solNumber')
cy.get('#solNumber')
.should('eq', '42') // passes
So, you need to inspect the DOM, it looks like it's not what you expect.
The first attempt you were passing a jquery element to the .should() and although some chainers change the subject yours did not so it saved the jquery element as solNumber.
The second attempt invokes the .text() which was passed to the .then() it logs the number correctly. However, you did not return anything at the end of the .then() block, therefore, solNumber should hold the entire paragraph.
This should help you out to extract the specific number and save it as an alias.
cy.get('p')
.invoke('text')
.invoke('trim')
.then(paragraph => {
const matcher = /some/
expect(paragraph).to.match(matcher) // check number is there
const indexOfText = paragraph.match(matcher) // get index of match text
return paragraph.substring(indexOfText.index, indexOfText.index + indexOfText[0].length) // return substring
})
.as('savedText')
cy.get('#savedText')
.then(cy.log) // will print out the number you seek

Express Validatior - How Do I Break The Validation Chain?

I have a date field that I want to ensure is in a valid format and if so is the user over 18. The format is YYYY-MM-DD.
Here is one of my validators - the one that is failing:
body('birthday', 'Date format should be: YYYY-MM-DD')
.isRFC3339()
.custom(date => {
const over18 = moment().diff(date, 'years') >= 18;
if(!over18) {
return Promise.reject('You must be 18 or over!');
}
}),
Currently what happens is if the date is not a RFC3339 date the validation chain continues. This is problematic because moment produces an error if I pass an ill formatted date.
How do I break the chain after the call to .isRFC3339() so that if the date is invalid the custom validator will not run? I couldn't find anything in the docs
You can use momentjs strict mode together with String + Format parsing using moment.ISO_8601 (or moment.HTML5_FMT.DATE) special formats.
Your code could be like the following:
body('birthday', 'Date format should be: YYYY-MM-DD')
// .isRFC3339() // no more needed
.custom(date => {
const mDate = moment(date, moment.ISO_8601, true);
const over18 = moment().diff(mDate, 'years') >= 18;
if(!mDate.isValid()) {
return Promise.reject('Date is not YYYY-MM-DD');
if(!over18) {
return Promise.reject('You must be 18 or over!');
}
}),

DataTables convert incoming epoch date to dd/mm/yyyy

I am using an AJAX call with datatables to populate a table. The format of the incoming data is:
{
"name": "John Doe",
"date":1244231200500
}
I can see that date formatting is discussed here: https://datatables.net/blog/2014-12-18 but there's no clear instructions for epoch data conversion.
What I want to do is put something (like within the example at the datatables site) like:
$.fn.dataTable.moment('epoch');
...but I don't see an option. What's the best approach here?
If you include moment.js you can sort this out quite easily using moment within a render function of your columns option array. Something like this should work:
{
"data":"date",
"render": function(data){
return moment.unix(data).format("DD/MM/YYYY")
}
}
Hope that helps.
You could create your own formatting function:
function ISODateString(d) {
function pad(n) { return n < 10 ? '0' + n : n }
return pad(d.getDate()) + '/' + pad(d.getMonth() + 1) + '/' + d.getFullYear();
}
And call it in datatables render:
"render": function(data){
var d = new Date(parseInt(data.substr(6), 0));
return ISODateString(d);
}

How to clear a date from input type="date" with WebdriverIO

I'm now using WebdriverIO and developing a web app.
these days I tried to set a date from input type="date", I got errors
invalid element state: Element must be user-editable in order to clear
it.
and found that
I could get rid of the errors by using addValue() but still the value won't be cleared by any API.
client.clearElement('#deadline')
Also get
invalid element state: Element must be user-editable in order to clear
it.
How can I remove the value from the form?
You can run in browser script to clear it
browser.execute(function () {
document.querySelector('#deadline').value = '';
}, null);
OR give it some value
var date = '2020-03-28';
browser.execute(function (date) {
document.querySelector('#deadline').value = date';
}, date);
reference: https://github.com/webdriverio/webdriverio/issues/386
A more elegant way is to create a custom command and put this piece of code inside
this one worked for me:
client.selectorExecute("#dateInput", function(inputs, value) {
// you can run over the inputs
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].type == 'date') // any condition
inputs[i].value = "1973-12-09";
}
// or just do that:
inputs[i].value = "1973-12-09";
return;
})

Formatted date filter in CGridView

I display my date in CGridView as: "22.6.2012 22:53" with:
array('name' => 'date',
'value' => date("j.n.Y G:i", strtotime($model->date))
),
But in my filter, I need to search in this format (which is in the database) to get results: "2012-06-22 22:53".
How can I make my filter to work in the format that is displayed in my CGridView? I've searched for an answer but haven't found one, I've also tried adding the date function in my model search() for this attribute:
$criteria->compare('date', date("j.n.Y G:i", strtotime($this->date), true);
but then I just get an empty list :)
Help would be greatly appreciated.
To begin with, you should not be using the value property to control the formatting of dates. The proper way is to set the type property to 'date' and, if you do not do this already, set CApplication.language to target the appropriate locale.
For the filter it would be best for the user if you use a CJuiDatePicker widget to let the user visually pick the date; there's a short and to-the-point guide on how to do that here.
Update:
Formatting columns with type == 'date' is done through CGridView.formatter, for which if you do not explicitly set a value the default is whatever the 'format' application component is. So you can specify and configure a CFormatter on the spot, or if you want to use the application's formatter but with slight modifications you can do
$formatter = clone Yii::app()->format;
$formatter->dateFormat = 'whatever'; // or $formatter->dateTimeFormat
and then assign this instance to CGridView.formatter.
compare() makes a sql sentence with the input, so I had to change the input to my wanted format.
my function:
function changeDateToDBformat($datum) {
if (strstr($datum, '.') || strstr($datum, ':')) {
$formats = array('!j.n', '!j.n.Y', '!j.n.Y H:i', '!n.Y H:i', '!n.Y', '!H:i', '!j.n.Y H', '!n.Y H', '!Y H:i', '!Y H');
$date = false;
foreach ($formats as $format) {
$date = DateTime::createFromFormat($format, $datum);
if (!($date === false)) {
$izbraniFormat = $format;
break;
}
}
if (!$date === false) {
$datum1 = $date->format('Y-m-d H:i');
$date2 = DateTime::createFromFormat(substr($izbraniFormat, 1, strlen($izbraniFormat)), $datum);
$datum2 = $date2->format('Y-m-d H:i');
$datumcas1 = explode(' ', $datum1);
$datumcas2 = explode(' ', $datum2);
$prvidatum = explode('-', $datumcas1[0]);
$drugidatum = explode('-', $datumcas2[0]);
$koncniDatum = '';
for ($a = 0; $a < sizeof($prvidatum); $a++) {
if ($prvidatum[$a] == $drugidatum[$a])
$koncniDatum .= '-' . $prvidatum[$a];
}
$koncniCas = '';
$prvicas = explode('-', $datumcas1[1]);
$drugicas = explode('-', $datumcas2[1]);
for ($a = 0; $a < sizeof($prvicas); $a++) {
if ($prvicas[$a] == $drugicas[$a])
$koncniCas .= ':' . $prvicas[$a];
}
$koncniDatum = substr($koncniDatum, 1, strlen($koncniDatum));
if (strlen($koncniCas) > 0)
$koncniDatum .= ' ' . substr($koncniCas, 1, strlen($koncniCas));
$datum = $koncniDatum;
}
}
return $datum;
}
//translations:
//izbrani == selected
//datum == date
//cas == time
//koncni == end
//prvi == first
//drugi == second
With this, a user can enter date in the format "j.n.Y H:i" and also just portions of this format (j.n, n.Y, Y H:i,...).
I would like to thank Jon and nickb for help! link
Like many others I also struggled with this, well displaying the grid wasn't the problem, but filtering in the localized datetime was!
So I created my own formatter, used it in the search() function of my models (when passing the search parameters to compare()) and it works like a charm.
I can now filter on date/datetime fields in any localization (I use Dutch):
"30-12-2018" becomes "2018-12-30"
">30-12-2018" becomes ">2018-12-30"
"30-12-2018 23:59:49" becomes "2018-12-30 23:59:49"
">=30-12-2018 23:59:49" becomes ">=2018-12-30 23:59:49"
My localization:
// dateFormat['short'] = 'dd-MM-yyyy'
// timeFormat['medium'] = 'HH:mm:ss'
Yii::app()->format->datetimeFormat = strtr(Yii::app()->locale->dateTimeFormat,
array("{0}" => Yii::app()->locale->getTimeFormat('medium'),
"{1}" => Yii::app()->locale->getDateFormat('short')));
Yii::app()->format->dateFormat = 'short';
Yii::app()->format->timeFormat = 'medium';
My CGridView contains the following date time column:
'mutation_date_time:dateTime'
And (a snippet of) my own formatter with some handy functions:
class Formatter extends CLocalizedFormatter
{
public function formatWithoutSearchOperator($value)
{
// This snippet is taken from CDbCriteria->compare()
if(preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/',$value,$matches))
{
$value=$matches[2];
$op=$matches[1];
}
else
$op='';
return $value;
}
public function formatOnlySearchOperator($value)
{
// This snippet is taken from CDbCriteria->compare()
if(preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/',$value,$matches))
{
$value=$matches[2];
$op=$matches[1];
}
else
$op='';
return $op;
}
/*
* Format a localized datetime back to a database datetime (Y-m-d H:i:s).
* If a comparison operator is given, it is preserved. So strip it if you need to save the date in the database.
* If no time given, it's also not returned (MySQL database appends '00:00:00' as time to it upon saving).
* With this function the following localized datetimes just work like the stock datetime filters:
* - "30-12-2018" becomes "2018-12-30"
* - "30-12-2018 " becomes "1970-01-01" (note the extra space in input)
* - ">30-12-2018" becomes ">2018-12-30"
* - "30-12-2018 23:59:49" becomes "2018-12-30 23:59:49"
* - ">=30-12-2018 23:59:49" becomes ">=2018-12-30 23:59:49"
*
* For save() and afterFind() integration see:
* https://github.com/YetOpen/i18n-datetime-behavior
*/
public function formatToDatabaseDatetime($value)
{
// get the comparison operator from the string:
$comparator = $this->onlySearchOperator($value);
// get the datetime without the comparison operator:
$datetime = $this->withoutSearchOperator($value);
// parse the given datetime according to the locale format to a timestamp
$datetime_parsed = CDateTimeParser::parse(
$datetime,
strtr(
Yii::app()->locale->datetimeFormat,
array(
"{0}" => Yii::app()->locale->getTimeFormat(Yii::app()->format->timeFormat),
"{1}" => Yii::app()->locale->getDateFormat(Yii::app()->format->dateFormat)
)
)
);
// if its not a valid date AND time, check if it can be parsed to a date only:
if($datetime_parsed === false)
{
$date_parsed = CDateTimeParser::parse(
$datetime,
Yii::app()->locale->getDateFormat(Yii::app()->format->dateFormat)
);
}
// If no time part given, also output only the date
if($datetime_parsed===false)
{
$transformed = date(
'Y-m-d',
$date_parsed
);
}
else
{
$transformed = date(
'Y-m-d H:i:s',
$datetime_parsed
);
}
return $comparator . $transformed;
}
}
And within my search() function in my CActiveRecord model I use the following to compare the localized datetime with the records in the database:
$criteria->compare('mutation_date_time',Yii::app()->format->toDatabaseDateTime(trim($this->mutation_date_time)),true);
Please note the trim() there, that's by design (see function description formatToDatabaseDateTime()).
A big difference with filtering directly in correct database format: an invalid date converts to "1970-01-01"!
I highly appreciate feedback and I really hope my code helps somebody!