-1

Suppose you have a json response string like this one, and you want to filter by date. You want to display records ONLY after a certain date.

[{btc=0, datetime=2018-01-30 12:16:11, eur=410.00, fee=0.00, usd=0, id=***, btc_usd=0.00, type=0}, {btc=0, eth_eur=692.81, datetime=2018-02-06 16:45:16, eur=-5.20, fee=0.02, usd=0, eth=0.00750000, id=***, type=2, order_id=***}, {btc=0, eth_eur=699, datetime=2018-02-05 17:04:25, eur=5.24, fee=0.02000000, usd=0, eth=-0.00750000, id=***, type=2, order_id=***}, {btc=0, eth_eur=562.1, datetime=2018-02-04 17:52:09, eur=-5.62, fee=0.02, usd=0, eth=0.01000000, id=***, type=2, order_id=***}, {btc=0, eth_eur=567.2, datetime=2018-02-06 20:24:07, eur=5.67, fee=0.02000000, usd=0, eth=-0.01000000, id=***, type=2, order_id=***}, {btc=0, datetime=2018-04-28 17:22:21, eur=0, fee=0.00000000, usd=0, eth=0.38811442, id=***, btc_usd=0.00, type=0}, {btc=0, eth_eur=563, datetime=2018-04-28 17:42:51, eur=218.50, fee=0.55000000, usd=0, eth=-0.38810000, id=***, type=2, order_id=***}, {btc=0, eth_eur=563, datetime=2018-04-28 17:47:52, eur=0.01, fee=0.01000000, usd=0, eth=-0.00001442, id=***, type=2, order_id=***}, {btc=0.01786568, btc_eur=5583.33, datetime=2018-08-14 12:01:13, eur=-99.75, fee=0.25, usd=0, id=***, type=2, order_id=***}]

In a similar case

{btc_available=0.01489932, eth_reserved=0.00000000, eur_balance=1858.63, btcusd_fee=0.500, xrpeur_fee=0.500, btc_balance=0.01489932, xrp_withdrawal_fee=0.02000000, ethusd_fee=0.500, ltceur_fee=0.500, eth_balance=1.30423351, xrp_reserved=0.00000000, bchusd_fee=0.500, eur_reserved=0.00, bch_available=0.00000000, usd_available=0.18, xrp_available=328.75000000, xrpusd_fee=0.500, ltcbtc_fee=0.500, bcheur_fee=0.500, ltc_available=0.00000000, btc_reserved=0.00000000, ltc_withdrawal_fee=0.00100000, usd_reserved=0.00, btc_withdrawal_fee=0.00050000, eurusd_fee=0.500, xrp_balance=328.75000000, ltcusd_fee=0.500, ltc_balance=0.00000000, bch_reserved=0.00000000, bch_withdrawal_fee=0.00010000, eur_available=1858.63, ltc_reserved=0.00000000, bchbtc_fee=0.500, ethbtc_fee=0.500, etheur_fee=0.500, usd_balance=0.18, eth_available=1.30423351, btceur_fee=0.500, eth_withdrawal_fee=0.00100000, bch_balance=0.00000000, xrpbtc_fee=0.500}

I could apply this filter:

var keys    = Object.keys(data);
var values  = Object.keys(data).map(function(e){return data[e]});

var result = [];
var k;
for (k=0; k<keys.length; k++){
  if (keys[k].slice(4, 13) == "available") {result.push([keys[k], values[k]]);}}

which I wasn't able to apply in this condition.

So I thought I would apply a for(){} case, but is there a more rational and efficient way to do this?

 var i;
  for (i=0; i < data.length; i++) {

   var d         = data[i].datetime;  
   var bits      = d.split(/\D/);
   var date      = new Date(bits[0], --bits[1], bits[2], bits[3], bits[4], bits[5]); 
   var date      = (date.getTime().toFixed(0))/1000;
   var date      = date.toString(); 

   if (date >  lastDate) { //IFFONE
   }
   }

I even considered trying to extract from the json string every pair of key/value set and render them as array, so that I could always point to the exact position (datetime in this case), but I guess there's a more immediate and consistent way. How would you do that? thanks
ps: I forgot to mentions that we are in google apps script

