Flask-Moment: Flask and Jinja2 Integration with moment.js

Posted by
on under

If you followed my Mega-Tutorial articles you may remember that I dedicated an entire article to the topic of rendering dates and times.

I needed to revisit this problem for my upcoming book, and this time I decided to package this functionality in an extension. That is how Flask-Moment was born.

Installation

As usual, you can install this extension with pip:

$ pip install flask-moment

Initialization and Configuration

Class Moment must be created to initialize the extension:

from flask import Flask
from flask.ext.moment import Moment
app = Flask(__name__)
moment = Moment(app)

The delayed initialization style with init_app() is also supported.

After that you have to include jquery.js and moment.js in your template(s). The template now has helper functions to make this easy:

<html>
    <head>
        <title>Flask-Moment example app</title>
        {{ moment.include_jquery() }}
        {{ moment.include_moment() }}
    </head>
    <body>
    ...
    </body>
</html>

Note that you can include the scripts at the bottom of the page as well.

Both javascript files are imported from a CDN. If you want to use a specific version of these files you can pass the full version number as a string argument into each function. If you already have jquery included in your page then you can omit the include_jquery() line, but note that the include_moment() line must be present.

If you work in a language that is not English, then you can also set the language in the <head> section:

        {{ moment.lang("es") }}

Usage

The usage in Jinja2 templates tries to mimic the style of the original APIs in Javascript.

For example, this will render the current time with a custom format string:

<p>The current date and time is: {{ moment().format('MMMM Do YYYY, h:mm:ss a') }}.</p>

If you want to render an instance of datetime instead of the current time then pass it as an argument to the moment() constructor:

<p>The current date and time is: {{ moment(timestamp).format('MMMM Do YYYY, h:mm:ss a') }}.</p>

Note that the timestamp argument must be a "naive" datetime instance expressed in UTC (see the datetime documentation for a discussion on naive date and time objects).

The rendered time will always be in the client's local time.

