From 87ff8f44d76def8cc31c4a6402bbcf8601105e08 Mon Sep 17 00:00:00 2001 From: yarnom Date: Mon, 1 Sep 2025 19:45:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=9A=84=E4=B8=80=E4=BA=9Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/weatherstation/main.go | 30 +++++++++++++++++++++++ internal/tools/exporter.go | 50 ++++++++++++++++++++++++++++++++++++++ static/js/weather-table.js | 13 +++++++--- templates/index.html | 2 +- 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/cmd/weatherstation/main.go b/cmd/weatherstation/main.go index f02730b..b86cca3 100644 --- a/cmd/weatherstation/main.go +++ b/cmd/weatherstation/main.go @@ -38,6 +38,10 @@ func main() { var historicalEnd = flag.String("historical_end", "", "历史数据结束日期(格式YYYY-MM-DD)") // 覆盖风:使用彩云实况替换导出中的风速/风向 var useWindOverride = flag.Bool("wind", false, "使用彩云实况覆盖导出CSV中的风速/风向") + // 历史CSV导出 + var exportRangeOnly = flag.Bool("export_range", false, "按日期范围导出10分钟CSV(含ZTD融合),并退出。日期格式支持 YYYY-MM-DD 或 YYYYMMDD") + var exportStart = flag.String("export_start", "", "导出起始日期(含),格式 YYYY-MM-DD 或 YYYYMMDD") + var exportEnd = flag.String("export_end", "", "导出结束日期(含),格式 YYYY-MM-DD 或 YYYYMMDD") flag.Parse() // 设置日志 @@ -87,6 +91,32 @@ func main() { return } + // 历史CSV范围导出 + if *exportRangeOnly { + if *exportStart == "" || *exportEnd == "" { + log.Fatalln("export_range 需要提供 --export_start 与 --export_end 日期(YYYY-MM-DD 或 YYYYMMDD)") + } + var opts tools.ExporterOptions + if *useWindOverride { + token := os.Getenv("CAIYUN_TOKEN") + if token == "" { + token = config.GetConfig().Forecast.CaiyunToken + } + if token == "" { + log.Println("警告: 指定了 --wind 但未提供彩云 token,忽略风覆盖") + } else { + opts.OverrideWindWithCaiyun = true + opts.CaiyunToken = token + } + } + exporter := tools.NewExporterWithOptions(opts) + if err := exporter.ExportRange(context.Background(), *exportStart, *exportEnd); err != nil { + log.Fatalf("export_range 失败: %v", err) + } + log.Println("export_range 完成") + return + } + // 工具:按日期抓取当天0点到当前时间+3小时(两家) if *forecastDay != "" { if err := tools.RunForecastFetchForDay(context.Background(), *forecastDay); err != nil { diff --git a/internal/tools/exporter.go b/internal/tools/exporter.go index 8d7b16e..8b67190 100644 --- a/internal/tools/exporter.go +++ b/internal/tools/exporter.go @@ -317,6 +317,56 @@ func (e *Exporter) exportBucket(ctx context.Context, bucketStart, bucketEnd time return nil } +// 导出一个日期范围内的全部10分钟桶(CST),start 与 end 为“日期起止(含)” +func (e *Exporter) ExportRange(ctx context.Context, startDate, endDate string) error { + // 解析日期,支持 YYYY-MM-DD 或 YYYYMMDD + parse := func(s string) (time.Time, error) { + if len(s) == 8 { + // YYYYMMDD -> YYYY-MM-DD + s = s[:4] + "-" + s[4:6] + "-" + s[6:8] + } + loc := e.loc + if loc == nil { + loc = time.FixedZone("CST", 8*3600) + } + return time.ParseInLocation("2006-01-02", s, loc) + } + + fromDay, err := parse(startDate) + if err != nil { + return fmt.Errorf("起始日期解析失败: %v", err) + } + toDay, err := parse(endDate) + if err != nil { + return fmt.Errorf("结束日期解析失败: %v", err) + } + if toDay.Before(fromDay) { + return fmt.Errorf("结束日期早于起始日期") + } + + // 日期起止 -> 时间范围(含):[fromDay 00:00, toDay 23:59:59] + from := time.Date(fromDay.Year(), fromDay.Month(), fromDay.Day(), 0, 0, 0, 0, e.loc) + to := time.Date(toDay.Year(), toDay.Month(), toDay.Day(), 23, 59, 59, 0, e.loc) + + firstBucket := from.Truncate(10 * time.Minute) + lastBucket := to.Truncate(10 * time.Minute) + + for b := firstBucket; !b.After(lastBucket); b = b.Add(10 * time.Minute) { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + bucketStart := b + bucketEnd := b.Add(10 * time.Minute) + if err := e.exportBucket(ctx, bucketStart, bucketEnd); err != nil { + // 不中断整个范围导出,记录错误继续 + e.logger.Printf("范围导出: 桶 %s-%s 导出失败: %v", bucketStart.Format("2006-01-02 15:04:05"), bucketEnd.Format("2006-01-02 15:04:05"), err) + } + } + return nil +} + func (e *Exporter) lookupZTD(ctx context.Context, deviceID string, bucketEnd time.Time) string { if e.my == nil { return "" diff --git a/static/js/weather-table.js b/static/js/weather-table.js index b4c1308..9590a41 100644 --- a/static/js/weather-table.js +++ b/static/js/weather-table.js @@ -8,13 +8,18 @@ const WeatherTable = { } const showPastEl = document.getElementById('showPastForecast'); - const nowTs = Date.now(); - const future3hTs = nowTs + 3 * 60 * 60 * 1000; + const endInput = document.getElementById('endDate')?.value; + let endTs = Date.now(); + if (endInput) { + const d = new Date(endInput); + if (!isNaN(d)) endTs = d.getTime(); + } + const future3hTs = endTs + 3 * 60 * 60 * 1000; const showPast = !!(showPastEl && showPastEl.checked); const displayedForecast = forecastData.filter(item => { const t = new Date(item.date_time).getTime(); - const isFuture3h = t > nowTs && t <= future3hTs; - const isPast = t <= nowTs; + const isFuture3h = t > endTs && t <= future3hTs; + const isPast = t <= endTs; return isFuture3h || (showPast && isPast); }); const taggedHistory = historyData.map(item => ({ ...item, __source: '实测' })); diff --git a/templates/index.html b/templates/index.html index 2e92ca3..97cf34b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -533,7 +533,7 @@
-