EDIT: outcome when applying @contributorpw's filter:

this script

var data  = {key: cred.key, signature: signature, nonce: nonce, sort: 'asc', limit: '10'};

var options = {'method' : 'post', 'muteHttpExceptions' : true, 'payload' : data};

var data    = UrlFetchApp.fetch('https://www.bitstamp.net/api/v2/user_transactions/', options);
var data    = JSON.parse(data.getContentText());
var values  = data;

produces the following set of json data:

[{btc=0, datetime=2018-01-31 12:15:11, eur=410.00, fee=0.00, usd=0, id=50575781, btc_usd=0.00, type=0},
{btc=0, eth_eur=692.81, datetime=2018-02-04 16:49:16, eur=-5.20, fee=0.02, usd=0, eth=0.00750000, id=52010995, type=2, order_id=893820507}, 
{btc=0, eth_eur=699, datetime=2018-02-04 17:04:25, eur=5.24, fee=0.02000000, usd=0, eth=-0.00750000, id=52013332, type=2, order_id=893880089}, 
{btc=0, eth_eur=562.1, datetime=2018-02-05 17:52:09, eur=-5.62, fee=0.02, usd=0, eth=0.01000000, id=52338326, type=2, order_id=900603492},
{btc=0, eth_eur=567.2, datetime=2018-02-05 20:29:07, eur=5.67, fee=0.02000000, usd=0, eth=-0.01000000, id=52444353, type=2, order_id=900621129}, 
{btc=0, datetime=2018-04-30 17:29:21, eur=0, fee=0.00000000, usd=0, eth=0.38811442, id=64286847, btc_usd=0.00, type=0},
{btc=0, eth_eur=563, datetime=2018-04-30 17:41:51, eur=218.50, fee=0.55000000, usd=0, eth=-0.38810000, id=64287296, type=2, order_id=1424216470}, 
{btc=0, eth_eur=563, datetime=2018-04-30 17:41:52, eur=0.01, fee=0.01000000, usd=0, eth=-0.00001442, id=64287298, type=2, order_id=1424216470}, 
{btc=0, datetime=2018-05-24 13:45:15, eur=1300.00, fee=0.00, usd=0, id=66875907, btc_usd=0.00, type=0}, 
{btc=0.01786568, btc_eur=5583.33, datetime=2018-08-12 12:01:13, eur=-99.75, fee=0.25, usd=0, id=72064184, type=2, order_id=1986400456}]

It's limited to only 10 records for semplicity.

Now, let's suppose we want to apply a filter that returns only data after the date 2018, 4, 16.
Here's what happens:

[19-12-17 12:08:48:897 PST] [
 {
  "fee": "0.00",
  "btc_usd": "0.00",
  "datetime": "2018-05-24 13:45:15",
  "usd": 0,
  "btc": 0,
  "type": "0",
  "id": 66875907,
  "eur": "1300.00"
 },
 {
  "fee": "0.25",
  "order_id": 1986400456,
  "datetime": "2018-08-12 12:01:13",
  "usd": 0,
  "btc": "0.01786568",
  "btc_eur": 5583.33,
  "type": "2",
  "id": 72064184,
  "eur": "-99.75"
 }
]

while instead you should have more records.. what do I do wrong?
here's the full script:

/* nuova funzione nonce */
_generateNonce = function() {
  var now = new Date().getTime();

  if(now !== this.last)
    this.nonceIncr = -1;

  this.last = now;
  this.nonceIncr++;

  // add padding to nonce incr
  var padding =
    this.nonceIncr < 10 ? '000' :
      this.nonceIncr < 100 ? '00' :
        this.nonceIncr < 1000 ?  '0' : '';
  return now + padding + this.nonceIncr;
} //fine funzione nonce

var nonce = this._generateNonce(); 

var cred = {
       id:'***',
      key:'***',
   secret:'***'};


var message = nonce + cred.id +  cred.key;

var res = Utilities.computeHmacSha256Signature(message, cred.secret).map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
var signature = res.toUpperCase();

  var data  = {key: cred.key, signature: signature, nonce: nonce, sort: 'asc', limit: '10'};

