2

I would like to convert a UTC TimeDate stamp string into an integer value of milliseconds (might need to be a 64-bit quantity), so that it takes up less space when stored in a mySQL database column. This UTC string is being generated from another library, and I store it as a kind of per-user GUID.

Can datetime or dateutil convert this into a single integer value (like "milliseconds since epoch")? Or do I need to do that myself?

Parsing using this approach:

myDateTime = dateutil.parser.parse("2015-06-27T02:10:05.653000Z")
print("Parsed datetime String is {0}, ordinal value is {1}".format(myDateTime, myDateTime.toordinal()))

Gives the output:

Parsed datetime String is 2015-06-27 02:10:05.652999+00:00, ordinal value is 735776

…which only gives an ordinal value for the date. Further, if I have a time with an integer 653 milliseconds, then I want that parsed object to know it has 653 milliseconds, not 652999.

SMGreenfield
  • 1,680
  • 19
  • 35
  • Why do you think that storing an integer or milliseconds would require less space than the native `datetime` format in the database? – Gordon Linoff Jun 28 '15 at 01:41
  • 2
    You could do something like `int(datetime.strptime(time_string, your_format).strftime('%s'))*1e3`. This would be a float in milliseconds since epoch, which you could cast as int if you wanted. – Scott Jun 28 '15 at 01:44
  • @GordonLinoff - I mean less space than the STRING representation. – SMGreenfield Jun 28 '15 at 01:46
  • 1
    @SMGreenfield . . . That is not how date/time formats work in the database. Use the native formats. – Gordon Linoff Jun 28 '15 at 01:47
  • @Scott -- Your suggestion was very close, and exactly the approach. Here's the precise syntax I used: int(Decimal(datetime.datetime.strptime("2015-06-27T02:10:05.653000Z", "%Y-%m-%dT%H:%M:%S.%fZ").strftime('%s.%f'))*1000)) I'm assuming you meant multiplying by 3e8 (1000), not 1e3 (483).... – SMGreenfield Jun 28 '15 at 02:39
  • @GordonLinoff - Although scott's suggestion led to syntax that gets me a number like 1435396205653 -- which is the UTC datetime in milliseconds since epoch -- I'm curious how the UTC datetime string -- with milliseconds -- would be stored in a native mysql datetime format. Would I store it in the row's datetime column with insert or update/set syntax passing the UTC datetime as a quoted string, or would it first need to be wrapped in another SQL function? – SMGreenfield Jun 28 '15 at 03:00
  • I would like to comment on the downgrade vote. My 30 years of C/C++ programming only goes so far when I have 30 days of python/mysql experience. I DID research this question for several hours before electing to post. Stackoverflow is an amazing resource -- as are the plethora of python/mysql docs scattered across the Internet. It's always preferable to see an example where someone has done something similar. In my case, it was critical to preserve milliseconds without separating the time and date. I didn't see similar posts. My thanks to those who posted replies. – SMGreenfield Jun 28 '15 at 03:17
  • @SMGreenfield 1e3 is 1x10^3. Python will give you a float 1000.0. I believe it's the same in C++. Regarding the downvote, I agree your question is valid. Drive by downvoting with no comments is annoying. – Scott Jun 28 '15 at 03:37
  • @GordonLinoff -- So it does seem I can UPDATE myTable SET myDateTimeField = '2015-06-27T02:10:05.653000Z'. Researching the storage requirements of the mysql datetime data type, it uses 8 bytes. So you're (of course) correct -- I was just trying to avoid storing a datetime as string and/or losing the milliseconds. – SMGreenfield Jun 28 '15 at 03:40
  • @Scott -- thanks for clarifying. Amazingly, after all that C++ experience -- I've never used the 'e' notation! – SMGreenfield Jun 28 '15 at 03:43
  • minor addendum: to support millisecond (or microsecond) precision, I needed to use DATETIME(6) data type... – SMGreenfield Jun 28 '15 at 04:15

3 Answers3

6

[Edited following suggestion in the comments]

Using Ben Alpert's answer to How can I convert a datetime object to milliseconds since epoch (unix time) in Python we can do the following:

from datetime import datetime
def unix_time(dt):
    epoch = datetime.utcfromtimestamp(0)
    delta = dt - epoch
    return delta.total_seconds()

def unix_time_millis(dt):
    return int(unix_time(dt) * 1000)

a = datetime.strptime("2015-06-27T02:10:05.653000Z", "%Y-%m-%dT%H:%M:%S.%fZ")
unix_time_millis(a)

returns:

1435371005653

which is equivalent to: Sat, 27 Jun 2015 02:10:05 GMT (as expected)

We can also use datetime's .strftime('%s') to get unix time, even milliseconds using the following (but this is not advised):

from decimal import Decimal

int(Decimal(datetime.strptime("2015-06-27T02:10:05.653000Z", "%Y-%m-%dT%H:%M:%S.%fZ").strftime('%s.%f'))*1000)

returns:

1435396205653

equivalent to: Sat, 27 Jun 2015 09:10:05 GMT (on my mac in San Diego; Note: this is 7 hours off what we may have expected).

