15

I have a MySQL table, and this table has a DATETIME column named datetime_utc. It is, as you might expect, a date and time in UTC. In my Bookshelf models, I have defined a virtual getter that converts this into ISO 8601 string format using Moment.js. My model looks something like this:

bookshelf.plugin('virtuals');

exports.MyModel = bookshelf.Model.extend({
    tableName : 'my_table',
    idAttribute : 'id',
    virtuals : {
        datetime_iso : {
            get : function () {
                return moment.utc(this.get('datetime_utc')).format();
            }
        }
    }
});

Problem is, when Bookshelf (or the underlying Knex that powers it) sees the DATETIME column, it wraps the value in a new Date(...) before giving it to my code. Since the date's value is in UTC, but the Date constructor assumes the value to be in the server's local non-UTC timezone, I end up with a Date object that has the right date in the wrong timezone. Once Moment starts working on this Date, all the values are off by a fixed number of hours.

I worked around this by looking for Date objects, and decomposing the date components directly into the Moment constructor. But it feels gross:

get : function () {
    var dt = this.get('datetime_utc');

    if (dt instanceof Date) {
        dt = [
            dt.getFullYear(), dt.getMonth(), dt.getDate(),
            dt.getHours(), dt.getMinutes(), dt.getSeconds()
        ];
    }

    return moment.utc(dt).format();
}

Is there a cleaner way to either get a non-wrapped YYYY-MM-DD HH:MM:SS string value from Bookshelf, or a shorthand to create a new Moment object from a Date with the timezone ignored/munged to UTC?

smitelli
  • 6,835
  • 3
  • 31
  • 53

1 Answers1

28

Turns out this is not caused by either Knex or Bookshelf, but rather the underlying node-mysql library. There is a connection property called timezone that will be appended to each DATETIME, DATE, TIMESTAMP, and NEWDATE value before it is parsed into a Date object.

Knex will pass this property through to node-mysql while it is initializing:

require('knex')({
    "client": "mysql",
    "connection": {
        "host": "...",
        "user": "...",
        "password": "...",
        "database": "...",
        "timezone": "UTC"    <-- This is the culprit
    }
});
smitelli
  • 6,835
  • 3
  • 31
  • 53
  • But how about when inserting? I see "where `end_stamp` >= '2016-01-31 16:12:58.615'" which is converting based on the current timezone, rather than UTC... ideas for that? – user1278519 Feb 01 '16 at 08:49
  • 1
    In order to solve the problem, did you add `"timezone": "UTC"` or did you remove it? – Ozymandias Mar 09 '17 at 21:45
  • 1
    I encountered the same problem. To solve it, you add "timezone": "UTC"... – BlackSoil Apr 04 '17 at 11:03