From e0d7c3ac79bd9167ce7d295f51bf3779a04f7a07 Mon Sep 17 00:00:00 2001 From: Sofia Rodrigues Date: Thu, 14 Nov 2024 06:04:19 -0800 Subject: [PATCH] feat: add date and time functionality (#4904) This PR introduces date and time functionality to the Lean 4 Std. Breaking Changes: - `Lean.Data.Rat` is now `Std.Internal.Rat` because it's used by the DateTime library. --------- Co-authored-by: Markus Himmel Co-authored-by: Mac Malone --- nix/bootstrap.nix | 2 +- src/Lean/Data.lean | 1 - src/Lean/Meta/Tactic/LinearArith/Solver.lean | 3 +- src/Std.lean | 1 + src/{Lean/Data => Std/Internal}/Rat.lean | 6 +- src/Std/Time.lean | 231 +++ src/Std/Time/Date.lean | 8 + src/Std/Time/Date/Basic.lean | 476 ++++++ src/Std/Time/Date/PlainDate.lean | 354 +++++ src/Std/Time/Date/Unit/Basic.lean | 41 + src/Std/Time/Date/Unit/Day.lean | 221 +++ src/Std/Time/Date/Unit/Month.lean | 308 ++++ src/Std/Time/Date/Unit/Week.lean | 185 +++ src/Std/Time/Date/Unit/Weekday.lean | 134 ++ src/Std/Time/Date/Unit/Year.lean | 128 ++ src/Std/Time/Date/ValidDate.lean | 88 ++ src/Std/Time/DateTime.lean | 104 ++ src/Std/Time/DateTime/PlainDateTime.lean | 601 +++++++ src/Std/Time/DateTime/Timestamp.lean | 289 ++++ src/Std/Time/Duration.lean | 361 +++++ src/Std/Time/Format.lean | 623 ++++++++ src/Std/Time/Format/Basic.lean | 1488 ++++++++++++++++++ src/Std/Time/Internal.lean | 8 + src/Std/Time/Internal/Bounded.lean | 474 ++++++ src/Std/Time/Internal/UnitVal.lean | 125 ++ src/Std/Time/Notation.lean | 246 +++ src/Std/Time/Notation/Spec.lean | 113 ++ src/Std/Time/Time.lean | 9 + src/Std/Time/Time/Basic.lean | 7 + src/Std/Time/Time/HourMarker.lean | 71 + src/Std/Time/Time/PlainTime.lean | 297 ++++ src/Std/Time/Time/Unit/Basic.lean | 329 ++++ src/Std/Time/Time/Unit/Hour.lean | 111 ++ src/Std/Time/Time/Unit/Millisecond.lean | 96 ++ src/Std/Time/Time/Unit/Minute.lean | 96 ++ src/Std/Time/Time/Unit/Nanosecond.lean | 124 ++ src/Std/Time/Time/Unit/Second.lean | 110 ++ src/Std/Time/Zoned.lean | 180 +++ src/Std/Time/Zoned/Database.lean | 38 + src/Std/Time/Zoned/Database/Basic.lean | 114 ++ src/Std/Time/Zoned/Database/TZdb.lean | 92 ++ src/Std/Time/Zoned/Database/TzIf.lean | 328 ++++ src/Std/Time/Zoned/Database/Windows.lean | 89 ++ src/Std/Time/Zoned/DateTime.lean | 516 ++++++ src/Std/Time/Zoned/Offset.lean | 74 + src/Std/Time/Zoned/TimeZone.lean | 71 + src/Std/Time/Zoned/ZoneRules.lean | 225 +++ src/Std/Time/Zoned/ZonedDateTime.lean | 587 +++++++ src/runtime/io.cpp | 171 ++ tests/lean/hpow.lean | 5 +- tests/lean/ppNumericTypes.lean | 5 +- tests/lean/rat1.lean | 3 +- tests/lean/run/1780.lean | 2 +- tests/lean/run/timeAPI.lean | 908 +++++++++++ tests/lean/run/timeClassOperations.lean | 188 +++ tests/lean/run/timeFormats.lean | 812 ++++++++++ tests/lean/run/timeIO.lean | 83 + tests/lean/run/timeLocalDateTime.lean | 87 + tests/lean/run/timeOperations.lean | 319 ++++ tests/lean/run/timeOperationsOffset.lean | 596 +++++++ tests/lean/run/timeParse.lean | 204 +++ tests/lean/run/timeSet.lean | 100 ++ tests/lean/run/timeTzifParse.lean | 110 ++ 63 files changed, 13765 insertions(+), 11 deletions(-) rename src/{Lean/Data => Std/Internal}/Rat.lean (98%) create mode 100644 src/Std/Time.lean create mode 100644 src/Std/Time/Date.lean create mode 100644 src/Std/Time/Date/Basic.lean create mode 100644 src/Std/Time/Date/PlainDate.lean create mode 100644 src/Std/Time/Date/Unit/Basic.lean create mode 100644 src/Std/Time/Date/Unit/Day.lean create mode 100644 src/Std/Time/Date/Unit/Month.lean create mode 100644 src/Std/Time/Date/Unit/Week.lean create mode 100644 src/Std/Time/Date/Unit/Weekday.lean create mode 100644 src/Std/Time/Date/Unit/Year.lean create mode 100644 src/Std/Time/Date/ValidDate.lean create mode 100644 src/Std/Time/DateTime.lean create mode 100644 src/Std/Time/DateTime/PlainDateTime.lean create mode 100644 src/Std/Time/DateTime/Timestamp.lean create mode 100644 src/Std/Time/Duration.lean create mode 100644 src/Std/Time/Format.lean create mode 100644 src/Std/Time/Format/Basic.lean create mode 100644 src/Std/Time/Internal.lean create mode 100644 src/Std/Time/Internal/Bounded.lean create mode 100644 src/Std/Time/Internal/UnitVal.lean create mode 100644 src/Std/Time/Notation.lean create mode 100644 src/Std/Time/Notation/Spec.lean create mode 100644 src/Std/Time/Time.lean create mode 100644 src/Std/Time/Time/Basic.lean create mode 100644 src/Std/Time/Time/HourMarker.lean create mode 100644 src/Std/Time/Time/PlainTime.lean create mode 100644 src/Std/Time/Time/Unit/Basic.lean create mode 100644 src/Std/Time/Time/Unit/Hour.lean create mode 100644 src/Std/Time/Time/Unit/Millisecond.lean create mode 100644 src/Std/Time/Time/Unit/Minute.lean create mode 100644 src/Std/Time/Time/Unit/Nanosecond.lean create mode 100644 src/Std/Time/Time/Unit/Second.lean create mode 100644 src/Std/Time/Zoned.lean create mode 100644 src/Std/Time/Zoned/Database.lean create mode 100644 src/Std/Time/Zoned/Database/Basic.lean create mode 100644 src/Std/Time/Zoned/Database/TZdb.lean create mode 100644 src/Std/Time/Zoned/Database/TzIf.lean create mode 100644 src/Std/Time/Zoned/Database/Windows.lean create mode 100644 src/Std/Time/Zoned/DateTime.lean create mode 100644 src/Std/Time/Zoned/Offset.lean create mode 100644 src/Std/Time/Zoned/TimeZone.lean create mode 100644 src/Std/Time/Zoned/ZoneRules.lean create mode 100644 src/Std/Time/Zoned/ZonedDateTime.lean create mode 100644 tests/lean/run/timeAPI.lean create mode 100644 tests/lean/run/timeClassOperations.lean create mode 100644 tests/lean/run/timeFormats.lean create mode 100644 tests/lean/run/timeIO.lean create mode 100644 tests/lean/run/timeLocalDateTime.lean create mode 100644 tests/lean/run/timeOperations.lean create mode 100644 tests/lean/run/timeOperationsOffset.lean create mode 100644 tests/lean/run/timeParse.lean create mode 100644 tests/lean/run/timeSet.lean create mode 100644 tests/lean/run/timeTzifParse.lean diff --git a/nix/bootstrap.nix b/nix/bootstrap.nix index 661fa34230e7..3c2b9b9151c9 100644 --- a/nix/bootstrap.nix +++ b/nix/bootstrap.nix @@ -170,7 +170,7 @@ lib.warn "The Nix-based build is deprecated" rec { ln -sf ${lean-all}/* . ''; buildPhase = '' - ctest --output-junit test-results.xml --output-on-failure -E 'leancomptest_(doc_example|foreign)|leanlaketest_reverse-ffi' -j$NIX_BUILD_CORES + ctest --output-junit test-results.xml --output-on-failure -E 'leancomptest_(doc_example|foreign)|leanlaketest_reverse-ffi|leanruntest_timeIO' -j$NIX_BUILD_CORES ''; installPhase = '' mkdir $out diff --git a/src/Lean/Data.lean b/src/Lean/Data.lean index 222451cb4408..9f6a17d2d71d 100644 --- a/src/Lean/Data.lean +++ b/src/Lean/Data.lean @@ -29,5 +29,4 @@ import Lean.Data.Xml import Lean.Data.NameTrie import Lean.Data.RBTree import Lean.Data.RBMap -import Lean.Data.Rat import Lean.Data.RArray diff --git a/src/Lean/Meta/Tactic/LinearArith/Solver.lean b/src/Lean/Meta/Tactic/LinearArith/Solver.lean index 538358c52945..8cdda51ed674 100644 --- a/src/Lean/Meta/Tactic/LinearArith/Solver.lean +++ b/src/Lean/Meta/Tactic/LinearArith/Solver.lean @@ -6,9 +6,10 @@ Authors: Leonardo de Moura prelude import Init.Data.Ord import Init.Data.Array.DecidableEq -import Lean.Data.Rat +import Std.Internal.Rat namespace Lean.Meta.Linear +open Std.Internal structure Var where id : Nat diff --git a/src/Std.lean b/src/Std.lean index aeee8c6e31f5..e48e80154693 100644 --- a/src/Std.lean +++ b/src/Std.lean @@ -6,5 +6,6 @@ Authors: Sebastian Ullrich prelude import Std.Data import Std.Sat +import Std.Time import Std.Tactic import Std.Internal diff --git a/src/Lean/Data/Rat.lean b/src/Std/Internal/Rat.lean similarity index 98% rename from src/Lean/Data/Rat.lean rename to src/Std/Internal/Rat.lean index 127d9eb707d7..eddd2890a36e 100644 --- a/src/Lean/Data/Rat.lean +++ b/src/Std/Internal/Rat.lean @@ -8,7 +8,8 @@ import Init.NotationExtra import Init.Data.ToString.Macro import Init.Data.Int.DivMod import Init.Data.Nat.Gcd -namespace Lean +namespace Std +namespace Internal /-! Rational numbers for implementing decision procedures. @@ -144,4 +145,5 @@ instance : Coe Int Rat where coe num := { num } end Rat -end Lean +end Internal +end Std diff --git a/src/Std/Time.lean b/src/Std/Time.lean new file mode 100644 index 000000000000..14d3b57cc9e2 --- /dev/null +++ b/src/Std/Time.lean @@ -0,0 +1,231 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time +import Std.Time.Date +import Std.Time.Zoned +import Std.Time.Format +import Std.Time.DateTime +import Std.Time.Notation +import Std.Time.Duration +import Std.Time.Zoned.Database + +namespace Std +namespace Time + +/-! +# Time + +The Lean API for date, time, and duration functionalities. + +# Overview + +This module of the standard library defines various concepts related to time, such as dates, times, +time zones and durations. These types are designed to be strongly-typed and to avoid problems with +conversion. It offers both unbounded and bounded variants to suit different use cases, like +adding days to a date or representing ordinal values. + +# Date and Time Components + +Date and time components are classified into different types based how you SHOULD use them. These +components are categorized as: + +## Offset + +Offsets represent unbounded shifts in specific date or time units. They are typically used in operations +like `date.addDays` where a `Day.Offset` is the parameter. Some offsets, such as `Month.Offset`, do not +correspond directly to a specific duration in seconds, as their value depends on the context (if +the year is leap or the size of the month). Offsets with a clear correspondent to seconds can be +converted because they use an internal type called `UnitVal`. + +- Types with a correspondence to seconds: + - `Day.Offset` + - `Hour.Offset` + - `Week.Offset` + - `Millisecond.Offset` + - `Nanosecond.Offset` + - `Second.Offset` + +- Types without a correspondence to seconds: + - `Month.Offset` + - `Year.Offset` + +## Ordinal + +Ordinal types represent specific bounded values in reference to another unit, e.g., `Day.Ordinal` +represents a day in a month, ranging from 1 to 31. Some ordinal types like `Hour.Ordinal` and `Second.Ordinal`, +allow for values beyond the normal range (e.g, 60 seconds) to accommodate special cases with leap seconds +like `23:59:60` that is valid in ISO 8601. + +- Ordinal types: + - `Day.Ordinal`: Ranges from 1 to 31. + - `Day.Ordinal.OfYear`: Ranges from 1 to (365 or 366). + - `Month.Ordinal`: Ranges from 1 to 12. + - `WeekOfYear.Ordinal`: Ranges from 1 to 53. + - `Hour.Ordinal`: Ranges from 0 to 23. + - `Millisecond.Ordinal`: Ranges from 0 to 999. + - `Minute.Ordinal`: Ranges from 0 to 59. + - `Nanosecond.Ordinal`: Ranges from 0 to 999,999,999. + - `Second.Ordinal`: Ranges from 0 to 60. + - `Weekday`: That is a inductive type with all the seven days. + +## Span + +Span types are used as subcomponents of other types. They represent a range of values in the limits +of the parent type, e.g, `Nanosecond.Span` ranges from -999,999,999 to 999,999,999, as 1,000,000,000 +nanoseconds corresponds to one second. + +- Span types: + - `Nanosecond.Span`: Ranges from -999,999,999 to 999,999,999. + +# Date and Time Types + +Dates and times are made up of different parts. An `Ordinal` is an absolute value, like a specific day in a month, +while an `Offset` is a shift forward or backward in time, used in arithmetic operations to add or subtract days, months or years. +Dates use components like `Year.Ordinal`, `Month.Ordinal`, and `Day.Ordinal` to ensure they represent +valid points in time. + +Some types, like `Duration`, include a `Span` to represent ranges over other types, such as `Second.Offset`. +This type can have a fractional nanosecond part that can be negative or positive that is represented as a `Nanosecond.Span`. + +## Date +These types provide precision down to the day level, useful for representing and manipulating dates. + +- **`PlainDate`:** Represents a calendar date in the format `YYYY-MM-DD`. + +## Time +These types offer precision down to the nanosecond level, useful for representing and manipulating time of day. + +- **`PlainTime`**: Represents a time of day in the format `HH:mm:ss,sssssssss`. + +## Date and time +Combines date and time into a single representation, useful for precise timestamps and scheduling. + +- **`PlainDateTime`**: Represents both date and time in the format `YYYY-MM-DDTHH:mm:ss,sssssssss`. +- **`Timestamp`**: Represents a specific point in time with nanosecond precision. Its zero value corresponds +to the UNIX epoch. This type should be used when sending or receiving timestamps between systems. + +## Zoned date and times. +Combines date, time and time zones. + +- **`DateTime`**: Represents both date and time but with a time zone in the type constructor. +- **`ZonedDateTime`**: Is a way to represent date and time that includes `ZoneRules`, which consider +Daylight Saving Time (DST). This means it can handle local time changes throughout the year better +than a regular `DateTime`. If you want to use a specific time zone without worrying about DST, you can +use the `ofTimestampWithZone` function, which gives you a `ZonedDateTime` based only on that time zone, +without considering the zone rules, otherwise you can use `ofTimestamp` or `ofTimestampWithIdentifier`. + +## Duration +Represents spans of time and the difference between two points in time. + +- **`Duration`**: Represents the time span or difference between two `Timestamp`s values with nanosecond precision. + +# Formats + +Format strings are used to convert between `String` representations and date/time types, like `yyyy-MM-dd'T'HH:mm:ss.sssZ`. +The table below shows the available format specifiers. Some specifiers can be repeated to control truncation or offsets. +When a character is repeated `n` times, it usually truncates the value to `n` characters. + +The supported formats include: +- `G`: Represents the era, such as AD (Anno Domini) or BC (Before Christ). + - `G`, `GG`, `GGG` (short): Displays the era in a short format (e.g., "AD"). + - `GGGG` (full): Displays the era in a full format (e.g., "Anno Domini"). + - `GGGGG` (narrow): Displays the era in a narrow format (e.g., "A"). +- `y`: Represents the year of the era. + - `yy`: Displays the year in a two-digit format, showing the last two digits (e.g., "04" for 2004). + - `yyyy`: Displays the year in a four-digit format (e.g., "2004"). + - `yyyy+`: Extended format for years with more than four digits. +- `u`: Represents the year. + - `uu`: Two-digit year format, showing the last two digits (e.g., "04" for 2004). + - `uuuu`: Displays the year in a four-digit format (e.g., "2004" or "-1000"). + - `uuuu+`: Extended format for handling years with more than four digits (e.g., "12345" or "-12345"). Useful for historical dates far into the past or future! +- `D`: Represents the day of the year. +- `M`: Represents the month of the year, displayed as either a number or text. + - `M`, `MM`: Displays the month as a number, with `MM` zero-padded (e.g., "7" for July, "07" for July with padding). + - `MMM`: Displays the abbreviated month name (e.g., "Jul"). + - `MMMM`: Displays the full month name (e.g., "July"). + - `MMMMM`: Displays the month in a narrow form (e.g., "J" for July). +- `d`: Represents the day of the month. +- `Q`: Represents the quarter of the year. + - `Q`, `QQ`: Displays the quarter as a number (e.g., "3", "03"). + - `QQQ` (short): Displays the quarter as an abbreviated text (e.g., "Q3"). + - `QQQQ` (full): Displays the full quarter text (e.g., "3rd quarter"). + - `QQQQQ` (narrow): Displays the quarter as a short number (e.g., "3"). +- `w`: Represents the week of the week-based year, each week starts on Monday (e.g., "27"). +- `W`: Represents the week of the month, each week starts on Monday (e.g., "4"). +- `E`: Represents the day of the week as text. + - `E`, `EE`, `EEE`: Displays the abbreviated weekday name (e.g., "Tue"). + - `EEEE`: Displays the full day name (e.g., "Tuesday"). + - `EEEEE`: Displays the narrow day name (e.g., "T" for Tuesday). +- `e`: Represents the weekday as number or text. + - `e`, `ee`: Displays the the as a number, starting from 1 (Monday) to 7 (Sunday). + - `eee`, `eeee`, `eeeee`: Displays the weekday as text (same format as `E`). +- `F`: Represents the week of the month that the first week starts on the first day of the month (e.g., "3"). +- `a`: Represents the AM or PM designation of the day. + - `a`, `aa`, `aaa`: Displays AM or PM in a concise format (e.g., "PM"). + - `aaaa`: Displays the full AM/PM designation (e.g., "Post Meridium"). +- `h`: Represents the hour of the AM/PM clock (1-12) (e.g., "12"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `K`: Represents the hour of the AM/PM clock (0-11) (e.g., "0"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `k`: Represents the hour of the day in a 1-24 format (e.g., "24"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `H`: Represents the hour of the day in a 0-23 format (e.g., "0"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `m`: Represents the minute of the hour (e.g., "30"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `s`: Represents the second of the minute (e.g., "55"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `S`: Represents a fraction of a second, typically displayed as a decimal number (e.g., "978" for milliseconds). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `A`: Represents the millisecond of the day (e.g., "1234"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `n`: Represents the nanosecond of the second (e.g., "987654321"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `N`: Represents the nanosecond of the day (e.g., "1234000000"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `VV`: Represents the time zone ID, which could be a city-based zone (e.g., "America/Los_Angeles"), a UTC marker (`"Z"`), or a specific offset (e.g., "-08:30"). + - One or more repetitions of the character indicates the truncation of the value to the specified number of characters. +- `z`: Represents the time zone name. + - `z`, `zz`, `zzz`: Shows an abbreviated time zone name (e.g., "PST" for Pacific Standard Time). + - `zzzz`: Displays the full time zone name (e.g., "Pacific Standard Time"). +- `O`: Represents the localized zone offset in the format "GMT" followed by the time difference from UTC. + - `O`: Displays the GMT offset in a simple format (e.g., "GMT+8"). + - `OOOO`: Displays the full GMT offset, including hours and minutes (e.g., "GMT+08:00"). +- `X`: Represents the zone offset. It uses 'Z' for UTC and can represent any offset (positive or negative). + - `X`: Displays the hour offset (e.g., "-08"). + - `XX`: Displays the hour and minute offset without a colon (e.g., "-0830"). + - `XXX`: Displays the hour and minute offset with a colon (e.g., "-08:30"). + - `XXXX`: Displays the hour, minute, and second offset without a colon (e.g., "-083045"). + - `XXXXX`: Displays the hour, minute, and second offset with a colon (e.g., "-08:30:45"). +- `x`: Represents the zone offset. Similar to X, but does not display 'Z' for UTC and focuses only on positive offsets. + - `x`: Displays the hour offset (e.g., "+08"). + - `xx`: Displays the hour and minute offset without a colon (e.g., "+0830"). + - `xxx`: Displays the hour and minute offset with a colon (e.g., "+08:30"). + - `xxxx`: Displays the hour, minute, and second offset without a colon (e.g., "+083045"). + - `xxxxx`: Displays the hour, minute, and second offset with a colon (e.g., "+08:30:45"). +- `Z`: Always includes an hour and minute offset and may use 'Z' for UTC, providing clear differentiation between UTC and other time zones. + - `Z`: Displays the hour and minute offset without a colon (e.g., "+0800"). + - `ZZ`: Displays "GMT" followed by the time offset (e.g., "GMT+08:00" or "Z"). + - `ZZZ`: Displays the full hour, minute, and second offset with a colon (e.g., "+08:30:45" or "Z"). + +# Macros + +In order to help the user build dates easily, there are a lot of macros available for creating dates. +The `.sssssssss` can be ommited in most cases. + + +- **`date("uuuu-MM-dd")`**: Represents a date in the `uuuu-MM-dd` format, where `uuuu` refers to the year. +- **`time("HH:mm:ss.sssssssss")`**: Represents a time in the format `HH:mm:ss.sssssssss`, including optional support for nanoseconds. +- **`datetime("uuuu-MM-ddTHH:mm:ss.sssssssss")`**: Represents a datetime value in the `uuuu-MM-ddTHH:mm:ss.sssssssss` format, with optional nanoseconds. +- **`offset("+HH:mm")`**: Represents a timezone offset in the format `+HH:mm`, where `+` or `-` indicates the direction from UTC. +- **`timezone("NAME/ID ZZZ")`**: Specifies a timezone using a region-based name or ID, along with its associated offset. +- **`datespec("FORMAT")`**: Defines a compile-time date format based on the provided string. +- **`zoned("uuuu-MM-ddTHH:mm:ss.sssssssssZZZ")`**: Represents a `ZonedDateTime` with a fixed timezone and optional nanosecond precision. +- **`zoned("uuuu-MM-ddTHH:mm:ss.sssssssss[IDENTIFIER]")`**: Defines an `IO ZonedDateTime`, where the timezone identifier is dynamically retrieved from the default timezone database. +- **`zoned("uuuu-MM-ddTHH:mm:ss.sssssssss, timezone")`**: Represents an `IO ZonedDateTime`, using a specified `timezone` term and allowing optional nanoseconds. + +-/ diff --git a/src/Std/Time/Date.lean b/src/Std/Time/Date.lean new file mode 100644 index 000000000000..8caabbe8e108 --- /dev/null +++ b/src/Std/Time/Date.lean @@ -0,0 +1,8 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date.Basic +import Std.Time.Date.PlainDate diff --git a/src/Std/Time/Date/Basic.lean b/src/Std/Time/Date/Basic.lean new file mode 100644 index 000000000000..4d4755901faf --- /dev/null +++ b/src/Std/Time/Date/Basic.lean @@ -0,0 +1,476 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date.Unit.Basic +import Std.Time.Date.ValidDate +import Std.Time.Time.Basic + +namespace Std +namespace Time + +namespace Nanosecond +namespace Offset + +/-- +Convert `Nanosecond.Offset` into `Day.Offset`. +-/ +@[inline] +def toDays (nanoseconds : Nanosecond.Offset) : Day.Offset := + nanoseconds.div 86400000000000 + +/-- +Convert `Day.Offset` into `Nanosecond.Offset`. +-/ +@[inline] +def ofDays (days : Day.Offset) : Nanosecond.Offset := + days.mul 86400000000000 + +/-- +Convert `Nanosecond.Offset` into `Week.Offset`. +-/ +@[inline] +def toWeeks (nanoseconds : Nanosecond.Offset) : Week.Offset := + nanoseconds.div 604800000000000 + +/-- +Convert `Week.Offset` into `Nanosecond.Offset`. +-/ +@[inline] +def ofWeeks (weeks : Week.Offset) : Nanosecond.Offset := + weeks.mul 604800000000000 + +end Offset +end Nanosecond + +namespace Millisecond +namespace Offset + +/-- +Convert `Millisecond.Offset` into `Day.Offset`. +-/ +@[inline] +def toDays (milliseconds : Millisecond.Offset) : Day.Offset := + milliseconds.div 86400000 + +/-- +Convert `Day.Offset` into `Millisecond.Offset`. +-/ +@[inline] +def ofDays (days : Day.Offset) : Millisecond.Offset := + days.mul 86400000 + +/-- +Convert `Millisecond.Offset` into `Week.Offset`. +-/ +@[inline] +def toWeeks (milliseconds : Millisecond.Offset) : Week.Offset := + milliseconds.div 604800000 + +/-- +Convert `Week.Offset` into `Millisecond.Offset`. +-/ +@[inline] +def ofWeeks (weeks : Week.Offset) : Millisecond.Offset := + weeks.mul 604800000 + +end Offset +end Millisecond + +namespace Second +namespace Offset + +/-- +Convert `Second.Offset` into `Day.Offset`. +-/ +@[inline] +def toDays (seconds : Second.Offset) : Day.Offset := + seconds.div 86400 + +/-- +Convert `Day.Offset` into `Second.Offset`. +-/ +@[inline] +def ofDays (days : Day.Offset) : Second.Offset := + days.mul 86400 + +/-- +Convert `Second.Offset` into `Week.Offset`. +-/ +@[inline] +def toWeeks (seconds : Second.Offset) : Week.Offset := + seconds.div 604800 + +/-- +Convert `Week.Offset` into `Second.Offset`. +-/ +@[inline] +def ofWeeks (weeks : Week.Offset) : Second.Offset := + weeks.mul 604800 + +end Offset +end Second + +namespace Minute +namespace Offset + +/-- +Convert `Minute.Offset` into `Day.Offset`. +-/ +@[inline] +def toDays (minutes : Minute.Offset) : Day.Offset := + minutes.div 1440 + +/-- +Convert `Day.Offset` into `Minute.Offset`. +-/ +@[inline] +def ofDays (days : Day.Offset) : Minute.Offset := + days.mul 1440 + +/-- +Convert `Minute.Offset` into `Week.Offset`. +-/ +@[inline] +def toWeeks (minutes : Minute.Offset) : Week.Offset := + minutes.div 10080 + +/-- +Convert `Week.Offset` into `Minute.Offset`. +-/ +@[inline] +def ofWeeks (weeks : Week.Offset) : Minute.Offset := + weeks.mul 10080 + +end Offset +end Minute + +namespace Hour +namespace Offset + +/-- +Convert `Hour.Offset` into `Day.Offset`. +-/ +@[inline] +def toDays (hours : Hour.Offset) : Day.Offset := + hours.div 24 + +/-- +Convert `Day.Offset` into `Hour.Offset`. +-/ +@[inline] +def ofDays (days : Day.Offset) : Hour.Offset := + days.mul 24 + +/-- +Convert `Hour.Offset` into `Week.Offset`. +-/ +@[inline] +def toWeeks (hours : Hour.Offset) : Week.Offset := + hours.div 168 + +/-- +Convert `Week.Offset` into `Hour.Offset`. +-/ +@[inline] +def ofWeeks (weeks : Week.Offset) : Hour.Offset := + weeks.mul 168 + +end Offset +end Hour + +instance : HAdd Nanosecond.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.add y + +instance : HAdd Nanosecond.Offset Millisecond.Offset Nanosecond.Offset where + hAdd x y := x.add y.toNanoseconds + +instance : HAdd Nanosecond.Offset Second.Offset Nanosecond.Offset where + hAdd x y := x.add y.toNanoseconds + +instance : HAdd Nanosecond.Offset Minute.Offset Nanosecond.Offset where + hAdd x y := x.add y.toNanoseconds + +instance : HAdd Nanosecond.Offset Hour.Offset Nanosecond.Offset where + hAdd x y := x.add y.toNanoseconds + +instance : HAdd Nanosecond.Offset Day.Offset Nanosecond.Offset where + hAdd x y := x.add y.toNanoseconds + +instance : HAdd Nanosecond.Offset Week.Offset Nanosecond.Offset where + hAdd x y := x.add y.toNanoseconds + +instance : HAdd Millisecond.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.toNanoseconds.add y + +instance : HAdd Millisecond.Offset Millisecond.Offset Millisecond.Offset where + hAdd x y := x.add y + +instance : HAdd Millisecond.Offset Second.Offset Millisecond.Offset where + hAdd x y := x.add y.toMilliseconds + +instance : HAdd Millisecond.Offset Minute.Offset Millisecond.Offset where + hAdd x y := x.add y.toMilliseconds + +instance : HAdd Millisecond.Offset Hour.Offset Millisecond.Offset where + hAdd x y := x.add y.toMilliseconds + +instance : HAdd Millisecond.Offset Day.Offset Millisecond.Offset where + hAdd x y := x.add y.toMilliseconds + +instance : HAdd Millisecond.Offset Week.Offset Millisecond.Offset where + hAdd x y := x.add y.toMilliseconds + +instance : HAdd Second.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.toNanoseconds.add y + +instance : HAdd Second.Offset Millisecond.Offset Millisecond.Offset where + hAdd x y := x.toMilliseconds.add y + +instance : HAdd Second.Offset Second.Offset Second.Offset where + hAdd x y := x.add y + +instance : HAdd Second.Offset Minute.Offset Second.Offset where + hAdd x y := x.add y.toSeconds + +instance : HAdd Second.Offset Hour.Offset Second.Offset where + hAdd x y := x.add y.toSeconds + +instance : HAdd Second.Offset Day.Offset Second.Offset where + hAdd x y := x.add y.toSeconds + +instance : HAdd Second.Offset Week.Offset Second.Offset where + hAdd x y := x.add y.toSeconds + +instance : HAdd Minute.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.toNanoseconds.add y + +instance : HAdd Minute.Offset Millisecond.Offset Millisecond.Offset where + hAdd x y := x.toMilliseconds.add y + +instance : HAdd Minute.Offset Second.Offset Second.Offset where + hAdd x y := x.toSeconds.add y + +instance : HAdd Minute.Offset Minute.Offset Minute.Offset where + hAdd x y := x.add y + +instance : HAdd Minute.Offset Hour.Offset Minute.Offset where + hAdd x y := x.add y.toMinutes + +instance : HAdd Minute.Offset Day.Offset Minute.Offset where + hAdd x y := x.add y.toMinutes + +instance : HAdd Minute.Offset Week.Offset Minute.Offset where + hAdd x y := x.add y.toMinutes + +instance : HAdd Hour.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.toNanoseconds.add y + +instance : HAdd Hour.Offset Millisecond.Offset Millisecond.Offset where + hAdd x y := x.toMilliseconds.add y + +instance : HAdd Hour.Offset Second.Offset Second.Offset where + hAdd x y := x.toSeconds.add y + +instance : HAdd Hour.Offset Minute.Offset Minute.Offset where + hAdd x y := x.toMinutes.add y + +instance : HAdd Hour.Offset Hour.Offset Hour.Offset where + hAdd x y := x.add y + +instance : HAdd Hour.Offset Day.Offset Hour.Offset where + hAdd x y := x.add y.toHours + +instance : HAdd Hour.Offset Week.Offset Hour.Offset where + hAdd x y := x.add y.toHours + +instance : HAdd Day.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.toNanoseconds.add y + +instance : HAdd Day.Offset Millisecond.Offset Millisecond.Offset where + hAdd x y := x.toMilliseconds.add y + +instance : HAdd Day.Offset Second.Offset Second.Offset where + hAdd x y := x.toSeconds.add y + +instance : HAdd Day.Offset Minute.Offset Minute.Offset where + hAdd x y := x.toMinutes.add y + +instance : HAdd Day.Offset Hour.Offset Hour.Offset where + hAdd x y := x.toHours.add y + +instance : HAdd Day.Offset Day.Offset Day.Offset where + hAdd x y := x.add y + +instance : HAdd Day.Offset Week.Offset Day.Offset where + hAdd x y := x.add y.toDays + +instance : HAdd Week.Offset Nanosecond.Offset Nanosecond.Offset where + hAdd x y := x.toNanoseconds.add y + +instance : HAdd Week.Offset Millisecond.Offset Millisecond.Offset where + hAdd x y := x.toMilliseconds.add y + +instance : HAdd Week.Offset Second.Offset Second.Offset where + hAdd x y := x.toSeconds.add y + +instance : HAdd Week.Offset Minute.Offset Minute.Offset where + hAdd x y := x.toMinutes.add y + +instance : HAdd Week.Offset Hour.Offset Hour.Offset where + hAdd x y := x.toHours.add y + +instance : HAdd Week.Offset Day.Offset Day.Offset where + hAdd x y := x.toDays.add y + +instance : HAdd Week.Offset Week.Offset Week.Offset where + hAdd x y := x.add y + +instance : HSub Nanosecond.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.sub y + +instance : HSub Nanosecond.Offset Millisecond.Offset Nanosecond.Offset where + hSub x y := x.sub y.toNanoseconds + +instance : HSub Nanosecond.Offset Second.Offset Nanosecond.Offset where + hSub x y := x.sub y.toNanoseconds + +instance : HSub Nanosecond.Offset Minute.Offset Nanosecond.Offset where + hSub x y := x.sub y.toNanoseconds + +instance : HSub Nanosecond.Offset Hour.Offset Nanosecond.Offset where + hSub x y := x.sub y.toNanoseconds + +instance : HSub Nanosecond.Offset Day.Offset Nanosecond.Offset where + hSub x y := x.sub y.toNanoseconds + +instance : HSub Nanosecond.Offset Week.Offset Nanosecond.Offset where + hSub x y := x.sub y.toNanoseconds + +instance : HSub Millisecond.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.toNanoseconds.sub y + +instance : HSub Millisecond.Offset Millisecond.Offset Millisecond.Offset where + hSub x y := x.sub y + +instance : HSub Millisecond.Offset Second.Offset Millisecond.Offset where + hSub x y := x.sub y.toMilliseconds + +instance : HSub Millisecond.Offset Minute.Offset Millisecond.Offset where + hSub x y := x.sub y.toMilliseconds + +instance : HSub Millisecond.Offset Hour.Offset Millisecond.Offset where + hSub x y := x.sub y.toMilliseconds + +instance : HSub Millisecond.Offset Day.Offset Millisecond.Offset where + hSub x y := x.sub y.toMilliseconds + +instance : HSub Millisecond.Offset Week.Offset Millisecond.Offset where + hSub x y := x.sub y.toMilliseconds + +instance : HSub Second.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.toNanoseconds.sub y + +instance : HSub Second.Offset Millisecond.Offset Millisecond.Offset where + hSub x y := x.toMilliseconds.sub y + +instance : HSub Second.Offset Second.Offset Second.Offset where + hSub x y := x.sub y + +instance : HSub Second.Offset Minute.Offset Second.Offset where + hSub x y := x.sub y.toSeconds + +instance : HSub Second.Offset Hour.Offset Second.Offset where + hSub x y := x.sub y.toSeconds + +instance : HSub Second.Offset Day.Offset Second.Offset where + hSub x y := x.sub y.toSeconds + +instance : HSub Second.Offset Week.Offset Second.Offset where + hSub x y := x.sub y.toSeconds + +instance : HSub Minute.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.toNanoseconds.sub y + +instance : HSub Minute.Offset Millisecond.Offset Millisecond.Offset where + hSub x y := x.toMilliseconds.sub y + +instance : HSub Minute.Offset Second.Offset Second.Offset where + hSub x y := x.toSeconds.sub y + +instance : HSub Minute.Offset Minute.Offset Minute.Offset where + hSub x y := x.sub y + +instance : HSub Minute.Offset Hour.Offset Minute.Offset where + hSub x y := x.sub y.toMinutes + +instance : HSub Minute.Offset Day.Offset Minute.Offset where + hSub x y := x.sub y.toMinutes + +instance : HSub Minute.Offset Week.Offset Minute.Offset where + hSub x y := x.sub y.toMinutes + +instance : HSub Hour.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.toNanoseconds.sub y + +instance : HSub Hour.Offset Millisecond.Offset Millisecond.Offset where + hSub x y := x.toMilliseconds.sub y + +instance : HSub Hour.Offset Second.Offset Second.Offset where + hSub x y := x.toSeconds.sub y + +instance : HSub Hour.Offset Minute.Offset Minute.Offset where + hSub x y := x.toMinutes.sub y + +instance : HSub Hour.Offset Hour.Offset Hour.Offset where + hSub x y := x.sub y + +instance : HSub Hour.Offset Day.Offset Hour.Offset where + hSub x y := x.sub y.toHours + +instance : HSub Hour.Offset Week.Offset Hour.Offset where + hSub x y := x.sub y.toHours + +instance : HSub Day.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.toNanoseconds.sub y + +instance : HSub Day.Offset Millisecond.Offset Millisecond.Offset where + hSub x y := x.toMilliseconds.sub y + +instance : HSub Day.Offset Second.Offset Second.Offset where + hSub x y := x.toSeconds.sub y + +instance : HSub Day.Offset Minute.Offset Minute.Offset where + hSub x y := x.toMinutes.sub y + +instance : HSub Day.Offset Hour.Offset Hour.Offset where + hSub x y := x.toHours.sub y + +instance : HSub Day.Offset Day.Offset Day.Offset where + hSub x y := x.sub y + +instance : HSub Day.Offset Week.Offset Day.Offset where + hSub x y := x.sub y.toDays + +instance : HSub Week.Offset Nanosecond.Offset Nanosecond.Offset where + hSub x y := x.toNanoseconds.sub y + +instance : HSub Week.Offset Millisecond.Offset Millisecond.Offset where + hSub x y := x.toMilliseconds.sub y + +instance : HSub Week.Offset Second.Offset Second.Offset where + hSub x y := x.toSeconds.sub y + +instance : HSub Week.Offset Minute.Offset Minute.Offset where + hSub x y := x.toMinutes.sub y + +instance : HSub Week.Offset Hour.Offset Hour.Offset where + hSub x y := x.toHours.sub y + +instance : HSub Week.Offset Day.Offset Day.Offset where + hSub x y := x.toDays.sub y + +instance : HSub Week.Offset Week.Offset Week.Offset where + hSub x y := x.sub y diff --git a/src/Std/Time/Date/PlainDate.lean b/src/Std/Time/Date/PlainDate.lean new file mode 100644 index 000000000000..a85f3d0c119a --- /dev/null +++ b/src/Std/Time/Date/PlainDate.lean @@ -0,0 +1,354 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Internal +import Std.Time.Date.Basic +import Std.Internal.Rat + +namespace Std +namespace Time +open Std.Internal +open Std.Time +open Internal +open Lean + +set_option linter.all true + +/-- +`PlainDate` represents a date in the Year-Month-Day (YMD) format. It encapsulates the year, month, +and day components, with validation to ensure the date is valid. +-/ +structure PlainDate where + + /-- The year component of the date. It is represented as an `Offset` type from `Year`. -/ + year : Year.Offset + + /-- The month component of the date. It is represented as an `Ordinal` type from `Month`. -/ + month : Month.Ordinal + + /-- The day component of the date. It is represented as an `Ordinal` type from `Day`. -/ + day : Day.Ordinal + + /-- Validates the date by ensuring that the year, month, and day form a correct and valid date. -/ + valid : year.Valid month day + deriving Repr + +instance : Inhabited PlainDate where + default := ⟨1, 1, 1, by decide⟩ + +instance : BEq PlainDate where + beq x y := x.day == y.day && x.month == y.month && x.year == y.year + +namespace PlainDate + +/-- +Creates a `PlainDate` by clipping the day to ensure validity. This function forces the date to be +valid by adjusting the day to fit within the valid range to fit the given month and year. +-/ +@[inline] +def ofYearMonthDayClip (year : Year.Offset) (month : Month.Ordinal) (day : Day.Ordinal) : PlainDate := + let day := month.clipDay year.isLeap day + PlainDate.mk year month day Month.Ordinal.valid_clipDay + +instance : Inhabited PlainDate where + default := mk 0 1 1 (by decide) + +/-- +Creates a new `PlainDate` from year, month, and day components. +-/ +@[inline] +def ofYearMonthDay? (year : Year.Offset) (month : Month.Ordinal) (day : Day.Ordinal) : Option PlainDate := + if valid : year.Valid month day + then some (PlainDate.mk year month day valid) + else none + +/-- +Creates a `PlainDate` from a year and a day ordinal within that year. +-/ +@[inline] +def ofYearOrdinal (year : Year.Offset) (ordinal : Day.Ordinal.OfYear year.isLeap) : PlainDate := + let ⟨⟨month, day⟩, proof⟩ := ValidDate.ofOrdinal ordinal + ⟨year, month, day, proof⟩ + +/-- +Creates a `PlainDate` from the number of days since the UNIX epoch (January 1st, 1970). +-/ +def ofDaysSinceUNIXEpoch (day : Day.Offset) : PlainDate := + let z := day.toInt + 719468 + let era := (if z ≥ 0 then z else z - 146096).tdiv 146097 + let doe := z - era * 146097 + let yoe := (doe - doe.tdiv 1460 + doe.tdiv 36524 - doe.tdiv 146096).tdiv 365 + let y := yoe + era * 400 + let doy := doe - (365 * yoe + yoe.tdiv 4 - yoe.tdiv 100) + let mp := (5 * doy + 2).tdiv 153 + let d := doy - (153 * mp + 2).tdiv 5 + 1 + let m := mp + (if mp < 10 then 3 else -9) + let y := y + (if m <= 2 then 1 else 0) + .ofYearMonthDayClip y (.clip m (by decide)) (.clip d (by decide)) + +/-- +Returns the unaligned week of the month for a `PlainDate` (day divided by 7, plus 1). +-/ +def weekOfMonth (date : PlainDate) : Bounded.LE 1 5 := + date.day.sub 1 |>.ediv 7 (by decide) |>.add 1 + +/-- +Determines the quarter of the year for the given `PlainDate`. +-/ +def quarter (date : PlainDate) : Bounded.LE 1 4 := + date.month.sub 1 |>.ediv 3 (by decide) |>.add 1 + +/-- +Transforms a `PlainDate` into a `Day.Ordinal.OfYear`. +-/ +def dayOfYear (date : PlainDate) : Day.Ordinal.OfYear date.year.isLeap := + ValidDate.dayOfYear ⟨(date.month, date.day), date.valid⟩ + +/-- +Determines the era of the given `PlainDate` based on its year. +-/ +@[inline] +def era (date : PlainDate) : Year.Era := + date.year.era + +/-- +Checks if the `PlainDate` is in a leap year. +-/ +@[inline] +def inLeapYear (date : PlainDate) : Bool := + date.year.isLeap + +/-- +Converts a `PlainDate` to the number of days since the UNIX epoch. +-/ +def toDaysSinceUNIXEpoch (date : PlainDate) : Day.Offset := + let y : Int := if date.month.toInt > 2 then date.year else date.year.toInt - 1 + let era : Int := (if y ≥ 0 then y else y - 399).tdiv 400 + let yoe : Int := y - era * 400 + let m : Int := date.month.toInt + let d : Int := date.day.toInt + let doy := (153 * (m + (if m > 2 then -3 else 9)) + 2).tdiv 5 + d - 1 + let doe := yoe * 365 + yoe.tdiv 4 - yoe.tdiv 100 + doy + + .ofInt (era * 146097 + doe - 719468) + +/-- +Adds a given number of days to a `PlainDate`. +-/ +@[inline] +def addDays (date : PlainDate) (days : Day.Offset) : PlainDate := + let dateDays := date.toDaysSinceUNIXEpoch + ofDaysSinceUNIXEpoch (dateDays + days) + +/-- +Subtracts a given number of days from a `PlainDate`. +-/ +@[inline] +def subDays (date : PlainDate) (days : Day.Offset) : PlainDate := + addDays date (-days) + +/-- +Adds a given number of weeks to a `PlainDate`. +-/ +@[inline] +def addWeeks (date : PlainDate) (weeks : Week.Offset) : PlainDate := + let dateDays := date.toDaysSinceUNIXEpoch + let daysToAdd := weeks.toDays + ofDaysSinceUNIXEpoch (dateDays + daysToAdd) + +/-- +Subtracts a given number of weeks from a `PlainDate`. +-/ +@[inline] +def subWeeks (date : PlainDate) (weeks : Week.Offset) : PlainDate := + addWeeks date (-weeks) + +/-- +Adds a given number of months to a `PlainDate`, clipping the day to the last valid day of the month. +-/ +def addMonthsClip (date : PlainDate) (months : Month.Offset) : PlainDate := + let totalMonths := (date.month.toOffset - 1) + months + let totalMonths : Int := totalMonths + let wrappedMonths := Bounded.LE.byEmod totalMonths 12 (by decide) |>.add 1 + let yearsOffset := totalMonths / 12 + PlainDate.ofYearMonthDayClip (date.year.add yearsOffset) wrappedMonths date.day + +/-- +Subtracts `Month.Offset` from a `PlainDate`, it clips the day to the last valid day of that month. +-/ +@[inline] +def subMonthsClip (date : PlainDate) (months : Month.Offset) : PlainDate := + addMonthsClip date (-months) + +/-- +Creates a `PlainDate` by rolling over the extra days to the next month. +-/ +def rollOver (year : Year.Offset) (month : Month.Ordinal) (day : Day.Ordinal) : PlainDate := + ofYearMonthDayClip year month 1 |>.addDays (day.toOffset - 1) + +/-- +Creates a new `PlainDate` by adjusting the year to the given `year` value. The month and day remain unchanged, +and any invalid days for the new year will be handled according to the `clip` behavior. +-/ +@[inline] +def withYearClip (dt : PlainDate) (year : Year.Offset) : PlainDate := + ofYearMonthDayClip year dt.month dt.day + +/-- +Creates a new `PlainDate` by adjusting the year to the given `year` value. The month and day are rolled +over to the next valid month and day if necessary. +-/ +@[inline] +def withYearRollOver (dt : PlainDate) (year : Year.Offset) : PlainDate := + rollOver year dt.month dt.day + +/-- +Adds a given number of months to a `PlainDate`, rolling over any excess days into the following month. +-/ +def addMonthsRollOver (date : PlainDate) (months : Month.Offset) : PlainDate := + addMonthsClip (ofYearMonthDayClip date.year date.month 1) months + |>.addDays (date.day.toOffset - 1) + +/-- +Subtracts `Month.Offset` from a `PlainDate`, rolling over excess days as needed. +-/ +@[inline] +def subMonthsRollOver (date : PlainDate) (months : Month.Offset) : PlainDate := + addMonthsRollOver date (-months) + +/-- +Adds `Year.Offset` to a `PlainDate`, rolling over excess days to the next month, or next year. +-/ +@[inline] +def addYearsRollOver (date : PlainDate) (years : Year.Offset) : PlainDate := + addMonthsRollOver date (years.mul 12) + +/-- +Subtracts `Year.Offset` from a `PlainDate`, rolling over excess days to the next month. +-/ +@[inline] +def subYearsRollOver (date : PlainDate) (years : Year.Offset) : PlainDate := + addMonthsRollOver date (- years.mul 12) + +/-- +Adds `Year.Offset` to a `PlainDate`, clipping the day to the last valid day of the month. +-/ +@[inline] +def addYearsClip (date : PlainDate) (years : Year.Offset) : PlainDate := + addMonthsClip date (years.mul 12) + +/-- +Subtracts `Year.Offset` from a `PlainDate`, clipping the day to the last valid day of the month. +-/ +@[inline] +def subYearsClip (date : PlainDate) (years : Year.Offset) : PlainDate := + addMonthsClip date (- years.mul 12) + +/-- +Creates a new `PlainDate` by adjusting the day of the month to the given `days` value, with any +out-of-range days clipped to the nearest valid date. +-/ +@[inline] +def withDaysClip (dt : PlainDate) (days : Day.Ordinal) : PlainDate := + ofYearMonthDayClip dt.year dt.month days + +/-- +Creates a new `PlainDate` by adjusting the day of the month to the given `days` value, with any +out-of-range days rolled over to the next month or year as needed. +-/ +@[inline] +def withDaysRollOver (dt : PlainDate) (days : Day.Ordinal) : PlainDate := + rollOver dt.year dt.month days + +/-- +Creates a new `PlainDate` by adjusting the month to the given `month` value. +The day remains unchanged, and any invalid days for the new month will be handled according to the `clip` behavior. +-/ +@[inline] +def withMonthClip (dt : PlainDate) (month : Month.Ordinal) : PlainDate := + ofYearMonthDayClip dt.year month dt.day + +/-- +Creates a new `PlainDate` by adjusting the month to the given `month` value. +The day is rolled over to the next valid month if necessary. +-/ +@[inline] +def withMonthRollOver (dt : PlainDate) (month : Month.Ordinal) : PlainDate := + rollOver dt.year month dt.day + +/-- +Calculates the `Weekday` of a given `PlainDate` using Zeller's Congruence for the Gregorian calendar. +-/ +def weekday (date : PlainDate) : Weekday := + let days := date.toDaysSinceUNIXEpoch.val + let res := if days ≥ -4 then (days + 4) % 7 else (days + 5) % 7 + 6 + .ofOrdinal (Bounded.LE.ofNatWrapping res (by decide)) + +/-- +Determines the week of the month for the given `PlainDate`. The week of the month is calculated based +on the day of the month and the weekday. Each week starts on Monday because the entire library is +based on the Gregorian Calendar. +-/ +def alignedWeekOfMonth (date : PlainDate) : Week.Ordinal.OfMonth := + let weekday := date.withDaysClip 1 |>.weekday |>.toOrdinal |>.sub 1 + let days := date.day |>.sub 1 |>.addBounds weekday + days |>.ediv 7 (by decide) |>.add 1 + +/-- +Sets the date to the specified `desiredWeekday`. If the `desiredWeekday` is the same as the current weekday, +the original `date` is returned without modification. If the `desiredWeekday` is in the future, the +function adjusts the date forward to the next occurrence of that weekday. +-/ +def withWeekday (date : PlainDate) (desiredWeekday : Weekday) : PlainDate := + let weekday := date |>.weekday |>.toOrdinal + let offset := desiredWeekday.toOrdinal |>.subBounds weekday + + let offset : Bounded.LE 0 6 := + if h : offset.val < 0 then + offset.truncateTop (Int.le_sub_one_of_lt h) |>.addBounds (.exact 7) + |>.expandBottom (by decide) + else + offset.truncateBottom (Int.not_lt.mp h) + |>.expandTop (by decide) + + date.addDays (Day.Offset.ofInt offset.toInt) + +/-- +Calculates the week of the year starting Monday for a given year. +-/ +def weekOfYear (date : PlainDate) : Week.Ordinal := + let y := date.year + + let w := Bounded.LE.exact 10 + |>.addBounds date.dayOfYear + |>.subBounds date.weekday.toOrdinal + |>.ediv 7 (by decide) + + if h : w.val < 1 then + (y-1).weeks |>.expandBottom (by decide) + else if h₁ : w.val > y.weeks.val then + .ofNat' 1 (by decide) + else + let h := Int.not_lt.mp h + let h₁ := Int.not_lt.mp h₁ + let w := w.truncateBottom h |>.truncateTop (Int.le_trans h₁ y.weeks.property.right) + w + +instance : HAdd PlainDate Day.Offset PlainDate where + hAdd := addDays + +instance : HSub PlainDate Day.Offset PlainDate where + hSub := subDays + +instance : HAdd PlainDate Week.Offset PlainDate where + hAdd := addWeeks + +instance : HSub PlainDate Week.Offset PlainDate where + hSub := subWeeks + +end PlainDate +end Time +end Std diff --git a/src/Std/Time/Date/Unit/Basic.lean b/src/Std/Time/Date/Unit/Basic.lean new file mode 100644 index 000000000000..92b33bf07d62 --- /dev/null +++ b/src/Std/Time/Date/Unit/Basic.lean @@ -0,0 +1,41 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date.Unit.Day +import Std.Time.Date.Unit.Month +import Std.Time.Date.Unit.Year +import Std.Time.Date.Unit.Weekday +import Std.Time.Date.Unit.Week + +/-! +This module defines various units used for measuring, counting, and converting between days, months, +years, weekdays, and weeks of the year. + +The units are organized into types representing these time-related concepts, with operations provided +to facilitate conversions and manipulations between them. +-/ + +namespace Std +namespace Time +open Internal + +namespace Day.Offset + +/-- +Convert `Week.Offset` into `Day.Offset`. +-/ +@[inline] +def ofWeeks (week : Week.Offset) : Day.Offset := + week.mul 7 + +/-- +Convert `Day.Offset` into `Week.Offset`. +-/ +@[inline] +def toWeeks (day : Day.Offset) : Week.Offset := + day.ediv 7 + +end Day.Offset diff --git a/src/Std/Time/Date/Unit/Day.lean b/src/Std/Time/Date/Unit/Day.lean new file mode 100644 index 000000000000..9020ea068b47 --- /dev/null +++ b/src/Std/Time/Date/Unit/Day.lean @@ -0,0 +1,221 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time + +namespace Std +namespace Time +namespace Day +open Lean Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for days, which ranges between 1 and 31. +-/ +def Ordinal := Bounded.LE 1 31 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 1 (1 + (30 : Nat))) n) + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +instance : Inhabited Ordinal where default := 1 + +/-- +`Offset` represents an offset in days. It is defined as an `Int` with a base unit of 86400 +(the number of seconds in a day). +-/ +def Offset : Type := UnitVal 86400 + deriving Repr, BEq, Inhabited, Add, Sub, Neg, LE, LT, ToString + +instance : OfNat Offset n := ⟨UnitVal.ofNat n⟩ + +instance {x y : Offset} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Offset} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +namespace Ordinal + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 1 ≤ data ∧ data ≤ 31) : Ordinal := + Bounded.LE.mk data h + +/-- +`OfYear` represents the day ordinal within a year, which can be bounded between 1 and 365 or 366, +depending on whether it's a leap year. +-/ +def OfYear (leap : Bool) := Bounded.LE 1 (.ofNat (if leap then 366 else 365)) + +instance : Repr (OfYear leap) where + reprPrec r p := reprPrec r.val p + +instance : ToString (OfYear leap) where + toString r := toString r.val + +namespace OfYear + +/-- +Creates an ordinal for a specific day within the year, ensuring that the provided day (`data`) +is within the valid range for the year, which can be 1 to 365 or 366 for leap years. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≥ 1 ∧ data ≤ (if leap then 366 else 365) := by decide) : OfYear leap := + Bounded.LE.ofNat' data h + +end OfYear + +instance : OfNat (Ordinal.OfYear leap) n := + match leap with + | true => inferInstanceAs (OfNat (Bounded.LE 1 (1 + (365 : Nat))) n) + | false => inferInstanceAs (OfNat (Bounded.LE 1 (1 + (364 : Nat))) n) + +instance : Inhabited (Ordinal.OfYear leap) where + default := by + refine ⟨1, And.intro (by decide) ?_⟩ + split <;> simp + +/-- +Creates an ordinal from a natural number, ensuring the number is within the valid range +for days of a month (1 to 31). +-/ +@[inline] +def ofNat (data : Nat) (h : data ≥ 1 ∧ data ≤ 31 := by decide) : Ordinal := + Bounded.LE.ofNat' data h + +/-- +Creates an ordinal from a `Fin` value, ensuring it is within the valid range for days of the month (1 to 31). +If the `Fin` value is 0, it is converted to 1. +-/ +@[inline] +def ofFin (data : Fin 32) : Ordinal := + Bounded.LE.ofFin' data (by decide) + +/-- +Converts an `Ordinal` to an `Offset`. +-/ +@[inline] +def toOffset (ordinal : Ordinal) : Offset := + UnitVal.ofInt ordinal.val + +namespace OfYear + +/-- +Converts an `OfYear` ordinal to a `Offset`. +-/ +def toOffset (ofYear : OfYear leap) : Offset := + UnitVal.ofInt ofYear.val + +end OfYear +end Ordinal + +namespace Offset + +/-- +Converts an `Offset` to an `Ordinal`. +-/ +@[inline] +def toOrdinal (off : Day.Offset) (h : off.val ≥ 1 ∧ off.val ≤ 31) : Ordinal := + Bounded.LE.mk off.val h + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Day.Offset := + UnitVal.ofInt data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Day.Offset := + UnitVal.ofInt data + +/-- +Convert `Day.Offset` into `Nanosecond.Offset`. +-/ +@[inline] +def toNanoseconds (days : Day.Offset) : Nanosecond.Offset := + days.mul 86400000000000 + +/-- +Convert `Nanosecond.Offset` into `Day.Offset`. +-/ +@[inline] +def ofNanoseconds (ns : Nanosecond.Offset) : Day.Offset := + ns.ediv 86400000000000 + +/-- +Convert `Day.Offset` into `Millisecond.Offset`. +-/ +@[inline] +def toMilliseconds (days : Day.Offset) : Millisecond.Offset := + days.mul 86400000 + +/-- +Convert `Millisecond.Offset` into `Day.Offset`. +-/ +@[inline] +def ofMilliseconds (ms : Millisecond.Offset) : Day.Offset := + ms.ediv 86400000 + +/-- +Convert `Day.Offset` into `Second.Offset`. +-/ +@[inline] +def toSeconds (days : Day.Offset) : Second.Offset := + days.mul 86400 + +/-- +Convert `Second.Offset` into `Day.Offset`. +-/ +@[inline] +def ofSeconds (secs : Second.Offset) : Day.Offset := + secs.ediv 86400 + +/-- +Convert `Day.Offset` into `Minute.Offset`. +-/ +@[inline] +def toMinutes (days : Day.Offset) : Minute.Offset := + days.mul 1440 + +/-- +Convert `Minute.Offset` into `Day.Offset`. +-/ +@[inline] +def ofMinutes (minutes : Minute.Offset) : Day.Offset := + minutes.ediv 1440 + +/-- +Convert `Day.Offset` into `Hour.Offset`. +-/ +@[inline] +def toHours (days : Day.Offset) : Hour.Offset := + days.mul 24 + +/-- +Convert `Hour.Offset` into `Day.Offset`. +-/ +@[inline] +def ofHours (hours : Hour.Offset) : Day.Offset := + hours.ediv 24 + +end Offset +end Day +end Time +end Std diff --git a/src/Std/Time/Date/Unit/Month.lean b/src/Std/Time/Date/Unit/Month.lean new file mode 100644 index 000000000000..d26210080621 --- /dev/null +++ b/src/Std/Time/Date/Unit/Month.lean @@ -0,0 +1,308 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Date.Unit.Day + +namespace Std +namespace Time +namespace Month +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for months, which ranges between 1 and 12. +-/ +def Ordinal := Bounded.LE 1 12 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 1 (1 + (11 : Nat))) n) + +instance : Inhabited Ordinal where + default := 1 + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +/-- +`Offset` represents an offset in months. It is defined as an `Int`. +-/ +def Offset : Type := Int + deriving Repr, BEq, Inhabited, Add, Sub, Mul, Div, Neg, ToString, LT, LE, DecidableEq + +instance : OfNat Offset n := + ⟨Int.ofNat n⟩ + +/-- +`Quarter` represents a value between 1 and 4, inclusive, corresponding to the four quarters of a year. +-/ +def Quarter := Bounded.LE 1 4 + +namespace Quarter + +/-- +Determine the `Quarter` by the month. +-/ +def ofMonth (month : Month.Ordinal) : Quarter := + month + |>.sub 1 + |>.ediv 3 (by decide) + |>.add 1 + +end Quarter + +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Offset := + .ofNat data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Offset := + data + +end Offset + +namespace Ordinal + +/-- +The ordinal value representing the month of January. +-/ +@[inline] def january : Ordinal := 1 + +/-- +The ordinal value representing the month of February. +-/ +@[inline] def february : Ordinal := 2 + +/-- +The ordinal value representing the month of March. +-/ +@[inline] def march : Ordinal := 3 + +/-- +The ordinal value representing the month of April. +-/ +@[inline] def april : Ordinal := 4 + +/-- +The ordinal value representing the month of May. +-/ +@[inline] def may : Ordinal := 5 + +/-- +The ordinal value representing the month of June. +-/ +@[inline] def june : Ordinal := 6 + +/-- +The ordinal value representing the month of July. +-/ +@[inline] def july : Ordinal := 7 + +/-- +The ordinal value representing the month of August. +-/ +@[inline] def august : Ordinal := 8 + +/-- +The ordinal value representing the month of September. +-/ +@[inline] def september : Ordinal := 9 + +/-- +The ordinal value representing the month of October. +-/ +@[inline] def october : Ordinal := 10 + +/-- +The ordinal value representing the month of November. +-/ +@[inline] def november : Ordinal := 11 + +/-- +The ordinal value representing the month of December. +-/ +@[inline] def december : Ordinal := 12 + +/-- +Converts a `Ordinal` into a `Offset`. +-/ +@[inline] +def toOffset (month : Ordinal) : Offset := + month.val + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 1 ≤ data ∧ data ≤ 12) : Ordinal := + Bounded.LE.mk data h + +/-- +Creates an `Ordinal` from a `Nat`, ensuring the value is within bounds. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≥ 1 ∧ data ≤ 12 := by decide) : Ordinal := + Bounded.LE.ofNat' data h + +/-- +Converts a `Ordinal` into a `Nat`. +-/ +@[inline] +def toNat (month : Ordinal) : Nat := by + match month with + | ⟨.ofNat s, _⟩ => exact s + | ⟨.negSucc s, h⟩ => nomatch h.left + +/-- +Creates an `Ordinal` from a `Fin`, ensuring the value is within bounds, if its 0 then its converted +to 1. +-/ +@[inline] +def ofFin (data : Fin 13) : Ordinal := + Bounded.LE.ofFin' data (by decide) + +/-- +Transforms `Month.Ordinal` into `Second.Offset`. +-/ +def toSeconds (leap : Bool) (month : Ordinal) : Second.Offset := + let daysAcc := #[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] + let days : Day.Offset := daysAcc[month.toNat]! + let time := days.toSeconds + if leap && month.toNat ≥ 2 + then time + 86400 + else time + +/-- +Transforms `Month.Ordinal` into `Minute.Offset`. +-/ +@[inline] +def toMinutes (leap : Bool) (month : Ordinal) : Minute.Offset := + toSeconds leap month + |>.ediv 60 + +/-- +Transforms `Month.Ordinal` into `Hour.Offset`. +-/ +@[inline] +def toHours (leap : Bool) (month : Ordinal) : Hour.Offset := + toMinutes leap month + |>.ediv 60 + +/-- +Transforms `Month.Ordinal` into `Day.Offset`. +-/ +@[inline] +def toDays (leap : Bool) (month : Ordinal) : Day.Offset := + toSeconds leap month + |>.convert + +/-- +Size in days of each month if the year is not a leap year. +-/ +@[inline] +private def monthSizesNonLeap : { val : Array Day.Ordinal // val.size = 12 } := + ⟨#[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], by decide⟩ + +/-- +Returns the cumulative size in days of each month for a non-leap year. +-/ +@[inline] +private def cumulativeSizes : { val : Array Day.Offset // val.size = 12 } := + ⟨#[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], by decide⟩ + +/-- +Gets the number of days in a month. +-/ +def days (leap : Bool) (month : Ordinal) : Day.Ordinal := + if month.val = 2 then + if leap then 29 else 28 + else + let ⟨months, p⟩ := monthSizesNonLeap + let index : Fin 12 := (month.sub 1).toFin (by decide) + let idx := (index.cast (by rw [p])) + months.get idx.val idx.isLt + +theorem days_gt_27 (leap : Bool) (i : Month.Ordinal) : days leap i > 27 := by + match i with + | ⟨2, _⟩ => + simp [days] + split <;> decide + | ⟨1, _⟩ | ⟨3, _⟩ | ⟨4, _⟩ | ⟨5, _⟩ | ⟨6, _⟩ | ⟨7, _⟩ + | ⟨8, _⟩ | ⟨9, _⟩ | ⟨10, _⟩ | ⟨11, _⟩ | ⟨12, _⟩ => + simp [days, monthSizesNonLeap] + decide +revert + +/-- +Returns the number of days until the `month`. +-/ +def cumulativeDays (leap : Bool) (month : Ordinal) : Day.Offset := by + let ⟨months, p⟩ := cumulativeSizes + let index : Fin 12 := (month.sub 1).toFin (by decide) + rw [← p] at index + let res := months.get index.val index.isLt + exact res + (if leap ∧ month.val > 2 then 1 else 0) + +theorem cumulativeDays_le (leap : Bool) (month : Month.Ordinal) : cumulativeDays leap month ≥ 0 ∧ cumulativeDays leap month ≤ 334 + (if leap then 1 else 0) := by + match month with + | ⟨1, _⟩ | ⟨2, _⟩ | ⟨3, _⟩ | ⟨4, _⟩ | ⟨5, _⟩ | ⟨6, _⟩ | ⟨7, _⟩ | ⟨8, _⟩ | ⟨9, _⟩ | ⟨10, _⟩ | ⟨11, _⟩ | ⟨12, _⟩ => + simp [cumulativeSizes, Bounded.LE.sub, Bounded.LE.add, Bounded.LE.toFin, cumulativeDays] + try split + all_goals decide +revert + +theorem difference_eq (p : month.val ≤ 11) : + let next := month.truncateTop p |>.addTop 1 (by decide) + (cumulativeDays leap next).val = (cumulativeDays leap month).val + (days leap month).val := by + match month with + | ⟨1, _⟩ | ⟨2, _⟩ | ⟨3, _⟩ | ⟨4, _⟩ | ⟨5, _⟩ | ⟨6, _⟩ | ⟨7, _⟩ | ⟨8, _⟩ | ⟨9, _⟩ | ⟨10, _⟩ | ⟨11, _⟩ => + simp [cumulativeDays, Bounded.LE.addTop, days, monthSizesNonLeap]; + try split <;> rfl + try rfl + | ⟨12, _⟩ => contradiction + +/-- +Checks if a given day is valid for the specified month and year. For example, `29/02` is valid only +if the year is a leap year. +-/ +abbrev Valid (leap : Bool) (month : Month.Ordinal) (day : Day.Ordinal) : Prop := + day.val ≤ (days leap month).val + +/-- +Clips the day to be within the valid range. +-/ +@[inline] +def clipDay (leap : Bool) (month : Month.Ordinal) (day : Day.Ordinal) : Day.Ordinal := + let max : Day.Ordinal := month.days leap + if day.val > max.val + then max + else day + +/-- +Proves that every value provided by a clipDay is a valid day in a year. +-/ +theorem valid_clipDay : Valid leap month (clipDay leap month day) := by + simp [Valid, clipDay] + split + exact Int.le_refl (days leap month).val + next h => exact Int.not_lt.mp h + +end Ordinal +end Month +end Time +end Std diff --git a/src/Std/Time/Date/Unit/Week.lean b/src/Std/Time/Date/Unit/Week.lean new file mode 100644 index 000000000000..84fe108728a2 --- /dev/null +++ b/src/Std/Time/Date/Unit/Week.lean @@ -0,0 +1,185 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Date.Unit.Day + +namespace Std +namespace Time +namespace Week +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for weeks, which ranges between 1 and 53. +-/ +def Ordinal := Bounded.LE 1 53 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 1 (1 + (52 : Nat))) n) + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +instance : Inhabited Ordinal where + default := 1 + +/-- +`Offset` represents an offset in weeks. +-/ +def Offset : Type := UnitVal (86400 * 7) + deriving Repr, BEq, Inhabited, Add, Sub, Neg, LE, LT, ToString + +instance : OfNat Offset n := ⟨UnitVal.ofNat n⟩ + +namespace Ordinal + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 1 ≤ data ∧ data ≤ 53) : Ordinal := + Bounded.LE.mk data h + +/-- +`OfMonth` represents the number of weeks within a month. It ensures that the week is within the +correct bounds—either 1 to 6, representing the possible weeks in a month. +-/ +def OfMonth := Bounded.LE 1 6 + deriving Repr + +/-- +Creates an `Ordinal` from a natural number, ensuring the value is within bounds. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≥ 1 ∧ data ≤ 53 := by decide) : Ordinal := + Bounded.LE.ofNat' data h + +/-- +Creates an `Ordinal` from a `Fin`, ensuring the value is within bounds. +-/ +@[inline] +def ofFin (data : Fin 54) : Ordinal := + Bounded.LE.ofFin' data (by decide) + +/-- +Converts an `Ordinal` to an `Offset`. +-/ +@[inline] +def toOffset (ordinal : Ordinal) : Offset := + UnitVal.ofInt ordinal.val + +end Ordinal +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Week.Offset := + UnitVal.ofInt data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Week.Offset := + UnitVal.ofInt data + +/-- +Convert `Week.Offset` into `Millisecond.Offset`. +-/ +@[inline] +def toMilliseconds (weeks : Week.Offset) : Millisecond.Offset := + weeks.mul 604800000 + +/-- +Convert `Millisecond.Offset` into `Week.Offset`. +-/ +@[inline] +def ofMilliseconds (millis : Millisecond.Offset) : Week.Offset := + millis.ediv 604800000 + +/-- +Convert `Week.Offset` into `Nanosecond.Offset`. +-/ +@[inline] +def toNanoseconds (weeks : Week.Offset) : Nanosecond.Offset := + weeks.mul 604800000000000 + +/-- +Convert `Nanosecond.Offset` into `Week.Offset`. +-/ +@[inline] +def ofNanoseconds (nanos : Nanosecond.Offset) : Week.Offset := + nanos.ediv 604800000000000 + +/-- +Convert `Week.Offset` into `Second.Offset`. +-/ +@[inline] +def toSeconds (weeks : Week.Offset) : Second.Offset := + weeks.mul 604800 + +/-- +Convert `Second.Offset` into `Week.Offset`. +-/ +@[inline] +def ofSeconds (secs : Second.Offset) : Week.Offset := + secs.ediv 604800 + +/-- +Convert `Week.Offset` into `Minute.Offset`. +-/ +@[inline] +def toMinutes (weeks : Week.Offset) : Minute.Offset := + weeks.mul 10080 + +/-- +Convert `Minute.Offset` into `Week.Offset`. +-/ +@[inline] +def ofMinutes (minutes : Minute.Offset) : Week.Offset := + minutes.ediv 10080 + +/-- +Convert `Week.Offset` into `Hour.Offset`. +-/ +@[inline] +def toHours (weeks : Week.Offset) : Hour.Offset := + weeks.mul 168 + +/-- +Convert `Hour.Offset` into `Week.Offset`. +-/ +@[inline] +def ofHours (hours : Hour.Offset) : Week.Offset := + hours.ediv 168 + +/-- +Convert `Week.Offset` into `Day.Offset`. +-/ +@[inline] +def toDays (weeks : Week.Offset) : Day.Offset := + weeks.mul 7 + +/-- +Convert `Day.Offset` into `Week.Offset`. +-/ +@[inline] +def ofDays (days : Day.Offset) : Week.Offset := + days.ediv 7 + +end Offset +end Week +end Time +end Std diff --git a/src/Std/Time/Date/Unit/Weekday.lean b/src/Std/Time/Date/Unit/Weekday.lean new file mode 100644 index 000000000000..14c90848a869 --- /dev/null +++ b/src/Std/Time/Date/Unit/Weekday.lean @@ -0,0 +1,134 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Date.Unit.Day + +namespace Std +namespace Time +open Std.Internal +open Internal + +set_option linter.all true + +/-- +Defines the enumeration for days of the week. Each variant corresponds to a day of the week. +-/ +inductive Weekday + /-- Monday. -/ + | monday + + /-- Tuesday. -/ + | tuesday + + /-- Wednesday. -/ + | wednesday + + /-- Thursday. -/ + | thursday + + /-- Friday. -/ + | friday + + /-- Saturday. -/ + | saturday + + /-- Sunday. -/ + | sunday + deriving Repr, Inhabited, BEq + +namespace Weekday + +/-- +`Ordinal` represents a bounded value for weekdays, which ranges between 1 and 7. +-/ +def Ordinal := Bounded.LE 1 7 + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 1 (1 + (6 : Nat))) n) + +/-- +Converts a `Ordinal` representing a day index into a corresponding `Weekday`. This function is useful +for mapping numerical representations to days of the week. +-/ +def ofOrdinal : Ordinal → Weekday + | 1 => .monday + | 2 => .tuesday + | 3 => .wednesday + | 4 => .thursday + | 5 => .friday + | 6 => .saturday + | 7 => .sunday + +/-- +Converts a `Weekday` to a `Ordinal`. +-/ +def toOrdinal : Weekday → Ordinal + | .monday => 1 + | .tuesday => 2 + | .wednesday => 3 + | .thursday => 4 + | .friday => 5 + | .saturday => 6 + | .sunday => 7 + +/-- +Converts a `Weekday` to a `Nat`. +-/ +def toNat : Weekday → Nat + | .monday => 1 + | .tuesday => 2 + | .wednesday => 3 + | .thursday => 4 + | .friday => 5 + | .saturday => 6 + | .sunday => 7 + +/-- +Converts a `Nat` to an `Option Weekday`. +-/ +def ofNat? : Nat → Option Weekday + | 1 => some .monday + | 2 => some .tuesday + | 3 => some .wednesday + | 4 => some .thursday + | 5 => some .friday + | 6 => some .saturday + | 7 => some .sunday + | _ => none + +/-- +Converts a `Nat` to a `Weekday`. Panics if the value provided is invalid. +-/ +@[inline] +def ofNat! (n : Nat) : Weekday := + match ofNat? n with + | some res => res + | none => panic! "invalid weekday" + +/-- +Gets the next `Weekday`. +-/ +def next : Weekday → Weekday + | .monday => .tuesday + | .tuesday => .wednesday + | .wednesday => .thursday + | .thursday => .friday + | .friday => .saturday + | .saturday => .sunday + | .sunday => .monday + +/-- +Check if it's a weekend. +-/ +def isWeekend : Weekday → Bool + | .saturday => true + | .sunday => true + | _ => false + +end Weekday +end Time +end Std diff --git a/src/Std/Time/Date/Unit/Year.lean b/src/Std/Time/Date/Unit/Year.lean new file mode 100644 index 000000000000..94ac4d67ea49 --- /dev/null +++ b/src/Std/Time/Date/Unit/Year.lean @@ -0,0 +1,128 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Internal +import Std.Internal.Rat +import Std.Time.Date.Unit.Day +import Std.Time.Date.Unit.Month + +namespace Std +namespace Time +namespace Year +open Std.Internal +open Internal + +set_option linter.all true + +/-- +Defines the different eras. +-/ +inductive Era + /-- The era before the Common Era (BCE), always represents a date before year 0. -/ + | bce + + /-- The Common Era (CE), represents dates from year 0 onwards. -/ + | ce + deriving Repr, Inhabited + +instance : ToString Era where + toString + | .bce => "BCE" + | .ce => "CE" + +/-- +`Offset` represents a year offset, defined as an `Int`. +-/ +def Offset : Type := Int + deriving Repr, BEq, Inhabited, Add, Sub, Neg, LE, LT, ToString + +instance {x y : Offset} : Decidable (x ≤ y) := + let x : Int := x + inferInstanceAs (Decidable (x ≤ y)) + +instance {x y : Offset} : Decidable (x < y) := + let x : Int := x + inferInstanceAs (Decidable (x < y)) + +instance : OfNat Offset n := ⟨Int.ofNat n⟩ + +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Offset := + .ofNat data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Offset := + data + +/-- +Converts the `Year` offset to an `Int`. +-/ +@[inline] +def toInt (offset : Offset) : Int := + offset + +/-- +Converts the `Year` offset to a `Month` offset. +-/ +@[inline] +def toMonths (val : Offset) : Month.Offset := + val.mul 12 + +/-- +Determines if a year is a leap year in the proleptic Gregorian calendar. +-/ +@[inline] +def isLeap (y : Offset) : Bool := + y.toInt.tmod 4 = 0 ∧ (y.toInt.tmod 100 ≠ 0 ∨ y.toInt.tmod 400 = 0) + +/-- +Returns the `Era` of the `Year`. +-/ +def era (year : Offset) : Era := + if year.toInt ≥ 1 + then .ce + else .bce + +/-- +Calculates the number of days in the specified `year`. +-/ +def days (year : Offset) : Bounded.LE 365 366 := + if year.isLeap + then .ofNatWrapping 366 (by decide) + else .ofNatWrapping 355 (by decide) + +/-- +Calculates the number of weeks in the specified `year`. +-/ +def weeks (year : Offset) : Bounded.LE 52 53 := + let p (year : Offset) := Bounded.LE.byEmod (year.toInt + year.toInt/4 - year.toInt/100 + year.toInt/400) 7 (by decide) + + let add : Bounded.LE 0 1 := + if (p year).val = 4 ∨ (p (year - 1)).val = 3 + then Bounded.LE.ofNat 1 (by decide) + else Bounded.LE.ofNat 0 (by decide) + + Bounded.LE.exact 52 |>.addBounds add + +/-- +Checks if the given date is valid for the specified year, month, and day. +-/ +@[inline] +abbrev Valid (year : Year.Offset) (month : Month.Ordinal) (day : Day.Ordinal) : Prop := + day ≤ month.days year.isLeap + +end Offset +end Year +end Time +end Std diff --git a/src/Std/Time/Date/ValidDate.lean b/src/Std/Time/Date/ValidDate.lean new file mode 100644 index 000000000000..bd38c6520d26 --- /dev/null +++ b/src/Std/Time/Date/ValidDate.lean @@ -0,0 +1,88 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Date.Unit.Day +import Std.Time.Date.Unit.Month + +namespace Std +namespace Time +open Std.Internal +open Internal +open Month.Ordinal + +set_option linter.all true + +/-- +Represents a valid date for a given year, considering whether it is a leap year. Example: `(2, 29)` +is valid only if `leap` is `true`. +-/ +def ValidDate (leap : Bool) := { val : Month.Ordinal × Day.Ordinal // Valid leap (Prod.fst val) (Prod.snd val) } + +instance : Inhabited (ValidDate l) where + default := ⟨⟨1, 1⟩, (by cases l <;> decide)⟩ + +namespace ValidDate + +/-- +Transforms a tuple of a `Month` and a `Day` into a `Day.Ordinal.OfYear`. +-/ +def dayOfYear (ordinal : ValidDate leap) : Day.Ordinal.OfYear leap := + let days := cumulativeDays leap ordinal.val.fst + let proof := cumulativeDays_le leap ordinal.val.fst + let bounded := Bounded.LE.mk days.toInt proof |>.addBounds ordinal.val.snd + match leap, bounded with + | true, bounded => bounded + | false, bounded => bounded + +/-- +Transforms a `Day.Ordinal.OfYear` into a tuple of a `Month` and a `Day`. +-/ +def ofOrdinal (ordinal : Day.Ordinal.OfYear leap) : ValidDate leap := + let rec go (idx : Month.Ordinal) (acc : Int) (h : ordinal.val > acc) (p : acc = (cumulativeDays leap idx).val) : ValidDate leap := + let monthDays := days leap idx + if h₁ : ordinal.val ≤ acc + monthDays.val then + let bounded := Bounded.LE.mk ordinal.val (And.intro h h₁) |>.sub acc + let bounded : Bounded.LE 1 monthDays.val := bounded.cast (by omega) (by omega) + let days₁ : Day.Ordinal := ⟨bounded.val, And.intro bounded.property.left (Int.le_trans bounded.property.right monthDays.property.right)⟩ + ⟨⟨idx, days₁⟩, Int.le_trans bounded.property.right (by simp)⟩ + else by + let h₂ := Int.not_le.mp h₁ + + have h₃ : idx.val < 12 := Int.not_le.mp <| λh₃ => by + have h₅ := ordinal.property.right + let eq := Int.eq_iff_le_and_ge.mpr (And.intro idx.property.right h₃) + simp [monthDays, days, eq] at h₂ + simp [cumulativeDays, eq] at p + simp [p] at h₂ + cases leap + all_goals (simp at h₂; simp_all) + · have h₂ : 365 < ordinal.val := h₂ + omega + · have h₂ : 366 < ordinal.val := h₂ + omega + + let idx₂ := idx.truncateTop (Int.le_sub_one_of_lt h₃) |>.addTop 1 (by decide) + refine go idx₂ (acc + monthDays.val) h₂ ?_ + simp [monthDays, p] + rw [difference_eq (Int.le_of_lt_add_one h₃)] + + termination_by 12 - idx.val.toNat + decreasing_by + simp_wf + simp [Bounded.LE.addTop] + let gt0 : idx.val ≥ 0 := Int.le_trans (by decide) idx.property.left + refine Nat.sub_lt_sub_left (Int.toNat_lt gt0 |>.mpr h₃) ?_ + let toNat_lt_lt {n z : Int} (h : 0 ≤ z) (h₁ : 0 ≤ n) : z.toNat < n.toNat ↔ z < n := by + rw [← Int.not_le, ← Nat.not_le, ← Int.ofNat_le, Int.toNat_of_nonneg h, Int.toNat_of_nonneg h₁] + rw [toNat_lt_lt (by omega) (by omega)] + omega + + go 1 0 (Int.le_trans (by decide) ordinal.property.left) (by cases leap <;> decide) + +end ValidDate +end Time +end Std diff --git a/src/Std/Time/DateTime.lean b/src/Std/Time/DateTime.lean new file mode 100644 index 000000000000..ec83e83f0790 --- /dev/null +++ b/src/Std/Time/DateTime.lean @@ -0,0 +1,104 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.DateTime.Timestamp +import Std.Time.DateTime.PlainDateTime + +namespace Std +namespace Time + +namespace Timestamp + +set_option linter.all true + +/-- +Converts a `PlainDateTime` to a `Timestamp` +-/ +@[inline] +def ofPlainDateTimeAssumingUTC (pdt : PlainDateTime) : Timestamp := + pdt.toTimestampAssumingUTC + +/-- +Converts a `Timestamp` to a `PlainDateTime` +-/ +@[inline] +def toPlainDateTimeAssumingUTC (timestamp : Timestamp) : PlainDateTime := + PlainDateTime.ofTimestampAssumingUTC timestamp + +/-- +Converts a `PlainDate` to a `Timestamp` +-/ +@[inline] +def ofPlainDateAssumingUTC (pd : PlainDate) : Timestamp := + let days := pd.toDaysSinceUNIXEpoch + let secs := days.toSeconds + Timestamp.ofSecondsSinceUnixEpoch secs + +/-- +Converts a `Timestamp` to a `PlainDate` +-/ +@[inline] +def toPlainDateAssumingUTC (timestamp : Timestamp) : PlainDate := + let secs := timestamp.toSecondsSinceUnixEpoch + let days := Day.Offset.ofSeconds secs + PlainDate.ofDaysSinceUNIXEpoch days + +/-- +Converts a `Timestamp` to a `PlainTime` +-/ +@[inline] +def getTimeAssumingUTC (timestamp : Timestamp) : PlainTime := + let nanos := timestamp.toNanosecondsSinceUnixEpoch + PlainTime.ofNanoseconds nanos + +end Timestamp +namespace PlainDate + +/-- +Converts a `PlainDate` to a `Timestamp` +-/ +@[inline] +def toTimestampAssumingUTC (pdt : PlainDate) : Timestamp := + Timestamp.ofPlainDateAssumingUTC pdt + +instance : HSub PlainDate PlainDate Duration where + hSub x y := x.toTimestampAssumingUTC - y.toTimestampAssumingUTC + +end PlainDate +namespace PlainDateTime + +/-- +Converts a `PlainDate` to a `Timestamp` +-/ +@[inline] +def ofPlainDate (date : PlainDate) : PlainDateTime := + { date, time := PlainTime.midnight } + +/-- +Converts a `PlainDateTime` to a `PlainDate` +-/ +@[inline] +def toPlainDate (pdt : PlainDateTime) : PlainDate := + pdt.date + +/-- +Converts a `PlainTime` to a `PlainDateTime` +-/ +@[inline] +def ofPlainTime (time : PlainTime) : PlainDateTime := + { date := ⟨1, 1, 1, by decide⟩, time } + +/-- +Converts a `PlainDateTime` to a `PlainTime` +-/ +@[inline] +def toPlainTime (pdt : PlainDateTime) : PlainTime := + pdt.time + +instance : HSub PlainDateTime PlainDateTime Duration where + hSub x y := x.toTimestampAssumingUTC - y.toTimestampAssumingUTC + +end PlainDateTime diff --git a/src/Std/Time/DateTime/PlainDateTime.lean b/src/Std/Time/DateTime/PlainDateTime.lean new file mode 100644 index 000000000000..ab9ce145fdfd --- /dev/null +++ b/src/Std/Time/DateTime/PlainDateTime.lean @@ -0,0 +1,601 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date +import Std.Time.Time +import Std.Time.Internal +import Std.Time.DateTime.Timestamp + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +/-- +Represents a date and time with components for Year, Month, Day, Hour, Minute, Second, and Nanosecond. +-/ +structure PlainDateTime where + + /-- + The `Date` component of a `PlainDate` + -/ + date : PlainDate + + /-- + The `Time` component of a `PlainTime` + -/ + time : PlainTime + + deriving Inhabited, BEq, Repr + +namespace PlainDateTime + +/-- +Converts a `PlainDateTime` to a `Timestamp` +-/ +def toTimestampAssumingUTC (dt : PlainDateTime) : Timestamp := + let days := dt.date.toDaysSinceUNIXEpoch + let nanos := days.toSeconds + dt.time.toSeconds |>.mul 1000000000 + let nanos := nanos.val + dt.time.nanosecond.val + Timestamp.ofNanosecondsSinceUnixEpoch (Nanosecond.Offset.ofInt nanos) + +/-- +Converts a `Timestamp` to a `PlainDateTime`. +-/ +def ofTimestampAssumingUTC (stamp : Timestamp) : PlainDateTime := Id.run do + let leapYearEpoch := 11017 + let daysPer400Y := 365 * 400 + 97 + let daysPer100Y := 365 * 100 + 24 + let daysPer4Y := 365 * 4 + 1 + + let nanos := stamp.toNanosecondsSinceUnixEpoch + let secs : Second.Offset := nanos.ediv 1000000000 + let daysSinceEpoch : Day.Offset := secs.ediv 86400 + let boundedDaysSinceEpoch := daysSinceEpoch + + let mut rawDays := boundedDaysSinceEpoch - leapYearEpoch + let mut rem := Bounded.LE.byMod secs.val 86400 (by decide) + + let ⟨remSecs, days⟩ := + if h : rem.val ≤ -1 then + let remSecs := rem.truncateTop h + let remSecs : Bounded.LE 1 86399 := remSecs.add 86400 + let rawDays := rawDays - 1 + (remSecs.expandBottom (by decide), rawDays) + else + let h := rem.truncateBottom (Int.not_le.mp h) + (h, rawDays) + + let mut quadracentennialCycles := days.val / daysPer400Y; + let mut remDays := days.val % daysPer400Y; + + if remDays < 0 then + remDays := remDays + daysPer400Y + quadracentennialCycles := quadracentennialCycles - 1 + + let mut centenialCycles := remDays / daysPer100Y; + + if centenialCycles = 4 then + centenialCycles := centenialCycles - 1 + + remDays := remDays - centenialCycles * daysPer100Y + let mut quadrennialCycles := remDays / daysPer4Y; + + if quadrennialCycles = 25 then + quadrennialCycles := quadrennialCycles - 1 + + remDays := remDays - quadrennialCycles * daysPer4Y + let mut remYears := remDays / 365; + + if remYears = 4 then + remYears := remYears - 1 + + remDays := remDays - remYears * 365 + + let mut year := 2000 + remYears + 4 * quadrennialCycles + 100 * centenialCycles + 400 * quadracentennialCycles + let months := [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; + let mut mon : Fin 13 := 0; + + for monLen in months do + mon := mon + 1; + if remDays < monLen then + break + remDays := remDays - monLen + + let mday : Fin 31 := Fin.ofNat (Int.toNat remDays) + + let hmon ← + if h₁ : mon.val > 10 + then do + year := year + 1 + pure (Month.Ordinal.ofNat (mon.val - 10) (by omega)) + else + pure (Month.Ordinal.ofNat (mon.val + 2) (by omega)) + + let second : Bounded.LE 0 59 := remSecs.emod 60 (by decide) + let minute : Bounded.LE 0 59 := (remSecs.ediv 60 (by decide)).emod 60 (by decide) + let hour : Bounded.LE 0 23 := remSecs.ediv 3600 (by decide) + let nano : Bounded.LE 0 999999999 := Bounded.LE.byEmod nanos.val 1000000000 (by decide) + + return { + date := PlainDate.ofYearMonthDayClip year hmon (Day.Ordinal.ofFin (Fin.succ mday)) + time := PlainTime.ofHourMinuteSecondsNano (leap := false) (hour.expandTop (by decide)) minute second nano + } + +/-- +Converts a `PlainDateTime` to the number of days since the UNIX epoch. +-/ +@[inline] +def toDaysSinceUNIXEpoch (pdt : PlainDateTime) : Day.Offset := + pdt.date.toDaysSinceUNIXEpoch + +/-- +Converts a `PlainDateTime` to the number of days since the UNIX epoch. +-/ +@[inline] +def ofDaysSinceUNIXEpoch (days : Day.Offset) (time : PlainTime) : PlainDateTime := + PlainDateTime.mk (PlainDate.ofDaysSinceUNIXEpoch days) time + +/-- +Sets the `PlainDateTime` to the specified `desiredWeekday`. +-/ +def withWeekday (dt : PlainDateTime) (desiredWeekday : Weekday) : PlainDateTime := + { dt with date := PlainDate.withWeekday dt.date desiredWeekday } + +/-- +Creates a new `PlainDateTime` by adjusting the day of the month to the given `days` value, with any +out-of-range days clipped to the nearest valid date. +-/ +@[inline] +def withDaysClip (dt : PlainDateTime) (days : Day.Ordinal) : PlainDateTime := + { dt with date := PlainDate.ofYearMonthDayClip dt.date.year dt.date.month days } + +/-- +Creates a new `PlainDateTime` by adjusting the day of the month to the given `days` value, with any +out-of-range days rolled over to the next month or year as needed. +-/ +@[inline] +def withDaysRollOver (dt : PlainDateTime) (days : Day.Ordinal) : PlainDateTime := + { dt with date := PlainDate.rollOver dt.date.year dt.date.month days } + +/-- +Creates a new `PlainDateTime` by adjusting the month to the given `month` value, with any +out-of-range days clipped to the nearest valid date. +-/ +@[inline] +def withMonthClip (dt : PlainDateTime) (month : Month.Ordinal) : PlainDateTime := + { dt with date := PlainDate.ofYearMonthDayClip dt.date.year month dt.date.day } + +/-- +Creates a new `PlainDateTime` by adjusting the month to the given `month` value. +The day is rolled over to the next valid month if necessary. +-/ +@[inline] +def withMonthRollOver (dt : PlainDateTime) (month : Month.Ordinal) : PlainDateTime := + { dt with date := PlainDate.rollOver dt.date.year month dt.date.day } + +/-- +Creates a new `PlainDateTime` by adjusting the year to the given `year` value. The month and day +remain unchanged, with any out-of-range days clipped to the nearest valid date. +-/ +@[inline] +def withYearClip (dt : PlainDateTime) (year : Year.Offset) : PlainDateTime := + { dt with date := PlainDate.ofYearMonthDayClip year dt.date.month dt.date.day } + +/-- +Creates a new `PlainDateTime` by adjusting the year to the given `year` value. The month and day are rolled +over to the next valid month and day if necessary. +-/ +@[inline] +def withYearRollOver (dt : PlainDateTime) (year : Year.Offset) : PlainDateTime := + { dt with date := PlainDate.rollOver year dt.date.month dt.date.day } + +/-- +Creates a new `PlainDateTime` by adjusting the `hour` component of its `time` to the given value. +-/ +@[inline] +def withHours (dt : PlainDateTime) (hour : Hour.Ordinal) : PlainDateTime := + { dt with time := { dt.time with hour := hour } } + +/-- +Creates a new `PlainDateTime` by adjusting the `minute` component of its `time` to the given value. +-/ +@[inline] +def withMinutes (dt : PlainDateTime) (minute : Minute.Ordinal) : PlainDateTime := + { dt with time := { dt.time with minute := minute } } + +/-- +Creates a new `PlainDateTime` by adjusting the `second` component of its `time` to the given value. +-/ +@[inline] +def withSeconds (dt : PlainDateTime) (second : Sigma Second.Ordinal) : PlainDateTime := + { dt with time := { dt.time with second := second } } + +/-- +Creates a new `PlainDateTime` by adjusting the milliseconds component inside the `nano` component of its `time` to the given value. +-/ +@[inline] +def withMilliseconds (dt : PlainDateTime) (millis : Millisecond.Ordinal) : PlainDateTime := + { dt with time := dt.time.withMilliseconds millis } + +/-- +Creates a new `PlainDateTime` by adjusting the `nano` component of its `time` to the given value. +-/ +@[inline] +def withNanoseconds (dt : PlainDateTime) (nano : Nanosecond.Ordinal) : PlainDateTime := + { dt with time := dt.time.withNanoseconds nano } + +/-- +Adds a `Day.Offset` to a `PlainDateTime`. +-/ +@[inline] +def addDays (dt : PlainDateTime) (days : Day.Offset) : PlainDateTime := + { dt with date := dt.date.addDays days } + +/-- +Subtracts a `Day.Offset` from a `PlainDateTime`. +-/ +@[inline] +def subDays (dt : PlainDateTime) (days : Day.Offset) : PlainDateTime := + { dt with date := dt.date.subDays days } + +/-- +Adds a `Week.Offset` to a `PlainDateTime`. +-/ +@[inline] +def addWeeks (dt : PlainDateTime) (weeks : Week.Offset) : PlainDateTime := + { dt with date := dt.date.addWeeks weeks } + +/-- +Subtracts a `Week.Offset` from a `PlainDateTime`. +-/ +@[inline] +def subWeeks (dt : PlainDateTime) (weeks : Week.Offset) : PlainDateTime := + { dt with date := dt.date.subWeeks weeks } + +/-- +Adds a `Month.Offset` to a `PlainDateTime`, adjusting the day to the last valid day of the resulting +month. +-/ +def addMonthsClip (dt : PlainDateTime) (months : Month.Offset) : PlainDateTime := + { dt with date := dt.date.addMonthsClip months } + +/-- +Subtracts `Month.Offset` from a `PlainDateTime`, it clips the day to the last valid day of that month. +-/ +@[inline] +def subMonthsClip (dt : PlainDateTime) (months : Month.Offset) : PlainDateTime := + { dt with date := dt.date.subMonthsClip months } + +/-- +Adds a `Month.Offset` to a `PlainDateTime`, rolling over excess days to the following month if needed. +-/ +def addMonthsRollOver (dt : PlainDateTime) (months : Month.Offset) : PlainDateTime := + { dt with date := dt.date.addMonthsRollOver months } + +/-- +Subtracts a `Month.Offset` from a `PlainDateTime`, adjusting the day to the last valid day of the +resulting month. +-/ +@[inline] +def subMonthsRollOver (dt : PlainDateTime) (months : Month.Offset) : PlainDateTime := + { dt with date := dt.date.subMonthsRollOver months } + +/-- +Adds a `Month.Offset` to a `PlainDateTime`, rolling over excess days to the following month if needed. +-/ +@[inline] +def addYearsRollOver (dt : PlainDateTime) (years : Year.Offset) : PlainDateTime := + { dt with date := dt.date.addYearsRollOver years } + +/-- +Subtracts a `Month.Offset` from a `PlainDateTime`, rolling over excess days to the following month if +needed. +-/ +@[inline] +def addYearsClip (dt : PlainDateTime) (years : Year.Offset) : PlainDateTime := + { dt with date := dt.date.addYearsClip years } + +/-- +Subtracts a `Year.Offset` from a `PlainDateTime`, this function rolls over any excess days into the +following month. +-/ +@[inline] +def subYearsRollOver (dt : PlainDateTime) (years : Year.Offset) : PlainDateTime := + { dt with date := dt.date.subYearsRollOver years } + +/-- +Subtracts a `Year.Offset` from a `PlainDateTime`, adjusting the day to the last valid day of the +resulting month. +-/ +@[inline] +def subYearsClip (dt : PlainDateTime) (years : Year.Offset) : PlainDateTime := + { dt with date := dt.date.subYearsClip years } + + +/-- +Adds an `Hour.Offset` to a `PlainDateTime`, adjusting the date if the hour overflows. +-/ +@[inline] +def addHours (dt : PlainDateTime) (hours : Hour.Offset) : PlainDateTime := + let totalSeconds := dt.time.toSeconds + hours.toSeconds + let days := totalSeconds.ediv 86400 + let newTime := dt.time.addSeconds (hours.toSeconds) + { dt with date := dt.date.addDays days, time := newTime } + +/-- +Subtracts an `Hour.Offset` from a `PlainDateTime`, adjusting the date if the hour underflows. +-/ +@[inline] +def subHours (dt : PlainDateTime) (hours : Hour.Offset) : PlainDateTime := + addHours dt (-hours) + +/-- +Adds a `Minute.Offset` to a `PlainDateTime`, adjusting the hour and date if the minutes overflow. +-/ +@[inline] +def addMinutes (dt : PlainDateTime) (minutes : Minute.Offset) : PlainDateTime := + let totalSeconds := dt.time.toSeconds + minutes.toSeconds + let days := totalSeconds.ediv 86400 + let newTime := dt.time.addSeconds (minutes.toSeconds) + { dt with date := dt.date.addDays days, time := newTime } + +/-- +Subtracts a `Minute.Offset` from a `PlainDateTime`, adjusting the hour and date if the minutes underflow. +-/ +@[inline] +def subMinutes (dt : PlainDateTime) (minutes : Minute.Offset) : PlainDateTime := + addMinutes dt (-minutes) + +/-- +Adds a `Second.Offset` to a `PlainDateTime`, adjusting the minute, hour, and date if the seconds overflow. +-/ +@[inline] +def addSeconds (dt : PlainDateTime) (seconds : Second.Offset) : PlainDateTime := + let totalSeconds := dt.time.toSeconds + seconds + let days := totalSeconds.ediv 86400 + let newTime := dt.time.addSeconds seconds + { dt with date := dt.date.addDays days, time := newTime } + +/-- +Subtracts a `Second.Offset` from a `PlainDateTime`, adjusting the minute, hour, and date if the seconds underflow. +-/ +@[inline] +def subSeconds (dt : PlainDateTime) (seconds : Second.Offset) : PlainDateTime := + addSeconds dt (-seconds) + +/-- +Adds a `Millisecond.Offset` to a `PlainDateTime`, adjusting the second, minute, hour, and date if the milliseconds overflow. +-/ +@[inline] +def addMilliseconds (dt : PlainDateTime) (milliseconds : Millisecond.Offset) : PlainDateTime := + let totalMilliseconds := dt.time.toMilliseconds + milliseconds + let days := totalMilliseconds.ediv 86400000 -- 86400000 ms in a day + let newTime := dt.time.addMilliseconds milliseconds + { dt with date := dt.date.addDays days, time := newTime } + +/-- +Subtracts a `Millisecond.Offset` from a `PlainDateTime`, adjusting the second, minute, hour, and date if the milliseconds underflow. +-/ +@[inline] +def subMilliseconds (dt : PlainDateTime) (milliseconds : Millisecond.Offset) : PlainDateTime := + addMilliseconds dt (-milliseconds) + +/-- +Adds a `Nanosecond.Offset` to a `PlainDateTime`, adjusting the seconds, minutes, hours, and date if the nanoseconds overflow. +-/ +@[inline] +def addNanoseconds (dt : PlainDateTime) (nanos : Nanosecond.Offset) : PlainDateTime := + let nano := Nanosecond.Offset.ofInt dt.time.nanosecond.val + let totalNanos := nano + nanos + let extraSeconds := totalNanos.ediv 1000000000 + let nanosecond := Bounded.LE.byEmod totalNanos.val 1000000000 (by decide) + let newTime := dt.time.addSeconds extraSeconds + { dt with time := { newTime with nanosecond } } + +/-- +Subtracts a `Nanosecond.Offset` from a `PlainDateTime`, adjusting the seconds, minutes, hours, and date if the nanoseconds underflow. +-/ +@[inline] +def subNanoseconds (dt : PlainDateTime) (nanos : Nanosecond.Offset) : PlainDateTime := + addNanoseconds dt (-nanos) + +/-- +Getter for the `Year` inside of a `PlainDateTime`. +-/ +@[inline] +def year (dt : PlainDateTime) : Year.Offset := + dt.date.year + +/-- +Getter for the `Month` inside of a `PlainDateTime`. +-/ +@[inline] +def month (dt : PlainDateTime) : Month.Ordinal := + dt.date.month + +/-- +Getter for the `Day` inside of a `PlainDateTime`. +-/ +@[inline] +def day (dt : PlainDateTime) : Day.Ordinal := + dt.date.day + +/-- +Getter for the `Weekday` inside of a `PlainDateTime`. +-/ +@[inline] +def weekday (dt : PlainDateTime) : Weekday := + dt.date.weekday + +/-- +Getter for the `Hour` inside of a `PlainDateTime`. +-/ +@[inline] +def hour (dt : PlainDateTime) : Hour.Ordinal := + dt.time.hour + +/-- +Getter for the `Minute` inside of a `PlainDateTime`. +-/ +@[inline] +def minute (dt : PlainDateTime) : Minute.Ordinal := + dt.time.minute + +/-- +Getter for the `Millisecond` inside of a `PlainDateTime`. +-/ +@[inline] +def millisecond (dt : PlainDateTime) : Millisecond.Ordinal := + dt.time.millisecond + +/-- +Getter for the `Second` inside of a `PlainDateTime`. +-/ +@[inline] +def second (dt : PlainDateTime) : Second.Ordinal dt.time.second.fst := + dt.time.second.snd + +/-- +Getter for the `Nanosecond.Ordinal` inside of a `PlainDateTime`. +-/ +@[inline] +def nanosecond (dt : PlainDateTime) : Nanosecond.Ordinal := + dt.time.nanosecond + +/-- +Determines the era of the given `PlainDateTime` based on its year. +-/ +@[inline] +def era (date : PlainDateTime) : Year.Era := + date.date.era + +/-- +Checks if the `PlainDateTime` is in a leap year. +-/ +@[inline] +def inLeapYear (date : PlainDateTime) : Bool := + date.year.isLeap + +/-- +Determines the week of the year for the given `PlainDateTime`. +-/ +@[inline] +def weekOfYear (date : PlainDateTime) : Week.Ordinal := + date.date.weekOfYear + +/-- +Returns the unaligned week of the month for a `PlainDateTime` (day divided by 7, plus 1). +-/ +def weekOfMonth (date : PlainDateTime) : Bounded.LE 1 5 := + date.date.weekOfMonth + +/-- +Determines the week of the month for the given `PlainDateTime`. The week of the month is calculated based +on the day of the month and the weekday. Each week starts on Monday because the entire library is +based on the Gregorian Calendar. +-/ +@[inline] +def alignedWeekOfMonth (date : PlainDateTime) : Week.Ordinal.OfMonth := + date.date.alignedWeekOfMonth + +/-- +Transforms a tuple of a `PlainDateTime` into a `Day.Ordinal.OfYear`. +-/ +@[inline] +def dayOfYear (date : PlainDateTime) : Day.Ordinal.OfYear date.year.isLeap := + ValidDate.dayOfYear ⟨(date.month, date.day), date.date.valid⟩ + +/-- +Determines the quarter of the year for the given `PlainDateTime`. +-/ +@[inline] +def quarter (date : PlainDateTime) : Bounded.LE 1 4 := + date.date.quarter + +/-- +Combines a `PlainDate` and `PlainTime` into a `PlainDateTime`. +-/ +@[inline] +def atTime : PlainDate → PlainTime → PlainDateTime := + PlainDateTime.mk + +/-- +Combines a `PlainTime` and `PlainDate` into a `PlainDateTime`. +-/ +@[inline] +def atDate (time: PlainTime) (date: PlainDate) : PlainDateTime := + PlainDateTime.mk date time + +instance : HAdd PlainDateTime Day.Offset PlainDateTime where + hAdd := addDays + +instance : HSub PlainDateTime Day.Offset PlainDateTime where + hSub := subDays + +instance : HAdd PlainDateTime Week.Offset PlainDateTime where + hAdd := addWeeks + +instance : HSub PlainDateTime Week.Offset PlainDateTime where + hSub := subWeeks + +instance : HAdd PlainDateTime Hour.Offset PlainDateTime where + hAdd := addHours + +instance : HSub PlainDateTime Hour.Offset PlainDateTime where + hSub := subHours + +instance : HAdd PlainDateTime Minute.Offset PlainDateTime where + hAdd := addMinutes + +instance : HSub PlainDateTime Minute.Offset PlainDateTime where + hSub := subMinutes + +instance : HAdd PlainDateTime Millisecond.Offset PlainDateTime where + hAdd := addMilliseconds + +instance : HSub PlainDateTime Millisecond.Offset PlainDateTime where + hSub := addMilliseconds + +instance : HAdd PlainDateTime Second.Offset PlainDateTime where + hAdd := addSeconds + +instance : HSub PlainDateTime Second.Offset PlainDateTime where + hSub := subSeconds + +instance : HAdd PlainDateTime Nanosecond.Offset PlainDateTime where + hAdd := addNanoseconds + +instance : HSub PlainDateTime Nanosecond.Offset PlainDateTime where + hSub := subNanoseconds + +instance : HAdd PlainDateTime Duration PlainDateTime where + hAdd x y := addNanoseconds x y.toNanoseconds + +end PlainDateTime +namespace PlainDate + +/-- +Combines a `PlainDate` and `PlainTime` into a `PlainDateTime`. +-/ +@[inline] +def atTime : PlainDate → PlainTime → PlainDateTime := + PlainDateTime.mk + +end PlainDate +namespace PlainTime + +/-- +Combines a `PlainTime` and `PlainDate` into a `PlainDateTime`. +-/ +@[inline] +def atDate (time: PlainTime) (date: PlainDate) : PlainDateTime := + PlainDateTime.mk date time + +end PlainTime +end Time +end Std diff --git a/src/Std/Time/DateTime/Timestamp.lean b/src/Std/Time/DateTime/Timestamp.lean new file mode 100644 index 000000000000..f2c38ddea7b2 --- /dev/null +++ b/src/Std/Time/DateTime/Timestamp.lean @@ -0,0 +1,289 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Internal +import Init.Data.Int +import Std.Time.Time +import Std.Time.Date +import Std.Time.Duration + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +/-- +Represents an exact point in time as a UNIX Epoch timestamp. +-/ +structure Timestamp where + + /-- + Duration since the unix epoch. + -/ + val : Duration + deriving Repr, BEq, Inhabited + +instance : LE Timestamp where + le x y := x.val ≤ y.val + +instance { x y : Timestamp } : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance : OfNat Timestamp n where + ofNat := ⟨OfNat.ofNat n⟩ + +instance : ToString Timestamp where + toString s := toString s.val.toMilliseconds + +instance : Repr Timestamp where + reprPrec s := reprPrec (toString s) + +namespace Timestamp + +/-- +Fetches the current duration from the system. +-/ +@[extern "lean_get_current_time"] +opaque now : IO Timestamp + +/-- +Converts a `Timestamp` to minutes as `Minute.Offset`. +-/ +@[inline] +def toMinutes (tm : Timestamp) : Minute.Offset := + tm.val.second.ediv 60 + +/-- +Converts a `Timestamp` to days as `Day.Offset`. +-/ +@[inline] +def toDays (tm : Timestamp) : Day.Offset := + tm.val.second.ediv 86400 + +/-- +Creates a `Timestamp` from a `Second.Offset` since the Unix epoch. +-/ +@[inline] +def ofSecondsSinceUnixEpoch (secs : Second.Offset) : Timestamp := + ⟨Duration.ofSeconds secs⟩ + +/-- +Creates a `Timestamp` from a `Nanosecond.Offset` since the Unix epoch. +-/ +@[inline] +def ofNanosecondsSinceUnixEpoch (nanos : Nanosecond.Offset) : Timestamp := + ⟨Duration.ofNanoseconds nanos⟩ + +/-- +Creates a `Timestamp` from a `Millisecond.Offset` since the Unix epoch. +-/ +@[inline] +def ofMillisecondsSinceUnixEpoch (milli : Millisecond.Offset) : Timestamp := + ⟨Duration.ofNanoseconds milli.toNanoseconds⟩ + +/-- +Converts a `Timestamp` to seconds as `Second.Offset`. +-/ +@[inline] +def toSecondsSinceUnixEpoch (t : Timestamp) : Second.Offset := + t.val.second + +/-- +Converts a `Timestamp` to nanoseconds as `Nanosecond.Offset`. +-/ +@[inline] +def toNanosecondsSinceUnixEpoch (tm : Timestamp) : Nanosecond.Offset := + let nanos := tm.toSecondsSinceUnixEpoch.mul 1000000000 + let nanos := nanos + (.ofInt tm.val.nano.val) + nanos + +/-- +Converts a `Timestamp` to nanoseconds as `Millisecond.Offset`. +-/ +@[inline] +def toMillisecondsSinceUnixEpoch (tm : Timestamp) : Millisecond.Offset := + tm.toNanosecondsSinceUnixEpoch.toMilliseconds + +/-- +Calculates the duration from the given `Timestamp` to the current time. +-/ +@[inline] +def since (f : Timestamp) : IO Duration := do + let cur ← Timestamp.now + return Std.Time.Duration.sub cur.val f.val + +/-- +Returns the `Duration` represented by the `Timestamp` since the Unix epoch. +-/ +@[inline] +def toDurationSinceUnixEpoch (tm : Timestamp) : Duration := + tm.val + +/-- +Adds a `Millisecond.Offset` to the given `Timestamp`. +-/ +@[inline] +def addMilliseconds (t : Timestamp) (s : Millisecond.Offset) : Timestamp := + ⟨t.val + s⟩ + +/-- +Subtracts a `Millisecond.Offset` from the given `Timestamp`. +-/ +@[inline] +def subMilliseconds (t : Timestamp) (s : Millisecond.Offset) : Timestamp := + ⟨t.val - s⟩ + +/-- +Adds a `Nanosecond.Offset` to the given `Timestamp`. +-/ +@[inline] +def addNanoseconds (t : Timestamp) (s : Nanosecond.Offset) : Timestamp := + ⟨t.val + s⟩ + +/-- +Subtracts a `Nanosecond.Offset` from the given `Timestamp`. +-/ +@[inline] +def subNanoseconds (t : Timestamp) (s : Nanosecond.Offset) : Timestamp := + ⟨t.val - s⟩ + +/-- +Adds a `Second.Offset` to the given `Timestamp`. +-/ +@[inline] +def addSeconds (t : Timestamp) (s : Second.Offset) : Timestamp := + ⟨t.val + s⟩ + +/-- +Subtracts a `Second.Offset` from the given `Timestamp`. +-/ +@[inline] +def subSeconds (t : Timestamp) (s : Second.Offset) : Timestamp := + ⟨t.val - s⟩ + +/-- +Adds a `Minute.Offset` to the given `Timestamp`. +-/ +@[inline] +def addMinutes (t : Timestamp) (m : Minute.Offset) : Timestamp := + ⟨t.val + m⟩ + +/-- +Subtracts a `Minute.Offset` from the given `Timestamp`. +-/ +@[inline] +def subMinutes (t : Timestamp) (m : Minute.Offset) : Timestamp := + ⟨t.val - m⟩ + +/-- +Adds an `Hour.Offset` to the given `Timestamp`. +-/ +@[inline] +def addHours (t : Timestamp) (h : Hour.Offset) : Timestamp := + ⟨t.val + h⟩ + +/-- +Subtracts an `Hour.Offset` from the given `Timestamp`. +-/ +@[inline] +def subHours (t : Timestamp) (h : Hour.Offset) : Timestamp := + ⟨t.val - h⟩ + +/-- +Adds a `Day.Offset` to the given `Timestamp`. +-/ +@[inline] +def addDays (t : Timestamp) (d : Day.Offset) : Timestamp := + ⟨t.val + d⟩ + +/-- +Subtracts a `Day.Offset` from the given `Timestamp`. +-/ +@[inline] +def subDays (t : Timestamp) (d : Day.Offset) : Timestamp := + ⟨t.val - d⟩ + +/-- +Adds a `Week.Offset` to the given `Timestamp`. +-/ +@[inline] +def addWeeks (t : Timestamp) (d : Week.Offset) : Timestamp := + ⟨t.val + d⟩ + +/-- +Subtracts a `Week.Offset` from the given `Timestamp`. +-/ +@[inline] +def subWeeks (t : Timestamp) (d : Week.Offset) : Timestamp := + ⟨t.val - d⟩ + +/-- +Adds a `Duration` to the given `Timestamp`. +-/ +@[inline] +def addDuration (t : Timestamp) (d : Duration) : Timestamp := + ⟨t.val + d⟩ + +/-- +Subtracts a `Duration` from the given `Timestamp`. +-/ +@[inline] +def subDuration (t : Timestamp) (d : Duration) : Timestamp := + ⟨t.val - d⟩ + +instance : HAdd Timestamp Duration Timestamp where + hAdd := addDuration + +instance : HSub Timestamp Duration Timestamp where + hSub := subDuration + +instance : HAdd Timestamp Day.Offset Timestamp where + hAdd := addDays + +instance : HSub Timestamp Day.Offset Timestamp where + hSub := subDays + +instance : HAdd Timestamp Week.Offset Timestamp where + hAdd := addWeeks + +instance : HSub Timestamp Week.Offset Timestamp where + hSub := subWeeks + +instance : HAdd Timestamp Hour.Offset Timestamp where + hAdd := addHours + +instance : HSub Timestamp Hour.Offset Timestamp where + hSub := subHours + +instance : HAdd Timestamp Minute.Offset Timestamp where + hAdd := addMinutes + +instance : HSub Timestamp Minute.Offset Timestamp where + hSub := subMinutes + +instance : HAdd Timestamp Second.Offset Timestamp where + hAdd := addSeconds + +instance : HSub Timestamp Second.Offset Timestamp where + hSub := subSeconds + +instance : HAdd Timestamp Millisecond.Offset Timestamp where + hAdd := addMilliseconds + +instance : HSub Timestamp Millisecond.Offset Timestamp where + hSub := subMilliseconds + +instance : HAdd Timestamp Nanosecond.Offset Timestamp where + hAdd := addNanoseconds + +instance : HSub Timestamp Nanosecond.Offset Timestamp where + hSub := subNanoseconds + +instance : HSub Timestamp Timestamp Duration where + hSub x y := x.val - y.val + +end Timestamp diff --git a/src/Std/Time/Duration.lean b/src/Std/Time/Duration.lean new file mode 100644 index 000000000000..54d6d0364340 --- /dev/null +++ b/src/Std/Time/Duration.lean @@ -0,0 +1,361 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date +import Std.Time.Time + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +/-- +Represents a time interval with nanoseconds precision. +-/ +structure Duration where + + /-- + Second offset of the duration. + -/ + second : Second.Offset + + /-- + Nanosecond span that ranges from -999999999 and 999999999 + -/ + nano : Nanosecond.Span + + /-- + Proof that the duration is valid, ensuring that the `second` and `nano` values are correctly related. + -/ + proof : (second.val ≥ 0 ∧ nano.val ≥ 0) ∨ (second.val ≤ 0 ∧ nano.val ≤ 0) + deriving Repr + +instance : ToString Duration where + toString s := + let (sign, secs, nanos) := + if s.second.val > 0 then ("" ,s.second, s.nano.val) + else if s.second.val < 0 then ("-", -s.second, -s.nano.val) + else if s.nano.val < 0 then ("-", -s.second, -s.nano.val) else ("", s.second, s.nano.val) + sign ++ toString secs ++ (if s.nano.val == 0 then "" else "." ++ (leftPad 9 <| toString nanos)) ++ "s" + where + leftPad n s := "".pushn '0' (n - s.length) ++ s + +instance : Repr Duration where + reprPrec s := reprPrec (toString s) + +instance : BEq Duration where + beq x y := x.second == y.second && y.nano == x.nano + +instance : Inhabited Duration where + default := ⟨0, Bounded.LE.mk 0 (by decide), by decide⟩ + +instance : OfNat Duration n where + ofNat := by + refine ⟨.ofInt n, ⟨0, by decide⟩, ?_⟩ + simp <;> exact Int.le_total n 0 |>.symm + +namespace Duration + +/-- +Negates a `Duration`, flipping its second and nanosecond values. +-/ +@[inline] +protected def neg (duration : Duration) : Duration := by + refine ⟨-duration.second, duration.nano.neg, ?_⟩ + cases duration.proof with + | inl n => exact Or.inr (n.imp Int.neg_le_neg Int.neg_le_neg) + | inr n => exact Or.inl (n.imp Int.neg_le_neg Int.neg_le_neg) + +/-- +Creates a new `Duration` out of `Second.Offset`. +-/ +@[inline] +def ofSeconds (s : Second.Offset) : Duration := by + refine ⟨s, ⟨0, by decide⟩, ?_⟩ + simp <;> exact Int.le_total s.val 0 |>.symm + +/-- +Creates a new `Duration` out of `Nanosecond.Offset`. +-/ +def ofNanoseconds (s : Nanosecond.Offset) : Duration := by + refine ⟨s.ediv 1000000000, Bounded.LE.byMod s.val 1000000000 (by decide), ?_⟩ + cases Int.le_total s.val 0 + next n => exact Or.inr (And.intro (Int.ediv_le_ediv (by decide) n) (mod_nonpos 1000000000 n (by decide))) + next n => exact Or.inl (And.intro (Int.ediv_nonneg n (by decide)) (Int.tmod_nonneg 1000000000 n)) + where + mod_nonpos : ∀ {a : Int} (b : Int), (a ≤ 0) → (b ≥ 0) → 0 ≥ a.tmod b + | .negSucc m, .ofNat n, _, _ => Int.neg_le_neg (Int.tmod_nonneg (↑n) (Int.ofNat_le.mpr (Nat.zero_le (m + 1)))) + | 0, n, _, _ => Int.eq_iff_le_and_ge.mp (Int.zero_tmod n) |>.left + +/-- +Creates a new `Duration` out of `Millisecond.Offset`. +-/ +@[inline] +def ofMillisecond (s : Millisecond.Offset) : Duration := + ofNanoseconds (s.mul 1000000) + +/-- +Checks if the duration is zero seconds and zero nanoseconds. +-/ +@[inline] +def isZero (d : Duration) : Bool := + d.second.val = 0 ∧ d.nano.val = 0 + +/-- +Converts a `Duration` to a `Second.Offset` +-/ +@[inline] +def toSeconds (duration : Duration) : Second.Offset := + duration.second + +/-- +Converts a `Duration` to a `Millisecond.Offset` +-/ +@[inline] +def toMilliseconds (duration : Duration) : Millisecond.Offset := + let secMillis := duration.second.mul 1000 + let nanosMillis := duration.nano.ediv 1000000 (by decide) + let millis := secMillis + (.ofInt nanosMillis.val) + millis + +/-- +Converts a `Duration` to a `Nanosecond.Offset` +-/ +@[inline] +def toNanoseconds (duration : Duration) : Nanosecond.Offset := + let nanos := duration.second.mul 1000000000 + let nanos := nanos + (.ofInt duration.nano.val) + nanos + +instance : LE Duration where + le d1 d2 := d1.toNanoseconds ≤ d2.toNanoseconds + +instance {x y : Duration} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.toNanoseconds ≤ y.toNanoseconds)) + +/-- +Converts a `Duration` to a `Minute.Offset` +-/ +@[inline] +def toMinutes (tm : Duration) : Minute.Offset := + tm.second.ediv 60 + +/-- +Converts a `Duration` to a `Day.Offset` +-/ +@[inline] +def toDays (tm : Duration) : Day.Offset := + tm.second.ediv 86400 + +/-- +Normalizes `Second.Offset` and `NanoSecond.span` in order to build a new `Duration` out of it. +-/ +@[inline] +def fromComponents (secs : Second.Offset) (nanos : Nanosecond.Span) : Duration := + ofNanoseconds (secs.toNanoseconds + nanos.toOffset) + +/-- +Adds two durations together, handling any carry-over in nanoseconds. +-/ +@[inline] +def add (t₁ t₂ : Duration) : Duration := + ofNanoseconds (toNanoseconds t₁ + toNanoseconds t₂) + +/-- +Subtracts one `Duration` from another. +-/ +@[inline] +def sub (t₁ t₂ : Duration) : Duration := + t₁.add t₂.neg + +/-- +Adds a `Nanosecond.Offset` to a `Duration` +-/ +@[inline] +def addNanoseconds (t : Duration) (s : Nanosecond.Offset) : Duration := + t.add (ofNanoseconds s) + +/-- +Adds a `Millisecond.Offset` to a `Duration` +-/ +@[inline] +def addMilliseconds (t : Duration) (s : Millisecond.Offset) : Duration := + t.add (ofNanoseconds s.toNanoseconds) + +/-- +Adds a `Millisecond.Offset` to a `Duration` +-/ +@[inline] +def subMilliseconds (t : Duration) (s : Millisecond.Offset) : Duration := + t.sub (ofNanoseconds s.toNanoseconds) + +/-- +Adds a `Nanosecond.Offset` to a `Duration` +-/ +@[inline] +def subNanoseconds (t : Duration) (s : Nanosecond.Offset) : Duration := + t.sub (ofNanoseconds s) + +/-- +Adds a `Second.Offset` to a `Duration` +-/ +@[inline] +def addSeconds (t : Duration) (s : Second.Offset) : Duration := + t.add (ofSeconds s) + +/-- +Subtracts a `Second.Offset` from a `Duration` +-/ +@[inline] +def subSeconds (t : Duration) (s : Second.Offset) : Duration := + t.sub (ofSeconds s) + +/-- +Adds a `Minute.Offset` to a `Duration` +-/ +@[inline] +def addMinutes (t : Duration) (m : Minute.Offset) : Duration := + let seconds := m.mul 60 + t.addSeconds seconds + +/-- +Subtracts a `Minute.Offset` from a `Duration` +-/ +@[inline] +def subMinutes (t : Duration) (m : Minute.Offset) : Duration := + let seconds := m.mul 60 + t.subSeconds seconds + +/-- +Adds an `Hour.Offset` to a `Duration` +-/ +@[inline] +def addHours (t : Duration) (h : Hour.Offset) : Duration := + let seconds := h.mul 3600 + t.addSeconds seconds + +/-- +Subtracts an `Hour.Offset` from a `Duration` +-/ +@[inline] +def subHours (t : Duration) (h : Hour.Offset) : Duration := + let seconds := h.mul 3600 + t.subSeconds seconds + +/-- +Adds a `Day.Offset` to a `Duration` +-/ +@[inline] +def addDays (t : Duration) (d : Day.Offset) : Duration := + let seconds := d.mul 86400 + t.addSeconds seconds + +/-- +Subtracts a `Day.Offset` from a `Duration` +-/ +@[inline] +def subDays (t : Duration) (d : Day.Offset) : Duration := + let seconds := d.mul 86400 + t.subSeconds seconds + +/-- +Adds a `Week.Offset` to a `Duration` +-/ +@[inline] +def addWeeks (t : Duration) (w : Week.Offset) : Duration := + let seconds := w.mul 604800 + t.addSeconds seconds + +/-- +Subtracts a `Week.Offset` from a `Duration` +-/ +@[inline] +def subWeeks (t : Duration) (w : Week.Offset) : Duration := + let seconds := w.mul 604800 + t.subSeconds seconds + +instance : HAdd Duration Day.Offset Duration where + hAdd := addDays + +instance : HSub Duration Day.Offset Duration where + hSub := subDays + +instance : HAdd Duration Week.Offset Duration where + hAdd := addWeeks + +instance : HSub Duration Week.Offset Duration where + hSub := subWeeks + +instance : HAdd Duration Hour.Offset Duration where + hAdd := addHours + +instance : HSub Duration Hour.Offset Duration where + hSub := subHours + +instance : HAdd Duration Minute.Offset Duration where + hAdd := addMinutes + +instance : HSub Duration Minute.Offset Duration where + hSub := subMinutes + +instance : HAdd Duration Second.Offset Duration where + hAdd := addSeconds + +instance : HSub Duration Second.Offset Duration where + hSub := subSeconds + +instance : HAdd Duration Nanosecond.Offset Duration where + hAdd := addNanoseconds + +instance : HSub Duration Nanosecond.Offset Duration where + hSub := subNanoseconds + +instance : HAdd Duration Millisecond.Offset Duration where + hAdd := addMilliseconds + +instance : HSub Duration Millisecond.Offset Duration where + hSub := subMilliseconds + +instance : HSub Duration Duration Duration where + hSub := sub + +instance : HAdd Duration Duration Duration where + hAdd := add + +instance : Coe Nanosecond.Offset Duration where + coe := ofNanoseconds + +instance : Coe Second.Offset Duration where + coe := ofSeconds + +instance : Coe Minute.Offset Duration where + coe := ofSeconds ∘ Minute.Offset.toSeconds + +instance : Coe Hour.Offset Duration where + coe := ofSeconds ∘ Hour.Offset.toSeconds + +instance : Coe Week.Offset Duration where + coe := ofSeconds ∘ Day.Offset.toSeconds ∘ Week.Offset.toDays + +instance : Coe Day.Offset Duration where + coe := ofSeconds ∘ Day.Offset.toSeconds + +instance : HMul Int Duration Duration where + hMul i d := Duration.ofNanoseconds <| Nanosecond.Offset.ofInt (d.toNanoseconds.val * i) + +instance : HMul Duration Int Duration where + hMul d i := Duration.ofNanoseconds <| Nanosecond.Offset.ofInt (d.toNanoseconds.val * i) + +instance : HAdd PlainTime Duration PlainTime where + hAdd pt d := PlainTime.ofNanoseconds (d.toNanoseconds + pt.toNanoseconds) + +instance : HSub PlainTime Duration PlainTime where + hSub pt d := PlainTime.ofNanoseconds (d.toNanoseconds - pt.toNanoseconds) + +end Duration +end Time +end Std diff --git a/src/Std/Time/Format.lean b/src/Std/Time/Format.lean new file mode 100644 index 000000000000..6d4b31c3af37 --- /dev/null +++ b/src/Std/Time/Format.lean @@ -0,0 +1,623 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Notation.Spec +import Std.Time.Format.Basic +import Std.Time.Internal.Bounded + +namespace Std +namespace Time +namespace Formats +open Internal + +set_option linter.all true + +/-- +The ISO8601 format, which is always 24 or 27 characters long, used for representing date and time in +a standardized format. The format follows the pattern `uuuu-MM-dd'T'HH:mm:ssZ`. +-/ +def iso8601 : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm.ssZ") + +/-- +The americanDate format, which follows the pattern `MM-dd-uuuu`. +-/ +def americanDate : GenericFormat .any := datespec("MM-dd-uuuu") + +/-- +The europeanDate format, which follows the pattern `dd-MM-uuuu`. +-/ +def europeanDate : GenericFormat .any := datespec("dd-MM-uuuu") + +/-- +The time12Hour format, which follows the pattern `hh:mm:ss aa` for representing time +in a 12-hour clock format with an upper case AM/PM marker. +-/ +def time12Hour : GenericFormat .any := datespec("hh:mm:ss aa") + +/-- +The Time24Hour format, which follows the pattern `HH:mm:ss` for representing time +in a 24-hour clock format. +-/ +def time24Hour : GenericFormat .any := datespec("HH:mm:ss") + +/-- +The DateTimeZone24Hour format, which follows the pattern `uuuu-MM-dd:HH:mm:ss.SSSSSSSSS` for +representing date, time, and time zone. +-/ +def dateTime24Hour : GenericFormat (.only .GMT) := datespec("uuuu-MM-dd:HH:mm:ss.SSSSSSSSS") + +/-- +The DateTimeWithZone format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSZZZ` +for representing date, time, and time zone. +-/ +def dateTimeWithZone : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSZZZ") + +/-- +The leanTime24Hour format, which follows the pattern `HH:mm:ss.SSSSSSSSS` for representing time +in a 24-hour clock format. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanTime24Hour : GenericFormat .any := datespec("HH:mm:ss.SSSSSSSSS") + +/-- +The leanTime24HourNoNanos format, which follows the pattern `HH:mm:ss` for representing time +in a 24-hour clock format. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanTime24HourNoNanos : GenericFormat .any := datespec("HH:mm:ss") + +/-- +The leanDateTime24Hour format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS` for +representing date, time, and time zone. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDateTime24Hour : GenericFormat (.only .GMT) := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS") + +/-- +The leanDateTime24HourNoNanos format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ss` for +representing date, time, and time zone. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDateTime24HourNoNanos : GenericFormat (.only .GMT) := datespec("uuuu-MM-dd'T'HH:mm:ss") + +/-- +The leanDateTimeWithZone format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSZZZZZ` +for representing date, time, and time zone. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDateTimeWithZone : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSZZZZZ") + +/-- +The leanDateTimeWithZoneNoNanos format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ssZZZZZ` +for representing date, time, and time zone. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDateTimeWithZoneNoNanos : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ssZZZZZ") + +/-- +The leanDateTimeWithIdentifier format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ss[z]` +for representing date, time, and time zone. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDateTimeWithIdentifier : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss'['zzzz']'") + +/-- +The leanDateTimeWithIdentifierAndNanos format, which follows the pattern `uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS'[z]'` +for representing date, time, and time zone. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDateTimeWithIdentifierAndNanos : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS'['zzzz']'") + +/-- +The Lean Date format, which follows the pattern `uuuu-MM-dd`. It uses the default value that can be parsed with the +notation of dates. +-/ +def leanDate : GenericFormat .any := datespec("uuuu-MM-dd") + +/-- +The SQLDate format, which follows the pattern `uuuu-MM-dd` and is commonly used +in SQL databases to represent dates. +-/ +def sqlDate : GenericFormat .any := datespec("uuuu-MM-dd") + +/-- +The LongDateFormat, which follows the pattern `EEEE, MMMM D, uuuu HH:mm:ss` for +representing a full date and time with the day of the week and month name. +-/ +def longDateFormat : GenericFormat (.only .GMT) := datespec("EEEE, MMMM D, uuuu HH:mm:ss") + +/-- +The AscTime format, which follows the pattern `EEE MMM d HH:mm:ss uuuu`. This format +is often used in older systems for logging and time-stamping events. +-/ +def ascTime : GenericFormat (.only .GMT) := datespec("EEE MMM d HH:mm:ss uuuu") + +/-- +The RFC822 format, which follows the pattern `eee, dd MMM uuuu HH:mm:ss ZZZ`. +This format is used in email headers and HTTP headers. +-/ +def rfc822 : GenericFormat .any := datespec("eee, dd MMM uuuu HH:mm:ss ZZZ") + +/-- +The RFC850 format, which follows the pattern `eee, dd-MMM-YY HH:mm:ss ZZZ`. +This format is an older standard for representing date and time in headers. +-/ +def rfc850 : GenericFormat .any := datespec("eee, dd-MM-uuuu HH:mm:ss ZZZ") + +end Formats + +namespace TimeZone + +/-- +Parses a string into a `TimeZone` object. The input string must be in the format `"VV ZZZZZ"`. +-/ +def fromTimeZone (input : String) : Except String TimeZone := do + let spec : GenericFormat .any := datespec("VV ZZZZZ") + spec.parseBuilder (fun id off => some (TimeZone.mk off id (off.toIsoString true) false)) input + +namespace Offset + +/-- +Parses a string representing an offset into an `Offset` object. The input string must follow the `"xxx"` format. +-/ +def fromOffset (input : String) : Except String Offset := do + let spec : GenericFormat .any := datespec("xxx") + spec.parseBuilder some input + +end Offset +end TimeZone + +namespace PlainDate + +/-- +Formats a `PlainDate` using a specific format. +-/ +def format (date : PlainDate) (format : String) : String := + let format : Except String (GenericFormat .any) := GenericFormat.spec format + match format with + | .error err => s!"error: {err}" + | .ok res => + let res := res.formatGeneric fun + | .G _ => some date.era + | .y _ => some date.year + | .u _ => some date.year + | .D _ => some (Sigma.mk date.year.isLeap date.dayOfYear) + | .Qorq _ => some date.quarter + | .w _ => some date.weekOfYear + | .W _ => some date.alignedWeekOfMonth + | .MorL _ => some date.month + | .d _ => some date.day + | .E _ => some date.weekday + | .eorc _ => some date.weekday + | .F _ => some date.weekOfMonth + | _ => none + match res with + | some res => res + | none => "invalid time" + +/-- +Parses a date string in the American format (`MM-dd-uuuu`) and returns a `PlainDate`. +-/ +def fromAmericanDateString (input : String) : Except String PlainDate := do + Formats.americanDate.parseBuilder (fun m d y => PlainDate.ofYearMonthDay? y m d) input + +/-- +Converts a date in the American format (`MM-dd-uuuu`) into a `String`. +-/ +def toAmericanDateString (input : PlainDate) : String := + Formats.americanDate.formatBuilder input.month input.day input.year + +/-- +Parses a date string in the SQL format (`uuuu-MM-dd`) and returns a `PlainDate`. +-/ +def fromSQLDateString (input : String) : Except String PlainDate := do + Formats.sqlDate.parseBuilder PlainDate.ofYearMonthDay? input + +/-- +Converts a date in the SQL format (`uuuu-MM-dd`) into a `String`. +-/ +def toSQLDateString (input : PlainDate) : String := + Formats.sqlDate.formatBuilder input.year input.month input.day + +/-- +Parses a date string in the Lean format (`uuuu-MM-dd`) and returns a `PlainDate`. +-/ +def fromLeanDateString (input : String) : Except String PlainDate := do + Formats.leanDate.parseBuilder PlainDate.ofYearMonthDay? input + +/-- +Converts a date in the Lean format (`uuuu-MM-dd`) into a `String`. +-/ +def toLeanDateString (input : PlainDate) : String := + Formats.leanDate.formatBuilder input.year input.month input.day + +/-- +Parses a `String` in the `AmericanDate` or `SQLDate` format and returns a `PlainDate`. +-/ +def parse (input : String) : Except String PlainDate := + fromAmericanDateString input + <|> fromSQLDateString input + +instance : ToString PlainDate where + toString := toLeanDateString + +instance : Repr PlainDate where + reprPrec data := Repr.addAppParen ("date(\"" ++ toLeanDateString data ++ "\")") + +end PlainDate + +namespace PlainTime + +/-- +Formats a `PlainTime` using a specific format. +-/ +def format (time : PlainTime) (format : String) : String := + let format : Except String (GenericFormat .any) := GenericFormat.spec format + match format with + | .error err => s!"error: {err}" + | .ok res => + let res := res.formatGeneric fun + | .H _ => some time.hour + | .k _ => some (time.hour.shiftTo1BasedHour) + | .m _ => some time.minute + | .n _ => some time.nanosecond + | .s _ => some time.second + | .a _ => some (HourMarker.ofOrdinal time.hour) + | .h _ => some time.hour.toRelative + | .K _ => some (time.hour.emod 12 (by decide)) + | .S _ => some time.nanosecond + | .A _ => some time.toMilliseconds + | .N _ => some time.toNanoseconds + | _ => none + match res with + | some res => res + | none => "invalid time" + +/-- +Parses a time string in the 24-hour format (`HH:mm:ss`) and returns a `PlainTime`. +-/ +def fromTime24Hour (input : String) : Except String PlainTime := + Formats.time24Hour.parseBuilder (fun h m s => some (PlainTime.ofHourMinuteSeconds h m s.snd)) input + +/-- +Formats a `PlainTime` value into a 24-hour format string (`HH:mm:ss`). +-/ +def toTime24Hour (input : PlainTime) : String := + Formats.time24Hour.formatBuilder input.hour input.minute input.second + +/-- +Parses a time string in the lean 24-hour format (`HH:mm:ss.SSSSSSSSS` or `HH:mm:ss`) and returns a `PlainTime`. +-/ +def fromLeanTime24Hour (input : String) : Except String PlainTime := + Formats.leanTime24Hour.parseBuilder (fun h m s n => some (PlainTime.ofHourMinuteSecondsNano h m s.snd n)) input + <|> Formats.leanTime24HourNoNanos.parseBuilder (fun h m s => some (PlainTime.ofHourMinuteSecondsNano h m s.snd 0)) input + +/-- +Formats a `PlainTime` value into a 24-hour format string (`HH:mm:ss.SSSSSSSSS`). +-/ +def toLeanTime24Hour (input : PlainTime) : String := + Formats.leanTime24Hour.formatBuilder input.hour input.minute input.second input.nanosecond + +/-- +Parses a time string in the 12-hour format (`hh:mm:ss aa`) and returns a `PlainTime`. +-/ +def fromTime12Hour (input : String) : Except String PlainTime := do + let builder h m s a : Option PlainTime := do + let value ← Internal.Bounded.ofInt? h.val + some <| PlainTime.ofHourMinuteSeconds (HourMarker.toAbsolute a value) m s.snd + + Formats.time12Hour.parseBuilder builder input + +/-- +Formats a `PlainTime` value into a 12-hour format string (`hh:mm:ss aa`). +-/ +def toTime12Hour (input : PlainTime) : String := + Formats.time12Hour.formatBuilder (input.hour.emod 12 (by decide) |>.add 1) input.minute input.second (if input.hour.val ≥ 12 then HourMarker.pm else HourMarker.am) + +/-- +Parses a `String` in the `Time12Hour` or `Time24Hour` format and returns a `PlainTime`. +-/ +def parse (input : String) : Except String PlainTime := + fromTime12Hour input + <|> fromTime24Hour input + +instance : ToString PlainTime where + toString := toLeanTime24Hour + +instance : Repr PlainTime where + reprPrec data := Repr.addAppParen ("time(\"" ++ toLeanTime24Hour data ++ "\")") + +end PlainTime + +namespace ZonedDateTime + +/-- +Formats a `ZonedDateTime` using a specific format. +-/ +def format (data: ZonedDateTime) (format : String) : String := + let format : Except String (GenericFormat .any) := GenericFormat.spec format + match format with + | .error err => s!"error: {err}" + | .ok res => res.format data.toDateTime + +/-- +Parses a `String` in the `ISO8601` format and returns a `ZonedDateTime`. +-/ +def fromISO8601String (input : String) : Except String ZonedDateTime := + Formats.iso8601.parse input + +/-- +Formats a `ZonedDateTime` value into an ISO8601 string. +-/ +def toISO8601String (date : ZonedDateTime) : String := + Formats.iso8601.format date.toDateTime + +/-- +Parses a `String` in the rfc822 format and returns a `ZonedDateTime`. +-/ +def fromRFC822String (input : String) : Except String ZonedDateTime := + Formats.rfc822.parse input + +/-- +Formats a `ZonedDateTime` value into an RFC822 format string. +-/ +def toRFC822String (date : ZonedDateTime) : String := + Formats.rfc822.format date.toDateTime + +/-- +Parses a `String` in the rfc850 format and returns a `ZonedDateTime`. +-/ +def fromRFC850String (input : String) : Except String ZonedDateTime := + Formats.rfc850.parse input + +/-- +Formats a `ZonedDateTime` value into an RFC850 format string. +-/ +def toRFC850String (date : ZonedDateTime) : String := + Formats.rfc850.format date.toDateTime + +/-- +Parses a `String` in the dateTimeWithZone format and returns a `ZonedDateTime` object in the GMT time zone. +-/ +def fromDateTimeWithZoneString (input : String) : Except String ZonedDateTime := + Formats.dateTimeWithZone.parse input + +/-- +Formats a `ZonedDateTime` value into a simple date time with timezone string. +-/ +def toDateTimeWithZoneString (pdt : ZonedDateTime) : String := + Formats.dateTimeWithZone.format pdt.toDateTime + +/-- +Parses a `String` in the lean date time format with timezone format and returns a `ZonedDateTime` object. +-/ +def fromLeanDateTimeWithZoneString (input : String) : Except String ZonedDateTime := + Formats.leanDateTimeWithZone.parse input + <|> Formats.leanDateTimeWithZoneNoNanos.parse input + +/-- +Parses a `String` in the lean date time format with identifier and returns a `ZonedDateTime` object. +-/ +def fromLeanDateTimeWithIdentifierString (input : String) : Except String ZonedDateTime := + Formats.leanDateTimeWithIdentifier.parse input + <|> Formats.leanDateTimeWithIdentifierAndNanos.parse input + +/-- +Formats a `DateTime` value into a simple date time with timezone string that can be parsed by the date% notation. +-/ +def toLeanDateTimeWithZoneString (zdt : ZonedDateTime) : String := + Formats.leanDateTimeWithZone.formatBuilder zdt.year zdt.month zdt.day zdt.hour zdt.minute zdt.date.get.time.second zdt.nanosecond zdt.offset +/-- +Formats a `DateTime` value into a simple date time with timezone string that can be parsed by the date% notation with the timezone identifier. +-/ +def toLeanDateTimeWithIdentifierString (zdt : ZonedDateTime) : String := + Formats.leanDateTimeWithIdentifierAndNanos.formatBuilder zdt.year zdt.month zdt.day zdt.hour zdt.minute zdt.date.get.time.second zdt.nanosecond zdt.timezone.name + +/-- +Parses a `String` in the `ISO8601`, `RFC822` or `RFC850` format and returns a `ZonedDateTime`. +-/ +def parse (input : String) : Except String ZonedDateTime := + fromISO8601String input + <|> fromRFC822String input + <|> fromRFC850String input + <|> fromDateTimeWithZoneString input + <|> fromLeanDateTimeWithIdentifierString input + +instance : ToString ZonedDateTime where + toString := toLeanDateTimeWithIdentifierString + +instance : Repr ZonedDateTime where + reprPrec data := Repr.addAppParen ("zoned(\"" ++ toLeanDateTimeWithZoneString data ++ "\")") + +end ZonedDateTime + +namespace PlainDateTime + +/-- +Formats a `PlainDateTime` using a specific format. +-/ +def format (date : PlainDateTime) (format : String) : String := + let format : Except String (GenericFormat .any) := GenericFormat.spec format + match format with + | .error err => s!"error: {err}" + | .ok res => + let res := res.formatGeneric fun + | .G _ => some date.era + | .y _ => some date.year + | .u _ => some date.year + | .D _ => some (Sigma.mk date.year.isLeap date.dayOfYear) + | .Qorq _ => some date.quarter + | .w _ => some date.weekOfYear + | .W _ => some date.alignedWeekOfMonth + | .MorL _ => some date.month + | .d _ => some date.day + | .E _ => some date.weekday + | .eorc _ => some date.weekday + | .F _ => some date.weekOfMonth + | .H _ => some date.hour + | .k _ => some date.hour.shiftTo1BasedHour + | .m _ => some date.minute + | .n _ => some date.nanosecond + | .s _ => some date.time.second + | .a _ => some (HourMarker.ofOrdinal date.hour) + | .h _ => some date.hour.toRelative + | .K _ => some (date.hour.emod 12 (by decide)) + | .S _ => some date.nanosecond + | .A _ => some date.time.toMilliseconds + | .N _ => some date.time.toNanoseconds + | _ => none + match res with + | some res => res + | none => "invalid time" + +/-- +Parses a `String` in the `AscTime` format and returns a `PlainDateTime` object in the GMT time zone. +-/ +def fromAscTimeString (input : String) : Except String PlainDateTime := + Formats.ascTime.parse input + |>.map DateTime.toPlainDateTime + +/-- +Formats a `PlainDateTime` value into an AscTime format string. +-/ +def toAscTimeString (pdt : PlainDateTime) : String := + Formats.ascTime.format (DateTime.ofPlainDateTimeAssumingUTC pdt .UTC) + +/-- +Parses a `String` in the `LongDateFormat` and returns a `PlainDateTime` object in the GMT time zone. +-/ +def fromLongDateFormatString (input : String) : Except String PlainDateTime := + Formats.longDateFormat.parse input + |>.map DateTime.toPlainDateTime + +/-- +Formats a `PlainDateTime` value into a LongDateFormat string. +-/ +def toLongDateFormatString (pdt : PlainDateTime) : String := + Formats.longDateFormat.format (DateTime.ofPlainDateTimeAssumingUTC pdt .UTC) + +/-- +Parses a `String` in the `DateTime` format and returns a `PlainDateTime`. +-/ +def fromDateTimeString (input : String) : Except String PlainDateTime := + Formats.dateTime24Hour.parse input + |>.map DateTime.toPlainDateTime + +/-- +Formats a `PlainDateTime` value into a `DateTime` format string. +-/ +def toDateTimeString (pdt : PlainDateTime) : String := + Formats.dateTime24Hour.formatBuilder pdt.year pdt.month pdt.day pdt.hour pdt.minute pdt.time.second pdt.nanosecond + +/-- +Parses a `String` in the `DateTime` format and returns a `PlainDateTime`. +-/ +def fromLeanDateTimeString (input : String) : Except String PlainDateTime := + (Formats.leanDateTime24Hour.parse input <|> Formats.leanDateTime24HourNoNanos.parse input) + |>.map DateTime.toPlainDateTime + +/-- +Formats a `PlainDateTime` value into a `DateTime` format string. +-/ +def toLeanDateTimeString (pdt : PlainDateTime) : String := + Formats.leanDateTime24Hour.formatBuilder pdt.year pdt.month pdt.day pdt.hour pdt.minute pdt.time.second pdt.nanosecond + +/-- +Parses a `String` in the `AscTime` or `LongDate` format and returns a `PlainDateTime`. +-/ +def parse (date : String) : Except String PlainDateTime := + fromAscTimeString date + <|> fromLongDateFormatString date + <|> fromDateTimeString date + <|> fromLeanDateTimeString date + +instance : ToString PlainDateTime where + toString := toLeanDateTimeString + +instance : Repr PlainDateTime where + reprPrec data := Repr.addAppParen ("datetime(\"" ++ toLeanDateTimeString data ++ "\")") + +end PlainDateTime + +namespace DateTime + +/-- +Formats a `DateTime` using a specific format. +-/ +def format (data: DateTime tz) (format : String) : String := + let format : Except String (GenericFormat .any) := GenericFormat.spec format + match format with + | .error err => s!"error: {err}" + | .ok res => res.format data + +/-- +Parses a `String` in the `AscTime` format and returns a `DateTime` object in the GMT time zone. +-/ +def fromAscTimeString (input : String) : Except String (DateTime .GMT) := + Formats.ascTime.parse input + +/-- +Formats a `DateTime` value into an AscTime format string. +-/ +def toAscTimeString (datetime : DateTime .GMT) : String := + Formats.ascTime.format datetime + +/-- +Parses a `String` in the `LongDateFormat` and returns a `DateTime` object in the GMT time zone. +-/ +def fromLongDateFormatString (input : String) : Except String (DateTime .GMT) := + Formats.longDateFormat.parse input + +/-- +Formats a `DateTime` value into a LongDateFormat string. +-/ +def toLongDateFormatString (datetime : DateTime .GMT) : String := + Formats.longDateFormat.format datetime + +/-- +Formats a `DateTime` value into an ISO8601 string. +-/ +def toISO8601String (date : DateTime tz) : String := + Formats.iso8601.format date + +/-- +Formats a `DateTime` value into an RFC822 format string. +-/ +def toRFC822String (date : DateTime tz) : String := + Formats.rfc822.format date + +/-- +Formats a `DateTime` value into an RFC850 format string. +-/ +def toRFC850String (date : DateTime tz) : String := + Formats.rfc850.format date + +/-- +Formats a `DateTime` value into a `DateTimeWithZone` format string. +-/ +def toDateTimeWithZoneString (pdt : DateTime tz) : String := + Formats.dateTimeWithZone.format pdt + +/-- +Formats a `DateTime` value into a `DateTimeWithZone` format string that can be parsed by `date%`. +-/ +def toLeanDateTimeWithZoneString (pdt : DateTime tz) : String := + Formats.leanDateTimeWithZone.format pdt + +/-- +Parses a `String` in the `AscTime` or `LongDate` format and returns a `DateTime`. +-/ +def parse (date : String) : Except String (DateTime .GMT) := + fromAscTimeString date + <|> fromLongDateFormatString date + +instance : Repr (DateTime tz) where + reprPrec data := Repr.addAppParen (toLeanDateTimeWithZoneString data) + +instance : ToString (DateTime tz) where + toString := toLeanDateTimeWithZoneString + +end DateTime diff --git a/src/Std/Time/Format/Basic.lean b/src/Std/Time/Format/Basic.lean new file mode 100644 index 000000000000..0ecde2581c55 --- /dev/null +++ b/src/Std/Time/Format/Basic.lean @@ -0,0 +1,1488 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Parsec +import Std.Time.Date +import Std.Time.Time +import Std.Time.Zoned +import Std.Time.DateTime + +/-! +This module defines the `Formatter` types. It is based on the Java's `DateTimeFormatter` format. +-/ + +namespace Std +namespace Time +open Internal +open Std.Internal.Parsec.String +open Std.Internal.Parsec Lean PlainTime PlainDate TimeZone DateTime + +set_option linter.all true + + +/-- +`Text` represents different text formatting styles. +-/ +inductive Text + /-- Short form (e.g., "Tue") -/ + | short + /-- Full form (e.g., "Tuesday") -/ + | full + /-- Narrow form (e.g., "T") -/ + | narrow + deriving Repr, Inhabited + +namespace Text + +/-- +`classify` classifies the number of pattern letters into a `Text` type. +-/ +def classify (num : Nat) : Option Text := + if num < 4 then + some (.short) + else if num = 4 then + some (.full) + else if num = 5 then + some (.narrow) + else + none + +end Text + +/-- +`Number` represents different number formatting styles. +-/ +structure Number where + /-- + The number of digits to pad, based on the count of pattern letters. + -/ + padding : Nat + deriving Repr, Inhabited + +/-- +`classifyNumberText` classifies the number of pattern letters into either a `Number` or `Text`. +-/ +def classifyNumberText : Nat → Option (Number ⊕ Text) + | n => if n < 3 then some (.inl ⟨n⟩) else .inr <$> (Text.classify n) + +/-- +`Fraction` represents the fraction of a second, which can either be full nanoseconds +or a truncated form with fewer digits. +-/ +inductive Fraction + /-- Nanosecond precision (up to 9 digits) -/ + | nano + /-- Fewer digits (truncated precision) -/ + | truncated (digits : Nat) + deriving Repr, Inhabited + +namespace Fraction + +/-- +`classify` classifies the number of pattern letters into either a `Fraction`. It's used for `nano`. +-/ +def classify (nat : Nat) : Option Fraction := + if nat < 9 then + some (.truncated nat) + else if nat = 9 then + some (.nano) + else + none + +end Fraction + +/-- +`Year` represents different year formatting styles based on the number of pattern letters. +-/ +inductive Year + /-- Two-digit year format (e.g., "23" for 2023) -/ + | twoDigit + /-- Four-digit year format (e.g., "2023") -/ + | fourDigit + /-- Extended year format for more than 4 digits (e.g., "002023") -/ + | extended (num : Nat) + deriving Repr, Inhabited + +namespace Year + +/-- +`classify` classifies the number of pattern letters into a `Year` format. +-/ +def classify (num : Nat) : Option Year := + if num = 2 then + some (.twoDigit) + else if num = 4 then + some (.fourDigit) + else if num > 4 ∨ num = 3 then + some (.extended num) + else + none + +end Year + +/-- +`ZoneId` represents different time zone ID formats based on the number of pattern letters. +-/ +inductive ZoneId + /-- Short form of time zone (e.g., "PST") -/ + | short + /-- Full form of time zone (e.g., "Pacific Standard Time") -/ + | full + deriving Repr, Inhabited + +namespace ZoneId + +/-- +`classify` classifies the number of pattern letters into a `ZoneId` format. +- If 2 letters, it returns the short form. +- If 4 letters, it returns the full form. +- Otherwise, it returns none. +-/ +def classify (num : Nat) : Option ZoneId := + if num = 2 then + some (.short) + else if num = 4 then + some (.full) + else + none + +end ZoneId + +/-- +`ZoneName` represents different zone name formats based on the number of pattern letters and +whether daylight saving time is considered. +-/ +inductive ZoneName + /-- Short form of zone name (e.g., "PST") -/ + | short + /-- Full form of zone name (e.g., "Pacific Standard Time") -/ + | full + deriving Repr, Inhabited + +namespace ZoneName + +/-- +`classify` classifies the number of pattern letters and the letter type ('z' or 'v') +into a `ZoneName` format. +- For 'z', if less than 4 letters, it returns the short form; if 4 letters, it returns the full form. +- For 'v', if 1 letter, it returns the short form; if 4 letters, it returns the full form. +- Otherwise, it returns none. +-/ +def classify (letter : Char) (num : Nat) : Option ZoneName := + if letter = 'z' then + if num < 4 then + some (.short) + else if num = 4 then + some (.full) + else + none + else if letter = 'v' then + if num = 1 then + some (.short) + else if num = 4 then + some (.full) + else + none + else + none + +end ZoneName + +/-- +`OffsetX` represents different offset formats based on the number of pattern letters. +The output will vary between the number of pattern letters, whether it's the hour, minute, second, +and whether colons are used. +-/ +inductive OffsetX + /-- Only the hour is output (e.g., "+01") -/ + | hour + /-- Hour and minute without colon (e.g., "+0130") -/ + | hourMinute + /-- Hour and minute with colon (e.g., "+01:30") -/ + | hourMinuteColon + /-- Hour, minute, and second without colon (e.g., "+013015") -/ + | hourMinuteSecond + /-- Hour, minute, and second with colon (e.g., "+01:30:15") -/ + | hourMinuteSecondColon + deriving Repr, Inhabited + +namespace OffsetX + +/-- +`classify` classifies the number of pattern letters into an `OffsetX` format. +-/ +def classify (num : Nat) : Option OffsetX := + if num = 1 then + some (.hour) + else if num = 2 then + some (.hourMinute) + else if num = 3 then + some (.hourMinuteColon) + else if num = 4 then + some (.hourMinuteSecond) + else if num = 5 then + some (.hourMinuteSecondColon) + else + none + +end OffsetX + +/-- +`OffsetO` represents localized offset text formats based on the number of pattern letters. +-/ +inductive OffsetO + /-- Short form of the localized offset (e.g., "GMT+8") -/ + | short + /-- Full form of the localized offset (e.g., "GMT+08:00") -/ + | full + deriving Repr, Inhabited + +namespace OffsetO + +/-- +`classify` classifies the number of pattern letters into an `OffsetO` format. +-/ +def classify (num : Nat) : Option OffsetO := + match num with + | 1 => some (.short) + | 4 => some (.full) + | _ => none + +end OffsetO + +/-- +`OffsetZ` represents different offset formats based on the number of pattern letters (capital 'Z'). +-/ +inductive OffsetZ + /-- Hour and minute without colon (e.g., "+0130") -/ + | hourMinute + /-- Localized offset text in full form (e.g., "GMT+08:00") -/ + | full + /-- Hour, minute, and second with colon (e.g., "+01:30:15") -/ + | hourMinuteSecondColon + deriving Repr, Inhabited + +namespace OffsetZ + +/-- +`classify` classifies the number of pattern letters into an `OffsetZ` format. +-/ +def classify (num : Nat) : Option OffsetZ := + match num with + | 1 | 2 | 3 => some (.hourMinute) + | 4 => some (.full) + | 5 => some (.hourMinuteSecondColon) + | _ => none + +end OffsetZ + +/-- +The `Modifier` inductive type represents various formatting options for date and time components, +matching the format symbols used in date and time strings. +These modifiers can be applied in formatting functions to generate custom date and time outputs. +-/ +inductive Modifier + /-- + `G`: Era (e.g., AD, Anno Domini, A). + -/ + | G (presentation : Text) + + /-- + `y`: Year of era (e.g., 2004, 04, 0002, 2). + -/ + | y (presentation : Year) + + /-- + `u`: Year (e.g., 2004, 04, -0001, -1). + -/ + | u (presentation : Year) + + /-- + `D`: Day of year (e.g., 189). + -/ + | D (presentation : Number) + + /-- + `M`: Month of year as number or text (e.g., 7, 07, Jul, July, J). + -/ + | MorL (presentation : Number ⊕ Text) + + /-- + `d`: Day of month (e.g., 10). + -/ + | d (presentation : Number) + + /-- + `Q`: Quarter of year as number or text (e.g., 3, 03, Q3, 3rd quarter). + -/ + | Qorq (presentation : Number ⊕ Text) + + /-- + `w`: Week of week-based year (e.g., 27). + -/ + | w (presentation : Number) + + /-- + `W`: Week of month (e.g., 4). + -/ + | W (presentation : Number) + + /-- + `E`: Day of week as text (e.g., Tue, Tuesday, T). + -/ + | E (presentation : Text) + + /-- + `e`: Localized day of week as number or text (e.g., 2, 02, Tue, Tuesday, T). + -/ + | eorc (presentation : Number ⊕ Text) + + /-- + `F`: Aligned week of month (e.g., 3). + -/ + | F (presentation : Number) + + /-- + `a`: AM/PM of day (e.g., PM). + -/ + | a (presentation : Text) + + /-- + `h`: Clock hour of AM/PM (1-12) (e.g., 12). + -/ + | h (presentation : Number) + + /-- + `K`: Hour of AM/PM (0-11) (e.g., 0). + -/ + | K (presentation : Number) + + /-- + `k`: Clock hour of day (1-24) (e.g., 24). + -/ + | k (presentation : Number) + + /-- + `H`: Hour of day (0-23) (e.g., 0). + -/ + | H (presentation : Number) + + /-- + `m`: Minute of hour (e.g., 30). + -/ + | m (presentation : Number) + + /-- + `s`: Second of minute (e.g., 55). + -/ + | s (presentation : Number) + + /-- + `S`: Fraction of second (e.g., 978). + -/ + | S (presentation : Fraction) + + /-- + `A`: Millisecond of day (e.g., 1234). + -/ + | A (presentation : Number) + + /-- + `n`: Nanosecond of second (e.g., 987654321). + -/ + | n (presentation : Number) + + /-- + `N`: Nanosecond of day (e.g., 1234000000). + -/ + | N (presentation : Number) + + /-- + `V`: Time zone ID (e.g., America/Los_Angeles, Z, -08:30). + -/ + | V + + /-- + `z`: Time zone name (e.g., Pacific Standard Time, PST). + -/ + | z (presentation : ZoneName) + + /-- + `O`: Localized zone offset (e.g., GMT+8, GMT+08:00, UTC-08:00). + -/ + | O (presentation : OffsetO) + + /-- + `X`: Zone offset with 'Z' for zero (e.g., Z, -08, -0830, -08:30). + -/ + | X (presentation : OffsetX) + + /-- + `x`: Zone offset without 'Z' (e.g., +0000, -08, -0830, -08:30). + -/ + | x (presentation : OffsetX) + + /-- + `Z`: Zone offset with 'Z' for UTC (e.g., +0000, -0800, -08:00). + -/ + | Z (presentation : OffsetZ) + deriving Repr, Inhabited + +/-- +`abstractParse` abstracts the parsing logic for any type that has a classify function. +It takes a constructor function to build the `Modifier` and a classify function that maps the pattern length to a specific type. +-/ +private def parseMod (constructor : α → Modifier) (classify : Nat → Option α) (p : String) : Parser Modifier := + let len := p.length + match classify len with + | some res => pure (constructor res) + | none => fail s!"invalid quantity of characters for '{p.get 0}'" + +private def parseText (constructor : Text → Modifier) (p : String) : Parser Modifier := + parseMod constructor Text.classify p + +private def parseFraction (constructor : Fraction → Modifier) (p : String) : Parser Modifier := + parseMod constructor Fraction.classify p + +private def parseNumber (constructor : Number → Modifier) (p : String) : Parser Modifier := + pure (constructor ⟨p.length⟩) + +private def parseYear (constructor : Year → Modifier) (p : String) : Parser Modifier := + parseMod constructor Year.classify p + +private def parseOffsetX (constructor : OffsetX → Modifier) (p : String) : Parser Modifier := + parseMod constructor OffsetX.classify p + +private def parseOffsetZ (constructor : OffsetZ → Modifier) (p : String) : Parser Modifier := + parseMod constructor OffsetZ.classify p + +private def parseOffsetO (constructor : OffsetO → Modifier) (p : String) : Parser Modifier := + parseMod constructor OffsetO.classify p + +private def parseZoneId (p : String) : Parser Modifier := + if p.length = 2 then pure .V else fail s!"invalid quantity of characters for '{p.get 0}'" + +private def parseNumberText (constructor : (Number ⊕ Text) → Modifier) (p : String) : Parser Modifier := + parseMod constructor classifyNumberText p + +private def parseZoneName (constructor : ZoneName → Modifier) (p : String) : Parser Modifier := + let len := p.length + match ZoneName.classify (p.get 0) len with + | some res => pure (constructor res) + | none => fail s!"invalid quantity of characters for '{p.get 0}'" + +private def parseModifier : Parser Modifier + := (parseText Modifier.G =<< many1Chars (pchar 'G')) + <|> parseYear Modifier.y =<< many1Chars (pchar 'y') + <|> parseYear Modifier.u =<< many1Chars (pchar 'u') + <|> parseNumber Modifier.D =<< many1Chars (pchar 'D') + <|> parseNumberText Modifier.MorL =<< many1Chars (pchar 'M') + <|> parseNumberText Modifier.MorL =<< many1Chars (pchar 'L') + <|> parseNumber Modifier.d =<< many1Chars (pchar 'd') + <|> parseNumberText Modifier.Qorq =<< many1Chars (pchar 'Q') + <|> parseNumberText Modifier.Qorq =<< many1Chars (pchar 'q') + <|> parseNumber Modifier.w =<< many1Chars (pchar 'w') + <|> parseNumber Modifier.W =<< many1Chars (pchar 'W') + <|> parseText Modifier.E =<< many1Chars (pchar 'E') + <|> parseNumberText Modifier.eorc =<< many1Chars (pchar 'e') + <|> parseNumberText Modifier.eorc =<< many1Chars (pchar 'c') + <|> parseNumber Modifier.F =<< many1Chars (pchar 'F') + <|> parseText Modifier.a =<< many1Chars (pchar 'a') + <|> parseNumber Modifier.h =<< many1Chars (pchar 'h') + <|> parseNumber Modifier.K =<< many1Chars (pchar 'K') + <|> parseNumber Modifier.k =<< many1Chars (pchar 'k') + <|> parseNumber Modifier.H =<< many1Chars (pchar 'H') + <|> parseNumber Modifier.m =<< many1Chars (pchar 'm') + <|> parseNumber Modifier.s =<< many1Chars (pchar 's') + <|> parseFraction Modifier.S =<< many1Chars (pchar 'S') + <|> parseNumber Modifier.A =<< many1Chars (pchar 'A') + <|> parseNumber Modifier.n =<< many1Chars (pchar 'n') + <|> parseNumber Modifier.N =<< many1Chars (pchar 'N') + <|> parseZoneId =<< many1Chars (pchar 'V') + <|> parseZoneName Modifier.z =<< many1Chars (pchar 'z') + <|> parseOffsetO Modifier.O =<< many1Chars (pchar 'O') + <|> parseOffsetX Modifier.X =<< many1Chars (pchar 'X') + <|> parseOffsetX Modifier.x =<< many1Chars (pchar 'x') + <|> parseOffsetZ Modifier.Z =<< many1Chars (pchar 'Z') + +/-- +The part of a formatting string. A string is just a text and a modifier is in the format described in +the `Modifier` type. +-/ +inductive FormatPart + /-- + A string literal. + -/ + | string (val : String) + + /-- + A modifier that renders some data into text. + -/ + | modifier (modifier : Modifier) + deriving Repr + +instance : Coe String FormatPart where + coe := .string + +instance : Coe Modifier FormatPart where + coe := .modifier + +/-- +The format of date and time string. +-/ +abbrev FormatString := List FormatPart + +/-- +If the format is aware of some timezone data it parses or if it parses any timezone. +-/ +inductive Awareness + /-- The format only parses a single timezone. -/ + | only : TimeZone → Awareness + /-- The format parses any timezone. -/ + | any + +namespace Awareness + +instance : Coe TimeZone Awareness where + coe := .only + +@[simp] +private def type (x : Awareness) : Type := + match x with + | .any => ZonedDateTime + | .only tz => DateTime tz + +instance : Inhabited (type aw) where + default := by + simp [type] + split <;> exact Inhabited.default + +private def getD (x : Awareness) (default : TimeZone) : TimeZone := + match x with + | .any => default + | .only tz => tz + +end Awareness + +/-- +A specification on how to format a data or parse some string. +-/ +structure GenericFormat (awareness : Awareness) where + + /-- + The format that is not aware of the timezone. + -/ + string : FormatString + deriving Inhabited, Repr + +private def parseFormatPart : Parser FormatPart + := (.modifier <$> parseModifier) + <|> (pchar '\\') *> any <&> (.string ∘ toString) + <|> (pchar '\"' *> many1Chars (satisfy (· ≠ '\"')) <* pchar '\"') <&> .string + <|> (pchar '\'' *> many1Chars (satisfy (· ≠ '\'')) <* pchar '\'') <&> .string + <|> many1Chars (satisfy (fun x => ¬Char.isAlpha x ∧ x ≠ '\'' ∧ x ≠ '\"')) <&> .string + +private def specParser : Parser FormatString := + (Array.toList <$> many parseFormatPart) <* eof + +private def specParse (s : String) : Except String FormatString := + specParser.run s + +-- Pretty printer + +private def leftPad (n : Nat) (a : Char) (s : String) : String := + "".pushn a (n - s.length) ++ s + +private def rightPad (n : Nat) (a : Char) (s : String) : String := + s ++ "".pushn a (n - s.length) + +private def pad (size : Nat) (n : Int) (cut : Bool := false) : String := + let (sign, n) := if n < 0 then ("-", -n) else ("", n) + + let numStr := toString n + if numStr.length > size then + sign ++ if cut then numStr.drop (numStr.length - size) else numStr + else + sign ++ leftPad size '0' numStr + +private def rightTruncate (size : Nat) (n : Int) (cut : Bool := false) : String := + let (sign, n) := if n < 0 then ("-", -n) else ("", n) + + let numStr := toString n + if numStr.length > size then + sign ++ if cut then numStr.take size else numStr + else + sign ++ rightPad size '0' numStr + +private def formatMonthLong : Month.Ordinal → String + | ⟨1, _⟩ => "January" + | ⟨2, _⟩ => "February" + | ⟨3, _⟩ => "March" + | ⟨4, _⟩ => "April" + | ⟨5, _⟩ => "May" + | ⟨6, _⟩ => "June" + | ⟨7, _⟩ => "July" + | ⟨8, _⟩ => "August" + | ⟨9, _⟩ => "September" + | ⟨10, _⟩ => "October" + | ⟨11, _⟩ => "November" + | ⟨12, _⟩ => "December" + +private def formatMonthShort : Month.Ordinal → String + | ⟨1, _⟩ => "Jan" + | ⟨2, _⟩ => "Feb" + | ⟨3, _⟩ => "Mar" + | ⟨4, _⟩ => "Apr" + | ⟨5, _⟩ => "May" + | ⟨6, _⟩ => "Jun" + | ⟨7, _⟩ => "Jul" + | ⟨8, _⟩ => "Aug" + | ⟨9, _⟩ => "Sep" + | ⟨10, _⟩ => "Oct" + | ⟨11, _⟩ => "Nov" + | ⟨12, _⟩ => "Dec" + +private def formatMonthNarrow : Month.Ordinal → String + | ⟨1, _⟩ => "J" + | ⟨2, _⟩ => "F" + | ⟨3, _⟩ => "M" + | ⟨4, _⟩ => "A" + | ⟨5, _⟩ => "M" + | ⟨6, _⟩ => "J" + | ⟨7, _⟩ => "J" + | ⟨8, _⟩ => "A" + | ⟨9, _⟩ => "S" + | ⟨10, _⟩ => "O" + | ⟨11, _⟩ => "N" + | ⟨12, _⟩ => "D" + +private def formatWeekdayLong : Weekday → String + | .sunday => "Sunday" + | .monday => "Monday" + | .tuesday => "Tuesday" + | .wednesday => "Wednesday" + | .thursday => "Thursday" + | .friday => "Friday" + | .saturday => "Saturday" + +private def formatWeekdayShort : Weekday → String + | .sunday => "Sun" + | .monday => "Mon" + | .tuesday => "Tue" + | .wednesday => "Wed" + | .thursday => "Thu" + | .friday => "Fri" + | .saturday => "Sat" + +private def formatWeekdayNarrow : Weekday → String + | .sunday => "S" + | .monday => "M" + | .tuesday => "T" + | .wednesday => "W" + | .thursday => "T" + | .friday => "F" + | .saturday => "S" + +private def formatEraShort : Year.Era → String + | .bce => "BCE" + | .ce => "CE" + +private def formatEraLong : Year.Era → String + | .bce => "Before Common Era" + | .ce => "Common Era" + +private def formatEraNarrow : Year.Era → String + | .bce => "B" + | .ce => "C" + +private def formatQuarterNumber : Month.Quarter → String + |⟨1, _⟩ => "1" + |⟨2, _⟩ => "2" + |⟨3, _⟩ => "3" + |⟨4, _⟩ => "4" + +private def formatQuarterShort : Month.Quarter → String + | ⟨1, _⟩ => "Q1" + | ⟨2, _⟩ => "Q2" + | ⟨3, _⟩ => "Q3" + | ⟨4, _⟩ => "Q4" + +private def formatQuarterLong : Month.Quarter → String + | ⟨1, _⟩ => "1st quarter" + | ⟨2, _⟩ => "2nd quarter" + | ⟨3, _⟩ => "3rd quarter" + | ⟨4, _⟩ => "4th quarter" + +private def formatMarkerShort (marker : HourMarker) : String := + match marker with + | .am => "AM" + | .pm => "PM" + +private def formatMarkerLong (marker : HourMarker) : String := + match marker with + | .am => "Ante Meridiem" + | .pm => "Post Meridiem" + +private def formatMarkerNarrow (marker : HourMarker) : String := + match marker with + | .am => "A" + | .pm => "P" + +private def toSigned (data : Int) : String := + if data < 0 then toString data else "+" ++ toString data + +private def toIsoString (offset : Offset) (withMinutes : Bool) (withSeconds : Bool) (colon : Bool) : String := + let (sign, time) := if offset.second.val ≥ 0 then ("+", offset.second) else ("-", -offset.second) + let time := PlainTime.ofSeconds time + let pad := leftPad 2 '0' ∘ toString + + let data := s!"{sign}{pad time.hour.val}" + let data := if withMinutes then s!"{data}{if colon then ":" else ""}{pad time.minute.val}" else data + let data := if withSeconds ∧ time.second.snd.val ≠ 0 then s!"{data}{if colon then ":" else ""}{pad time.second.snd.val}" else data + + data + +private def TypeFormat : Modifier → Type + | .G _ => Year.Era + | .y _ => Year.Offset + | .u _ => Year.Offset + | .D _ => Sigma Day.Ordinal.OfYear + | .MorL _ => Month.Ordinal + | .d _ => Day.Ordinal + | .Qorq _ => Month.Quarter + | .w _ => Week.Ordinal + | .W _ => Week.Ordinal.OfMonth + | .E _ => Weekday + | .eorc _ => Weekday + | .F _ => Bounded.LE 1 5 + | .a _ => HourMarker + | .h _ => Bounded.LE 1 12 + | .K _ => Bounded.LE 0 11 + | .k _ => Bounded.LE 1 24 + | .H _ => Hour.Ordinal + | .m _ => Minute.Ordinal + | .s _ => Sigma Second.Ordinal + | .S _ => Nanosecond.Ordinal + | .A _ => Millisecond.Offset + | .n _ => Nanosecond.Ordinal + | .N _ => Nanosecond.Offset + | .V => String + | .z _ => String + | .O _ => Offset + | .X _ => Offset + | .x _ => Offset + | .Z _ => Offset + +private def formatWith (modifier : Modifier) (data: TypeFormat modifier) : String := + match modifier with + | .G format => + match format with + | .short => formatEraShort data + | .full => formatEraLong data + | .narrow => formatEraNarrow data + | .y format => + let info := data.toInt + let info := if info ≤ 0 then -info + 1 else info + match format with + | .twoDigit => pad 2 (info % 100) + | .fourDigit => pad 4 info + | .extended n => pad n info + | .u format => + match format with + | .twoDigit => pad 2 (data.toInt % 100) + | .fourDigit => pad 4 data.toInt + | .extended n => pad n data.toInt + | .D format => + pad format.padding data.snd.val + | .MorL format => + match format with + | .inl format => pad format.padding data.val + | .inr .short => formatMonthShort data + | .inr .full => formatMonthLong data + | .inr .narrow => formatMonthNarrow data + | .d format => + pad format.padding data.val + | .Qorq format => + match format with + | .inl format => pad format.padding data.val + | .inr .short => formatQuarterShort data + | .inr .full => formatQuarterLong data + | .inr .narrow => formatQuarterNumber data + | .w format => + pad format.padding data.val + | .W format => + pad format.padding data.val + | .E format => + match format with + | .short => formatWeekdayShort data + | .full => formatWeekdayLong data + | .narrow => formatWeekdayNarrow data + | .eorc format => + match format with + | .inl format => pad format.padding data.toOrdinal.val + | .inr .short => formatWeekdayShort data + | .inr .full => formatWeekdayLong data + | .inr .narrow => formatWeekdayNarrow data + | .F format => + pad format.padding data.val + | .a format => + match format with + | .short => formatMarkerShort data + | .full => formatMarkerLong data + | .narrow => formatMarkerNarrow data + | .h format => pad format.padding (data.val % 12) + | .K format => pad format.padding (data.val % 12) + | .k format => pad format.padding (data.val) + | .H format => pad format.padding (data.val) + | .m format => pad format.padding (data.val) + | .s format => pad format.padding (data.snd.val) + | .S format => + match format with + | .nano => pad 9 data.val + | .truncated n => rightTruncate n data.val (cut := true) + | .A format => + pad format.padding data.val + | .n format => + pad format.padding data.val + | .N format => + pad format.padding data.val + | .V => data + | .z format => + match format with + | .short => data + | .full => data + | .O format => + match format with + | .short => s!"GMT{toSigned data.second.toHours.toInt}" + | .full => s!"GMT{toIsoString data true false true}" + | .X format => + if data.second == 0 then + "Z" + else + match format with + | .hour => toIsoString data false false false + | .hourMinute => toIsoString data true false false + | .hourMinuteColon => toIsoString data true false true + | .hourMinuteSecond => toIsoString data true true false + | .hourMinuteSecondColon => toIsoString data true true true + | .x format => + match format with + | .hour => + toIsoString data (data.second.toMinutes.val % 60 ≠ 0) false false + | .hourMinute => + toIsoString data true false false + | .hourMinuteColon => + toIsoString data true (data.second.val % 60 ≠ 0) true + | .hourMinuteSecond => + toIsoString data true (data.second.val % 60 ≠ 0) false + | .hourMinuteSecondColon => + toIsoString data true true true + | .Z format => + match format with + | .hourMinute => + toIsoString data true false false + | .full => + if data.second.val = 0 + then "GMT" + else s!"GMT{toIsoString data true false true}" + | .hourMinuteSecondColon => + if data.second == 0 + then "Z" + else toIsoString data true (data.second.val % 60 ≠ 0) true + +private def dateFromModifier (date : DateTime tz) : TypeFormat modifier := + match modifier with + | .G _ => date.era + | .y _ => date.year + | .u _ => date.year + | .D _ => Sigma.mk _ date.dayOfYear + | .MorL _ => date.month + | .d _ => date.day + | .Qorq _ => date.quarter + | .w _ => date.weekOfYear + | .W _ => date.alignedWeekOfMonth + | .E _ => date.weekday + | .eorc _ => date.weekday + | .F _ => date.weekOfMonth + | .a _ => HourMarker.ofOrdinal date.hour + | .h _ => HourMarker.toRelative date.hour |>.fst + | .K _ => date.hour.emod 12 (by decide) + | .k _ => date.hour.shiftTo1BasedHour + | .H _ => date.hour + | .m _ => date.minute + | .s _ => date.date.get.time.second + | .S _ => date.nanosecond + | .A _ => date.date.get.time.toMilliseconds + | .n _ => date.nanosecond + | .N _ => date.date.get.time.toNanoseconds + | .V => tz.name + | .z .short => tz.abbreviation + | .z .full => tz.name + | .O _ => tz.offset + | .X _ => tz.offset + | .x _ => tz.offset + | .Z _ => tz.offset + +private def parseMonthLong : Parser Month.Ordinal + := pstring "January" *> pure ⟨1, by decide⟩ + <|> pstring "February" *> pure ⟨2, by decide⟩ + <|> pstring "March" *> pure ⟨3, by decide⟩ + <|> pstring "April" *> pure ⟨4, by decide⟩ + <|> pstring "May" *> pure ⟨5, by decide⟩ + <|> pstring "June" *> pure ⟨6, by decide⟩ + <|> pstring "July" *> pure ⟨7, by decide⟩ + <|> pstring "August" *> pure ⟨8, by decide⟩ + <|> pstring "September" *> pure ⟨9, by decide⟩ + <|> pstring "October" *> pure ⟨10, by decide⟩ + <|> pstring "November" *> pure ⟨11, by decide⟩ + <|> pstring "December" *> pure ⟨12, by decide⟩ + +/-- +Parses a short value of a `Month.Ordinal` +-/ +def parseMonthShort : Parser Month.Ordinal + := pstring "Jan" *> pure ⟨1, by decide⟩ + <|> pstring "Feb" *> pure ⟨2, by decide⟩ + <|> pstring "Mar" *> pure ⟨3, by decide⟩ + <|> pstring "Apr" *> pure ⟨4, by decide⟩ + <|> pstring "May" *> pure ⟨5, by decide⟩ + <|> pstring "Jun" *> pure ⟨6, by decide⟩ + <|> pstring "Jul" *> pure ⟨7, by decide⟩ + <|> pstring "Aug" *> pure ⟨8, by decide⟩ + <|> pstring "Sep" *> pure ⟨9, by decide⟩ + <|> pstring "Oct" *> pure ⟨10, by decide⟩ + <|> pstring "Nov" *> pure ⟨11, by decide⟩ + <|> pstring "Dec" *> pure ⟨12, by decide⟩ + +private def parseMonthNarrow : Parser Month.Ordinal + := pstring "J" *> pure ⟨1, by decide⟩ + <|> pstring "F" *> pure ⟨2, by decide⟩ + <|> pstring "M" *> pure ⟨3, by decide⟩ + <|> pstring "A" *> pure ⟨4, by decide⟩ + <|> pstring "M" *> pure ⟨5, by decide⟩ + <|> pstring "J" *> pure ⟨6, by decide⟩ + <|> pstring "J" *> pure ⟨7, by decide⟩ + <|> pstring "A" *> pure ⟨8, by decide⟩ + <|> pstring "S" *> pure ⟨9, by decide⟩ + <|> pstring "O" *> pure ⟨10, by decide⟩ + <|> pstring "N" *> pure ⟨11, by decide⟩ + <|> pstring "D" *> pure ⟨12, by decide⟩ + +private def parseWeekdayLong : Parser Weekday + := pstring "Sunday" *> pure Weekday.sunday + <|> pstring "Monday" *> pure Weekday.monday + <|> pstring "Tuesday" *> pure Weekday.tuesday + <|> pstring "Wednesday" *> pure Weekday.wednesday + <|> pstring "Thursday" *> pure Weekday.thursday + <|> pstring "Friday" *> pure Weekday.friday + <|> pstring "Saturday" *> pure Weekday.saturday + +private def parseWeekdayShort : Parser Weekday + := pstring "Sun" *> pure Weekday.sunday + <|> pstring "Mon" *> pure Weekday.monday + <|> pstring "Tue" *> pure Weekday.tuesday + <|> pstring "Wed" *> pure Weekday.wednesday + <|> pstring "Thu" *> pure Weekday.thursday + <|> pstring "Fri" *> pure Weekday.friday + <|> pstring "Sat" *> pure Weekday.saturday + +private def parseWeekdayNarrow : Parser Weekday + := pstring "S" *> pure Weekday.sunday + <|> pstring "M" *> pure Weekday.monday + <|> pstring "T" *> pure Weekday.tuesday + <|> pstring "W" *> pure Weekday.wednesday + <|> pstring "T" *> pure Weekday.thursday + <|> pstring "F" *> pure Weekday.friday + <|> pstring "S" *> pure Weekday.saturday + +private def parseEraShort : Parser Year.Era + := pstring "BCE" *> pure Year.Era.bce + <|> pstring "CE" *> pure Year.Era.ce + +private def parseEraLong : Parser Year.Era + := pstring "Before Common Era" *> pure Year.Era.bce + <|> pstring "Common Era" *> pure Year.Era.ce + +private def parseEraNarrow : Parser Year.Era + := pstring "B" *> pure Year.Era.bce + <|> pstring "C" *> pure Year.Era.ce + +private def parseQuarterNumber : Parser Month.Quarter + := pstring "1" *> pure ⟨1, by decide⟩ + <|> pstring "2" *> pure ⟨2, by decide⟩ + <|> pstring "3" *> pure ⟨3, by decide⟩ + <|> pstring "4" *> pure ⟨4, by decide⟩ + +private def parseQuarterLong : Parser Month.Quarter + := pstring "1st quarter" *> pure ⟨1, by decide⟩ + <|> pstring "2nd quarter" *> pure ⟨2, by decide⟩ + <|> pstring "3rd quarter" *> pure ⟨3, by decide⟩ + <|> pstring "4th quarter" *> pure ⟨4, by decide⟩ + +private def parseQuarterShort : Parser Month.Quarter + := pstring "Q1" *> pure ⟨1, by decide⟩ + <|> pstring "Q2" *> pure ⟨2, by decide⟩ + <|> pstring "Q3" *> pure ⟨3, by decide⟩ + <|> pstring "Q4" *> pure ⟨4, by decide⟩ + +private def parseMarkerShort : Parser HourMarker + := pstring "AM" *> pure HourMarker.am + <|> pstring "PM" *> pure HourMarker.pm + +private def parseMarkerLong : Parser HourMarker + := pstring "Ante Meridiem" *> pure HourMarker.am + <|> pstring "Post Meridiem" *> pure HourMarker.pm + +private def parseMarkerNarrow : Parser HourMarker + := pstring "A" *> pure HourMarker.am + <|> pstring "P" *> pure HourMarker.pm + +private def exactly (parse : Parser α) (size : Nat) : Parser (Array α) := + let rec go (acc : Array α) (count : Nat) : Parser (Array α) := + if count ≥ size then + pure acc + else do + let res ← parse + go (acc.push res) count.succ + termination_by size - count + + go #[] 12 + +private def exactlyChars (parse : Parser Char) (size : Nat) : Parser String := + let rec go (acc : String) (count : Nat) : Parser String := + if count ≥ size then + pure acc + else do + let res ← parse + go (acc.push res) count.succ + termination_by size - count + + go "" 0 + +private def parseSigned (parser : Parser Nat) : Parser Int := do + let signed ← optional (pstring "-") + let res ← parser + return if signed.isSome then -res else res + +private def parseNum (size : Nat) : Parser Nat := + String.toNat! <$> exactlyChars (satisfy Char.isDigit) size + +private def parseAtLeastNum (size : Nat) : Parser Nat := + String.toNat! <$> do + let start ← exactlyChars (satisfy Char.isDigit) size + let end_ ← manyChars (satisfy Char.isDigit) + pure (start ++ end_) + +private def parseFractionNum (size : Nat) (pad : Nat) : Parser Nat := + String.toNat! <$> rightPad pad '0' <$> exactlyChars (satisfy Char.isDigit) size + +private def parseIdentifier : Parser String := + many1Chars (satisfy (fun x => x.isAlpha ∨ x.isDigit ∨ x = '_' ∨ x = '-' ∨ x = '/')) + +private def parseNatToBounded { n m : Nat } (parser : Parser Nat) : Parser (Bounded.LE n m) := do + let res ← parser + if h : n ≤ res ∧ res ≤ m then + return Bounded.LE.ofNat' res h + else + fail s!"need a natural number in the interval of {n} to {m}" + +private inductive Reason + | yes + | no + | optional + +private def parseOffset (withMinutes : Reason) (withSeconds : Reason) (withColon : Bool) : Parser Offset := do + let sign ← (pchar '+' *> pure 1) <|> (pchar '-' *> pure (-1)) + let hours : Hour.Offset ← UnitVal.ofInt <$> parseNum 2 + + let colon := if withColon then pchar ':' else pure ':' + + let parseUnit {n} (reason : Reason) : Parser (Option (UnitVal n)) := + match reason with + | .yes => some <$> (colon *> UnitVal.ofInt <$> parseNum 2) + | .no => pure none + | .optional => optional (colon *> UnitVal.ofInt <$> parseNum 2) + + let minutes : Option Minute.Offset ← parseUnit withMinutes + let seconds : Option Second.Offset ← parseUnit withSeconds + + let hours := hours.toSeconds + (minutes.getD 0).toSeconds + (seconds.getD 0) + + return Offset.ofSeconds ⟨hours.val * sign⟩ + +private def parseWith : (mod : Modifier) → Parser (TypeFormat mod) + | .G format => + match format with + | .short => parseEraShort + | .full => parseEraLong + | .narrow => parseEraNarrow + | .y format => + match format with + | .twoDigit => (2000 + ·) <$> (Int.ofNat <$> parseNum 2) + | .fourDigit => Int.ofNat <$> parseNum 4 + | .extended n => Int.ofNat <$> parseAtLeastNum n + | .u format => + match format with + | .twoDigit => (2000 + ·) <$> (parseSigned <| parseNum 2) + | .fourDigit => parseSigned <| parseAtLeastNum 4 + | .extended n => parseSigned <| parseAtLeastNum n + | .D format => Sigma.mk true <$> parseNatToBounded (parseAtLeastNum format.padding) + | .MorL format => + match format with + | .inl format => parseNatToBounded (parseAtLeastNum format.padding) + | .inr .short => parseMonthShort + | .inr .full => parseMonthLong + | .inr .narrow => parseMonthNarrow + | .d format => parseNatToBounded (parseAtLeastNum format.padding) + | .Qorq format => + match format with + | .inl format => parseNatToBounded (parseAtLeastNum format.padding) + | .inr .short => parseQuarterShort + | .inr .full => parseQuarterLong + | .inr .narrow => parseQuarterNumber + | .w format => parseNatToBounded (parseAtLeastNum format.padding) + | .W format => parseNatToBounded (parseAtLeastNum format.padding) + | .E format => + match format with + | .short => parseWeekdayShort + | .full => parseWeekdayLong + | .narrow => parseWeekdayNarrow + | .eorc format => + match format with + | .inl format => Weekday.ofOrdinal <$> parseNatToBounded (parseAtLeastNum format.padding) + | .inr .short => parseWeekdayShort + | .inr .full => parseWeekdayLong + | .inr .narrow => parseWeekdayNarrow + | .F format => parseNatToBounded (parseAtLeastNum format.padding) + | .a format => + match format with + | .short => parseMarkerShort + | .full => parseMarkerLong + | .narrow => parseMarkerNarrow + | .h format => parseNatToBounded (parseAtLeastNum format.padding) + | .K format => parseNatToBounded (parseAtLeastNum format.padding) + | .k format => parseNatToBounded (parseAtLeastNum format.padding) + | .H format => parseNatToBounded (parseAtLeastNum format.padding) + | .m format => parseNatToBounded (parseAtLeastNum format.padding) + | .s format => Sigma.mk true <$> (parseNatToBounded (parseAtLeastNum format.padding)) + | .S format => + match format with + | .nano => parseNatToBounded (parseAtLeastNum 9) + | .truncated n => parseNatToBounded (parseFractionNum n 9) + | .A format => Millisecond.Offset.ofNat <$> (parseAtLeastNum format.padding) + | .n format => parseNatToBounded (parseAtLeastNum format.padding) + | .N format => Nanosecond.Offset.ofNat <$> (parseAtLeastNum format.padding) + | .V => parseIdentifier + | .z format => + match format with + | .short => parseIdentifier + | .full => parseIdentifier + | .O format => + match format with + | .short => pstring "GMT" *> parseOffset .no .no false + | .full => pstring "GMT" *> parseOffset .yes .optional false + | .X format => + let p : Parser Offset := + match format with + | .hour => parseOffset .no .no false + | .hourMinute => parseOffset .yes .no false + | .hourMinuteColon => parseOffset .yes .no true + | .hourMinuteSecond => parseOffset .yes .yes false + | .hourMinuteSecondColon => parseOffset .yes .yes true + p <|> (pstring "Z" *> pure (Offset.ofSeconds 0)) + | .x format => + match format with + | .hour => + parseOffset .optional .no false + | .hourMinute => + parseOffset .yes .no false + | .hourMinuteColon => + parseOffset .yes .optional true + | .hourMinuteSecond => + parseOffset .yes .optional false + | .hourMinuteSecondColon => + parseOffset .yes .yes true + | .Z format => + match format with + | .hourMinute => + parseOffset .yes .no false + | .full => do + skipString "GMT" + let res ← optional (parseOffset .yes .no true) + return res.getD Offset.zero + | .hourMinuteSecondColon => + (skipString "Z" *> pure Offset.zero) + <|> (parseOffset .yes .optional true) + +private def formatPartWithDate (date : DateTime tz) (part : FormatPart) : String := + match part with + | .modifier mod => formatWith mod (dateFromModifier date) + | .string s => s + +@[simp] +private def FormatType (result : Type) : FormatString → Type + | .modifier entry :: xs => (TypeFormat entry) → (FormatType result xs) + | .string _ :: xs => (FormatType result xs) + | [] => result + +namespace GenericFormat + +private structure DateBuilder where + G : Option Year.Era := none + y : Option Year.Offset := none + u : Option Year.Offset := none + D : Option (Sigma Day.Ordinal.OfYear) := none + MorL : Option Month.Ordinal := none + d : Option Day.Ordinal := none + Qorq : Option Month.Quarter := none + w : Option Week.Ordinal := none + W : Option Week.Ordinal.OfMonth := none + E : Option Weekday := none + eorc : Option Weekday := none + F : Option (Bounded.LE 1 5) := none + a : Option HourMarker := none + h : Option (Bounded.LE 1 12) := none + K : Option (Bounded.LE 0 11) := none + k : Option (Bounded.LE 1 24) := none + H : Option Hour.Ordinal := none + m : Option Minute.Ordinal := none + s : Option (Sigma Second.Ordinal) := none + S : Option Nanosecond.Ordinal := none + A : Option Millisecond.Offset := none + n : Option Nanosecond.Ordinal := none + N : Option Nanosecond.Offset := none + V : Option String := none + z : Option String := none + zabbrev : Option String := none + O : Option Offset := none + X : Option Offset := none + x : Option Offset := none + Z : Option Offset := none + +namespace DateBuilder + +private def insert (date : DateBuilder) (modifier : Modifier) (data : TypeFormat modifier) : DateBuilder := + match modifier with + | .G _ => { date with G := some data } + | .y _ => { date with y := some data } + | .u _ => { date with u := some data } + | .D _ => { date with D := some data } + | .MorL _ => { date with MorL := some data } + | .d _ => { date with d := some data } + | .Qorq _ => { date with Qorq := some data } + | .w _ => { date with w := some data } + | .W _ => { date with W := some data } + | .E _ => { date with E := some data } + | .eorc _ => { date with eorc := some data } + | .F _ => { date with F := some data } + | .a _ => { date with a := some data } + | .h _ => { date with h := some data } + | .K _ => { date with K := some data } + | .k _ => { date with k := some data } + | .H _ => { date with H := some data } + | .m _ => { date with m := some data } + | .s _ => { date with s := some data } + | .S _ => { date with S := some data } + | .A _ => { date with A := some data } + | .n _ => { date with n := some data } + | .N _ => { date with N := some data } + | .V => { date with V := some data } + | .z .full => { date with z := some data } + | .z .short => { date with zabbrev := some data } + | .O _ => { date with O := some data } + | .X _ => { date with X := some data } + | .x _ => { date with x := some data } + | .Z _ => { date with Z := some data } + +private def convertYearAndEra (year : Year.Offset) : Year.Era → Year.Offset + | .ce => year + | .bce => -(year + 1) + +private def build (builder : DateBuilder) (aw : Awareness) : Option aw.type := + let offset := builder.O <|> builder.X <|> builder.x <|> builder.Z |>.getD Offset.zero + + let tz : TimeZone := { + offset, + name := builder.V <|> builder.z |>.getD (offset.toIsoString true), + abbreviation := builder.zabbrev |>.getD (offset.toIsoString true), + isDST := false, + } + + let month := builder.MorL |>.getD 0 + let day := builder.d |>.getD 0 + let era := (builder.G.getD .ce) + + let year + := builder.u + <|> ((convertYearAndEra · era) <$> builder.y) + |>.getD 0 + + let hour : Option (Bounded.LE 0 23) := + if let some marker := builder.a then + marker.toAbsolute <$> builder.h + <|> marker.toAbsolute <$> ((Bounded.LE.add · 1) <$> builder.K) + else + none + + let hour := + hour <|> ( + let one : Option (Bounded.LE 0 23) := builder.H + let other : Option (Bounded.LE 0 23) := (Bounded.LE.sub · 1) <$> builder.k + (one <|> other)) + |>.getD ⟨0, by decide⟩ + + let minute := builder.m |>.getD 0 + let second := builder.s |>.getD ⟨false, 0⟩ + let nano := (builder.n <|> builder.S) |>.getD 0 + + let time : PlainTime + := PlainTime.ofNanoseconds <$> builder.N + <|> PlainTime.ofMilliseconds <$> builder.A + |>.getD (PlainTime.mk hour minute second nano) + + let datetime : Option PlainDateTime := + if valid : year.Valid month day then + let date : PlainDate := { year, month, day, valid } + some { date, time } + else + none + + match aw with + | .only newTz => (ofPlainDateTime · newTz) <$> datetime + | .any => (ZonedDateTime.ofPlainDateTime · (ZoneRules.ofTimeZone tz)) <$> datetime + +end DateBuilder + +private def parseWithDate (date : DateBuilder) (mod : FormatPart) : Parser DateBuilder := do + match mod with + | .modifier s => do + let res ← parseWith s + return date.insert s res + | .string s => pstring s *> pure date + +/-- +Constructs a new `GenericFormat` specification for a date-time string. Modifiers can be combined to create +custom formats, such as "YYYY, MMMM, D". +-/ +def spec (input : String) : Except String (GenericFormat tz) := do + let string ← specParser.run input + return ⟨string⟩ + +/-- +Builds a `GenericFormat` from the input string. If parsing fails, it will panic +-/ +def spec! (input : String) : GenericFormat tz := + match specParser.run input with + | .ok res => ⟨res⟩ + | .error res => panic! res + +/-- +Formats a `DateTime` value into a string using the given `GenericFormat`. +-/ +def format (format : GenericFormat aw) (date : DateTime tz) : String := + let mapper (part : FormatPart) := + match aw with + | .any => formatPartWithDate date part + | .only tz => formatPartWithDate (date.convertTimeZone tz) part + + format.string.map mapper + |> String.join + +private def parser (format : FormatString) (aw : Awareness) : Parser (aw.type) := + let rec go (builder : DateBuilder) (x : FormatString) : Parser aw.type := + match x with + | x :: xs => parseWithDate builder x >>= (go · xs) + | [] => + match builder.build aw with + | some res => pure res + | none => fail "could not parse the date" + go {} format + +/-- +Parser for a format with a builder. +-/ +def builderParser (format: FormatString) (func: FormatType (Option α) format) : Parser α := + let rec go (format : FormatString) (func: FormatType (Option α) format) : Parser α := + match format with + | .modifier x :: xs => do + let res ← parseWith x + go xs (func res) + | .string s :: xs => skipString s *> (go xs func) + | [] => + match func with + | some res => eof *> pure res + | none => fail "invalid date." + go format func + +/-- +Parses the input string into a `ZoneDateTime`. +-/ +def parse (format : GenericFormat aw) (input : String) : Except String aw.type := + (parser format.string aw <* eof).run input + +/-- +Parses the input string into a `ZoneDateTime` and panics if its wrong. +-/ +def parse! (format : GenericFormat aw) (input : String) : aw.type := + match parse format input with + | .ok res => res + | .error err => panic! err + +/-- +Parses an input string using a builder function to produce a value. +-/ +def parseBuilder (format : GenericFormat aw) (builder : FormatType (Option α) format.string) (input : String) : Except String α := + (builderParser format.string builder).run input + +/-- +Parses an input string using a builder function, panicking on errors. +-/ +def parseBuilder! [Inhabited α] (format : GenericFormat aw) (builder : FormatType (Option α) format.string) (input : String) : α := + match parseBuilder format builder input with + | .ok res => res + | .error err => panic! err + +/-- +Formats the date using the format into a String, using a `getInfo` function to get the information needed to build the `String`. +-/ +def formatGeneric (format : GenericFormat aw) (getInfo : (typ : Modifier) → Option (TypeFormat typ)) : Option String := + let rec go (data : String) : (format : FormatString) → Option String + | .modifier x :: xs => do go (data ++ formatWith x (← getInfo x)) xs + | .string x :: xs => go (data ++ x) xs + | [] => data + go "" format.string + +/-- +Constructs a `FormatType` function to format a date into a string using a `GenericFormat`. +-/ +def formatBuilder (format : GenericFormat aw) : FormatType String format.string := + let rec go (data : String) : (format : FormatString) → FormatType String format + | .modifier x :: xs => fun res => go (data ++ formatWith x res) xs + | .string x :: xs => go (data ++ x) xs + | [] => data + go "" format.string + +end GenericFormat + +/-- +Typeclass for formatting and parsing values with the given format type. +-/ +class Format (f : Type) (typ : Type → f → Type) where + /-- + Converts a format `f` into a string. + -/ + format : (fmt : f) → typ String fmt + + /-- + Parses a string into a format using the provided format type `f`. + -/ + parse : (fmt : f) → typ (Option α) fmt → String → Except String α + +instance : Format (GenericFormat aw) (FormatType · ·.string) where + format := GenericFormat.formatBuilder + parse := GenericFormat.parseBuilder + +end Time diff --git a/src/Std/Time/Internal.lean b/src/Std/Time/Internal.lean new file mode 100644 index 000000000000..596a2a0489c7 --- /dev/null +++ b/src/Std/Time/Internal.lean @@ -0,0 +1,8 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Internal.Bounded +import Std.Time.Internal.UnitVal diff --git a/src/Std/Time/Internal/Bounded.lean b/src/Std/Time/Internal/Bounded.lean new file mode 100644 index 000000000000..897636c9a077 --- /dev/null +++ b/src/Std/Time/Internal/Bounded.lean @@ -0,0 +1,474 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Init.Data.Int + +namespace Std +namespace Time +namespace Internal + +set_option linter.all true in + +/-- +A `Bounded` is represented by an `Int` that is constrained by a lower and higher bounded using some +relation `rel`. It includes all the integers that `rel lo val ∧ rel val hi`. +-/ +def Bounded (rel : Int → Int → Prop) (lo : Int) (hi : Int) := { val : Int // rel lo val ∧ rel val hi } + +namespace Bounded + +@[always_inline] +instance : LE (Bounded rel n m) where + le l r := l.val ≤ r.val + +@[always_inline] +instance : LT (Bounded rel n m) where + lt l r := l.val < r.val + +@[always_inline] +instance : Repr (Bounded rel m n) where + reprPrec n := reprPrec n.val + +@[always_inline] +instance : BEq (Bounded rel n m) where + beq x y := (x.val = y.val) + +@[always_inline] +instance {x y : Bounded rel a b} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +/-- +A `Bounded` integer that the relation used is the the less-equal relation so, it includes all +integers that `lo ≤ val ≤ hi`. +-/ +abbrev LE := @Bounded LE.le + +/-- +Casts the boundaries of the `Bounded` using equivalences. +-/ +@[inline] +def cast {rel : Int → Int → Prop} {lo₁ lo₂ hi₁ hi₂ : Int} (h₁ : lo₁ = lo₂) (h₂ : hi₁ = hi₂) (b : Bounded rel lo₁ hi₁) : Bounded rel lo₂ hi₂ := + .mk b.val ⟨h₁ ▸ b.property.1, h₂ ▸ b.property.2⟩ + +/-- +A `Bounded` integer that the relation used is the the less-than relation so, it includes all +integers that `lo < val < hi`. +-/ +abbrev LT := @Bounded LT.lt + +/-- +Creates a new `Bounded` Integer. +-/ +@[inline] +def mk {rel : Int → Int → Prop} (val : Int) (proof : rel lo val ∧ rel val hi) : @Bounded rel lo hi := + ⟨val, proof⟩ + +/-- +Convert a `Int` to a `Bounded` if it checks. +-/ +@[inline] +def ofInt? [DecidableRel rel] (val : Int) : Option (Bounded rel lo hi) := + if h : rel lo ↑val ∧ rel ↑val hi then + some ⟨val, h⟩ + else + none + +namespace LE + +/-- +Convert a `Nat` to a `Bounded.LE` by wrapping it. +-/ +@[inline] +def ofNatWrapping { lo hi : Int } (val : Int) (h : lo ≤ hi) : Bounded.LE lo hi := by + let range := hi - lo + 1 + have range_pos := Int.add_pos_of_nonneg_of_pos (b := 1) (Int.sub_nonneg_of_le h) (by decide) + have not_zero := Int.ne_iff_lt_or_gt.mpr (Or.inl range_pos) + have mod_nonneg : 0 ≤ (val - lo) % range := Int.emod_nonneg (val - lo) not_zero.symm + have add_nonneg : lo ≤ lo + (val - lo) % range := Int.le_add_of_nonneg_right mod_nonneg + have mod_range : (val - lo) % (hi - lo + 1) < range := Int.emod_lt_of_pos (a := val - lo) range_pos + refine ⟨((val - lo) % range + range) % range + lo, And.intro ?_ ?_⟩ + · simp_all [range] + rw [Int.add_comm] at add_nonneg + exact add_nonneg + · apply Int.add_le_of_le_sub_right + simp_all [range] + exact Int.le_of_lt_add_one mod_range + +instance {k : Nat} : OfNat (Bounded.LE lo (lo + k)) n where + ofNat := + let h : lo ≤ lo + k := Int.le_add_of_nonneg_right (Int.ofNat_zero_le k) + ofNatWrapping n h + +instance {k : Nat} : Inhabited (Bounded.LE lo (lo + k)) where + default := + let h : lo ≤ lo + k := Int.le_add_of_nonneg_right (Int.ofNat_zero_le k) + ofNatWrapping lo h + +/-- +Creates a new `Bounded` integer that the relation is less-equal. +-/ +@[inline] +def mk (val : Int) (proof : lo ≤ val ∧ val ≤ hi) : Bounded.LE lo hi := + ⟨val, proof⟩ + +/-- +Creates a new `Bounded` integer that the relation is less-equal. +-/ +@[inline] +def exact (val : Nat) : Bounded.LE val val := + ⟨val, by simp⟩ + +/-- +Creates a new `Bounded` integer. +-/ +@[inline] +def ofInt { lo hi : Int } (val : Int) : Option (Bounded.LE lo hi) := + if h : lo ≤ val ∧ val ≤ hi + then some ⟨val, h⟩ + else none + +/-- +Convert a `Nat` to a `Bounded.LE`. +-/ +@[inline] +def ofNat (val : Nat) (h : val ≤ hi) : Bounded.LE 0 hi := + Bounded.mk val (And.intro (Int.ofNat_zero_le val) (Int.ofNat_le.mpr h)) + +/-- +Convert a `Nat` to a `Bounded.LE` if it checks. +-/ +@[inline] +def ofNat? { hi : Nat } (val : Nat) : Option (Bounded.LE 0 hi) := + if h : val ≤ hi then + ofNat val h + else + none + +/-- +Convert a `Nat` to a `Bounded.LE` using the lower boundary too. +-/ +@[inline] +def ofNat' (val : Nat) (h : lo ≤ val ∧ val ≤ hi) : Bounded.LE lo hi := + Bounded.mk val (And.intro (Int.ofNat_le.mpr h.left) (Int.ofNat_le.mpr h.right)) + +/-- +Convert a `Nat` to a `Bounded.LE` using the lower boundary too. +-/ +@[inline] +def clip (val : Int) (h : lo ≤ hi) : Bounded.LE lo hi := + if h₀ : lo ≤ val then + if h₁ : val ≤ hi + then ⟨val, And.intro h₀ h₁⟩ + else ⟨hi, And.intro h (Int.le_refl hi)⟩ + else ⟨lo, And.intro (Int.le_refl lo) h⟩ + +/-- +Convert a `Bounded.LE` to a Nat. +-/ +@[inline] +def toNat (n : Bounded.LE lo hi) : Nat := + n.val.toNat + +/-- +Convert a `Bounded.LE` to a Nat. +-/ +@[inline] +def toNat' (n : Bounded.LE lo hi) (h : lo ≥ 0) : Nat := + let h₁ := (Int.le_trans h n.property.left) + match n.val, h₁ with + | .ofNat n, _ => n + | .negSucc _, h => by contradiction + +/-- +Convert a `Bounded.LE` to an Int. +-/ +@[inline] +def toInt (n : Bounded.LE lo hi) : Int := + n.val + +/-- +Convert a `Bounded.LE` to a `Fin`. +-/ +@[inline, simp] +def toFin (n : Bounded.LE lo hi) (h₀ : 0 ≤ lo) : Fin (hi + 1).toNat := by + let h := n.property.right + let h₁ := Int.le_trans h₀ n.property.left + refine ⟨n.val.toNat, (Int.toNat_lt h₁).mpr ?_⟩ + rw [Int.toNat_of_nonneg (by omega)] + exact Int.lt_add_one_of_le h + +/-- +Convert a `Fin` to a `Bounded.LE`. +-/ +@[inline] +def ofFin (fin : Fin (Nat.succ hi)) : Bounded.LE 0 hi := + ofNat fin.val (Nat.le_of_lt_succ fin.isLt) + +/-- +Convert a `Fin` to a `Bounded.LE`. +-/ +@[inline] +def ofFin' {lo : Nat} (fin : Fin (Nat.succ hi)) (h : lo ≤ hi) : Bounded.LE lo hi := + if h₁ : fin.val ≥ lo + then ofNat' fin.val (And.intro h₁ ((Nat.le_of_lt_succ fin.isLt))) + else ofNat' lo (And.intro (Nat.le_refl lo) h) + +/-- +Creates a new `Bounded.LE` using a the modulus of a number. +-/ +@[inline] +def byEmod (b : Int) (i : Int) (hi : i > 0) : Bounded.LE 0 (i - 1) := by + refine ⟨b % i, And.intro ?_ ?_⟩ + · apply Int.emod_nonneg b + intro a + simp_all [Int.lt_irrefl] + · apply Int.le_of_lt_add_one + simp [Int.add_sub_assoc] + exact Int.emod_lt_of_pos b hi + +/-- +Creates a new `Bounded.LE` using a the Truncating modulus of a number. +-/ +@[inline] +def byMod (b : Int) (i : Int) (hi : 0 < i) : Bounded.LE (- (i - 1)) (i - 1) := by + refine ⟨b.tmod i, And.intro ?_ ?_⟩ + · simp [Int.tmod] + split <;> try contradiction + next m n => + let h := Int.emod_nonneg (a := m) (b := n) (Int.ne_of_gt hi) + apply (Int.le_trans · h) + apply Int.le_of_neg_le_neg + simp_all + exact (Int.le_sub_one_of_lt hi) + next m n => + apply Int.neg_le_neg + have h := Int.tmod_lt_of_pos (m + 1) hi + exact Int.le_sub_one_of_lt h + · exact Int.le_sub_one_of_lt (Int.tmod_lt_of_pos b hi) + +/-- +Adjust the bounds of a `Bounded` by setting the lower bound to zero and the maximum value to (m - n). +-/ +@[inline] +def truncate (bounded : Bounded.LE n m) : Bounded.LE 0 (m - n) := by + let ⟨left, right⟩ := bounded.property + refine ⟨bounded.val - n, And.intro ?_ ?_⟩ + all_goals omega + +/-- +Adjust the bounds of a `Bounded` by changing the higher bound if another value `j` satisfies the same +constraint. +-/ +@[inline, simp] +def truncateTop (bounded : Bounded.LE n m) (h : bounded.val ≤ j) : Bounded.LE n j := by + refine ⟨bounded.val, And.intro ?_ ?_⟩ + · exact bounded.property.left + · exact h + +/-- +Adjust the bounds of a `Bounded` by changing the lower bound if another value `j` satisfies the same +constraint. +-/ +@[inline] +def truncateBottom (bounded : Bounded.LE n m) (h : bounded.val ≥ j) : Bounded.LE j m := by + refine ⟨bounded.val, And.intro ?_ ?_⟩ + · exact h + · exact bounded.property.right + +/-- +Adjust the bounds of a `Bounded` by adding a constant value to both the lower and upper bounds. +-/ +@[inline] +def neg (bounded : Bounded.LE n m) : Bounded.LE (-m) (-n) := by + refine ⟨-bounded.val, And.intro ?_ ?_⟩ + · exact Int.neg_le_neg bounded.property.right + · exact Int.neg_le_neg bounded.property.left + +/-- +Adjust the bounds of a `Bounded` by adding a constant value to both the lower and upper bounds. +-/ +@[inline, simp] +def add (bounded : Bounded.LE n m) (num : Int) : Bounded.LE (n + num) (m + num) := by + refine ⟨bounded.val + num, And.intro ?_ ?_⟩ + all_goals apply (Int.add_le_add · (Int.le_refl num)) + · exact bounded.property.left + · exact bounded.property.right + +/-- +Adjust the bounds of a `Bounded` by adding a constant value to both the lower and upper bounds. +-/ +@[inline] +def addProven (bounded : Bounded.LE n m) (h₀ : bounded.val + num ≤ m) (h₁ : num ≥ 0) : Bounded.LE n m := by + refine ⟨bounded.val + num, And.intro ?_ ?_⟩ + · exact Int.le_trans bounded.property.left (Int.le_add_of_nonneg_right h₁) + · exact h₀ + +/-- +Adjust the bounds of a `Bounded` by adding a constant value to the upper bounds. +-/ +@[inline] +def addTop (bounded : Bounded.LE n m) (num : Int) (h : num ≥ 0) : Bounded.LE n (m + num) := by + refine ⟨bounded.val + num, And.intro ?_ ?_⟩ + · let h := Int.add_le_add bounded.property.left h + simp at h + exact h + · exact Int.add_le_add bounded.property.right (Int.le_refl num) + +/-- +Adjust the bounds of a `Bounded` by adding a constant value to the lower bounds. +-/ +@[inline] +def subBottom (bounded : Bounded.LE n m) (num : Int) (h : num ≥ 0) : Bounded.LE (n - num) m := by + refine ⟨bounded.val - num, And.intro ?_ ?_⟩ + · exact Int.add_le_add bounded.property.left (Int.le_refl (-num)) + · let h := Int.sub_le_sub bounded.property.right h + simp at h + exact h + +/-- +Adds two `Bounded` and adjust the boundaries. +-/ +@[inline] +def addBounds (bounded : Bounded.LE n m) (bounded₂ : Bounded.LE i j) : Bounded.LE (n + i) (m + j) := by + refine ⟨bounded.val + bounded₂.val, And.intro ?_ ?_⟩ + · exact Int.add_le_add bounded.property.left bounded₂.property.left + · exact Int.add_le_add bounded.property.right bounded₂.property.right + +/-- +Adjust the bounds of a `Bounded` by subtracting a constant value to both the lower and upper bounds. +-/ +@[inline, simp] +def sub (bounded : Bounded.LE n m) (num : Int) : Bounded.LE (n - num) (m - num) := + add bounded (-num) + +/-- +Adds two `Bounded` and adjust the boundaries. +-/ +@[inline] +def subBounds (bounded : Bounded.LE n m) (bounded₂ : Bounded.LE i j) : Bounded.LE (n - j) (m - i) := + addBounds bounded bounded₂.neg + +/-- +Adjust the bounds of a `Bounded` by applying the emod operation constraining the lower bound to 0 and +the upper bound to the value. +-/ +@[inline] +def emod (bounded : Bounded.LE n num) (num : Int) (hi : 0 < num) : Bounded.LE 0 (num - 1) := + byEmod bounded.val num hi + +/-- +Adjust the bounds of a `Bounded` by applying the mod operation. +-/ +@[inline] +def mod (bounded : Bounded.LE n num) (num : Int) (hi : 0 < num) : Bounded.LE (- (num - 1)) (num - 1) := + byMod bounded.val num hi + +/-- +Adjust the bounds of a `Bounded` by applying the multiplication operation with a positive number. +-/ +@[inline] +def mul_pos (bounded : Bounded.LE n m) (num : Int) (h : num ≥ 0) : Bounded.LE (n * num) (m * num) := by + refine ⟨bounded.val * num, And.intro ?_ ?_⟩ + · exact Int.mul_le_mul_of_nonneg_right bounded.property.left h + · exact Int.mul_le_mul_of_nonneg_right bounded.property.right h + +/-- +Adjust the bounds of a `Bounded` by applying the multiplication operation with a positive number. +-/ +@[inline] +def mul_neg (bounded : Bounded.LE n m) (num : Int) (h : num ≤ 0) : Bounded.LE (m * num) (n * num) := by + refine ⟨bounded.val * num, And.intro ?_ ?_⟩ + · exact Int.mul_le_mul_of_nonpos_right bounded.property.right h + · exact Int.mul_le_mul_of_nonpos_right bounded.property.left h + +/-- +Adjust the bounds of a `Bounded` by applying the div operation. +-/ +@[inline] +def ediv (bounded : Bounded.LE n m) (num : Int) (h : num > 0) : Bounded.LE (n / num) (m / num) := by + let ⟨left, right⟩ := bounded.property + refine ⟨bounded.val.ediv num, And.intro ?_ ?_⟩ + apply Int.ediv_le_ediv + · exact h + · exact left + · apply Int.ediv_le_ediv + · exact h + · exact right + +@[inline] +def eq {n : Int} : Bounded.LE n n := + ⟨n, And.intro (Int.le_refl n) (Int.le_refl n)⟩ + +/-- +Expand the range of a bounded value. +-/ +@[inline] +def expand (bounded : Bounded.LE lo hi) (h : hi ≤ nhi) (h₁ : nlo ≤ lo) : Bounded.LE nlo nhi := + ⟨bounded.val, And.intro (Int.le_trans h₁ bounded.property.left) (Int.le_trans bounded.property.right h)⟩ + +/-- +Expand the bottom of the bounded to a number `nhi` is `hi` is less or equal to the previous higher bound. +-/ +@[inline] +def expandTop (bounded : Bounded.LE lo hi) (h : hi ≤ nhi) : Bounded.LE lo nhi := + expand bounded h (Int.le_refl lo) + +/-- +Expand the bottom of the bounded to a number `nlo` if `lo` is greater or equal to the previous lower bound. +-/ +@[inline] +def expandBottom (bounded : Bounded.LE lo hi) (h : nlo ≤ lo) : Bounded.LE nlo hi := + expand bounded (Int.le_refl hi) h + +/-- +Adds one to the value of the bounded if the value is less than the higher bound of the bounded number. +-/ +@[inline] +def succ (bounded : Bounded.LE lo hi) (h : bounded.val < hi) : Bounded.LE lo hi := + let left := bounded.property.left + ⟨bounded.val + 1, And.intro (by omega) (by omega)⟩ + +/-- +Returns the absolute value of the bounded number `bo` with bounds `-(i - 1)` to `i - 1`. The result +will be a new bounded number with bounds `0` to `i - 1`. +-/ +@[inline] +def abs (bo : Bounded.LE (-i) i) : Bounded.LE 0 i := + if h : bo.val ≥ 0 then + bo.truncateBottom h + else by + let r := bo.truncateTop (Int.le_of_lt (Int.not_le.mp h)) |>.neg + rw [Int.neg_neg] at r + exact r + +/-- +Returns the maximum between a number and the bounded. +-/ +def max (bounded : Bounded.LE n m) (val : Int) : Bounded.LE (Max.max n val) (Max.max m val) := by + let ⟨left, right⟩ := bounded.property + refine ⟨Max.max bounded.val val, And.intro ?_ ?_⟩ + + all_goals + simp [Int.max_def] + split <;> split + + next h => simp [h, Int.le_trans left h] + next h h₁ => exact Int.le_of_lt <| Int.not_le.mp h₁ + next h => simp [h, Int.le_trans left h] + next h h₁ => exact left + next h h₁ => simp [h, Int.le_trans left h] + next h h₁ => exact Int.le_of_lt <| Int.not_le.mp h₁ + next h h₁ => + let h₃ := Int.lt_of_lt_of_le (Int.not_le.mp h) right + let h₄ := Int.not_le.mpr h₃ h₁ + contradiction + next h h₁ => exact right + +end LE +end Bounded +end Internal +end Time +end Std diff --git a/src/Std/Time/Internal/UnitVal.lean b/src/Std/Time/Internal/UnitVal.lean new file mode 100644 index 000000000000..cc97c0b6481c --- /dev/null +++ b/src/Std/Time/Internal/UnitVal.lean @@ -0,0 +1,125 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Init.Data +import Std.Internal.Rat + +namespace Std +namespace Time +namespace Internal +open Std.Internal +open Lean + +set_option linter.all true + +/-- +A structure representing a unit of a given ratio type `α`. +-/ +structure UnitVal (α : Rat) where + /-- + Creates a `UnitVal` from an `Int`. + -/ + ofInt :: + + /-- + Value inside the UnitVal Value. + -/ + val : Int + deriving Inhabited, BEq + +instance : LE (UnitVal x) where + le x y := x.val ≤ y.val + +instance { x y : UnitVal z }: Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +namespace UnitVal + +/-- +Creates a `UnitVal` from a `Nat`. +-/ +@[inline] +def ofNat (value : Nat) : UnitVal α := + ⟨value⟩ + +/-- +Converts a `UnitVal` to an `Int`. +-/ +@[inline] +def toInt (unit : UnitVal α) : Int := + unit.val + +/-- +Multiplies the `UnitVal` by an `Int`, resulting in a new `UnitVal` with an adjusted ratio. +-/ +@[inline] +def mul (unit : UnitVal a) (factor : Int) : UnitVal (a / factor) := + ⟨unit.val * factor⟩ + +/-- +Divides the `UnitVal` by an `Int`, resulting in a new `UnitVal` with an adjusted ratio. +-/ +@[inline] +def ediv (unit : UnitVal a) (divisor : Int) : UnitVal (a * divisor) := + ⟨unit.val.ediv divisor⟩ + +/-- +Divides the `UnitVal` by an `Int`, resulting in a new `UnitVal` with an adjusted ratio. +-/ +@[inline] +def div (unit : UnitVal a) (divisor : Int) : UnitVal (a * divisor) := + ⟨unit.val.tdiv divisor⟩ + +/-- +Adds two `UnitVal` values of the same ratio. +-/ +@[inline] +def add (u1 : UnitVal α) (u2 : UnitVal α) : UnitVal α := + ⟨u1.val + u2.val⟩ + +/-- +Subtracts one `UnitVal` value from another of the same ratio. +-/ +@[inline] +def sub (u1 : UnitVal α) (u2 : UnitVal α) : UnitVal α := + ⟨u1.val - u2.val⟩ + +/-- +Returns the absolute value of a `UnitVal`. +-/ +@[inline] +def abs (u : UnitVal α) : UnitVal α := + ⟨u.val.natAbs⟩ + +/-- +Converts an `Offset` to another unit type. +-/ +@[inline] +def convert (val : UnitVal a) : UnitVal b := + let ratio := a.div b + ofInt <| val.toInt * ratio.num / ratio.den + +instance : OfNat (UnitVal α) n where ofNat := ⟨Int.ofNat n⟩ + +instance : Repr (UnitVal α) where reprPrec x p := reprPrec x.val p + +instance : LE (UnitVal α) where le x y := x.val ≤ y.val + +instance : LT (UnitVal α) where lt x y := x.val < y.val + +instance : Add (UnitVal α) where add := UnitVal.add + +instance : Sub (UnitVal α) where sub := UnitVal.sub + +instance : Neg (UnitVal α) where neg x := ⟨-x.val⟩ + +instance : ToString (UnitVal n) where toString n := toString n.val + + +end UnitVal +end Internal +end Time +end Std diff --git a/src/Std/Time/Notation.lean b/src/Std/Time/Notation.lean new file mode 100644 index 000000000000..b8b5e35bcfdb --- /dev/null +++ b/src/Std/Time/Notation.lean @@ -0,0 +1,246 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date +import Std.Time.Time +import Std.Time.Zoned +import Std.Time.DateTime +import Std.Time.Format + +namespace Std +namespace Time +open Lean Parser Command Std + +set_option linter.all true + +private def convertText : Text → MacroM (TSyntax `term) + | .short => `(Std.Time.Text.short) + | .full => `(Std.Time.Text.full) + | .narrow => `(Std.Time.Text.narrow) + +private def convertNumber : Number → MacroM (TSyntax `term) + | ⟨padding⟩ => `(Std.Time.Number.mk $(quote padding)) + +private def convertFraction : Fraction → MacroM (TSyntax `term) + | .nano => `(Std.Time.Fraction.nano) + | .truncated digits => `(Std.Time.Fraction.truncated $(quote digits)) + +private def convertYear : Year → MacroM (TSyntax `term) + | .twoDigit => `(Std.Time.Year.twoDigit) + | .fourDigit => `(Std.Time.Year.fourDigit) + | .extended n => `(Std.Time.Year.extended $(quote n)) + +private def convertZoneName : ZoneName → MacroM (TSyntax `term) + | .short => `(Std.Time.ZoneName.short) + | .full => `(Std.Time.ZoneName.full) + +private def convertOffsetX : OffsetX → MacroM (TSyntax `term) + | .hour => `(Std.Time.OffsetX.hour) + | .hourMinute => `(Std.Time.OffsetX.hourMinute) + | .hourMinuteColon => `(Std.Time.OffsetX.hourMinuteColon) + | .hourMinuteSecond => `(Std.Time.OffsetX.hourMinuteSecond) + | .hourMinuteSecondColon => `(Std.Time.OffsetX.hourMinuteSecondColon) + +private def convertOffsetO : OffsetO → MacroM (TSyntax `term) + | .short => `(Std.Time.OffsetO.short) + | .full => `(Std.Time.OffsetO.full) + +private def convertOffsetZ : OffsetZ → MacroM (TSyntax `term) + | .hourMinute => `(Std.Time.OffsetZ.hourMinute) + | .full => `(Std.Time.OffsetZ.full) + | .hourMinuteSecondColon => `(Std.Time.OffsetZ.hourMinuteSecondColon) + +private def convertModifier : Modifier → MacroM (TSyntax `term) + | .G p => do `(Std.Time.Modifier.G $(← convertText p)) + | .y p => do `(Std.Time.Modifier.y $(← convertYear p)) + | .u p => do `(Std.Time.Modifier.u $(← convertYear p)) + | .D p => do `(Std.Time.Modifier.D $(← convertNumber p)) + | .MorL p => + match p with + | .inl num => do `(Std.Time.Modifier.MorL (.inl $(← convertNumber num))) + | .inr txt => do `(Std.Time.Modifier.MorL (.inr $(← convertText txt))) + | .d p => do `(Std.Time.Modifier.d $(← convertNumber p)) + | .Qorq p => + match p with + | .inl num => do `(Std.Time.Modifier.Qorq (.inl $(← convertNumber num))) + | .inr txt => do `(Std.Time.Modifier.Qorq (.inr $(← convertText txt))) + | .w p => do `(Std.Time.Modifier.w $(← convertNumber p)) + | .W p => do `(Std.Time.Modifier.W $(← convertNumber p)) + | .E p => do `(Std.Time.Modifier.E $(← convertText p)) + | .eorc p => + match p with + | .inl num => do `(Std.Time.Modifier.eorc (.inl $(← convertNumber num))) + | .inr txt => do `(Std.Time.Modifier.eorc (.inr $(← convertText txt))) + | .F p => do `(Std.Time.Modifier.F $(← convertNumber p)) + | .a p => do `(Std.Time.Modifier.a $(← convertText p)) + | .h p => do `(Std.Time.Modifier.h $(← convertNumber p)) + | .K p => do `(Std.Time.Modifier.K $(← convertNumber p)) + | .k p => do `(Std.Time.Modifier.k $(← convertNumber p)) + | .H p => do `(Std.Time.Modifier.H $(← convertNumber p)) + | .m p => do `(Std.Time.Modifier.m $(← convertNumber p)) + | .s p => do `(Std.Time.Modifier.s $(← convertNumber p)) + | .S p => do `(Std.Time.Modifier.S $(← convertFraction p)) + | .A p => do `(Std.Time.Modifier.A $(← convertNumber p)) + | .n p => do `(Std.Time.Modifier.n $(← convertNumber p)) + | .N p => do `(Std.Time.Modifier.N $(← convertNumber p)) + | .V => `(Std.Time.Modifier.V) + | .z p => do `(Std.Time.Modifier.z $(← convertZoneName p)) + | .O p => do `(Std.Time.Modifier.O $(← convertOffsetO p)) + | .X p => do `(Std.Time.Modifier.X $(← convertOffsetX p)) + | .x p => do `(Std.Time.Modifier.x $(← convertOffsetX p)) + | .Z p => do `(Std.Time.Modifier.Z $(← convertOffsetZ p)) + +private def convertFormatPart : FormatPart → MacroM (TSyntax `term) + | .string s => `(.string $(Syntax.mkStrLit s)) + | .modifier mod => do `(.modifier $(← convertModifier mod)) + +private def syntaxNat (n : Nat) : MacroM (TSyntax `term) := do + let info ← MonadRef.mkInfoFromRefPos + pure { raw := Syntax.node1 info `num (Lean.Syntax.atom info (toString n)) } + +private def syntaxString (n : String) : MacroM (TSyntax `term) := do + let info ← MonadRef.mkInfoFromRefPos + pure { raw := Syntax.node1 info `str (Lean.Syntax.atom info (toString n)) } + +private def syntaxInt (n : Int) : MacroM (TSyntax `term) := do + match n with + | .ofNat n => `(Int.ofNat $(Syntax.mkNumLit <| toString n)) + | .negSucc n => `(Int.negSucc $(Syntax.mkNumLit <| toString n)) + +private def syntaxBounded (n : Int) : MacroM (TSyntax `term) := do + `(Std.Time.Internal.Bounded.LE.ofNatWrapping $(← syntaxInt n) (by decide)) + +private def syntaxVal (n : Int) : MacroM (TSyntax `term) := do + `(Std.Time.Internal.UnitVal.ofInt $(← syntaxInt n)) + +private def convertOffset (offset : Std.Time.TimeZone.Offset) : MacroM (TSyntax `term) := do + `(Std.Time.TimeZone.Offset.ofSeconds $(← syntaxVal offset.second.val)) + +private def convertTimezone (tz : Std.Time.TimeZone) : MacroM (TSyntax `term) := do + `(Std.Time.TimeZone.mk $(← convertOffset tz.offset) $(Syntax.mkStrLit tz.name) $(Syntax.mkStrLit tz.abbreviation) false) + +private def convertPlainDate (d : Std.Time.PlainDate) : MacroM (TSyntax `term) := do + `(Std.Time.PlainDate.ofYearMonthDayClip $(← syntaxInt d.year) $(← syntaxBounded d.month.val) $(← syntaxBounded d.day.val)) + +private def convertPlainTime (d : Std.Time.PlainTime) : MacroM (TSyntax `term) := do + `(Std.Time.PlainTime.mk $(← syntaxBounded d.hour.val) $(← syntaxBounded d.minute.val) ⟨true, $(← syntaxBounded d.second.snd.val)⟩ $(← syntaxBounded d.nanosecond.val)) + +private def convertPlainDateTime (d : Std.Time.PlainDateTime) : MacroM (TSyntax `term) := do + `(Std.Time.PlainDateTime.mk $(← convertPlainDate d.date) $(← convertPlainTime d.time)) + +private def convertZonedDateTime (d : Std.Time.ZonedDateTime) (identifier := false) : MacroM (TSyntax `term) := do + let plain ← convertPlainDateTime d.toPlainDateTime + + if identifier then + `(Std.Time.ZonedDateTime.ofPlainDateTime $plain <$> Std.Time.Database.defaultGetZoneRules $(Syntax.mkStrLit d.timezone.name)) + else + `(Std.Time.ZonedDateTime.ofPlainDateTime $plain (Std.Time.TimeZone.ZoneRules.ofTimeZone $(← convertTimezone d.timezone))) + +/-- +Defines a syntax for zoned datetime values. It expects a string representing a datetime with +timezone information. + +Example: +`zoned("2024-10-13T15:00:00-03:00")` +-/ +syntax "zoned(" str ")" : term + +/-- +Defines a syntax for zoned datetime values. It expects a string representing a datetime and a +timezone information as a term. + +Example: +`zoned("2024-10-13T15:00:00", timezone)` +-/ +syntax "zoned(" str "," term ")" : term + + +/-- +Defines a syntax for datetime values without timezone. The input should be a string in an +ISO8601-like format. + +Example: +`datetime("2024-10-13T15:00:00")` +-/ +syntax "datetime(" str ")" : term + +/-- +Defines a syntax for date-only values. The input string represents a date in formats like "YYYY-MM-DD". + +Example: +`date("2024-10-13")` +-/ +syntax "date(" str ")" : term + +/-- +Defines a syntax for time-only values. The string should represent a time, either in 24-hour or +12-hour format. + +Example: +`time("15:00:00")` or `time("03:00:00 PM")` +-/ +syntax "time(" str ")" : term + +/-- +Defines a syntax for UTC offset values. The string should indicate the time difference from UTC +(e.g., "-03:00"). + +Example: +`offset("-03:00")` +-/ +syntax "offset(" str ")" : term + +/-- +Defines a syntax for timezone identifiers. The input string should be a valid timezone name or +abbreviation. + +Example: +`timezone("America/Sao_Paulo")` +-/ +syntax "timezone(" str ")" : term + + +macro_rules + | `(zoned( $date:str )) => do + match ZonedDateTime.fromLeanDateTimeWithZoneString date.getString with + | .ok res => do return ← convertZonedDateTime res + | .error _ => + match ZonedDateTime.fromLeanDateTimeWithIdentifierString date.getString with + | .ok res => do return ← convertZonedDateTime res (identifier := true) + | .error res => Macro.throwErrorAt date s!"error: {res}" + + | `(zoned( $date:str, $timezone )) => do + match PlainDateTime.fromLeanDateTimeString date.getString with + | .ok res => do + let plain ← convertPlainDateTime res + `(Std.Time.ZonedDateTime.ofPlainDateTime $plain $timezone) + | .error res => Macro.throwErrorAt date s!"error: {res}" + + | `(datetime( $date:str )) => do + match PlainDateTime.fromLeanDateTimeString date.getString with + | .ok res => do + return ← convertPlainDateTime res + | .error res => Macro.throwErrorAt date s!"error: {res}" + + | `(date( $date:str )) => do + match PlainDate.fromSQLDateString date.getString with + | .ok res => return ← convertPlainDate res + | .error res => Macro.throwErrorAt date s!"error: {res}" + + | `(time( $time:str )) => do + match PlainTime.fromLeanTime24Hour time.getString with + | .ok res => return ← convertPlainTime res + | .error res => Macro.throwErrorAt time s!"error: {res}" + + | `(offset( $offset:str )) => do + match TimeZone.Offset.fromOffset offset.getString with + | .ok res => return ← convertOffset res + | .error res => Macro.throwErrorAt offset s!"error: {res}" + + | `(timezone( $tz:str )) => do + match TimeZone.fromTimeZone tz.getString with + | .ok res => return ← convertTimezone res + | .error res => Macro.throwErrorAt tz s!"error: {res}" diff --git a/src/Std/Time/Notation/Spec.lean b/src/Std/Time/Notation/Spec.lean new file mode 100644 index 000000000000..cd77dd4948e3 --- /dev/null +++ b/src/Std/Time/Notation/Spec.lean @@ -0,0 +1,113 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Date +import Std.Time.Time +import Std.Time.Zoned +import Std.Time.DateTime +import Std.Time.Format.Basic + +namespace Std +namespace Time +open Lean Parser Command Std + +private def convertText : Text → MacroM (TSyntax `term) + | .short => `(Std.Time.Text.short) + | .full => `(Std.Time.Text.full) + | .narrow => `(Std.Time.Text.narrow) + +private def convertNumber : Number → MacroM (TSyntax `term) + | ⟨padding⟩ => `(Std.Time.Number.mk $(quote padding)) + +private def convertFraction : Fraction → MacroM (TSyntax `term) + | .nano => `(Std.Time.Fraction.nano) + | .truncated digits => `(Std.Time.Fraction.truncated $(quote digits)) + +private def convertYear : Year → MacroM (TSyntax `term) + | .twoDigit => `(Std.Time.Year.twoDigit) + | .fourDigit => `(Std.Time.Year.fourDigit) + | .extended n => `(Std.Time.Year.extended $(quote n)) + +private def convertZoneName : ZoneName → MacroM (TSyntax `term) + | .short => `(Std.Time.ZoneName.short) + | .full => `(Std.Time.ZoneName.full) + +private def convertOffsetX : OffsetX → MacroM (TSyntax `term) + | .hour => `(Std.Time.OffsetX.hour) + | .hourMinute => `(Std.Time.OffsetX.hourMinute) + | .hourMinuteColon => `(Std.Time.OffsetX.hourMinuteColon) + | .hourMinuteSecond => `(Std.Time.OffsetX.hourMinuteSecond) + | .hourMinuteSecondColon => `(Std.Time.OffsetX.hourMinuteSecondColon) + +private def convertOffsetO : OffsetO → MacroM (TSyntax `term) + | .short => `(Std.Time.OffsetO.short) + | .full => `(Std.Time.OffsetO.full) + +private def convertOffsetZ : OffsetZ → MacroM (TSyntax `term) + | .hourMinute => `(Std.Time.OffsetZ.hourMinute) + | .full => `(Std.Time.OffsetZ.full) + | .hourMinuteSecondColon => `(Std.Time.OffsetZ.hourMinuteSecondColon) + +private def convertModifier : Modifier → MacroM (TSyntax `term) + | .G p => do `(Std.Time.Modifier.G $(← convertText p)) + | .y p => do `(Std.Time.Modifier.y $(← convertYear p)) + | .u p => do `(Std.Time.Modifier.u $(← convertYear p)) + | .D p => do `(Std.Time.Modifier.D $(← convertNumber p)) + | .MorL p => + match p with + | .inl num => do `(Std.Time.Modifier.MorL (.inl $(← convertNumber num))) + | .inr txt => do `(Std.Time.Modifier.MorL (.inr $(← convertText txt))) + | .d p => do `(Std.Time.Modifier.d $(← convertNumber p)) + | .Qorq p => + match p with + | .inl num => do `(Std.Time.Modifier.Qorq (.inl $(← convertNumber num))) + | .inr txt => do `(Std.Time.Modifier.Qorq (.inr $(← convertText txt))) + | .w p => do `(Std.Time.Modifier.w $(← convertNumber p)) + | .W p => do `(Std.Time.Modifier.W $(← convertNumber p)) + | .E p => do `(Std.Time.Modifier.E $(← convertText p)) + | .eorc p => + match p with + | .inl num => do `(Std.Time.Modifier.eorc (.inl $(← convertNumber num))) + | .inr txt => do `(Std.Time.Modifier.eorc (.inr $(← convertText txt))) + | .F p => do `(Std.Time.Modifier.F $(← convertNumber p)) + | .a p => do `(Std.Time.Modifier.a $(← convertText p)) + | .h p => do `(Std.Time.Modifier.h $(← convertNumber p)) + | .K p => do `(Std.Time.Modifier.K $(← convertNumber p)) + | .k p => do `(Std.Time.Modifier.k $(← convertNumber p)) + | .H p => do `(Std.Time.Modifier.H $(← convertNumber p)) + | .m p => do `(Std.Time.Modifier.m $(← convertNumber p)) + | .s p => do `(Std.Time.Modifier.s $(← convertNumber p)) + | .S p => do `(Std.Time.Modifier.S $(← convertFraction p)) + | .A p => do `(Std.Time.Modifier.A $(← convertNumber p)) + | .n p => do `(Std.Time.Modifier.n $(← convertNumber p)) + | .N p => do `(Std.Time.Modifier.N $(← convertNumber p)) + | .V => `(Std.Time.Modifier.V) + | .z p => do `(Std.Time.Modifier.z $(← convertZoneName p)) + | .O p => do `(Std.Time.Modifier.O $(← convertOffsetO p)) + | .X p => do `(Std.Time.Modifier.X $(← convertOffsetX p)) + | .x p => do `(Std.Time.Modifier.x $(← convertOffsetX p)) + | .Z p => do `(Std.Time.Modifier.Z $(← convertOffsetZ p)) + +private def convertFormatPart : FormatPart → MacroM (TSyntax `term) + | .string s => `(.string $(Syntax.mkStrLit s)) + | .modifier mod => do `(.modifier $(← convertModifier mod)) + +/-- +Syntax for defining a date spec at compile time. +-/ +syntax "datespec(" str ")" : term + +macro_rules + | `(datespec( $format_string:str )) => do + let input := format_string.getString + let format : Except String (GenericFormat .any) := GenericFormat.spec input + match format with + | .ok res => + let alts ← res.string.mapM convertFormatPart + let alts := alts.foldl Syntax.TSepArray.push (Syntax.TSepArray.mk #[] (sep := ",")) + `(⟨[$alts,*]⟩) + | .error err => + Macro.throwErrorAt format_string s!"cannot compile spec: {err}" diff --git a/src/Std/Time/Time.lean b/src/Std/Time/Time.lean new file mode 100644 index 000000000000..43590cf600a0 --- /dev/null +++ b/src/Std/Time/Time.lean @@ -0,0 +1,9 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time.Basic +import Std.Time.Time.HourMarker +import Std.Time.Time.PlainTime diff --git a/src/Std/Time/Time/Basic.lean b/src/Std/Time/Time/Basic.lean new file mode 100644 index 000000000000..754d0bc6bea7 --- /dev/null +++ b/src/Std/Time/Time/Basic.lean @@ -0,0 +1,7 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time.Unit.Basic diff --git a/src/Std/Time/Time/HourMarker.lean b/src/Std/Time/Time/HourMarker.lean new file mode 100644 index 000000000000..0a05dd161384 --- /dev/null +++ b/src/Std/Time/Time/HourMarker.lean @@ -0,0 +1,71 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time.Basic + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +/-- +`HourMarker` represents the two 12-hour periods of the day: `am` for hours between 12:00 AM and +11:59 AM, and `pm` for hours between 12:00 PM and 11:59 PM. +-/ +inductive HourMarker + + /-- + Ante meridiem. + -/ + | am + + /-- + Post meridiem. + -/ + | pm + deriving Repr, BEq + +namespace HourMarker + +/-- +`ofOrdinal` converts an `Hour.Ordinal` value to an `HourMarker`, indicating whether it is AM or PM. +-/ +def ofOrdinal (time : Hour.Ordinal) : HourMarker := + if time.val ≥ 12 then + .pm + else + .am + +/-- +Converts a 12-hour clock time to a 24-hour clock time based on the `HourMarker`. +-/ +def toAbsolute (marker : HourMarker) (time : Bounded.LE 1 12) : Hour.Ordinal := + match marker with + | .am => if time.val = 12 then 0 else time.expand (by decide) (by decide) + | .pm => if time.val = 12 then 12 else time.add 12 |>.emod 24 (by decide) + +/-- +Converts a 24-hour clock time to a 12-hour clock time with a `HourMarker`. +-/ +def toRelative (hour : Hour.Ordinal) : Bounded.LE 1 12 × HourMarker := + if h₀ : hour.val = 0 then + (⟨12, by decide⟩, .am) + else if h₁ : hour.val ≤ 12 then + if hour.val = 12 then + (⟨12, by decide⟩, .pm) + else + Int.ne_iff_lt_or_gt.mp h₀ |>.by_cases + (nomatch Int.not_le.mpr · <| hour.property.left) + (⟨hour.val, And.intro · h₁⟩, .am) + else + let h := Int.not_le.mp h₁ + let t := hour |>.truncateBottom h |>.sub 12 + (t.expandTop (by decide), .pm) + +end HourMarker +end Time +end Std diff --git a/src/Std/Time/Time/PlainTime.lean b/src/Std/Time/Time/PlainTime.lean new file mode 100644 index 000000000000..3c0b2d3e0061 --- /dev/null +++ b/src/Std/Time/Time/PlainTime.lean @@ -0,0 +1,297 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time.Basic + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +/-- +Represents a specific point in a day, including hours, minutes, seconds, and nanoseconds. +-/ +structure PlainTime where + + /-- + `Hour` component of the `PlainTime` + -/ + hour : Hour.Ordinal + + /-- + `Minute` component of the `PlainTime` + -/ + minute : Minute.Ordinal + + /-- + `Second` component of the `PlainTime` + -/ + second : Sigma Second.Ordinal + + /-- + `Nanoseconds` component of the `PlainTime` + -/ + nanosecond : Nanosecond.Ordinal + deriving Repr + +instance : Inhabited PlainTime where + default := ⟨0, 0, Sigma.mk false 0, 0, by decide⟩ + +instance : BEq PlainTime where + beq x y := x.hour.val == y.hour.val && x.minute == y.minute + && x.second.snd.val == y.second.snd.val && x.nanosecond == y.nanosecond + +namespace PlainTime + +/-- +Creates a `PlainTime` value representing midnight (00:00:00.000000000). +-/ +def midnight : PlainTime := + ⟨0, 0, ⟨true, 0⟩, 0⟩ + +/-- +Creates a `PlainTime` value from the provided hours, minutes, seconds and nanoseconds components. +-/ +@[inline] +def ofHourMinuteSecondsNano (hour : Hour.Ordinal) (minute : Minute.Ordinal) (second : Second.Ordinal leap) (nano : Nanosecond.Ordinal) : PlainTime := + ⟨hour, minute, Sigma.mk leap second, nano⟩ + +/-- +Creates a `PlainTime` value from the provided hours, minutes, and seconds. +-/ +@[inline] +def ofHourMinuteSeconds (hour : Hour.Ordinal) (minute : Minute.Ordinal) (second : Second.Ordinal leap) : PlainTime := + ofHourMinuteSecondsNano hour minute second 0 + +/-- +Converts a `PlainTime` value to the total number of milliseconds. +-/ +def toMilliseconds (time : PlainTime) : Millisecond.Offset := + time.hour.toOffset.toMilliseconds + + time.minute.toOffset.toMilliseconds + + time.second.snd.toOffset.toMilliseconds + + time.nanosecond.toOffset.toMilliseconds + +/-- +Converts a `PlainTime` value to the total number of nanoseconds. +-/ +def toNanoseconds (time : PlainTime) : Nanosecond.Offset := + time.hour.toOffset.toNanoseconds + + time.minute.toOffset.toNanoseconds + + time.second.snd.toOffset.toNanoseconds + + time.nanosecond.toOffset + +/-- +Converts a `PlainTime` value to the total number of seconds. +-/ +def toSeconds (time : PlainTime) : Second.Offset := + time.hour.toOffset.toSeconds + + time.minute.toOffset.toSeconds + + time.second.snd.toOffset + +/-- +Converts a `PlainTime` value to the total number of minutes. +-/ +def toMinutes (time : PlainTime) : Minute.Offset := + time.hour.toOffset.toMinutes + + time.minute.toOffset + + time.second.snd.toOffset.toMinutes + +/-- +Converts a `PlainTime` value to the total number of hours. +-/ +def toHours (time : PlainTime) : Hour.Offset := + time.hour.toOffset + +/-- +Creates a `PlainTime` value from a total number of nanoseconds. +-/ +def ofNanoseconds (nanos : Nanosecond.Offset) : PlainTime := + have totalSeconds := nanos.ediv 1000000000 + have remainingNanos := Bounded.LE.byEmod nanos.val 1000000000 (by decide) + have hours := Bounded.LE.byEmod (totalSeconds.val / 3600) 24 (by decide) + have minutes := (Bounded.LE.byEmod totalSeconds.val 3600 (by decide)).ediv 60 (by decide) + have seconds := Bounded.LE.byEmod totalSeconds.val 60 (by decide) + let nanos := Bounded.LE.byEmod nanos.val 1000000000 (by decide) + PlainTime.mk hours minutes (Sigma.mk false seconds) nanos + +/-- +Creates a `PlainTime` value from a total number of millisecond. +-/ +@[inline] +def ofMilliseconds (millis : Millisecond.Offset) : PlainTime := + ofNanoseconds millis.toNanoseconds + +/-- +Creates a `PlainTime` value from a total number of seconds. +-/ +@[inline] +def ofSeconds (secs : Second.Offset) : PlainTime := + ofNanoseconds secs.toNanoseconds + +/-- +Creates a `PlainTime` value from a total number of minutes. +-/ +@[inline] +def ofMinutes (secs : Minute.Offset) : PlainTime := + ofNanoseconds secs.toNanoseconds + +/-- +Creates a `PlainTime` value from a total number of hours. +-/ +@[inline] +def ofHours (hour : Hour.Offset) : PlainTime := + ofNanoseconds hour.toNanoseconds + +/-- +Adds seconds to a `PlainTime`. +-/ +@[inline] +def addSeconds (time : PlainTime) (secondsToAdd : Second.Offset) : PlainTime := + let totalSeconds := time.toNanoseconds + secondsToAdd.toNanoseconds + ofNanoseconds totalSeconds + +/-- +Subtracts seconds from a `PlainTime`. +-/ +@[inline] +def subSeconds (time : PlainTime) (secondsToSub : Second.Offset) : PlainTime := + addSeconds time (-secondsToSub) + +/-- +Adds minutes to a `PlainTime`. +-/ +@[inline] +def addMinutes (time : PlainTime) (minutesToAdd : Minute.Offset) : PlainTime := + let total := time.toNanoseconds + minutesToAdd.toNanoseconds + ofNanoseconds total + +/-- +Subtracts minutes from a `PlainTime`. +-/ +@[inline] +def subMinutes (time : PlainTime) (minutesToSub : Minute.Offset) : PlainTime := + addMinutes time (-minutesToSub) + +/-- +Adds hours to a `PlainTime`. +-/ +@[inline] +def addHours (time : PlainTime) (hoursToAdd : Hour.Offset) : PlainTime := + let total := time.toNanoseconds + hoursToAdd.toNanoseconds + ofNanoseconds total + +/-- +Subtracts hours from a `PlainTime`. +-/ +@[inline] +def subHours (time : PlainTime) (hoursToSub : Hour.Offset) : PlainTime := + addHours time (-hoursToSub) + +/-- +Adds nanoseconds to a `PlainTime`. +-/ +def addNanoseconds (time : PlainTime) (nanosToAdd : Nanosecond.Offset) : PlainTime := + let total := time.toNanoseconds + nanosToAdd + ofNanoseconds total + +/-- +Subtracts nanoseconds from a `PlainTime`. +-/ +def subNanoseconds (time : PlainTime) (nanosToSub : Nanosecond.Offset) : PlainTime := + addNanoseconds time (-nanosToSub) + +/-- +Adds milliseconds to a `PlainTime`. +-/ +def addMilliseconds (time : PlainTime) (millisToAdd : Millisecond.Offset) : PlainTime := + let total := time.toMilliseconds + millisToAdd + ofMilliseconds total + +/-- +Subtracts milliseconds from a `PlainTime`. +-/ +def subMilliseconds (time : PlainTime) (millisToSub : Millisecond.Offset) : PlainTime := + addMilliseconds time (-millisToSub) + +/-- +Creates a new `PlainTime` by adjusting the `second` component to the given value. +-/ +@[inline] +def withSeconds (pt : PlainTime) (second : Sigma Second.Ordinal) : PlainTime := + { pt with second := second } + +/-- +Creates a new `PlainTime` by adjusting the `minute` component to the given value. +-/ +@[inline] +def withMinutes (pt : PlainTime) (minute : Minute.Ordinal) : PlainTime := + { pt with minute := minute } + +/-- +Creates a new `PlainTime` by adjusting the milliseconds component inside the `nano` component of its `time` to the given value. +-/ +@[inline] +def withMilliseconds (pt : PlainTime) (millis : Millisecond.Ordinal) : PlainTime := + let minorPart := pt.nanosecond.emod 1000 (by decide) + let majorPart := millis.mul_pos 1000000 (by decide) |>.addBounds minorPart + { pt with nanosecond := majorPart |>.expandTop (by decide) } + +/-- +Creates a new `PlainTime` by adjusting the `nano` component to the given value. +-/ +@[inline] +def withNanoseconds (pt : PlainTime) (nano : Nanosecond.Ordinal) : PlainTime := + { pt with nanosecond := nano } + +/-- +Creates a new `PlainTime` by adjusting the `hour` component to the given value. +-/ +@[inline] +def withHours (pt : PlainTime) (hour : Hour.Ordinal) : PlainTime := + { pt with hour := hour } + +/-- +`Millisecond` component of the `PlainTime` +-/ +@[inline] +def millisecond (pt : PlainTime) : Millisecond.Ordinal := + pt.nanosecond.ediv 1000000 (by decide) + +instance : HAdd PlainTime Nanosecond.Offset PlainTime where + hAdd := addNanoseconds + +instance : HSub PlainTime Nanosecond.Offset PlainTime where + hSub := subNanoseconds + +instance : HAdd PlainTime Millisecond.Offset PlainTime where + hAdd := addMilliseconds + +instance : HSub PlainTime Millisecond.Offset PlainTime where + hSub := subMilliseconds + +instance : HAdd PlainTime Second.Offset PlainTime where + hAdd := addSeconds + +instance : HSub PlainTime Second.Offset PlainTime where + hSub := subSeconds + +instance : HAdd PlainTime Minute.Offset PlainTime where + hAdd := addMinutes + +instance : HSub PlainTime Minute.Offset PlainTime where + hSub := subMinutes + +instance : HAdd PlainTime Hour.Offset PlainTime where + hAdd := addHours + +instance : HSub PlainTime Hour.Offset PlainTime where + hSub := subHours + +end PlainTime +end Time +end Std diff --git a/src/Std/Time/Time/Unit/Basic.lean b/src/Std/Time/Time/Unit/Basic.lean new file mode 100644 index 000000000000..ab5f9d282d0c --- /dev/null +++ b/src/Std/Time/Time/Unit/Basic.lean @@ -0,0 +1,329 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Internal +import Std.Time.Time.Unit.Hour +import Std.Time.Time.Unit.Minute +import Std.Time.Time.Unit.Second +import Std.Time.Time.Unit.Nanosecond +import Std.Time.Time.Unit.Millisecond + +/-! +This module defines various units used for measuring, counting, and converting between hours, minutes, +second, nanosecond, millisecond and nanoseconds. + +The units are organized into types representing these time-related concepts, with operations provided +to facilitate conversions and manipulations between them. +-/ + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +namespace Nanosecond.Offset + +/-- +Converts a `Nanosecond.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def toMilliseconds (offset : Nanosecond.Offset) : Millisecond.Offset := + offset.div 1000000 + +/-- +Converts a `Millisecond.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def ofMilliseconds (offset : Millisecond.Offset) : Nanosecond.Offset := + offset.mul 1000000 + +/-- +Converts a `Nanosecond.Offset` to a `Second.Offset`. +-/ +@[inline] +def toSeconds (offset : Nanosecond.Offset) : Second.Offset := + offset.div 1000000000 + +/-- +Converts a `Second.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def ofSeconds (offset : Second.Offset) : Nanosecond.Offset := + offset.mul 1000000000 + +/-- +Converts a `Nanosecond.Offset` to a `Minute.Offset`. +-/ +@[inline] +def toMinutes (offset : Nanosecond.Offset) : Minute.Offset := + offset.div 60000000000 + +/-- +Converts a `Minute.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def ofMinutes (offset : Minute.Offset) : Nanosecond.Offset := + offset.mul 60000000000 + +/-- +Converts a `Nanosecond.Offset` to an `Hour.Offset`. +-/ +@[inline] +def toHours (offset : Nanosecond.Offset) : Hour.Offset := + offset.div 3600000000000 + +/-- +Converts an `Hour.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def ofHours (offset : Hour.Offset) : Nanosecond.Offset := + offset.mul 3600000000000 + +end Nanosecond.Offset + +namespace Millisecond.Offset + +/-- +Converts a `Millisecond.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def toNanoseconds (offset : Millisecond.Offset) : Nanosecond.Offset := + offset.mul 1000000 + +/-- +Converts a `Nanosecond.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def ofNanoseconds (offset : Nanosecond.Offset) : Millisecond.Offset := + offset.div 1000000 + +/-- +Converts a `Millisecond.Offset` to a `Second.Offset`. +-/ +@[inline] +def toSeconds (offset : Millisecond.Offset) : Second.Offset := + offset.div 1000 + +/-- +Converts a `Second.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def ofSeconds (offset : Second.Offset) : Millisecond.Offset := + offset.mul 1000 + +/-- +Converts a `Millisecond.Offset` to a `Minute.Offset`. +-/ +@[inline] +def toMinutes (offset : Millisecond.Offset) : Minute.Offset := + offset.div 60000 + +/-- +Converts a `Minute.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def ofMinutes (offset : Minute.Offset) : Millisecond.Offset := + offset.mul 60000 + +/-- +Converts a `Millisecond.Offset` to an `Hour.Offset`. +-/ +@[inline] +def toHours (offset : Millisecond.Offset) : Hour.Offset := + offset.div 3600000 + +/-- +Converts an `Hour.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def ofHours (offset : Hour.Offset) : Millisecond.Offset := + offset.mul 3600000 + +end Millisecond.Offset + +namespace Second.Offset + +/-- +Converts a `Second.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def toNanoseconds (offset : Second.Offset) : Nanosecond.Offset := + offset.mul 1000000000 + +/-- +Converts a `Nanosecond.Offset` to a `Second.Offset`. +-/ +@[inline] +def ofNanoseconds (offset : Nanosecond.Offset) : Second.Offset := + offset.div 1000000000 + +/-- +Converts a `Second.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def toMilliseconds (offset : Second.Offset) : Millisecond.Offset := + offset.mul 1000 + +/-- +Converts a `Millisecond.Offset` to a `Second.Offset`. +-/ +@[inline] +def ofMilliseconds (offset : Millisecond.Offset) : Second.Offset := + offset.div 1000 + +/-- +Converts a `Second.Offset` to a `Minute.Offset`. +-/ +@[inline] +def toMinutes (offset : Second.Offset) : Minute.Offset := + offset.div 60 + +/-- +Converts a `Minute.Offset` to a `Second.Offset`. +-/ +@[inline] +def ofMinutes (offset : Minute.Offset) : Second.Offset := + offset.mul 60 + +/-- +Converts a `Second.Offset` to an `Hour.Offset`. +-/ +@[inline] +def toHours (offset : Second.Offset) : Hour.Offset := + offset.div 3600 + +/-- +Converts an `Hour.Offset` to a `Second.Offset`. +-/ +@[inline] +def ofHours (offset : Hour.Offset) : Second.Offset := + offset.mul 3600 + +end Second.Offset + +namespace Minute.Offset + +/-- +Converts a `Minute.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def toNanoseconds (offset : Minute.Offset) : Nanosecond.Offset := + offset.mul 60000000000 + +/-- +Converts a `Nanosecond.Offset` to a `Minute.Offset`. +-/ +@[inline] +def ofNanoseconds (offset : Nanosecond.Offset) : Minute.Offset := + offset.div 60000000000 + +/-- +Converts a `Minute.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def toMilliseconds (offset : Minute.Offset) : Millisecond.Offset := + offset.mul 60000 + +/-- +Converts a `Millisecond.Offset` to a `Minute.Offset`. +-/ +@[inline] +def ofMilliseconds (offset : Millisecond.Offset) : Minute.Offset := + offset.div 60000 + +/-- +Converts a `Minute.Offset` to a `Second.Offset`. +-/ +@[inline] +def toSeconds (offset : Minute.Offset) : Second.Offset := + offset.mul 60 + +/-- +Converts a `Second.Offset` to a `Minute.Offset`. +-/ +@[inline] +def ofSeconds (offset : Second.Offset) : Minute.Offset := + offset.div 60 + +/-- +Converts a `Minute.Offset` to an `Hour.Offset`. +-/ +@[inline] +def toHours (offset : Minute.Offset) : Hour.Offset := + offset.div 60 + +/-- +Converts an `Hour.Offset` to a `Minute.Offset`. +-/ +@[inline] +def ofHours (offset : Hour.Offset) : Minute.Offset := + offset.mul 60 + +end Minute.Offset + +namespace Hour.Offset + +/-- +Converts an `Hour.Offset` to a `Nanosecond.Offset`. +-/ +@[inline] +def toNanoseconds (offset : Hour.Offset) : Nanosecond.Offset := + offset.mul 3600000000000 + +/-- +Converts a `Nanosecond.Offset` to an `Hour.Offset`. +-/ +@[inline] +def ofNanoseconds (offset : Nanosecond.Offset) : Hour.Offset := + offset.div 3600000000000 + +/-- +Converts an `Hour.Offset` to a `Millisecond.Offset`. +-/ +@[inline] +def toMilliseconds (offset : Hour.Offset) : Millisecond.Offset := + offset.mul 3600000 + +/-- +Converts a `Millisecond.Offset` to an `Hour.Offset`. +-/ +@[inline] +def ofMilliseconds (offset : Millisecond.Offset) : Hour.Offset := + offset.div 3600000 + +/-- +Converts an `Hour.Offset` to a `Second.Offset`. +-/ +@[inline] +def toSeconds (offset : Hour.Offset) : Second.Offset := + offset.mul 3600 + +/-- +Converts a `Second.Offset` to an `Hour.Offset`. +-/ +@[inline] +def ofSeconds (offset : Second.Offset) : Hour.Offset := + offset.div 3600 + +/-- +Converts an `Hour.Offset` to a `Minute.Offset`. +-/ +@[inline] +def toMinutes (offset : Hour.Offset) : Minute.Offset := + offset.mul 60 + +/-- +Converts a `Minute.Offset` to an `Hour.Offset`. +-/ +@[inline] +def ofMinutes (offset : Minute.Offset) : Hour.Offset := + offset.div 60 + +end Hour.Offset + +end Time +end Std diff --git a/src/Std/Time/Time/Unit/Hour.lean b/src/Std/Time/Time/Unit/Hour.lean new file mode 100644 index 000000000000..7eceb2c7648b --- /dev/null +++ b/src/Std/Time/Time/Unit/Hour.lean @@ -0,0 +1,111 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Internal +import Std.Time.Time.Unit.Minute +import Std.Time.Time.Unit.Second + +namespace Std +namespace Time +namespace Hour +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for hours, ranging from 0 to 23. +-/ +def Ordinal := Bounded.LE 0 23 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 0 (0 + (23 : Nat))) n) + +instance : Inhabited Ordinal where + default := 0 + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +/-- +`Offset` represents an offset in hours, defined as an `Int`. This can be used to express durations +or differences in hours. +-/ +def Offset : Type := UnitVal 3600 + deriving Repr, BEq, Inhabited, Add, Sub, Neg, ToString + +instance : OfNat Offset n := + ⟨UnitVal.ofNat n⟩ + +namespace Ordinal + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 0 ≤ data ∧ data ≤ 23) : Ordinal := + Bounded.LE.mk data h + +/-- +Converts an `Ordinal` into a relative hour in the range of 1 to 12. +-/ +def toRelative (ordinal : Ordinal) : Bounded.LE 1 12 := + (ordinal.add 11).emod 12 (by decide) |>.add 1 + +/-- +Converts an Ordinal into a 1-based hour representation within the range of 1 to 24. +-/ +def shiftTo1BasedHour (ordinal : Ordinal) : Bounded.LE 1 24 := + if h : ordinal.val < 1 + then Internal.Bounded.LE.ofNatWrapping 24 (by decide) + else ordinal.truncateBottom (Int.not_lt.mp h) |>.expandTop (by decide) +/-- +Creates an `Ordinal` from a natural number, ensuring the value is within the valid bounds for hours. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≤ 23) : Ordinal := + Bounded.LE.ofNat data h + +/-- +Creates an `Ordinal` from a `Fin` value. +-/ +@[inline] +def ofFin (data : Fin 24) : Ordinal := + Bounded.LE.ofFin data + +/-- +Converts an `Ordinal` to an `Offset`, which represents the duration in hours as an integer value. +-/ +@[inline] +def toOffset (ordinal : Ordinal) : Offset := + UnitVal.ofInt ordinal.val + +end Ordinal +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Offset := + UnitVal.ofInt data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Offset := + UnitVal.ofInt data + +end Offset +end Hour +end Time +end Std diff --git a/src/Std/Time/Time/Unit/Millisecond.lean b/src/Std/Time/Time/Unit/Millisecond.lean new file mode 100644 index 000000000000..754ddf22c477 --- /dev/null +++ b/src/Std/Time/Time/Unit/Millisecond.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Internal +import Std.Time.Time.Unit.Nanosecond + +namespace Std +namespace Time +namespace Millisecond +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for milliseconds, ranging from 0 to 999 milliseconds. +-/ +def Ordinal := Bounded.LE 0 999 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 0 (0 + (999 : Nat))) n) + +instance : Inhabited Ordinal where + default := 0 + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +/-- +`Offset` represents a duration offset in milliseconds. +-/ +def Offset : Type := UnitVal (1 / 1000) + deriving Repr, BEq, Inhabited, Add, Sub, Neg, LE, LT, ToString + +instance : OfNat Offset n := + ⟨UnitVal.ofNat n⟩ + +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Offset := + UnitVal.ofInt data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Offset := + UnitVal.ofInt data + +end Offset +namespace Ordinal + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 0 ≤ data ∧ data ≤ 999) : Ordinal := + Bounded.LE.mk data h + +/-- +Creates an `Ordinal` from a natural number, ensuring the value is within bounds. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≤ 999) : Ordinal := + Bounded.LE.ofNat data h + +/-- +Creates an `Ordinal` from a `Fin`, ensuring the value is within bounds. +-/ +@[inline] +def ofFin (data : Fin 1000) : Ordinal := + Bounded.LE.ofFin data + +/-- +Converts an `Ordinal` to an `Offset`. +-/ +@[inline] +def toOffset (ordinal : Ordinal) : Offset := + UnitVal.ofInt ordinal.val + +end Ordinal +end Millisecond +end Time +end Std diff --git a/src/Std/Time/Time/Unit/Minute.lean b/src/Std/Time/Time/Unit/Minute.lean new file mode 100644 index 000000000000..0accf7f45dab --- /dev/null +++ b/src/Std/Time/Time/Unit/Minute.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Internal +import Std.Time.Time.Unit.Second + +namespace Std +namespace Time +namespace Minute +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for minutes, ranging from 0 to 59. This is useful for representing the minute component of a time. +-/ +def Ordinal := Bounded.LE 0 59 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n := + inferInstanceAs (OfNat (Bounded.LE 0 (0 + (59 : Nat))) n) + +instance : Inhabited Ordinal where + default := 0 + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +/-- +`Offset` represents a duration offset in minutes. +-/ +def Offset : Type := UnitVal 60 + deriving Repr, BEq, Inhabited, Add, Sub, Neg, ToString + +instance : OfNat Offset n := + ⟨UnitVal.ofInt <| Int.ofNat n⟩ + +namespace Ordinal + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 0 ≤ data ∧ data ≤ 59) : Ordinal := + Bounded.LE.mk data h + +/-- +Creates an `Ordinal` from a natural number, ensuring the value is within bounds. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≤ 59) : Ordinal := + Bounded.LE.ofNat data h + +/-- +Creates an `Ordinal` from a `Fin`, ensuring the value is within bounds. +-/ +@[inline] +def ofFin (data : Fin 60) : Ordinal := + Bounded.LE.ofFin data + +/-- +Converts an `Ordinal` to an `Offset`. +-/ +@[inline] +def toOffset (ordinal : Ordinal) : Offset := + UnitVal.ofInt ordinal.val + +end Ordinal +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Offset := + UnitVal.ofInt data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Offset := + UnitVal.ofInt data + +end Offset +end Minute +end Time +end Std diff --git a/src/Std/Time/Time/Unit/Nanosecond.lean b/src/Std/Time/Time/Unit/Nanosecond.lean new file mode 100644 index 000000000000..1f092cd4bd80 --- /dev/null +++ b/src/Std/Time/Time/Unit/Nanosecond.lean @@ -0,0 +1,124 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Internal + +namespace Std +namespace Time +namespace Nanosecond +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a nanosecond value that is bounded between 0 and 999,999,999 nanoseconds. +-/ +def Ordinal := Bounded.LE 0 999999999 + deriving Repr, BEq, LE, LT + +instance : OfNat Ordinal n where + ofNat := Bounded.LE.ofFin (Fin.ofNat n) + +instance : Inhabited Ordinal where + default := 0 + +instance {x y : Ordinal} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +/-- +`Offset` represents a time offset in nanoseconds. +-/ +def Offset : Type := UnitVal (1 / 1000000000) + deriving Repr, BEq, Inhabited, Add, Sub, Neg, LE, LT, ToString + +instance { x y : Offset } : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance : OfNat Offset n := + ⟨UnitVal.ofNat n⟩ + +namespace Offset + +/-- +Creates an `Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Offset := + UnitVal.ofInt data + +/-- +Creates an `Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Offset := + UnitVal.ofInt data + +end Offset + +/-- +`Span` represents a bounded value for nanoseconds, ranging between -999999999 and 999999999. +This can be used for operations that involve differences or adjustments within this range. +-/ +def Span := Bounded.LE (-999999999) 999999999 + deriving Repr, BEq, LE, LT + +instance : Inhabited Span where default := Bounded.LE.mk 0 (by decide) + +namespace Span + +/-- +Creates a new `Offset` out of a `Span`. +-/ +def toOffset (span : Span) : Offset := + UnitVal.ofInt span.val + +end Span + +namespace Ordinal + +/-- +`Ordinal` represents a bounded value for nanoseconds in a day, which ranges between 0 and 86400000000000. +-/ +def OfDay := Bounded.LE 0 86400000000000 + deriving Repr, BEq, LE, LT + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 0 ≤ data ∧ data ≤ 999999999) : Ordinal := + Bounded.LE.mk data h + +/-- +Creates an `Ordinal` from a natural number, ensuring the value is within bounds. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≤ 999999999) : Ordinal := + Bounded.LE.ofNat data h + +/-- +Creates an `Ordinal` from a `Fin`, ensuring the value is within bounds. +-/ +@[inline] +def ofFin (data : Fin 1000000000) : Ordinal := + Bounded.LE.ofFin data + +/-- +Converts an `Ordinal` to an `Offset`. +-/ +@[inline] +def toOffset (ordinal : Ordinal) : Offset := + UnitVal.ofInt ordinal.val + +end Ordinal +end Nanosecond +end Time +end Std diff --git a/src/Std/Time/Time/Unit/Second.lean b/src/Std/Time/Time/Unit/Second.lean new file mode 100644 index 000000000000..39c409f56056 --- /dev/null +++ b/src/Std/Time/Time/Unit/Second.lean @@ -0,0 +1,110 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Internal.Rat +import Std.Time.Time.Unit.Nanosecond + +namespace Std +namespace Time +namespace Second +open Std.Internal +open Internal + +set_option linter.all true + +/-- +`Ordinal` represents a bounded value for second, which ranges between 0 and 59 or 60. This accounts +for potential leap second. +-/ +def Ordinal (leap : Bool) := Bounded.LE 0 (.ofNat (if leap then 60 else 59)) + +instance : BEq (Ordinal leap) where + beq x y := BEq.beq x.val y.val + +instance : LE (Ordinal leap) where + le x y := LE.le x.val y.val + +instance : LT (Ordinal leap) where + lt x y := LT.lt x.val y.val + +instance : Repr (Ordinal l) where + reprPrec r := reprPrec r.val + +instance : OfNat (Ordinal leap) n := by + have inst := inferInstanceAs (OfNat (Bounded.LE 0 (0 + (59 : Nat))) n) + cases leap + · exact inst + · exact ⟨inst.ofNat.expandTop (by decide)⟩ + +instance {x y : Ordinal l} : Decidable (x ≤ y) := + inferInstanceAs (Decidable (x.val ≤ y.val)) + +instance {x y : Ordinal l} : Decidable (x < y) := + inferInstanceAs (Decidable (x.val < y.val)) + +/-- +`Offset` represents an offset in seconds. It is defined as an `Int`. +-/ +def Offset : Type := UnitVal 1 + deriving Repr, BEq, Inhabited, Add, Sub, Neg, LE, LT, ToString + +instance : OfNat Offset n := + ⟨UnitVal.ofNat n⟩ + +namespace Offset + +/-- +Creates an `Second.Offset` from a natural number. +-/ +@[inline] +def ofNat (data : Nat) : Second.Offset := + UnitVal.ofInt data + +/-- +Creates an `Second.Offset` from an integer. +-/ +@[inline] +def ofInt (data : Int) : Second.Offset := + UnitVal.ofInt data + +end Offset + +namespace Ordinal + +/-- +Creates an `Ordinal` from an integer, ensuring the value is within bounds. +-/ +@[inline] +def ofInt (data : Int) (h : 0 ≤ data ∧ data ≤ Int.ofNat (if leap then 60 else 59)) : Ordinal leap := + Bounded.LE.mk data h + +/-- +Creates an `Ordinal` from a natural number, ensuring the value is within bounds. +-/ +@[inline] +def ofNat (data : Nat) (h : data ≤ (if leap then 60 else 59)) : Ordinal leap := + Bounded.LE.ofNat data h + +/-- +Creates an `Ordinal` from a `Fin`, ensuring the value is within bounds. +-/ +@[inline] +def ofFin (data : Fin (if leap then 61 else 60)) : Ordinal leap := + match leap with + | true => Bounded.LE.ofFin data + | false => Bounded.LE.ofFin data + +/-- +Converts an `Ordinal` to an `Second.Offset`. +-/ +@[inline] +def toOffset (ordinal : Ordinal leap) : Second.Offset := + UnitVal.ofInt ordinal.val + +end Ordinal +end Second +end Time +end Std diff --git a/src/Std/Time/Zoned.lean b/src/Std/Time/Zoned.lean new file mode 100644 index 000000000000..570007ce5ecb --- /dev/null +++ b/src/Std/Time/Zoned.lean @@ -0,0 +1,180 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Zoned.DateTime +import Std.Time.Zoned.ZoneRules +import Std.Time.Zoned.ZonedDateTime +import Std.Time.Zoned.Database + +namespace Std +namespace Time + +set_option linter.all true + +namespace PlainDateTime + +/-- +Get the current time. +-/ +@[inline] +def now : IO PlainDateTime := do + let tm ← Timestamp.now + let rules ← Database.defaultGetLocalZoneRules + let ltt := rules.findLocalTimeTypeForTimestamp tm + + return PlainDateTime.ofTimestampAssumingUTC tm |>.addSeconds ltt.getTimeZone.toSeconds + +end PlainDateTime + +namespace PlainDate + +/-- +Get the current date. +-/ +@[inline] +def now : IO PlainDate := + PlainDateTime.date <$> PlainDateTime.now + +end PlainDate +namespace PlainTime + +/-- +Get the current time. +-/ +@[inline] +def now : IO PlainTime := + PlainDateTime.time <$> PlainDateTime.now + +end PlainTime + +namespace DateTime + +/-- +Converts a `PlainDate` with a `TimeZone` to a `DateTime` +-/ +@[inline] +def ofPlainDate (pd : PlainDate) (tz : TimeZone) : DateTime tz := + DateTime.ofTimestamp (Timestamp.ofPlainDateAssumingUTC pd) tz + +/-- +Converts a `DateTime` to a `PlainDate` +-/ +@[inline] +def toPlainDate (dt : DateTime tz) : PlainDate := + Timestamp.toPlainDateAssumingUTC dt.toTimestamp + +/-- +Converts a `DateTime` to a `PlainTime` +-/ +@[inline] +def toPlainTime (dt : DateTime tz) : PlainTime := + dt.date.get.time + +end DateTime +namespace DateTime + +/-- +Gets the current `ZonedDateTime`. +-/ +@[inline] +def now : IO (DateTime tz) := do + let tm ← Timestamp.now + return DateTime.ofTimestamp tm tz + +end DateTime +namespace ZonedDateTime + +/-- +Gets the current `ZonedDateTime`. +-/ +@[inline] +def now : IO ZonedDateTime := do + let tm ← Timestamp.now + let rules ← Database.defaultGetLocalZoneRules + return ZonedDateTime.ofTimestamp tm rules + +/-- +Gets the current `ZonedDateTime` using the identifier of a time zone. +-/ +@[inline] +def nowAt (id : String) : IO ZonedDateTime := do + let tm ← Timestamp.now + let rules ← Database.defaultGetZoneRules id + return ZonedDateTime.ofTimestamp tm rules + +/-- +Converts a `PlainDate` to a `ZonedDateTime`. +-/ +@[inline] +def ofPlainDate (pd : PlainDate) (zr : TimeZone.ZoneRules) : ZonedDateTime := + ZonedDateTime.ofPlainDateTime (pd.atTime PlainTime.midnight) zr + +/-- +Converts a `PlainDate` to a `ZonedDateTime` using `TimeZone`. +-/ +@[inline] +def ofPlainDateWithZone (pd : PlainDate) (zr : TimeZone) : ZonedDateTime := + ZonedDateTime.ofPlainDateTime (pd.atTime PlainTime.midnight) (TimeZone.ZoneRules.ofTimeZone zr) + +/-- +Converts a `ZonedDateTime` to a `PlainDate` +-/ +@[inline] +def toPlainDate (dt : ZonedDateTime) : PlainDate := + dt.toPlainDateTime.date + +/-- +Converts a `ZonedDateTime` to a `PlainTime` +-/ +@[inline] +def toPlainTime (dt : ZonedDateTime) : PlainTime := + dt.toPlainDateTime.time + +/-- +Creates a new `ZonedDateTime` out of a `PlainDateTime` and a time zone identifier. +-/ +@[inline] +def of (pdt : PlainDateTime) (id : String) : IO ZonedDateTime := do + let zr ← Database.defaultGetZoneRules id + return ZonedDateTime.ofPlainDateTime pdt zr + +end ZonedDateTime + +namespace PlainDateTime + +/-- +Converts a `PlainDateTime` to a `Timestamp` using the `ZoneRules`. +-/ +@[inline] +def toTimestamp (pdt : PlainDateTime) (zr : TimeZone.ZoneRules) : Timestamp := + ZonedDateTime.ofPlainDateTime pdt zr |>.toTimestamp + +/-- +Converts a `PlainDateTime` to a `Timestamp` using the `TimeZone`. +-/ +@[inline] +def toTimestampWithZone (pdt : PlainDateTime) (tz : TimeZone) : Timestamp := + ZonedDateTime.ofPlainDateTimeWithZone pdt tz |>.toTimestamp + +end PlainDateTime + +namespace PlainDate + +/-- +Converts a `PlainDate` to a `Timestamp` using the `ZoneRules`. +-/ +@[inline] +def toTimestamp (dt : PlainDate) (zr : TimeZone.ZoneRules) : Timestamp := + ZonedDateTime.ofPlainDate dt zr |>.toTimestamp + +/-- +Converts a `PlainDate` to a `Timestamp` using the `TimeZone`. +-/ +@[inline] +def toTimestampWithZone (dt : PlainDate) (tz : TimeZone) : Timestamp := + ZonedDateTime.ofPlainDateWithZone dt tz |>.toTimestamp + +end PlainDate diff --git a/src/Std/Time/Zoned/Database.lean b/src/Std/Time/Zoned/Database.lean new file mode 100644 index 000000000000..66e206a998db --- /dev/null +++ b/src/Std/Time/Zoned/Database.lean @@ -0,0 +1,38 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Zoned.ZonedDateTime +import Std.Time.Zoned.Database.Basic +import Std.Time.Zoned.Database.TZdb +import Std.Time.Zoned.Database.Windows +import Init.System.Platform + +namespace Std +namespace Time +namespace Database +open TimeZone.ZoneRules + +set_option linter.all true + +/-- +Gets the zone rules for a specific time zone identifier, handling Windows and non-Windows platforms. +In windows it uses the current `icu.h` in Windows SDK. If it's linux or macos then it will use the `tzdata` +files. +-/ +def defaultGetZoneRules : String → IO TimeZone.ZoneRules := + if System.Platform.isWindows + then getZoneRules WindowsDb.default + else getZoneRules TZdb.default + +/-- +Gets the local zone rules, accounting for platform differences. +In windows it uses the current `icu.h` in Windows SDK. If it's linux or macos then it will use the `tzdata` +files. +-/ +def defaultGetLocalZoneRules : IO TimeZone.ZoneRules := + if System.Platform.isWindows + then getLocalZoneRules WindowsDb.default + else getLocalZoneRules TZdb.default diff --git a/src/Std/Time/Zoned/Database/Basic.lean b/src/Std/Time/Zoned/Database/Basic.lean new file mode 100644 index 000000000000..7b046d2ce75c --- /dev/null +++ b/src/Std/Time/Zoned/Database/Basic.lean @@ -0,0 +1,114 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Zoned.ZoneRules +import Std.Time.Zoned.Database.TzIf + +namespace Std +namespace Time + +set_option linter.all true + +/-- +A timezone database from which we can read the `ZoneRules` of some area by it's id. +-/ +protected class Database (α : Type) where + + /-- + Retrieves the zone rules information (`ZoneRules`) for a given area at a specific point in time. + -/ + getZoneRules : α → String → IO TimeZone.ZoneRules + + /-- + Retrieves the local zone rules information (`ZoneRules`) at a given timestamp. + -/ + getLocalZoneRules : α → IO TimeZone.ZoneRules + +namespace TimeZone + +/-- +Converts a Boolean value to a corresponding `StdWall` type. +-/ +def convertWall : Bool → StdWall + | true => .standard + | false => .wall + +/-- +Converts a Boolean value to a corresponding `UTLocal` type. +-/ +def convertUt : Bool → UTLocal + | true => .ut + | false => .local + +/-- +Converts a given time index into a `LocalTimeType` by using a time zone (`tz`) and its identifier. +-/ +def convertLocalTimeType (index : Nat) (tz : TZif.TZifV1) (identifier : String) : Option LocalTimeType := do + let localType ← tz.localTimeTypes.get? index + let offset := Offset.ofSeconds <| .ofInt localType.gmtOffset + let abbreviation ← tz.abbreviations.getD index (offset.toIsoString true) + let wallflag := convertWall (tz.stdWallIndicators.getD index true) + let utLocal := convertUt (tz.utLocalIndicators.getD index true) + + return { + gmtOffset := offset + isDst := localType.isDst + abbreviation + wall := wallflag + utLocal + identifier + } + +/-- +Converts a transition. +-/ +def convertTransition (times: Array LocalTimeType) (index : Nat) (tz : TZif.TZifV1) : Option Transition := do + let time := tz.transitionTimes.get! index + let time := Second.Offset.ofInt time + let indice := tz.transitionIndices.get! index + return { time, localTimeType := times.get! indice.toNat } + +/-- +Converts a `TZif.TZifV1` structure to a `ZoneRules` structure. +-/ +def convertTZifV1 (tz : TZif.TZifV1) (id : String) : Except String ZoneRules := do + let mut times : Array LocalTimeType := #[] + + for i in [0:tz.header.typecnt.toNat] do + if let some result := convertLocalTimeType i tz id + then times := times.push result + else .error s!"cannot convert local time {i} of the file" + + let mut transitions := #[] + + for i in [0:tz.transitionTimes.size] do + if let some result := convertTransition times i tz + then transitions := transitions.push result + else .error s!"cannot convert transition {i} of the file" + + -- Local time for timestamps before the first transition is specified by the first time + -- type (time type 0). + + let initialLocalTimeType ← + if let some res := convertLocalTimeType 0 tz id + then .ok res + else .error s!"empty transitions for {id}" + + .ok { transitions, initialLocalTimeType } + +/-- +Converts a `TZif.TZifV2` structure to a `ZoneRules` structure. +-/ +def convertTZifV2 (tz : TZif.TZifV2) (id : String) : Except String ZoneRules := do + convertTZifV1 tz.toTZifV1 id + +/-- +Converts a `TZif.TZif` structure to a `ZoneRules` structure. +-/ +def convertTZif (tz : TZif.TZif) (id : String) : Except String ZoneRules := do + if let some v2 := tz.v2 + then convertTZifV2 v2 id + else convertTZifV1 tz.v1 id diff --git a/src/Std/Time/Zoned/Database/TZdb.lean b/src/Std/Time/Zoned/Database/TZdb.lean new file mode 100644 index 000000000000..40fe0f7fd13e --- /dev/null +++ b/src/Std/Time/Zoned/Database/TZdb.lean @@ -0,0 +1,92 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.DateTime +import Std.Time.Zoned.TimeZone +import Std.Time.Zoned.ZoneRules +import Std.Time.Zoned.Database.Basic + +namespace Std +namespace Time +namespace Database + +set_option linter.all true + +/-- +Represents a Time Zone Database (TZdb) configuration with paths to local and general timezone data. +-/ +structure TZdb where + + /-- + The path to the local timezone file. This is typically a symlink to a file within the timezone + database that corresponds to the current local time zone. + -/ + localPath : System.FilePath := "/etc/localtime" + + /-- + The path to the directory containing all available time zone files. These files define various + time zones and their rules. + -/ + zonesPath : System.FilePath := "/usr/share/zoneinfo/" + +namespace TZdb +open TimeZone + +/-- +Returns a default `TZdb` instance with common timezone data paths for most Linux distributions and macOS. +-/ +@[inline] +def default : TZdb := {} + +/-- +Parses binary timezone data into zone rules based on a given timezone ID. +-/ +def parseTZif (bin : ByteArray) (id : String) : Except String ZoneRules := do + let database ← TZif.parse.run bin + convertTZif database id + +/-- +Reads a TZif file from disk and retrieves the zone rules for the specified timezone ID. +-/ +def parseTZIfFromDisk (path : System.FilePath) (id : String) : IO ZoneRules := do + let binary ← try IO.FS.readBinFile path catch _ => throw <| IO.userError s!"cannot find {id} in the local timezone database" + IO.ofExcept (parseTZif binary id) + +/-- +Extracts a timezone ID from a file path. +-/ +def idFromPath (path : System.FilePath) : Option String := do + let res := path.components.toArray + let last ← res.get? (res.size - 1) + let last₁ ← res.get? (res.size - 2) + + if last₁ = some "zoneinfo" + then last + else last₁ ++ "/" ++ last + +/-- +Retrieves the timezone rules from the local timezone data file. +-/ +def localRules (path : System.FilePath) : IO ZoneRules := do + let localTimePath ← + try + IO.Process.run { cmd := "readlink", args := #["-f", path.toString] } + catch _ => + throw <| IO.userError "cannot find the local timezone database" + + if let some id := idFromPath localTimePath + then parseTZIfFromDisk path id + else throw (IO.userError "cannot read the id of the path.") + +/-- +Reads timezone rules from disk based on the provided file path and timezone ID. +-/ +def readRulesFromDisk (path : System.FilePath) (id : String) : IO ZoneRules := do + parseTZIfFromDisk (System.FilePath.join path id) id + +instance : Std.Time.Database TZdb where + getLocalZoneRules db := localRules db.localPath + getZoneRules db id := readRulesFromDisk db.zonesPath id diff --git a/src/Std/Time/Zoned/Database/TzIf.lean b/src/Std/Time/Zoned/Database/TzIf.lean new file mode 100644 index 000000000000..3822d582f84c --- /dev/null +++ b/src/Std/Time/Zoned/Database/TzIf.lean @@ -0,0 +1,328 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Init.Data.Range +import Std.Internal.Parsec +import Std.Internal.Parsec.ByteArray + +-- Based on: https://www.rfc-editor.org/rfc/rfc8536.html + +namespace Std +namespace Time +namespace TimeZone +namespace TZif +open Std.Internal.Parsec Std.Internal.Parsec.ByteArray + +set_option linter.all true + +private abbrev Int32 := Int + +private abbrev Int64 := Int + +/-- +Represents the header of a TZif file, containing metadata about the file's structure. +-/ +structure Header where + + /-- + The version of the TZif file format. + -/ + version : UInt8 + + /-- + The count of UT local indicators in the file. + -/ + isutcnt : UInt32 + + /-- + The count of standard/wall indicators in the file. + -/ + isstdcnt : UInt32 + + /-- + The number of leap second records. + -/ + leapcnt : UInt32 + + /-- + The number of transition times in the file. + -/ + timecnt : UInt32 + + /-- + The number of local time types in the file. + -/ + typecnt : UInt32 + + /-- + The total number of characters used in abbreviations. + -/ + charcnt : UInt32 + deriving Repr, Inhabited + +/-- +Represents the local time type information, including offset and daylight saving details. +-/ +structure LocalTimeType where + + /-- + The GMT offset in seconds for this local time type. + -/ + gmtOffset : Int32 + + /-- + Indicates if this local time type observes daylight saving time. + -/ + isDst : Bool + + /-- + The index into the abbreviation string table for this time type. + -/ + abbreviationIndex : UInt8 + deriving Repr, Inhabited + +/-- +Represents a leap second record, including the transition time and the correction applied. +-/ +structure LeapSecond where + + /-- + The transition time of the leap second event. + -/ + transitionTime : Int64 + + /-- + The correction applied during the leap second event in seconds. + -/ + correction : Int64 + deriving Repr, Inhabited + +/-- +Represents version 1 of the TZif format. +-/ +structure TZifV1 where + + /-- + The header information of the TZif file. + -/ + header : Header + + /-- + The array of transition times in seconds since the epoch. + -/ + transitionTimes : Array Int32 + + /-- + The array of local time type indices corresponding to each transition time. + -/ + transitionIndices : Array UInt8 + + /-- + The array of local time type structures. + -/ + localTimeTypes : Array LocalTimeType + + /-- + The array of abbreviation strings used by local time types. + -/ + abbreviations : Array String + + /-- + The array of leap second records. + -/ + leapSeconds : Array LeapSecond + + /-- + The array indicating whether each transition time uses wall clock time or standard time. + -/ + stdWallIndicators : Array Bool + + /-- + The array indicating whether each transition time uses universal time or local time. + -/ + utLocalIndicators : Array Bool + deriving Repr, Inhabited + +/-- +Represents version 2 of the TZif format, extending TZifV1 with an optional footer. +-/ +structure TZifV2 extends TZifV1 where + + /-- + An optional footer for additional metadata in version 2. + -/ + footer : Option String + deriving Repr, Inhabited + +/-- +Represents a TZif file, which can be either version 1 or version 2. +-/ +structure TZif where + + /-- + The data for version 1 of the TZif file. + -/ + v1 : TZifV1 + + /-- + Optionally, the data for version 2 of the TZif file. + -/ + v2 : Option TZifV2 + deriving Repr, Inhabited + +private def toUInt32 (bs : ByteArray) : UInt32 := + assert! bs.size == 4 + (bs.get! 0).toUInt32 <<< 0x18 ||| + (bs.get! 1).toUInt32 <<< 0x10 ||| + (bs.get! 2).toUInt32 <<< 0x8 ||| + (bs.get! 3).toUInt32 + +private def toInt32 (bs : ByteArray) : Int32 := + let n := toUInt32 bs |>.toNat + if n < (1 <<< 31) + then Int.ofNat n + else Int.negOfNat (UInt32.size - n) + +private def toInt64 (bs : ByteArray) : Int64 := + let n := ByteArray.toUInt64BE! bs |>.toNat + if n < (1 <<< 63) + then Int.ofNat n + else Int.negOfNat (UInt64.size - n) + +private def manyN (n : Nat) (p : Parser α) : Parser (Array α) := do + let mut result := #[] + for _ in [0:n] do + let x ← p + result := result.push x + return result + +private def pu64 : Parser UInt64 := ByteArray.toUInt64LE! <$> take 8 +private def pi64 : Parser Int64 := toInt64 <$> take 8 +private def pu32 : Parser UInt32 := toUInt32 <$> take 4 +private def pi32 : Parser Int32 := toInt32 <$> take 4 +private def pu8 : Parser UInt8 := any +private def pbool : Parser Bool := (· != 0) <$> pu8 + +private def parseHeader : Parser Header := + Header.mk + <$> (pstring "TZif" *> pu8) + <*> (take 15 *> pu32) + <*> pu32 + <*> pu32 + <*> pu32 + <*> pu32 + <*> pu32 + +private def parseLocalTimeType : Parser LocalTimeType := + LocalTimeType.mk + <$> pi32 + <*> pbool + <*> pu8 + +private def parseLeapSecond (p : Parser Int) : Parser LeapSecond := + LeapSecond.mk + <$> p + <*> pi32 + +private def parseTransitionTimes (size : Parser Int32) (n : UInt32) : Parser (Array Int32) := + manyN (n.toNat) size + +private def parseTransitionIndices (n : UInt32) : Parser (Array UInt8) := + manyN (n.toNat) pu8 + +private def parseLocalTimeTypes (n : UInt32) : Parser (Array LocalTimeType) := + manyN (n.toNat) parseLocalTimeType + +private def parseAbbreviations (times : Array LocalTimeType) (n : UInt32) : Parser (Array String) := do + let mut strings := #[] + let mut current := "" + let mut chars ← manyN n.toNat pu8 + + for time in times do + for indx in [time.abbreviationIndex.toNat:n.toNat] do + let char := chars.get! indx + if char = 0 then + strings := strings.push current + current := "" + break + else + current := current.push (Char.ofUInt8 char) + + return strings + +private def parseLeapSeconds (size : Parser Int) (n : UInt32) : Parser (Array LeapSecond) := + manyN (n.toNat) (parseLeapSecond size) + +private def parseIndicators (n : UInt32) : Parser (Array Bool) := + manyN (n.toNat) pbool + +private def parseTZifV1 : Parser TZifV1 := do + let header ← parseHeader + + let transitionTimes ← parseTransitionTimes pi32 header.timecnt + let transitionIndices ← parseTransitionIndices header.timecnt + let localTimeTypes ← parseLocalTimeTypes header.typecnt + let abbreviations ← parseAbbreviations localTimeTypes header.charcnt + let leapSeconds ← parseLeapSeconds pi32 header.leapcnt + let stdWallIndicators ← parseIndicators header.isstdcnt + let utLocalIndicators ← parseIndicators header.isutcnt + + return { + header + transitionTimes + transitionIndices + localTimeTypes + abbreviations + leapSeconds + stdWallIndicators + utLocalIndicators + } + +private def parseFooter : Parser (Option String) := do + let char ← pu8 + + if char = 0x0A then pure () else return none + + let tzString ← many (satisfy (· ≠ 0x0A)) + let mut str := "" + + for byte in tzString do + str := str.push (Char.ofUInt8 byte) + + return str + +private def parseTZifV2 : Parser (Option TZifV2) := optional do + let header ← parseHeader + + let transitionTimes ← parseTransitionTimes pi64 header.timecnt + let transitionIndices ← parseTransitionIndices header.timecnt + let localTimeTypes ← parseLocalTimeTypes header.typecnt + let abbreviations ← parseAbbreviations localTimeTypes header.charcnt + let leapSeconds ← parseLeapSeconds pi64 header.leapcnt + let stdWallIndicators ← parseIndicators header.isstdcnt + let utLocalIndicators ← parseIndicators header.isutcnt + + return { + header + transitionTimes + transitionIndices + localTimeTypes + abbreviations + leapSeconds + stdWallIndicators + utLocalIndicators + footer := ← parseFooter + } + +/-- +Parses a TZif file, which may be in either version 1 or version 2 format. +-/ +def parse : Parser TZif := do + let v1 ← parseTZifV1 + let v2 ← parseTZifV2 + return { v1, v2 } + +end TZif diff --git a/src/Std/Time/Zoned/Database/Windows.lean b/src/Std/Time/Zoned/Database/Windows.lean new file mode 100644 index 000000000000..2a9a0bea23f3 --- /dev/null +++ b/src/Std/Time/Zoned/Database/Windows.lean @@ -0,0 +1,89 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.DateTime +import Std.Time.Zoned.TimeZone +import Std.Time.Zoned.ZoneRules +import Std.Time.Zoned.Database.Basic + +namespace Std +namespace Time +namespace Database + +set_option linter.all true + +namespace Windows + +/-- +Fetches the next timezone transition for a given timezone identifier and timestamp. +-/ +@[extern "lean_windows_get_next_transition"] +opaque getNextTransition : @&String → Int64 → Bool → IO (Option (Int64 × TimeZone)) + +/-- +Fetches the timezone at a timestamp. +-/ +@[extern "lean_get_windows_local_timezone_id_at"] +opaque getLocalTimeZoneIdentifierAt : Int64 → IO String + +/-- +Retrieves the timezone rules, including all transitions, for a given timezone identifier. +-/ +def getZoneRules (id : String) : IO TimeZone.ZoneRules := do + let mut start := -2147483648 + let mut transitions : Array TimeZone.Transition := #[] + + let mut initialLocalTimeType ← + if let some res := ← Windows.getNextTransition id start true + then pure (toLocalTime res.snd) + else throw (IO.userError "cannot find first transition in zone rules") + + while true do + let result ← Windows.getNextTransition id start false + + if let some res := result then + transitions := transitions.push { time := Second.Offset.ofInt start.toInt, localTimeType := toLocalTime res.snd } + + -- Avoid zone rules for more than year 3000 + if res.fst ≤ start ∨ res.fst >= 32503690800 then + break + + start := res.fst + else + break + + return { transitions, initialLocalTimeType } + + where + toLocalTime (res : TimeZone) : TimeZone.LocalTimeType := + { + gmtOffset := res.offset, + abbreviation := res.abbreviation, + identifier := res.name, + isDst := res.isDST, + wall := .wall, + utLocal := .local + } + +end Windows + +/-- +Represents a Time Zone Database that we get from ICU available on Windows SDK. +-/ +structure WindowsDb where + +namespace WindowsDb +open TimeZone + +/-- +Returns a default `WindowsDb` instance. +-/ +@[inline] +def default : WindowsDb := {} + +instance : Std.Time.Database WindowsDb where + getZoneRules _ id := Windows.getZoneRules id + getLocalZoneRules _ := Windows.getZoneRules =<< Windows.getLocalTimeZoneIdentifierAt (-2147483648) diff --git a/src/Std/Time/Zoned/DateTime.lean b/src/Std/Time/Zoned/DateTime.lean new file mode 100644 index 000000000000..8082a6da89cf --- /dev/null +++ b/src/Std/Time/Zoned/DateTime.lean @@ -0,0 +1,516 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.DateTime +import Std.Time.Zoned.TimeZone +import Std.Internal + +namespace Std +namespace Time +open Internal + +set_option linter.all true + +/-- +Represents a specific point in time associated with a `TimeZone`. +-/ +structure DateTime (tz : TimeZone) where + private mk :: + + /-- + `Timestamp` represents the exact moment in time. It's a UTC related `Timestamp`. + -/ + timestamp : Timestamp + + /-- + `Date` is a `Thunk` containing the `PlainDateTime` that represents the local date and time, it's + used for accessing data like `day` and `month` without having to recompute the data everytime. + -/ + date : Thunk PlainDateTime + +instance : BEq (DateTime tz) where + beq x y := x.timestamp == y.timestamp + +instance : Inhabited (DateTime tz) where + default := ⟨Inhabited.default, Thunk.mk fun _ => Inhabited.default⟩ + +namespace DateTime + +/-- +Creates a new `DateTime` out of a `Timestamp` that is in a `TimeZone`. +-/ +@[inline] +def ofTimestamp (tm : Timestamp) (tz : TimeZone) : DateTime tz := + DateTime.mk tm (Thunk.mk fun _ => tm.toPlainDateTimeAssumingUTC |>.addSeconds tz.toSeconds) + +/-- +Converts a `DateTime` to the number of days since the UNIX epoch. +-/ +def toDaysSinceUNIXEpoch (date : DateTime tz) : Day.Offset := + date.date.get.toDaysSinceUNIXEpoch + +/-- +Creates a `Timestamp` out of a `DateTime`. +-/ +@[inline] +def toTimestamp (date : DateTime tz) : Timestamp := + date.timestamp + +/-- +Changes the `TimeZone` to a new one. +-/ +@[inline] +def convertTimeZone (date : DateTime tz) (tz₁ : TimeZone) : DateTime tz₁ := + ofTimestamp date.timestamp tz₁ + +/-- +Creates a new `DateTime` out of a `PlainDateTime`. It assumes that the `PlainDateTime` is relative +to UTC. +-/ +@[inline] +def ofPlainDateTimeAssumingUTC (date : PlainDateTime) (tz : TimeZone) : DateTime tz := + let tm := Timestamp.ofPlainDateTimeAssumingUTC date + DateTime.mk tm (Thunk.mk fun _ => date.addSeconds tz.toSeconds) + +/-- +Creates a new `DateTime` from a `PlainDateTime`, assuming that the `PlainDateTime` +is relative to the given `TimeZone`. +-/ +@[inline] +def ofPlainDateTime (date : PlainDateTime) (tz : TimeZone) : DateTime tz := + let tm := date.subSeconds tz.toSeconds + DateTime.mk (Timestamp.ofPlainDateTimeAssumingUTC tm) (Thunk.mk fun _ => date) + +/-- +Add `Hour.Offset` to a `DateTime`. +-/ +@[inline] +def addHours (dt : DateTime tz) (hours : Hour.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addHours hours) tz + +/-- +Subtract `Hour.Offset` from a `DateTime`. +-/ +@[inline] +def subHours (dt : DateTime tz) (hours : Hour.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subHours hours) tz + +/-- +Add `Minute.Offset` to a `DateTime`. +-/ +@[inline] +def addMinutes (dt : DateTime tz) (minutes : Minute.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addMinutes minutes) tz + +/-- +Subtract `Minute.Offset` from a `DateTime`. +-/ +@[inline] +def subMinutes (dt : DateTime tz) (minutes : Minute.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subMinutes minutes) tz + +/-- +Add `Second.Offset` to a `DateTime`. +-/ +@[inline] +def addSeconds (dt : DateTime tz) (seconds : Second.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addSeconds seconds) tz + +/-- +Subtract `Second.Offset` from a `DateTime`. +-/ +@[inline] +def subSeconds (dt : DateTime tz) (seconds : Second.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subSeconds seconds) tz + +/-- +Add `Millisecond.Offset` to a `DateTime`. +-/ +@[inline] +def addMilliseconds (dt : DateTime tz) (milliseconds : Millisecond.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addMilliseconds milliseconds) tz + +/-- +Subtract `Millisecond.Offset` from a `DateTime`. +-/ +@[inline] +def subMilliseconds (dt : DateTime tz) (milliseconds : Millisecond.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subMilliseconds milliseconds) tz + +/-- +Add `Nanosecond.Offset` to a `DateTime`. +-/ +@[inline] +def addNanoseconds (dt : DateTime tz) (nanoseconds : Nanosecond.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addNanoseconds nanoseconds) tz + +/-- +Subtract `Nanosecond.Offset` from a `DateTime`. +-/ +@[inline] +def subNanoseconds (dt : DateTime tz) (nanoseconds : Nanosecond.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subNanoseconds nanoseconds) tz + +/-- +Add `Day.Offset` to a `DateTime`. +-/ +@[inline] +def addDays (dt : DateTime tz) (days : Day.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addDays days) tz + +/-- +Subtracts `Day.Offset` to a `DateTime`. +-/ +@[inline] +def subDays (dt : DateTime tz) (days : Day.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subDays days) tz + +/-- +Add `Week.Offset` to a `DateTime`. +-/ +@[inline] +def addWeeks (dt : DateTime tz) (weeks : Week.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addWeeks weeks) tz + +/-- +Subtracts `Week.Offset` to a `DateTime`. +-/ +@[inline] +def subWeeks (dt : DateTime tz) (weeks : Week.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subWeeks weeks) tz + +/-- +Add `Month.Offset` to a `DateTime`, it clips the day to the last valid day of that month. +-/ +def addMonthsClip (dt : DateTime tz) (months : Month.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addMonthsClip months) tz + +/-- +Subtracts `Month.Offset` from a `DateTime`, it clips the day to the last valid day of that month. +-/ +@[inline] +def subMonthsClip (dt : DateTime tz) (months : Month.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subMonthsClip months) tz + +/-- +Add `Month.Offset` from a `DateTime`, this function rolls over any excess days into the following +month. +-/ +def addMonthsRollOver (dt : DateTime tz) (months : Month.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addMonthsRollOver months) tz + +/-- +Subtract `Month.Offset` from a `DateTime`, this function rolls over any excess days into the following +month. +-/ +@[inline] +def subMonthsRollOver (dt : DateTime tz) (months : Month.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subMonthsRollOver months) tz + +/-- +Add `Year.Offset` to a `DateTime`, this function rolls over any excess days into the following +month. +-/ +@[inline] +def addYearsRollOver (dt : DateTime tz) (years : Year.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addYearsRollOver years) tz + +/-- +Add `Year.Offset` to a `DateTime`, it clips the day to the last valid day of that month. +-/ +@[inline] +def addYearsClip (dt : DateTime tz) (years : Year.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.addYearsClip years) tz + +/-- +Subtract `Year.Offset` from a `DateTime`, this function rolls over any excess days into the following +month. +-/ +@[inline] +def subYearsRollOver (dt : DateTime tz) (years : Year.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subYearsRollOver years) tz + +/-- +Subtract `Year.Offset` from to a `DateTime`, it clips the day to the last valid day of that month. +-/ +@[inline] +def subYearsClip (dt : DateTime tz) (years : Year.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.subYearsClip years) tz + +/-- +Creates a new `DateTime tz` by adjusting the day of the month to the given `days` value, with any +out-of-range days clipped to the nearest valid date. +-/ +@[inline] +def withDaysClip (dt : DateTime tz) (days : Day.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withDaysClip days) tz + +/-- +Creates a new `DateTime tz` by adjusting the day of the month to the given `days` value, with any +out-of-range days rolled over to the next month or year as needed. +-/ +@[inline] +def withDaysRollOver (dt : DateTime tz) (days : Day.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withDaysRollOver days) tz + +/-- +Creates a new `DateTime tz` by adjusting the month to the given `month` value. +The day remains unchanged, and any invalid days for the new month will be handled according to the `clip` behavior. +-/ +@[inline] +def withMonthClip (dt : DateTime tz) (month : Month.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withMonthClip month) tz + +/-- +Creates a new `DateTime tz` by adjusting the month to the given `month` value. +The day is rolled over to the next valid month if necessary. +-/ +@[inline] +def withMonthRollOver (dt : DateTime tz) (month : Month.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withMonthRollOver month) tz + +/-- +Creates a new `DateTime tz` by adjusting the year to the given `year` value. The month and day remain unchanged, +and any invalid days for the new year will be handled according to the `clip` behavior. +-/ +@[inline] +def withYearClip (dt : DateTime tz) (year : Year.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.withYearClip year) tz + +/-- +Creates a new `DateTime tz` by adjusting the year to the given `year` value. The month and day are rolled +over to the next valid month and day if necessary. +-/ +@[inline] +def withYearRollOver (dt : DateTime tz) (year : Year.Offset) : DateTime tz := + ofPlainDateTime (dt.date.get.withYearRollOver year) tz + +/-- +Creates a new `DateTime tz` by adjusting the `hour` component. +-/ +@[inline] +def withHours (dt : DateTime tz) (hour : Hour.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withHours hour) tz + +/-- +Creates a new `DateTime tz` by adjusting the `minute` component. +-/ +@[inline] +def withMinutes (dt : DateTime tz) (minute : Minute.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withMinutes minute) tz + +/-- +Creates a new `DateTime tz` by adjusting the `second` component. +-/ +@[inline] +def withSeconds (dt : DateTime tz) (second : Sigma Second.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withSeconds second) tz + +/-- +Creates a new `DateTime tz` by adjusting the `nano` component. +-/ +@[inline] +def withNanoseconds (dt : DateTime tz) (nano : Nanosecond.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withNanoseconds nano) tz + +/-- +Creates a new `DateTime tz` by adjusting the `millisecond` component. +-/ +@[inline] +def withMilliseconds (dt : DateTime tz) (milli : Millisecond.Ordinal) : DateTime tz := + ofPlainDateTime (dt.date.get.withMilliseconds milli) tz + +/-- +Converts a `Timestamp` to a `PlainDateTime` +-/ +@[inline] +def toPlainDateTime (dt : DateTime tz) : PlainDateTime := + dt.date.get + +/-- +Getter for the `Year` inside of a `DateTime` +-/ +@[inline] +def year (dt : DateTime tz) : Year.Offset := + dt.date.get.year + +/-- +Getter for the `Month` inside of a `DateTime` +-/ +@[inline] +def month (dt : DateTime tz) : Month.Ordinal := + dt.date.get.month + +/-- +Getter for the `Day` inside of a `DateTime` +-/ +@[inline] +def day (dt : DateTime tz) : Day.Ordinal := + dt.date.get.day + +/-- +Getter for the `Hour` inside of a `DateTime` +-/ +@[inline] +def hour (dt : DateTime tz) : Hour.Ordinal := + dt.date.get.hour + +/-- +Getter for the `Minute` inside of a `DateTime` +-/ +@[inline] +def minute (dt : DateTime tz) : Minute.Ordinal := + dt.date.get.minute + +/-- +Getter for the `Second` inside of a `DateTime` +-/ +@[inline] +def second (dt : DateTime tz) : Second.Ordinal dt.date.get.time.second.fst := + dt.date.get.second + +/-- +Getter for the `Milliseconds` inside of a `DateTime` +-/ +@[inline] +def millisecond (dt : DateTime tz) : Millisecond.Ordinal := + dt.date.get.time.nanosecond.emod 1000 (by decide) + +/-- +Getter for the `Nanosecond` inside of a `DateTime` +-/ +@[inline] +def nanosecond (dt : DateTime tz) : Nanosecond.Ordinal := + dt.date.get.time.nanosecond + +/-- +Gets the `Weekday` of a DateTime. +-/ +@[inline] +def weekday (dt : DateTime tz) : Weekday := + dt.date.get.date.weekday + +/-- +Determines the era of the given `DateTime` based on its year. +-/ +def era (date : DateTime tz) : Year.Era := + date.year.era + +/-- +Sets the `DateTime` to the specified `desiredWeekday`. +-/ +def withWeekday (dt : DateTime tz) (desiredWeekday : Weekday) : DateTime tz := + ofPlainDateTime (dt.date.get.withWeekday desiredWeekday) tz + +/-- +Checks if the `DateTime` is in a leap year. +-/ +def inLeapYear (date : DateTime tz) : Bool := + date.year.isLeap + +/-- +Determines the ordinal day of the year for the given `DateTime`. +-/ +def dayOfYear (date : DateTime tz) : Day.Ordinal.OfYear date.year.isLeap := + ValidDate.dayOfYear ⟨⟨date.month, date.day⟩, date.date.get.date.valid⟩ + +/-- +Determines the week of the year for the given `DateTime`. +-/ +@[inline] +def weekOfYear (date : DateTime tz) : Week.Ordinal := + date.date.get.weekOfYear + +/-- +Returns the unaligned week of the month for a `DateTime` (day divided by 7, plus 1). +-/ +def weekOfMonth (date : DateTime tz) : Bounded.LE 1 5 := + date.date.get.weekOfMonth + +/-- +Determines the week of the month for the given `DateTime`. The week of the month is calculated based +on the day of the month and the weekday. Each week starts on Monday because the entire library is +based on the Gregorian Calendar. +-/ +@[inline] +def alignedWeekOfMonth (date : DateTime tz) : Week.Ordinal.OfMonth := + date.date.get.alignedWeekOfMonth + +/-- +Determines the quarter of the year for the given `DateTime`. +-/ +@[inline] +def quarter (date : DateTime tz) : Bounded.LE 1 4 := + date.date.get.quarter + +/-- +Getter for the `PlainTime` inside of a `DateTime` +-/ +@[inline] +def time (zdt : DateTime tz) : PlainTime := + zdt.date.get.time + +/-- +Converts a `DateTime` to the number of days since the UNIX epoch. +-/ +@[inline] +def ofDaysSinceUNIXEpoch (days : Day.Offset) (time : PlainTime) (tz : TimeZone) : DateTime tz := + DateTime.ofPlainDateTime (PlainDateTime.ofDaysSinceUNIXEpoch days time) tz + +instance : HAdd (DateTime tz) (Day.Offset) (DateTime tz) where + hAdd := addDays + +instance : HSub (DateTime tz) (Day.Offset) (DateTime tz) where + hSub := subDays + +instance : HAdd (DateTime tz) (Week.Offset) (DateTime tz) where + hAdd := addWeeks + +instance : HSub (DateTime tz) (Week.Offset) (DateTime tz) where + hSub := subWeeks + +instance : HAdd (DateTime tz) (Hour.Offset) (DateTime tz) where + hAdd := addHours + +instance : HSub (DateTime tz) (Hour.Offset) (DateTime tz) where + hSub := subHours + +instance : HAdd (DateTime tz) (Minute.Offset) (DateTime tz) where + hAdd := addMinutes + +instance : HSub (DateTime tz) (Minute.Offset) (DateTime tz) where + hSub := subMinutes + +instance : HAdd (DateTime tz) (Second.Offset) (DateTime tz) where + hAdd := addSeconds + +instance : HSub (DateTime tz) (Second.Offset) (DateTime tz) where + hSub := subSeconds + +instance : HAdd (DateTime tz) (Millisecond.Offset) (DateTime tz) where + hAdd := addMilliseconds + +instance : HSub (DateTime tz) (Millisecond.Offset) (DateTime tz) where + hSub := subMilliseconds + +instance : HAdd (DateTime tz) (Nanosecond.Offset) (DateTime tz) where + hAdd := addNanoseconds + +instance : HSub (DateTime tz) (Nanosecond.Offset) (DateTime tz) where + hSub := subNanoseconds + +instance : HSub (DateTime tz) (DateTime tz₁) Duration where + hSub x y := x.toTimestamp - y.toTimestamp + +instance : HAdd (DateTime tz) Duration (DateTime tz) where + hAdd x y := x.addNanoseconds y.toNanoseconds + +instance : HSub (DateTime tz) Duration (DateTime tz) where + hSub x y := x.subNanoseconds y.toNanoseconds + +end DateTime +end Time +end Std diff --git a/src/Std/Time/Zoned/Offset.lean b/src/Std/Time/Zoned/Offset.lean new file mode 100644 index 000000000000..c6efccf550a7 --- /dev/null +++ b/src/Std/Time/Zoned/Offset.lean @@ -0,0 +1,74 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Time + +namespace Std +namespace Time +namespace TimeZone +open Internal + +set_option linter.all true + +/-- +Represents a timezone offset with an hour and second component. +-/ +structure Offset where + + /-- + Creates an `Offset` from a given number of seconds. + -/ + ofSeconds :: + + /-- + The same timezone offset in seconds. + -/ + second : Second.Offset + deriving Repr + +instance : Inhabited Offset where + default := ⟨0⟩ + +instance : BEq Offset where + beq x y := BEq.beq x.second y.second + +namespace Offset + +/-- +Converts an `Offset` to a string in ISO 8601 format. The `colon` parameter determines if the hour +and minute components are separated by a colon (e.g., "+01:00" or "+0100"). +-/ +def toIsoString (offset : Offset) (colon : Bool) : String := + let (sign, time) := if offset.second.val ≥ 0 then ("+", offset.second) else ("-", -offset.second) + let hour : Hour.Offset := time.ediv 3600 + let minute := Int.ediv (Int.tmod time.val 3600) 60 + let hourStr := if hour.val < 10 then s!"0{hour.val}" else toString hour.val + let minuteStr := if minute < 10 then s!"0{minute}" else toString minute + if colon then s!"{sign}{hourStr}:{minuteStr}" + else s!"{sign}{hourStr}{minuteStr}" + +/-- +A zero `Offset` representing UTC (no offset). +-/ +def zero : Offset := + { second := 0 } + +/-- +Creates an `Offset` from a given number of hour. +-/ +def ofHours (n : Hour.Offset) : Offset := + ofSeconds n.toSeconds + +/-- +Creates an `Offset` from a given number of hours and minutes. +-/ +def ofHoursAndMinutes (n : Hour.Offset) (m : Minute.Offset) : Offset := + ofSeconds (n.toSeconds + m.toSeconds) + +end Offset +end TimeZone +end Time +end Std diff --git a/src/Std/Time/Zoned/TimeZone.lean b/src/Std/Time/Zoned/TimeZone.lean new file mode 100644 index 000000000000..1dd0d6c3d887 --- /dev/null +++ b/src/Std/Time/Zoned/TimeZone.lean @@ -0,0 +1,71 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Zoned.Offset + +namespace Std +namespace Time + +set_option linter.all true + +/-- +A TimeZone structure that stores the timezone offset, the name, abbreviation and if it's in daylight +saving time. +-/ +structure TimeZone where + + /-- + The `Offset` of the date time. + -/ + offset : TimeZone.Offset + + /-- + The name of the time zone. + -/ + name : String + + /-- + The abbreviation of the time zone. + -/ + abbreviation : String + + /-- + Day light saving flag. + -/ + isDST : Bool + deriving Inhabited, Repr, BEq + +namespace TimeZone + +/-- +A zeroed `Timezone` representing UTC (no offset). +-/ +def UTC : TimeZone := + TimeZone.mk (Offset.zero) "UTC" "UTC" false + +/-- +A zeroed `Timezone` representing GMT (no offset). +-/ +def GMT : TimeZone := + TimeZone.mk (Offset.zero) "Greenwich Mean Time" "GMT" false + +/-- +Creates a `Timestamp` from a given number of hour. +-/ +def ofHours (name : String) (abbreviation : String) (n : Hour.Offset) (isDST : Bool := false) : TimeZone := + TimeZone.mk (Offset.ofHours n) name abbreviation isDST + +/-- +Creates a `Timestamp` from a given number of second. +-/ +def ofSeconds (name : String) (abbreviation : String) (n : Second.Offset) (isDST : Bool := false) : TimeZone := + TimeZone.mk (Offset.ofSeconds n) name abbreviation isDST + +/-- +Gets the number of seconds in a timezone offset. +-/ +def toSeconds (tz : TimeZone) : Second.Offset := + tz.offset.second diff --git a/src/Std/Time/Zoned/ZoneRules.lean b/src/Std/Time/Zoned/ZoneRules.lean new file mode 100644 index 000000000000..8bfdcb48bc27 --- /dev/null +++ b/src/Std/Time/Zoned/ZoneRules.lean @@ -0,0 +1,225 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.DateTime +import Std.Time.Zoned.TimeZone + +namespace Std +namespace Time +namespace TimeZone +open Internal + +set_option linter.all true + +/-- +Represents the type of local time in relation to UTC. +-/ +inductive UTLocal + /-- + Universal Time (UT), often referred to as UTC. + -/ + | ut + + /-- + Local time that is not necessarily UTC. + -/ + | local + deriving Repr, Inhabited + +/-- +Represents types of wall clocks or standard times. +-/ +inductive StdWall + /-- + Time based on a wall clock, which can include daylight saving adjustments. + -/ + | wall + + /-- + Standard time without adjustments for daylight saving. + -/ + | standard + deriving Repr, Inhabited + +/-- +Represents a type of local time, including offset and daylight saving information. +-/ +structure LocalTimeType where + + /-- + The offset from GMT for this local time. + -/ + gmtOffset : TimeZone.Offset + + /-- + Indicates if daylight saving time is observed. + -/ + isDst : Bool + + /-- + The abbreviation for this local time type (e.g., "EST", "PDT"). + -/ + abbreviation : String + + /-- + Indicates if the time is wall clock or standard time. + -/ + wall : StdWall + + /-- + Distinguishes between universal time and local time. + -/ + utLocal : UTLocal + + /-- + ID of the timezone + -/ + identifier : String + deriving Repr, Inhabited + +namespace LocalTimeType + +/-- +Gets the `TimeZone` offset from a `LocalTimeType`. +-/ +def getTimeZone (time : LocalTimeType) : TimeZone := + ⟨time.gmtOffset, time.identifier, time.abbreviation, time.isDst⟩ + +end LocalTimeType + +/-- +Represents a time zone transition, mapping a time to a local time type. +-/ +structure Transition where + + /-- + The specific time of the transition event. + -/ + time : Second.Offset + + /-- + The local time type associated with this transition. + -/ + localTimeType : LocalTimeType + deriving Repr, Inhabited + +/-- +Represents the rules for a time zone. +-/ +structure ZoneRules where + + /-- + The first `LocalTimeType` used as a fallback when no matching transition is found. + -/ + initialLocalTimeType : LocalTimeType + + /-- + The array of transitions for the time zone. + -/ + transitions : Array Transition + + deriving Repr, Inhabited + +namespace Transition + +/-- +Create a TimeZone from a Transition. +-/ +def createTimeZoneFromTransition (transition : Transition) : TimeZone := + let name := transition.localTimeType.identifier + let offset := transition.localTimeType.gmtOffset + let abbreviation := transition.localTimeType.abbreviation + TimeZone.mk offset name abbreviation transition.localTimeType.isDst + +/-- +Applies the transition to a Timestamp. +-/ +def apply (timestamp : Timestamp) (transition : Transition) : Timestamp := + let offsetInSeconds := transition.localTimeType.gmtOffset.second |>.add transition.localTimeType.gmtOffset.second + timestamp.addSeconds offsetInSeconds + +/-- +Finds the transition corresponding to a given timestamp in `Array Transition`. +If the timestamp falls between two transitions, it returns the most recent transition before the timestamp. +-/ +def findTransitionIndexForTimestamp (transitions : Array Transition) (timestamp : Timestamp) : Option Nat := + let value := timestamp.toSecondsSinceUnixEpoch + if let some idx := transitions.findIdx? (fun t => t.time.val > value.val) + then some idx + else none + +/-- +Finds the transition corresponding to a given timestamp in `Array Transition`. +If the timestamp falls between two transitions, it returns the most recent transition before the timestamp. +-/ +def findTransitionForTimestamp (transitions : Array Transition) (timestamp : Timestamp) : Option Transition := + if let some idx := findTransitionIndexForTimestamp transitions timestamp + then transitions.get? (idx - 1) + else transitions.back? + +/-- +Find the current `TimeZone` out of a `Transition` in a `Array Transition` +-/ +def timezoneAt (transitions : Array Transition) (tm : Timestamp) : Except String TimeZone := + if let some transition := findTransitionForTimestamp transitions tm + then .ok transition.createTimeZoneFromTransition + else .error "cannot find local timezone." + +end Transition +namespace ZoneRules + +/-- +Creates ZoneRules with a fixed GMT offset. +-/ +def fixedOffsetZone (second : Second.Offset) (identifier : Option String := none) (abbreviation : Option String := none) : ZoneRules := + let offset : Offset := { second } + { + transitions := #[], + initialLocalTimeType := { + gmtOffset := offset, + isDst := false, abbreviation := abbreviation.getD (offset.toIsoString true), + wall := .standard, + utLocal := .ut, + identifier := identifier.getD (offset.toIsoString true) + } + } + +/-- +Creates ZoneRules with a fixed offset of UTC (GMT+0). +-/ +@[inline] +def UTC : ZoneRules := + fixedOffsetZone 0 "UTC" "UTC" + +/-- +Finds the `LocalTimeType` corresponding to a given `Timestamp` in `ZoneRules`. +If the timestamp falls between two transitions, it returns the most recent transition before the timestamp. +If no transition is found, it falls back to `initialLocalTimeType`. +-/ +@[inline] +def findLocalTimeTypeForTimestamp (zr : ZoneRules) (timestamp : Timestamp) : LocalTimeType := + Transition.findTransitionForTimestamp zr.transitions timestamp + |>.map (·.localTimeType) + |>.getD zr.initialLocalTimeType + +/-- +Find the current `TimeZone` out of a `Transition` in a `ZoneRules` +-/ +@[inline] +def timezoneAt (zr : ZoneRules) (tm : Timestamp) : TimeZone := + Transition.timezoneAt zr.transitions tm + |>.toOption + |>.getD (zr.initialLocalTimeType |>.getTimeZone) + +/-- +Creates `ZoneRules` for the given `TimeZone`. +-/ +@[inline] +def ofTimeZone (tz : TimeZone) : ZoneRules := + let ltt := LocalTimeType.mk tz.offset tz.isDST tz.abbreviation .wall .local tz.name + ZoneRules.mk ltt #[] + +end ZoneRules diff --git a/src/Std/Time/Zoned/ZonedDateTime.lean b/src/Std/Time/Zoned/ZonedDateTime.lean new file mode 100644 index 000000000000..bd6686b75df2 --- /dev/null +++ b/src/Std/Time/Zoned/ZonedDateTime.lean @@ -0,0 +1,587 @@ +/- +Copyright (c) 2024 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Sofia Rodrigues +-/ +prelude +import Std.Time.Zoned.DateTime +import Std.Time.Zoned.ZoneRules + +namespace Std +namespace Time + +set_option linter.all true + +/-- +Represents a date and time with timezone information. +-/ +structure ZonedDateTime where + private mk:: + + /-- + The plain datetime component, evaluated lazily. + -/ + date : Thunk PlainDateTime + + /-- + The corresponding timestamp for the datetime. + -/ + timestamp : Timestamp + + /-- + The timezone rules applied to this datetime. + -/ + rules : TimeZone.ZoneRules + + /-- + The timezone associated with this datetime. + -/ + timezone : TimeZone + +instance : Inhabited ZonedDateTime where + default := ⟨Thunk.mk Inhabited.default, Inhabited.default, Inhabited.default, Inhabited.default⟩ + +namespace ZonedDateTime +open DateTime + +/-- +Creates a new `ZonedDateTime` out of a `Timestamp` and a `ZoneRules`. +-/ +@[inline] +def ofTimestamp (tm : Timestamp) (rules : TimeZone.ZoneRules) : ZonedDateTime := + let tz := rules.timezoneAt tm + ZonedDateTime.mk (Thunk.mk fun _ => tm.toPlainDateTimeAssumingUTC.addSeconds tz.toSeconds) tm rules tz + +/-- +Creates a new `ZonedDateTime` out of a `PlainDateTime` and a `ZoneRules`. +-/ +@[inline] +def ofPlainDateTime (pdt : PlainDateTime) (zr : TimeZone.ZoneRules) : ZonedDateTime := + let tm := pdt.toTimestampAssumingUTC + + let transition := + let value := tm.toSecondsSinceUnixEpoch + if let some idx := zr.transitions.findIdx? (fun t => t.time.val ≥ value.val) + then do + let last ← zr.transitions.get? (idx - 1) + let next ← zr.transitions.get? idx <|> zr.transitions.back? + + let utcNext := next.time.sub last.localTimeType.gmtOffset.second.abs + + if utcNext.val > tm.toSecondsSinceUnixEpoch.val + then pure last + else pure next + + else zr.transitions.back? + + let tz := + transition + |>.map (·.localTimeType) + |>.getD zr.initialLocalTimeType + |>.getTimeZone + + let tm := tm.subSeconds tz.toSeconds + ZonedDateTime.mk (Thunk.mk fun _ => tm.toPlainDateTimeAssumingUTC.addSeconds tz.toSeconds) tm zr tz + +/-- +Creates a new `ZonedDateTime` out of a `Timestamp` and a `TimeZone`. +-/ +@[inline] +def ofTimestampWithZone (tm : Timestamp) (tz : TimeZone) : ZonedDateTime := + ofTimestamp tm (TimeZone.ZoneRules.ofTimeZone tz) + +/-- +Creates a new `ZonedDateTime` out of a `PlainDateTime` and a `TimeZone`. +-/ +@[inline] +def ofPlainDateTimeWithZone (tm : PlainDateTime) (tz : TimeZone) : ZonedDateTime := + ofPlainDateTime tm (TimeZone.ZoneRules.ofTimeZone tz) + +/-- +Creates a new `Timestamp` out of a `ZonedDateTime`. +-/ +@[inline] +def toTimestamp (date : ZonedDateTime) : Timestamp := + date.timestamp + +/-- +Changes the `ZoleRules` to a new one. +-/ +@[inline] +def convertZoneRules (date : ZonedDateTime) (tz₁ : TimeZone.ZoneRules) : ZonedDateTime := + ofTimestamp date.toTimestamp tz₁ + +/-- +Creates a new `ZonedDateTime` out of a `PlainDateTime`. It assumes that the `PlainDateTime` is relative +to UTC. +-/ +@[inline] +def ofPlainDateTimeAssumingUTC (date : PlainDateTime) (tz : TimeZone.ZoneRules) : ZonedDateTime := + ofTimestamp date.toTimestampAssumingUTC tz + +/-- +Converts a `ZonedDateTime` to a `PlainDateTime` +-/ +@[inline] +def toPlainDateTime (dt : ZonedDateTime) : PlainDateTime := + dt.date.get + +/-- +Converts a `ZonedDateTime` to a `PlainDateTime` +-/ +@[inline] +def toDateTime (dt : ZonedDateTime) : DateTime dt.timezone := + DateTime.ofTimestamp dt.timestamp dt.timezone + +/-- +Getter for the `PlainTime` inside of a `ZonedDateTime` +-/ +@[inline] +def time (zdt : ZonedDateTime) : PlainTime := + zdt.date.get.time + +/-- +Getter for the `Year` inside of a `ZonedDateTime` +-/ +@[inline] +def year (zdt : ZonedDateTime) : Year.Offset := + zdt.date.get.year + +/-- +Getter for the `Month` inside of a `ZonedDateTime` +-/ +@[inline] +def month (zdt : ZonedDateTime) : Month.Ordinal := + zdt.date.get.month + +/-- +Getter for the `Day` inside of a `ZonedDateTime` +-/ +@[inline] +def day (zdt : ZonedDateTime) : Day.Ordinal := + zdt.date.get.day + +/-- +Getter for the `Hour` inside of a `ZonedDateTime` +-/ +@[inline] +def hour (zdt : ZonedDateTime) : Hour.Ordinal := + zdt.date.get.time.hour + +/-- +Getter for the `Minute` inside of a `ZonedDateTime` +-/ +@[inline] +def minute (zdt : ZonedDateTime) : Minute.Ordinal := + zdt.date.get.minute + +/-- +Getter for the `Second` inside of a `ZonedDateTime` +-/ +@[inline] +def second (zdt : ZonedDateTime) : Second.Ordinal zdt.date.get.time.second.fst := + zdt.date.get.time.second.snd + +/-- +Getter for the `Millisecond` inside of a `ZonedDateTime`. +-/ +@[inline] +def millisecond (dt : ZonedDateTime) : Millisecond.Ordinal := + dt.date.get.time.millisecond + +/-- +Getter for the `Nanosecond` inside of a `ZonedDateTime` +-/ +@[inline] +def nanosecond (zdt : ZonedDateTime) : Nanosecond.Ordinal := + zdt.date.get.time.nanosecond + +/-- +Getter for the `TimeZone.Offset` inside of a `ZonedDateTime` +-/ +@[inline] +def offset (zdt : ZonedDateTime) : TimeZone.Offset := + zdt.timezone.offset + +/-- +Returns the weekday. +-/ +@[inline] +def weekday (zdt : ZonedDateTime) : Weekday := + zdt.date.get.weekday + +/-- +Transforms a tuple of a `ZonedDateTime` into a `Day.Ordinal.OfYear`. +-/ +@[inline] +def dayOfYear (date : ZonedDateTime) : Day.Ordinal.OfYear date.year.isLeap := + ValidDate.dayOfYear ⟨(date.month, date.day), date.date.get.date.valid⟩ + +/-- +Determines the week of the year for the given `ZonedDateTime`. +-/ +@[inline] +def weekOfYear (date : ZonedDateTime) : Week.Ordinal := + date.date.get.weekOfYear + +/-- +Returns the unaligned week of the month for a `ZonedDateTime` (day divided by 7, plus 1). +-/ +def weekOfMonth (date : ZonedDateTime) : Internal.Bounded.LE 1 5 := + date.date.get.weekOfMonth + +/-- +Determines the week of the month for the given `ZonedDateTime`. The week of the month is calculated based +on the day of the month and the weekday. Each week starts on Monday because the entire library is +based on the Gregorian Calendar. +-/ +@[inline] +def alignedWeekOfMonth (date : ZonedDateTime) : Week.Ordinal.OfMonth := + date.date.get.alignedWeekOfMonth + +/-- +Determines the quarter of the year for the given `ZonedDateTime`. +-/ +@[inline] +def quarter (date : ZonedDateTime) : Internal.Bounded.LE 1 4 := + date.date.get.quarter + +/-- +Add `Day.Offset` to a `ZonedDateTime`. +-/ +def addDays (dt : ZonedDateTime) (days : Day.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addDays days).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Day.Offset` from a `ZonedDateTime`. +-/ +def subDays (dt : ZonedDateTime) (days : Day.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subDays days).toTimestampAssumingUTC dt.rules + +/-- +Add `Week.Offset` to a `ZonedDateTime`. +-/ +def addWeeks (dt : ZonedDateTime) (weeks : Week.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addWeeks weeks).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Week.Offset` from a `ZonedDateTime`. +-/ +def subWeeks (dt : ZonedDateTime) (weeks : Week.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subWeeks weeks).toTimestampAssumingUTC dt.rules + +/-- +Add `Month.Offset` to a `ZonedDateTime`, clipping to the last valid day. +-/ +def addMonthsClip (dt : ZonedDateTime) (months : Month.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addMonthsClip months).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Month.Offset` from a `ZonedDateTime`, clipping to the last valid day. +-/ +def subMonthsClip (dt : ZonedDateTime) (months : Month.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subMonthsClip months).toTimestampAssumingUTC dt.rules + +/-- +Add `Month.Offset` to a `ZonedDateTime`, rolling over excess days. +-/ +def addMonthsRollOver (dt : ZonedDateTime) (months : Month.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addMonthsRollOver months).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Month.Offset` from a `ZonedDateTime`, rolling over excess days. +-/ +def subMonthsRollOver (dt : ZonedDateTime) (months : Month.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subMonthsRollOver months).toTimestampAssumingUTC dt.rules + +/-- +Add `Year.Offset` to a `ZonedDateTime`, rolling over excess days. +-/ +def addYearsRollOver (dt : ZonedDateTime) (years : Year.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addYearsRollOver years).toTimestampAssumingUTC dt.rules + +/-- +Add `Year.Offset` to a `ZonedDateTime`, clipping to the last valid day. +-/ +def addYearsClip (dt : ZonedDateTime) (years : Year.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addYearsClip years).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Year.Offset` from a `ZonedDateTime`, clipping to the last valid day. +-/ +def subYearsClip (dt : ZonedDateTime) (years : Year.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subYearsClip years).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Year.Offset` from a `ZonedDateTime`, rolling over excess days. +-/ +def subYearsRollOver (dt : ZonedDateTime) (years : Year.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subYearsRollOver years).toTimestampAssumingUTC dt.rules + +/-- +Add `Hour.Offset` to a `ZonedDateTime`. +-/ +def addHours (dt : ZonedDateTime) (hours : Hour.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addHours hours).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Hour.Offset` from a `ZonedDateTime`. +-/ +def subHours (dt : ZonedDateTime) (hours : Hour.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subHours hours).toTimestampAssumingUTC dt.rules + +/-- +Add `Minute.Offset` to a `ZonedDateTime`. +-/ +def addMinutes (dt : ZonedDateTime) (minutes : Minute.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addMinutes minutes).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Minute.Offset` from a `ZonedDateTime`. +-/ +def subMinutes (dt : ZonedDateTime) (minutes : Minute.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subMinutes minutes).toTimestampAssumingUTC dt.rules + +/-- +Add `Millisecond.Offset` to a `DateTime`. +-/ +@[inline] +def addMilliseconds (dt : ZonedDateTime) (milliseconds : Millisecond.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addMilliseconds milliseconds).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Millisecond.Offset` from a `DateTime`. +-/ +@[inline] +def subMilliseconds (dt : ZonedDateTime) (milliseconds : Millisecond.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subMilliseconds milliseconds).toTimestampAssumingUTC dt.rules + +/-- +Add `Second.Offset` to a `ZonedDateTime`. +-/ +def addSeconds (dt : ZonedDateTime) (seconds : Second.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addSeconds seconds).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Second.Offset` from a `ZonedDateTime`. +-/ +def subSeconds (dt : ZonedDateTime) (seconds : Second.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subSeconds seconds).toTimestampAssumingUTC dt.rules + +/-- +Add `Nanosecond.Offset` to a `ZonedDateTime`. +-/ +def addNanoseconds (dt : ZonedDateTime) (nanoseconds : Nanosecond.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.addNanoseconds nanoseconds).toTimestampAssumingUTC dt.rules + +/-- +Subtract `Nanosecond.Offset` from a `ZonedDateTime`. +-/ +def subNanoseconds (dt : ZonedDateTime) (nanoseconds : Nanosecond.Offset) : ZonedDateTime := + let date := dt.timestamp.toPlainDateTimeAssumingUTC + ZonedDateTime.ofTimestamp (date.subNanoseconds nanoseconds).toTimestampAssumingUTC dt.rules + +/-- +Determines the era of the given `ZonedDateTime` based on its year. +-/ +@[inline] +def era (date : ZonedDateTime) : Year.Era := + date.date.get.era + +/-- +Sets the `ZonedDateTime` to the specified `desiredWeekday`. +-/ +def withWeekday (dt : ZonedDateTime) (desiredWeekday : Weekday) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withWeekday desiredWeekday) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the day of the month to the given `days` value, with any +out-of-range days clipped to the nearest valid date. +-/ +@[inline] +def withDaysClip (dt : ZonedDateTime) (days : Day.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withDaysClip days) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the day of the month to the given `days` value, with any +out-of-range days rolled over to the next month or year as needed. +-/ +@[inline] +def withDaysRollOver (dt : ZonedDateTime) (days : Day.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withDaysRollOver days) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the month to the given `month` value. +The day remains unchanged, and any invalid days for the new month will be handled according to the `clip` behavior. +-/ +@[inline] +def withMonthClip (dt : ZonedDateTime) (month : Month.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withMonthClip month) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the month to the given `month` value. +The day is rolled over to the next valid month if necessary. +-/ +@[inline] +def withMonthRollOver (dt : ZonedDateTime) (month : Month.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withMonthRollOver month) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the year to the given `year` value. The month and day remain unchanged, +and any invalid days for the new year will be handled according to the `clip` behavior. +-/ +@[inline] +def withYearClip (dt : ZonedDateTime) (year : Year.Offset) : ZonedDateTime := + let date := dt.date.get + + ZonedDateTime.ofPlainDateTime (date.withYearClip year) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the year to the given `year` value. The month and day are rolled +over to the next valid month and day if necessary. +-/ +@[inline] +def withYearRollOver (dt : ZonedDateTime) (year : Year.Offset) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withYearRollOver year) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the `hour` component. +-/ +@[inline] +def withHours (dt : ZonedDateTime) (hour : Hour.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withHours hour) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the `minute` component. +-/ +@[inline] +def withMinutes (dt : ZonedDateTime) (minute : Minute.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withMinutes minute) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the `second` component. +-/ +@[inline] +def withSeconds (dt : ZonedDateTime) (second : Sigma Second.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withSeconds second) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the `nano` component with a new `millis` that will set +in the millisecond scale. +-/ +@[inline] +def withMilliseconds (dt : ZonedDateTime) (millis : Millisecond.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withMilliseconds millis) dt.rules + +/-- +Creates a new `ZonedDateTime` by adjusting the `nano` component. +-/ +@[inline] +def withNanoseconds (dt : ZonedDateTime) (nano : Nanosecond.Ordinal) : ZonedDateTime := + let date := dt.date.get + ZonedDateTime.ofPlainDateTime (date.withNanoseconds nano) dt.rules + +/-- +Checks if the `ZonedDateTime` is in a leap year. +-/ +def inLeapYear (date : ZonedDateTime) : Bool := + date.year.isLeap + +/-- +Converts a `ZonedDateTime` to the number of days since the UNIX epoch. +-/ +def toDaysSinceUNIXEpoch (date : ZonedDateTime) : Day.Offset := + date.date.get.toDaysSinceUNIXEpoch + +/-- +Converts a `ZonedDateTime` to the number of days since the UNIX epoch. +-/ +@[inline] +def ofDaysSinceUNIXEpoch (days : Day.Offset) (time : PlainTime) (zt : TimeZone.ZoneRules) : ZonedDateTime := + ZonedDateTime.ofPlainDateTime (PlainDateTime.ofDaysSinceUNIXEpoch days time) zt + +instance : HAdd ZonedDateTime Day.Offset ZonedDateTime where + hAdd := addDays + +instance : HSub ZonedDateTime Day.Offset ZonedDateTime where + hSub := subDays + +instance : HAdd ZonedDateTime Week.Offset ZonedDateTime where + hAdd := addWeeks + +instance : HSub ZonedDateTime Week.Offset ZonedDateTime where + hSub := subWeeks + +instance : HAdd ZonedDateTime Hour.Offset ZonedDateTime where + hAdd := addHours + +instance : HSub ZonedDateTime Hour.Offset ZonedDateTime where + hSub := subHours + +instance : HAdd ZonedDateTime Minute.Offset ZonedDateTime where + hAdd := addMinutes + +instance : HSub ZonedDateTime Minute.Offset ZonedDateTime where + hSub := subMinutes + +instance : HAdd ZonedDateTime Second.Offset ZonedDateTime where + hAdd := addSeconds + +instance : HSub ZonedDateTime Second.Offset ZonedDateTime where + hSub := subSeconds + +instance : HAdd ZonedDateTime Millisecond.Offset ZonedDateTime where + hAdd := addMilliseconds + +instance : HSub ZonedDateTime Millisecond.Offset ZonedDateTime where + hSub := subMilliseconds + +instance : HAdd ZonedDateTime Nanosecond.Offset ZonedDateTime where + hAdd := addNanoseconds + +instance : HSub ZonedDateTime Nanosecond.Offset ZonedDateTime where + hSub := subNanoseconds + +instance : HSub ZonedDateTime ZonedDateTime Duration where + hSub x y := x.toTimestamp - y.toTimestamp + +instance : HAdd ZonedDateTime Duration ZonedDateTime where + hAdd x y := x.addNanoseconds y.toNanoseconds + +instance : HSub ZonedDateTime Duration ZonedDateTime where + hSub x y := x.subNanoseconds y.toNanoseconds + +end ZonedDateTime +end Time +end Std diff --git a/src/runtime/io.cpp b/src/runtime/io.cpp index afeea75c92ff..44f18cc58733 100644 --- a/src/runtime/io.cpp +++ b/src/runtime/io.cpp @@ -5,6 +5,7 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura, Sebastian Ullrich */ #if defined(LEAN_WINDOWS) +#include #include #include #define NOMINMAX // prevent ntdef.h from defining min/max macros @@ -630,6 +631,176 @@ extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_put_str(b_obj_arg h, b_obj_ar } } +/* Std.Time.Timestamp.now : IO Timestamp */ +extern "C" LEAN_EXPORT obj_res lean_get_current_time(obj_arg /* w */) { + using namespace std::chrono; + + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + long long timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); + + long long secs = timestamp / 1000000000; + long long nano = timestamp % 1000000000; + + lean_object *lean_ts = lean_alloc_ctor(0, 2, 0); + lean_ctor_set(lean_ts, 0, lean_int64_to_int(secs)); + lean_ctor_set(lean_ts, 1, lean_int64_to_int(nano)); + + return lean_io_result_mk_ok(lean_ts); +} + +/* Std.Time.Database.Windows.getNextTransition : @&String -> Int64 -> Bool -> IO (Option (Int64 × TimeZone)) */ +extern "C" LEAN_EXPORT obj_res lean_windows_get_next_transition(b_obj_arg timezone_str, uint64_t tm_obj, uint8 default_time, obj_arg /* w */) { +#if defined(LEAN_WINDOWS) + UErrorCode status = U_ZERO_ERROR; + const char* dst_name_id = lean_string_cstr(timezone_str); + + UChar tzID[256]; + u_strFromUTF8(tzID, sizeof(tzID) / sizeof(tzID[0]), NULL, dst_name_id, strlen(dst_name_id), &status); + + if (U_FAILURE(status)) { + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to read identifier"))); + } + + UCalendar *cal = ucal_open(tzID, -1, NULL, UCAL_GREGORIAN, &status); + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to open calendar"))); + } + + int64_t tm = 0; + + if (!default_time) { + int64_t timestamp_secs = (int64_t)tm_obj; + + ucal_setMillis(cal, timestamp_secs * 1000, &status); + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to set calendar time"))); + } + + UDate nextTransition; + if (!ucal_getTimeZoneTransitionDate(cal, UCAL_TZ_TRANSITION_NEXT, &nextTransition, &status)) { + ucal_close(cal); + return io_result_mk_ok(mk_option_none()); + } + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to get next transation"))); + } + + tm = (int64_t)(nextTransition / 1000.0); + } + + int32_t dst_offset = ucal_get(cal, UCAL_DST_OFFSET, &status); + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to get dst_offset"))); + } + + int is_dst = dst_offset != 0; + + int32_t tzIDLength = ucal_getTimeZoneDisplayName(cal, is_dst ? UCAL_DST : UCAL_STANDARD, "en_US", tzID, 32, &status); + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to timezone identifier"))); + } + + char dst_name[256]; + int32_t dst_name_len; + u_strToUTF8(dst_name, sizeof(dst_name), &dst_name_len, tzID, tzIDLength, &status); + + if (U_FAILURE(status)) { + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to convert DST name to UTF-8"))); + } + + UChar display_name[32]; + int32_t display_name_len = ucal_getTimeZoneDisplayName(cal, is_dst ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD, "en_US", display_name, 32, &status); + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to read abbreaviation"))); + } + + char display_name_str[256]; + int32_t display_name_str_len; + u_strToUTF8(display_name_str, sizeof(display_name_str), &display_name_str_len, display_name, display_name_len, &status); + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to get abbreviation to cstr"))); + } + + int32_t zone_offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status); + zone_offset += dst_offset; + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to get zone_offset"))); + } + + ucal_close(cal); + + int offset_seconds = zone_offset / 1000; + + lean_object *lean_tz = lean_alloc_ctor(0, 3, 1); + lean_ctor_set(lean_tz, 0, lean_int_to_int(offset_seconds)); + lean_ctor_set(lean_tz, 1, lean_mk_string_from_bytes_unchecked(dst_name, dst_name_len)); + lean_ctor_set(lean_tz, 2, lean_mk_string_from_bytes_unchecked(display_name_str, display_name_str_len)); + lean_ctor_set_uint8(lean_tz, sizeof(void*)*3, is_dst); + + lean_object *lean_pair = lean_alloc_ctor(0, 2, 0); + lean_ctor_set(lean_pair, 0, lean_box_uint64((uint64_t)tm)); + lean_ctor_set(lean_pair, 1, lean_tz); + + return lean_io_result_mk_ok(mk_option_some(lean_pair)); +#else + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to get timezone, its windows only."))); +#endif +} + +/* Std.Time.Database.Windows.getLocalTimeZoneIdentifierAt : Int64 → IO String */ +extern "C" LEAN_EXPORT obj_res lean_get_windows_local_timezone_id_at(uint64_t tm_obj, obj_arg /* w */) { +#if defined(LEAN_WINDOWS) + UErrorCode status = U_ZERO_ERROR; + UCalendar* cal = ucal_open(NULL, -1, NULL, UCAL_GREGORIAN, &status); + + if (U_FAILURE(status)) { + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to open calendar"))); + } + + int64_t timestamp_secs = (int64_t)tm_obj; + ucal_setMillis(cal, timestamp_secs * 1000, &status); + + if (U_FAILURE(status)) { + ucal_close(cal); + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to set calendar time"))); + } + + UChar tzId[256]; + int32_t tzIdLength = ucal_getTimeZoneID(cal, tzId, sizeof(tzId) / sizeof(tzId[0]), &status); + ucal_close(cal); + + if (U_FAILURE(status)) { + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to get timezone ID"))); + } + + char tzIdStr[256]; + u_strToUTF8(tzIdStr, sizeof(tzIdStr), NULL, tzId, tzIdLength, &status); + + if (U_FAILURE(status)) { + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("failed to convert timezone ID to UTF-8"))); + } + + return lean_io_result_mk_ok(lean_mk_ascii_string_unchecked(tzIdStr)); +#else + return lean_io_result_mk_error(lean_decode_io_error(EINVAL, mk_string("timezone retrieval is Windows-only"))); +#endif +} + /* monoMsNow : BaseIO Nat */ extern "C" LEAN_EXPORT obj_res lean_io_mono_ms_now(obj_arg /* w */) { static_assert(sizeof(std::chrono::milliseconds::rep) <= sizeof(uint64), "size of std::chrono::nanoseconds::rep may not exceed 64"); diff --git a/tests/lean/hpow.lean b/tests/lean/hpow.lean index 6dd24d41c369..fe896373faed 100644 --- a/tests/lean/hpow.lean +++ b/tests/lean/hpow.lean @@ -1,4 +1,5 @@ -import Lean.Data.Rat +import Std.Internal.Rat +open Std.Internal /-! Test the `rightact%` elaborator for `HPow.hPow`, added to address #2854 @@ -14,7 +15,7 @@ variable (n : Nat) (m : Int) (q : Rat) #check (n ^ 2 + (1 : Nat) : Int) instance instNatPowRat : NatPow Rat where - pow q n := Lean.mkRat (q.num ^ n) (q.den ^ n) + pow q n := Std.Internal.mkRat (q.num ^ n) (q.den ^ n) instance instPowRatInt : Pow Rat Int where pow q m := if 0 ≤ m then q ^ (m.toNat : Nat) else (1/q) ^ ((-m).toNat) diff --git a/tests/lean/ppNumericTypes.lean b/tests/lean/ppNumericTypes.lean index 3acd48538b77..a52dcda22f4c 100644 --- a/tests/lean/ppNumericTypes.lean +++ b/tests/lean/ppNumericTypes.lean @@ -1,4 +1,5 @@ -import Lean.Data.Rat +import Std.Internal.Rat +open Std.Internal /-! Tests for `pp.numericTypes` and `pp.natLit` @@ -6,7 +7,7 @@ Tests for `pp.numericTypes` and `pp.natLit` RFC #3021 -/ -open Lean (Rat) +open Lean section diff --git a/tests/lean/rat1.lean b/tests/lean/rat1.lean index 64d2ffd38068..5ddea7524766 100644 --- a/tests/lean/rat1.lean +++ b/tests/lean/rat1.lean @@ -1,4 +1,5 @@ -import Lean.Data.Rat +import Std.Internal.Rat +open Std.Internal open Lean #eval (15 : Rat) / 10 diff --git a/tests/lean/run/1780.lean b/tests/lean/run/1780.lean index d3345288469d..0712fd9a1ff9 100644 --- a/tests/lean/run/1780.lean +++ b/tests/lean/run/1780.lean @@ -6,6 +6,6 @@ open Meta -- ok end section -open Lean hiding Rat +open Lean hiding AttributeImplCore open Meta -- ok end diff --git a/tests/lean/run/timeAPI.lean b/tests/lean/run/timeAPI.lean new file mode 100644 index 000000000000..881a38bad547 --- /dev/null +++ b/tests/lean/run/timeAPI.lean @@ -0,0 +1,908 @@ +import Std.Time +import Init +open Std.Time + +def sao_paulo : TimeZone.ZoneRules := + { + initialLocalTimeType := + { + gmtOffset := { second := -11188 }, + isDst := false, + abbreviation := "LMT", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + }, + transitions := #[ + { + time := 782276400, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 793159200, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 813726000, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 824004000, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 844570800, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 856058400, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 876106800, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 888717600, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 908074800, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 919562400, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 938919600, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 951616800, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 970974000, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 982461600, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 1003028400, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 1013911200, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 1036292400, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 1045360800, + localTimeType := { + gmtOffset := { second := -10800 }, + isDst := false, + abbreviation := "-03", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + }, + { time := 1066532400, + localTimeType := { + gmtOffset := { second := -7200 }, + isDst := true, + abbreviation := "-02", + wall := Std.Time.TimeZone.StdWall.standard, + utLocal := Std.Time.TimeZone.UTLocal.ut, + identifier := "America/Sao_Paulo" + } + } + ] + } + +/- +Unit conversion tests. +-/ + +/-- +info: --- nanosecond +nanoseconds: 1209600000000000 +milliseconds: 1209600000 +seconds: 1209600 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 +--- millisecond +nanoseconds: 1209600000000000 +seconds: 1209600 +milliseconds: 1209600000 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 +--- second +nanoseconds: 1209600000000000 +milliseconds: 1209600000 +seconds: 1209600 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 +--- minute +nanoseconds: 1209600000000000 +milliseconds: 1209600000 +seconds: 1209600 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 +--- hour +nanoseconds: 1209600000000000 +milliseconds: 1209600000 +seconds: 1209600 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 +--- day +nanoseconds: 1209600000000000 +milliseconds: 1209600000 +seconds: 1209600 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 +--- week +nanoseconds: 1209600000000000 +milliseconds: 1209600000 +seconds: 1209600 +minutes: 20160 +hours: 336 +days: 14 +weeks: 2 + +-/ +#guard_msgs in +#eval do + let nanosecond: Nanosecond.Offset := 1209600000000000 + println! s!"--- nanosecond" + println! s!"nanoseconds: {nanosecond}" + println! s!"milliseconds: {nanosecond.toMilliseconds}" + println! s!"seconds: {nanosecond.toSeconds}" + println! s!"minutes: {nanosecond.toMinutes}" + println! s!"hours: {nanosecond.toHours}" + println! s!"days: {nanosecond.toDays}" + println! s!"weeks: {nanosecond.toWeeks}" + -- Cannot do this for months or years because sizes are variable. + + let millisecond: Millisecond.Offset := 1209600000 + println! s!"--- millisecond" + println! s!"nanoseconds: {millisecond.toNanoseconds}" + println! s!"seconds: {millisecond.toSeconds}" + println! s!"milliseconds: {millisecond}" + println! s!"minutes: {millisecond.toMinutes}" + println! s!"hours: {millisecond.toHours}" + println! s!"days: {millisecond.toDays}" + println! s!"weeks: {millisecond.toWeeks}" + -- Cannot do this for months or years because sizes are variable. + + let second : Second.Offset := 1209600 + println! s!"--- second" + println! s!"nanoseconds: {second.toNanoseconds}" + println! s!"milliseconds: {second.toMilliseconds}" + println! s!"seconds: {second}" + println! s!"minutes: {second.toMinutes}" + println! s!"hours: {second.toHours}" + println! s!"days: {second.toDays}" + println! s!"weeks: {second.toWeeks}" + -- Cannot do this for months or years because sizes are variable. + + let minute: Minute.Offset := 20160 + println! s!"--- minute" + println! s!"nanoseconds: {minute.toNanoseconds}" + println! s!"milliseconds: {minute.toMilliseconds}" + println! s!"seconds: {minute.toSeconds}" + println! s!"minutes: {minute}" + println! s!"hours: {minute.toHours}" + println! s!"days: {minute.toDays}" + println! s!"weeks: {minute.toWeeks}" + -- Cannot do this for months or years because sizes are variable. + + let hour: Hour.Offset := 336 + println! s!"--- hour" + println! s!"nanoseconds: {hour.toNanoseconds}" + println! s!"milliseconds: {hour.toMilliseconds}" + println! s!"seconds: {hour.toSeconds}" + println! s!"minutes: {hour.toMinutes}" + println! s!"hours: {hour}" + println! s!"days: {hour.toDays}" + println! s!"weeks: {hour.toWeeks}" + -- Cannot do this for months or years because sizes are variable. + + let day: Day.Offset := 14 + println! s!"--- day" + println! s!"nanoseconds: {day.toNanoseconds}" + println! s!"milliseconds: {day.toMilliseconds}" + println! s!"seconds: {day.toSeconds}" + println! s!"minutes: {day.toMinutes}" + println! s!"hours: {day.toHours}" + println! s!"days: {day}" + println! s!"weeks: {day.toWeeks}" + -- Cannot do this for months or years because sizes are variable. + + let week: Week.Offset := 2 + println! s!"--- week" + println! s!"nanoseconds: {week.toNanoseconds}" + println! s!"milliseconds: {week.toMilliseconds}" + println! s!"seconds: {week.toSeconds}" + println! s!"minutes: {week.toMinutes}" + println! s!"hours: {week.toHours}" + println! s!"days: {week.toDays}" + println! s!"weeks: {week}" + -- Cannot do this for months or years because sizes are variable. + +theorem nano_nano_nano : (1 : Nanosecond.Offset) + (1 : Nanosecond.Offset) = (2 : Nanosecond.Offset) := rfl +theorem nano_milli_nano : (1 : Nanosecond.Offset) + (1 : Millisecond.Offset) = (1000001 : Nanosecond.Offset) := rfl +theorem nano_sec_nano : (1 : Nanosecond.Offset) + (1 : Second.Offset) = (1000000001 : Nanosecond.Offset) := rfl +theorem nano_min_nano : (1 : Nanosecond.Offset) + (1 : Minute.Offset) = (60000000001 : Nanosecond.Offset) := rfl +theorem nano_hour_nano : (1 : Nanosecond.Offset) + (1 : Hour.Offset) = (3600000000001 : Nanosecond.Offset) := rfl +theorem nano_day_nano : (1 : Nanosecond.Offset) + (1 : Day.Offset) = (86400000000001 : Nanosecond.Offset) := rfl +theorem nano_week_nano : (1 : Nanosecond.Offset) + (1 : Week.Offset) = (604800000000001 : Nanosecond.Offset) := rfl + +theorem milli_nano_nano : (1 : Millisecond.Offset) + (1 : Nanosecond.Offset) = (1000001 : Nanosecond.Offset) := rfl +theorem milli_milli_milli : (1 : Millisecond.Offset) + (1 : Millisecond.Offset) = (2 : Millisecond.Offset) := rfl +theorem milli_sec_milli : (1 : Millisecond.Offset) + (1 : Second.Offset) = (1001 : Millisecond.Offset) := rfl +theorem milli_min_milli : (1 : Millisecond.Offset) + (1 : Minute.Offset) = (60001 : Millisecond.Offset) := rfl +theorem milli_hour_milli : (1 : Millisecond.Offset) + (1 : Hour.Offset) = (3600001 : Millisecond.Offset) := rfl +theorem milli_day_milli : (1 : Millisecond.Offset) + (1 : Day.Offset) = (86400001 : Millisecond.Offset) := rfl +theorem milli_week_milli : (1 : Millisecond.Offset) + (1 : Week.Offset) = (604800001 : Millisecond.Offset) := rfl + +theorem sec_nano_nano : (1 : Second.Offset) + (1 : Nanosecond.Offset) = (1000000001 : Nanosecond.Offset) := rfl +theorem sec_milli_milli : (1 : Second.Offset) + (1 : Millisecond.Offset) = (1001 : Millisecond.Offset) := rfl +theorem sec_sec_sec : (1 : Second.Offset) + (1 : Second.Offset) = (2 : Second.Offset) := rfl +theorem sec_min_sec : (1 : Second.Offset) + (1 : Minute.Offset) = (61 : Second.Offset) := rfl +theorem sec_hour_sec : (1 : Second.Offset) + (1 : Hour.Offset) = (3601 : Second.Offset) := rfl +theorem sec_day_sec : (1 : Second.Offset) + (1 : Day.Offset) = (86401 : Second.Offset) := rfl +theorem sec_week_sec : (1 : Second.Offset) + (1 : Week.Offset) = (604801 : Second.Offset) := rfl + +theorem min_nano_nano : (1 : Minute.Offset) + (1 : Nanosecond.Offset) = (60000000001 : Nanosecond.Offset) := rfl +theorem min_milli_milli : (1 : Minute.Offset) + (1 : Millisecond.Offset) = (60001 : Millisecond.Offset) := rfl +theorem min_sec_sec : (1 : Minute.Offset) + (1 : Second.Offset) = (61 : Second.Offset) := rfl +theorem min_min_min : (1 : Minute.Offset) + (1 : Minute.Offset) = (2 : Minute.Offset) := rfl +theorem min_hour_min : (1 : Minute.Offset) + (1 : Hour.Offset) = (61 : Minute.Offset) := rfl +theorem min_day_min : (1 : Minute.Offset) + (1 : Day.Offset) = (1441 : Minute.Offset) := rfl +theorem min_week_min : (1 : Minute.Offset) + (1 : Week.Offset) = (10081 : Minute.Offset) := rfl + +theorem hour_nano_nano : (1 : Hour.Offset) + (1 : Nanosecond.Offset) = (3600000000001 : Nanosecond.Offset) := rfl +theorem hour_milli_milli : (1 : Hour.Offset) + (1 : Millisecond.Offset) = (3600001 : Millisecond.Offset) := rfl +theorem hour_sec_sec : (1 : Hour.Offset) + (1 : Second.Offset) = (3601 : Second.Offset) := rfl +theorem hour_min_min : (1 : Hour.Offset) + (1 : Minute.Offset) = (61 : Minute.Offset) := rfl +theorem hour_hour_hour : (1 : Hour.Offset) + (1 : Hour.Offset) = (2 : Hour.Offset) := rfl +theorem hour_day_hour : (1 : Hour.Offset) + (1 : Day.Offset) = (25 : Hour.Offset) := rfl +theorem hour_week_hour : (1 : Hour.Offset) + (1 : Week.Offset) = (169 : Hour.Offset) := rfl + +theorem day_nano_nano : (1 : Day.Offset) + (1 : Nanosecond.Offset) = (86400000000001 : Nanosecond.Offset) := rfl +theorem day_milli_milli : (1 : Day.Offset) + (1 : Millisecond.Offset) = (86400001 : Millisecond.Offset) := rfl +theorem day_sec_sec : (1 : Day.Offset) + (1 : Second.Offset) = (86401 : Second.Offset) := rfl +theorem day_min_min : (1 : Day.Offset) + (1 : Minute.Offset) = (1441 : Minute.Offset) := rfl +theorem day_hour_hour : (1 : Day.Offset) + (1 : Hour.Offset) = (25 : Hour.Offset) := rfl +theorem day_day_day : (1 : Day.Offset) + (1 : Day.Offset) = (2 : Day.Offset) := rfl +theorem day_week_day : (1 : Day.Offset) + (1 : Week.Offset) = (8 : Day.Offset) := rfl + +theorem week_nano_nano : (1 : Week.Offset) + (1 : Nanosecond.Offset) = (604800000000001 : Nanosecond.Offset) := rfl +theorem week_milli_milli : (1 : Week.Offset) + (1 : Millisecond.Offset) = (604800001 : Millisecond.Offset) := rfl +theorem week_sec_sec : (1 : Week.Offset) + (1 : Second.Offset) = (604801 : Second.Offset) := rfl +theorem week_min_min : (1 : Week.Offset) + (1 : Minute.Offset) = (10081 : Minute.Offset) := rfl +theorem week_hour_hour : (1 : Week.Offset) + (1 : Hour.Offset) = (169 : Hour.Offset) := rfl +theorem week_day_day : (1 : Week.Offset) + (1 : Day.Offset) = (8 : Day.Offset) := rfl +theorem week_week_week : (1 : Week.Offset) + (1 : Week.Offset) = (2 : Week.Offset) := rfl + +theorem nano_nano_nano_sub : (1 : Nanosecond.Offset) - (1 : Nanosecond.Offset) = (0 : Nanosecond.Offset) := rfl +theorem nano_milli_nano_sub : (1 : Nanosecond.Offset) - (1 : Millisecond.Offset) = (-999999 : Nanosecond.Offset) := rfl +theorem nano_sec_nano_sub : (1 : Nanosecond.Offset) - (1 : Second.Offset) = (-999999999 : Nanosecond.Offset) := rfl +theorem nano_min_nano_sub : (1 : Nanosecond.Offset) - (1 : Minute.Offset) = (-59999999999 : Nanosecond.Offset) := rfl +theorem nano_hour_nano_sub : (1 : Nanosecond.Offset) - (1 : Hour.Offset) = (-3599999999999 : Nanosecond.Offset) := rfl +theorem nano_day_nano_sub : (1 : Nanosecond.Offset) - (1 : Day.Offset) = (-86399999999999 : Nanosecond.Offset) := rfl +theorem nano_week_nano_sub : (1 : Nanosecond.Offset) - (1 : Week.Offset) = (-604799999999999 : Nanosecond.Offset) := rfl + +theorem milli_nano_nano_sub : (1 : Millisecond.Offset) - (1 : Nanosecond.Offset) = (999999 : Nanosecond.Offset) := rfl +theorem milli_milli_milli_sub : (1 : Millisecond.Offset) - (1 : Millisecond.Offset) = (0 : Millisecond.Offset) := rfl +theorem milli_sec_milli_sub : (1 : Millisecond.Offset) - (1 : Second.Offset) = (-999 : Millisecond.Offset) := rfl +theorem milli_min_milli_sub : (1 : Millisecond.Offset) - (1 : Minute.Offset) = (-59999 : Millisecond.Offset) := rfl +theorem milli_hour_milli_sub : (1 : Millisecond.Offset) - (1 : Hour.Offset) = (-3599999 : Millisecond.Offset) := rfl +theorem milli_day_milli_sub : (1 : Millisecond.Offset) - (1 : Day.Offset) = (-86399999 : Millisecond.Offset) := rfl +theorem milli_week_milli_sub : (1 : Millisecond.Offset) - (1 : Week.Offset) = (-604799999 : Millisecond.Offset) := rfl + +theorem sec_nano_nano_sub : (1 : Second.Offset) - (1 : Nanosecond.Offset) = (999999999 : Nanosecond.Offset) := rfl +theorem sec_milli_milli_sub : (1 : Second.Offset) - (1 : Millisecond.Offset) = (999 : Millisecond.Offset) := rfl +theorem sec_sec_sec_sub : (1 : Second.Offset) - (1 : Second.Offset) = (0 : Second.Offset) := rfl +theorem sec_min_sec_sub : (1 : Second.Offset) - (1 : Minute.Offset) = (-59 : Second.Offset) := rfl +theorem sec_hour_sec_sub : (1 : Second.Offset) - (1 : Hour.Offset) = (-3599 : Second.Offset) := rfl +theorem sec_day_sec_sub : (1 : Second.Offset) - (1 : Day.Offset) = (-86399 : Second.Offset) := rfl +theorem sec_week_sec_sub : (1 : Second.Offset) - (1 : Week.Offset) = (-604799 : Second.Offset) := rfl + +theorem min_nano_nano_sub : (1 : Minute.Offset) - (1 : Nanosecond.Offset) = (59999999999 : Nanosecond.Offset) := rfl +theorem min_milli_milli_sub : (1 : Minute.Offset) - (1 : Millisecond.Offset) = (59999 : Millisecond.Offset) := rfl +theorem min_sec_sec_sub : (1 : Minute.Offset) - (1 : Second.Offset) = (59 : Second.Offset) := rfl +theorem min_min_min_sub : (1 : Minute.Offset) - (1 : Minute.Offset) = (0 : Minute.Offset) := rfl +theorem min_hour_min_sub : (1 : Minute.Offset) - (1 : Hour.Offset) = (-59 : Minute.Offset) := rfl +theorem min_day_min_sub : (1 : Minute.Offset) - (1 : Day.Offset) = (-1439 : Minute.Offset) := rfl +theorem min_week_min_sub : (1 : Minute.Offset) - (1 : Week.Offset) = (-10079 : Minute.Offset) := rfl + +theorem hour_nano_nano_sub : (1 : Hour.Offset) - (1 : Nanosecond.Offset) = (3599999999999 : Nanosecond.Offset) := rfl +theorem hour_milli_milli_sub : (1 : Hour.Offset) - (1 : Millisecond.Offset) = (3599999 : Millisecond.Offset) := rfl +theorem hour_sec_sec_sub : (1 : Hour.Offset) - (1 : Second.Offset) = (3599 : Second.Offset) := rfl +theorem hour_min_min_sub : (1 : Hour.Offset) - (1 : Minute.Offset) = (59 : Minute.Offset) := rfl +theorem hour_hour_hour_sub : (1 : Hour.Offset) - (1 : Hour.Offset) = (0 : Hour.Offset) := rfl +theorem hour_day_hour_sub : (1 : Hour.Offset) - (1 : Day.Offset) = (-23 : Hour.Offset) := rfl +theorem hour_week_hour_sub : (1 : Hour.Offset) - (1 : Week.Offset) = (-167 : Hour.Offset) := rfl + +theorem day_nano_nano_sub : (1 : Day.Offset) - (1 : Nanosecond.Offset) = (86399999999999 : Nanosecond.Offset) := rfl +theorem day_milli_milli_sub : (1 : Day.Offset) - (1 : Millisecond.Offset) = (86399999 : Millisecond.Offset) := rfl +theorem day_sec_sec_sub : (1 : Day.Offset) - (1 : Second.Offset) = (86399 : Second.Offset) := rfl +theorem day_min_min_sub : (1 : Day.Offset) - (1 : Minute.Offset) = (1439 : Minute.Offset) := rfl +theorem day_hour_hour_sub : (1 : Day.Offset) - (1 : Hour.Offset) = (23 : Hour.Offset) := rfl +theorem day_day_day_sub : (1 : Day.Offset) - (1 : Day.Offset) = (0 : Day.Offset) := rfl +theorem day_week_day_sub : (1 : Day.Offset) - (1 : Week.Offset) = (-6 : Day.Offset) := rfl + +theorem week_nano_nano_sub : (1 : Week.Offset) - (1 : Nanosecond.Offset) = (604799999999999 : Nanosecond.Offset) := rfl +theorem week_milli_milli_sub : (1 : Week.Offset) - (1 : Millisecond.Offset) = (604799999 : Millisecond.Offset) := rfl +theorem week_sec_sec_sub : (1 : Week.Offset) - (1 : Second.Offset) = (604799 : Second.Offset) := rfl +theorem week_min_min_sub : (1 : Week.Offset) - (1 : Minute.Offset) = (10079 : Minute.Offset) := rfl +theorem week_hour_hour_sub : (1 : Week.Offset) - (1 : Hour.Offset) = (167 : Hour.Offset) := rfl +theorem week_day_day_sub : (1 : Week.Offset) - (1 : Day.Offset) = (6 : Day.Offset) := rfl +theorem week_week_week_sub : (1 : Week.Offset) - (1 : Week.Offset) = (0 : Week.Offset) := rfl + +/- +Of and To basic units +-/ + +example : (1 : Nanosecond.Offset).toInt = (1 : Int) := rfl +example : (1 : Millisecond.Offset).toInt = (1 : Int) := rfl +example : (1 : Second.Offset).toInt = (1 : Int) := rfl +example : (1 : Minute.Offset).toInt = (1 : Int) := rfl +example : (1 : Hour.Offset).toInt = (1 : Int) := rfl +example : (1 : Day.Offset).toInt = (1 : Int) := rfl +example : (1 : Week.Offset).toInt = (1 : Int) := rfl + +example : Nanosecond.Offset.ofInt 1 = (1 : Nanosecond.Offset) := rfl +example : Millisecond.Offset.ofInt 1 = (1 : Millisecond.Offset) := rfl +example : Second.Offset.ofInt 1 = (1 : Second.Offset) := rfl +example : Minute.Offset.ofInt 1 = (1 : Minute.Offset) := rfl +example : Hour.Offset.ofInt 1 = (1 : Hour.Offset) := rfl +example : Day.Offset.ofInt 1 = (1 : Day.Offset) := rfl +example : Week.Offset.ofInt 1 = (1 : Week.Offset) := rfl + +example : Nanosecond.Offset.ofNat 1 = (1 : Nanosecond.Offset) := rfl +example : Millisecond.Offset.ofNat 1 = (1 : Millisecond.Offset) := rfl +example : Second.Offset.ofNat 1 = (1 : Second.Offset) := rfl +example : Minute.Offset.ofNat 1 = (1 : Minute.Offset) := rfl +example : Hour.Offset.ofNat 1 = (1 : Hour.Offset) := rfl +example : Day.Offset.ofNat 1 = (1 : Day.Offset) := rfl +example : Week.Offset.ofNat 1 = (1 : Week.Offset) := rfl + +example := Nanosecond.Ordinal.ofInt 1 (by decide) +example := Millisecond.Ordinal.ofInt 1 (by decide) +example := Second.Ordinal.ofInt (leap := false) 59 (by decide) +example := Second.Ordinal.ofInt (leap := true) 60 (by decide) +example := Minute.Ordinal.ofInt 1 (by decide) +example := Hour.Ordinal.ofInt 1 (by decide) +example := Day.Ordinal.ofInt 1 (by decide) +example := Week.Ordinal.ofInt 1 (by decide) + +example := Nanosecond.Ordinal.ofFin 1 +example := Millisecond.Ordinal.ofFin 1 +example := Second.Ordinal.ofFin (leap := false) (Fin.ofNat 1) +example := Second.Ordinal.ofFin (leap := true) (Fin.ofNat 1) +example := Minute.Ordinal.ofFin 1 +example := Hour.Ordinal.ofFin 1 +example := Day.Ordinal.ofFin 1 +example := Week.Ordinal.ofFin 1 + +example := Nanosecond.Ordinal.ofNat 1 +example := Millisecond.Ordinal.ofNat 1 +example := Second.Ordinal.ofNat (leap := false) 1 +example := Second.Ordinal.ofNat (leap := true) 1 +example := Minute.Ordinal.ofNat 1 +example := Hour.Ordinal.ofNat 1 +example := Day.Ordinal.ofNat 1 +example := Week.Ordinal.ofNat 1 + +example := Nanosecond.Ordinal.toOffset 1 +example := Millisecond.Ordinal.toOffset 1 +example := Second.Ordinal.toOffset (leap := false) 1 +example := Second.Ordinal.toOffset (leap := true) 1 +example := Minute.Ordinal.toOffset 1 +example := Hour.Ordinal.toOffset 1 +example := Day.Ordinal.toOffset 1 +example := Week.Ordinal.toOffset 1 + +example : (1 : Nanosecond.Ordinal).toInt = (1 : Int) := rfl +example : (1 : Millisecond.Ordinal).toInt = (1 : Int) := rfl +example : (1 : Second.Ordinal false).toInt = (1 : Int) := rfl +example : (1 : Second.Ordinal true).toInt = (1 : Int) := rfl +example : (1 : Minute.Ordinal).toInt = (1 : Int) := rfl +example : (1 : Hour.Ordinal).toInt = (1 : Int) := rfl +example : (1 : Day.Ordinal).toInt = (1 : Int) := rfl +example : (1 : Week.Ordinal).toInt = (1 : Int) := rfl + +example : ((1 : Nanosecond.Ordinal).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Millisecond.Ordinal).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Second.Ordinal false).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Second.Ordinal true).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Minute.Ordinal).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Hour.Ordinal).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Day.Ordinal).toFin (by decide) |>.val) = 1 := rfl +example : ((1 : Week.Ordinal).toFin (by decide) |>.val) = 1 := rfl + +example : (1 : Nanosecond.Ordinal).toNat = 1 := rfl +example : (1 : Millisecond.Ordinal).toNat = 1 := rfl +example : (1 : Second.Ordinal false).toNat = 1 := rfl +example : (1 : Second.Ordinal true).toNat = 1 := rfl +example : (1 : Minute.Ordinal).toNat = 1 := rfl +example : (1 : Hour.Ordinal).toNat = 1 := rfl +example : (1 : Day.Ordinal).toNat = 1 := rfl +example : (1 : Week.Ordinal).toNat = 1 := rfl + +/-- +info: 9 +2023-06-10 +2023-06-16 +2023-07-09 +2023-07-09 +2024-06-09 +2024-06-09 +2023-06-08 +2023-06-02 +2023-05-09 +2023-05-09 +2022-06-09 +2022-06-09 +2023-06-01 +2023-06-01 +2023-06-11 +2023-01-09 +2023-01-09 +0001-06-09 +0001-06-09 +23 2023 2023 2023 23 2023 2023 23 J +false +160 +CE +2 +2 +Std.Time.Weekday.friday +23 +2 +06-09-2023 +2023-06-09 +2023-06-09 +19517 +1686268800000 +1970-01-02 + +-/ +#guard_msgs in +#eval do + -- :> + let date := date("2023-06-09") + + println! repr date.day + println! date.addDays 1 + println! date.addWeeks 1 + println! date.addMonthsClip 1 + println! date.addMonthsRollOver 1 + println! date.addYearsClip 1 + println! date.addYearsRollOver 1 + + println! date.subDays 1 + println! date.subWeeks 1 + println! date.subMonthsClip 1 + println! date.subMonthsRollOver 1 + println! date.subYearsClip 1 + println! date.subYearsRollOver 1 + + println! date.withDaysClip 1 + println! date.withDaysRollOver 1 + println! date.withWeekday .sunday + println! date.withMonthClip 1 + println! date.withMonthRollOver 1 + println! date.withYearClip 1 + println! date.withYearRollOver 1 + + println! date.format "yy yyyy yyy yyy yy uuuu uuu uu MMMMM" + println! date.inLeapYear + println! date.dayOfYear + println! date.era + println! repr date.quarter + println! repr date.alignedWeekOfMonth + println! repr date.weekday + println! repr date.weekOfYear + println! repr date.weekOfMonth + + println! date.toAmericanDateString + println! date.toLeanDateString + println! date.toSQLDateString + + println! date.toDaysSinceUNIXEpoch + println! date.toTimestampAssumingUTC + println! PlainDate.ofDaysSinceUNIXEpoch 1 + +/-- +info: 1997-03-19T02:03:04.000000000 +1997-03-25T02:03:04.000000000 +1997-04-18T02:03:04.000000000 +1997-04-18T02:03:04.000000000 +1998-03-18T02:03:04.000000000 +1998-03-18T02:03:04.000000000 +1997-03-17T02:03:04.000000000 +1997-03-11T02:03:04.000000000 +1997-02-18T02:03:04.000000000 +1997-02-18T02:03:04.000000000 +1996-03-18T02:03:04.000000000 +1996-03-18T02:03:04.000000000 +1997-03-01T02:03:04.000000000 +1997-03-01T02:03:04.000000000 +1997-03-23T02:03:04.000000000 +1997-01-18T02:03:04.000000000 +1997-01-18T02:03:04.000000000 +0001-03-18T02:03:04.000000000 +0001-03-18T02:03:04.000000000 +97 1997 1997 1997 97 1997 1997 97 M +false +77 +CE +1 +4 +Std.Time.Weekday.tuesday +12 +3 +9938 +858650584000 +1970-01-02T00:00:00.000000000 + +-/ +#guard_msgs in +#eval do + let plaindatetime := datetime("1997-03-18T02:03:04") + + println! plaindatetime.addDays 1 + println! plaindatetime.addWeeks 1 + println! plaindatetime.addMonthsClip 1 + println! plaindatetime.addMonthsRollOver 1 + println! plaindatetime.addYearsClip 1 + println! plaindatetime.addYearsRollOver 1 + + println! plaindatetime.subDays 1 + println! plaindatetime.subWeeks 1 + println! plaindatetime.subMonthsClip 1 + println! plaindatetime.subMonthsRollOver 1 + println! plaindatetime.subYearsClip 1 + println! plaindatetime.subYearsRollOver 1 + + println! plaindatetime.withDaysClip 1 + println! plaindatetime.withDaysRollOver 1 + println! plaindatetime.withWeekday .sunday + println! plaindatetime.withMonthClip 1 + println! plaindatetime.withMonthRollOver 1 + println! plaindatetime.withYearClip 1 + println! plaindatetime.withYearRollOver 1 + + println! plaindatetime.format "yy yyyy yyy yyy yy uuuu uuu uu MMMMM" + println! plaindatetime.inLeapYear + println! plaindatetime.dayOfYear + println! plaindatetime.era + println! repr plaindatetime.quarter + println! repr plaindatetime.alignedWeekOfMonth + println! repr plaindatetime.weekday + println! repr plaindatetime.weekOfYear + println! repr plaindatetime.weekOfMonth + + println! plaindatetime.toDaysSinceUNIXEpoch + println! plaindatetime.toTimestampAssumingUTC + println! PlainDateTime.ofDaysSinceUNIXEpoch 1 PlainTime.midnight + +/-- +info: 2024-09-13T02:01:02.000000000-03:00 +2024-09-19T02:01:02.000000000-03:00 +2024-10-12T02:01:02.000000000-03:00 +2024-10-12T02:01:02.000000000-03:00 +2025-09-12T02:01:02.000000000-03:00 +2025-09-12T02:01:02.000000000-03:00 +2024-09-11T02:01:02.000000000-03:00 +2024-09-05T02:01:02.000000000-03:00 +2024-08-12T02:01:02.000000000-03:00 +2024-08-12T02:01:02.000000000-03:00 +2023-09-12T02:01:02.000000000-03:00 +2023-09-12T02:01:02.000000000-03:00 +2024-09-01T02:01:02.000000000-03:00 +2024-09-01T02:01:02.000000000-03:00 +2024-09-15T02:01:02.000000000-03:00 +2024-01-12T02:01:02.000000000-03:00 +2024-01-12T02:01:02.000000000-03:00 +0001-09-12T02:01:02.000000000-03:00 +0001-09-12T02:01:02.000000000-03:00 +24 2024 2024 2024 24 2024 2024 24 S +true +256 +CE +3 +3 +Std.Time.Weekday.thursday +37 +2 +19978 +1726117262000 +1970-01-02T00:00:00.000000000Z + +-/ +#guard_msgs in +#eval do + let zoned := DateTime.ofPlainDateTimeAssumingUTC datetime("2024-09-12T05:01:02") timezone("America/Sao_Paulo -03:00") + + println! zoned.addDays 1 + println! zoned.addWeeks 1 + println! zoned.addMonthsClip 1 + println! zoned.addMonthsRollOver 1 + println! zoned.addYearsClip 1 + println! zoned.addYearsRollOver 1 + + println! zoned.subDays 1 + println! zoned.subWeeks 1 + println! zoned.subMonthsClip 1 + println! zoned.subMonthsRollOver 1 + println! zoned.subYearsClip 1 + println! zoned.subYearsRollOver 1 + + println! zoned.withDaysClip 1 + println! zoned.withDaysRollOver 1 + println! zoned.withWeekday .sunday + println! zoned.withMonthClip 1 + println! zoned.withMonthRollOver 1 + println! zoned.withYearClip 1 + println! zoned.withYearRollOver 1 + + println! zoned.format "yy yyyy yyy yyy yy uuuu uuu uu MMMMM" + println! zoned.inLeapYear + println! zoned.dayOfYear + println! zoned.era + println! repr zoned.quarter + println! repr zoned.alignedWeekOfMonth + println! repr zoned.weekday + println! repr zoned.weekOfYear + println! repr zoned.weekOfMonth + + println! zoned.toDaysSinceUNIXEpoch + println! zoned.toTimestamp + println! DateTime.ofDaysSinceUNIXEpoch 1 PlainTime.midnight .UTC + +/-- +info: 1997-03-19T02:03:04.000000000[America/Sao_Paulo] +1997-03-25T02:03:04.000000000[America/Sao_Paulo] +1997-04-18T02:03:04.000000000[America/Sao_Paulo] +1997-04-18T02:03:04.000000000[America/Sao_Paulo] +1998-03-18T02:03:04.000000000[America/Sao_Paulo] +1998-03-18T02:03:04.000000000[America/Sao_Paulo] +1997-03-17T02:03:04.000000000[America/Sao_Paulo] +1997-03-17T02:03:04.000000000[America/Sao_Paulo] +1997-03-11T02:03:04.000000000[America/Sao_Paulo] +1997-02-18T02:03:04.000000000[America/Sao_Paulo] +1997-02-18T02:03:04.000000000[America/Sao_Paulo] +1996-03-18T02:03:04.000000000[America/Sao_Paulo] +1996-03-18T02:03:04.000000000[America/Sao_Paulo] +1997-03-01T02:03:04.000000000[America/Sao_Paulo] +1997-03-01T02:03:04.000000000[America/Sao_Paulo] +1997-03-23T02:03:04.000000000[America/Sao_Paulo] +1997-01-18T02:03:04.000000000[America/Sao_Paulo] +1997-01-18T02:03:04.000000000[America/Sao_Paulo] +0001-03-17T02:03:04.000000000[America/Sao_Paulo] +0001-03-17T02:03:04.000000000[America/Sao_Paulo] +97 1997 1997 1997 97 1997 1997 97 M +false +77 +CE +1 +4 +Std.Time.Weekday.tuesday +12 +3 +9938 +858661384000 + +-/ +#guard_msgs in +#eval do + let zoned := zoned("1997-03-18T02:03:04", sao_paulo) + + println! zoned.addDays 1 + println! zoned.addWeeks 1 + println! zoned.addMonthsClip 1 + println! zoned.addMonthsRollOver 1 + println! zoned.addYearsClip 1 + println! zoned.addYearsRollOver 1 + + println! zoned.subDays 1 + println! zoned.subDays 1 + println! zoned.subWeeks 1 + println! zoned.subMonthsClip 1 + println! zoned.subMonthsRollOver 1 + println! zoned.subYearsClip 1 + println! zoned.subYearsRollOver 1 + + println! zoned.withDaysClip 1 + println! zoned.withDaysRollOver 1 + println! zoned.withWeekday .sunday + println! zoned.withMonthClip 1 + println! zoned.withMonthRollOver 1 + println! zoned.withYearClip 1 + println! zoned.withYearRollOver 1 + + println! zoned.format "yy yyyy yyy yyy yy uuuu uuu uu MMMMM" + println! zoned.inLeapYear + println! zoned.dayOfYear + println! zoned.era + println! repr zoned.quarter + println! repr zoned.alignedWeekOfMonth + println! repr zoned.weekday + println! repr zoned.weekOfYear + println! repr zoned.weekOfMonth + + println! zoned.toDaysSinceUNIXEpoch + println! zoned.toTimestamp + +/-- +info: 2023-06-09T00:00:00.000000000 +0001-01-01T12:32:43.000000000 +2033-11-18T12:32:43.000000000 +-/ +#guard_msgs in +#eval do + println! PlainDateTime.ofPlainDate date("2023-06-09") + println! PlainDateTime.ofPlainTime time("12:32:43") + println! PlainDateTime.ofDaysSinceUNIXEpoch 23332 time("12:32:43") + +/-- +info: 1970-01-02T00:00:00.000000000Z +1997-03-18T00:00:00.000000000Z +1997-03-18T00:01:02.000000000Z +1997-03-18T00:01:02.000000000Z +2024-02-16T22:07:14.000000000Z + +-/ +#guard_msgs in +#eval do + println! DateTime.ofDaysSinceUNIXEpoch 1 PlainTime.midnight .UTC + println! DateTime.ofPlainDate date("1997-03-18") .UTC + println! DateTime.ofPlainDateTime datetime("1997-03-18T00:01:02") .UTC + println! DateTime.ofPlainDateTimeAssumingUTC datetime("1997-03-18T00:01:02") .UTC + println! DateTime.ofTimestamp 1708121234 .UTC + +/-- +info: 1970-01-02T00:00:00.000000000[UTC] +1997-03-18T00:00:00.000000000[UTC] +1997-03-18T00:00:00.000000000[UTC] +1997-03-18T00:01:02.000000000[UTC] +1997-03-18T00:01:02.000000000[UTC] +1997-03-18T00:01:02.000000000[UTC] +2024-02-16T22:07:14.000000000[UTC] +2024-02-16T22:07:14.000000000[UTC] +-/ +#guard_msgs in +#eval do + println! ZonedDateTime.ofDaysSinceUNIXEpoch 1 PlainTime.midnight .UTC + println! ZonedDateTime.ofPlainDate date("1997-03-18") .UTC + println! ZonedDateTime.ofPlainDateWithZone date("1997-03-18") .UTC + println! ZonedDateTime.ofPlainDateTime datetime("1997-03-18T00:01:02") .UTC + println! ZonedDateTime.ofPlainDateTimeAssumingUTC datetime("1997-03-18T00:01:02") .UTC + println! ZonedDateTime.ofPlainDateTimeWithZone datetime("1997-03-18T00:01:02") .UTC + println! ZonedDateTime.ofTimestamp 1708121234 .UTC + println! ZonedDateTime.ofTimestampWithZone 1708121234 .UTC diff --git a/tests/lean/run/timeClassOperations.lean b/tests/lean/run/timeClassOperations.lean new file mode 100644 index 000000000000..39f1967df911 --- /dev/null +++ b/tests/lean/run/timeClassOperations.lean @@ -0,0 +1,188 @@ +import Std.Time +open Std.Time + +def date := date("1970-01-20") + +/-- +info: date("1970-02-01") +-/ +#guard_msgs in +#eval date + (12 : Day.Offset) + +/-- +info: date("1970-01-08") +-/ +#guard_msgs in +#eval date - (12 : Day.Offset) + +def datetime := datetime("2000-01-20T03:02:01") + +/-- +info: datetime("2000-01-20T04:02:01.000000000") +-/ +#guard_msgs in +#eval datetime + (1 : Hour.Offset) + +/-- +info: datetime("2000-01-20T02:02:01.000000000") +-/ +#guard_msgs in +#eval datetime - (1 : Hour.Offset) + +/-- +info: datetime("2000-01-20T03:12:01.000000000") +-/ +#guard_msgs in +#eval datetime + (10 : Minute.Offset) + +/-- +info: datetime("2000-01-20T02:52:01.000000000") +-/ +#guard_msgs in +#eval datetime - (10 : Minute.Offset) + +/-- +info: datetime("2000-01-20T03:03:01.000000000") +-/ +#guard_msgs in +#eval datetime + (60 : Second.Offset) + +/-- +info: datetime("2000-01-20T03:01:01.000000000") +-/ +#guard_msgs in +#eval datetime - (60 : Second.Offset) + +/-- +info: datetime("2000-01-21T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime + (1 : Day.Offset) + +/-- +info: datetime("2000-01-19T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime - (1 : Day.Offset) + +def time := time("13:02:01") + +/-- +info: time("14:02:01.000000000") +-/ +#guard_msgs in +#eval time + (1 : Hour.Offset) + +/-- +info: time("12:02:01.000000000") +-/ +#guard_msgs in +#eval time - (1 : Hour.Offset) + +/-- +info: time("13:12:01.000000000") +-/ +#guard_msgs in +#eval time + (10 : Minute.Offset) + +/-- +info: time("12:52:01.000000000") +-/ +#guard_msgs in +#eval time - (10 : Minute.Offset) + +/-- +info: time("13:03:01.000000000") +-/ +#guard_msgs in +#eval time + (60 : Second.Offset) + +/-- +info: time("13:01:01.000000000") +-/ +#guard_msgs in +#eval time - (60 : Second.Offset) + +def datetimetz := zoned("2000-01-20T06:02:01-03:00") + +/-- +info: zoned("2000-01-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz + +/-- +info: zoned("2000-01-22T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz + (2 : Day.Offset) + +/-- +info: zoned("2000-01-19T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz - (1 : Day.Offset) + +/-- +info: zoned("2000-01-20T07:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz + (1 : Hour.Offset) + +/-- +info: zoned("2000-01-20T05:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz - (1 : Hour.Offset) + +/-- +info: zoned("2000-01-20T06:12:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz + (10 : Minute.Offset) + +/-- +info: zoned("2000-01-20T05:52:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz - (10 : Minute.Offset) + +/-- +info: zoned("2000-01-20T06:03:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz + (60 : Second.Offset) + +/-- +info: zoned("2000-01-20T06:01:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz - (60 : Second.Offset) + +/-- +info: "3600s" +-/ +#guard_msgs in +#eval zoned("2000-12-20T00:00:00-03:00") - zoned("2000-12-20T00:00:00-02:00") + +def now := PlainDateTime.ofTimestampAssumingUTC ⟨1724859638, ⟨907328169, by decide⟩, by decide⟩ +def after := PlainDateTime.ofTimestampAssumingUTC ⟨1724859639, ⟨907641224, by decide⟩, by decide⟩ +def duration := after - now + +/-- +info: "15:40:38.907328169" +-/ +#guard_msgs in +#eval now.format "HH:mm:ss.SSSSSSSSS" + +/-- +info: "15.40:39.907641224" +-/ +#guard_msgs in +#eval after.format "HH.mm:ss.SSSSSSSSS" + +/-- +info: "1.000313055s" +-/ +#guard_msgs in +#eval duration diff --git a/tests/lean/run/timeFormats.lean b/tests/lean/run/timeFormats.lean new file mode 100644 index 000000000000..31be8b6faf55 --- /dev/null +++ b/tests/lean/run/timeFormats.lean @@ -0,0 +1,812 @@ +import Std.Time +open Std.Time + +def ISO8601UTC : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSZ") +def RFC1123 : GenericFormat .any := datespec("eee, dd MMM uuuu HH:mm:ss ZZZ") +def ShortDate : GenericFormat .any := datespec("MM/dd/uuuu") +def LongDate : GenericFormat .any := datespec("MMMM D, uuuu") +def ShortDateTime : GenericFormat .any := datespec("MM/dd/uuuu HH:mm:ss") +def LongDateTime : GenericFormat .any := datespec("MMMM D, uuuu h:mm aa") +def Time24Hour : GenericFormat .any := datespec("HH:mm:ss") +def Time12Hour : GenericFormat .any := datespec("hh:mm:ss aa") +def FullDayTimeZone : GenericFormat .any := datespec("EEEE, MMMM dd, uuuu HH:mm:ss ZZZ") +def CustomDayTime : GenericFormat .any := datespec("EEE dd MMM uuuu HH:mm") +def EraDate : GenericFormat .any := datespec("MM D, uuuu G") + +-- Dates + +def brTZ : TimeZone := timezone("America/Sao_Paulo -03:00") +def jpTZ : TimeZone := timezone("Asia/Tokyo +09:00") + +def date₁ := zoned("2014-06-16T03:03:03-03:00") + +def time₁ := time("14:11:01") +def time₂ := time("03:11:01") + +/-- +info: "Monday, June 16, 2014 03:03:03 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format date₁.toDateTime + +def tm := date₁.toTimestamp +def date₂ := DateTime.ofTimestamp tm brTZ + +/-- +info: "Monday, June 16, 2014 03:03:03 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format date₂ + +def tm₃ := date₁.toTimestamp +def date₃ := DateTime.ofTimestamp tm₃ brTZ + +/-- +info: "Monday, June 16, 2014 03:03:03 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format date₃ + +-- Section for testing timezone conversion. + +-- the timestamp is always related to UTC. + +/-- +Timestamp: 1723739292 +GMT: Thursday, 15 August 2024 16:28:12 +BR: 15 August 2024 13:28:12 GMT-03:00 +-/ +def tm₄ : Second.Offset := 1723739292 + +def dateBR := DateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch tm₄) brTZ +def dateJP := DateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch tm₄) jpTZ +def dateUTC := DateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch tm₄) .UTC + +/-- +info: "Thursday, August 15, 2024 13:28:12 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format dateBR + +/-- +info: "Friday, August 16, 2024 01:28:12 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format dateJP + +/-- +info: "Thursday, August 15, 2024 13:28:12 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateUTC.convertTimeZone brTZ) + +/-- +info: "Thursday, August 15, 2024 13:28:12 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateJP.convertTimeZone brTZ) + +/-- +info: "Thursday, August 15, 2024 16:28:12 +0000" +-/ +#guard_msgs in +#eval FullDayTimeZone.format dateUTC + +/-- +info: "Thursday, August 15, 2024 16:28:12 +0000" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateBR.convertTimeZone .UTC) + +/-- +info: "Thursday, August 15, 2024 16:28:12 +0000" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateJP.convertTimeZone .UTC) + +/-- +info: "Friday, August 16, 2024 01:28:12 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format dateJP + +/-- +info: "Friday, August 16, 2024 01:28:12 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateBR.convertTimeZone jpTZ) + +/-- +info: "Friday, August 16, 2024 01:28:12 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateUTC.convertTimeZone jpTZ) + +/-- +TM: 1723730627 +GMT: Thursday, 15 August 2024 14:03:47 +Your time zone: 15 August 2024 11:03:47 GMT-03:00 +-/ +def localTm : Second.Offset := 1723730627 + +/-- +This PlainDate is relative to the local time. +-/ +def PlainDate : PlainDateTime := Timestamp.toPlainDateTimeAssumingUTC (Timestamp.ofSecondsSinceUnixEpoch localTm) + +def dateBR₁ := DateTime.ofPlainDateTime PlainDate brTZ +def dateJP₁ := DateTime.ofPlainDateTime PlainDate jpTZ +def dateUTC₁ := DateTime.ofPlainDateTime PlainDate .UTC + +/-- +info: "Thursday, August 15, 2024 14:03:47 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format dateBR₁ + +/-- +info: "Thursday, August 15, 2024 14:03:47 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format dateJP₁ + +/-- +info: "Thursday, August 15, 2024 23:03:47 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateUTC₁.convertTimeZone jpTZ) + +/-- +info: "Friday, August 16, 2024 02:03:47 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateBR₁.convertTimeZone jpTZ) + +/-- +info: "Thursday, August 15, 2024 14:03:47 +0900" +-/ +#guard_msgs in +#eval FullDayTimeZone.format (dateJP₁.convertTimeZone jpTZ) + +/-- +info: "Monday, June 16, 2014 03:03:03 -0300" +-/ +#guard_msgs in +#eval FullDayTimeZone.format date₂ + +/-- +info: "14:11:01" +-/ +#guard_msgs in +#eval Time24Hour.formatBuilder time₁.hour time₁.minute time₁.second + +def l := Time12Hour.formatBuilder time₁.hour.toRelative time₁.minute time₁.second (if time₁.hour.val > 12 then HourMarker.pm else HourMarker.am) + +/-- +info: "02:11:01 PM" +-/ +#guard_msgs in +#eval l +/-- +info: "03:11:01 AM" +-/ +#guard_msgs in +#eval Time12Hour.formatBuilder time₂.hour.toRelative time₂.minute time₂.second (if time₂.hour.val > 12 then HourMarker.pm else HourMarker.am) + +/-- +info: "06/16/2014" +-/ +#guard_msgs in +#eval ShortDate.formatBuilder date₁.month date₁.day date₁.year + +/-- +info: "0053-06-19" +-/ +#guard_msgs in +#eval Formats.sqlDate.format (DateTime.ofPlainDate (PlainDate.ofDaysSinceUNIXEpoch ⟨-700000⟩) .UTC) + +/-- +info: "-0002-09-16" +-/ +#guard_msgs in +#eval Formats.sqlDate.format (DateTime.ofPlainDate (PlainDate.ofDaysSinceUNIXEpoch ⟨-720000⟩) .UTC) + +/-- +info: "-0084-07-28" +-/ +#guard_msgs in +#eval Formats.sqlDate.format (DateTime.ofPlainDate (PlainDate.ofDaysSinceUNIXEpoch ⟨-750000⟩) .UTC) + +/-- +info: "-0221-09-04" +-/ +#guard_msgs in +#eval Formats.sqlDate.format (DateTime.ofPlainDate (PlainDate.ofDaysSinceUNIXEpoch ⟨-800000⟩) .UTC) + +/-- +info: date("-0221-09-04") +-/ +#guard_msgs in +#eval PlainDate.ofDaysSinceUNIXEpoch ⟨-800000⟩ + +/-- +info: date("-0221-09-04") +-/ +#guard_msgs in +#eval PlainDate.ofDaysSinceUNIXEpoch ⟨-800000⟩ + +/-- +info: date("2002-07-14") +-/ +#guard_msgs in +#eval date("2002-07-14") + +/-- +info: time("14:13:12.000000000") +-/ +#guard_msgs in +#eval time("14:13:12") + +/-- +info: zoned("2002-07-14T14:13:12.000000000Z") +-/ +#guard_msgs in +#eval zoned("2002-07-14T14:13:12Z") + +/-- +info: zoned("2002-07-14T14:13:12.000000000+09:00") +-/ +#guard_msgs in +#eval zoned("2002-07-14T14:13:12+09:00") + +/-- +info: "2002-07-14" +-/ +#guard_msgs in +#eval zoned("2002-07-14T14:13:12+09:00").format "uuuu-MM-dd" + +/-- +info: "14-13-12" +-/ +#guard_msgs in +#eval zoned("2002-07-14T14:13:12+09:00").format "HH-mm-ss" + +/- +Format +-/ + +def time₄ := time("23:13:12.324354679") +def date₄ := date("2002-07-14") +def datetime₅ := PlainDateTime.mk (PlainDate.ofYearMonthDayClip (-2000) 3 4) (PlainTime.mk 12 23 ⟨false, 12⟩ 0) +def datetime₄ := datetime("2002-07-14T23:13:12.324354679") +def zoned₄ := zoned("2002-07-14T23:13:12.324354679+09:00") +def zoned₅ := zoned("2002-07-14T23:13:12.324354679+00:00") +def tz : TimeZone := { offset := { second := -3600 }, name := "America/Sao_Paulo", abbreviation := "BRT", isDST := false} +def zoned₆ := ZonedDateTime.ofPlainDateTime (zoned₄.toPlainDateTime) (TimeZone.ZoneRules.ofTimeZone tz) + +/-- +info: "CE CE CE Common Era C" +-/ +#guard_msgs in +#eval zoned₄.format "G GG GGG GGGG GGGGG" + +/-- +info: "02 2002 000002002" +-/ +#guard_msgs in +#eval zoned₄.format "yy yyyy yyyyyyyyy" + +/-- +info: "02 2002 000002002" +-/ +#guard_msgs in +#eval zoned₄.format "uu uuuu uuuuuuuuu" + +/-- +info: "195 195 195" +-/ +#guard_msgs in +#eval zoned₄.format "D DD DDD" + +/-- +info: "14 14 014 0014 00014" +-/ +#guard_msgs in +#eval zoned₄.format "d dd ddd dddd ddddd" + +/-- +info: "7 07 Jul July J" +-/ +#guard_msgs in +#eval zoned₄.format "M MM MMM MMMM MMMMM" + +/-- +info: "3 03 3rd quarter 3" +-/ +#guard_msgs in +#eval zoned₄.format "Q QQ QQQQ QQQQQ" + +/-- +info: "28 28 028 0028" +-/ +#guard_msgs in +#eval zoned₄.format "w ww www wwww" + +/-- +info: "2 02 002 0002" +-/ +#guard_msgs in +#eval zoned₄.format "W WW WWW WWWW" + +/-- +info: "Sun Sun Sun Sunday S" +-/ +#guard_msgs in +#eval zoned₄.format "E EE EEE EEEE EEEEE" + +/-- +info: "7 07 Sun Sunday S" +-/ +#guard_msgs in +#eval zoned₄.format "e ee eee eeee eeeee" + +/-- +info: "2 02 002 0002" +-/ +#guard_msgs in +#eval zoned₄.format "F FF FFF FFFF" + +/-- +info: "11 11 011 0011 0011" +-/ +#guard_msgs in +#eval zoned₄.format "h hh hhh hhhh hhhh" + +/-- +info: "11 11 011 0011 000011" +-/ +#guard_msgs in +#eval zoned₄.format "K KK KKK KKKK KKKKKK" + +/-- +info: "23 23 023 0023 000023" +-/ +#guard_msgs in +#eval zoned₄.format "k kk kkk kkkk kkkkkk" + +/-- +info: "23 23 023 0023 00023" +-/ +#guard_msgs in +#eval zoned₄.format "H HH HHH HHHH HHHHH" + +/-- +info: "13 13 013 0013 00013" +-/ +#guard_msgs in +#eval zoned₄.format "m mm mmm mmmm mmmmm" + +/-- +info: "12 12 012 0012 00012" +-/ +#guard_msgs in +#eval zoned₄.format "s ss sss ssss sssss" + + +/-- +info: "3 32 324 3243 32435" +-/#guard_msgs in +#eval zoned₄.format "S SS SSS SSSS SSSSS" + +/-- +info: "83592324 83592324 83592324 83592324 083592324" +-/ +#guard_msgs in +#eval zoned₄.format "A AA AAA AAAA AAAAAAAAA" + +/-- +info: "324354679 324354679 324354679 324354679 324354679" +-/ +#guard_msgs in +#eval zoned₄.format "n nn nnn nnnn nnnnnnnnn" + +/-- +info: "83592324354679 83592324354679 83592324354679 83592324354679 83592324354679" +-/ +#guard_msgs in +#eval zoned₄.format "N NN NNN NNNN NNNNNNNNN" + +/-- +info: "+09:00" +-/ +#guard_msgs in +#eval zoned₄.format "VV" + +/-- +info: "+09:00 +09:00 +09:00 +09:00" +-/ +#guard_msgs in +#eval zoned₄.format "z zz zzzz zzzz" + +/-- +info: "+00:00 +00:00 +00:00 +00:00" +-/ +#guard_msgs in +#eval zoned₅.format "z zz zzzz zzzz" + +/-- +info: "GMT+9 GMT+09:00" +-/ +#guard_msgs in +#eval zoned₄.format "O OOOO" + +/-- +info: "+09 +0900 +09:00 +0900 +09:00" +-/ +#guard_msgs in +#eval zoned₄.format "X XX XXX XXXX XXXXX" + +/-- +info: "+09 +0900 +09:00 +0900 +09:00" +-/ +#guard_msgs in +#eval zoned₄.format "x xx xxx xxxx xxxxx" + +/-- +info: "Z Z Z Z Z" +-/ +#guard_msgs in +#eval zoned₅.format "X XX XXX XXXX XXXXX" + +/-- +info: "+00 +0000 +00:00 +0000 +00:00" +-/ +#guard_msgs in +#eval zoned₅.format "x xx xxx xxxx xxxxx" + +/-- +info: "+0900 +0900 +0900 GMT+09:00 +09:00" +-/ +#guard_msgs in +#eval zoned₄.format "Z ZZ ZZZ ZZZZ ZZZZZ" + +/-- +info: "CE CE CE Common Era C" +-/ +#guard_msgs in +#eval datetime₄.format "G GG GGG GGGG GGGGG" + +/-- +info: "02 2002 000002002" +-/ +#guard_msgs in +#eval datetime₄.format "yy yyyy yyyyyyyyy" + +/-- +info: "02 2002 000002002" +-/ +#guard_msgs in +#eval datetime₄.format "uu uuuu uuuuuuuuu" + +/-- +info: "195 195 195" +-/ +#guard_msgs in +#eval datetime₄.format "D DD DDD" + +/-- +info: "7 07 Jul J" +-/ +#guard_msgs in +#eval datetime₄.format "M MM MMM MMMMM" + +/-- +info: "14 14 014 0014 00014" +-/ +#guard_msgs in +#eval datetime₄.format "d dd ddd dddd ddddd" + +/-- +info: "7 07 Jul July J" +-/ +#guard_msgs in +#eval datetime₄.format "M MM MMM MMMM MMMMM" + +/-- +info: "14 14 0014 0014" +-/#guard_msgs in +#eval datetime₄.format "d dd dddd dddd" + +/-- +info: "3 03 3rd quarter 3" +-/ +#guard_msgs in +#eval datetime₄.format "Q QQ QQQQ QQQQQ" + +/-- +info: "28 28 028 0028" +-/ +#guard_msgs in +#eval datetime₄.format "w ww www wwww" + +/-- +info: "2 02 002 0002" +-/ +#guard_msgs in +#eval datetime₄.format "W WW WWW WWWW" + +/-- +info: "Sun Sun Sun Sunday S" +-/ +#guard_msgs in +#eval datetime₄.format "E EE EEE EEEE EEEEE" + +/-- +info: "7 07 Sun Sunday S" +-/ +#guard_msgs in +#eval datetime₄.format "e ee eee eeee eeeee" + +/-- +info: "2 02 002 0002" +-/ +#guard_msgs in +#eval datetime₄.format "F FF FFF FFFF" + +/-- +info: "11 11 011 0011 0011" +-/ +#guard_msgs in +#eval datetime₄.format "h hh hhh hhhh hhhh" + +/-- +info: "11 11 011 0011 000011" +-/ +#guard_msgs in +#eval datetime₄.format "K KK KKK KKKK KKKKKK" + +/-- +info: "23 23 023 0023 000023" +-/ +#guard_msgs in +#eval datetime₄.format "k kk kkk kkkk kkkkkk" + +/-- +info: "23 23 023 0023 00023" +-/ +#guard_msgs in +#eval datetime₄.format "H HH HHH HHHH HHHHH" + +/-- +info: "13 13 013 0013 00013" +-/ +#guard_msgs in +#eval datetime₄.format "m mm mmm mmmm mmmmm" + +/-- +info: "12 12 012 0012 00012" +-/ +#guard_msgs in +#eval datetime₄.format "s ss sss ssss sssss" + + +/-- +info: "3 32 324 3243 32435" +-/#guard_msgs in +#eval datetime₄.format "S SS SSS SSSS SSSSS" + +/-- +info: "3 32 324 3243 324354679" +-/ +#guard_msgs in +#eval datetime₄.format "S SS SSS SSSS SSSSSSSSS" + +/-- +info: "83592324 83592324 83592324 83592324 083592324" +-/ +#guard_msgs in +#eval datetime₄.format "A AA AAA AAAA AAAAAAAAA" + +/-- +info: "324354679 324354679 324354679 324354679 324354679" +-/ +#guard_msgs in +#eval datetime₄.format "n nn nnn nnnn nnnnnnnnn" + +/-- +info: "83592324354679 83592324354679 83592324354679 83592324354679 83592324354679" +-/ +#guard_msgs in +#eval datetime₄.format "N NN NNN NNNN NNNNNNNNN" + +/-- +info: "11 11 011 0011 0011" +-/ +#guard_msgs in +#eval time₄.format "h hh hhh hhhh hhhh" + +/-- +info: "11 11 011 0011 000011" +-/ +#guard_msgs in +#eval time₄.format "K KK KKK KKKK KKKKKK" + +/-- +info: "23 23 023 0023 000023" + +-/ +#guard_msgs in +#eval time₄.format "k kk kkk kkkk kkkkkk" + +/-- +info: "23 23 023 0023 00023" +-/ +#guard_msgs in +#eval time₄.format "H HH HHH HHHH HHHHH" + +/-- +info: "13 13 013 0013 00013" +-/ +#guard_msgs in +#eval time₄.format "m mm mmm mmmm mmmmm" + +/-- +info: "12 12 012 0012 00012" +-/ +#guard_msgs in +#eval time₄.format "s ss sss ssss sssss" + + +/-- +info: "3 32 324 3243 32435" +-/#guard_msgs in +#eval time₄.format "S SS SSS SSSS SSSSS" + +/-- +info: "3 32 324 3243 324354679" +-/ +#guard_msgs in +#eval time₄.format "S SS SSS SSSS SSSSSSSSS" + +/-- +info: "83592324 83592324 83592324 83592324 083592324" +-/ +#guard_msgs in +#eval time₄.format "A AA AAA AAAA AAAAAAAAA" + +/-- +info: "324354679 324354679 324354679 324354679 324354679" +-/ +#guard_msgs in +#eval time₄.format "n nn nnn nnnn nnnnnnnnn" + +/-- +info: "83592324354679 83592324354679 83592324354679 83592324354679 83592324354679" +-/ +#guard_msgs in +#eval time₄.format "N NN NNN NNNN NNNNNNNNN" + +/-- +info: "CE CE CE Common Era C" +-/ +#guard_msgs in +#eval date₄.format "G GG GGG GGGG GGGGG" + +/-- +info: "02 2002 000002002" +-/ +#guard_msgs in +#eval date₄.format "yy yyyy yyyyyyyyy" + +/-- +info: "02 2002 000002002" +-/ +#guard_msgs in +#eval date₄.format "uu uuuu uuuuuuuuu" + +/-- +info: "195 195 195" +-/ +#guard_msgs in +#eval date₄.format "D DD DDD" + +/-- +info: "7 07 Jul J" +-/ +#guard_msgs in +#eval date₄.format "M MM MMM MMMMM" + +/-- +info: "14 14 014 0014 00014" +-/ +#guard_msgs in +#eval date₄.format "d dd ddd dddd ddddd" + +/-- +info: "7 07 Jul July J" +-/ +#guard_msgs in +#eval date₄.format "M MM MMM MMMM MMMMM" + +/-- +info: "14 14 0014 0014" +-/#guard_msgs in +#eval date₄.format "d dd dddd dddd" + +/-- +info: "3 03 3rd quarter 3" +-/ +#guard_msgs in +#eval date₄.format "Q QQ QQQQ QQQQQ" + +/-- +info: "28 28 028 0028" +-/ +#guard_msgs in +#eval date₄.format "w ww www wwww" + +/-- +info: "2 02 002 0002" +-/ +#guard_msgs in +#eval date₄.format "W WW WWW WWWW" + +/-- +info: "Sun Sun Sun Sunday S" +-/ +#guard_msgs in +#eval date₄.format "E EE EEE EEEE EEEEE" + +/-- +info: "7 07 Sun Sunday S" +-/ +#guard_msgs in +#eval date₄.format "e ee eee eeee eeeee" + +/-- +info: "2 02 002 0002" +-/ +#guard_msgs in +#eval date₄.format "F FF FFF FFFF" + +/-- +info: "-2000 2001 BCE" +-/ +#guard_msgs in +#eval datetime₅.format "uuuu yyyy G" + +/-- +info: "2002 2002 CE" +-/ +#guard_msgs in +#eval datetime₄.format "uuuu yyyy G" + +/-- +info: "BRT BRT BRT America/Sao_Paulo America/Sao_Paulo" +-/ +#guard_msgs in +#eval zoned₆.format "z zz zzz zzzz zzzz" + +/-- +info: 1 +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := .ofPlainDateTime datetime("2018-12-31T12:00:00") (TimeZone.ZoneRules.ofTimeZone TimeZone.UTC) + IO.println s!"{t.format "w"}" + +/- +Truncation Test +-/ + +/-- +info: ("19343232432-01-04T01:04:03.000000000", + Except.ok (datetime("19343232432-01-04T01:04:03.000000000")), + datetime("1932-01-02T05:04:03.000000000")) +-/ +#guard_msgs in +#eval + let r := (PlainDateTime.mk (PlainDate.ofYearMonthDayClip 19343232432 1 4) (PlainTime.mk 25 64 ⟨true, 3⟩ 0)) + let s := r.toLeanDateTimeString + let r := PlainDateTime.parse s + (s, r, datetime("1932-01-02T05:04:03.000000000")) diff --git a/tests/lean/run/timeIO.lean b/tests/lean/run/timeIO.lean new file mode 100644 index 000000000000..4bb46e57265d --- /dev/null +++ b/tests/lean/run/timeIO.lean @@ -0,0 +1,83 @@ +import Std.Time +import Init +open Std.Time + +/- +Test for quantity +-/ + +#eval do + let res ← Database.defaultGetZoneRules "America/Sao_Paulo" + if res.transitions.size < 1 then + throw <| IO.userError "invalid quantity for America/Sao_Paulo" + +/-- +info: { second := 0 } +-/ +#guard_msgs in +#eval do + let res ← Database.defaultGetZoneRules "UTC" + println! repr res.initialLocalTimeType.gmtOffset + +/- +The idea is just to check if there's no errors while computing the local zone rules. +-/ +#eval do + discard <| Database.defaultGetLocalZoneRules + +/- +Java: +2013-10-19T23:59:59-03:00[America/Sao_Paulo] 1382237999 +2013-10-20T01:00-02:00[America/Sao_Paulo] 1382238000 +2013-10-20T01:00:01-02:00[America/Sao_Paulo] 1382238001 +-/ + +/-- +info: 2013-10-19T23:59:59.000000000-03:00 +2013-10-20T00:00:00.000000000-02:00 +2013-10-20T00:00:01.000000000-02:00 +-/ +#guard_msgs in +#eval do + let zr ← Database.defaultGetZoneRules "America/Sao_Paulo" + println! "{ZonedDateTime.ofPlainDateTime datetime("2013-10-19T23:59:59") zr |>.toLeanDateTimeWithZoneString}" + println! "{ZonedDateTime.ofPlainDateTime datetime("2013-10-20T00:00:00") zr |>.toLeanDateTimeWithZoneString}" + println! "{ZonedDateTime.ofPlainDateTime datetime("2013-10-20T00:00:01") zr |>.toLeanDateTimeWithZoneString}" + +/- +Java: +2019-11-03T01:59:59-05:00[America/Chicago] 1572764399 +2019-11-03T02:00-06:00[America/Chicago] 1572768000 +2019-11-03T02:59:59-06:00[America/Chicago] 1572771599 +-/ + +/-- +info: 2019-11-03T01:59:59.000000000-05:00 +2019-11-03T02:00:00.000000000-06:00 +2019-11-03T02:59:59.000000000-06:00 +-/ +#guard_msgs in +#eval do + let zr ← Database.defaultGetZoneRules "America/Chicago" + println! "{ZonedDateTime.ofPlainDateTime datetime("2019-11-03T01:59:59") zr |>.toLeanDateTimeWithZoneString}" + println! "{ZonedDateTime.ofPlainDateTime datetime("2019-11-03T02:00:00") zr |>.toLeanDateTimeWithZoneString}" + println! "{ZonedDateTime.ofPlainDateTime datetime("2019-11-03T02:59:59") zr |>.toLeanDateTimeWithZoneString}" + +/- +Java: +2003-10-26T01:59:59-05:00[America/Monterrey] 1067151599 +2003-10-26T02:00-06:00[America/Monterrey] 1067155200 +2003-10-26T02:59:59-06:00[America/Monterrey] 1067158799 +-/ + +/-- +info: 2003-10-26T01:59:59.000000000-05:00 +2003-10-26T02:00:00.000000000-06:00 +2003-10-26T02:59:59.000000000-06:00 +-/ +#guard_msgs in +#eval do + let zr ← Database.defaultGetZoneRules "America/Monterrey" + println! "{ZonedDateTime.ofPlainDateTime datetime("2003-10-26T01:59:59") zr |>.toLeanDateTimeWithZoneString}" + println! "{ZonedDateTime.ofPlainDateTime datetime("2003-10-26T02:00:00") zr |>.toLeanDateTimeWithZoneString}" + println! "{ZonedDateTime.ofPlainDateTime datetime("2003-10-26T02:59:59") zr |>.toLeanDateTimeWithZoneString}" diff --git a/tests/lean/run/timeLocalDateTime.lean b/tests/lean/run/timeLocalDateTime.lean new file mode 100644 index 000000000000..4863a638729a --- /dev/null +++ b/tests/lean/run/timeLocalDateTime.lean @@ -0,0 +1,87 @@ +import Std.Time +import Init +open Std.Time + +def ShortDateTime : GenericFormat .any := datespec("dd/MM/uuuu HH:mm:ss") +def ShortDate : GenericFormat .any := datespec("dd/MM/uuuu") + +def format (PlainDate : PlainDateTime) : String := ShortDateTime.formatBuilder PlainDate.day PlainDate.month PlainDate.year PlainDate.time.hour PlainDate.minute PlainDate.time.second +def format₂ (PlainDate : PlainDate) : String := ShortDate.formatBuilder PlainDate.day PlainDate.month PlainDate.year + +def date₁ := datetime("1993-11-19T09:08:07") +def date₂ := datetime("1993-05-09T12:59:59") +def date₃ := date("2024-08-16") +def date₄ := date("1500-08-16") + +def tm₁ := 753700087 +def tm₂ := 736952399 + +/-- +info: "19/11/1993 09:08:07" +-/ +#guard_msgs in +#eval format date₁ + +/-- +info: "09/05/1993 12:59:59" +-/ +#guard_msgs in +#eval format date₂ + +/-- +info: 753700087 +-/ +#guard_msgs in +#eval date₁.toTimestampAssumingUTC.toSecondsSinceUnixEpoch + +/-- +info: 736952399 +-/ +#guard_msgs in +#eval date₂.toTimestampAssumingUTC.toSecondsSinceUnixEpoch + +/-- +info: "09/05/1993 12:59:59" +-/ +#guard_msgs in +#eval PlainDateTime.ofTimestampAssumingUTC 736952399 |> format + +/-- +info: 736952399 +-/ +#guard_msgs in +#eval PlainDateTime.toTimestampAssumingUTC date₂ |>.toSecondsSinceUnixEpoch + +/-- +info: "16/08/2024" +-/ +#guard_msgs in +#eval PlainDate.ofDaysSinceUNIXEpoch 19951 |> format₂ + +/-- +info: 19951 +-/ +#guard_msgs in +#eval PlainDate.toDaysSinceUNIXEpoch date₃ + +/-- +info: Std.Time.Weekday.friday +-/ +#guard_msgs in +#eval PlainDate.weekday date₃ + +/-- +info: #[] +-/ +#guard_msgs in +#eval Id.run do + let mut res := #[] + + for i in [0:10000] do + let i := Int.ofNat i - 999975 + let date := PlainDate.ofDaysSinceUNIXEpoch (Day.Offset.ofInt i) + let num := date.toDaysSinceUNIXEpoch + if i ≠ num.val then + res := res.push i + + return res diff --git a/tests/lean/run/timeOperations.lean b/tests/lean/run/timeOperations.lean new file mode 100644 index 000000000000..2aee656f9121 --- /dev/null +++ b/tests/lean/run/timeOperations.lean @@ -0,0 +1,319 @@ +import Std.Time +open Std.Time + +def date := date("1970-01-20") + +/-- +info: date("1970-02-01") +-/ +#guard_msgs in +#eval date.addDays 12 + +/-- +info: date("1970-02-20") +-/ +#guard_msgs in +#eval date.addMonthsClip 1 + +/-- +info: date("1971-01-20") +-/ +#guard_msgs in +#eval date.addYearsRollOver 1 + +/-- +info: date("1969-01-20") +-/ +#guard_msgs in +#eval date.subYearsClip 1 + +/-- +info: date("1969-12-20") +-/ +#guard_msgs in +#eval date.subMonthsClip 1 + +def datetime := datetime("2000-01-20T03:02:01") + +/-- +info: datetime("2000-01-20T04:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.addHours 1 + +/-- +info: datetime("2000-01-20T02:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.subHours 1 + +/-- +info: datetime("2000-01-20T03:12:01.000000000") +-/ +#guard_msgs in +#eval datetime.addMinutes 10 + +/-- +info: datetime("2000-01-20T02:52:01.000000000") +-/ +#guard_msgs in +#eval datetime.subMinutes 10 + +/-- +info: datetime("2000-01-20T03:03:01.000000000") +-/ +#guard_msgs in +#eval datetime.addSeconds 60 + +/-- +info: datetime("2000-01-20T03:01:01.000000000") +-/ +#guard_msgs in +#eval datetime.subSeconds 60 + +/-- +info: datetime("2000-01-21T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.addDays 1 + +/-- +info: datetime("2000-01-19T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.subDays 1 + +/-- +info: datetime("2000-02-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.addMonthsClip 1 + +/-- +info: datetime("1999-12-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.subMonthsClip 1 + +/-- +info: datetime("2000-02-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.addMonthsRollOver 1 + +/-- +info: datetime("1999-12-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.subMonthsRollOver 1 + +/-- +info: datetime("2001-01-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.addYearsClip 1 + +/-- +info: datetime("1999-01-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.subYearsClip 1 + +/-- +info: datetime("2001-01-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.addYearsRollOver 1 + +/-- +info: datetime("1999-01-20T03:02:01.000000000") +-/ +#guard_msgs in +#eval datetime.subYearsRollOver 1 + +def time := time("13:02:01") + +/-- +info: time("14:02:01.000000000") +-/ +#guard_msgs in +#eval time.addHours 1 + +/-- +info: time("12:02:01.000000000") +-/ +#guard_msgs in +#eval time.subHours 1 + +/-- +info: time("13:12:01.000000000") +-/ +#guard_msgs in +#eval time.addMinutes 10 + +/-- +info: time("12:52:01.000000000") +-/ +#guard_msgs in +#eval time.subMinutes 10 + +/-- +info: time("13:03:01.000000000") +-/ +#guard_msgs in +#eval time.addSeconds 60 + +/-- +info: time("13:01:01.000000000") +-/ +#guard_msgs in +#eval time.subSeconds 60 + +def datetimetz := zoned("2000-01-20T06:02:01-03:00") + +/-- +info: zoned("2000-01-20T06:02:01.000000000-03:00") + +-/ +#guard_msgs in +#eval datetimetz + +/-- +info: zoned("2000-01-22T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addDays 2 + +/-- +info: zoned("2000-01-19T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subDays 1 + +/-- +info: zoned("2000-02-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addMonthsClip 1 + +/-- +info: zoned("1999-12-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subMonthsClip 1 + +/-- +info: zoned("2000-02-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addMonthsRollOver 1 + +/-- +info: zoned("1999-12-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subMonthsRollOver 1 + +/-- +info: zoned("2001-01-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addYearsClip 1 + +/-- +info: zoned("2001-01-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addYearsClip 1 + +/-- +info: zoned("2001-01-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addYearsRollOver 1 + +/-- +info: zoned("1999-01-20T06:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subYearsRollOver 1 + +/-- +info: zoned("2000-01-20T07:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addHours 1 + +/-- +info: zoned("2000-01-20T05:02:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subHours 1 + +/-- +info: zoned("2000-01-20T06:12:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addMinutes 10 + +/-- +info: zoned("2000-01-20T05:52:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subMinutes 10 + +/-- +info: zoned("2000-01-20T06:03:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.addSeconds 60 + +/-- +info: zoned("2000-01-20T06:01:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subSeconds 60 + +def now := zoned("2024-08-29T10:56:43.276801081+02:00") + +/-- +info: zoned("2024-08-29T10:56:43.276801081+02:00") +-/ +#guard_msgs in +#eval now + +/-- +info: zoned("2024-08-30T10:56:43.276801081+02:00") +-/ +#guard_msgs in +#eval now.addDays 1 + +/-- +info: zoned("2000-01-20T06:01:01.000000000-03:00") +-/ +#guard_msgs in +#eval datetimetz.subSeconds 60 + +/-- +info: 3 +-/ +#guard_msgs in +#eval date("2024-11-17").alignedWeekOfMonth + +/-- +info: 4 +-/ +#guard_msgs in +#eval date("2024-11-18").alignedWeekOfMonth + +/-- +info: 3 +-/ +#guard_msgs in +#eval date("2024-01-21").alignedWeekOfMonth + +/-- +info: 4 +-/ +#guard_msgs in +#eval date("2024-01-22").alignedWeekOfMonth diff --git a/tests/lean/run/timeOperationsOffset.lean b/tests/lean/run/timeOperationsOffset.lean new file mode 100644 index 000000000000..53810de730d7 --- /dev/null +++ b/tests/lean/run/timeOperationsOffset.lean @@ -0,0 +1,596 @@ +import Std.Time +open Std.Time + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 1000001 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Millisecond.Offset) + +/-- +info: 1000000001 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Second.Offset) + +/-- +info: 60000000001 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Minute.Offset) + +/-- +info: 3600000000001 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Hour.Offset) + +/-- +info: 86400000000001 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Day.Offset) + +/-- +info: 604800000000001 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) + (1 : Week.Offset) + +/-- +info: 1000001 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Millisecond.Offset) + +/-- +info: 1001 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Second.Offset) + +/-- +info: 60001 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Minute.Offset) + +/-- +info: 3600001 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Hour.Offset) + +/-- +info: 86400001 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Day.Offset) + +/-- +info: 604800001 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) + (1 : Week.Offset) + +/-- +info: 1000000001 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 1001 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Millisecond.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Second.Offset) + +/-- +info: 61 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Minute.Offset) + +/-- +info: 3601 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Hour.Offset) + +/-- +info: 86401 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Day.Offset) + +/-- +info: 604801 +-/ +#guard_msgs in +#eval (1 : Second.Offset) + (1 : Week.Offset) + +/-- +info: 60000000001 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 60001 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Millisecond.Offset) + +/-- +info: 61 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Second.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Minute.Offset) + +/-- +info: 61 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Hour.Offset) + +/-- +info: 1441 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Day.Offset) + +/-- +info: 10081 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) + (1 : Week.Offset) + +/-- +info: 3600000000001 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 3600001 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Millisecond.Offset) + +/-- +info: 3601 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Second.Offset) + +/-- +info: 61 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Minute.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Hour.Offset) + +/-- +info: 25 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Day.Offset) + +/-- +info: 169 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) + (1 : Week.Offset) + +/-- +info: 86400000000001 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 86400001 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Millisecond.Offset) + +/-- +info: 86401 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Second.Offset) + +/-- +info: 1441 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Minute.Offset) + +/-- +info: 25 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Hour.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Day.Offset) + +/-- +info: 8 +-/ +#guard_msgs in +#eval (1 : Day.Offset) + (1 : Week.Offset) + +/-- +info: 604800000000001 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Nanosecond.Offset) + +/-- +info: 604800001 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Millisecond.Offset) + +/-- +info: 604801 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Second.Offset) + +/-- +info: 10081 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Minute.Offset) + +/-- +info: 169 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Hour.Offset) + +/-- +info: 8 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Day.Offset) + +/-- +info: 2 +-/ +#guard_msgs in +#eval (1 : Week.Offset) + (1 : Week.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Nanosecond.Offset) + +/-- +info: -999999 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Millisecond.Offset) + +/-- +info: -999999999 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Second.Offset) + +/-- +info: -59999999999 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Minute.Offset) + +/-- +info: -3599999999999 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Hour.Offset) + +/-- +info: -86399999999999 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Day.Offset) + +/-- +info: -604799999999999 +-/ +#guard_msgs in +#eval (1 : Nanosecond.Offset) - (1 : Week.Offset) + +/-- +info: 999999 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Nanosecond.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Millisecond.Offset) + +/-- +info: -999 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Second.Offset) + +/-- +info: -59999 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Minute.Offset) + +/-- +info: -3599999 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Hour.Offset) + +/-- +info: -86399999 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Day.Offset) + +/-- +info: -604799999 +-/ +#guard_msgs in +#eval (1 : Millisecond.Offset) - (1 : Week.Offset) + +/-- +info: 999999999 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Nanosecond.Offset) + +/-- +info: 999 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Millisecond.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Second.Offset) + +/-- +info: -59 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Minute.Offset) + +/-- +info: -3599 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Hour.Offset) + +/-- +info: -86399 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Day.Offset) + +/-- +info: -604799 +-/ +#guard_msgs in +#eval (1 : Second.Offset) - (1 : Week.Offset) + +/-- +info: 59999999999 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Nanosecond.Offset) + +/-- +info: 59999 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Millisecond.Offset) + +/-- +info: 59 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Second.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Minute.Offset) + +/-- +info: -59 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Hour.Offset) + +/-- +info: -1439 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Day.Offset) + +/-- +info: -10079 +-/ +#guard_msgs in +#eval (1 : Minute.Offset) - (1 : Week.Offset) + +/-- +info: 3599999999999 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Nanosecond.Offset) + +/-- +info: 3599999 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Millisecond.Offset) + +/-- +info: 3599 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Second.Offset) + +/-- +info: 59 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Minute.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Hour.Offset) + +/-- +info: -23 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Day.Offset) + +/-- +info: -167 +-/ +#guard_msgs in +#eval (1 : Hour.Offset) - (1 : Week.Offset) + +/-- +info: 86399999999999 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Nanosecond.Offset) + +/-- +info: 86399999 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Millisecond.Offset) + +/-- +info: 86399 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Second.Offset) + +/-- +info: 1439 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Minute.Offset) + +/-- +info: 23 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Hour.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Day.Offset) + +/-- +info: -6 +-/ +#guard_msgs in +#eval (1 : Day.Offset) - (1 : Week.Offset) + +/-- +info: 604799999999999 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Nanosecond.Offset) + +/-- +info: 604799999 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Millisecond.Offset) + +/-- +info: 604799 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Second.Offset) + +/-- +info: 10079 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Minute.Offset) + +/-- +info: 167 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Hour.Offset) + +/-- +info: 6 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Day.Offset) + +/-- +info: 0 +-/ +#guard_msgs in +#eval (1 : Week.Offset) - (1 : Week.Offset) diff --git a/tests/lean/run/timeParse.lean b/tests/lean/run/timeParse.lean new file mode 100644 index 000000000000..fcd7b6be0b77 --- /dev/null +++ b/tests/lean/run/timeParse.lean @@ -0,0 +1,204 @@ +import Std.Time +open Std.Time + +def ISO8601UTC : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX") +def RFC1123 : GenericFormat .any := datespec("eee, dd MMM uuuu HH:mm:ss ZZZ") +def ShortDate : GenericFormat .any := datespec("MM/dd/uuuu") +def LongDate : GenericFormat .any := datespec("MMMM D, uuuu") +def ShortDateTime : GenericFormat .any := datespec("MM/dd/uuuu HH:mm:ss") +def LongDateTime : GenericFormat .any := datespec("MMMM dd, uuuu hh:mm aa") +def Time24Hour : GenericFormat .any := datespec("HH:mm:ss") +def Time12Hour : GenericFormat .any := datespec("hh:mm:ss aa") +def FullDayTimeZone : GenericFormat .any := datespec("EEEE, MMMM dd, uuuu HH:mm:ss ZZZ") +def CustomDayTime : GenericFormat .any := datespec("EEE dd MMM uuuu HH:mm") + +def Full12HourWrong : GenericFormat .any := datespec("MM/dd/uuuu hh:mm:ss aa XXX") + +-- Dates + +def brTZ : TimeZone := timezone("America/Sao_Paulo -03:00") +def jpTZ : TimeZone := timezone("Asia/Tokyo +09:00") + +def date₁ := zoned("2014-06-16T03:03:03-03:00") + +def time₁ := time("14:11:01") +def time₂ := time("03:11:01") + +/-- +info: "2014-06-16T03:03:03.000000100-03:00" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := ISO8601UTC.parse! "2014-06-16T03:03:03.000000100-03:00" + ISO8601UTC.format t.toDateTime + +def tm := date₁.toTimestamp +def date₂ := DateTime.ofTimestamp tm brTZ + +/-- +info: "2014-06-16T03:03:03.000000000-03:00" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := RFC1123.parse! "Mon, 16 Jun 2014 03:03:03 -0300" + ISO8601UTC.format t.toDateTime + +def tm₃ := date₁.toTimestamp +def date₃ := DateTime.ofTimestamp tm₃ brTZ + +/-- +info: "2014-06-16T00:00:00.000000000Z" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := ShortDate.parse! "06/16/2014" + ISO8601UTC.format t.toDateTime + +-- the timestamp is always related to UTC. + +/-- +Timestamp: 1723739292 +GMT: Thursday, 15 August 2024 16:28:12 +BR: 15 August 2024 13:28:12 GMT-03:00 +-/ +def tm₄ : Second.Offset := 1723739292 + +def dateBR := DateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch tm₄) brTZ +def dateJP := DateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch tm₄) jpTZ +def dateUTC := DateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch tm₄) .UTC + +/-- +info: "2024-08-15T13:28:12.000000000-03:00" +-/ +#guard_msgs in +#eval + let t := FullDayTimeZone.parse! "Thursday, August 15, 2024 13:28:12 -0300" + ISO8601UTC.format t.toDateTime + +/-- +info: "2024-08-16T01:28:00.000000000Z" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := LongDateTime.parse! "August 16, 2024 01:28 AM" + ISO8601UTC.format t.toDateTime + +/-- +info: "0000-12-30T22:28:12.000000000+09:00" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := Time24Hour.parse! "13:28:12" + ISO8601UTC.format (t.toDateTime.convertTimeZone jpTZ) + +/-- +info: "0000-12-29T21:28:12.000000000-03:00" +-/ +#guard_msgs in +#eval + let t1 : ZonedDateTime := Time12Hour.parse! "12:28:12 AM" + ISO8601UTC.format (t1.toDateTime.convertTimeZone brTZ) + +/-- +info: "Thu 15 Aug 2024 16:28" +-/ +#guard_msgs in +#eval + let t2 : ZonedDateTime := FullDayTimeZone.parse! "Thursday, August 15, 2024 16:28:12 -0000" + CustomDayTime.format t2.toDateTime + +/-- +info: "2024-08-16T13:28:00.000000000Z" +-/ +#guard_msgs in +#eval + let t5 : ZonedDateTime := CustomDayTime.parse! "Thu 16 Aug 2024 13:28" + ISO8601UTC.format t5.toDateTime + +/-- +info: "2024-08-16T01:28:12.000000000+09:00" +-/ +#guard_msgs in +#eval + let t6 : ZonedDateTime := FullDayTimeZone.parse! "Friday, August 16, 2024 01:28:12 +0900" + ISO8601UTC.format (t6.toDateTime.convertTimeZone jpTZ) + +/-- +info: "2024-08-16T01:28:12.000000000+09:00" +-/ +#guard_msgs in +#eval + let t7 : ZonedDateTime := FullDayTimeZone.parse! "Friday, August 16, 2024 01:28:12 +0900" + ISO8601UTC.format (t7.toDateTime.convertTimeZone jpTZ) + +/-- +TM: 1723730627 +GMT: Thursday, 15 August 2024 14:03:47 +Your time zone: 15 Aguust 2024 11:03:47 GMT-03:00 +-/ +def localTm : Second.Offset := 1723730627 + +/-- +This PlainDate is relative to the local time. +-/ +def PlainDate : PlainDateTime := Timestamp.toPlainDateTimeAssumingUTC (Timestamp.ofSecondsSinceUnixEpoch localTm) + +def dateBR₁ := DateTime.ofPlainDateTime PlainDate brTZ +def dateJP₁ := DateTime.ofPlainDateTime PlainDate jpTZ +def dateUTC₁ := DateTime.ofPlainDateTime PlainDate .UTC + +/-- +info: "2024-08-15T14:03:47.000000000-03:00" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := FullDayTimeZone.parse! "Thursday, August 15, 2024 14:03:47 -0300" + ISO8601UTC.format t.toDateTime + +/-- +info: "2024-08-15T14:03:47.000000000+09:00" +-/ +#guard_msgs in +#eval + let t1 : ZonedDateTime := FullDayTimeZone.parse! "Thursday, August 15, 2024 14:03:47 +0900" + ISO8601UTC.format t1.toDateTime + +/-- +info: "2014-06-16T03:03:03.000000000-03:00" +-/ +#guard_msgs in +#eval + let t2 : ZonedDateTime := FullDayTimeZone.parse! "Monday, June 16, 2014 03:03:03 -0300" + ISO8601UTC.format t2.toDateTime + +/-- +info: Except.ok "1993-05-10T10:30:23.000000000+03:00" +-/ +#guard_msgs in +#eval + let t2 := Full12HourWrong.parse "05/10/1993 10:30:23 AM +03:00" + (ISO8601UTC.format ·.toDateTime) <$> t2 + +/-- +info: Except.ok "1993-05-10T22:30:23.000000000+03:00" +-/ +#guard_msgs in +#eval + let t2 := Full12HourWrong.parse "05/10/1993 10:30:23 PM +03:00" + (ISO8601UTC.format ·.toDateTime) <$> t2 + +/-- +info: Except.error "offset 13: need a natural number in the interval of 1 to 12" +-/ +#guard_msgs in +#eval + let t2 := Full12HourWrong.parse "05/10/1993 20:30:23 AM +03:00" + (ISO8601UTC.format ·.toDateTime) <$> t2 + +/-- +info: Except.error "offset 13: need a natural number in the interval of 1 to 12" +-/ +#guard_msgs in +#eval + let t2 := Full12HourWrong.parse "05/10/1993 20:30:23 PM +03:00" + (ISO8601UTC.format ·.toDateTime) <$> t2 diff --git a/tests/lean/run/timeSet.lean b/tests/lean/run/timeSet.lean new file mode 100644 index 000000000000..4f179d506132 --- /dev/null +++ b/tests/lean/run/timeSet.lean @@ -0,0 +1,100 @@ +import Std.Time +open Std.Time + +def ISO8601UTC : GenericFormat .any := datespec("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX") +def RFC1123 : GenericFormat .any := datespec("eee, dd MMM uuuu HH:mm:ss ZZZ") +def ShortDate : GenericFormat .any := datespec("MM/dd/uuuu") +def LongDate : GenericFormat .any := datespec("MMMM D, uuuu") +def ShortDateTime : GenericFormat .any := datespec("MM/dd/uuuu HH:mm:ss") +def LongDateTime : GenericFormat .any := datespec("MMMM dd, uuuu hh:mm aa") +def Time24Hour : GenericFormat .any := datespec("HH:mm:ss") +def Time12Hour : GenericFormat .any := datespec("hh:mm:ss aa") +def FullDayTimeZone : GenericFormat .any := datespec("EEEE, MMMM dd, uuuu HH:mm:ss ZZZ") +def CustomDayTime : GenericFormat .any := datespec("EEE dd MMM uuuu HH:mm") + +def Full12HourWrong : GenericFormat .any := datespec("MM/dd/uuuu hh:mm:ss aa XXX") + +-- Dates + +def brTZ : TimeZone := timezone("America/Sao_Paulo -03:00") +def jpTZ : TimeZone := timezone("Asia/Tokyo +09:00") + +def date₁ := zoned("2014-06-16T10:03:03-03:00") + +def time₁ := time("14:11:01") +def time₂ := time("03:11:01") + +/-- +info: "2014-06-16T10:03:03.000000100-03:00" +-/ +#guard_msgs in +#eval + let t : ZonedDateTime := ISO8601UTC.parse! "2014-06-16T10:03:03.000000100-03:00" + ISO8601UTC.format t.toDateTime + +/-- +info: zoned("2014-06-30T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withDaysClip 31 + +/-- +info: zoned("2014-07-01T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withDaysRollOver 31 + +/-- +info: zoned("2014-05-16T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withMonthClip 5 + +/-- +info: zoned("2014-05-16T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withMonthRollOver 5 + +/-- +info: zoned("2016-06-16T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withYearClip 2016 + +/-- +info: zoned("2016-06-16T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withYearRollOver 2016 + +/-- +info: zoned("2014-06-16T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withDaysClip 16 + +/-- +info: zoned("2014-06-16T10:45:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withMinutes 45 + + +/-- +info: zoned("2014-06-16T10:03:03.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withHours 10 + +/-- +info: zoned("2014-06-16T10:03:59.000000000-03:00") +-/ +#guard_msgs in +#eval date₁.withSeconds ⟨true, 59⟩ + +/-- +info: zoned("2014-06-16T10:03:03.000000002-03:00") +-/ +#guard_msgs in +#eval date₁.withNanoseconds 2 diff --git a/tests/lean/run/timeTzifParse.lean b/tests/lean/run/timeTzifParse.lean new file mode 100644 index 000000000000..a82603691cba --- /dev/null +++ b/tests/lean/run/timeTzifParse.lean @@ -0,0 +1,110 @@ +import Std.Time +open Std.Time + +def file := ByteArray.mk <| +#[84, 90, 105, 102, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0] +++ #[0, 0, 3, 0, 0, 0, 12, 150, 170, 114, 180, 184, 15, 73, 224, 184, 253, 64, 160, 185, 241, 52, 48, 186, 222, 116, 32, 218, 56, 174, 48, 218, 235, 250, 48, 220, 25] +++ #[225, 176, 220, 185, 89, 32, 221, 251, 21, 48, 222, 155, 222, 32, 223, 221, 154, 48, 224, 84, 51, 32, 244, 90, 9, 48, 245, 5, 94, 32, 246, 192, 100, 48, 247, 14, 30] +++ #[160, 248, 81, 44, 48, 248, 199, 197, 32, 250, 10, 210, 176, 250, 168, 248, 160, 251, 236, 6, 48, 252, 139, 125, 160, 29, 201, 142, 48, 30, 120, 215, 160, 31, 160, 53, 176] +++ #[32, 51, 207, 160, 33, 129, 105, 48, 34, 11, 200, 160, 35, 88, 16, 176, 35, 226, 112, 32, 37, 55, 242, 176, 37, 212, 199, 32, 39, 33, 15, 48, 39, 189, 227, 160, 41] +++ #[0, 241, 48, 41, 148, 139, 32, 42, 234, 13, 176, 43, 107, 50, 160, 44, 192, 181, 48, 45, 102, 196, 32, 46, 160, 151, 48, 47, 70, 166, 32, 48, 128, 121, 48, 49, 29] +++ #[77, 160, 50, 87, 32, 176, 51, 6, 106, 32, 52, 56, 84, 48, 52, 248, 193, 32, 54, 32, 31, 48, 54, 207, 104, 160, 55, 246, 198, 176, 56, 184, 133, 32, 57, 223, 227] +++ #[48, 58, 143, 44, 160, 59, 200, 255, 176, 60, 111, 14, 160, 61, 196, 145, 48, 62, 78, 240, 160, 63, 145, 254, 48, 64, 46, 210, 160, 65, 134, 248, 48, 66, 23, 239, 32] +++ #[67, 81, 194, 48, 67, 247, 209, 32, 69, 77, 83, 176, 69, 224, 237, 160, 71, 17, 134, 48, 71, 183, 149, 32, 72, 250, 162, 176, 73, 151, 119, 32, 74, 218, 132, 176, 75] +++ #[128, 147, 160, 76, 186, 102, 176, 77, 96, 117, 160, 78, 154, 72, 176, 79, 73, 146, 32, 80, 131, 101, 48, 81, 32, 57, 160, 82, 99, 71, 48, 83, 0, 27, 160, 84, 67] +++ #[41, 48, 84, 233, 56, 32, 86, 35, 11, 48, 86, 201, 26, 32, 88, 2, 237, 48, 88, 168, 252, 32, 89, 226, 207, 48, 90, 136, 222, 32, 91, 222, 96, 176, 92, 104, 192] +++ #[32, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1] +++ #[2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2] +++ #[1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 255, 255, 212, 76, 0, 0, 255, 255, 227, 224, 1, 4, 255, 255, 213, 208, 0, 8, 76] +++ #[77, 84, 0, 45, 48, 50, 0, 45, 48, 51, 0, 84, 90, 105, 102, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +++ #[0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 3, 0, 0, 0, 12, 255, 255, 255, 255, 150, 170, 114, 180, 255, 255, 255, 255, 184, 15, 73, 224, 255, 255, 255] +++ #[255, 184, 253, 64, 160, 255, 255, 255, 255, 185, 241, 52, 48, 255, 255, 255, 255, 186, 222, 116, 32, 255, 255, 255, 255, 218, 56, 174, 48, 255, 255, 255, 255, 218, 235, 250, 48] +++ #[255, 255, 255, 255, 220, 25, 225, 176, 255, 255, 255, 255, 220, 185, 89, 32, 255, 255, 255, 255, 221, 251, 21, 48, 255, 255, 255, 255, 222, 155, 222, 32, 255, 255, 255, 255, 223] +++ #[221, 154, 48, 255, 255, 255, 255, 224, 84, 51, 32, 255, 255, 255, 255, 244, 90, 9, 48, 255, 255, 255, 255, 245, 5, 94, 32, 255, 255, 255, 255, 246, 192, 100, 48, 255, 255] +++ #[255, 255, 247, 14, 30, 160, 255, 255, 255, 255, 248, 81, 44, 48, 255, 255, 255, 255, 248, 199, 197, 32, 255, 255, 255, 255, 250, 10, 210, 176, 255, 255, 255, 255, 250, 168, 248] +++ #[160, 255, 255, 255, 255, 251, 236, 6, 48, 255, 255, 255, 255, 252, 139, 125, 160, 0, 0, 0, 0, 29, 201, 142, 48, 0, 0, 0, 0, 30, 120, 215, 160, 0, 0, 0, 0] +++ #[31, 160, 53, 176, 0, 0, 0, 0, 32, 51, 207, 160, 0, 0, 0, 0, 33, 129, 105, 48, 0, 0, 0, 0, 34, 11, 200, 160, 0, 0, 0, 0, 35, 88, 16, 176, 0] +++ #[0, 0, 0, 35, 226, 112, 32, 0, 0, 0, 0, 37, 55, 242, 176, 0, 0, 0, 0, 37, 212, 199, 32, 0, 0, 0, 0, 39, 33, 15, 48, 0, 0, 0, 0, 39, 189] +++ #[227, 160, 0, 0, 0, 0, 41, 0, 241, 48, 0, 0, 0, 0, 41, 148, 139, 32, 0, 0, 0, 0, 42, 234, 13, 176, 0, 0, 0, 0, 43, 107, 50, 160, 0, 0, 0] +++ #[0, 44, 192, 181, 48, 0, 0, 0, 0, 45, 102, 196, 32, 0, 0, 0, 0, 46, 160, 151, 48, 0, 0, 0, 0, 47, 70, 166, 32, 0, 0, 0, 0, 48, 128, 121, 48] +++ #[0, 0, 0, 0, 49, 29, 77, 160, 0, 0, 0, 0, 50, 87, 32, 176, 0, 0, 0, 0, 51, 6, 106, 32, 0, 0, 0, 0, 52, 56, 84, 48, 0, 0, 0, 0, 52] +++ #[248, 193, 32, 0, 0, 0, 0, 54, 32, 31, 48, 0, 0, 0, 0, 54, 207, 104, 160, 0, 0, 0, 0, 55, 246, 198, 176, 0, 0, 0, 0, 56, 184, 133, 32, 0, 0] +++ #[0, 0, 57, 223, 227, 48, 0, 0, 0, 0, 58, 143, 44, 160, 0, 0, 0, 0, 59, 200, 255, 176, 0, 0, 0, 0, 60, 111, 14, 160, 0, 0, 0, 0, 61, 196, 145] +++ #[48, 0, 0, 0, 0, 62, 78, 240, 160, 0, 0, 0, 0, 63, 145, 254, 48, 0, 0, 0, 0, 64, 46, 210, 160, 0, 0, 0, 0, 65, 134, 248, 48, 0, 0, 0, 0] +++ #[66, 23, 239, 32, 0, 0, 0, 0, 67, 81, 194, 48, 0, 0, 0, 0, 67, 247, 209, 32, 0, 0, 0, 0, 69, 77, 83, 176, 0, 0, 0, 0, 69, 224, 237, 160, 0] +++ #[0, 0, 0, 71, 17, 134, 48, 0, 0, 0, 0, 71, 183, 149, 32, 0, 0, 0, 0, 72, 250, 162, 176, 0, 0, 0, 0, 73, 151, 119, 32, 0, 0, 0, 0, 74, 218] +++ #[132, 176, 0, 0, 0, 0, 75, 128, 147, 160, 0, 0, 0, 0, 76, 186, 102, 176, 0, 0, 0, 0, 77, 96, 117, 160, 0, 0, 0, 0, 78, 154, 72, 176, 0, 0, 0] +++ #[0, 79, 73, 146, 32, 0, 0, 0, 0, 80, 131, 101, 48, 0, 0, 0, 0, 81, 32, 57, 160, 0, 0, 0, 0, 82, 99, 71, 48, 0, 0, 0, 0, 83, 0, 27, 160] +++ #[0, 0, 0, 0, 84, 67, 41, 48, 0, 0, 0, 0, 84, 233, 56, 32, 0, 0, 0, 0, 86, 35, 11, 48, 0, 0, 0, 0, 86, 201, 26, 32, 0, 0, 0, 0, 88] +++ #[2, 237, 48, 0, 0, 0, 0, 88, 168, 252, 32, 0, 0, 0, 0, 89, 226, 207, 48, 0, 0, 0, 0, 90, 136, 222, 32, 0, 0, 0, 0, 91, 222, 96, 176, 0, 0] +++ #[0, 0, 92, 104, 192, 32, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2] +++ #[1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1] +++ #[2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 255, 255, 212, 76, 0, 0, 255, 255, 227, 224, 1, 4, 255, 255] +++ #[213, 208, 0, 8, 76, 77, 84, 0, 45, 48, 50, 0, 45, 48, 51, 0, 10, 60, 45, 48, 51, 62, 51, 10] + +def code := Std.Time.TimeZone.TZif.parse.run file |>.toOption |>.get! + +def rules := + match TimeZone.convertTZif code "America/Sao_Paulo" with + | .ok res => res + | .error err => panic! err + +/-- +info: { version := 50, isutcnt := 0, isstdcnt := 0, leapcnt := 0, timecnt := 91, typecnt := 3, charcnt := 12 } +-/ +#guard_msgs in +#eval code.v1.header + +/-- +info: 0 +-/ +#guard_msgs in +#eval code.v1.leapSeconds.size + +/-- +info: 3 +-/ +#guard_msgs in +#eval code.v1.abbreviations.size + +/-- +info: 91 +-/ +#guard_msgs in +#eval code.v1.transitionIndices.size + +/-- +info: 91 +-/ +#guard_msgs in +#eval code.v1.transitionTimes.size + +/-- +info: 3 +-/ +#guard_msgs in +#eval code.v1.localTimeTypes.size + +/-- +info: 0 +-/ +#guard_msgs in +#eval code.v1.stdWallIndicators.size + +/-- +info: 0 +-/ +#guard_msgs in +#eval code.v1.utLocalIndicators.size + +/-- +info: zoned("1969-12-31T21:00:00.000000000-03:00") +-/ +#guard_msgs in +#eval ZonedDateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch 0) rules + +/-- +info: zoned("2012-12-10T00:35:47.000000000-02:00") +-/ +#guard_msgs in +#eval ZonedDateTime.ofTimestamp (Timestamp.ofSecondsSinceUnixEpoch 1355106947) rules