Sublime Forum

Automatically updated Timestamp?

#1

Hello,

Vim user moving to ST2 here. I was using a very simple Vim plugin that would add a time stamp next to “Created: TIMESTAMP” and another (updated automatically every time the file is changed) next to “Last Modified: TIMESTAMP”. The script is here: is.gd/LhEgJY

Has anybody seen similar functionality for ST2, and/or whether it would be possible to implement such a thing?

Thanks.

0 Likes

#2

Such a plugin is definitely possible, but I don’t know that one exists.

0 Likes

#3

So you can write a plugin that performs an action on saving a file?

0 Likes

#4

Yeah, declare an on_pre_save function and you can do whatever you want.

0 Likes

#5

maybe something like that…

#! python
"""
TextCommand:
    insert_timetamp
EventListener:
    on_pre_save: update_timestamp
"""
import datetime
import sublime
import sublime_plugin

TIMESTAMP_PATTERN = 'FILE_CHANGED_ON\\s*=\\s*\'"]201[0-9]-\\d+-\\d+\\s+\\d+:\\d+:\\d+(\\.\\d+)?\'"]'

class InsertTimestampCommand (sublime_plugin.TextCommand):
    """
    Replace selection with a current timestamp in ISO format.
    """
    def run (self, edit, **args):
        timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.view.replace (edit, self.view.sel()[0], timestamp)

class UpdateTimestampListener (sublime_plugin.EventListener):
    """
    Search for a timestamp to update before saving the file.
    Maybe to simple.
    """
    def on_pre_save (self, view):
        region = view.find (TIMESTAMP_PATTERN, 1)
        if region :
            timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            replacement = 'FILE_CHANGED_ON = "%s"' % timestamp
            edit = view.begin_edit()
            view.replace (edit, region, replacement)
            view.end_edit (edit)
0 Likes

#6

Thanks, guys.

0 Likes

#7

I have a similar plugin which inserts the hash of the file,

github.com/mmmpie/insert-hash-on-save

0 Likes

#8

