Date Paster
Paste today’s date, time, or your favorite formats anywhere—fast. Perfect for notes, tickets, emails, and logs.
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_())
Why you’ll like it
Today’s date/time in your cursor’s spot—no copy/paste dance.
Support for patterns like YYYY-MM-DD
, MM/DD/YYYY
, HH:mm
, etc.
Lightweight footprint, starts with Windows (optional), tray icon control.
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
Double click to use (no install), or right-click tray icon and install for auto startup.
Pick from presets or add your own template strings and your preferences are saved.
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
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_())
1.0.0
Updated 2025-08-23
Size ~50.1 MB
Hotkeys (example)
Paste today’s date (YYYY-MM-DD)
Password 1
Paste full stamp (YYYY-MM-DD HH:mm:ss)
System requirements
- Windows 10 or later
- ~50 MB free space
- No .NET install required (bundled)