@@ -149,6 +149,23 @@ def get_content_for_date(content, start_pos):
149149 return content [start_pos :start_pos + next_date_match .start ()]
150150 return content [start_pos :]
151151
152+ def get_local_day_bounds_for_label (label_date , user_tz ):
153+ """
154+ Given a program label date (UTC tz-aware datetime) and a user's timezone,
155+ compute the start and end of that calendar day in the user's local time.
156+
157+ Example: label 2024-10-17 and Asia/Shanghai ->
158+ local_start = 2024-10-17 00:00+08:00, local_end = 2024-10-18 00:00+08:00.
159+ """
160+ try :
161+ local_start_naive = datetime (label_date .year , label_date .month , label_date .day , 0 , 0 , 0 )
162+ local_start = user_tz .localize (local_start_naive )
163+ except Exception :
164+ # Fallback: construct with tzinfo if localize is unavailable
165+ local_start = datetime (label_date .year , label_date .month , label_date .day , 0 , 0 , 0 , tzinfo = user_tz )
166+ local_end = local_start + timedelta (days = 1 )
167+ return local_start , local_end
168+
152169
153170def check_md_content (file_content , date , user_tz ):
154171 """
@@ -157,16 +174,18 @@ def check_md_content(file_content, date, user_tz):
157174 try :
158175 content = extract_content_between_markers (file_content )
159176 # 直接使用 UTC 日期进行匹配,因为用户写日期标题时使用的是标准日期格式
160- utc_date = date .replace (hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
161- current_date_match = find_date_in_content (content , utc_date )
177+ # Use the label date directly for matching (the table header uses the same
178+ # calendar date regardless of timezone).
179+ label_date = date .replace (hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
180+ current_date_match = find_date_in_content (content , label_date )
162181
163182 if not current_date_match :
164- logging .info (f"No match found for date { utc_date .strftime ('%Y-%m-%d' )} " )
183+ logging .info (f"No match found for date { label_date .strftime ('%Y-%m-%d' )} " )
165184 return False
166185
167186 date_content = get_content_for_date (content , current_date_match .end ())
168187 date_content = re .sub (r'\s' , '' , date_content )
169- logging .info (f"Content length for { utc_date .strftime ('%Y-%m-%d' )} : { len (date_content )} " )
188+ logging .info (f"Content length for { label_date .strftime ('%Y-%m-%d' )} : { len (date_content )} " )
170189 return len (date_content ) > 10
171190 except Exception as e :
172191 logging .error (f"Error in check_md_content: { str (e )} " )
@@ -184,23 +203,21 @@ def get_user_study_status(nickname):
184203 now_local = datetime .now (user_tz )
185204
186205 for date in get_date_range ():
187- # Treat each UTC program day as a 24h window [start_utc, next_start_utc)
206+ # Keyed by UTC midnight for consistency across the script
188207 start_utc = date .replace (hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
189- next_start_utc = (start_utc + timedelta (days = 1 ))
190208
191- # Convert window edges into user's local time for boundary checks
192- start_local = start_utc .astimezone (user_tz )
193- end_local = next_start_utc .astimezone (user_tz )
209+ # Use the user's local calendar day boundaries for the label date
210+ start_local , end_local = get_local_day_bounds_for_label (date , user_tz )
194211
195212 if now_local < start_local :
196213 # Future day for this user
197- user_status [date ] = " "
214+ user_status [start_utc ] = " "
198215 elif now_local >= end_local :
199- # Past day: must be ✅ or ⭕️
200- user_status [date ] = "✅" if check_md_content (file_content , date , user_tz ) else "⭕️"
216+ # Past day in user's local time
217+ user_status [start_utc ] = "✅" if check_md_content (file_content , date , user_tz ) else "⭕️"
201218 else :
202219 # In-progress (today for this user): show ✅ if already posted, else blank
203- user_status [date ] = "✅" if check_md_content (file_content , date , user_tz ) else " "
220+ user_status [start_utc ] = "✅" if check_md_content (file_content , date , user_tz ) else " "
204221 logging .info (f"Successfully processed file for user: { nickname } " )
205222 except FileNotFoundError :
206223 logging .error (f"Error: Could not find file { file_name } " )
@@ -322,18 +339,16 @@ def generate_user_row(user):
322339 date_range = get_date_range ()
323340
324341 for i , date in enumerate (date_range ):
325- # UTC window for the program day
342+ # UTC key for this program label day
326343 start_utc = date .astimezone (pytz .UTC ).replace (hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
327- next_start_utc = start_utc + timedelta (days = 1 )
328- # Localized boundaries
329- start_local = start_utc .astimezone (user_tz )
330- end_local = next_start_utc .astimezone (user_tz )
344+ # Local day boundaries for this label date
345+ start_local , end_local = get_local_day_bounds_for_label (date , user_tz )
331346
332347 if is_eliminated :
333348 new_row += " |"
334349 continue
335350
336- # Future day for this user
351+ # Future day for this user (based on local midnight)
337352 if now_local < start_local :
338353 new_row += " |"
339354 continue
@@ -348,7 +363,8 @@ def generate_user_row(user):
348363 for day_idx in range (cycle_start_day , min (cycle_end_day + 1 , i + 1 )):
349364 if day_idx < len (date_range ):
350365 check_start_utc = date_range [day_idx ].astimezone (pytz .UTC ).replace (hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
351- check_end_local = (check_start_utc + timedelta (days = 1 )).astimezone (user_tz )
366+ # End of the label day in user's local timezone
367+ _ , check_end_local = get_local_day_bounds_for_label (date_range [day_idx ], user_tz )
352368 # Only count days that have fully ended in user's local time
353369 if now_local >= check_end_local :
354370 status = user_status .get (check_start_utc , "⭕️" )
0 commit comments