1+ from widget import Widget , family , MEDIUM
2+ from datetime import datetime , date
3+
4+
5+ BACKGROUND_COLOR = ("#F8F9FA" , "#1C1C1E" )
6+ TITLE_COLOR = ("#2C3E50" , "#FFFFFF" )
7+ DIVIDER_COLOR = ("#DEE2E6" , "#3A3A3C" )
8+ NAME_COLOR = ("#212529" , "#FFFFFF" )
9+ FUTURE_COLOR = ("#2ECC71" , "#6EE7B7" )
10+ TODAY_COLOR = ("#F1C40F" , "#FFD966" )
11+ PAST_COLOR = ("#E74C3C" , "#FF6B6B" )
12+ AGE_COLOR = ("#E67E22" , "#F39C12" )
13+
14+ ICON_COLORS = {
15+ "birthday.cake.fill" : ("#FF6B6B" , "#FF9F4A" ),
16+ "person.fill" : ("#4A90E2" , "#6AB0FF" ),
17+ "heart.fill" : ("#E8436E" , "#FF6B8B" ),
18+ "graduationcap.fill" : ("#9B6DFF" , "#B78CFF" ),
19+ "star.fill" : ("#FFC107" , "#FFD966" ),
20+ "gift.fill" : ("#38B2AC" , "#6EE7B7" ),
21+ "default" : ("#8E8E93" , "#AEAEB2" )
22+ }
23+
24+ # 纪念日列表(直接在下面修改)
25+ # 每个条目包含:name(名称), date(YYYY-MM-DD), icon(SF Symbol), type(类型)
26+ # 类型说明:
27+ # - "yearly" : 每年重复(如生日、纪念日)
28+ # - "age" : 累计天数(如“来到这个世界”)
29+ # - "once" : 一次性固定日期(如某个活动截止日)
30+ ANNIVERSARIES = [
31+ {
32+ "name" : "生日" ,
33+ "date" : "2000-01-01" ,
34+ "icon" : "birthday.cake.fill" ,
35+ "type" : "yearly"
36+ },
37+ {
38+ "name" : "来到这个世界" ,
39+ "date" : "2000-01-01" ,
40+ "icon" : "person.fill" ,
41+ "type" : "age"
42+ },
43+ {
44+ "name" : "恋爱纪念日" ,
45+ "date" : "2022-05-20" ,
46+ "icon" : "heart.fill" ,
47+ "type" : "yearly"
48+ },
49+ {
50+ "name" : "毕业日" ,
51+ "date" : "2024-07-01" ,
52+ "icon" : "graduationcap.fill" ,
53+ "type" : "yearly"
54+ }
55+ ]
56+
57+ ICON_SIZE = 20
58+ TITLE_FONT_SIZE = 16
59+ NAME_FONT_SIZE = 14
60+ DAYS_FONT_SIZE = 12
61+ SPACING = 10
62+ PADDING = 12
63+
64+
65+ def days_since_birth (birth_date ):
66+ today = datetime .now ().date ()
67+ try :
68+ birth = datetime .strptime (birth_date , "%Y-%m-%d" ).date ()
69+ except :
70+ return None
71+ return (today - birth ).days
72+
73+ def get_next_yearly (month , day ):
74+ today = datetime .now ().date ()
75+ try :
76+ target_this_year = date (today .year , month , day )
77+ except ValueError :
78+ return None
79+ if target_this_year >= today :
80+ return target_this_year
81+ else :
82+ try :
83+ return date (today .year + 1 , month , day )
84+ except ValueError :
85+ return None
86+
87+ def days_diff (item ):
88+ if item ['type' ] == 'age' :
89+ days = days_since_birth (item ['date' ])
90+ if days is None :
91+ return None , None
92+ return days , f" { days } 天"
93+ elif item ['type' ] == 'yearly' :
94+ try :
95+ parts = item ['date' ].split ('-' )
96+ if len (parts ) != 3 :
97+ return None , None
98+ month , day = int (parts [1 ]), int (parts [2 ])
99+ next_date = get_next_yearly (month , day )
100+ if next_date is None :
101+ return None , None
102+ delta = (next_date - datetime .now ().date ()).days
103+ if delta == 0 :
104+ text = "就是今天!"
105+ elif delta > 0 :
106+ text = f"还有{ delta } 天"
107+ else :
108+ text = f"已过 { - delta } 天"
109+ return delta , text
110+ except :
111+ return None , None
112+ else :
113+ try :
114+ target = datetime .strptime (item ['date' ], "%Y-%m-%d" ).date ()
115+ delta = (target - datetime .now ().date ()).days
116+ if delta == 0 :
117+ text = "就是今天!"
118+ elif delta > 0 :
119+ text = f"还有 { delta } 天"
120+ else :
121+ text = f"已过 { - delta } 天"
122+ return delta , text
123+ except :
124+ return None , None
125+
126+ def get_icon_color (icon_name ):
127+ return ICON_COLORS .get (icon_name , ICON_COLORS ["default" ])
128+
129+ def get_status_color (delta , item_type ):
130+ if item_type == 'age' :
131+ return AGE_COLOR
132+ if delta == 0 :
133+ return TODAY_COLOR
134+ elif delta > 0 :
135+ return FUTURE_COLOR
136+ else :
137+ return PAST_COLOR
138+
139+ def render_widget ():
140+ items = []
141+ for a in ANNIVERSARIES :
142+ delta , text = days_diff (a )
143+ if delta is not None :
144+ items .append ({
145+ 'name' : a ['name' ],
146+ 'delta' : delta ,
147+ 'text' : text ,
148+ 'icon' : a .get ('icon' , 'calendar' ),
149+ 'type' : a ['type' ]
150+ })
151+
152+ items .sort (key = lambda x : (x ['type' ] == 'age' , abs (x ['delta' ])))
153+ display_items = items [:4 ]
154+
155+ w = Widget (background = BACKGROUND_COLOR )
156+
157+ with w .vstack (spacing = 8 , padding = PADDING ):
158+ with w .hstack (spacing = 4 ):
159+ w .icon ("calendar" , size = ICON_SIZE - 2 , color = TITLE_COLOR , weight = "medium" )
160+ w .text ("纪念日" , size = TITLE_FONT_SIZE , weight = "semibold" , color = TITLE_COLOR )
161+
162+ w .divider (color = DIVIDER_COLOR )
163+
164+ if not display_items :
165+ with w .hstack ():
166+ w .icon ("info.circle" , size = ICON_SIZE - 2 , color = ("#6C757D" , "#8E8E93" ))
167+ w .text ("暂无纪念日,请在脚本中添加" , size = 12 , color = ("#6C757D" , "#8E8E93" ), align = "center" )
168+ else :
169+ for item in display_items :
170+ with w .hstack (spacing = SPACING , align = "center" ):
171+ icon_light , icon_dark = get_icon_color (item ['icon' ])
172+ icon_color = (icon_light , icon_dark )
173+ w .icon (item ['icon' ], size = ICON_SIZE , color = icon_color , weight = "regular" )
174+
175+ w .text (item ['name' ], size = NAME_FONT_SIZE , weight = "medium" , color = NAME_COLOR , max_lines = 1 )
176+
177+ w .spacer ()
178+
179+ status_color = get_status_color (item ['delta' ], item ['type' ])
180+ w .text (item ['text' ], size = DAYS_FONT_SIZE , color = status_color , weight = "medium" , max_lines = 1 )
181+
182+ w .render (url = "pythonide://" )
183+
184+ if __name__ == '__main__' :
185+ try :
186+ family
187+ except NameError :
188+ family = MEDIUM
189+ render_widget ()
0 commit comments