Date Paster

Paste today’s date, time, or your favorite formats anywhere—fast. Perfect for notes, tickets, emails, and logs.

Download for Windows (.exe)

Source Code


import sys, os, threading, datetime, string, json, keyboard
from PyQt5 import QtWidgets, QtCore, QtGui
from PIL import Image, ImageDraw, ImageFont
from PyQt5.QtWidgets import (
    QApplication, QWidget, QFrame, QTabWidget, QCheckBox, QComboBox, QLineEdit,
    QLabel, QPushButton, QMessageBox, QGraphicsDropShadowEffect, QVBoxLayout,
    QHBoxLayout, QScrollArea
)
from pystray import Icon as TrayIcon, Menu as TrayMenu, MenuItem as TrayMenuItem

def create_icon():
    img = Image.new('RGB', (64, 64), 'white')
    draw = ImageDraw.Draw(img)
    try:
        font = ImageFont.truetype("seguiemj.ttf", 32)
    except:
        font = ImageFont.load_default()
    draw.text((10, 10), "📋", font=font, fill='black')
    return img

class DatePasterApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setFixedSize(600, 580)
        self.settings_file = os.path.join(os.path.expanduser("~"), ".datepaster_settings.json")
        self.custom_hotkeys = []
        self.initials = ""
        self.initials_position = "after"
        self.load_settings()
        self.container = QFrame(self)
        self.container.setGeometry(0, 0, 600, 580)
        self.container.setStyleSheet("QFrame{background:#fff;border-radius:18px}")
        shadow = QGraphicsDropShadowEffect(self.container)
        shadow.setBlurRadius(15); shadow.setOffset(0,0)
        shadow.setColor(QtGui.QColor(0,0,0,60))
        self.container.setGraphicsEffect(shadow)
        layout = QVBoxLayout(self.container)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        title_bar = QHBoxLayout()
        title_bar.setContentsMargins(10, 10, 10, 5)
        title = QLabel("Date Paster")
        title.setStyleSheet("font-size:14pt;font-weight:bold;color:#333")
        close_btn = QPushButton("✕")
        close_btn.setFixedSize(20, 20)
        close_btn.setStyleSheet("QPushButton{border:none;background:transparent;font-size:12pt;color:#333}")
        close_btn.clicked.connect(self.hide)
        title_bar.addWidget(title)
        title_bar.addStretch()
        title_bar.addWidget(close_btn)
        layout.addLayout(title_bar)
        self.tabs = QTabWidget()
        self.tabs.setStyleSheet("""
            QTabWidget::pane {
                background: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 #fafbfd, stop:1 #e8ecf0
                );
                border: 1px solid #d3d9e4;
                border-radius: 12px;
                margin-top: 36px;
                padding: 16px;
            }
            QTabBar {
                background: transparent;
                qproperty-expanding: false;
                spacing: 8px;
                padding-left: 12px;
            }
            QTabBar::tab {
                background: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 #eff3f8, stop:1 #dce3eb
                );
                color: #5a5f65;
                font: bold 10pt "Segoe UI";
                padding: 8px 20px;
                margin-top: 12px;
                border: none;
                border-top-left-radius: 8px;
                border-top-right-radius: 8px;
                min-width: 140px;
                min-height: 36px;
            }
            QTabBar::tab:hover {
                background: #ffffff;
                color: #0078d7;
            }
            QTabBar::tab:!selected {
                margin-top: 16px;
            }
            QTabBar::tab:selected {
                background: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 #ffffff, stop:1 #f7f9fb
                );
                color: #0078d7;
                margin-top: 0px;
                border-bottom: 3px solid #0078d7;
            }
            QPushButton {
                background-color: #5BB6FF;
                color: grey;
                border-radius: 6px;
                padding: 6px 12px;
                border: none;
            }
            QPushButton:hover {
                background-color: #A8D8FF;
            }
            QPushButton:pressed {
                background-color: #82A9C8;
            }
            QLineEdit {
                background: #f5f5f5;
                border: 1px solid #cccccc;
                border-radius: 6px;
                padding: 4px 8px;
            }
            QLineEdit:focus {
                border-color: #0078d7;
            }
            QComboBox {
                background: #f5f5f5;
                border: 1px solid #cccccc;
                border-radius: 6px;
                padding: 4px 8px;
                min-height: 28px;
            }
            QComboBox:hover {
                border-color: #0078d7;
            }
            QCheckBox {
                spacing: 6px;
            }
            QCheckBox::indicator {
                width: 16px;
                height: 16px;
                border: 1px solid #888888;
                border-radius: 4px;
                background: #eeeeee;
            }
            QCheckBox::indicator:checked {
                background: #0078d7;
            }
            QTableWidget {
                background: #ffffff;
                gridline-color: #e1e4e8;
                alternate-background-color: #f9fafb;
            }
            QHeaderView::section {
                background: #e4e7ec;
                padding: 6px;
                border: none;
                font-weight: bold;
            }
            QScrollArea QPushButton {
                background: transparent;
                color: #333333;
                border: none;
                padding: 0 4px;
                font-size: 14pt;
            }
        """)
        layout.addWidget(self.tabs)
        self.date_tab = QWidget()
        self.build_date_tab()
        self.tabs.addTab(self.date_tab, "Date Hotkey")
        self.custom_tab = QWidget()
        self.build_custom_tab()
        self.tabs.addTab(self.custom_tab, "Custom Hotkeys")
        self.setup_tray()
        self.update_date_hotkey()
        self.start_watchdog()
        self._drag_pos = None

    def build_date_tab(self):
        tab    = self.date_tab
        layout = QVBoxLayout(tab)
        layout.setContentsMargins(10, 0, 10, 10)
        layout.setSpacing(8)
        mod_layout = QHBoxLayout()
        self.ctrl_cb  = QCheckBox("Ctrl");  mod_layout.addWidget(self.ctrl_cb)
        self.shift_cb = QCheckBox("Shift"); mod_layout.addWidget(self.shift_cb)
        self.alt_cb   = QCheckBox("Alt");   mod_layout.addWidget(self.alt_cb)
        self.win_cb   = QCheckBox("Win");   mod_layout.addWidget(self.win_cb)
        layout.addLayout(mod_layout)
        row = QHBoxLayout()
        row.addWidget(QLabel("Key:"))
        self.key_dropdown = QComboBox()
        self.key_dropdown.setFixedHeight(24)
        for c in string.ascii_uppercase:
            self.key_dropdown.addItem(c, c.lower())
        for d in map(str, range(10)):
            self.key_dropdown.addItem(d, d)
        for f in range(1, 13):
            self.key_dropdown.addItem(f"F{f}", f"f{f}")
        row.addWidget(self.key_dropdown)
        layout.addLayout(row)
        self.hotkey_display = QLineEdit()
        self.hotkey_display.setReadOnly(True)
        self.hotkey_display.setFixedHeight(28)
        layout.addWidget(self.hotkey_display)
        layout.addWidget(QLabel("Select Date/Time Format:"))
        self.format_dropdown = QComboBox()
        self.format_dropdown.setFixedHeight(28)
        fmts = [
            ("YYYY-MM-DD","%Y-%m-%d"),("MM/DD/YYYY","%m/%d/%Y"),("DD-MM-YYYY","%d-%m-%Y"),
            ("Month D, YYYY","%B %d, %Y"),("HH:MM","%H:%M"),("HH:MM:SS","%H:%M:%S"),
            ("YYYY-MM-DD HH:MM","%Y-%m-%d %H:%M"),("YYYY-MM-DD HH:MM:SS","%Y-%m-%d %H:%M:%S"),
            ("Month D, YYYY HH:MM","%B %d, %Y %H:%M")
        ]
        for lbl, d in fmts:
            self.format_dropdown.addItem(lbl, d)
        layout.addWidget(self.format_dropdown)
        layout.addWidget(QLabel("Initials (optional):"))
        self.initials_input = QLineEdit()
        self.initials_input.setPlaceholderText("Enter initials")
        layout.addWidget(self.initials_input)
        layout.addWidget(QLabel("Initials position:"))
        self.initials_pos_dropdown = QComboBox()
        self.initials_pos_dropdown.addItem("Before date", "before")
        self.initials_pos_dropdown.addItem("After date",  "after")
        layout.addWidget(self.initials_pos_dropdown)
        cfg = self.date_cfg
        self.ctrl_cb.setChecked(cfg['ctrl'])
        self.shift_cb.setChecked(cfg['shift'])
        self.alt_cb.setChecked(cfg['alt'])
        self.win_cb.setChecked(cfg['win'])
        idx = self.key_dropdown.findData(cfg['key'])
        if idx >= 0: self.key_dropdown.setCurrentIndex(idx)
        fmt_idx = self.format_dropdown.findData(cfg['format'])
        if fmt_idx >= 0: self.format_dropdown.setCurrentIndex(fmt_idx)
        self.initials_input.setText(cfg['initials'])
        pos_idx = self.initials_pos_dropdown.findData(cfg['initials_position'])
        if pos_idx >= 0: self.initials_pos_dropdown.setCurrentIndex(pos_idx)
        for cb in (self.ctrl_cb, self.shift_cb, self.alt_cb, self.win_cb):
            cb.stateChanged.connect(self.update_date_hotkey)
            cb.stateChanged.connect(self.save_settings)

        self.key_dropdown.currentIndexChanged.connect(self.update_date_hotkey)
        self.key_dropdown.currentIndexChanged.connect(self.save_settings)
        self.format_dropdown.currentIndexChanged.connect(self.update_date_hotkey)
        self.format_dropdown.currentIndexChanged.connect(self.save_settings)
        self.initials_input.textChanged.connect(self.save_settings)
        self.initials_pos_dropdown.currentIndexChanged.connect(self.save_settings)
        self.update_date_hotkey()

    def build_custom_tab(self):
        tab = self.custom_tab
        outer = QVBoxLayout(tab)
        outer.setContentsMargins(10,10,10,10)
        outer.setSpacing(8)
        self.custom_hotkey_input = QLineEdit()
        self.custom_hotkey_input.setPlaceholderText("e.g. ctrl+alt+H")
        self.custom_text_input   = QLineEdit()
        self.custom_text_input.setPlaceholderText("Text to paste")
        add_btn = QPushButton("Add Hotkey")
        add_btn.clicked.connect(self.add_custom_hotkey)
        outer.addWidget(QLabel("Hotkey:"))
        outer.addWidget(self.custom_hotkey_input)
        outer.addWidget(QLabel("Text:"))
        outer.addWidget(self.custom_text_input)
        outer.addWidget(add_btn)
        self.list_area = QScrollArea()
        self.list_area.setWidgetResizable(True)
        list_container = QWidget()
        self.list_layout = QVBoxLayout(list_container)
        self.list_layout.setContentsMargins(0,0,0,0)
        self.list_layout.setSpacing(4)
        self.list_area.setWidget(list_container)
        outer.addWidget(self.list_area)
        outer.addStretch()
        tab.setStyleSheet(self._flat_stylesheet())
        self.refresh_custom_list()

    def refresh_custom_list(self):
        for i in reversed(range(self.list_layout.count())):
            w = self.list_layout.itemAt(i).widget()
            if w: w.setParent(None)
        for entry in self.custom_hotkeys:
            hk, txt = entry['hotkey'], entry['text']
            row = QHBoxLayout()
            lbl = QLabel(f"{hk} → {txt}")
            row.addWidget(lbl)
            btn = QPushButton("✕")
            btn.setFixedSize(20,20)
            btn.clicked.connect(lambda _,e=entry: self.remove_custom(e))
            row.addWidget(btn)
            container = QWidget()
            container.setLayout(row)
            self.list_layout.addWidget(container)

    def update_date_hotkey(self):
        parts = []
        if self.ctrl_cb.isChecked():  parts.append("ctrl")
        if self.shift_cb.isChecked(): parts.append("shift")
        if self.alt_cb.isChecked():   parts.append("alt")
        if self.win_cb.isChecked():   parts.append("win")
        k = self.key_dropdown.currentData()
        if k: parts.append(k)
        hotkey = "+".join(parts)
        self.hotkey_display.setText(hotkey)
        if hasattr(self, 'registered_date_hotkey'):
            try: keyboard.remove_hotkey(self.registered_date_hotkey)
            except: pass
        try:
            self.registered_date_hotkey = keyboard.add_hotkey(
                hotkey, self.paste_date, suppress=True
            )
        except ValueError:
            QMessageBox.warning(self, "Invalid Hotkey", f"Cannot register: {hotkey}")

    def paste_date(self):
        fmt = self.format_dropdown.currentData()
        date_str = datetime.datetime.now().strftime(fmt)
        initials = self.initials_input.text().strip()
        pos = self.initials_pos_dropdown.currentData()
        if initials:
            if pos == 'before':
                output = f"{initials} {date_str}"
            else:
                output = f"{date_str} {initials}"
        else:
            output = date_str
        keyboard.write(output)

    def add_custom_hotkey(self):
        hk = self.custom_hotkey_input.text().strip()
        txt = self.custom_text_input.text()
        if not hk or not txt:
            QMessageBox.warning(self, "Missing Input", "Both hotkey and text are required.")
            return
        try:
            keyboard.add_hotkey(hk, lambda t=txt: keyboard.write(t), suppress=True)
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to register: {e}")
            return
        self.custom_hotkeys.append({'hotkey':hk,'text':txt})
        self.save_settings()
        self.refresh_custom_list()
        self.custom_hotkey_input.clear()
        self.custom_text_input.clear()

    def remove_custom(self, entry):
        try:
            keyboard.remove_hotkey(entry['hotkey'])
        except: pass
        self.custom_hotkeys.remove(entry)
        self.save_settings()
        self.refresh_custom_list()

    def on_install(self):
        if sys.platform != "win32":
            QMessageBox.information(self, "Not supported", "Installation only supported on Windows.")
            return

        from win32com.client import Dispatch
        shell   = Dispatch("WScript.Shell")
        startup = os.path.join(os.environ["APPDATA"],
                            "Microsoft", "Windows", "Start Menu",
                            "Programs", "Startup")
        desktop = os.path.join(os.environ["USERPROFILE"], "Desktop")

        for folder in (startup, desktop):
            link = os.path.join(folder, "DatePaster.lnk")
            if os.path.isfile(link):
                try:
                    os.remove(link)
                except Exception:
                    pass

        try:
            if getattr(sys, "frozen", False):
                target, args = sys.executable, ""
            else:
                target = sys.executable
                args   = f"\"{os.path.realpath(sys.argv[0])}\""
            work_dir = os.path.dirname(target)
            icon_loc = target

            for folder in (startup, desktop):
                shortcut = shell.CreateShortcut(os.path.join(folder, "DatePaster.lnk"))
                shortcut.TargetPath     = target
                shortcut.Arguments      = args
                shortcut.WorkingDirectory = work_dir
                shortcut.IconLocation   = icon_loc
                shortcut.Save()

            QMessageBox.information(self, "Installed", "Shortcuts installing in 1-2 minutes.")
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Installation failed: {e}")

    def load_settings(self):
        try:
            with open(self.settings_file, 'r') as f:
                data = json.load(f)
            dh = data.get('date_hotkey', {})
            self.date_cfg = {
                'ctrl': dh.get('ctrl', True),
                'shift': dh.get('shift', False),
                'alt': dh.get('alt', False),
                'win': dh.get('win', False),
                'key': dh.get('key', 'd'),
                'format': dh.get('format', '%B %d, %Y %H:%M'),
                'initials': dh.get('initials', ''),
                'initials_position': dh.get('initials_position', 'after'),
            }
            for entry in data.get('custom_hotkeys', []):
                hk, txt = entry['hotkey'], entry['text']
                try:
                    keyboard.add_hotkey(hk, lambda t=txt: keyboard.write(t), suppress=True)
                    self.custom_hotkeys.append(entry)
                except:
                    pass
        except:
            self.date_cfg = {
                'ctrl': True, 'shift': False, 'alt': False, 'win': False,
                'key': 'd', 'format': '%B %d, %Y %H:%M',
                'initials': '', 'initials_position': 'after'
            }

    def save_settings(self):
        data = {
            'date_hotkey': {
                'ctrl': self.ctrl_cb.isChecked(),
                'shift': self.shift_cb.isChecked(),
                'alt': self.alt_cb.isChecked(),
                'win': self.win_cb.isChecked(),
                'key': self.key_dropdown.currentData(),
                'format': self.format_dropdown.currentData(),
                'initials': self.initials_input.text().strip(),
                'initials_position': self.initials_pos_dropdown.currentData()
            },
            'custom_hotkeys': self.custom_hotkeys
        }
        try:
            with open(self.settings_file, 'w') as f:
                json.dump(data, f, indent=2)
        except:
            pass

    def start_watchdog(self):
        def watch():
            while True:
                threading.Event().wait(900)
                self.update_date_hotkey()
        threading.Thread(target=watch, daemon=True).start()

    def setup_tray(self):
        menu = TrayMenu(
            TrayMenuItem("Show",   lambda icon, item: self.on_show()),
            TrayMenuItem("Install",lambda icon, item: self.on_install()),
            TrayMenuItem("Quit",   lambda icon, item: self.on_quit(icon))
        )
        self.tray_icon = TrayIcon("DatePaster", create_icon(), "DatePaster", menu)
        threading.Thread(target=self.tray_icon.run, daemon=True).start()

    def on_show(self):
        self.show()

    def on_quit(self, icon):
        try:
            keyboard.unhook_all_hotkeys()
        except:
            pass
        icon.stop()
        QApplication.quit()

    def mousePressEvent(self, e):
        if e.button() == QtCore.Qt.LeftButton:
            self._drag_pos = e.globalPos() - self.frameGeometry().topLeft()
            e.accept()
        else:
            super().mousePressEvent(e)

    def mouseMoveEvent(self, e):
        if self._drag_pos is None:
            return
        if e.buttons() & QtCore.Qt.LeftButton:
            new = e.globalPos() - self._drag_pos
            all_rect = QtCore.QRect()
            for s in QApplication.screens():
                all_rect = all_rect.united(s.availableGeometry())
            x = max(all_rect.left(),
                    min(new.x(), all_rect.right() - self.width()))
            y = max(all_rect.top(),
                    min(new.y(), all_rect.bottom() - self.height()))
            self.move(x, y)
            e.accept()
        else:
            super().mouseMoveEvent(e)

    def mouseReleaseEvent(self, e):
        self._drag_pos = None
        super().mouseReleaseEvent(e)

    def _flat_stylesheet(self):
        return """
            QWidget{font-family:'Segoe UI';font-size:11pt;}
            QCheckBox{spacing:6px;}
            QCheckBox::indicator{width:18px;height:18px;border-radius:4px;border:1px solid #888;background:#eee;}
            QCheckBox::indicator:checked{background:#0078d7;border-color:#0078d7;}
            QComboBox{padding:4px 8px;border:1px solid #aaa;border-radius:6px;background:#f9f9f9;}
            QComboBox:hover{border-color:#0078d7;}
            QLineEdit{padding:4px 8px;border:1px solid #bbb;border-radius:6px;background:#f0f0f0;}
            QLineEdit:hover{border-color:#0078d7;}
            QLabel{color:#333;}
            QPushButton{padding:6px;border:1px solid #0078d7;border-radius:6px;background:#f9f9f9;}
            QPushButton:hover{background:#e0f0ff;}
        """

    def closeEvent(self, e):
        e.ignore()
        self.hide()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    shared_mem = QtCore.QSharedMemory("DatePasterAppUniqueKey")
    if not shared_mem.create(1):
        sys.exit(0)
    app.setQuitOnLastWindowClosed(False)
    win = DatePasterApp()
    win.show()
    sys.exit(app.exec_())
            
