From da7bde38a654bf6a660d241726072dd815967f75 Mon Sep 17 00:00:00 2001 From: Paolo Donadeo Date: Mon, 18 May 2026 14:51:05 +0200 Subject: [PATCH] feat: add `--timezone` CLI option for configurable output timezone --- bin/commandLine.ml | 8 ++++++-- bin/main.ml | 3 ++- bin/utils.ml | 27 ++++++++++++++++++--------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/bin/commandLine.ml b/bin/commandLine.ml index 896d49c..7d37649 100644 --- a/bin/commandLine.ml +++ b/bin/commandLine.ml @@ -5,12 +5,16 @@ let files = let doc = "Files to process" in Arg.(non_empty & pos_all string [] & info [] ~docv:"FILE" ~doc) +let timezone = + let doc = "Target timezone for output (e.g. Europe/Rome). Defaults to local timezone." in + Arg.(value & opt (some string) None & info [ "timezone"; "z" ] ~docv:"TZ" ~doc) + let main_command f = let doc = "Convert iCalendar files to remind format" in let man = [] in Cmd.make (Cmd.info "ical2rem" ~version:"%%VERSION%%" ~doc ~man) @@ - let+ files = files in - f files + let+ files = files and+ tz = timezone in + f tz files let main f = Cmd.eval @@ main_command f diff --git a/bin/main.ml b/bin/main.ml index b4adce9..9ef6e40 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -14,7 +14,8 @@ let read_file filename = close_in ic; Bytes.unsafe_to_string s -let ical2rem ical_files = +let ical2rem tz_opt ical_files = + Utils.init_target_tz tz_opt; let good_rems = ListLabels.fold_left ~init:[] ical_files ~f:(fun good_rems_acc filename -> try diff --git a/bin/utils.ml b/bin/utils.ml index b7c6100..20c812a 100644 --- a/bin/utils.ml +++ b/bin/utils.ml @@ -1,5 +1,14 @@ open Icalendar +(** Target timezone for all timestamp conversions. Defaults to local timezone; overridden by --timezone CLI option + before any processing begins. *) +let target_tz : Timedesc.Time_zone.t ref = ref Timedesc.Time_zone.utc + +let init_target_tz (tz_opt : string option) : unit = + match tz_opt with + | None -> target_tz := Timedesc.Time_zone.local_exn () + | Some name -> target_tz := Timedesc.Time_zone.make_exn name + type months = Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec let timedesc_wd_to_ical (wd : Timedesc.weekday) : Icalendar.weekday = @@ -89,28 +98,28 @@ let string_of_span (sp : Timedesc.Span.t) : string = spf "%02d:%02d" hours minutes let timedesc_of_timestamp (ts : timestamp) : Timedesc.t = - let local_tz = Timedesc.Time_zone.local_exn () in match ts with - | `Local t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz + | `Local t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:!target_tz (* this case is not present in my current dataset… *) - | `Utc t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz + | `Utc t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:!target_tz | `With_tzid (ts, (_b, tz_name)) -> - (* Qui il timestamp è SCRITTO come se fosse UTC (+00:00) ma in realtà va interpretato con - il fuso orario indicato da tz_name. *) + (* The timestamp is stored as if it were UTC but must be interpreted in tz_name. + We reconstruct the wall-clock time in tz_name, then convert to target_tz. *) let tz = Timedesc.Time_zone.make_exn tz_name in let wrong_ts = Timedesc.Utils.timestamp_of_ptime ts in let date = Timedesc.date (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in let year, month, day = (Timedesc.Date.year date, Timedesc.Date.month date, Timedesc.Date.day date) in let time = Timedesc.time_view (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in let hour, minute, second = (time.Timedesc.Time.hour, time.Timedesc.Time.minute, time.Timedesc.Time.second) in - Timedesc.make_exn ~year ~month ~day ~hour ~minute ~second ~tz () + let t_in_named_tz = Timedesc.make_exn ~year ~month ~day ~hour ~minute ~second ~tz () in + (* Convert from tz_name to target_tz *) + Timedesc.of_timestamp_exn ~tz_of_date_time:!target_tz (Timedesc.to_timestamp_single t_in_named_tz) let timedesc_of_utc_or_timestamp_local (ts : utc_or_timestamp_local) : Timedesc.t = - let local_tz = Timedesc.Time_zone.local_exn () in match ts with - | `Local t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz + | `Local t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:!target_tz (* this case is not present in my current dataset… *) - | `Utc t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz + | `Utc t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:!target_tz (** Convert a UTC-or-local timestamp to a Timedesc.t in the given timezone. Use this (instead of [timedesc_of_utc_or_timestamp_local]) when the event has a known TZID, so that UNTIL comparisons are independent of