var options = {'method' : 'post', 'muteHttpExceptions' : true, 'payload' : data};

var data    = UrlFetchApp.fetch('https://www.bitstamp.net/api/v2/user_transactions/', options);
var data    = JSON.parse(data.getContentText());
var values  = data;

var date = new Date(2018, 4, 16).toISOString().split(/t/i)[0];

var filteredData = values.filter(function(item) {
  var d = item.datetime.split(/\s/)[0];
  return d >= date;});

Logger.log(JSON.stringify(filteredData, null, ' '));

2nd EDIT: what was the problem
The problem was simply that I had a one-month offset difference between the date I manually entered and the acutal date the script produced: so this messed up all the results and I couldnt understand why.

In other words, when entered manually, this line of command

var date = new Date(2018, 3, 28).toISOString().split(/t/i)[0];

Logger.log(date);

produces this outcome:

[19-12-18 13:54:17:932 CET] 2018-04-27

If, on the other hand, if you feed data straight from the cell (which is already date-formatted) nothing strange happens:

var date = aDate.toISOString().split(/t/i)[0];

var filteredData = values.filter(function(item) {
  var d = item.datetime.split(/\s/)[0];
  return d >= date;
});

Please read this for reference.
I have to say thanks to both @contributorpw and @Diego for their contribution. In the end I have chosen the answer from contributorpw because it's the one I've found more slender and agile on the code but both were good and functional. thanks

John Galassi
  • 309
  • 2
  • 16
  • 1
    This has already been answered: [*Why does Date.parse give incorrect results?*](https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results) and [*Compare two dates with JavaScript*](https://stackoverflow.com/questions/492994/compare-two-dates-with-javascript). – RobG Dec 17 '19 at 21:56
  • @RobG the answers you linked are not relevant unfortunately. The problem with my script has to do with ```days missing``` and I think it's not just a matter of difference in localtime vs UTC. As to the second one, we are not talking about ```comparing```, but rather ```filtering```. I'll look into the answers you linked anyway and see if they help, even if of course I made quite some research before posting. It's too easy to just call a duplicate in my opinion. thanks anyway – John Galassi Dec 17 '19 at 22:56
  • `new Date(2018, 3, 28).toISOString()` produces a date for 2018-04-27 in your timezone (CET or UTC+1) because of the timezone offset. Filtering necessitates a comparison, as in `return d >= date`. – RobG Dec 20 '19 at 00:05
  • @RobG could you please articulate your comment, even in a specific answer if necessary or linking to a (clear) source. I understand it's crucial but I can't understand how to apply what you're saying to my case. thanks – John Galassi Dec 21 '19 at 16:31

3 Answers3

1

I'd like work with strings

var values = [{
    btc: 0,
    datetime: '2018-01-30 12:16:11',
    eur: 410.0,
    fee: 0.0,
    usd: 0,
    id: '***',
    btc_usd: 0.0,
    type: 0
  },
  ...
];
var date = new Date(2018, 3, 28).toISOString().split(/t/i)[0];
var filteredData = values.filter(function(item) {
  var d = item.datetime.split(/\s/)[0];
  return d >= date;
});
Logger.log(JSON.stringify(filteredData, null, ' '));

It works great.

If you'd like compare datetime then get date as

var date = new Date(2018, 3, 28, 17 + 5, 42)
  .toISOString()
  .split(/[t\.]/i)
  .slice(0, 2)
  .join(' ');

And compare this as

 var d = item.datetime.split(/\s/)[0];
 return d >= date;

You have consider the time shift 17 + 5.

Anytime you can skip date using

var filteredData = values.filter(function(item) {
  return item.datetime >= '2018-04-28';
});
contributorpw
  • 4,739
  • 5
  • 27
  • 50
  • I like your script: it's simple and elegant; but there's apparently a problem with it: it misses some data from the Json set: i.e. all those rows marked as ```type=0```. If you check my post you'll notice that not all json strings share the same pattern. Some are ```type 0```(deposits), some ```1```(withdrawals) and some ```2```(the trades). Apparently your code returns only ```type 2```, the trades. Am I wrong? Please check into it if I am right because it's a really nice code and I have some further questions to ask. Many thanks – John Galassi Dec 17 '19 at 16:56
  • Nope. It's fine for me https://bitbucket.org/snippets/contributorpw/XLbMrd It returns type 0 and type 2. – contributorpw Dec 17 '19 at 18:18
  • unfortunately still doesn't work. I've edited my question including a detailed description of the outcome. Could you please give it a check and see if you find something? I really care about this function :) thanks – John Galassi Dec 17 '19 at 20:34
  • 2
    This compares **local** dates with **UTC** dates, so will appear one day out for users with a negative timezone offset. – RobG Dec 17 '19 at 20:43
  • @RobG I thought of that, but it seems to me that we are talking about several days missing, aren't we? – John Galassi Dec 17 '19 at 20:51
  • 1
    @JohnGalassi Sorry you need share more sample data. What you gave us is completely filtered in my example. I agree with Rob. – contributorpw Dec 18 '19 at 04:17
  • 1
    Yesterday night I finally was able to locate where the problem was, and was luckily able to solve it. Now give me just some time to organize my thoughts and I'll write details in the edited question, since I just woke up: after all I guess we have different time zones! :) – John Galassi Dec 18 '19 at 11:40
