A Date and Time Picker (Phoenix)
Keywords : wx.adv.TimePickerCtrl, wx.adv.CAL_MONDAY_FIRST, wx.DateTime, wx.pydate2wxdate, wx.adv.GenericCalendarCtrl, wx.PopupTransientWindow, wx.adv.CAL_SHOW_WEEK_NUMBERS.
Contents
Introduction :
Inspired by the need to set a deadline, where not only were a date and time required but also the ability to return a timestamp, which is easy to store in a database, easily sorted and easily converted back into a date. I’ve endeavoured to make this as friendly as possible, allowing for the retrieving of a wx.DateTime, a datetime, a string or a timestamp.
Demonstrating :
Tested py3.8.10, wx4.x and Linux.
Tested py3.10.5, wx4.2.1 and Win11.
Are you ready to use some samples ?
Test, modify, correct, complete, improve and share your discoveries !
First example
1 """
2 MiniDateTime.py
3
4 A custom class that allows selection of both date and time, with the ability to customise
5 the calendar and the output format.
6
7 Inspired by the need to set deadlines, where not only were a date and time required but also
8 the ability to return a timestamp, which is easy to store in a database, easily sorted and
9 easily converted back into a date. (Thus GetTimeStamp())
10
11 Works with wx.DateTime or python datetime values
12 With or without an activating button
13 Uses wx.adv.GenericCalendarCtrl and wx.adv.TimePickerCtrl
14 Uses locale to enable different languages for the calendar
15
16 MiniDateTime(parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
17 style=wx.BORDER_SIMPLE, name="MiniDateTime", date=0, formatter=''):
18
19 @param parent: Parent window. Must not be None.
20 @param id: identifier. A value of -1 indicates a default value.
21 @param pos: MiniDateTime position. If the position (-1, -1) is specified
22 then a default position is chosen.
23 @param size: If the default size (-1, -1), is specified then a default size is calculated.
24 Size should be able to accomodate the specified formatter string + button
25 @param style: Alignment (Left,Middle,Right).
26 @param name: Widget name.
27 @param date: Initial date - invalid date = now
28 @param formatter A date formatting string in the form of a lambda function
29 default lambda dt: dt.FormatISOCombined(sep=b' ')
30 = ISO 8601 format "YYYY-MM-DD HH:MM:SS".
31 or a lambda function with a format string
32 e.g.:
33 format = lambda dt: (f'{dt.Format("%a %d-%m-%Y %H:%M:%S")}')
34 format = lambda dt: (f'{dt.Format("%A %d %B %Y %H:%M:%S")}')
35 format = lambda dt: (f'{dt.Format("%a %d %B %Y %I:%M %p")}')
36 or
37 fmt = "%Y/%m/%d %H:%M:%S"
38 format = lambda dt: (dt.Format(fmt))
39 format = lambda dt: (dt.Format("%Y/%m/%d %H:%M:%S"))
40 format = lambda dt: (dt.FormatISOCombined(sep=b' '))
41
42 TextCtrl Styles: wx.TE_READONLY (Default)
43 wx.TE_RIGHT
44 wx.TE_LEFT
45 wx.TE_CENTRE
46
47 wx.BORDER_NONE is always applied to the internal textctrl
48 wx.BORDER_SIMPLE is the default border for the control itself
49
50 Events: EVT_DATE_CHANGED A date change occurred in the control
51
52 Event Functions:
53 GetValue() Returns formatted date in the event as a string
54
55 GetDate() Returns wxDateTime date in the event
56
57 GetDateTime() Returns python datetime of date in the event
58
59 GetTimeStamp() Returns seconds since Jan 1, 1970 UTC for selected date
60
61 Functions:
62 GetValue() Returns wxDateTime date in the event as a string
63
64 GetDate() Returns wxDateTime date in the control
65
66 GetDateTimeValue() Returns python datetime of date in the control
67
68 GetTimeStamp() Returns seconds since Jan 1, 1970 UTC for selected datetime
69
70 GetLocale() Returns tuple of current language code and encoding
71
72 SetValue(date) Sets the date in the control
73 expects a wx.DateTime, a python datetime datetime or a datetime timestamp
74 Any invalid date defaults to wx.DateTime.Now()
75 Milliseconds are stripped off
76
77 SetFormatter(formatter) Date format in the form of a lambda
78 default: lambda dt: dt.FormatISOCombined(sep=b' ') (see above)
79
80 SetButton(Boolean) Shows or Hides Ctrl Button
81
82 SetLocale(locale) Set the locale for Calendar day and month names
83 e.g. 'de_DE.UTF-8' German
84 'es_ES.UTF-8' Spanish
85 depends on the locale being available on the machine
86
87 SetCalendarStyle(style)
88 wx.adv.CAL_SUNDAY_FIRST: Show Sunday as the first day in the week (not in wxGTK)
89 wx.adv.CAL_MONDAY_FIRST: Show Monday as the first day in the week (not in wxGTK)
90 wx.adv.CAL_SHOW_HOLIDAYS: Highlight holidays in the calendar (only generic)
91 wx.adv.CAL_NO_YEAR_CHANGE: Disable the year changing (deprecated, only generic)
92 wx.adv.CAL_NO_MONTH_CHANGE: Disable the month (and, implicitly, the year) changing
93 wx.adv.CAL_SHOW_SURROUNDING_WEEKS: Show the neighbouring weeks in the previous and next months
94 wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION: more compact, style for the month and year selection controls.
95 wx.adv.CAL_SHOW_WEEK_NUMBERS
96
97 SetCalendarHighlights(colFg, colBg) Calendar and TimeCtrl highlight colours
98
99 SetCalendarHeaders(colFg, colBg) Calendar Header colours
100
101 SetCalendarFg(colFg) Calendar ForegroundColour
102
103 SetCalendarBg(colBg) Calendar & Ctrl BackgroundColour
104
105 Default Values:
106 date - Now
107 style - READ_ONLY
108
109 Author: J Healey
110 Created: 04/12/2022
111 Copyright: J Healey
112 License: GPL 3 or any later version
113 Email: <rolfofsaxony@gmx.com>
114
115 Usage example:
116
117 import wx
118 import minidatetime as MDT
119 class Frame(wx.Frame):
120 def __init__(self, parent):
121 wx.Frame.__init__(self, parent, -1, "MiniDateTime Demo")
122 format = (lambda dt: (f'{dt.Format("%A %d-%m-%Y %H:%M:%S")}'))
123 panel = wx.Panel(self)
124 mdp = MDT.MiniDateTime(panel, -1, pos=(50, 50), size=(280,-1), style=0, date=0, formatter=format)
125 self.Show()
126
127 app = wx.App()
128 frame = Frame(None)
129 app.MainLoop()
130
131 """
132
133 import wx
134 import wx.adv
135 from wx.lib.embeddedimage import PyEmbeddedImage
136 import datetime
137 import locale
138
139 img = PyEmbeddedImage(
140 b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAABhGlDQ1BJQ0MgcHJvZmlsZQAA'
141 b'KJF9kT1Iw0AcxV9TiyJVB4uIOGSoThZERRy1CkWoEGqFVh1MLv2CJg1Jiouj4Fpw8GOx6uDi'
142 b'rKuDqyAIfoA4OjkpukiJ/0sKLWI8OO7Hu3uPu3eAUC8zzeoYBzTdNlOJuJjJroqdrwgjhF4M'
143 b'QJSZZcxJUhK+4+seAb7exXiW/7k/R4+asxgQEIlnmWHaxBvE05u2wXmfOMKKskp8Tjxm0gWJ'
144 b'H7muePzGueCywDMjZjo1TxwhFgttrLQxK5oa8RRxVNV0yhcyHquctzhr5Spr3pO/MJzTV5a5'
145 b'TnMYCSxiCRJEKKiihDJsxGjVSbGQov24j3/I9UvkUshVAiPHAirQILt+8D/43a2Vn5zwksJx'
146 b'IPTiOB8jQOcu0Kg5zvex4zROgOAzcKW3/JU6MPNJeq2lRY+Avm3g4rqlKXvA5Q4w+GTIpuxK'
147 b'QZpCPg+8n9E3ZYH+W6B7zeutuY/TByBNXSVvgINDYLRA2es+7+5q7+3fM83+fgBDkHKUu8H2'
148 b'wwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAulJREFUOMvNVTFI80AY/VIirVh1qAoVLbWLVYeC'
149 b'i/aQiFIFV4WCi4OD4CAIDgXnDioKiludHFxEKNi6KHRQkkZEFKEhDqI/wVwrhLSIBkzJ/cP9'
150 b'lLZW7eDwvyEJX969e/e93IUhhMBvwAa/hAqhXC63sLDwmaTr+vT0dLFY/EaIpbf39/etrS1F'
151 b'Ufb29lwuVxWpUCjE4/HV1VW/3z8/P19biRBCCLm5uQGAvr4+AOjv76/i0PrAwAAVJbXAlg9Y'
152 b'Wlpqb2+/uLiQJKm8vry83Nraend3l8lkvlxbuaPR0dGNjY3PnOHh4d3dXafTWZcjhFAymbQs'
153 b'KxwOq6ra2dlJrxhjt9uNMeZ5PhAI/Jway7JOp7OlpUVVVYRQNptFCGGMg8GgqqrBYPDt7e3n'
154 b'1CheX191Xff5fDzP9/T08Dzv8/nS6XQgEMAYsywry7Ldbv+hRxzHpVIpABAEAQBEUQSATCZz'
155 b'f39/eno6OztbMr65uakoSlWPbFUxUxfU0fX1tcfjicVik5OTg4ODPM/f3t4mEonLy8tQKETn'
156 b'q8uRIAjRaNTlcsmyTAiRJCmVShFCTNOk4T4+PpYcVQhhjAVByOVygiDoui7LMtWlnLW1NY7j'
157 b'6PPHx8fc3FwkEqm9NEmSEEIPDw8IoaamJlEUJyYmhoaG6FuGYUrMhoaGxcXF9fX1fD5fHb9l'
158 b'Wb29vaIoer3edDrNMIwsyzMzMzbbP47D4TAMo/QRdHR0AMDLy0tF/B6P5/n5uaurq6R7dnZm'
159 b't9sLhUKp4vV6r66uuru7o9Ho1NSUaZoA0NzcXNFsQkg+n/9TBk3Tjo6O/H6/YRiUYFnW09PT'
160 b'wcFBW1vbzs7O8fExPTYqml0TiqI0NjaenJxU1Q3D0DSN47jt7e3q1L7C4eEhACQSiWKxWCpm'
161 b's9mVlZXx8XFN0+oVMk0zFosBQDgc3t/fj8fjkUjE7XaPjY2pqlrOZOo5/BVFSSaT5+fndPeG'
162 b'QqGRkRGHw1HOYf67v8hfVpeRQVPNMc8AAAAASUVORK5CYII=')
163
164 mdtEVT = wx.NewEventType()
165 EVT_DATETIME_SELECTED = wx.PyEventBinder(mdtEVT, 1)
166
167 class mdtEvent(wx.PyCommandEvent):
168 def __init__(self, eventType, eventId=1, date=None, value=''):
169 """
170 Default class constructor.
171
172 :param `eventType`: the event type;
173 :param `eventId`: the event identifier.
174 """
175 wx.PyCommandEvent.__init__(self, eventType, eventId)
176 self._eventType = eventType
177 self.date = date
178 self.value = value
179
180 def GetDate(self):
181 """
182 Retrieve the date value of the control at the time
183 this event was generated, Returning a wx.DateTime object"""
184 return self.date
185
186 def GetValue(self):
187 """
188 Retrieve the formatted date value of the control at the time
189 this event was generated, Returning a string"""
190 return self.value.title()
191
192 def GetTimeStamp(self):
193 """
194 Retrieve the date value represented as seconds since Jan 1, 1970 UTC.
195 Returning a integer
196 """
197 return int(self.date.GetValue()/1000)
198
199 def GetDateTime(self):
200 """
201 Retrieve the date value of the control at the time
202 this event was generated, Returning a python datetime object"""
203 return wx.wxdate2pydate(self.date)
204
205
206 class MiniDateTime(wx.Control):
207 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
208 style=wx.BORDER_SIMPLE, name="MiniDateTime", date=0, formatter=''):
209
210 wx.Control.__init__(self, parent, id, pos=pos, size=size, style=style, name=name)
211 self.parent = parent
212 self._date = date
213 if formatter:
214 format = formatter
215 else:
216 format = lambda dt: dt.FormatISOCombined(sep=b' ')
217 font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
218 #self.SetWindowStyle(wx.BORDER_NONE)
219 self._style = style
220 self._calendar_style = wx.adv.CAL_MONDAY_FIRST
221 self._calendar_headercolours = None
222 self._calendar_highlightcolours = None
223 self._calendar_Bg = None
224 self._calendar_Fg = None
225 if size == wx.DefaultSize:
226 dc = wx.ScreenDC()
227 dc.SetFont(font)
228 trialdate = format(wx.DateTime(28,9,2022)) # a Wednesday in September = longest names in English
229 w, h = dc.GetTextExtent(trialdate)
230 size = (w+64, -1) # Add image width (24) plus a buffer
231 del dc
232 self._veto = False
233
234 txtstyle = wx.TE_READONLY
235
236 if style & wx.TE_LEFT or style == wx.TE_LEFT:
237 txtstyle = txtstyle | wx.TE_LEFT
238 elif style & wx.TE_RIGHT:
239 txtstyle = txtstyle | wx.TE_RIGHT
240 else:
241 txtstyle = txtstyle | wx.TE_CENTRE
242 if style & wx.TE_READONLY:
243 txtstyle = txtstyle | wx.TE_READONLY
244 if style & wx.BORDER_NONE:
245 txtstyle = txtstyle | wx.BORDER_NONE
246
247 # MiniDateTime Picker
248
249 self.ctl = wx.TextCtrl(self, id, value=str(self._date),
250 pos=pos, size=size, style=txtstyle, name=name)
251 self.button = wx.BitmapButton(self, -1, bitmap=img.Bitmap)
252 self.MinSize = self.GetBestSize()
253
254 self._formatter = format
255 self.button.Bind(wx.EVT_BUTTON, self.OnCalendar)
256 self.ctl.Bind(wx.EVT_LEFT_DOWN, self.OnCalendar)
257 self.SetValue(date)
258
259 sizer = wx.BoxSizer(wx.HORIZONTAL)
260 sizer.Add(self.ctl, 1, wx.EXPAND, 0)
261 sizer.Add(self.button, 0, wx.ALIGN_CENTER_VERTICAL, 0)
262 self.SetSizerAndFit(sizer)
263 self.Show()
264
265 def OnCalendar(self, _event=None):
266 window = CalendarPopup(
267 self, self._date, self.OnDate, self.GetTopLevelParent(), wx.SIMPLE_BORDER)
268 pos = self.ClientToScreen((0, 0))
269 size = self.GetSize()
270 window.Position(pos, (0, size.height))
271
272 def SetFormatter(self, formatter):
273 '''formatter will be called with a wx.DateTime'''
274 self._formatter = formatter
275 self.OnDate(self._date)
276
277 def SetLocale(self, alias):
278 try:
279 locale.setlocale(locale.LC_TIME, locale=alias)
280 except Exception as e:
281 locale.setlocale(locale.LC_TIME, locale='')
282 self.SetValue(self._date)
283
284 def SetCalendarStyle(self, style=0):
285 self._calendar_style = style
286
287 def SetCalendarHeaders(self, colFg=wx.NullColour, colBg=wx.NullColour):
288 self._calendar_headercolours = colFg, colBg
289
290 def SetCalendarHighlights(self, colFg=wx.NullColour, colBg=wx.NullColour):
291 self._calendar_highlightcolours = colFg, colBg
292
293 def SetCalendarFg(self, colFg=wx.NullColour):
294 self._calendar_Fg = colFg
295
296 def SetCalendarBg(self, colBg=wx.NullColour):
297 self._calendar_Bg = colBg
298
299 def SetButton(self, button=True):
300 if button:
301 self.button.Show()
302 else:
303 self.button.Hide()
304 self.Layout()
305
306 def OnDate(self, date):
307 self._date = date
308 self.ctl.SetValue(self._formatter(date).title())
309 self.MinSize = self.GetBestSize()
310 if self._veto:
311 self._veto = False
312 return
313 event = mdtEvent(mdtEVT, self.GetId(), date=date, value=self._formatter(date))
314 event.SetEventObject(self)
315 self.GetEventHandler().ProcessEvent(event)
316
317 def GetValue(self):
318 return self.ctl.GetValue()
319
320 def GetDate(self):
321 return self._date
322
323 def GetTimeStamp(self):
324 """
325 Retrieve the date value represented as seconds since Jan 1, 1970 UTC.
326 """
327 return int(self._date.GetValue()/1000)
328
329 def GetDateTimeValue(self):
330 """
331 Return a python datetime object"""
332 return wx.wxdate2pydate(self._date)
333
334 def GetLocale(self):
335 return locale.getlocale(category=locale.LC_TIME)
336
337 def SetValue(self, date):
338 if isinstance(date, wx.DateTime):
339 pass
340 elif isinstance(date, datetime.date):
341 date = wx.pydate2wxdate(date)
342 elif isinstance(date, int) and date > 0:
343 date = wx.DateTime.FromTimeT(date)
344 elif isinstance(date, float) and date > 0:
345 date = wx.DateTime.FromTimeT(int(date))
346 else: # Invalid date value default to now
347 date = wx.DateTime.Now()
348 date.SetSecond(0)
349 date.millisecond = 0
350 self._date = date
351 self._veto = True
352 self.SetFormatter(self._formatter)
353
354
355 class CalendarPopup(wx.PopupTransientWindow):
356 def __init__(self, parent, date, callback, *args, **kwargs):
357 '''date is the initial date; callback is called with the chosen date'''
358 super().__init__(*args, **kwargs)
359 self.callback = callback
360 self.calendar = wx.adv.GenericCalendarCtrl(self, pos=(5, 5), style=parent._calendar_style)
361 self.calendar.SetDate(date)
362 self.tpc = wx.adv.TimePickerCtrl(self, size=(140, -1),
363 style = wx.adv.TP_DEFAULT)
364 self.tpc.SetTime(date.hour, date.minute, date.second)
365 # The object returned by GetChildren is not an actual Python list, but rather a C++ object that mimics
366 # some of the behavior of a list. (Robin Dunn)
367 # https://stackoverflow.com/questions/37996119/slicing-a-wxpython-windowlist
368 wins = list(self.tpc.GetChildren())
369 try:
370 tpc_textctrl = wins[0]
371 except Exception:
372 tpc_textctrl = self.tpc
373 self.select = wx.Button(self, -1, "&Select")
374 self.quit = wx.Button(self, -1, "&Quit")
375
376 if parent._calendar_headercolours:
377 self.calendar.SetHeaderColours(parent._calendar_headercolours[0],parent._calendar_headercolours[1])
378 if parent._calendar_Bg:
379 self.calendar.SetBackgroundColour(parent._calendar_Bg)
380 self.tpc.SetBackgroundColour(parent._calendar_Bg)
381 self.SetBackgroundColour(parent._calendar_Bg)
382 if parent._calendar_highlightcolours:
383 self.calendar.SetHighlightColours(parent._calendar_highlightcolours[0],parent._calendar_highlightcolours[1])
384 tpc_textctrl.SetForegroundColour(parent._calendar_highlightcolours[0])
385 tpc_textctrl.SetBackgroundColour(parent._calendar_highlightcolours[1])
386 if parent._calendar_Fg:
387 self.calendar.SetForegroundColour(parent._calendar_Fg)
388 sizer = wx.BoxSizer(wx.VERTICAL)
389 sizer1 = wx.BoxSizer(wx.VERTICAL)
390 sizer2 = wx.BoxSizer(wx.HORIZONTAL)
391 sizer1.Add(self.calendar, 1, wx.ALL | wx.EXPAND, 5)
392 sizer1.Add(self.tpc, 0, wx.ALL | wx.EXPAND, 5)
393 sizer2.Add(self.select, 0, wx.ALL, 5)
394 sizer2.Add(self.quit, 0, wx.ALL, 5)
395 sizer.Add(sizer1)
396 sizer.Add(sizer2)
397 self.SetSizerAndFit(sizer)
398
399 self.calendar.Bind(wx.adv.EVT_CALENDAR, self.OnDateChosen)
400 self.calendar.Bind(wx.adv.EVT_CALENDAR_SEL_CHANGED, self.OnFocus)
401 self.select.Bind(wx.EVT_BUTTON, self.OnChosen)
402 self.quit.Bind(wx.EVT_BUTTON, self.OnQuit)
403 self.calendar.SetToolTip("Arrow keys and PageUp/pageDn\nAdjust the Calendar")
404 self.Popup()
405
406 def OnDateChosen(self, _event=None):
407 self.tpc.SetFocus()
408
409 def OnFocus(self, _event=None):
410 self.calendar.SetFocus()
411
412 def OnChosen(self, _event=None):
413 _date = self.calendar.GetDate()
414 _hh, _mm, _ss = self.tpc.GetTime()
415 _date.SetHour(_hh)
416 _date.SetMinute(_mm)
417 _date.SetSecond(_ss)
418 self.callback(_date)
419 self.Dismiss()
420
421 def OnQuit(self, event):
422 self.Dismiss()
423
424 class DemoFrame(wx.Frame):
425 def __init__(self, parent):
426 wx.Frame.__init__(self, parent, -1, "MiniDateTime Picker Demo")
427
428 #format = (lambda dt:
429 # (f'{dt.GetWeekDayName(dt.GetWeekDay())} {str(dt.day).zfill(2)}/{str(dt.month+1).zfill(2)}/{dt.year}')
430 # )
431
432 #format = (lambda dt: (f'{dt.Format("%A %d-%m-%Y %H:%M:%S")}'))
433 format = (lambda dt: (f'{dt.Format("%A %d %B %Y %H:%M:%S")}'))
434
435 panel = wx.Panel(self)
436
437 self.mdp = MiniDateTime(panel, -1, pos=(50, 50), style=0, date=0, formatter=format)
438 self.mdp.SetLocale('fr_FR.UTF-8')
439 #self.mdp.SetLocale('es_ES.UTF-8')
440 #self.mdp.SetFormatter(format)
441 x=datetime.datetime.now()
442 #self.mdp.SetValue(x.timestamp())
443 #self.mdp.SetValue(0)
444 #self.mdp.SetButton(False)
445 self.mdp.SetCalendarStyle(wx.adv.CAL_SHOW_WEEK_NUMBERS|wx.adv.CAL_MONDAY_FIRST)
446 #self.mdp.button.SetBackgroundColour('lightgreen')
447 self.mdp.ctl.SetBackgroundColour('lightgreen')
448 self.mdp.SetCalendarHeaders(colFg='red', colBg='lightgreen')
449 self.mdp.SetCalendarHighlights(colFg='red', colBg='lightblue')
450 self.mdp.SetCalendarBg(colBg='azure')
451 self.Bind(EVT_DATETIME_SELECTED, self.OnEvent)
452
453 def OnEvent(self, event):
454 print("\nevt", event.GetValue())
455 print("evt", event.GetDate())
456 print("evt", event.GetDateTime())
457 print("evt", event.GetTimeStamp())
458 print("func", self.mdp.GetValue())
459 print("func", self.mdp.GetDate())
460 print("func", self.mdp.GetDateTimeValue())
461 print("func", self.mdp.GetTimeStamp())
462 print("func", self.mdp.GetLocale())
463
464 if __name__ == '__main__':
465 app = wx.App()
466 frame = DemoFrame(None)
467 frame.Show()
468 app.MainLoop()
Download source
Additional Information
Link :
https://discuss.wxpython.org/t/a-date-and-time-picker/36297/3
- - - - -
https://wiki.wxpython.org/TitleIndex
Thanks to
J Healey (MiniDateTime.py coding), the wxPython community...
About this page
Date(d/m/y) Person (bot) Comments :
16/01/23 - Ecco (Created page for wxPython Phoenix).
Comments
- blah, blah, blah...