diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs | 5 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Services/OtpService.cs | 47 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/es-ES.json | 8 | ||||
| -rw-r--r-- | src/frontend/app/routes/planner.tsx | 21 |
4 files changed, 53 insertions, 28 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs b/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs index 3224515..db0b573 100644 --- a/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs +++ b/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs @@ -14,6 +14,11 @@ public class AppConfiguration public int MaxWalkTime { get; set; } = 20; public int NumItineraries { get; set; } = 4; + // Comfort/Slack Parameters + public int TransferSlackSeconds { get; set; } = 120; // Extra buffer for transfers + public int MinTransferTimeSeconds { get; set; } = 120; // Minimum transfer time + public double WalkReluctance { get; set; } = 2.0; // Slightly penalize walking to add slack + // Fare Configuration public double FareCashPerBus { get; set; } = 1.63; public double FareCardPerBus { get; set; } = 0.67; diff --git a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs index 87895d3..6b01c5c 100644 --- a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs +++ b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs @@ -98,9 +98,26 @@ public class OtpService { try { - var date = time ?? DateTime.Now; - var dateStr = date.ToString("MM/dd/yyyy", CultureInfo.InvariantCulture); - var timeStr = date.ToString("h:mm tt", CultureInfo.InvariantCulture); + // Convert the provided time to Europe/Madrid local time and pass explicit offset to OTP + var tz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid"); + DateTime utcReference; + if (time.HasValue) + { + var t = time.Value; + if (t.Kind == DateTimeKind.Unspecified) + t = DateTime.SpecifyKind(t, DateTimeKind.Utc); + utcReference = t.Kind == DateTimeKind.Utc ? t : t.ToUniversalTime(); + } + else + { + utcReference = DateTime.UtcNow; + } + + var localMadrid = TimeZoneInfo.ConvertTimeFromUtc(utcReference, tz); + var offsetSeconds = (int)tz.GetUtcOffset(localMadrid).TotalSeconds; + + var dateStr = localMadrid.ToString("MM/dd/yyyy", CultureInfo.InvariantCulture); + var timeStr = localMadrid.ToString("HH:mm", CultureInfo.InvariantCulture); var queryParams = new Dictionary<string, string> { @@ -116,9 +133,15 @@ public class OtpService { "walkSpeed", _config.WalkSpeed.ToString(CultureInfo.InvariantCulture) }, { "maxWalkDistance", _config.MaxWalkDistance.ToString() }, // Note: OTP might ignore this if it's too small { "optimize", "QUICK" }, - { "wheelchair", "false" } + { "wheelchair", "false" }, + { "timeZoneOffset", offsetSeconds.ToString(CultureInfo.InvariantCulture) } }; + // Add slack/comfort parameters + queryParams["transferSlack"] = _config.TransferSlackSeconds.ToString(); + queryParams["minTransferTime"] = _config.MinTransferTimeSeconds.ToString(); + queryParams["walkReluctance"] = _config.WalkReluctance.ToString(CultureInfo.InvariantCulture); + var queryString = string.Join("&", queryParams.Select(kvp => $"{kvp.Key}={Uri.EscapeDataString(kvp.Value)}")); var url = $"{_config.OtpPlannerBaseUrl}/plan?{queryString}"; @@ -140,14 +163,12 @@ public class OtpService private RoutePlan MapToRoutePlan(OtpPlan otpPlan) { - // Compute time offset: OTP's "date" field is the server time in millis - var otpServerTime = DateTimeOffset.FromUnixTimeMilliseconds(otpPlan.Date); - var now = DateTimeOffset.Now; - var timeOffsetSeconds = (long)(otpServerTime - now).TotalSeconds; + // OTP times are already correct when requested with explicit offset + var timeOffsetSeconds = 0L; return new RoutePlan { - Itineraries = otpPlan.Itineraries.Select(MapItinerary).ToList(), + Itineraries = otpPlan.Itineraries.Select(MapItinerary).OrderBy(i => i.DurationSeconds).ToList(), TimeOffsetSeconds = timeOffsetSeconds }; } @@ -185,8 +206,8 @@ public class OtpService return new Itinerary { DurationSeconds = otpItinerary.Duration, - StartTime = DateTimeOffset.FromUnixTimeMilliseconds(otpItinerary.StartTime).LocalDateTime, // Assuming local time or handling timezone - EndTime = DateTimeOffset.FromUnixTimeMilliseconds(otpItinerary.EndTime).LocalDateTime, + StartTime = DateTimeOffset.FromUnixTimeMilliseconds(otpItinerary.StartTime).UtcDateTime, + EndTime = DateTimeOffset.FromUnixTimeMilliseconds(otpItinerary.EndTime).UtcDateTime, WalkDistanceMeters = otpItinerary.WalkDistance, WalkTimeSeconds = otpItinerary.WalkTime, TransitTimeSeconds = otpItinerary.TransitTime, @@ -211,8 +232,8 @@ public class OtpService RouteTextColor = otpLeg.RouteTextColor, From = MapPlace(otpLeg.From), To = MapPlace(otpLeg.To), - StartTime = DateTimeOffset.FromUnixTimeMilliseconds(otpLeg.StartTime).LocalDateTime, - EndTime = DateTimeOffset.FromUnixTimeMilliseconds(otpLeg.EndTime).LocalDateTime, + StartTime = DateTimeOffset.FromUnixTimeMilliseconds(otpLeg.StartTime).UtcDateTime, + EndTime = DateTimeOffset.FromUnixTimeMilliseconds(otpLeg.EndTime).UtcDateTime, DistanceMeters = otpLeg.Distance, Geometry = DecodePolyline(otpLeg.LegGeometry?.Points), Steps = otpLeg.Steps.Select(MapStep).ToList(), diff --git a/src/frontend/app/i18n/locales/es-ES.json b/src/frontend/app/i18n/locales/es-ES.json index d7c94ea..ab18c7b 100644 --- a/src/frontend/app/i18n/locales/es-ES.json +++ b/src/frontend/app/i18n/locales/es-ES.json @@ -120,8 +120,8 @@ "searching_ellipsis": "Buscando…", "results": "Resultados", "close": "Cerrar", - "results_title": "Results", - "clear": "Clear", + "results_title": "Resultados", + "clear": "Borrar", "no_routes_found": "No se encontraron rutas", "no_routes_message": "No pudimos encontrar una ruta para tu viaje. Intenta cambiar la hora o las ubicaciones.", "walk": "Caminar", @@ -129,8 +129,8 @@ "from_to": "De {{from}} a {{to}}", "itinerary_details": "Detalles del itinerario", "back": "← Atrás", - "cash_fare": "{{amount}} €", - "card_fare": "{{amount}} €" + "fare": "{{amount}} €", + "free": "Gratuito" }, "common": { "loading": "Cargando...", diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx index 0f52fef..5121dce 100644 --- a/src/frontend/app/routes/planner.tsx +++ b/src/frontend/app/routes/planner.tsx @@ -104,10 +104,12 @@ const ItinerarySummary = ({ const startTime = new Date(itinerary.startTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", + timeZone: "Europe/Madrid", }); const endTime = new Date(itinerary.endTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", + timeZone: "Europe/Madrid", }); const walkTotals = sumWalkMetrics(itinerary.legs); @@ -121,15 +123,6 @@ const ItinerarySummary = ({ itinerary.cardFareEuro ?? busLegsCount * FARE_CARD_PER_BUS ).toFixed(2); - // Format currency based on locale (ES/GL: "1,50 €", EN: "€1.50") - const formatCurrency = (amount: string) => { - const isSpanishOrGalician = - i18n.language.startsWith("es") || i18n.language.startsWith("gl"); - return isSpanishOrGalician - ? t("planner.cash_fare", { amount }) - : t("planner.cash_fare", { amount }); - }; - return ( <div className="bg-white dark:bg-slate-800 p-4 rounded-lg shadow mb-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-slate-700 border border-gray-200 dark:border-slate-700" @@ -193,11 +186,15 @@ const ItinerarySummary = ({ <span className="flex items-center gap-3"> <span className="flex items-center gap-1 font-semibold text-slate-700 dark:text-slate-300"> <Coins className="w-4 h-4" /> - {formatCurrency(cashFare)} + {cashFare === "0.00" + ? t("planner.free") + : t("planner.fare", { amount: cashFare })} </span> <span className="flex items-center gap-1 text-slate-600 dark:text-slate-400"> <CreditCard className="w-4 h-4" /> - {t("planner.card_fare", { amount: cardFare })} + {cardFare === "0.00" + ? t("planner.free") + : t("planner.fare", { amount: cardFare })} </span> </span> </div> @@ -628,6 +625,7 @@ const ItineraryDetail = ({ {new Date(leg.startTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", + timeZone: "Europe/Madrid", })}{" "} -{" "} {( @@ -812,6 +810,7 @@ export default function PlannerPage() { ? new Date(searchTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", + timeZone: "Europe/Madrid", }) : null; |