1

Filtering requires comparison of values, in this case a specific date with the datetime property. As datetime is in an ISO 8601 format (though not the format supported by ECMA-262), the comparison can be with a string in the same format. This approach means generating one comparison string and then filtering without further processing of datetime.

Alternatively, the datetime strings can be converted to Dates, then the array filtered by comparing Date objects or time values (which are effectively the same thing). This may be less efficient as every datetime must be converted to a Date.

Both these issues have been covered before:

Date strings without a timezone should be treated as local. If you have separate information that they should be treated as UTC, that should be included in the question. Parsing should be done manually, do not trust the built–in parser (at least one current browser will parse '2018-01-31 12:15:11' as an invalid date).

Generating a date in the right format can use a simple formatting function, then compare to data[i].datetime. Similarly, converting datetime to a Date needs a simple parse function (or a library):

let data = [{datetime: '2018-01-31 12:15:11'},
            {datetime: '2018-02-04 16:49:16'}, 
            {datetime: '2018-02-04 17:04:25'}
           ];

// Filter as strings
function toISO(d) {
  let z = n => (n<10? '0':'') + n;
  return d.getFullYear() + '-' +
         z(d.getMonth() + 1) + '-' +
         z(d.getDate()) + ' ' +
         z(d.getHours()) + ':' +
         z(d.getMinutes()) + ':' +
         z(d.getSeconds());
}

let d = new Date(2018, 1, 1); // 1 Feb 2018
let filterDate = toISO(d);    // 2018-02-01 00:00:00
let filteredData_A = data.filter( o => o.datetime > filterDate);

console.log(filteredData_A);

// Filter as Date objects

function parseDate(s) {
  let b = s.split(/\D/);
  return new Date(b[0], b[1]-1, b[2], b[3], b[4], b[5]);
}

let filteredData_B = data.filter(o => parseDate(o.datetime) > d);

console.log(filteredData_B);

PS When posting, it's helpful to reduce code to the minimum required to demonstrate the issue and ensure it's valid (e.g. the "JSON" in the OP). See How to create a minimal, reproducible example.

