DatePickerCtrl is seemingly stuck with a MM/DD/YYYY format, great for our North American chums but which makes it virtually useless for the rest of us.
Hopefully others will find this useful as you can decide the date format you wish to use and elect to use a calendar in your own language too.
Let me know if there are errors and omissions.
Apparently I’m still not permitted to upload a file, so I’ll have to paste it!
"""
MiniDatePicker.py
A custom class that looks like the wx.DatePickerCtrl but with the ability to customise
the calendar and the output format. (DatePickerCtrl is seemingly stuck with MM/DD/YYYY format)
Works with wx.DateTime or python datetime values
With or without an activating button
Uses wx.adv.GenericCalendarCtrl
Uses locale to enable different languages for the calendar
MiniDatePicker(parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
style=wx.BORDER_SIMPLE, name="MiniDatePicker", date=0, formatter=''):
@param parent: Parent window. Must not be None.
@param id: identifier. A value of -1 indicates a default value.
@param pos: MiniDatePicker position. If the position (-1, -1) is specified
then a default position is chosen.
@param size: If the default size (-1, -1) is specified then a default size is calculated.
Size should be able to accomodate the specified formatter string + button
@param style: Alignment (Left,Middle,Right).
@param name: Widget name.
@param date: Initial date (an invalid date = today)
@param formatter A date formatting string in the form of a lambda function
default lambda dt: dt.FormatISODate()
= ISO 8601 format "YYYY-MM-DD".
or a lambda function with a format string e.g.:
lambda dt: (f'{dt.Format("%a %d-%m-%Y")}')
e.g.:
format = lambda dt: (f'{dt.Format("%a %d-%m-%Y")}')
format = lambda dt: (f'{dt.Format("%A %d %B %Y")}')
or
fmt = "%Y/%m/%d"
format = lambda dt: (dt.Format(fmt))
format = lambda dt: (dt.Format("%Y/%m/%d"))
format = lambda dt: (dt.FormatISODate())
TextCtrl Styles: wx.TE_READONLY (Default)
wx.TE_RIGHT
wx.TE_LEFT
wx.TE_CENTRE
wx.BORDER_NONE is always applied to the internal textctrl
wx.BORDER_SIMPLE is the default border for the control itself
Events: EVT_DATE_CHANGED A date change occurred in the control
Event Functions:
GetValue() Returns formatted date in the event as a string
GetDate() Returns wxDateTime date in the event
GetDateTime() Returns python datetime of date in the event
GetTimeStamp() Returns seconds since Jan 1, 1970 UTC for current date
Functions:
GetValue() Returns formatted date in the event as a string
GetDate() Returns wxDateTime date in the control
GetDateTimeValue() Returns python datetime of date in the control
GetTimeStamp() Returns seconds since Jan 1, 1970 UTC for selected date
GetLocale() Returns tuple of current language code and encoding
SetValue(date) Sets the date in the control
expects a wx.DateTime, a python datetime datetime or a timestamp
Any invalid date defaults to wx.DateTime.Today()
SetFormatter(formatter) Date format in the form of a lambda
default: lambda dt: dt.FormatISODate()
SetButton(Boolean) Shows or Hides Ctrl Button
SetLocale(locale) Set the locale for Calendar day and month names
e.g. 'de_DE.UTF-8' German
'es_ES.UTF-8' Spanish
depends on the locale being available on the machine
SetCalendarStyle(style)
wx.adv.CAL_SUNDAY_FIRST: Show Sunday as the first day in the week (not in wxGTK)
wx.adv.CAL_MONDAY_FIRST: Show Monday as the first day in the week (not in wxGTK)
wx.adv.CAL_SHOW_HOLIDAYS: Highlight holidays in the calendar (only generic)
wx.adv.CAL_NO_YEAR_CHANGE: Disable the year changing (deprecated, only generic)
wx.adv.CAL_NO_MONTH_CHANGE: Disable the month (and, implicitly, the year) changing
wx.adv.CAL_SHOW_SURROUNDING_WEEKS: Show the neighbouring weeks in the previous and next months
wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION: more compact, style for the month and year selection controls.
wx.adv.CAL_SHOW_WEEK_NUMBERS
SetCalendarHighlights(colFg, colBg)
SetCalendarHeaders(colFg, colBg)
SetCalendarFg(colFg) Calendar ForegroundColour
SetCalendarBg(colBg) Calendar & Ctrl BackgroundColour
Default Values:
date - Today
style - READ_ONLY
Author: J Healey
Created: 04/12/2022
Copyright: J Healey - 2022
License: GPL 3 or any later version
Email: <rolfofsaxony@gmx.com>
Usage example:
import wx
import minidatepicker as MDP
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "MiniDatePicker Demo")
format = (lambda dt: (f'{dt.Format("%A %d-%m-%Y")}'))
panel = wx.Panel(self)
mdp = MDP.MiniDatePicker(panel, -1, pos=(50, 50), size=(-1,-1), style=0, date=0, formatter=format)
self.Show()
app = wx.App()
frame = Frame(None)
app.MainLoop()
"""
import wx
import wx.adv
from wx.lib.embeddedimage import PyEmbeddedImage
import datetime
import locale
img = PyEmbeddedImage(
b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABg2lDQ1BJQ0MgcHJvZmlsZQAA'
b'KJF9kT1Iw0AcxV9TS0UiDu0g4pChOlkQFXHUKhShQqgVWnUwufQLmjQkKS6OgmvBwY/FqoOL'
b's64OroIg+AHi6OSk6CIl/i8ptIj14Lgf7+497t4BQqPCdLtnHNANx0onE1I2tyqFXyEihAhi'
b'EBVmm3OynELX8XWPAF/v4jyr+7k/R7+WtxkQkIhnmWk5xBvE05uOyXmfOMpKikZ8Tjxm0QWJ'
b'H7mu+vzGueixwDOjViY9TxwlloodrHYwK1k68RRxTNMNyheyPmuctzjrlRpr3ZO/UMwbK8tc'
b'pzmMJBaxBBkSVNRQRgUO4rQapNhI036ii3/I88vkUslVBiPHAqrQoXh+8D/43a1dmJzwk8QE'
b'EHpx3Y8RILwLNOuu+33sus0TIPgMXBltf7UBzHySXm9rsSNgYBu4uG5r6h5wuQMMPpmKpXhS'
b'kKZQKADvZ/RNOSByC/St+b219nH6AGSoq9QNcHAIjBYpe73Lu3s7e/v3TKu/H0FDcpMpQj/v'
b'AAAACXBIWXMAAAsTAAALEwEAmpwYAAACk0lEQVRIx43W3+unQxQH8Nc83w9tVutHNj9v5M4q'
b'tty7oC2u2Fy58KOV9g/AlRJygQvlRrIlpbhSW7uJorCKlKQoJand7AVJYm3Z7zMu9v18zWfM'
b'J6aeZs7MmTNnznm/zzwFE6p/WunkLczNXEnfyhO2Rza2rLfpP4xPG4zPm2xsNR6NPN/uNuq8'
b'nAa3W4tGaRQrrsZFkX/FIbyO3dG7PHq/RP4d9+NIs/YHTi+OrzKYcS2OZtNpvIqbcQfuyqFv'
b'pH80e45hP26JM1fhYtyDHzGvUGuttZSyB3/hWdyAn3P4KXwYg+dywAfx9mTmf8JH+A5P45Ls'
b'Ox/XUkpJzM/kJofxJ17JjR7GTfgGX2EfHsHZ3PRM5OsynvvEtTCrGR+Ih3fmJvsTgmsSEtE5'
b'lb5N7g7qVgO0LIk/FM9r5N14uUPM3TiY/bVbK5hbDPdEaudq8/VQnBu4Lzo7XJiWJA8Y2reK'
b'5/F4jN6L9/EaHsQFsVewHZtWSfLUeTBqBbfitxi6HTfiisjn1pTPA2daNZ7PDengh8R9b9Cz'
b'L0g6G73bcGUguSv7/1UypkFYlnA9iZfwPR4K+V7EM/H2cIh2JGG7sCszFXW1oZDNeA57Ujre'
b'CkSfSClYpYRcH3Ie7EJU2ySPilnBCXwW4rwdKH4axlYcDwe+xnsbSvlO1vt6Lsa/TGH7OP0X'
b'+Dy6J/BtdD5pbK1BfjVg8aL4QlMIj2btscaBN5s9D3RkW4hWpwaztXmAapfwTdAdEbOF/BoP'
b'JlyamD/VvU6tV/OgrCxzu3BZq7NqFE/iXdzXkW5UOspAZznonaVUY6t9zeRdKMu4YeVUa507'
b'lg51lrXlPS+dh6MwTJibw6dBSdn4J1K6R39ofPDH8L9/c/4GBa36v+mJzSMAAAAASUVORK5C'
b'YII=')
mdpEVT = wx.NewEventType()
EVT_DATE_CHANGED = wx.PyEventBinder(mdpEVT, 1)
class mdpEvent(wx.PyCommandEvent):
def __init__(self, eventType, eventId=1, date=None, value=''):
"""
Default class constructor.
:param `eventType`: the event type;
:param `eventId`: the event identifier.
"""
wx.PyCommandEvent.__init__(self, eventType, eventId)
self._eventType = eventType
self.date = date
self.value = value
def GetDate(self):
"""
Retrieve the date value of the control at the time
this event was generated, Returning a wx.DateTime object"""
return self.date
def GetValue(self):
"""
Retrieve the formatted date value of the control at the time
this event was generated, Returning a string"""
return self.value
def GetDateTime(self):
"""
Retrieve the date value of the control at the time
this event was generated, Returning a python datetime object"""
return wx.wxdate2pydate(self.date)
def GetTimeStamp(self):
"""
Retrieve the date value represented as seconds since Jan 1, 1970 UTC.
Returning a integer
"""
return int(self.date.GetValue()/1000)
class MiniDatePicker(wx.Control):
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
style=wx.BORDER_SIMPLE, name="MiniDatePicker", date=0, formatter=''):
wx.Control.__init__(self, parent, id, pos=pos, size=size, style=style, name=name)
self.parent = parent
self._date = date
if formatter:
format = formatter
else:
format = lambda dt: dt.FormatISODate()
font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
self.SetWindowStyle(wx.BORDER_NONE)
self._style = style
self._calendar_style = wx.adv.CAL_MONDAY_FIRST
self._calendar_headercolours = None
self._calendar_highlightcolours = None
self._calendar_Bg = None
self._calendar_Fg = None
if size == wx.DefaultSize:
dc = wx.ScreenDC()
dc.SetFont(font)
trialdate = format(wx.DateTime(28,9,2022)) # a Wednesday in September = longest names in English
w, h = dc.GetTextExtent(trialdate)
size = (w+64, -1) # Add image width (24) plus a buffer
del dc
self._veto = False
#try:
# locale.setlocale(locale.LC_TIME, ".".join(locale.getlocale()))
#except Exception as e:
# pass
txtstyle = wx.TE_READONLY
if style & wx.TE_LEFT or style == wx.TE_LEFT:
txtstyle = txtstyle | wx.TE_LEFT
elif style & wx.TE_RIGHT:
txtstyle = txtstyle | wx.TE_RIGHT
else:
txtstyle = txtstyle | wx.TE_CENTRE
if style & wx.TE_READONLY:
txtstyle = txtstyle | wx.TE_READONLY
if style & wx.BORDER_NONE:
txtstyle = txtstyle | wx.BORDER_NONE
# MiniDatePicker
self.ctl = wx.TextCtrl(self, id, value=str(self._date),
pos=pos, size=size, style=txtstyle, name=name)
self.button = wx.BitmapButton(self, -1, bitmap=img.Bitmap)
self.MinSize = self.GetBestSize()
# End
# Bind the events
self._formatter = format
self.button.Bind(wx.EVT_BUTTON, self.OnCalendar)
self.ctl.Bind(wx.EVT_LEFT_DOWN, self.OnCalendar)
self.SetValue(date)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.ctl, 1, wx.EXPAND, 0)
sizer.Add(self.button, 0, wx.ALIGN_CENTER_VERTICAL, 0)
self.SetSizerAndFit(sizer)
self.Show()
def OnCalendar(self, _event=None):
window = CalendarPopup(
self, self._date, self.OnDate, self.GetTopLevelParent(), wx.SIMPLE_BORDER)
pos = self.ClientToScreen((0, 0))
size = self.GetSize()
window.Position(pos, (0, size.height))
def SetFormatter(self, formatter):
'''formatter will be called with a wx.DateTime'''
self._formatter = formatter
self.OnDate(self._date)
def SetLocale(self, alias):
try:
locale.setlocale(locale.LC_TIME, locale=alias)
except Exception as e:
locale.setlocale(locale.LC_TIME, locale='')
self.SetValue(self._date)
def SetCalendarStyle(self, style=0):
self._calendar_style = style
def SetCalendarHeaders(self, colFg=wx.NullColour, colBg=wx.NullColour):
self._calendar_headercolours = colFg, colBg
def SetCalendarHighlights(self, colFg=wx.NullColour, colBg=wx.NullColour):
self._calendar_highlightcolours = colFg, colBg
def SetCalendarFg(self, colFg=wx.NullColour):
self._calendar_Fg = colFg
def SetCalendarBg(self, colBg=wx.NullColour):
self._calendar_Bg = colBg
def SetButton(self, button=True):
if button:
self.button.Show()
else:
self.button.Hide()
self.Layout()
def OnDate(self, date):
self._date = date
self.ctl.SetValue(self._formatter(date))
self.MinSize = self.GetBestSize()
if self._veto:
self._veto = False
return
event = mdpEvent(mdpEVT, self.GetId(), date=date, value=self._formatter(date))
event.SetEventObject(self)
self.GetEventHandler().ProcessEvent(event)
def GetValue(self):
return self._ctl.GetValue()
def GetDate(self):
return self._date
def GetDateTimeValue(self):
"""
Return a python datetime object"""
return wx.wxdate2pydate(self._date)
def GetTimeStamp(self):
"""
Retrieve the date value represented as seconds since Jan 1, 1970 UTC.
Returning a integer
"""
return int(self._date.GetValue()/1000)
def GetLocale(self):
return locale.getlocale(category=locale.LC_TIME)
def SetValue(self, date):
if isinstance(date, wx.DateTime):
pass
elif isinstance(date, datetime.date):
date = wx.pydate2wxdate(date)
elif isinstance(date, int) and date > 0:
date = wx.DateTime.FromTimeT(date)
elif isinstance(date, float) and date > 0:
date = wx.DateTime.FromTimeT(int(date))
else: # Invalid date value default to today's date
date = wx.DateTime.Today()
self._date = date.ResetTime()
print(self._date)
self._veto = True
self.SetFormatter(self._formatter)
class CalendarPopup(wx.PopupTransientWindow):
def __init__(self, parent, date, callback, *args, **kwargs):
'''date is the initial date; callback is called with the chosen date'''
super().__init__(*args, **kwargs)
self.callback = callback
self.calendar = wx.adv.GenericCalendarCtrl(self, pos=(5, 5), style=parent._calendar_style)
self.calendar.SetDate(date)
if parent._calendar_headercolours:
self.calendar.SetHeaderColours(parent._calendar_headercolours[0],parent._calendar_headercolours[1])
if parent._calendar_highlightcolours:
self.calendar.SetHighlightColours(parent._calendar_highlightcolours[0],parent._calendar_highlightcolours[1])
if parent._calendar_Bg:
self.calendar.SetBackgroundColour(parent._calendar_Bg)
self.SetBackgroundColour(parent._calendar_Bg)
if parent._calendar_Fg:
self.calendar.SetForegroundColour(parent._calendar_Fg)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.calendar, 1, wx.ALL | wx.EXPAND)
self.SetSizerAndFit(sizer)
self.calendar.Bind(wx.adv.EVT_CALENDAR, self.OnChosen)
self.calendar.SetToolTip("Arrow keys and PageUp/pageDn\nAdjust the Calendar")
self.Popup()
def OnChosen(self, _event=None):
self.callback(self.calendar.GetDate())
self.Dismiss()
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "MiniDatePicker Demo")
#format = (lambda dt:
# (f'{dt.GetWeekDayName(dt.GetWeekDay())} {str(dt.day).zfill(2)}/{str(dt.month+1).zfill(2)}/{dt.year}')
# )
format = (lambda dt: (f'{dt.Format("%a %d-%m-%Y")}'))
#format = (lambda dt: (f'{dt.Format("%A %d %B %Y")}'))
panel = wx.Panel(self)
mdp = MiniDatePicker(panel, -1, pos=(50, 50), style=wx.TE_CENTRE, date=0, formatter=format)
#mdp.SetLocale('es_ES.UTF-8')
#mdp.SetFormatter(format)
x=datetime.datetime.now()
mdp.SetValue(x.timestamp())
#mdp.SetValue(0)
#mdp.SetButton(False)
mdp.SetCalendarStyle(wx.adv.CAL_SHOW_WEEK_NUMBERS|wx.adv.CAL_MONDAY_FIRST)
mdp.button.SetBackgroundColour('white')
mdp.ctl.SetBackgroundColour('lightgreen')
mdp.SetCalendarHeaders(colFg='red', colBg='lightgreen')
mdp.SetCalendarHighlights(colFg='red', colBg='lightgreen')
mdp.SetCalendarBg(colBg='azure')
self.Bind(EVT_DATE_CHANGED, self.OnEvent)
def OnEvent(self, event):
print(event.GetValue())
print(event.GetDate())
print(event.GetTimeStamp())
if __name__ == '__main__':
app = wx.App()
frame = DemoFrame(None)
frame.Show()
app.MainLoop()