@@ -76,13 +76,20 @@ fn main() {
7676 let drive_dir = resolve_drive_dir ( & base_dir, & drive_lower) ;
7777
7878 // Parse optional arguments
79- let tz_offset = parse_tz_offset ( & args) ;
79+ let explicit_tz = parse_tz_offset ( & args) ;
8080 let custom_bin = parse_bin_path ( & args) ;
8181
8282 // Determine mode
8383 let mode = & args[ 3 ] ;
8484 let rust_output = match mode. as_str ( ) {
8585 "--regenerate" => {
86+ // Find baseline first for tz auto-detection
87+ let golden_baseline = find_golden_baseline_file ( & drive_dir, & drive_lower) ;
88+
89+ // Auto-detect or use explicit tz
90+ let tz_offset =
91+ explicit_tz. unwrap_or_else ( || detect_tz_from_baseline ( & golden_baseline) ) ;
92+
8693 // Regenerate mode: run uffs to produce fresh output
8794 regenerate_rust_output (
8895 & drive_dir,
@@ -224,8 +231,9 @@ fn print_usage(prog: &str) {
224231 ) ;
225232 eprintln ! ( ) ;
226233 eprintln ! ( "Options:" ) ;
227- eprintln ! ( " --tz OFFSET Timezone offset in hours (default: -7 for PDT)" ) ;
228- eprintln ! ( " Use -7 for PDT (Pacific Daylight), -8 for PST (Pacific Standard)" ) ;
234+ eprintln ! ( " --tz OFFSET Timezone offset in hours (default: auto-detect from baseline)" ) ;
235+ eprintln ! ( " Auto-detection uses Pacific time DST rules based on capture date." ) ;
236+ eprintln ! ( " Override with -7 for PDT (Mar-Nov), -8 for PST (Nov-Mar)" ) ;
229237 eprintln ! ( " --bin PATH Path to uffs binary (default: auto-detect from cargo metadata)" ) ;
230238 eprintln ! ( ) ;
231239 eprintln ! ( "The script auto-detects the drive data directory:" ) ;
@@ -234,7 +242,10 @@ fn print_usage(prog: &str) {
234242 eprintln ! ( ) ;
235243 eprintln ! ( "Examples:" ) ;
236244 eprintln ! ( " {} /Users/rnio/uffs_data F --regenerate" , prog) ;
237- eprintln ! ( " {} /Users/rnio/uffs_data F --regenerate --tz -8" , prog) ;
245+ eprintln ! (
246+ " {} /Users/rnio/uffs_data F --regenerate --tz -8 # override auto-detect" ,
247+ prog
248+ ) ;
238249 eprintln ! (
239250 " {} /Users/rnio/uffs_data F --regenerate --bin ./target/release/uffs" ,
240251 prog
@@ -245,14 +256,95 @@ fn print_usage(prog: &str) {
245256 ) ;
246257}
247258
248- /// Parse --tz argument from command line. Default: -7 (PDT).
249- fn parse_tz_offset ( args : & [ String ] ) -> i32 {
259+ /// Parse --tz argument from command line. Returns None if not specified
260+ /// (auto-detect).
261+ fn parse_tz_offset ( args : & [ String ] ) -> Option < i32 > {
250262 for i in 0 ..args. len ( ) {
251263 if args[ i] == "--tz" && i + 1 < args. len ( ) {
252- return args[ i + 1 ] . parse ( ) . unwrap_or ( -7 ) ;
264+ return Some ( args[ i + 1 ] . parse ( ) . unwrap_or ( -7 ) ) ;
265+ }
266+ }
267+ None // Auto-detect from baseline
268+ }
269+
270+ /// Auto-detect timezone offset from C++ baseline file.
271+ /// Extracts a date from the baseline and determines PDT vs PST based on Pacific
272+ /// DST rules. Pacific DST: 2nd Sunday of March 2:00 AM to 1st Sunday of
273+ /// November 2:00 AM
274+ fn detect_tz_from_baseline ( baseline_path : & Path ) -> i32 {
275+ // Read first few lines to find a data line with a timestamp
276+ let file = match std:: fs:: File :: open ( baseline_path) {
277+ Ok ( f) => f,
278+ Err ( _) => return -7 , // Default to PDT if can't read
279+ } ;
280+ let reader = std:: io:: BufReader :: new ( file) ;
281+
282+ for line in std:: io:: BufRead :: lines ( reader) . take ( 10 ) . flatten ( ) {
283+ // Look for a timestamp pattern: YYYY-MM-DD HH:MM:SS
284+ if let Some ( date_match) = extract_date_from_line ( & line) {
285+ let offset = pacific_tz_offset ( date_match. 0 , date_match. 1 , date_match. 2 ) ;
286+ println ! (
287+ "Auto-detected timezone from baseline date {}-{:02}-{:02}: {} ({})" ,
288+ date_match. 0 ,
289+ date_match. 1 ,
290+ date_match. 2 ,
291+ offset,
292+ if offset == -7 { "PDT" } else { "PST" }
293+ ) ;
294+ return offset;
295+ }
296+ }
297+
298+ println ! ( "Could not auto-detect timezone, defaulting to -7 (PDT)" ) ;
299+ -7
300+ }
301+
302+ /// Extract (year, month, day) from a CSV line containing a timestamp.
303+ fn extract_date_from_line ( line : & str ) -> Option < ( i32 , u32 , u32 ) > {
304+ // Look for pattern: YYYY-MM-DD (4 digits, dash, 2 digits, dash, 2 digits)
305+ let bytes = line. as_bytes ( ) ;
306+ for i in 0 ..bytes. len ( ) . saturating_sub ( 10 ) {
307+ if bytes[ i] . is_ascii_digit ( )
308+ && bytes[ i + 1 ] . is_ascii_digit ( )
309+ && bytes[ i + 2 ] . is_ascii_digit ( )
310+ && bytes[ i + 3 ] . is_ascii_digit ( )
311+ && bytes[ i + 4 ] == b'-'
312+ && bytes[ i + 5 ] . is_ascii_digit ( )
313+ && bytes[ i + 6 ] . is_ascii_digit ( )
314+ && bytes[ i + 7 ] == b'-'
315+ && bytes[ i + 8 ] . is_ascii_digit ( )
316+ && bytes[ i + 9 ] . is_ascii_digit ( )
317+ {
318+ let year: i32 = line[ i..i + 4 ] . parse ( ) . ok ( ) ?;
319+ let month: u32 = line[ i + 5 ..i + 7 ] . parse ( ) . ok ( ) ?;
320+ let day: u32 = line[ i + 8 ..i + 10 ] . parse ( ) . ok ( ) ?;
321+ if year >= 2000 && year <= 2100 && month >= 1 && month <= 12 && day >= 1 && day <= 31 {
322+ return Some ( ( year, month, day) ) ;
323+ }
253324 }
254325 }
255- -7 // Default to PDT
326+ None
327+ }
328+
329+ /// Determine Pacific timezone offset for a given date.
330+ /// Returns -7 for PDT (Daylight), -8 for PST (Standard).
331+ /// Pacific DST: 2nd Sunday of March 2:00 AM to 1st Sunday of November 2:00 AM
332+ fn pacific_tz_offset ( year : i32 , month : u32 , day : u32 ) -> i32 {
333+ // Simple rule: March 15 - November 1 is approximately PDT
334+ // More precise would calculate exact Sunday transitions, but this is close
335+ // enough
336+ let dst_start = ( 3 , 8 ) ; // March 8 (earliest 2nd Sunday)
337+ let dst_end = ( 11 , 1 ) ; // November 1 (earliest 1st Sunday)
338+
339+ if month > dst_start. 0 && month < dst_end. 0 {
340+ -7 // PDT: April through October
341+ } else if month == dst_start. 0 && day >= 8 {
342+ -7 // PDT: March 8+ (approx 2nd Sunday)
343+ } else if month == dst_end. 0 && day < 8 {
344+ -7 // PDT: November 1-7 (before 1st Sunday ends DST)
345+ } else {
346+ -8 // PST: November 8+ through early March
347+ }
256348}
257349
258350/// Parse --bin argument from command line. Returns None if not specified.
0 commit comments