Here’s a script I wrote for this (Code adapted from hintermair’s answer on:
https://stackoverflow.com/questions/28032780/automatic-update-date-in-sublime-by-save):

""" Command (with key-binding ctrl + s) that automatically updates
the date-last-modified section in a file with each save

Only thing the user has to change (if they so wish) is the variables
Header_Format and/or Date_Format """


import sublime, sublime_plugin
import time

class AddDateCommand(sublime_plugin.TextCommand):
    def run(self, args):
        content = self.view.substr(sublime.Region(0, self.view.size()))

        """Can change Header_Format if you so wish.
        Don't worry about whether you have to add an extra space 
        at the end of 'Header_Format'; the rest of the code already
        takes care of that. """
        Header_Format = 'Date Last Edited:'

        begin = content.find(Header_Format)
        if begin == -1:
            return

        """ If you want your date printed out in a different format
            (e.g. YYYY/mm/dd instead of dd mm YYYY), feel free to 
            play around with the parameters in strftime() """
        Date_Format = time.strftime("%d %b %Y %I:%M%p")

        # To update time/date last modified, command looks for the beginning
        # of the date-string (e.g. "D" if it's "Date Last Edited: 26 Jun 2017 05:38PM") and 
        # the end of the date-string (e.g. "M" if it's "Date Last Edited: 26 Jun 2017 05:38PM")
        # and replaces the whole string with an identical string (only change being the time/date)

        # "+ 1" is to take into account the space between Header_Format and Date_Format
        length_of_line = len(Header_Format) + 1 + len(Date_Format)
        end = begin + length_of_line

        target_region = sublime.Region(begin, end)
        self.view.sel().clear()
        self.view.sel().add(target_region)

        # The " " adds a space between Header_Format and Date_Format
        self.view.run_command("insert_snippet", { "contents": Header_Format + " " + Date_Format } )
    

# Command does 2 things; update the date, and save the file
class DateAndSaveCommand(sublime_plugin.WindowCommand):
    def run(self):
        self.window.run_command("add_date")
        self.window.run_command("save")
  1. Create a file called add_date.py and copy-paste the above-code into it.

  2. Save the file in Sublime Text -> Packages -> User

  3. Go to Sublime Text -> Preferences -> Key Bindings and add the below into User:

    [
    {“keys”: [“ctrl+s”], “command”: “date_and_save” }
    ]

  4. Now the file you’re working on should have the date auto-updated everytime you hit ctrl + s

So it will turn a file like this:

// Author: Me
// Date Last Edited: 26 Jun 2017 07:08PM
// Generic Program Description

#include <stdio.h>

int main (void) {
    //Some stuff happens here
	return 0;
}

into something like this after hitting ctrl + s :

// Author: Me
// Date Last Edited: 26 Jun 2017 07:39PM
// Generic Program Description

#include <stdio.h>

int main (void) {
    //Some stuff happens here
	return 0;
}

Should also work with other languages

NOTE: You DO need some date string in the file to begin with for the formatting to stay the same. If u try hitting ctrl + s on something like this:

// Author: Me
// Date Last Edited: 
// Generic Program Description

#include <stdio.h>

int main (void) {
	//Some stuff happens here
	return 0;
}

You’ll get something like this:

// Author: Me
// Date Last Edited: 26 Jun 2017 07:49PM Description

#include <stdio.h>

int main (void) {
	//Some stuff happens here
	return 0;
}

But at least you’ll only have to add the date format once (which I just use a snippet for anyways).

1 Like

#9

Edit: Improved the add_date.py code so that it wouldn’t move the view too much (which was annoying). The updated code is:

# -*- coding: utf-8 -*-

"""Key-binding-activated command that automatically updates the
date-last-modified section in a file with each save.

Only thing the user has to change (if they so wish) are the variables
Header_Format and/or Date_Format.

Code adapted from hintermair's answer on: https://stackoverflow.com/questions/28032780/automatic-update-date-in-sublime-by-save

NOTE: Only change the variables Header_Format and/or Date_Format.
The rest of the code should remain untouched.
"""

import sublime
import sublime_plugin
import time


class AddDateCommand(sublime_plugin.TextCommand):
    """To update time/date last modified, command looks for the beginning
    of the date-line (e.g. "L" if it's "// Last Edited: 26 Jun 2017
    05:38PM") and the end of the date-line (e.g. "M" if it's "// Last Edited:
    26 Jun 2017 05:38PM") and replaces the whole string with an identical
    string (only change being the time/date).
    """

    def run(self, args):
        content = self.view.substr(sublime.Region(0, self.view.size()))
        original_position = self.view.sel()[0]

        # Can change Header_Format to a different title if you so wish
        # (you don't have to add an extra space at the end of Header_Format;
        # the rest of the code already takes care of that for you).
        Header_Format = "Last Edited:"

        begin = content.find(Header_Format)

        # If there's no date-line in the file, nothing happens.
        if begin == -1:
            return
        # else:

        # If you want your date printed out in a different format
        # (e.g. YYYY/mm/dd instead of dd mm YYYY),
        # feel free to rearrange/play around with the parameters in strftime().
        Date_Format = time.strftime("%d %b %Y %I:%M%p")

        # The " " adds a space between Header_Format and Date_Format.
        date_line = Header_Format + " " + Date_Format
        end = begin + len(date_line)

        # Move the cursor to the region in the file where the header/date are.
        target_region = sublime.Region(begin, end)
        self.view.sel().clear()
        self.view.sel().add(target_region)

        # Update the date.
        self.view.run_command("insert_snippet", { "contents": date_line })

        # Move the cursor back to the original position the user was typing at
        # (so the screen doesn't move away too much from the user).
        self.view.sel().clear()
        self.view.sel().add(original_position)
        self.view.show(original_position)


class DateAndSaveCommand(sublime_plugin.WindowCommand):
    """Command does 2 things; save the file, and update the date-line."""

    def run(self):
        self.window.run_command("save")
        self.window.run_command("add_date")
        # We save again here, since the change-of-date itself needs to be saved
        self.window.run_command("save")

All the other info in my reply still remains the same.

2 Likes

#10

Thanks Saeed. It worked like a charm.

Only update for Mac users, you may need to change Key Bindings to:

[
  {"keys": ["ctrl+s"], "command": "date_and_save" },
  {"keys": ["super+s"], "command": "date_and_save" }
]
0 Likes

#11

Hi there,

I have been using the above plugin as posted by SaeedBaig in Nov 2017 for some time. Unfortunately it stopped working after the latest Sublime update to version 4121. The date still gets changed correctly, but the file doesn’t save anymore.

The plugin was developed for an earlier version of Sublime, but I’m pretty sure that it worked under Sublime 4 before (I’m not 100% sure though that I already had Sublime 4 installed before the 4121 update, but 99.9%, because I usually install all program updates once they pop up. I just didn’t notice the major version switch from 3 to 4 earlier this year… :o) ).