These are the render styles that are supported:

  • format(format_string): custom format.
  • fromNow(no_suffix = False): relative to current time (i.e. "N minutes ago", "in N hours", etc.).
  • fromTime(another_timesatmp, no_suffix = False): relative to the given timestamp.
  • calendar(): another format relative to current time ('last Monday 3PM", "Tomorrow 1:30AM", etc.).
  • valueOf(): number of milliseconds since Unix Epoch.
  • unix(): number of seconds since Unix Epoch.

The rendered dates and times can be refreshed periodically by passing a refresh = True argument to any of the above functions. This is useful for the relative formats, because they are updated as time passes. The refresh interval is fixed at one minute.

Conclusion

I hope you find this little extension useful. The source code is available on github, and a demo application is included.

If you have any ideas for improvements let me know below in the comments.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

21 comments
  • #1 Collin said

    Great addition! I've spent the last day figuring out how to use python's datetime library, ended up having to get pytz. This looks a lot more intuitive.

  • #2 dowlf said

    Miguel, thanks for another awesome contribution to the Flask community!

  • #3 Sam said

    I may have this wrong but for performance reasons isn't it better to load scripts last? i.e just before </body> rather than in <head>?

  • #4 Miguel Grinberg said

    @Sam: Yes, loading the scripts last seems to be preferred these days, because the loading happens after the page has been displayed. I will add a note to that effect above.

  • #5 Nedda Kaltcheva said

    Thank you for the extension, added it to our project today - very handy and a lot prettier than writing custom Jinja filters!

  • #6 Bruce said

    Hi Miguel,

    I've been adopting Flask-Moment into my application and I've been happy with how easily it works. However, I have just transitioned over to using AJAX to load some templates, and the templates that are loaded into DOM elements don't render the {{ moment }} tag - they are rendered without the base template, which is where moment has been included (because I don't want to load the main template into the DOM element - just the template snippet).

    Say you were planning to use this in a comment that you post and then load with AJAX calls - what is the extra step I'm missing that will ensure moment will render correctly? I've checked and if I use it in any page that isn't loaded with ajax and extends the base template it is fine. One workaround I've come up with is to have all templates extend an "includes" template that contains only the include_moment loader, but that seems like an ugly solution to me.

  • #7 Miguel Grinberg said

    @Bruce: the example app in the Flask-Moment repository on github has an Ajax example.

  • #8 Nou said

    I'm developing an extranet, and I'm using Flask as framework, I'm using pycharm as IDE ... I've installed flask and the plugin required for my application, I've installed flask-moment and flask-pagedown with pip but in my init.py there are errors mentioned that they does't exist

  • #9 Miguel Grinberg said

    @Nou: what exact errors do you get?

  • #10 Ryan said

    I'm a python/flask beginner, so go easy on me...
    I have a mysql table that I display in a jinja for loop such that:
    {% for data in items %}
    ...

    <td> {{data.timeIn}} </td>

    ...
    {% endfor %}

    where data.timeIn gets displayed as UTC

    How do I implement moment.js to present data.timeIn in the current time zone, (for me EST), assuming I have followed your install directions correctly?

    I tried <td> {{moment(data.timeIn)}}</td> and this is what gets displayed in the table column:
    <flask_moment._moment object at 0x04231A90>

    I feel like I'm close, but just not quite there. I haven't had any luck trying to use pytz or dateutil =/

  • #11 Miguel Grinberg said

    @Ryan: moment.js displays times in the local timezone of the client by default. I suspect your timestamp is incorrect.

  • #12 Andrey said

    Hello, Miguel.

    I am studying Flask. In my first project I use Flask-Moment. It works fine in templates. But I have a small problem and need some help.

    I have a separate file _macros.html and a bunch of macro in it. In my templates I use macros from that file importing them. When I write in templates something like {{ moment(comment.timestamp).format('LLL') }} - it works and I get what I need. But when I write the same line in _macros.html and then import macro with this line I get an error "jinja2.exceptions.UndefinedError: 'moment' is undefined".

    It would be wonderful to get any idea how to use moment inside a separate html-file with macros which does not "extends" anything.

    Thank you in advance.

  • #13 Miguel Grinberg said

    @Andrey: probably a good idea that you post the question on Stack Overflow, including a runnable example I can try here.

  • #14 Brian Wilson said

    The example runs great. When I try to plug the code into the examples in your book which integrate with bootstrap, nothing renders. "The current time is ." I realize this is a fundamental gap in my understanding but am still frustrated because I don't know where the gap is! :-) I can view the page source in each case and see the same span tags wrapping the same formatted time. I don't understand what is filtering out the results. I am guessing it's CSS? Your example css works, bootstrap css does not??? Feeling clueless or like you left out a bit of crucial information. Love the book. Thanks -- Brian

  • #15 Miguel Grinberg said

    @Brian: You may need to debug this a bit more. Are there any errors in the browser's console, for example?

  • #16 transreductionist said

    @Brian @Miguel The reason the date-time is not being rendered is that:

    <span class="flask-moment" data-timestamp="2016-05-28T17:47:10Z" data-format="format('LLL')" data-refresh="0" style="display: none">
    2016-05-28T17:47:10Z
    </span>

    has the display property set to none.

  • #17 transreductionist said

    @brian @miguel The issue is that in Flask Web Development the file index.html is not explicitly shown to extend base.html. Once this is done the dates are rendered in the template.

  • #18 ujang said

    how can i convert unix millisecond? when i use {{moment(1467380959000).format('DD/MM/YYYY HH:mm:ss')}}
    it give me an error: AttributeError: 'int' object has no attribute 'strftime'.

  • #19 Miguel Grinberg said

    @ujang: the moment class takes a DateTime instance as argument. If you need to construct a DateTime from a unix timestamp, see this: https://docs.python.org/2/library/datetime.html#datetime.date.fromtimestamp

  • #20 Andrey Glazkov said

    Hello Miguel! Thanks for great project!

    I want use flask-moment at title attribute of span tag but have a trouble.
    Example of code:
    <span title="{{ moment(user.last_seen_utc).format('DD.MM.YYYY, HH:mm') }}">
    {{ moment(user.last_seen_utc).fromNow() }}
    </span>

    The problem is that it generates mess of "span" tags :(

    Could you suggest any solution?

  • #21 Miguel Grinberg said

    @Andrey: use JavaScript to set your attribute. This package does not provide the option to set attributes.

Leave a Comment