RobG
  • 142,382
  • 31
  • 172
  • 209
  • thanks very much, let me read it understand it and I'll get back to you – John Galassi Dec 22 '19 at 14:49
  • I have an additional question: suppose I want to just read ```data[0].datetime``` in order to receive an output in this format```Sat Mar 03 00:00:00 GMT+01:00 2018 ```: what command shall I use? Because if I try to read it with simple ```Logger.log(data[0].datetime);``` all I get is: ```2018-01-31 12:15:11```. What command line should I use if I want the read to produce an output like this one ```Sat Mar 03 00:00:00 GMT+01:00 2018 ``` on the Logger? I hope the question was clear, thanks – John Galassi Dec 22 '19 at 21:18
  • I am trying to use your code, which I kinda like. I read your explanation and found it relieving. The problem is I cant seem to get to work arrow functions within GAS, could you translate this piece of code into normal function syntax? ```// Filter as strings function toISO(d) { let z = n => (n<10? '0':'') + n; return d.getFullYear() + '-' + z(d.getMonth() + 1) + '-' + z(d.getDate()) + ' ' + z(d.getHours()) + ':' + z(d.getMinutes()) + ':' + z(d.getSeconds()); } ``` – John Galassi Dec 23 '19 at 17:55
  • and yes: tomorrow I am going to write a more detailed report, but the point is that the dates I get from json (i.e. the data collected from the api) is in gmt timezone (UTC), while mytimezone is gmt+1. – John Galassi Dec 24 '19 at 03:31
  • 1
    @RobG I think the important context to understand here is that this code is running on Google Apps Script and not in a browser. That is why your otherwise very valid concern about inconsistencies with the built-in Date parser is unlikely to affect this particular question. Neither arrow functions nor `let` work in Apps Script because it's [based on JavaScript 1.6](https://developers.google.com/apps-script/guides/services/#basic_javascript_features). – Diego Dec 25 '19 at 13:06
  • @Diego, thanks in fact I had to change the code a bit. ps how can I shift say 1hr forward the timezone with this code you gave me? ```function parseDate(s) { let b = s.split(/\D/); return new Date(b[0], b[1]-1, b[2], b[3], b[4], b[5]); }```. I've tried with ```b[3]+1``` but obviosly it didn't work. I like it but I miss this bit – John Galassi Dec 25 '19 at 20:44
0

You can easily convert that string into an ISO 8601 formatted date/time string and then create a new Date from it.

/**
 * Parse a datetime value formatted like "2018-01-30 12:16:11" into a JavaScript
 * Date object by converting it into a simple ISO 8601 formatted date time,
 * without a time zone designator. As such, the date returned is in the executing
 * machine's time zone (i.e. project time zone setting).
 * @param {String} datetime
 * @returns {Date}
 */
function parseDatetime(datetime) {
  var dateParts = datetime.split(" ");
  return new Date(dateParts.join("T"));
}

/**
 * Test that parseDatetime() works as expected.
 */
function test_parseDatetime() {
  var datetime = "2018-01-30 12:16:11";
  var date = parseDatetime(datetime);
  Logger.log(date); // Tue Jan 30 12:16:11 GMT+00:00 2018
}

Once you have your dates, you can use the comparison operators just as you wrote out.

Do please be aware that I had set the time zone in my project properties to "(GMT+00:00) GMT (no daylight saving)". The date returned by parseDatetime() will be affected by your project time zone settings. So long as you're consistent though, it shouldn't affect your comparisons.

Your final code could look something like this. I've included two options for filtering: (1) using a for loop and (2) using .filter(). You can see by checking the Logger output that they're equivalent. I also made a modification in this example to the parseDatetime() function to specify a UTC+0 time zone, to do address specific concerns brought up with Date.parse(), but I don't think it's entirely necessary.