The cause of the error is described by J.F. Sebastian in the comments of the link above and in this answer regarding .strftime('%s') behavior. J.F. Sebastian points out that "it is not supported, it is not portable, it may silently produce a wrong result for an aware datetime object, it fails if input is in UTC (as in the question) but local timezone is not UTC"

Community
  • 1
  • 1
Scott
  • 6,089
  • 4
  • 34
  • 51
  • I, too, was puzzled why there wasn't a lowercase %s listed in the strftime() docs, but when it appeared to work, I figured someone else must know better. It's still important to note that with the Z parsing string other UTC with offsets wouldn't parse, but my use case SPECIFICALLY was limited to Z (Zulu time). I really appreciate your effort on this! Thanks! – SMGreenfield Jun 28 '15 at 17:49
  • both questions you've linked have explicit comments on why you should not use `.strftime('%s')` – jfs Jun 28 '15 at 19:57
  • @J.F.Sebastian I have actually used `.strftime('%s')` in certain cases where timezone was not a factor and therefore was not aware of the problem **until** I read the linked posts and comments (specifically your comment). Do you think there is an added benefit to explicitly stating the problems with `.strftime('%s')` or let users follow the links? – Scott Jun 28 '15 at 20:05
  • Just put the correct working solution at the top of the answer. At the bottom of the answer you could mention why `.strftime('%s')` should not be used (or just link to my comment). – jfs Jun 28 '15 at 20:14
  • @J.F.Sebastian Thanks for the suggestion. Edited following your advice. – Scott Jun 28 '15 at 21:35
1

There are two parts:

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • I've read both posts (quite a discussion!) Assuming a ZULU-ONLY UTC (my use case), is the above approach superior to my edit which eliminates strftime? Or just more elegant because it accounts for parsing timezone offsets? – SMGreenfield Jun 28 '15 at 20:29
  • @SMGreenfield: if we ignore the bare `except:` and `long` usage that you should not do then there are only minor differences (validation, rounding behavior, supported python versions). btw, timezone offsets are not parsed; utc time is assumed (zero utc offset). – jfs Jun 28 '15 at 20:54
  • That is an incredibly detailed and well referenced answer you give in your link. Will be looking through that for a while. – Scott Jun 28 '15 at 21:45
0

Both Scott and GordonLinoff provided excellent help in solving my issue. I'm adding the answer for completeness.

Python code to convert UTC datetime string to milliseconds since epoch:

EDITED TO ELIMINATE strftime:

from datetime import datetime

def convert_UTC_zulu_string_to_milliseconds_since_epoch(myUTCzuluString):
    try:
        dt_unix = datetime.strptime(myUTCzuluString, "%Y-%m-%dT%H:%M:%S.%fZ")
        epoch = datetime.utcfromtimestamp(0)
        delta = dt_unix - epoch
        millisecondsSinceEpoch = long(delta.total_seconds() * 1000)

    except:
        millisecondsSinceEpoch = 0L

    return millisecondsSinceEpoch


myUTCzuluString = "2015-06-27T02:10:05.653000Z"
millisecondsSinceEpoch = convert_UTC_zulu_string_to_milliseconds_since_epoch(myUTCzuluString)
print("Milliseconds since epoch: {0}".format(millisecondsSinceEpoch))

ALSO: mysql WILL accept a datetime value with milliseconds/microseconds directly from a string IF I've defined the column as a DATETIME(6) datatype:

UPDATE myTable SET myDateTimeField = '2015-06-27T02:10:05.653000Z'

Note that including the "Z" at the end of the UTC datetime string results in a truncation warning from mysql.

I could not determine if the added precision of DATETIME(6) over DATETIME resulted in mysql's InnoDB engine using more than 8 bytes, which was one of the initial reasons for my researching the issue.

SMGreenfield
  • 1,680
  • 19
  • 35
  • What I thought was the way to go (and have personally used) turns out to have possible errors. So, unless you live along gmt you might not want to use %s. – Scott Jun 28 '15 at 05:35
  • 1
    You may already know this but here is some discussion on mysql fractional time/dates http://stackoverflow.com/a/26299379/4663466 and there is a mysql built in `UNIX_TIMESTAMP()` http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_unix-timestamp – Scott Jun 28 '15 at 05:50
  • don't use `.strftime('%s')` with utc input unless your local timezone is utc. – jfs Jun 28 '15 at 19:54
  • @J.F.Sebastian -- thanks -- as my post stated, my specific use case was UTC-only. We also re-wrote the example to NOT use .strftime. – SMGreenfield Jun 28 '15 at 19:59
  • 1
    It might be worthwhile looking into just inserting your diatomite objects with format `%Y-%m-%dT%H:%M:%S.%fZ` using the appropriate mysql DATE type and when you need unix time in milliseconds, using built in mysql `UNIX_TIMESTAMP(date)` (assuming it deals with fractional seconds and outputs to milliseconds). I have no experience in this so investigate it, but this may be faster than conversion to unix time before insert and possibly conversion to human readable after fetching. – Scott Jun 28 '15 at 20:12
  • 1
    @Scott -- before taking that approach, I need to investigate 1) why a mysql DATETIME(6) type gives a warning if the time string ends in 'Z', and 2) how many bytes a DATETIME(6) uses. – SMGreenfield Jun 28 '15 at 20:32