Version 1.0.0 Updated Aug 23, 2025 Size ~50.1 MB

Why you’ll like it

One-click paste

Today’s date/time in your cursor’s spot—no copy/paste dance.

Custom formats

Support for patterns like YYYY-MM-DD, MM/DD/YYYY, HH:mm, etc.

Snappy & tiny

Lightweight footprint, starts with Windows (optional), tray icon control.

Safe by design

No internet calls. No tracking. Just formats and paste.

Preview of common formats

YYYY-MM-DD → 2025-08-23
MM/DD/YYYY → 08/23/2025
DD Mon YYYY → 23 Aug 2025
HH:mm      → 18:04
hh:mm a    → 06:04 pm
YYYY-MM-DD HH:mm:ss → 2025-08-23 18:04:39
          

How it works

1
Install

Double click to use (no install), or right-click tray icon and install for auto startup.

2
Choose a format

Pick from presets or add your own template strings and your preferences are saved.

3
Paste anywhere

Use the tray menu or a hotkey to paste instantly.

FAQ

Does it require admin rights?

No admin rights required; the installer may request rights to paste icons.

Will it work offline?

Yes—no network usage. All formatting is local.

Where are settings stored?

In your user profile directory. Uninstalling or deleting the folder removes them.

Is there a Mac/Linux build?