function filterOrders() {
  var orders = [
    {btc:0, datetime:"2018-01-30 12:16:11", eur:410.00, fee:0.00, usd:0, id:"***", btc_usd:0.00, type:0},
    {btc:0, eth_eur:692.81, datetime:"2018-02-06 16:45:16", eur:-5.20, fee:0.02, usd:0, eth:0.00750000, id:"***", type:2, order_id:"***"},
    {btc:0, eth_eur:699, datetime:"2018-02-05 17:04:25", eur:5.24, fee:0.02000000, usd:0, eth:-0.00750000, id:"***", type:2, order_id:"***"},
    {btc:0, eth_eur:562.1, datetime:"2018-02-04 17:52:09", eur:-5.62, fee:0.02, usd:0, eth:0.01000000, id:"***", type:2, order_id:"***"},
    {btc:0, eth_eur:567.2, datetime:"2018-02-06 20:24:07", eur:5.67, fee:0.02000000, usd:0, eth:-0.01000000, id:"***", type:2, order_id:"***"},
    {btc:0, datetime:"2018-04-28 17:22:21", eur:0, fee:0.00000000, usd:0, eth:0.38811442, id:"***", btc_usd:0.00, type:0},
    {btc:0, eth_eur:563, datetime:"2018-04-28 17:42:51", eur:218.50, fee:0.55000000, usd:0, eth:-0.38810000, id:"***", type:2, order_id:"***"},
    {btc:0, eth_eur:563, datetime:"2018-04-28 17:47:52", eur:0.01, fee:0.01000000, usd:0, eth:-0.00001442, id:"***", type:2, order_id:"***"},
    {btc:0.01786568, btc_eur:5583.33, datetime:"2018-08-14 12:01:13", eur:-99.75, fee:0.25, usd:0, id:"***", type:2, order_id:"***"}
  ];

  // Select all orders where datetime is greater than April 1, 2018
  var filterDate = new Date("2018-04-01");

  // FOR loop method
  var filteredOrders1 = [];
  for (var i = 0; i < orders.length; i++) {
    var order = orders[i];
    if (parseDatetime(order.datetime) > filterDate) {
      filteredOrders1.push(order);
    }
  }

  // FILTER method
  var filteredOrders2 = orders.filter(function(order) {
    return parseDatetime(order.datetime) > this;
  }, filterDate);

  // Same results? 
  Logger.log(JSON.stringify(filteredOrders1) == JSON.stringify(filteredOrders2)); // true

  Logger.log(JSON.stringify(filteredOrders1));
}

/**
 * Parse a datetime value formatted like "2018-01-30 12:16:11" into a JavaScript
 * Date object by converting it into a simple ISO 8601 formatted date time, with
 * UTC as the time zone.
 * @param {String} datetime
 * @returns {Date}
 */
function parseDatetime(datetime) {
  var dateParts = datetime.split(" ");
  return new Date(dateParts.join("T") + "Z");
}
Diego
  • 9,261
  • 2
  • 19
  • 33
  • 1
    thanks both for your answers, let me try them out and in case ask for clarifications. see you later! – John Galassi Dec 17 '19 at 13:28
  • I encountered two issues with this approach, I hope that you can help me out. First, it seems to me that I cannot directly compare dates even in this format, so a conversion to ```timestamp``` seems necessary anyway in order to be able to ```if (date > lastDate)```. Second, most important: I still cannot filter the set of json data for a specific range of dates. How could I apply your method to data set and filter it, say, for ```dates > a specific date```. Could you show me? thanks – John Galassi Dec 17 '19 at 14:05
  • 1
    @JohnGalassi Yes. I just edited my answer to include an example. You could use [`.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) as I've done, or easily modify it to use a `for` loop. – Diego Dec 17 '19 at 14:24
  • dear @Diego, I have several questions about your code. But, considering that I may have simply not understood your script due to my lack of knowledge in the first place, could you please explain it as possibile as step-by-step? So I am sure I am not missing anything, thanks – John Galassi Dec 17 '19 at 16:28
  • Please **do not** use the built–in parser for parsing strings. See [*Why does Date.parse give incorrect results?*](https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results) – RobG Dec 17 '19 at 20:41
  • @RobG I'm not using `Date.parse()`. I think as long as there's consistency in usage, it shouldn't make much difference for the purpose of _comparisons_. As mentioned in the answer, time zone is coming from the project properties. – Diego Dec 17 '19 at 22:40
  • 1
    @JohnGalassi I updated the example to show how it's done with a for loop. – Diego Dec 17 '19 at 22:53
  • @Diego—I said "*don't use the built–in parser*", which is used by both `Date.parse(string)` and `new Date(string)`. So. `new Date("2018-04-01")` uses the built–in parser, and produces an identical result to `new Date(Date.parse("2018-04-01"))`. It makes a big difference if you parse one string as local and another as UTC, or one parser returns a different result to another parser. It is because results are not consistent that it is strongly recommended to not use the built–in parser. – RobG Dec 18 '19 at 09:35
  • @Diego thanks very much for your support and attention. I've chosen the other answer not because yours was less but simply because it was the one that best suited my script or that simply I could understand better. I hope I can always count on your knowledge in the future. thanks again – John Galassi Dec 18 '19 at 13:07