|
1 | 1 | # openhours |
2 | 2 |
|
3 | | -A compromise of complexity of the ["opening_hours"](https://wiki.openstreetmap.org/wiki/Key:opening_hours). |
4 | | -Only the `day-day time-time" will work for now. |
| 3 | +A high-performance Go parser and evaluator for OpenStreetMap [`opening_hours`](https://wiki.openstreetmap.org/wiki/Key:opening_hours) specifications. |
5 | 4 |
|
6 | | -## Online tools |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Interval Math**: Bakes rules into disjoint week-minute time windows without bitset scanning. |
| 8 | +- **Fast Evaluation**: $O(\log N)$ point-in-time checks via binary search. |
| 9 | +- **Instance Interning**: Caches and shares identical schedules concurrently to minimize memory allocations. |
| 10 | +- **Overnight Shifts**: Supports shifts spanning across midnight (e.g. `Mo 22:00-04:00`, `Su 22:00-04:00`). |
| 11 | +- **Overrides & Exclusions**: Supports `off` / `closed` rules overriding previous rules (e.g. `Mo-Su 00:00-24:00; Tu 12:00-13:00 off`). |
| 12 | +- **Shortcuts & Open-Ended Intervals**: Supports `24/7`, open-ended (`Mo 10:00+`), day-only rules (`Mo-Fr`), and time-only rules (`10:00-12:00`). |
| 13 | +- **Duration Queries**: Find when an opening window long enough for a task of duration $D$ is available (`GetTimeToOpenForDuration` / `When`). |
| 14 | +- **JSON Support**: Native `json.Marshaler` and `json.Unmarshaler` implementations. |
| 15 | + |
| 16 | +## Online Tools |
7 | 17 |
|
8 | 18 | <https://openingh.openstreetmap.de/evaluation_tool/?setLng=en> |
9 | 19 |
|
10 | | -## Example |
| 20 | +## Examples |
11 | 21 |
|
12 | | -```go |
13 | | -oh := openhours.New("Mo-Fr 09:00-17:00") |
14 | | -t := time.Date(2019, 3, 6, 10, 0, 0, 0, time.Now().Location()) |
15 | | -// oh.Location = t.Location() //default to system but can be changed this way |
| 22 | +### Modern API (`OpeningHours`) |
16 | 23 |
|
17 | | -fmt.Println("t =", t) |
| 24 | +```go |
| 25 | +package main |
18 | 26 |
|
19 | | -isOpen := oh.Match(t) |
20 | | -fmt.Println("Is it open at this date?", isOpen) |
| 27 | +import ( |
| 28 | + "fmt" |
| 29 | + "time" |
21 | 30 |
|
22 | | -_, duration := oh.NextDur(t) |
23 | | -fmt.Println("For how long?", duration) |
| 31 | + "github.com/chneau/openhours/v2" |
| 32 | +) |
24 | 33 |
|
25 | | -_, date := oh.NextDate(t) |
26 | | -fmt.Println("When will it close?", date) |
| 34 | +func main() { |
| 35 | + // Parse an OSM opening_hours expression |
| 36 | + oh := openhours.Parse("Mo-Fr 08:00-12:00, 13:00-17:00; Sa 08:00-12:00") |
27 | 37 |
|
28 | | -fmt.Println(" +++++++++++++++++++++ ") |
| 38 | + now := time.Date(2026, 5, 18, 10, 0, 0, 0, time.UTC) // Monday 10:00 AM |
29 | 39 |
|
30 | | -t = time.Date(2019, 3, 6, 18, 0, 0, 0, time.Now().Location()) |
| 40 | + // Check if open |
| 41 | + fmt.Println("Is open:", oh.IsOpen(now)) // true |
31 | 42 |
|
32 | | -fmt.Println("t =", t) |
| 43 | + // Get current shift end |
| 44 | + if end := oh.GetCurrentShiftEnd(now); end != nil { |
| 45 | + fmt.Println("Current shift ends at:", *end) // 2026-05-18 12:00:00 |
| 46 | + } |
33 | 47 |
|
34 | | -isOpen, date = oh.NextDate(t) |
35 | | -fmt.Println("Is it open?", isOpen, "Ok, when will it open then ?", date) |
| 48 | + // Time to next open |
| 49 | + wait := oh.GetTimeToOpen(time.Date(2026, 5, 18, 12, 30, 0, 0, time.UTC)) |
| 50 | + fmt.Println("Opens in:", *wait) // 30m |
36 | 51 |
|
37 | | -/* output |
38 | | -t = 2019-03-06 10:00:00 +0000 GMT |
39 | | -Is it open at this date? true |
40 | | -For how long? 7h0m0s |
41 | | -When will it close? 2019-03-06 17:00:00 +0000 GMT |
42 | | - +++++++++++++++++++++ |
43 | | -t = 2019-03-06 18:00:00 +0000 GMT |
44 | | -Is it open? false Ok, when will it open then ? 2019-03-07 09:00:00 +0000 GMT |
45 | | -*/ |
| 52 | + // Find wait time for continuous duration (e.g. 3-hour job) |
| 53 | + waitDur := oh.GetTimeToOpenForDuration(time.Date(2026, 5, 18, 11, 0, 0, 0, time.UTC), 3*time.Hour) |
| 54 | + fmt.Println("Wait for 3h slot:", *waitDur) // 2h (opens at 13:00) |
| 55 | +} |
46 | 56 | ``` |
| 57 | + |
0 commit comments