I’m not into plugin scripting, but I hope someone can help me. Thanks in advance.

0 Likes

#12

That’s kinda interesting because as written it looks like that should work just fine but indeed does not. That probably bears more inspection, but in a pinch you can try replacing the last line of the plugin with this:

sublime.set_timeout(lambda: self.window.run_command("save"), 1)

That is, instead of executing the save command directly, call it after a very slight delay.

0 Likes

Run_command("save") leaves dirty
#13

Thank you very much, this workaround seems to work.

I had to increase the amount of time though, beacuse with 1ms it only worked occasionally. I set it to 100 with no noticeable delay.

I will keep an eye on this thread. Maybe someone comes up with a “proper” solution - or the problem is due to a Sublime bug that will be fixed in the future, so one day the original code will work again.

0 Likes

#14

Ah yes, I was going to mention that and forgot (dang work interruptions).

In the general case, any time there’s a time delay involved in the solution to a problem, it’s a “not great” solution because that sort of thing is bound to be variable. In this context the only time delay that would be considered safe is 0, but that doesn’t work here.

In any case, my recommendation in general (biased though it is) would be to replace the entire plugin and any associated key bindings you might have created as outlined by posts above with this:

import sublime
import sublime_plugin
import time

class UpdateLastEditedDateCommand(sublime_plugin.TextCommand):
    """
    Check the current file for the first line that contains the provided
    header, and if found update it to include the current date and time
    as formatted by the given date format.
    """
    def run(self, edit, hdr="Last Edited: ", fmt="%d %b %Y %I:%M%p"):
        span = self.view.find(f'{hdr}.*$', 0)
        if span is not None:
            self.view.replace(edit, span, f"{hdr}{time.strftime(fmt)}")

class UpdateLastSavedEvent(sublime_plugin.EventListener):
    """
    Update the last edited time in a file every time it's saved.
    """
    def on_pre_save(self, view):
        view.run_command('update_last_edited_date')

This creates a command named update_last_edited_date that will find the first instance of the date header and replace it all in one shot in code that is much less verbose than the original (largely because the original was using the wrong kind of command to do this in the first place). The header to look for and the date format are arguments to the command, so the defaults will be used but you can easily change it while executing the command if desired (even having multiple keys bound that do it in different ways, etc)

The event listener will run the command every time the file is saving, and change the content of the file prior to it being saved to disk.

Thus taken together you don’t need any special key bindings, and the natural save (or even if you save_all using the menu item or a bound key) will cause all saved files to update automagically.

Should you wish for this to be bound to a separate save action key so that you can trigger it as you want rather than automatically, comment out or remove the event listener class and add a key binding like this (changing the key as you like):

    { "keys": ["super+t"], "command": "chain",
       "args": {
          "commands": [
             {"command": "update_last_edited_date"},
             {"command": "save"},
          ]
       }
    },

The built in chain command will execute all the commands you give it one after the other, so here it will update the file date and then save the file.

3 Likes

#15

This really looks like the proper solution I hoped for. And it works like a charm. :smiley:

Thank you for taking your time.

(Well, at first it didn’t work, but stupid me had saved your code in a file before disabling the old plugin. I leave it to you to guess what happened…:laughing:)

1 Like