Windows build is primary; cross-platform versions are planned for as needed currently.

Download options

Installer (.exe)

Source Code


import sys, os, threading, datetime, string, json, keyboard
from PyQt5 import QtWidgets, QtCore, QtGui
from PIL import Image, ImageDraw, ImageFont
from PyQt5.QtWidgets import (
    QApplication, QWidget, QFrame, QTabWidget, QCheckBox, QComboBox, QLineEdit,
    QLabel, QPushButton, QMessageBox, QGraphicsDropShadowEffect, QVBoxLayout,
    QHBoxLayout, QScrollArea
)
from pystray import Icon as TrayIcon, Menu as TrayMenu, MenuItem as TrayMenuItem

def create_icon():
    img = Image.new('RGB', (64, 64), 'white')
    draw = ImageDraw.Draw(img)
    try:
        font = ImageFont.truetype("seguiemj.ttf", 32)
    except:
        font = ImageFont.load_default()
    draw.text((10, 10), "📋", font=font, fill='black')
    return img

class DatePasterApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setFixedSize(600, 580)
        self.settings_file = os.path.join(os.path.expanduser("~"), ".datepaster_settings.json")
        self.custom_hotkeys = []
        self.initials = ""
        self.initials_position = "after"
        self.load_settings()
        self.container = QFrame(self)
        self.container.setGeometry(0, 0, 600, 580)
        self.container.setStyleSheet("QFrame{background:#fff;border-radius:18px}")
        shadow = QGraphicsDropShadowEffect(self.container)
        shadow.setBlurRadius(15); shadow.setOffset(0,0)
        shadow.setColor(QtGui.QColor(0,0,0,60))
        self.container.setGraphicsEffect(shadow)
        layout = QVBoxLayout(self.container)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        title_bar = QHBoxLayout()
        title_bar.setContentsMargins(10, 10, 10, 5)
        title = QLabel("Date Paster")
        title.setStyleSheet("font-size:14pt;font-weight:bold;color:#333")
        close_btn = QPushButton("✕")
        close_btn.setFixedSize(20, 20)
        close_btn.setStyleSheet("QPushButton{border:none;background:transparent;font-size:12pt;color:#333}")
        close_btn.clicked.connect(self.hide)
        title_bar.addWidget(title)
        title_bar.addStretch()
        title_bar.addWidget(close_btn)
        layout.addLayout(title_bar)
        self.tabs = QTabWidget()
        self.tabs.setStyleSheet("""
            QTabWidget::pane {
                background: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 #fafbfd, stop:1 #e8ecf0
                );
                border: 1px solid #d3d9e4;
                border-radius: 12px;
                margin-top: 36px;
                padding: 16px;
            }
            QTabBar {
                background: transparent;
                qproperty-expanding: false;
                spacing: 8px;
                padding-left: 12px;
            }
            QTabBar::tab {
                background: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 #eff3f8, stop:1 #dce3eb
                );
                color: #5a5f65;
                font: bold 10pt "Segoe UI";
                padding: 8px 20px;
                margin-top: 12px;
                border: none;
                border-top-left-radius: 8px;
                border-top-right-radius: 8px;
                min-width: 140px;
                min-height: 36px;
            }
            QTabBar::tab:hover {
                background: #ffffff;
                color: #0078d7;
            }
            QTabBar::tab:!selected {
                margin-top: 16px;
            }
            QTabBar::tab:selected {
                background: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 #ffffff, stop:1 #f7f9fb
                );
                color: #0078d7;
                margin-top: 0px;
                border-bottom: 3px solid #0078d7;
            }
            QPushButton {
                background-color: #5BB6FF;
                color: grey;
                border-radius: 6px;
                padding: 6px 12px;
                border: none;
            }
            QPushButton:hover {
                background-color: #A8D8FF;
            }
            QPushButton:pressed {
                background-color: #82A9C8;
            }
            QLineEdit {
                background: #f5f5f5;
                border: 1px solid #cccccc;
                border-radius: 6px;
                padding: 4px 8px;
            }
            QLineEdit:focus {
                border-color: #0078d7;
            }
            QComboBox {
                background: #f5f5f5;
                border: 1px solid #cccccc;
                border-radius: 6px;
                padding: 4px 8px;
                min-height: 28px;
            }
            QComboBox:hover {
                border-color: #0078d7;
            }
            QCheckBox {
                spacing: 6px;
            }
            QCheckBox::indicator {
                width: 16px;
                height: 16px;
                border: 1px solid #888888;
                border-radius: 4px;
                background: #eeeeee;
            }
            QCheckBox::indicator:checked {
                background: #0078d7;
            }
            QTableWidget {
                background: #ffffff;
                gridline-color: #e1e4e8;
                alternate-background-color: #f9fafb;
            }
            QHeaderView::section {
                background: #e4e7ec;
                padding: 6px;
                border: none;
                font-weight: bold;
            }
            QScrollArea QPushButton {
                background: transparent;
                color: #333333;
                border: none;
                padding: 0 4px;
                font-size: 14pt;
            }
        """)
        layout.addWidget(self.tabs)
        self.date_tab = QWidget()
        self.build_date_tab()
        self.tabs.addTab(self.date_tab, "Date Hotkey")
        self.custom_tab = QWidget()
        self.build_custom_tab()
        self.tabs.addTab(self.custom_tab, "Custom Hotkeys")
        self.setup_tray()
        self.update_date_hotkey()
        self.start_watchdog()
        self._drag_pos = None

    def build_date_tab(self):
        tab    = self.date_tab
        layout = QVBoxLayout(tab)
        layout.setContentsMargins(10, 0, 10, 10)
        layout.setSpacing(8)
        mod_layout = QHBoxLayout()
        self.ctrl_cb  = QCheckBox("Ctrl");  mod_layout.addWidget(self.ctrl_cb)
        self.shift_cb = QCheckBox("Shift"); mod_layout.addWidget(self.shift_cb)
        self.alt_cb   = QCheckBox("Alt");   mod_layout.addWidget(self.alt_cb)
        self.win_cb   = QCheckBox("Win");   mod_layout.addWidget(self.win_cb)
        layout.addLayout(mod_layout)
        row = QHBoxLayout()
        row.addWidget(QLabel("Key:"))
        self.key_dropdown = QComboBox()
        self.key_dropdown.setFixedHeight(24)
        for c in string.ascii_uppercase:
            self.key_dropdown.addItem(c, c.lower())
        for d in map(str, range(10)):
            self.key_dropdown.addItem(d, d)
        for f in range(1, 13):
            self.key_dropdown.addItem(f"F{f}", f"f{f}")
        row.addWidget(self.key_dropdown)
        layout.addLayout(row)
        self.hotkey_display = QLineEdit()
        self.hotkey_display.setReadOnly(True)
        self.hotkey_display.setFixedHeight(28)
        layout.addWidget(self.hotkey_display)
        layout.addWidget(QLabel("Select Date/Time Format:"))
        self.format_dropdown = QComboBox()
        self.format_dropdown.setFixedHeight(28)
        fmts = [
            ("YYYY-MM-DD","%Y-%m-%d"),("MM/DD/YYYY","%m/%d/%Y"),("DD-MM-YYYY","%d-%m-%Y"),
            ("Month D, YYYY","%B %d, %Y"),("HH:MM","%H:%M"),("HH:MM:SS","%H:%M:%S"),
            ("YYYY-MM-DD HH:MM","%Y-%m-%d %H:%M"),("YYYY-MM-DD HH:MM:SS","%Y-%m-%d %H:%M:%S"),
            ("Month D, YYYY HH:MM","%B %d, %Y %H:%M")
        ]
        for lbl, d in fmts:
            self.format_dropdown.addItem(lbl, d)
        layout.addWidget(self.format_dropdown)
        layout.addWidget(QLabel("Initials (optional):"))
        self.initials_input = QLineEdit()
        self.initials_input.setPlaceholderText("Enter initials")
        layout.addWidget(self.initials_input)
        layout.addWidget(QLabel("Initials position:"))
        self.initials_pos_dropdown = QComboBox()
        self.initials_pos_dropdown.addItem("Before date", "before")
        self.initials_pos_dropdown.addItem("After date",  "after")
        layout.addWidget(self.initials_pos_dropdown)
        cfg = self.date_cfg
        self.ctrl_cb.setChecked(cfg['ctrl'])
        self.shift_cb.setChecked(cfg['shift'])
        self.alt_cb.setChecked(cfg['alt'])
        self.win_cb.setChecked(cfg['win'])
        idx = self.key_dropdown.findData(cfg['key'])
        if idx >= 0: self.key_dropdown.setCurrentIndex(idx)
        fmt_idx = self.format_dropdown.findData(cfg['format'])
        if fmt_idx >= 0: self.format_dropdown.setCurrentIndex(fmt_idx)
        self.initials_input.setText(cfg['initials'])
        pos_idx = self.initials_pos_dropdown.findData(cfg['initials_position'])
        if pos_idx >= 0: self.initials_pos_dropdown.setCurrentIndex(pos_idx)
        for cb in (self.ctrl_cb, self.shift_cb, self.alt_cb, self.win_cb):
            cb.stateChanged.connect(self.update_date_hotkey)
            cb.stateChanged.connect(self.save_settings)

        self.key_dropdown.currentIndexChanged.connect(self.update_date_hotkey)
        self.key_dropdown.currentIndexChanged.connect(self.save_settings)
        self.format_dropdown.currentIndexChanged.connect(self.update_date_hotkey)
        self.format_dropdown.currentIndexChanged.connect(self.save_settings)
        self.initials_input.textChanged.connect(self.save_settings)
        self.initials_pos_dropdown.currentIndexChanged.connect(self.save_settings)
        self.update_date_hotkey()

    def build_custom_tab(self):
        tab = self.custom_tab
        outer = QVBoxLayout(tab)
        outer.setContentsMargins(10,10,10,10)
        outer.setSpacing(8)
        self.custom_hotkey_input = QLineEdit()
        self.custom_hotkey_input.setPlaceholderText("e.g. ctrl+alt+H")
        self.custom_text_input   = QLineEdit()
        self.custom_text_input.setPlaceholderText("Text to paste")
        add_btn = QPushButton("Add Hotkey")
        add_btn.clicked.connect(self.add_custom_hotkey)
        outer.addWidget(QLabel("Hotkey:"))
        outer.addWidget(self.custom_hotkey_input)
        outer.addWidget(QLabel("Text:"))
        outer.addWidget(self.custom_text_input)
        outer.addWidget(add_btn)
        self.list_area = QScrollArea()
        self.list_area.setWidgetResizable(True)
        list_container = QWidget()
        self.list_layout = QVBoxLayout(list_container)
        self.list_layout.setContentsMargins(0,0,0,0)
        self.list_layout.setSpacing(4)
        self.list_area.setWidget(list_container)
        outer.addWidget(self.list_area)
        outer.addStretch()
        tab.setStyleSheet(self._flat_stylesheet())
        self.refresh_custom_list()

    def refresh_custom_list(self):
        for i in reversed(range(self.list_layout.count())):
            w = self.list_layout.itemAt(i).widget()
            if w: w.setParent(None)
        for entry in self.custom_hotkeys:
            hk, txt = entry['hotkey'], entry['text']
            row = QHBoxLayout()
            lbl = QLabel(f"{hk} → {txt}")
            row.addWidget(lbl)
            btn = QPushButton("✕")
            btn.setFixedSize(20,20)
            btn.clicked.connect(lambda _,e=entry: self.remove_custom(e))
            row.addWidget(btn)
            container = QWidget()
            container.setLayout(row)
            self.list_layout.addWidget(container)

    def update_date_hotkey(self):
        parts = []
        if self.ctrl_cb.isChecked():  parts.append("ctrl")
        if self.shift_cb.isChecked(): parts.append("shift")
        if self.alt_cb.isChecked():   parts.append("alt")
        if self.win_cb.isChecked():   parts.append("win")
        k = self.key_dropdown.currentData()
        if k: parts.append(k)
        hotkey = "+".join(parts)
        self.hotkey_display.setText(hotkey)
        if hasattr(self, 'registered_date_hotkey'):
            try: keyboard.remove_hotkey(self.registered_date_hotkey)
            except: pass
        try:
            self.registered_date_hotkey = keyboard.add_hotkey(
                hotkey, self.paste_date, suppress=True
            )
        except ValueError:
            QMessageBox.warning(self, "Invalid Hotkey", f"Cannot register: {hotkey}")

    def paste_date(self):
        fmt = self.format_dropdown.currentData()
        date_str = datetime.datetime.now().strftime(fmt)
        initials = self.initials_input.text().strip()
        pos = self.initials_pos_dropdown.currentData()
        if initials:
            if pos == 'before':
                output = f"{initials} {date_str}"
            else:
                output = f"{date_str} {initials}"
        else:
            output = date_str
        keyboard.write(output)

    def add_custom_hotkey(self):
        hk = self.custom_hotkey_input.text().strip()
        txt = self.custom_text_input.text()
        if not hk or not txt:
            QMessageBox.warning(self, "Missing Input", "Both hotkey and text are required.")
            return
        try:
            keyboard.add_hotkey(hk, lambda t=txt: keyboard.write(t), suppress=True)
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to register: {e}")
            return
        self.custom_hotkeys.append({'hotkey':hk,'text':txt})
        self.save_settings()
        self.refresh_custom_list()
        self.custom_hotkey_input.clear()
        self.custom_text_input.clear()

    def remove_custom(self, entry):
        try:
            keyboard.remove_hotkey(entry['hotkey'])
        except: pass
        self.custom_hotkeys.remove(entry)
        self.save_settings()
        self.refresh_custom_list()

    def on_install(self):
        if sys.platform != "win32":
            QMessageBox.information(self, "Not supported", "Installation only supported on Windows.")
            return

        from win32com.client import Dispatch
        shell   = Dispatch("WScript.Shell")
        startup = os.path.join(os.environ["APPDATA"],
                            "Microsoft", "Windows", "Start Menu",
                            "Programs", "Startup")
        desktop = os.path.join(os.environ["USERPROFILE"], "Desktop")

        for folder in (startup, desktop):
            link = os.path.join(folder, "DatePaster.lnk")
            if os.path.isfile(link):
                try:
                    os.remove(link)
                except Exception:
                    pass

        try:
            if getattr(sys, "frozen", False):
                target, args = sys.executable, ""
            else:
                target = sys.executable
                args   = f"\"{os.path.realpath(sys.argv[0])}\""
            work_dir = os.path.dirname(target)
            icon_loc = target

            for folder in (startup, desktop):
                shortcut = shell.CreateShortcut(os.path.join(folder, "DatePaster.lnk"))
                shortcut.TargetPath     = target
                shortcut.Arguments      = args
                shortcut.WorkingDirectory = work_dir
                shortcut.IconLocation   = icon_loc
                shortcut.Save()

            QMessageBox.information(self, "Installed", "Shortcuts installing in 1-2 minutes.")
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Installation failed: {e}")

    def load_settings(self):
        try:
            with open(self.settings_file, 'r') as f:
                data = json.load(f)
            dh = data.get('date_hotkey', {})
            self.date_cfg = {
                'ctrl': dh.get('ctrl', True),
                'shift': dh.get('shift', False),
                'alt': dh.get('alt', False),
                'win': dh.get('win', False),
                'key': dh.get('key', 'd'),
                'format': dh.get('format', '%B %d, %Y %H:%M'),
                'initials': dh.get('initials', ''),
                'initials_position': dh.get('initials_position', 'after'),
            }
            for entry in data.get('custom_hotkeys', []):
                hk, txt = entry['hotkey'], entry['text']
                try:
                    keyboard.add_hotkey(hk, lambda t=txt: keyboard.write(t), suppress=True)
                    self.custom_hotkeys.append(entry)
                except:
                    pass
        except:
            self.date_cfg = {
                'ctrl': True, 'shift': False, 'alt': False, 'win': False,
                'key': 'd', 'format': '%B %d, %Y %H:%M',
                'initials': '', 'initials_position': 'after'
            }

    def save_settings(self):
        data = {
            'date_hotkey': {
                'ctrl': self.ctrl_cb.isChecked(),
                'shift': self.shift_cb.isChecked(),
                'alt': self.alt_cb.isChecked(),
                'win': self.win_cb.isChecked(),
                'key': self.key_dropdown.currentData(),
                'format': self.format_dropdown.currentData(),
                'initials': self.initials_input.text().strip(),
                'initials_position': self.initials_pos_dropdown.currentData()
            },
            'custom_hotkeys': self.custom_hotkeys
        }
        try:
            with open(self.settings_file, 'w') as f:
                json.dump(data, f, indent=2)
        except:
            pass

    def start_watchdog(self):
        def watch():
            while True:
                threading.Event().wait(900)
                self.update_date_hotkey()
        threading.Thread(target=watch, daemon=True).start()

    def setup_tray(self):
        menu = TrayMenu(
            TrayMenuItem("Show",   lambda icon, item: self.on_show()),
            TrayMenuItem("Install",lambda icon, item: self.on_install()),
            TrayMenuItem("Quit",   lambda icon, item: self.on_quit(icon))
        )
        self.tray_icon = TrayIcon("DatePaster", create_icon(), "DatePaster", menu)
        threading.Thread(target=self.tray_icon.run, daemon=True).start()

    def on_show(self):
        self.show()

    def on_quit(self, icon):
        try:
            keyboard.unhook_all_hotkeys()
        except:
            pass
        icon.stop()
        QApplication.quit()

    def mousePressEvent(self, e):
        if e.button() == QtCore.Qt.LeftButton:
            self._drag_pos = e.globalPos() - self.frameGeometry().topLeft()
            e.accept()
        else:
            super().mousePressEvent(e)

    def mouseMoveEvent(self, e):
        if self._drag_pos is None:
            return
        if e.buttons() & QtCore.Qt.LeftButton:
            new = e.globalPos() - self._drag_pos
            all_rect = QtCore.QRect()
            for s in QApplication.screens():
                all_rect = all_rect.united(s.availableGeometry())
            x = max(all_rect.left(),
                    min(new.x(), all_rect.right() - self.width()))
            y = max(all_rect.top(),
                    min(new.y(), all_rect.bottom() - self.height()))
            self.move(x, y)
            e.accept()
        else:
            super().mouseMoveEvent(e)

    def mouseReleaseEvent(self, e):
        self._drag_pos = None
        super().mouseReleaseEvent(e)

    def _flat_stylesheet(self):
        return """
            QWidget{font-family:'Segoe UI';font-size:11pt;}
            QCheckBox{spacing:6px;}
            QCheckBox::indicator{width:18px;height:18px;border-radius:4px;border:1px solid #888;background:#eee;}
            QCheckBox::indicator:checked{background:#0078d7;border-color:#0078d7;}
            QComboBox{padding:4px 8px;border:1px solid #aaa;border-radius:6px;background:#f9f9f9;}
            QComboBox:hover{border-color:#0078d7;}
            QLineEdit{padding:4px 8px;border:1px solid #bbb;border-radius:6px;background:#f0f0f0;}
            QLineEdit:hover{border-color:#0078d7;}
            QLabel{color:#333;}
            QPushButton{padding:6px;border:1px solid #0078d7;border-radius:6px;background:#f9f9f9;}
            QPushButton:hover{background:#e0f0ff;}
        """

    def closeEvent(self, e):
        e.ignore()
        self.hide()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    shared_mem = QtCore.QSharedMemory("DatePasterAppUniqueKey")
    if not shared_mem.create(1):
        sys.exit(0)
    app.setQuitOnLastWindowClosed(False)
    win = DatePasterApp()
    win.show()
    sys.exit(app.exec_())
                
Version 1.0.0 Updated 2025-08-23 Size ~50.1 MB

Hotkeys (example)

Ctrl + D

Paste today’s date (YYYY-MM-DD)

Ctrl + 1

Password 1

Custom Keys

Paste full stamp (YYYY-MM-DD HH:mm:ss)

System requirements

  • Windows 10 or later
  • ~50 MB free space
  • No .NET install required (bundled)
Prefer the tools bundle? Explore more free utilities on the home page.

← Back to Home