├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── gleam.toml ├── manifest.toml ├── src ├── gleam │ └── time │ │ ├── calendar.gleam │ │ ├── duration.gleam │ │ └── timestamp.gleam ├── gleam_time_ffi.erl └── gleam_time_ffi.mjs └── test ├── gleam └── time │ ├── calendar_test.gleam │ ├── duration_test.gleam │ ├── generators.gleam │ ├── timestamp_test.gleam │ └── timestamps_parsed.tsv ├── gleam_time_test.gleam ├── gleam_time_test_ffi.erl └── gleam_time_test_ffi.mjs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: erlef/setup-beam@v1 16 | with: 17 | otp-version: "27" 18 | gleam-version: "1.9.1" 19 | rebar3-version: "3" 20 | - run: gleam deps download 21 | - run: gleam test --target erlang 22 | - run: gleam test --target javascript 23 | - run: gleam format --check src test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | /build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.2.0 - 2025-04-20 4 | 5 | - Fixed a bug where the `milliseconds` function could return an incorrect value 6 | for negative numbers. 7 | - The `duration` module gains the `Unit` type and the `approximate`, `minutes` 8 | and `hours` functions. 9 | - The `calendar` module gains the `month_to_int` and `month_from_int` 10 | functions. 11 | 12 | ## v1.1.0 - 2025-03-29 13 | 14 | - The `calendar` module gains the `month_to_string` function. 15 | 16 | ## v1.0.0 - 2025-03-05 17 | 18 | ## v1.0.0-rc2 - 2025-02-05 19 | 20 | - Fixed 2 bugs with time-zone offset handling. 21 | 22 | ## v1.0.0-rc1 - 2025-02-03 23 | 24 | - Initial release, with the timestamp, calendar, and duration modules. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time 🕰️ 2 | 3 | Work with time in Gleam! 4 | 5 | [![Package Version](https://img.shields.io/hexpm/v/gleam_time)](https://hex.pm/packages/gleam_time) 6 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_time/) 7 | 8 | ```sh 9 | gleam add gleam_time 10 | ``` 11 | 12 | Working with time can be difficult so be sure to read the documentation, 13 | starting with the [`gleam/time/timestamp`][timestamp] module. 14 | 15 | [timestamp]: https://hexdocs.pm/gleam_time/gleam/time/timestamp.html 16 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "gleam_time" 2 | version = "1.2.0" 3 | description = "Work with time in Gleam!" 4 | gleam = ">= 1.5.0" 5 | licences = ["Apache-2.0"] 6 | repository = { type = "github", user = "gleam-lang", repo = "time" } 7 | links = [ 8 | { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, 9 | ] 10 | 11 | [dependencies] 12 | gleam_stdlib = ">= 0.44.0 and < 2.0.0" 13 | 14 | [dev-dependencies] 15 | gleeunit = ">= 1.0.0 and < 2.0.0" 16 | qcheck = ">= 1.0.0 and < 2.0.0" 17 | simplifile = ">= 2.2.0 and < 3.0.0" 18 | gleam_regexp = ">= 1.0.0 and < 2.0.0" 19 | prng = ">= 4.0.1 and < 5.0.0" 20 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, 6 | { name = "filepath", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "65F51013BCF78A603AFFD7992EF1CC6ECA96C74038EB48887F656DE44DBC1902" }, 7 | { name = "gleam_bitwise", version = "1.3.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_bitwise", source = "hex", outer_checksum = "B36E1D3188D7F594C7FD4F43D0D2CE17561DE896202017548578B16FE1FE9EFC" }, 8 | { name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" }, 9 | { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, 10 | { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 11 | { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" }, 12 | { name = "prng", version = "4.0.1", build_tools = ["gleam"], requirements = ["gleam_bitwise", "gleam_stdlib", "gleam_yielder"], otp_app = "prng", source = "hex", outer_checksum = "695AB70E4BE713042062E901975FC08D1EC725B85B808D4786A14C406ADFBCF1" }, 13 | { name = "qcheck", version = "1.0.0", build_tools = ["gleam"], requirements = ["exception", "gleam_regexp", "gleam_stdlib", "gleam_yielder", "prng"], otp_app = "qcheck", source = "hex", outer_checksum = "6DAE7925E350480CE813F80D07AC4B9BAB25360F0D63EC98C5742D8456C9A9A1" }, 14 | { name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" }, 15 | ] 16 | 17 | [requirements] 18 | gleam_regexp = { version = ">= 1.0.0 and < 2.0.0" } 19 | gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 20 | gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 21 | prng = { version = ">= 4.0.1 and < 5.0.0" } 22 | qcheck = { version = ">= 1.0.0 and < 2.0.0" } 23 | simplifile = { version = ">= 2.2.0 and < 3.0.0" } 24 | -------------------------------------------------------------------------------- /src/gleam/time/calendar.gleam: -------------------------------------------------------------------------------- 1 | //// This module is for working with the Gregorian calendar, established by 2 | //// Pope Gregory XIII in 1582! 3 | //// 4 | //// ## When should you use this module? 5 | //// 6 | //// The types in this module type are useful when you want to communicate time 7 | //// to a human reader, but they are not ideal for computers to work with. 8 | //// Disadvantages of calendar time types include: 9 | //// 10 | //// - They are ambiguous if you don't know what time-zone they are for. 11 | //// - The type permits invalid states. e.g. `days` could be set to the number 12 | //// 32, but this should not be possible! 13 | //// - There is not a single unique canonical value for each point in time, 14 | //// thanks to time zones. Two different `Date` + `TimeOfDay` value pairs 15 | //// could represent the same point in time. This means that you can't check 16 | //// for time equality with `==` when using calendar types. 17 | //// 18 | //// Prefer to represent your time using the `Timestamp` type, and convert it 19 | //// only to calendar types when you need to display them. 20 | //// 21 | //// ## Time zone offsets 22 | //// 23 | //// This package includes the `utc_offset` value and the `local_offset` 24 | //// function, which are the offset for the UTC time zone and get the time 25 | //// offset the computer running the program is configured to respectively. 26 | //// 27 | //// If you need to use other offsets in your program then you will need to get 28 | //// them from somewhere else, such as from a package which loads the 29 | //// [IANA Time Zone Database](https://www.iana.org/time-zones), or from the 30 | //// website visitor's web browser, which your frontend can send for you. 31 | //// 32 | //// ## Use in APIs 33 | //// 34 | //// If you are making an API such as a HTTP JSON API you are encouraged to use 35 | //// Unix timestamps instead of calendar times. 36 | 37 | import gleam/time/duration 38 | 39 | /// The Gregorian calendar date. Ambiguous without a time zone. 40 | /// 41 | /// Prefer to represent your time using the `Timestamp` type, and convert it 42 | /// only to calendar types when you need to display them. See the documentation 43 | /// for this module for more information. 44 | /// 45 | pub type Date { 46 | Date(year: Int, month: Month, day: Int) 47 | } 48 | 49 | /// The time of day. Ambiguous without a date and time zone. 50 | /// 51 | pub type TimeOfDay { 52 | TimeOfDay(hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) 53 | } 54 | 55 | /// The 12 months of the year. 56 | pub type Month { 57 | January 58 | February 59 | March 60 | April 61 | May 62 | June 63 | July 64 | August 65 | September 66 | October 67 | November 68 | December 69 | } 70 | 71 | /// The offset for the [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) 72 | /// time zone. 73 | /// 74 | /// The utc zone has no time adjustments, it is always zero. It never observes 75 | /// daylight-saving time and it never shifts around based on political 76 | /// restructuring. 77 | /// 78 | pub const utc_offset = duration.empty 79 | 80 | /// Get the offset for the computer's currently configured time zone. 81 | /// 82 | /// Note this may not be the time zone that is correct to use for your user. 83 | /// For example, if you are making a web application that runs on a server you 84 | /// want _their_ computer's time zone, not yours. 85 | /// 86 | /// This is the _current local_ offset, not the current local time zone. This 87 | /// means that while it will result in the expected outcome for the current 88 | /// time, it may result in unexpected output if used with other timestamps. For 89 | /// example: a timestamp that would locally be during daylight savings time if 90 | /// is it not currently daylight savings time when this function is called. 91 | /// 92 | pub fn local_offset() -> duration.Duration { 93 | duration.seconds(local_time_offset_seconds()) 94 | } 95 | 96 | @external(erlang, "gleam_time_ffi", "local_time_offset_seconds") 97 | @external(javascript, "../../gleam_time_ffi.mjs", "local_time_offset_seconds") 98 | fn local_time_offset_seconds() -> Int 99 | 100 | /// Returns the English name for a month. 101 | /// 102 | /// # Examples 103 | /// 104 | /// ```gleam 105 | /// month_to_string(April) 106 | /// // -> "April" 107 | /// ``` 108 | pub fn month_to_string(month: Month) -> String { 109 | case month { 110 | January -> "January" 111 | February -> "February" 112 | March -> "March" 113 | April -> "April" 114 | May -> "May" 115 | June -> "June" 116 | July -> "July" 117 | August -> "August" 118 | September -> "September" 119 | October -> "October" 120 | November -> "November" 121 | December -> "December" 122 | } 123 | } 124 | 125 | /// Returns the number for the month, where January is 1 and December is 12. 126 | /// 127 | /// # Examples 128 | /// 129 | /// ```gleam 130 | /// month_to_int(January) 131 | /// // -> 1 132 | /// ``` 133 | pub fn month_to_int(month: Month) -> Int { 134 | case month { 135 | January -> 1 136 | February -> 2 137 | March -> 3 138 | April -> 4 139 | May -> 5 140 | June -> 6 141 | July -> 7 142 | August -> 8 143 | September -> 9 144 | October -> 10 145 | November -> 11 146 | December -> 12 147 | } 148 | } 149 | 150 | /// Returns the month for a given number, where January is 1 and December is 12. 151 | /// 152 | /// # Examples 153 | /// 154 | /// ```gleam 155 | /// month_from_int(1) 156 | /// // -> Ok(January) 157 | /// ``` 158 | pub fn month_from_int(month: Int) -> Result(Month, Nil) { 159 | case month { 160 | 1 -> Ok(January) 161 | 2 -> Ok(February) 162 | 3 -> Ok(March) 163 | 4 -> Ok(April) 164 | 5 -> Ok(May) 165 | 6 -> Ok(June) 166 | 7 -> Ok(July) 167 | 8 -> Ok(August) 168 | 9 -> Ok(September) 169 | 10 -> Ok(October) 170 | 11 -> Ok(November) 171 | 12 -> Ok(December) 172 | _ -> Error(Nil) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/gleam/time/duration.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/order 3 | import gleam/string 4 | 5 | /// An amount of time, with up to nanosecond precision. 6 | /// 7 | /// This type does not represent calendar periods such as "1 month" or "2 8 | /// days". Those periods will be different lengths of time depending on which 9 | /// month or day they apply to. For example, January is longer than February. 10 | /// A different type should be used for calendar periods. 11 | /// 12 | pub opaque type Duration { 13 | // When compiling to JavaScript ints have limited precision and size. This 14 | // means that if we were to store the the timestamp in a single int the 15 | // duration would not be able to represent very large or small durations. 16 | // Durations are instead represented as a number of seconds and a number of 17 | // nanoseconds. 18 | // 19 | // If you have manually adjusted the seconds and nanoseconds values the 20 | // `normalise` function can be used to ensure the time is represented the 21 | // intended way, with `nanoseconds` being positive and less than 1 second. 22 | // 23 | // The duration is the sum of the seconds and the nanoseconds. 24 | Duration(seconds: Int, nanoseconds: Int) 25 | } 26 | 27 | /// A division of time. 28 | /// 29 | /// Note that not all months and years are the same length, so a reasonable 30 | /// average length is used by this module. 31 | /// 32 | pub type Unit { 33 | Nanosecond 34 | /// 1000 nanoseconds. 35 | Microsecond 36 | /// 1000 microseconds. 37 | Millisecond 38 | /// 1000 microseconds. 39 | Second 40 | /// 60 seconds. 41 | Minute 42 | /// 60 minutes. 43 | Hour 44 | /// 24 hours. 45 | Day 46 | /// 7 days. 47 | Week 48 | /// About 30.4375 days. Real calendar months vary in length. 49 | Month 50 | /// About 365.25 days. Real calendar years vary in length. 51 | Year 52 | } 53 | 54 | /// Convert a duration to a number of the largest number of a unit, serving as 55 | /// a rough description of the duration that a human can understand. 56 | /// 57 | /// The size used for each unit are described in the documentation for the 58 | /// `Unit` type. 59 | /// 60 | /// ```gleam 61 | /// seconds(125) 62 | /// |> approximate 63 | /// // -> #(2, Minute) 64 | /// ``` 65 | /// 66 | /// This function rounds _towards zero_. This means that if a duration is just 67 | /// short of 2 days then it will approximate to 1 day. 68 | /// 69 | /// ```gleam 70 | /// hours(47) 71 | /// |> approximate 72 | /// // -> #(1, Day) 73 | /// ``` 74 | /// 75 | pub fn approximate(duration: Duration) -> #(Int, Unit) { 76 | let Duration(seconds: s, nanoseconds: ns) = duration 77 | let minute = 60 78 | let hour = minute * 60 79 | let day = hour * 24 80 | let week = day * 7 81 | let year = day * 365 + hour * 6 82 | let month = year / 12 83 | let microsecond = 1000 84 | let millisecond = microsecond * 1000 85 | case Nil { 86 | _ if s < 0 -> { 87 | let #(amount, unit) = Duration(-s, -ns) |> normalise |> approximate 88 | #(-amount, unit) 89 | } 90 | _ if s >= year -> #(s / year, Year) 91 | _ if s >= month -> #(s / month, Month) 92 | _ if s >= week -> #(s / week, Week) 93 | _ if s >= day -> #(s / day, Day) 94 | _ if s >= hour -> #(s / hour, Hour) 95 | _ if s >= minute -> #(s / minute, Minute) 96 | _ if s > 0 -> #(s, Second) 97 | _ if ns >= millisecond -> #(ns / millisecond, Millisecond) 98 | _ if ns >= microsecond -> #(ns / microsecond, Microsecond) 99 | _ -> #(ns, Nanosecond) 100 | } 101 | } 102 | 103 | /// Ensure the duration is represented with `nanoseconds` being positive and 104 | /// less than 1 second. 105 | /// 106 | /// This function does not change the amount of time that the duratoin refers 107 | /// to, it only adjusts the values used to represent the time. 108 | /// 109 | fn normalise(duration: Duration) -> Duration { 110 | let multiplier = 1_000_000_000 111 | let nanoseconds = duration.nanoseconds % multiplier 112 | let overflow = duration.nanoseconds - nanoseconds 113 | let seconds = duration.seconds + overflow / multiplier 114 | case nanoseconds >= 0 { 115 | True -> Duration(seconds, nanoseconds) 116 | False -> Duration(seconds - 1, multiplier + nanoseconds) 117 | } 118 | } 119 | 120 | /// Compare one duration to another, indicating whether the first spans a 121 | /// larger amount of time (and so is greater) or smaller amount of time (and so 122 | /// is lesser) than the second. 123 | /// 124 | /// # Examples 125 | /// 126 | /// ```gleam 127 | /// compare(seconds(1), seconds(2)) 128 | /// // -> order.Lt 129 | /// ``` 130 | /// 131 | /// Whether a duration is negative or positive doesn't matter for comparing 132 | /// them, only the amount of time spanned matters. 133 | /// 134 | /// ```gleam 135 | /// compare(seconds(-2), seconds(1)) 136 | /// // -> order.Gt 137 | /// ``` 138 | /// 139 | pub fn compare(left: Duration, right: Duration) -> order.Order { 140 | let parts = fn(x: Duration) { 141 | case x.seconds >= 0 { 142 | True -> #(x.seconds, x.nanoseconds) 143 | False -> #(x.seconds * -1 - 1, 1_000_000_000 - x.nanoseconds) 144 | } 145 | } 146 | let #(ls, lns) = parts(left) 147 | let #(rs, rns) = parts(right) 148 | int.compare(ls, rs) 149 | |> order.break_tie(int.compare(lns, rns)) 150 | } 151 | 152 | /// Calculate the difference between two durations. 153 | /// 154 | /// This is effectively substracting the first duration from the second. 155 | /// 156 | /// # Examples 157 | /// 158 | /// ```gleam 159 | /// difference(seconds(1), seconds(5)) 160 | /// // -> seconds(4) 161 | /// ``` 162 | /// 163 | pub fn difference(left: Duration, right: Duration) -> Duration { 164 | Duration(right.seconds - left.seconds, right.nanoseconds - left.nanoseconds) 165 | |> normalise 166 | } 167 | 168 | /// Add two durations together. 169 | /// 170 | /// # Examples 171 | /// 172 | /// ```gleam 173 | /// add(seconds(1), seconds(5)) 174 | /// // -> seconds(6) 175 | /// ``` 176 | /// 177 | pub fn add(left: Duration, right: Duration) -> Duration { 178 | Duration(left.seconds + right.seconds, left.nanoseconds + right.nanoseconds) 179 | |> normalise 180 | } 181 | 182 | /// Convert the duration to an [ISO8601][1] formatted duration string. 183 | /// 184 | /// The ISO8601 duration format is ambiguous without context due to months and 185 | /// years having different lengths, and because of leap seconds. This function 186 | /// encodes the duration as days, hours, and seconds without any leap seconds. 187 | /// Be sure to take this into account when using the duration strings. 188 | /// 189 | /// [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations 190 | /// 191 | pub fn to_iso8601_string(duration: Duration) -> String { 192 | let split = fn(total, limit) { 193 | let amount = total % limit 194 | let remainder = { total - amount } / limit 195 | #(amount, remainder) 196 | } 197 | let #(seconds, rest) = split(duration.seconds, 60) 198 | let #(minutes, rest) = split(rest, 60) 199 | let #(hours, rest) = split(rest, 24) 200 | let days = rest 201 | let add = fn(out, value, unit) { 202 | case value { 203 | 0 -> out 204 | _ -> out <> int.to_string(value) <> unit 205 | } 206 | } 207 | let output = 208 | "P" 209 | |> add(days, "D") 210 | |> string.append("T") 211 | |> add(hours, "H") 212 | |> add(minutes, "M") 213 | case seconds, duration.nanoseconds { 214 | 0, 0 -> output 215 | _, 0 -> output <> int.to_string(seconds) <> "S" 216 | _, _ -> { 217 | let f = nanosecond_digits(duration.nanoseconds, 0, "") 218 | output <> int.to_string(seconds) <> "." <> f <> "S" 219 | } 220 | } 221 | } 222 | 223 | fn nanosecond_digits(n: Int, position: Int, acc: String) -> String { 224 | case position { 225 | 9 -> acc 226 | _ if acc == "" && n % 10 == 0 -> { 227 | nanosecond_digits(n / 10, position + 1, acc) 228 | } 229 | _ -> { 230 | let acc = int.to_string(n % 10) <> acc 231 | nanosecond_digits(n / 10, position + 1, acc) 232 | } 233 | } 234 | } 235 | 236 | /// Create a duration of a number of seconds. 237 | pub fn seconds(amount: Int) -> Duration { 238 | Duration(amount, 0) 239 | } 240 | 241 | /// Create a duration of a number of minutes. 242 | pub fn minutes(amount: Int) -> Duration { 243 | seconds(amount * 60) 244 | } 245 | 246 | /// Create a duration of a number of hours. 247 | pub fn hours(amount: Int) -> Duration { 248 | seconds(amount * 60 * 60) 249 | } 250 | 251 | /// Create a duration of a number of milliseconds. 252 | pub fn milliseconds(amount: Int) -> Duration { 253 | let remainder = amount % 1000 254 | let overflow = amount - remainder 255 | let nanoseconds = remainder * 1_000_000 256 | let seconds = overflow / 1000 257 | Duration(seconds, nanoseconds) 258 | |> normalise 259 | } 260 | 261 | /// Create a duration of a number of nanoseconds. 262 | /// 263 | /// # JavaScript int limitations 264 | /// 265 | /// Remember that JavaScript can only perfectly represent ints between positive 266 | /// and negative 9,007,199,254,740,991! If you use a single call to this 267 | /// function to create durations larger than that number of nanoseconds then 268 | /// you will likely not get exactly the value you expect. Use `seconds` and 269 | /// `milliseconds` as much as possible for large durations. 270 | /// 271 | pub fn nanoseconds(amount: Int) -> Duration { 272 | Duration(0, amount) 273 | |> normalise 274 | } 275 | 276 | /// Convert the duration to a number of seconds. 277 | /// 278 | /// There may be some small loss of precision due to `Duration` being 279 | /// nanosecond accurate and `Float` not being able to represent this. 280 | /// 281 | pub fn to_seconds(duration: Duration) -> Float { 282 | let seconds = int.to_float(duration.seconds) 283 | let nanoseconds = int.to_float(duration.nanoseconds) 284 | seconds +. { nanoseconds /. 1_000_000_000.0 } 285 | } 286 | 287 | /// Convert the duration to a number of seconds and nanoseconds. There is no 288 | /// loss of precision with this conversion on any target. 289 | /// 290 | pub fn to_seconds_and_nanoseconds(duration: Duration) -> #(Int, Int) { 291 | #(duration.seconds, duration.nanoseconds) 292 | } 293 | 294 | @internal 295 | pub const empty = Duration(0, 0) 296 | -------------------------------------------------------------------------------- /src/gleam/time/timestamp.gleam: -------------------------------------------------------------------------------- 1 | //// Welcome to the timestamp module! This module and its `Timestamp` type are 2 | //// what you will be using most commonly when working with time in Gleam. 3 | //// 4 | //// A timestamp represents a moment in time, represented as an amount of time 5 | //// since the calendar time 00:00:00 UTC on 1 January 1970, also known as the 6 | //// _Unix epoch_. 7 | //// 8 | //// # Wall clock time and monotonicity 9 | //// 10 | //// Time is very complicated, especially on computers! While they generally do 11 | //// a good job of keeping track of what the time is, computers can get 12 | //// out-of-sync and start to report a time that is too late or too early. Most 13 | //// computers use "network time protocol" to tell each other what they think 14 | //// the time is, and computers that realise they are running too fast or too 15 | //// slow will adjust their clock to correct it. When this happens it can seem 16 | //// to your program that the current time has changed, and it may have even 17 | //// jumped backwards in time! 18 | //// 19 | //// This measure of time is called _wall clock time_, and it is what people 20 | //// commonly think of when they think of time. It is important to be aware that 21 | //// it can go backwards, and your program must not rely on it only ever going 22 | //// forwards at a steady rate. For example, for tracking what order events happen 23 | //// in. 24 | //// 25 | //// This module uses wall clock time. If your program needs time values to always 26 | //// increase you will need a _monotonic_ time instead. It's uncommon that you 27 | //// would need monotonic time, one example might be if you're making a 28 | //// benchmarking framework. 29 | //// 30 | //// The exact way that time works will depend on what runtime you use. The 31 | //// Erlang documentation on time has a lot of detail about time generally as well 32 | //// as how it works on the BEAM, it is worth reading. 33 | //// . 34 | //// 35 | //// # Converting to local time 36 | //// 37 | //// Timestamps don't take into account time zones, so a moment in time will 38 | //// have the same timestamp value regardless of where you are in the world. To 39 | //// convert them to local time you will need to know the offset for the time 40 | //// zone you wish to use, likely from a time zone database. See the 41 | //// `gleam/time/calendar` module for more information. 42 | //// 43 | 44 | import gleam/bit_array 45 | import gleam/float 46 | import gleam/int 47 | import gleam/list 48 | import gleam/order 49 | import gleam/result 50 | import gleam/string 51 | import gleam/time/calendar 52 | import gleam/time/duration.{type Duration} 53 | 54 | const seconds_per_day: Int = 86_400 55 | 56 | const seconds_per_hour: Int = 3600 57 | 58 | const seconds_per_minute: Int = 60 59 | 60 | const nanoseconds_per_second: Int = 1_000_000_000 61 | 62 | /// The `:` character as a byte 63 | const byte_colon: Int = 0x3A 64 | 65 | /// The `-` character as a byte 66 | const byte_minus: Int = 0x2D 67 | 68 | /// The `0` character as a byte 69 | const byte_zero: Int = 0x30 70 | 71 | /// The `9` character as a byte 72 | const byte_nine: Int = 0x39 73 | 74 | /// The `t` character as a byte 75 | const byte_t_lowercase: Int = 0x74 76 | 77 | /// The `T` character as a byte 78 | const byte_t_uppercase: Int = 0x54 79 | 80 | /// The Julian seconds of the UNIX epoch (Julian day is 2_440_588) 81 | const julian_seconds_unix_epoch: Int = 210_866_803_200 82 | 83 | /// The main time type, which you should favour over other types such as 84 | /// calendar time types. It is efficient, unambiguous, and it is not possible 85 | /// to construct an invalid timestamp. 86 | /// 87 | /// The most common situation in which you may need a different time data 88 | /// structure is when you need to display time to human for them to read. When 89 | /// you need to do this convert the timestamp to calendar time when presenting 90 | /// it, but internally always keep the time as a timestamp. 91 | /// 92 | pub opaque type Timestamp { 93 | // When compiling to JavaScript ints have limited precision and size. This 94 | // means that if we were to store the the timestamp in a single int the 95 | // timestamp would not be able to represent times far in the future or in the 96 | // past, or distinguish between two times that are close together. Timestamps 97 | // are instead represented as a number of seconds and a number of nanoseconds. 98 | // 99 | // If you have manually adjusted the seconds and nanoseconds values the 100 | // `normalise` function can be used to ensure the time is represented the 101 | // intended way, with `nanoseconds` being positive and less than 1 second. 102 | // 103 | // The timestamp is the sum of the seconds and the nanoseconds. 104 | Timestamp(seconds: Int, nanoseconds: Int) 105 | } 106 | 107 | /// Ensure the time is represented with `nanoseconds` being positive and less 108 | /// than 1 second. 109 | /// 110 | /// This function does not change the time that the timestamp refers to, it 111 | /// only adjusts the values used to represent the time. 112 | /// 113 | fn normalise(timestamp: Timestamp) -> Timestamp { 114 | let multiplier = 1_000_000_000 115 | let nanoseconds = timestamp.nanoseconds % multiplier 116 | let overflow = timestamp.nanoseconds - nanoseconds 117 | let seconds = timestamp.seconds + overflow / multiplier 118 | case nanoseconds >= 0 { 119 | True -> Timestamp(seconds, nanoseconds) 120 | False -> Timestamp(seconds - 1, multiplier + nanoseconds) 121 | } 122 | } 123 | 124 | /// Compare one timestamp to another, indicating whether the first is further 125 | /// into the future (greater) or further into the past (lesser) than the 126 | /// second. 127 | /// 128 | /// # Examples 129 | /// 130 | /// ```gleam 131 | /// compare(from_unix_seconds(1), from_unix_seconds(2)) 132 | /// // -> order.Lt 133 | /// ``` 134 | /// 135 | pub fn compare(left: Timestamp, right: Timestamp) -> order.Order { 136 | order.break_tie( 137 | int.compare(left.seconds, right.seconds), 138 | int.compare(left.nanoseconds, right.nanoseconds), 139 | ) 140 | } 141 | 142 | /// Get the current system time. 143 | /// 144 | /// Note this time is not unique or monotonic, it could change at any time or 145 | /// even go backwards! The exact behaviour will depend on the runtime used. See 146 | /// the module documentation for more information. 147 | /// 148 | /// On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses 149 | /// [`Date.now`][2]. 150 | /// 151 | /// [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1 152 | /// [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now 153 | /// 154 | pub fn system_time() -> Timestamp { 155 | let #(seconds, nanoseconds) = get_system_time() 156 | normalise(Timestamp(seconds, nanoseconds)) 157 | } 158 | 159 | @external(erlang, "gleam_time_ffi", "system_time") 160 | @external(javascript, "../../gleam_time_ffi.mjs", "system_time") 161 | fn get_system_time() -> #(Int, Int) 162 | 163 | /// Calculate the difference between two timestamps. 164 | /// 165 | /// This is effectively substracting the first timestamp from the second. 166 | /// 167 | /// # Examples 168 | /// 169 | /// ```gleam 170 | /// difference(from_unix_seconds(1), from_unix_seconds(5)) 171 | /// // -> duration.seconds(4) 172 | /// ``` 173 | /// 174 | pub fn difference(left: Timestamp, right: Timestamp) -> Duration { 175 | let seconds = duration.seconds(right.seconds - left.seconds) 176 | let nanoseconds = duration.nanoseconds(right.nanoseconds - left.nanoseconds) 177 | duration.add(seconds, nanoseconds) 178 | } 179 | 180 | /// Add a duration to a timestamp. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ```gleam 185 | /// add(from_unix_seconds(1000), duration.seconds(5)) 186 | /// // -> from_unix_seconds(1005) 187 | /// ``` 188 | /// 189 | pub fn add(timestamp: Timestamp, duration: Duration) -> Timestamp { 190 | let #(seconds, nanoseconds) = duration.to_seconds_and_nanoseconds(duration) 191 | Timestamp(timestamp.seconds + seconds, timestamp.nanoseconds + nanoseconds) 192 | |> normalise 193 | } 194 | 195 | /// Convert a timestamp to a RFC 3339 formatted time string, with an offset 196 | /// supplied as an additional argument. 197 | /// 198 | /// The output of this function is also ISO 8601 compatible so long as the 199 | /// offset not negative. Offsets have at-most minute precision, so an offset 200 | /// with higher precision will be rounded to the nearest minute. 201 | /// 202 | /// If you are making an API such as a HTTP JSON API you are encouraged to use 203 | /// Unix timestamps instead of this format or ISO 8601. Unix timestamps are a 204 | /// better choice as they don't contain offset information. Consider: 205 | /// 206 | /// - UTC offsets are not time zones. This does not and cannot tell us the time 207 | /// zone in which the date was recorded. So what are we supposed to do with 208 | /// this information? 209 | /// - Users typically want dates formatted according to their local time zone. 210 | /// What if the provided UTC offset is different from the current user's time 211 | /// zone? What are we supposed to do with it then? 212 | /// - Despite it being useless (or worse, a source of bugs), the UTC offset 213 | /// creates a larger payload to transfer. 214 | /// 215 | /// They also uses more memory than a unix timestamp. The way they are better 216 | /// than Unix timestamp is that it is easier for a human to read them, but 217 | /// this is a hinderance that tooling can remedy, and APIs are not primarily 218 | /// for humans. 219 | /// 220 | /// # Examples 221 | /// 222 | /// ```gleam 223 | /// timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000) 224 | /// |> to_rfc3339(calendar.utc_offset) 225 | /// // -> "1970-01-01T00:16:40.123Z" 226 | /// ``` 227 | /// 228 | /// ```gleam 229 | /// timestamp.from_unix_seconds(1000) 230 | /// |> to_rfc3339(duration.seconds(3600)) 231 | /// // -> "1970-01-01T01:16:40+01:00" 232 | /// ``` 233 | /// 234 | pub fn to_rfc3339(timestamp: Timestamp, offset: Duration) -> String { 235 | let offset = duration_to_minutes(offset) 236 | let #(years, months, days, hours, minutes, seconds) = 237 | to_calendar_from_offset(timestamp, offset) 238 | 239 | let offset_minutes = modulo(offset, 60) 240 | let offset_hours = int.absolute_value(floored_div(offset, 60.0)) 241 | 242 | let n2 = pad_digit(_, to: 2) 243 | let n4 = pad_digit(_, to: 4) 244 | let out = "" 245 | let out = out <> n4(years) <> "-" <> n2(months) <> "-" <> n2(days) 246 | let out = out <> "T" 247 | let out = out <> n2(hours) <> ":" <> n2(minutes) <> ":" <> n2(seconds) 248 | let out = out <> show_second_fraction(timestamp.nanoseconds) 249 | case int.compare(offset, 0) { 250 | order.Eq -> out <> "Z" 251 | order.Gt -> out <> "+" <> n2(offset_hours) <> ":" <> n2(offset_minutes) 252 | order.Lt -> out <> "-" <> n2(offset_hours) <> ":" <> n2(offset_minutes) 253 | } 254 | } 255 | 256 | fn pad_digit(digit: Int, to desired_length: Int) -> String { 257 | int.to_string(digit) |> string.pad_start(desired_length, "0") 258 | } 259 | 260 | /// Convert a `Timestamp` to calendar time, suitable for presenting to a human 261 | /// to read. 262 | /// 263 | /// If you want a machine to use the time value then you should not use this 264 | /// function and should instead keep it as a timestamp. See the documentation 265 | /// for the `gleam/time/calendar` module for more information. 266 | /// 267 | /// # Examples 268 | /// 269 | /// ```gleam 270 | /// timestamp.from_unix_seconds(0) 271 | /// |> timestamp.to_calendar(calendar.utc_offset) 272 | /// // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0)) 273 | /// ``` 274 | /// 275 | pub fn to_calendar( 276 | timestamp: Timestamp, 277 | offset: Duration, 278 | ) -> #(calendar.Date, calendar.TimeOfDay) { 279 | let offset = duration_to_minutes(offset) 280 | let #(year, month, day, hours, minutes, seconds) = 281 | to_calendar_from_offset(timestamp, offset) 282 | let month = case month { 283 | 1 -> calendar.January 284 | 2 -> calendar.February 285 | 3 -> calendar.March 286 | 4 -> calendar.April 287 | 5 -> calendar.May 288 | 6 -> calendar.June 289 | 7 -> calendar.July 290 | 8 -> calendar.August 291 | 9 -> calendar.September 292 | 10 -> calendar.October 293 | 11 -> calendar.November 294 | _ -> calendar.December 295 | } 296 | let nanoseconds = timestamp.nanoseconds 297 | let date = calendar.Date(year:, month:, day:) 298 | let time = calendar.TimeOfDay(hours:, minutes:, seconds:, nanoseconds:) 299 | #(date, time) 300 | } 301 | 302 | fn duration_to_minutes(duration: duration.Duration) -> Int { 303 | float.round(duration.to_seconds(duration) /. 60.0) 304 | } 305 | 306 | fn to_calendar_from_offset( 307 | timestamp: Timestamp, 308 | offset: Int, 309 | ) -> #(Int, Int, Int, Int, Int, Int) { 310 | let total = timestamp.seconds + { offset * 60 } 311 | let seconds = modulo(total, 60) 312 | let total_minutes = floored_div(total, 60.0) 313 | let minutes = modulo(total, 60 * 60) / 60 314 | let hours = modulo(total, 24 * 60 * 60) / { 60 * 60 } 315 | let #(year, month, day) = to_civil(total_minutes) 316 | #(year, month, day, hours, minutes, seconds) 317 | } 318 | 319 | /// Create a `Timestamp` from a human-readable calendar time. 320 | /// 321 | /// # Examples 322 | /// 323 | /// ```gleam 324 | /// timestamp.from_calendar( 325 | /// date: calendar.Date(2024, calendar.December, 25), 326 | /// time: calendar.TimeOfDay(12, 30, 50, 0), 327 | /// offset: calendar.utc_offset, 328 | /// ) 329 | /// |> timestamp.to_rfc3339(calendar.utc_offset) 330 | /// // -> "2024-12-25T12:30:50Z" 331 | /// ``` 332 | /// 333 | pub fn from_calendar( 334 | date date: calendar.Date, 335 | time time: calendar.TimeOfDay, 336 | offset offset: Duration, 337 | ) -> Timestamp { 338 | let month = case date.month { 339 | calendar.January -> 1 340 | calendar.February -> 2 341 | calendar.March -> 3 342 | calendar.April -> 4 343 | calendar.May -> 5 344 | calendar.June -> 6 345 | calendar.July -> 7 346 | calendar.August -> 8 347 | calendar.September -> 9 348 | calendar.October -> 10 349 | calendar.November -> 11 350 | calendar.December -> 12 351 | } 352 | from_date_time( 353 | year: date.year, 354 | month:, 355 | day: date.day, 356 | hours: time.hours, 357 | minutes: time.minutes, 358 | seconds: time.seconds, 359 | second_fraction_as_nanoseconds: time.nanoseconds, 360 | offset_seconds: float.round(duration.to_seconds(offset)), 361 | ) 362 | } 363 | 364 | fn modulo(n: Int, m: Int) -> Int { 365 | case int.modulo(n, m) { 366 | Ok(n) -> n 367 | Error(_) -> 0 368 | } 369 | } 370 | 371 | fn floored_div(numerator: Int, denominator: Float) -> Int { 372 | let n = int.to_float(numerator) /. denominator 373 | float.round(float.floor(n)) 374 | } 375 | 376 | // Adapted from Elm's Time module 377 | fn to_civil(minutes: Int) -> #(Int, Int, Int) { 378 | let raw_day = floored_div(minutes, { 60.0 *. 24.0 }) + 719_468 379 | let era = case raw_day >= 0 { 380 | True -> raw_day / 146_097 381 | False -> { raw_day - 146_096 } / 146_097 382 | } 383 | let day_of_era = raw_day - era * 146_097 384 | let year_of_era = 385 | { 386 | day_of_era 387 | - { day_of_era / 1460 } 388 | + { day_of_era / 36_524 } 389 | - { day_of_era / 146_096 } 390 | } 391 | / 365 392 | let year = year_of_era + era * 400 393 | let day_of_year = 394 | day_of_era 395 | - { 365 * year_of_era + { year_of_era / 4 } - { year_of_era / 100 } } 396 | let mp = { 5 * day_of_year + 2 } / 153 397 | let month = case mp < 10 { 398 | True -> mp + 3 399 | False -> mp - 9 400 | } 401 | let day = day_of_year - { 153 * mp + 2 } / 5 + 1 402 | let year = case month <= 2 { 403 | True -> year + 1 404 | False -> year 405 | } 406 | #(year, month, day) 407 | } 408 | 409 | /// Converts nanoseconds into a `String` representation of fractional seconds. 410 | /// 411 | /// Assumes that `nanoseconds < 1_000_000_000`, which will be true for any 412 | /// normalised timestamp. 413 | /// 414 | fn show_second_fraction(nanoseconds: Int) -> String { 415 | case int.compare(nanoseconds, 0) { 416 | // Zero fractional seconds are not shown. 417 | order.Lt | order.Eq -> "" 418 | order.Gt -> { 419 | let second_fraction_part = { 420 | nanoseconds 421 | |> get_zero_padded_digits 422 | |> remove_trailing_zeros 423 | |> list.map(int.to_string) 424 | |> string.join("") 425 | } 426 | 427 | "." <> second_fraction_part 428 | } 429 | } 430 | } 431 | 432 | /// Given a list of digits, return new list with any trailing zeros removed. 433 | /// 434 | fn remove_trailing_zeros(digits: List(Int)) -> List(Int) { 435 | let reversed_digits = list.reverse(digits) 436 | 437 | do_remove_trailing_zeros(reversed_digits) 438 | } 439 | 440 | fn do_remove_trailing_zeros(reversed_digits) { 441 | case reversed_digits { 442 | [] -> [] 443 | [digit, ..digits] if digit == 0 -> do_remove_trailing_zeros(digits) 444 | reversed_digits -> list.reverse(reversed_digits) 445 | } 446 | } 447 | 448 | /// Returns the list of digits of `number`. If the number of digits is less 449 | /// than 9, the result is zero-padded at the front. 450 | /// 451 | fn get_zero_padded_digits(number: Int) -> List(Int) { 452 | do_get_zero_padded_digits(number, [], 0) 453 | } 454 | 455 | fn do_get_zero_padded_digits( 456 | number: Int, 457 | digits: List(Int), 458 | count: Int, 459 | ) -> List(Int) { 460 | case number { 461 | number if number <= 0 && count >= 9 -> digits 462 | number if number <= 0 -> 463 | // Zero-pad the digits at the front until we have at least 9 digits. 464 | do_get_zero_padded_digits(number, [0, ..digits], count + 1) 465 | number -> { 466 | let digit = number % 10 467 | let number = floored_div(number, 10.0) 468 | do_get_zero_padded_digits(number, [digit, ..digits], count + 1) 469 | } 470 | } 471 | } 472 | 473 | /// Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`. 474 | /// 475 | /// [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 476 | /// 477 | /// # Examples 478 | /// 479 | /// ```gleam 480 | /// let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:01Z") 481 | /// timestamp.to_unix_seconds_and_nanoseconds(ts) 482 | /// // -> #(1, 0) 483 | /// ``` 484 | /// 485 | /// Parsing an invalid timestamp returns an error. 486 | /// 487 | /// ```gleam 488 | /// let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31") 489 | /// ``` 490 | /// 491 | /// # Notes 492 | /// 493 | /// - Follows the grammar specified in section 5.6 Internet Date/Time Format of 494 | /// RFC 3339 . 495 | /// - The `T` and `Z` characters may alternatively be lower case `t` or `z`, 496 | /// respectively. 497 | /// - Full dates and full times must be separated by `T` or `t`, not any other 498 | /// character such as a space (` `). 499 | /// - Leap seconds rules are not considered. That is, any timestamp may 500 | /// specify digts `00` - `60` for the seconds. 501 | /// - Any part of a fractional second that cannot be represented in the 502 | /// nanosecond precision is tructated. That is, for the time string, 503 | /// `"1970-01-01T00:00:00.1234567899Z"`, the fractional second `.1234567899` 504 | /// will be represented as `123_456_789` in the `Timestamp`. 505 | /// 506 | pub fn parse_rfc3339(input: String) -> Result(Timestamp, Nil) { 507 | let bytes = bit_array.from_string(input) 508 | 509 | // Date 510 | use #(year, bytes) <- result.try(parse_year(from: bytes)) 511 | use bytes <- result.try(accept_byte(from: bytes, value: byte_minus)) 512 | use #(month, bytes) <- result.try(parse_month(from: bytes)) 513 | use bytes <- result.try(accept_byte(from: bytes, value: byte_minus)) 514 | use #(day, bytes) <- result.try(parse_day(from: bytes, year:, month:)) 515 | 516 | use bytes <- result.try(accept_date_time_separator(from: bytes)) 517 | 518 | // Time 519 | use #(hours, bytes) <- result.try(parse_hours(from: bytes)) 520 | use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) 521 | use #(minutes, bytes) <- result.try(parse_minutes(from: bytes)) 522 | use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) 523 | use #(seconds, bytes) <- result.try(parse_seconds(from: bytes)) 524 | use #(second_fraction_as_nanoseconds, bytes) <- result.try( 525 | parse_second_fraction_as_nanoseconds(from: bytes), 526 | ) 527 | 528 | // Offset 529 | use #(offset_seconds, bytes) <- result.try(parse_offset(from: bytes)) 530 | 531 | // Done 532 | use Nil <- result.try(accept_empty(bytes)) 533 | 534 | Ok(from_date_time( 535 | year:, 536 | month:, 537 | day:, 538 | hours:, 539 | minutes:, 540 | seconds:, 541 | second_fraction_as_nanoseconds:, 542 | offset_seconds:, 543 | )) 544 | } 545 | 546 | fn parse_year(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 547 | parse_digits(from: bytes, count: 4) 548 | } 549 | 550 | fn parse_month(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 551 | use #(month, bytes) <- result.try(parse_digits(from: bytes, count: 2)) 552 | case 1 <= month && month <= 12 { 553 | True -> Ok(#(month, bytes)) 554 | False -> Error(Nil) 555 | } 556 | } 557 | 558 | fn parse_day( 559 | from bytes: BitArray, 560 | year year, 561 | month month, 562 | ) -> Result(#(Int, BitArray), Nil) { 563 | use #(day, bytes) <- result.try(parse_digits(from: bytes, count: 2)) 564 | 565 | use max_day <- result.try(case month { 566 | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> Ok(31) 567 | 4 | 6 | 9 | 11 -> Ok(30) 568 | 2 -> { 569 | case is_leap_year(year) { 570 | True -> Ok(29) 571 | False -> Ok(28) 572 | } 573 | } 574 | _ -> Error(Nil) 575 | }) 576 | 577 | case 1 <= day && day <= max_day { 578 | True -> Ok(#(day, bytes)) 579 | False -> Error(Nil) 580 | } 581 | } 582 | 583 | // Implementation from RFC 3339 Appendix C 584 | fn is_leap_year(year: Int) -> Bool { 585 | year % 4 == 0 && { year % 100 != 0 || year % 400 == 0 } 586 | } 587 | 588 | fn parse_hours(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 589 | use #(hours, bytes) <- result.try(parse_digits(from: bytes, count: 2)) 590 | case 0 <= hours && hours <= 23 { 591 | True -> Ok(#(hours, bytes)) 592 | False -> Error(Nil) 593 | } 594 | } 595 | 596 | fn parse_minutes(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 597 | use #(minutes, bytes) <- result.try(parse_digits(from: bytes, count: 2)) 598 | case 0 <= minutes && minutes <= 59 { 599 | True -> Ok(#(minutes, bytes)) 600 | False -> Error(Nil) 601 | } 602 | } 603 | 604 | fn parse_seconds(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 605 | use #(seconds, bytes) <- result.try(parse_digits(from: bytes, count: 2)) 606 | // Max of 60 for leap seconds. We don't bother to check if this leap second 607 | // actually occurred in the past or not. 608 | case 0 <= seconds && seconds <= 60 { 609 | True -> Ok(#(seconds, bytes)) 610 | False -> Error(Nil) 611 | } 612 | } 613 | 614 | // Truncates any part of the fraction that is beyond the nanosecond precision. 615 | fn parse_second_fraction_as_nanoseconds(from bytes: BitArray) { 616 | case bytes { 617 | <<".", byte, remaining_bytes:bytes>> 618 | if byte_zero <= byte && byte <= byte_nine 619 | -> { 620 | do_parse_second_fraction_as_nanoseconds( 621 | from: <>, 622 | acc: 0, 623 | power: nanoseconds_per_second, 624 | ) 625 | } 626 | // bytes starts with a ".", which should introduce a fraction, but it does 627 | // not, and so it is an ill-formed input. 628 | <<".", _:bytes>> -> Error(Nil) 629 | // bytes does not start with a "." so there is no fraction. Call this 0 630 | // nanoseconds. 631 | _ -> Ok(#(0, bytes)) 632 | } 633 | } 634 | 635 | fn do_parse_second_fraction_as_nanoseconds( 636 | from bytes: BitArray, 637 | acc acc: Int, 638 | power power: Int, 639 | ) -> Result(#(Int, BitArray), a) { 640 | // Each digit place to the left in the fractional second is 10x fewer 641 | // nanoseconds. 642 | let power = power / 10 643 | 644 | case bytes { 645 | <> 646 | if byte_zero <= byte && byte <= byte_nine && power < 1 647 | -> { 648 | // We already have the max precision for nanoseconds. Truncate any 649 | // remaining digits. 650 | do_parse_second_fraction_as_nanoseconds( 651 | from: remaining_bytes, 652 | acc:, 653 | power:, 654 | ) 655 | } 656 | <> if byte_zero <= byte && byte <= byte_nine -> { 657 | // We have not yet reached the precision limit. Parse the next digit. 658 | let digit = byte - 0x30 659 | do_parse_second_fraction_as_nanoseconds( 660 | from: remaining_bytes, 661 | acc: acc + digit * power, 662 | power:, 663 | ) 664 | } 665 | _ -> Ok(#(acc, bytes)) 666 | } 667 | } 668 | 669 | fn parse_offset(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 670 | case bytes { 671 | <<"Z", remaining_bytes:bytes>> | <<"z", remaining_bytes:bytes>> -> 672 | Ok(#(0, remaining_bytes)) 673 | _ -> parse_numeric_offset(bytes) 674 | } 675 | } 676 | 677 | fn parse_numeric_offset(from bytes: BitArray) -> Result(#(Int, BitArray), Nil) { 678 | use #(sign, bytes) <- result.try(parse_sign(from: bytes)) 679 | use #(hours, bytes) <- result.try(parse_hours(from: bytes)) 680 | use bytes <- result.try(accept_byte(from: bytes, value: byte_colon)) 681 | use #(minutes, bytes) <- result.try(parse_minutes(from: bytes)) 682 | 683 | let offset_seconds = offset_to_seconds(sign, hours:, minutes:) 684 | 685 | Ok(#(offset_seconds, bytes)) 686 | } 687 | 688 | fn parse_sign(from bytes) { 689 | case bytes { 690 | <<"+", remaining_bytes:bytes>> -> Ok(#("+", remaining_bytes)) 691 | <<"-", remaining_bytes:bytes>> -> Ok(#("-", remaining_bytes)) 692 | _ -> Error(Nil) 693 | } 694 | } 695 | 696 | fn offset_to_seconds(sign, hours hours, minutes minutes) { 697 | let abs_seconds = hours * seconds_per_hour + minutes * seconds_per_minute 698 | 699 | case sign { 700 | "-" -> -abs_seconds 701 | _ -> abs_seconds 702 | } 703 | } 704 | 705 | /// Parse and return the given number of digits from the given bytes. 706 | /// 707 | fn parse_digits( 708 | from bytes: BitArray, 709 | count count: Int, 710 | ) -> Result(#(Int, BitArray), Nil) { 711 | do_parse_digits(from: bytes, count:, acc: 0, k: 0) 712 | } 713 | 714 | fn do_parse_digits( 715 | from bytes: BitArray, 716 | count count: Int, 717 | acc acc: Int, 718 | k k: Int, 719 | ) -> Result(#(Int, BitArray), Nil) { 720 | case bytes { 721 | _ if k >= count -> Ok(#(acc, bytes)) 722 | <> if byte_zero <= byte && byte <= byte_nine -> 723 | do_parse_digits( 724 | from: remaining_bytes, 725 | count:, 726 | acc: acc * 10 + { byte - 0x30 }, 727 | k: k + 1, 728 | ) 729 | _ -> Error(Nil) 730 | } 731 | } 732 | 733 | /// Accept the given value from `bytes` and move past it if found. 734 | /// 735 | fn accept_byte(from bytes: BitArray, value value: Int) -> Result(BitArray, Nil) { 736 | case bytes { 737 | <> if byte == value -> Ok(remaining_bytes) 738 | _ -> Error(Nil) 739 | } 740 | } 741 | 742 | fn accept_date_time_separator(from bytes: BitArray) -> Result(BitArray, Nil) { 743 | case bytes { 744 | <> 745 | if byte == byte_t_uppercase || byte == byte_t_lowercase 746 | -> Ok(remaining_bytes) 747 | _ -> Error(Nil) 748 | } 749 | } 750 | 751 | fn accept_empty(from bytes: BitArray) -> Result(Nil, Nil) { 752 | case bytes { 753 | <<>> -> Ok(Nil) 754 | _ -> Error(Nil) 755 | } 756 | } 757 | 758 | /// Note: The caller of this function must ensure that all inputs are valid. 759 | /// 760 | fn from_date_time( 761 | year year: Int, 762 | month month: Int, 763 | day day: Int, 764 | hours hours: Int, 765 | minutes minutes: Int, 766 | seconds seconds: Int, 767 | second_fraction_as_nanoseconds second_fraction_as_nanoseconds: Int, 768 | offset_seconds offset_seconds: Int, 769 | ) -> Timestamp { 770 | let julian_seconds = 771 | julian_seconds_from_parts(year:, month:, day:, hours:, minutes:, seconds:) 772 | 773 | let julian_seconds_since_epoch = julian_seconds - julian_seconds_unix_epoch 774 | 775 | Timestamp( 776 | seconds: julian_seconds_since_epoch - offset_seconds, 777 | nanoseconds: second_fraction_as_nanoseconds, 778 | ) 779 | |> normalise 780 | } 781 | 782 | /// `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)` 783 | /// returns the number of Julian 784 | /// seconds represented by the given arguments. 785 | /// 786 | /// Note: It is the callers responsibility to ensure the inputs are valid. 787 | /// 788 | /// See https://www.tondering.dk/claus/cal/julperiod.php#formula 789 | /// 790 | fn julian_seconds_from_parts( 791 | year year: Int, 792 | month month: Int, 793 | day day: Int, 794 | hours hours: Int, 795 | minutes minutes: Int, 796 | seconds seconds: Int, 797 | ) { 798 | let julian_day_seconds = 799 | julian_day_from_ymd(year:, month:, day:) * seconds_per_day 800 | 801 | julian_day_seconds 802 | + { hours * seconds_per_hour } 803 | + { minutes * seconds_per_minute } 804 | + seconds 805 | } 806 | 807 | /// Note: It is the callers responsibility to ensure the inputs are valid. 808 | /// 809 | /// See https://www.tondering.dk/claus/cal/julperiod.php#formula 810 | /// 811 | fn julian_day_from_ymd(year year: Int, month month: Int, day day: Int) -> Int { 812 | let adjustment = { 14 - month } / 12 813 | let adjusted_year = year + 4800 - adjustment 814 | let adjusted_month = month + 12 * adjustment - 3 815 | 816 | day 817 | + { { 153 * adjusted_month } + 2 } 818 | / 5 819 | + 365 820 | * adjusted_year 821 | + { adjusted_year / 4 } 822 | - { adjusted_year / 100 } 823 | + { adjusted_year / 400 } 824 | - 32_045 825 | } 826 | 827 | /// Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January 828 | /// 1970. 829 | /// 830 | pub fn from_unix_seconds(seconds: Int) -> Timestamp { 831 | Timestamp(seconds, 0) 832 | } 833 | 834 | /// Create a timestamp from a number of seconds and nanoseconds since 00:00:00 835 | /// UTC on 1 January 1970. 836 | /// 837 | /// # JavaScript int limitations 838 | /// 839 | /// Remember that JavaScript can only perfectly represent ints between positive 840 | /// and negative 9,007,199,254,740,991! If you only use the nanosecond field 841 | /// then you will almost certainly not get the date value you want due to this 842 | /// loss of precision. Always use seconds primarily and then use nanoseconds 843 | /// for the final sub-second adjustment. 844 | /// 845 | pub fn from_unix_seconds_and_nanoseconds( 846 | seconds seconds: Int, 847 | nanoseconds nanoseconds: Int, 848 | ) -> Timestamp { 849 | Timestamp(seconds, nanoseconds) 850 | |> normalise 851 | } 852 | 853 | /// Convert the timestamp to a number of seconds since 00:00:00 UTC on 1 854 | /// January 1970. 855 | /// 856 | /// There may be some small loss of precision due to `Timestamp` being 857 | /// nanosecond accurate and `Float` not being able to represent this. 858 | /// 859 | pub fn to_unix_seconds(timestamp: Timestamp) -> Float { 860 | let seconds = int.to_float(timestamp.seconds) 861 | let nanoseconds = int.to_float(timestamp.nanoseconds) 862 | seconds +. { nanoseconds /. 1_000_000_000.0 } 863 | } 864 | 865 | /// Convert the timestamp to a number of seconds and nanoseconds since 00:00:00 866 | /// UTC on 1 January 1970. There is no loss of precision with this conversion 867 | /// on any target. 868 | pub fn to_unix_seconds_and_nanoseconds(timestamp: Timestamp) -> #(Int, Int) { 869 | #(timestamp.seconds, timestamp.nanoseconds) 870 | } 871 | -------------------------------------------------------------------------------- /src/gleam_time_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(gleam_time_ffi). 2 | -export([system_time/0, local_time_offset_seconds/0]). 3 | 4 | system_time() -> 5 | {0, erlang:system_time(nanosecond)}. 6 | 7 | local_time_offset_seconds() -> 8 | Utc = calendar:universal_time(), 9 | Local = calendar:local_time(), 10 | UtcSeconds = calendar:datetime_to_gregorian_seconds(Utc), 11 | LocalSeconds = calendar:datetime_to_gregorian_seconds(Local), 12 | LocalSeconds - UtcSeconds. 13 | -------------------------------------------------------------------------------- /src/gleam_time_ffi.mjs: -------------------------------------------------------------------------------- 1 | export function system_time() { 2 | const now = Date.now(); 3 | const milliseconds = now % 1_000; 4 | const nanoseconds = milliseconds * 1000_000; 5 | const seconds = (now - milliseconds) / 1_000; 6 | return [seconds, nanoseconds]; 7 | } 8 | 9 | export function local_time_offset_seconds() { 10 | return new Date().getTimezoneOffset() * -60; 11 | } 12 | -------------------------------------------------------------------------------- /test/gleam/time/calendar_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/float 2 | import gleam/time/calendar 3 | import gleam/time/duration 4 | import gleeunit/should 5 | 6 | pub fn local_offset_test() { 7 | let hours = 8 | float.round(duration.to_seconds(calendar.local_offset())) / 60 / 60 9 | should.be_true(hours > -24) 10 | should.be_true(hours < 24) 11 | should.be_true(calendar.local_offset() == calendar.local_offset()) 12 | } 13 | 14 | pub fn utc_offset_test() { 15 | calendar.utc_offset 16 | |> should.equal(duration.seconds(0)) 17 | } 18 | 19 | pub fn month_to_string_test() { 20 | calendar.April 21 | |> calendar.month_to_string 22 | |> should.equal("April") 23 | } 24 | -------------------------------------------------------------------------------- /test/gleam/time/duration_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/order 3 | import gleam/time/duration 4 | import gleeunit/should 5 | import qcheck 6 | 7 | pub fn add_property_0_test() { 8 | use #(x, y) <- qcheck.given(qcheck.tuple2( 9 | qcheck.uniform_int(), 10 | qcheck.uniform_int(), 11 | )) 12 | let expected = duration.nanoseconds(x + y) 13 | let actual = duration.nanoseconds(x) |> duration.add(duration.nanoseconds(y)) 14 | should.equal(expected, actual) 15 | } 16 | 17 | pub fn add_property_1_test() { 18 | use #(x, y) <- qcheck.given(qcheck.tuple2( 19 | qcheck.uniform_int(), 20 | qcheck.uniform_int(), 21 | )) 22 | let expected = duration.seconds(x + y) 23 | let actual = duration.seconds(x) |> duration.add(duration.seconds(y)) 24 | should.equal(expected, actual) 25 | } 26 | 27 | pub fn add_0_test() { 28 | duration.nanoseconds(500_000_000) 29 | |> duration.add(duration.nanoseconds(500_000_000)) 30 | |> should.equal(duration.seconds(1)) 31 | } 32 | 33 | pub fn add_1_test() { 34 | duration.nanoseconds(-500_000_000) 35 | |> duration.add(duration.nanoseconds(-500_000_000)) 36 | |> should.equal(duration.seconds(-1)) 37 | } 38 | 39 | pub fn add_2_test() { 40 | duration.nanoseconds(-500_000_000) 41 | |> duration.add(duration.nanoseconds(500_000_000)) 42 | |> should.equal(duration.seconds(0)) 43 | } 44 | 45 | pub fn add_3_test() { 46 | duration.seconds(4) 47 | |> duration.add(duration.nanoseconds(4_000_000_000)) 48 | |> should.equal(duration.seconds(8)) 49 | } 50 | 51 | pub fn add_4_test() { 52 | duration.seconds(4) 53 | |> duration.add(duration.nanoseconds(-5_000_000_000)) 54 | |> should.equal(duration.seconds(-1)) 55 | } 56 | 57 | pub fn add_5_test() { 58 | duration.nanoseconds(4_000_000) 59 | |> duration.add(duration.milliseconds(4)) 60 | |> should.equal(duration.milliseconds(8)) 61 | } 62 | 63 | pub fn add_6_test() { 64 | duration.nanoseconds(-2) 65 | |> duration.add(duration.nanoseconds(-3)) 66 | |> should.equal(duration.nanoseconds(-5)) 67 | } 68 | 69 | pub fn add_7_test() { 70 | duration.nanoseconds(-1) 71 | |> duration.add(duration.nanoseconds(-1_000_000_000)) 72 | |> should.equal(duration.nanoseconds(-1_000_000_001)) 73 | } 74 | 75 | pub fn add_8_test() { 76 | duration.nanoseconds(1) 77 | |> duration.add(duration.nanoseconds(-1_000_000_000)) 78 | |> should.equal(duration.nanoseconds(-999_999_999)) 79 | } 80 | 81 | pub fn to_seconds_and_nanoseconds_0_test() { 82 | duration.seconds(1) 83 | |> duration.to_seconds_and_nanoseconds() 84 | |> should.equal(#(1, 0)) 85 | } 86 | 87 | pub fn to_seconds_and_nanoseconds_1_test() { 88 | duration.milliseconds(1) 89 | |> duration.to_seconds_and_nanoseconds() 90 | |> should.equal(#(0, 1_000_000)) 91 | } 92 | 93 | pub fn to_seconds_0_test() { 94 | duration.seconds(1) 95 | |> duration.to_seconds 96 | |> should.equal(1.0) 97 | } 98 | 99 | pub fn to_seconds_1_test() { 100 | duration.seconds(2) 101 | |> duration.to_seconds 102 | |> should.equal(2.0) 103 | } 104 | 105 | pub fn to_seconds_2_test() { 106 | duration.milliseconds(500) 107 | |> duration.to_seconds 108 | |> should.equal(0.5) 109 | } 110 | 111 | pub fn to_seconds_3_test() { 112 | duration.milliseconds(5100) 113 | |> duration.to_seconds 114 | |> should.equal(5.1) 115 | } 116 | 117 | pub fn to_seconds_4_test() { 118 | duration.nanoseconds(500) 119 | |> duration.to_seconds 120 | |> should.equal(0.0000005) 121 | } 122 | 123 | pub fn compare_property_0_test() { 124 | use #(x, y) <- qcheck.given(qcheck.tuple2( 125 | qcheck.bounded_int(0, 999_999), 126 | qcheck.bounded_int(0, 999_999), 127 | )) 128 | // Durations of seconds 129 | let tx = duration.seconds(x) 130 | let ty = duration.seconds(y) 131 | should.equal(duration.compare(tx, ty), int.compare(x, y)) 132 | } 133 | 134 | pub fn compare_property_1_test() { 135 | use #(x, y) <- qcheck.given(qcheck.tuple2( 136 | qcheck.bounded_int(0, 999_999), 137 | qcheck.bounded_int(0, 999_999), 138 | )) 139 | // Durations of nanoseconds 140 | let tx = duration.nanoseconds(x) 141 | let ty = duration.nanoseconds(y) 142 | should.equal(duration.compare(tx, ty), int.compare(x, y)) 143 | } 144 | 145 | pub fn compare_property_2_test() { 146 | use x <- qcheck.given(qcheck.uniform_int()) 147 | let tx = duration.nanoseconds(x) 148 | should.equal(duration.compare(tx, tx), order.Eq) 149 | } 150 | 151 | pub fn compare_property_3_test() { 152 | use #(x, y) <- qcheck.given(qcheck.tuple2( 153 | qcheck.bounded_int(0, 999_999), 154 | qcheck.bounded_int(0, 999_999), 155 | )) 156 | let tx = duration.nanoseconds(x) 157 | // It doesn't matter if a duration is positive or negative, it's the amount 158 | // of time spanned that matters. 159 | // 160 | // Second durations 161 | should.equal( 162 | duration.compare(tx, duration.seconds(y)), 163 | duration.compare(tx, duration.seconds(0 - y)), 164 | ) 165 | } 166 | 167 | pub fn compare_property_4_test() { 168 | use #(x, y) <- qcheck.given(qcheck.tuple2( 169 | qcheck.bounded_int(0, 999_999), 170 | qcheck.bounded_int(0, 999_999), 171 | )) 172 | let tx = duration.nanoseconds(x) 173 | // It doesn't matter if a duration is positive or negative, it's the amount 174 | // of time spanned that matters. 175 | // 176 | // Nanosecond durations 177 | should.equal( 178 | duration.compare(tx, duration.nanoseconds(y)), 179 | duration.compare(tx, duration.nanoseconds(y * -1)), 180 | ) 181 | } 182 | 183 | pub fn compare_0_test() { 184 | duration.compare(duration.seconds(1), duration.seconds(1)) 185 | |> should.equal(order.Eq) 186 | } 187 | 188 | pub fn compare_1_test() { 189 | duration.compare(duration.seconds(2), duration.seconds(1)) 190 | |> should.equal(order.Gt) 191 | } 192 | 193 | pub fn compare_2_test() { 194 | duration.compare(duration.seconds(0), duration.seconds(1)) 195 | |> should.equal(order.Lt) 196 | } 197 | 198 | pub fn compare_3_test() { 199 | duration.compare(duration.nanoseconds(999_999_999), duration.seconds(1)) 200 | |> should.equal(order.Lt) 201 | } 202 | 203 | pub fn compare_4_test() { 204 | duration.compare(duration.nanoseconds(1_000_000_001), duration.seconds(1)) 205 | |> should.equal(order.Gt) 206 | } 207 | 208 | pub fn compare_5_test() { 209 | duration.compare(duration.nanoseconds(1_000_000_000), duration.seconds(1)) 210 | |> should.equal(order.Eq) 211 | } 212 | 213 | pub fn compare_6_test() { 214 | duration.compare(duration.seconds(-10), duration.seconds(-20)) 215 | |> should.equal(order.Lt) 216 | } 217 | 218 | pub fn compare_7_test() { 219 | duration.compare( 220 | duration.seconds(1) |> duration.add(duration.nanoseconds(1)), 221 | duration.seconds(-1) |> duration.add(duration.nanoseconds(-1)), 222 | ) 223 | |> should.equal(order.Eq) 224 | } 225 | 226 | pub fn to_iso8601_string_0_test() { 227 | duration.seconds(42) 228 | |> duration.to_iso8601_string 229 | |> should.equal("PT42S") 230 | } 231 | 232 | pub fn to_iso8601_string_1_test() { 233 | duration.seconds(60) 234 | |> duration.to_iso8601_string 235 | |> should.equal("PT1M") 236 | } 237 | 238 | pub fn to_iso8601_string_2_test() { 239 | duration.seconds(362) 240 | |> duration.to_iso8601_string 241 | |> should.equal("PT6M2S") 242 | } 243 | 244 | pub fn to_iso8601_string_3_test() { 245 | duration.seconds(60 * 60) 246 | |> duration.to_iso8601_string 247 | |> should.equal("PT1H") 248 | } 249 | 250 | pub fn to_iso8601_string_4_test() { 251 | duration.seconds(60 * 60 * 24) 252 | |> duration.to_iso8601_string 253 | |> should.equal("P1DT") 254 | } 255 | 256 | pub fn to_iso8601_string_5_test() { 257 | duration.seconds(60 * 60 * 24 * 50) 258 | |> duration.to_iso8601_string 259 | |> should.equal("P50DT") 260 | } 261 | 262 | pub fn to_iso8601_string_6_test() { 263 | // We don't use years because you can't tell how long a year is in seconds 264 | // without context. _Which_ year? They have different lengths. 265 | duration.seconds(60 * 60 * 24 * 365) 266 | |> duration.to_iso8601_string 267 | |> should.equal("P365DT") 268 | } 269 | 270 | pub fn to_iso8601_string_7_test() { 271 | let year = 60 * 60 * 24 * 365 272 | let hour = 60 * 60 273 | duration.seconds(year + hour * 3 + 66) 274 | |> duration.to_iso8601_string 275 | |> should.equal("P365DT3H1M6S") 276 | } 277 | 278 | pub fn to_iso8601_string_8_test() { 279 | duration.milliseconds(1000) 280 | |> duration.to_iso8601_string 281 | |> should.equal("PT1S") 282 | } 283 | 284 | pub fn to_iso8601_string_9_test() { 285 | duration.milliseconds(100) 286 | |> duration.to_iso8601_string 287 | |> should.equal("PT0.1S") 288 | } 289 | 290 | pub fn to_iso8601_string_10_test() { 291 | duration.milliseconds(10) 292 | |> duration.to_iso8601_string 293 | |> should.equal("PT0.01S") 294 | } 295 | 296 | pub fn to_iso8601_string_11_test() { 297 | duration.milliseconds(1) 298 | |> duration.to_iso8601_string 299 | |> should.equal("PT0.001S") 300 | } 301 | 302 | pub fn to_iso8601_string_12_test() { 303 | duration.nanoseconds(1_000_000) 304 | |> duration.to_iso8601_string 305 | |> should.equal("PT0.001S") 306 | } 307 | 308 | pub fn to_iso8601_string_13_test() { 309 | duration.nanoseconds(100_000) 310 | |> duration.to_iso8601_string 311 | |> should.equal("PT0.0001S") 312 | } 313 | 314 | pub fn to_iso8601_string_14_test() { 315 | duration.nanoseconds(10_000) 316 | |> duration.to_iso8601_string 317 | |> should.equal("PT0.00001S") 318 | } 319 | 320 | pub fn to_iso8601_string_15_test() { 321 | duration.nanoseconds(1000) 322 | |> duration.to_iso8601_string 323 | |> should.equal("PT0.000001S") 324 | } 325 | 326 | pub fn to_iso8601_string_16_test() { 327 | duration.nanoseconds(100) 328 | |> duration.to_iso8601_string 329 | |> should.equal("PT0.0000001S") 330 | } 331 | 332 | pub fn to_iso8601_string_17_test() { 333 | duration.nanoseconds(10) 334 | |> duration.to_iso8601_string 335 | |> should.equal("PT0.00000001S") 336 | } 337 | 338 | pub fn to_iso8601_string_18_test() { 339 | duration.nanoseconds(1) 340 | |> duration.to_iso8601_string 341 | |> should.equal("PT0.000000001S") 342 | } 343 | 344 | pub fn to_iso8601_string_19_test() { 345 | duration.nanoseconds(123_456_789) 346 | |> duration.to_iso8601_string 347 | |> should.equal("PT0.123456789S") 348 | } 349 | 350 | pub fn difference_0_test() { 351 | duration.difference(duration.seconds(100), duration.seconds(250)) 352 | |> should.equal(duration.seconds(150)) 353 | } 354 | 355 | pub fn difference_1_test() { 356 | duration.difference(duration.seconds(250), duration.seconds(100)) 357 | |> should.equal(duration.seconds(-150)) 358 | } 359 | 360 | pub fn difference_2_test() { 361 | duration.difference(duration.seconds(2), duration.milliseconds(3500)) 362 | |> should.equal(duration.milliseconds(1500)) 363 | } 364 | 365 | pub fn approximate_0_test() { 366 | duration.minutes(10) 367 | |> duration.approximate 368 | |> should.equal(#(10, duration.Minute)) 369 | } 370 | 371 | pub fn approximate_1_test() { 372 | duration.seconds(30) 373 | |> duration.approximate 374 | |> should.equal(#(30, duration.Second)) 375 | } 376 | 377 | pub fn approximate_2_test() { 378 | duration.hours(23) 379 | |> duration.approximate 380 | |> should.equal(#(23, duration.Hour)) 381 | } 382 | 383 | pub fn approximate_3_test() { 384 | duration.hours(24) 385 | |> duration.approximate 386 | |> should.equal(#(1, duration.Day)) 387 | } 388 | 389 | pub fn approximate_4_test() { 390 | duration.hours(48) 391 | |> duration.approximate 392 | |> should.equal(#(2, duration.Day)) 393 | } 394 | 395 | pub fn approximate_5_test() { 396 | duration.hours(47) 397 | |> duration.approximate 398 | |> should.equal(#(1, duration.Day)) 399 | } 400 | 401 | pub fn approximate_6_test() { 402 | duration.hours(24 * 7) 403 | |> duration.approximate 404 | |> should.equal(#(1, duration.Week)) 405 | } 406 | 407 | pub fn approximate_7_test() { 408 | duration.hours(24 * 30) 409 | |> duration.approximate 410 | |> should.equal(#(4, duration.Week)) 411 | } 412 | 413 | pub fn approximate_8_test() { 414 | duration.hours(24 * 31) 415 | |> duration.approximate 416 | |> should.equal(#(1, duration.Month)) 417 | } 418 | 419 | pub fn approximate_9_test() { 420 | duration.hours(24 * 66) 421 | |> duration.approximate 422 | |> should.equal(#(2, duration.Month)) 423 | } 424 | 425 | pub fn approximate_10_test() { 426 | duration.hours(24 * 365) 427 | |> duration.approximate 428 | |> should.equal(#(11, duration.Month)) 429 | } 430 | 431 | pub fn approximate_11_test() { 432 | duration.hours(24 * 365 + 5) 433 | |> duration.approximate 434 | |> should.equal(#(11, duration.Month)) 435 | } 436 | 437 | pub fn approximate_12_test() { 438 | duration.hours(24 * 365 + 6) 439 | |> duration.approximate 440 | |> should.equal(#(1, duration.Year)) 441 | } 442 | 443 | pub fn approximate_13_test() { 444 | duration.hours(5 * 24 * 365 + 6) 445 | |> duration.approximate 446 | |> should.equal(#(4, duration.Year)) 447 | } 448 | 449 | pub fn approximate_14_test() { 450 | duration.hours(-5 * 24 * 365 + 6) 451 | |> duration.approximate 452 | |> should.equal(#(-4, duration.Year)) 453 | } 454 | 455 | pub fn approximate_15_test() { 456 | duration.milliseconds(1) 457 | |> duration.approximate 458 | |> should.equal(#(1, duration.Millisecond)) 459 | } 460 | 461 | pub fn approximate_16_test() { 462 | duration.milliseconds(-1) 463 | |> duration.approximate 464 | |> should.equal(#(-1, duration.Millisecond)) 465 | } 466 | 467 | pub fn approximate_17_test() { 468 | duration.milliseconds(999) 469 | |> duration.approximate 470 | |> should.equal(#(999, duration.Millisecond)) 471 | } 472 | 473 | pub fn approximate_18_test() { 474 | duration.nanoseconds(1000) 475 | |> duration.approximate 476 | |> should.equal(#(1, duration.Microsecond)) 477 | } 478 | 479 | pub fn approximate_19_test() { 480 | duration.nanoseconds(-1000) 481 | |> duration.approximate 482 | |> should.equal(#(-1, duration.Microsecond)) 483 | } 484 | 485 | pub fn approximate_20_test() { 486 | duration.nanoseconds(23_000) 487 | |> duration.approximate 488 | |> should.equal(#(23, duration.Microsecond)) 489 | } 490 | 491 | pub fn approximate_21_test() { 492 | duration.nanoseconds(999) 493 | |> duration.approximate 494 | |> should.equal(#(999, duration.Nanosecond)) 495 | } 496 | 497 | pub fn approximate_22_test() { 498 | duration.nanoseconds(-999) 499 | |> duration.approximate 500 | |> should.equal(#(-999, duration.Nanosecond)) 501 | } 502 | 503 | pub fn approximate_23_test() { 504 | duration.nanoseconds(0) 505 | |> duration.approximate 506 | |> should.equal(#(0, duration.Nanosecond)) 507 | } 508 | -------------------------------------------------------------------------------- /test/gleam/time/generators.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/option 3 | import gleam/regexp 4 | import gleam/string 5 | import gleam/time/timestamp 6 | import qcheck 7 | 8 | /// Generate timestamps representing instants in the range `0000-01-01T00:00:00Z` 9 | /// to `9999-12-31T23:59:59.999999999Z`. 10 | /// 11 | pub fn timestamp() { 12 | // prng can only generate good integers in the range 13 | // [-2_147_483_648, 2_147_483_647] 14 | // 15 | // So we must get to the range we need by generating the values in parts, then 16 | // adding them together. 17 | // 18 | // The smallest number of milliseconds we need to generate: 19 | // > d=new Date("0000-01-01T00:00:00"); d.getTime() 20 | // -62_167_201_438_000 ms 21 | // -62_167_201_438 s 22 | // 23 | // The largest number of milliseconds without leap second we need to generate: 24 | // > d=new Date("9999-12-31T23:59:59"); d.getTime() 25 | // 253_402_318_799_000 ms 26 | // 253_402_318_799 s 27 | // 28 | 29 | let megasecond_generator = { 30 | use second <- qcheck.map(qcheck.bounded_int(-62_167, 253_402)) 31 | second * 1_000_000 32 | } 33 | 34 | let second_generator = qcheck.bounded_int(-201_438, 318_799) 35 | 36 | use megasecond, second, nanosecond <- qcheck.map3( 37 | megasecond_generator, 38 | second_generator, 39 | qcheck.bounded_int(0, 999_999_999), 40 | ) 41 | let total_seconds = megasecond + second 42 | 43 | let assert True = 44 | -62_167_201_438 <= total_seconds && total_seconds <= 253_402_318_799 45 | 46 | timestamp.from_unix_seconds_and_nanoseconds(total_seconds, nanosecond) 47 | } 48 | 49 | pub fn rfc3339( 50 | with_leap_second with_leap_second: Bool, 51 | second_fraction_spec second_fraction_spec: SecondFractionSpec, 52 | avoid_erlang_errors avoid_erlang_errors: Bool, 53 | ) -> qcheck.Generator(String) { 54 | use full_date, t, full_time <- qcheck.map3( 55 | full_date_generator(), 56 | t_generator(), 57 | full_time_generator(with_leap_second, second_fraction_spec), 58 | ) 59 | let date_time = full_date <> t <> full_time 60 | 61 | // There are valid timestamps that the Erlang oracle will fail to parse, but 62 | // that timestamp.parse_rfc3339 and the JS oracle correctly parse. E.g., 63 | // "0000-01-01T00:00:00+00:01" and greater offsets, or with 64 | // 9999-12-31T23:59:59-00:01 and lesser offsets. For some of the property 65 | // tests, we need to account for this. 66 | // 67 | // This is a very rare occurence, but we can check it heree to avoid flaky 68 | // failures. 69 | case avoid_erlang_errors { 70 | True -> { 71 | let assert Ok(re_0000) = 72 | regexp.from_string( 73 | "(0000-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2})\\+[0-9]{2}:[0-9]{2}", 74 | ) 75 | 76 | let assert Ok(re_9999) = 77 | regexp.from_string( 78 | "(9999-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2})-[0-9]{2}:[0-9]{2}", 79 | ) 80 | 81 | case regexp.scan(re_0000, date_time), regexp.scan(re_9999, date_time) { 82 | [regexp.Match(_, submatches: [option.Some(date_time_no_offset)])], [] 83 | | [], [regexp.Match(_, submatches: [option.Some(date_time_no_offset)])] 84 | -> { 85 | // It is one of the bad Erlang cases, so replace the offset with "Z". 86 | date_time_no_offset <> "Z" 87 | } 88 | 89 | _, _ -> date_time 90 | } 91 | } 92 | False -> date_time 93 | } 94 | } 95 | 96 | fn full_date_generator() -> qcheck.Generator(String) { 97 | use date_fullyear <- qcheck.bind(date_fullyear_generator()) 98 | use date_month <- qcheck.bind(date_month_generator()) 99 | use date_month_day <- qcheck.map(date_month_day_generator( 100 | year: date_fullyear, 101 | month: date_month, 102 | )) 103 | date_fullyear <> "-" <> date_month <> "-" <> date_month_day 104 | } 105 | 106 | fn date_fullyear_generator() -> qcheck.Generator(String) { 107 | zero_padded_digits_generator(length: 4, from: 0, to: 9999) 108 | } 109 | 110 | fn date_month_generator() -> qcheck.Generator(String) { 111 | zero_padded_digits_generator(length: 2, from: 1, to: 12) 112 | } 113 | 114 | fn date_month_day_generator( 115 | year year: String, 116 | month month: String, 117 | ) -> qcheck.Generator(String) { 118 | let is_leap_year = is_leap_year(year) 119 | 120 | case month { 121 | "01" | "03" | "05" | "07" | "08" | "10" | "12" -> 122 | zero_padded_digits_generator(length: 2, from: 1, to: 31) 123 | "04" | "06" | "09" | "11" -> 124 | zero_padded_digits_generator(length: 2, from: 1, to: 30) 125 | "02" if is_leap_year -> 126 | zero_padded_digits_generator(length: 2, from: 1, to: 29) 127 | "02" -> zero_padded_digits_generator(length: 2, from: 1, to: 28) 128 | _ -> panic as { "date_month_day_generator: bad month " <> month } 129 | } 130 | } 131 | 132 | // Implementation from RFC 3339 Appendix C 133 | fn is_leap_year(year_input: String) -> Bool { 134 | let assert 4 = string.length(year_input) 135 | let assert Ok(year) = int.parse(year_input) 136 | 137 | let result = year % 4 == 0 && { year % 100 != 0 || year % 400 == 0 } 138 | 139 | result 140 | } 141 | 142 | fn t_generator() { 143 | qcheck.from_generators(qcheck.constant("T"), [qcheck.constant("t")]) 144 | } 145 | 146 | fn full_time_generator( 147 | with_leap_second with_leap_second: Bool, 148 | second_fraction_spec second_fraction_spec: SecondFractionSpec, 149 | ) -> qcheck.Generator(String) { 150 | use partial_time, time_offset <- qcheck.map2( 151 | partial_time_generator(with_leap_second, second_fraction_spec), 152 | time_offset_generator(), 153 | ) 154 | partial_time <> time_offset 155 | } 156 | 157 | fn partial_time_generator( 158 | with_leap_second with_leap_second: Bool, 159 | second_fraction_spec second_fraction_spec: SecondFractionSpec, 160 | ) -> qcheck.Generator(String) { 161 | qcheck.constant({ 162 | use time_hour <- qcheck.parameter 163 | use time_minute <- qcheck.parameter 164 | use time_second <- qcheck.parameter 165 | use optional_time_second_fraction <- qcheck.parameter 166 | time_hour 167 | <> ":" 168 | <> time_minute 169 | <> ":" 170 | <> time_second 171 | <> unwrap_optional_string(optional_time_second_fraction) 172 | }) 173 | |> qcheck.apply(time_hour_generator()) 174 | |> qcheck.apply(time_minute_generator()) 175 | |> qcheck.apply(time_second_generator(with_leap_second)) 176 | |> qcheck.apply( 177 | qcheck.option_from(time_second_fraction_generator(second_fraction_spec)), 178 | ) 179 | } 180 | 181 | fn time_hour_generator() -> qcheck.Generator(String) { 182 | zero_padded_digits_generator(length: 2, from: 0, to: 23) 183 | } 184 | 185 | fn time_minute_generator() -> qcheck.Generator(String) { 186 | zero_padded_digits_generator(length: 2, from: 0, to: 59) 187 | } 188 | 189 | fn time_second_generator( 190 | with_leap_second with_leap_second: Bool, 191 | ) -> qcheck.Generator(String) { 192 | let max_second = case with_leap_second { 193 | True -> 60 194 | False -> 59 195 | } 196 | zero_padded_digits_generator(length: 2, from: 0, to: max_second) 197 | } 198 | 199 | fn zero_padded_digits_generator( 200 | length length: Int, 201 | from min: Int, 202 | to max: Int, 203 | ) -> qcheck.Generator(String) { 204 | use n <- qcheck.map(qcheck.bounded_int(min, max)) 205 | int.to_string(n) |> string.pad_start(to: length, with: "0") 206 | } 207 | 208 | pub type SecondFractionSpec { 209 | Default 210 | WithMaxLength(Int) 211 | } 212 | 213 | fn time_second_fraction_generator( 214 | second_fraction_spec: SecondFractionSpec, 215 | ) -> qcheck.Generator(String) { 216 | let generator = case second_fraction_spec { 217 | Default -> one_or_more_digits_generator() 218 | WithMaxLength(max_count) -> digits_generator(min_count: 1, max_count:) 219 | } 220 | 221 | use digits <- qcheck.map(generator) 222 | "." <> digits 223 | } 224 | 225 | fn one_or_more_digits_generator() -> qcheck.Generator(String) { 226 | qcheck.non_empty_string_from(qcheck.ascii_digit_codepoint()) 227 | } 228 | 229 | fn digits_generator( 230 | min_count min_count: Int, 231 | max_count max_count: Int, 232 | ) -> qcheck.Generator(String) { 233 | qcheck.generic_string( 234 | qcheck.ascii_digit_codepoint(), 235 | qcheck.bounded_int(min_count, max_count), 236 | ) 237 | } 238 | 239 | fn time_offset_generator() -> qcheck.Generator(String) { 240 | qcheck.from_generators(z_generator(), [time_numoffset_generator()]) 241 | } 242 | 243 | fn z_generator() { 244 | qcheck.from_generators(qcheck.constant("Z"), [qcheck.constant("z")]) 245 | } 246 | 247 | fn time_numoffset_generator() -> qcheck.Generator(String) { 248 | use plus_or_minus, time_hour, time_minute <- qcheck.map3( 249 | plus_or_minus_generator(), 250 | time_hour_generator(), 251 | time_minute_generator(), 252 | ) 253 | 254 | plus_or_minus <> time_hour <> ":" <> time_minute 255 | } 256 | 257 | fn plus_or_minus_generator() -> qcheck.Generator(String) { 258 | qcheck.from_generators(qcheck.constant("+"), [qcheck.constant("-")]) 259 | } 260 | 261 | fn unwrap_optional_string(optional_string: option.Option(String)) -> String { 262 | case optional_string { 263 | option.None -> "" 264 | option.Some(string) -> string 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /test/gleam/time/timestamp_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/list 3 | import gleam/order 4 | import gleam/result 5 | import gleam/string 6 | import gleam/time/calendar.{August, Date, December, January, TimeOfDay} 7 | import gleam/time/duration 8 | import gleam/time/generators 9 | import gleam/time/timestamp 10 | import gleeunit/should 11 | import qcheck 12 | import simplifile 13 | 14 | pub fn compare_property_0_test() { 15 | use #(x, y) <- qcheck.given(qcheck.tuple2( 16 | qcheck.uniform_int(), 17 | qcheck.uniform_int(), 18 | )) 19 | let tx = timestamp.from_unix_seconds(x) 20 | let ty = timestamp.from_unix_seconds(y) 21 | should.equal(timestamp.compare(tx, ty), int.compare(x, y)) 22 | } 23 | 24 | pub fn compare_0_test() { 25 | timestamp.compare( 26 | timestamp.from_unix_seconds(1), 27 | timestamp.from_unix_seconds(1), 28 | ) 29 | |> should.equal(order.Eq) 30 | } 31 | 32 | pub fn compare_1_test() { 33 | timestamp.compare( 34 | timestamp.from_unix_seconds(2), 35 | timestamp.from_unix_seconds(1), 36 | ) 37 | |> should.equal(order.Gt) 38 | } 39 | 40 | pub fn compare_2_test() { 41 | timestamp.compare( 42 | timestamp.from_unix_seconds(2), 43 | timestamp.from_unix_seconds(3), 44 | ) 45 | |> should.equal(order.Lt) 46 | } 47 | 48 | pub fn difference_0_test() { 49 | timestamp.difference( 50 | timestamp.from_unix_seconds(1), 51 | timestamp.from_unix_seconds(1), 52 | ) 53 | |> should.equal(duration.seconds(0)) 54 | } 55 | 56 | pub fn difference_1_test() { 57 | timestamp.difference( 58 | timestamp.from_unix_seconds(1), 59 | timestamp.from_unix_seconds(5), 60 | ) 61 | |> should.equal(duration.seconds(4)) 62 | } 63 | 64 | pub fn difference_2_test() { 65 | timestamp.difference( 66 | timestamp.from_unix_seconds_and_nanoseconds(1, 10), 67 | timestamp.from_unix_seconds_and_nanoseconds(5, 20), 68 | ) 69 | |> should.equal(duration.seconds(4) |> duration.add(duration.nanoseconds(10))) 70 | } 71 | 72 | pub fn add_property_0_test() { 73 | use #(x, y) <- qcheck.given(qcheck.tuple2( 74 | qcheck.uniform_int(), 75 | qcheck.uniform_int(), 76 | )) 77 | let expected = timestamp.from_unix_seconds_and_nanoseconds(0, x + y) 78 | let actual = 79 | timestamp.from_unix_seconds_and_nanoseconds(0, x) 80 | |> timestamp.add(duration.nanoseconds(y)) 81 | should.equal(expected, actual) 82 | } 83 | 84 | pub fn add_property_1_test() { 85 | use #(x, y) <- qcheck.given(qcheck.tuple2( 86 | qcheck.uniform_int(), 87 | qcheck.uniform_int(), 88 | )) 89 | let expected = timestamp.from_unix_seconds_and_nanoseconds(x + y, 0) 90 | let actual = 91 | timestamp.from_unix_seconds_and_nanoseconds(x, 0) 92 | |> timestamp.add(duration.seconds(y)) 93 | should.equal(expected, actual) 94 | } 95 | 96 | pub fn add_0_test() { 97 | timestamp.from_unix_seconds(0) 98 | |> timestamp.add(duration.seconds(1)) 99 | |> should.equal(timestamp.from_unix_seconds(1)) 100 | } 101 | 102 | pub fn add_1_test() { 103 | timestamp.from_unix_seconds(100) 104 | |> timestamp.add(duration.seconds(-1)) 105 | |> should.equal(timestamp.from_unix_seconds(99)) 106 | } 107 | 108 | pub fn add_2_test() { 109 | timestamp.from_unix_seconds(99) 110 | |> timestamp.add(duration.nanoseconds(100)) 111 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(99, 100)) 112 | } 113 | 114 | pub fn add_3_test() { 115 | timestamp.from_unix_seconds_and_nanoseconds(0, -1) 116 | |> timestamp.add(duration.nanoseconds(-1_000_000_000)) 117 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(0, -1_000_000_001)) 118 | } 119 | 120 | pub fn add_4_test() { 121 | timestamp.from_unix_seconds_and_nanoseconds(0, 1) 122 | |> timestamp.add(duration.nanoseconds(-1_000_000_000)) 123 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(0, -999_999_999)) 124 | } 125 | 126 | pub fn to_unix_seconds_0_test() { 127 | timestamp.from_unix_seconds_and_nanoseconds(1, 0) 128 | |> timestamp.to_unix_seconds 129 | |> should.equal(1.0) 130 | } 131 | 132 | pub fn to_unix_seconds_1_test() { 133 | timestamp.from_unix_seconds_and_nanoseconds(1, 500_000_000) 134 | |> timestamp.to_unix_seconds 135 | |> should.equal(1.5) 136 | } 137 | 138 | pub fn to_unix_seconds_and_nanoseconds_0_test() { 139 | timestamp.from_unix_seconds_and_nanoseconds(1, 0) 140 | |> timestamp.to_unix_seconds_and_nanoseconds 141 | |> should.equal(#(1, 0)) 142 | } 143 | 144 | pub fn to_unix_seconds_and_nanoseconds_1_test() { 145 | timestamp.from_unix_seconds_and_nanoseconds(1, 2) 146 | |> timestamp.to_unix_seconds_and_nanoseconds 147 | |> should.equal(#(1, 2)) 148 | } 149 | 150 | pub fn system_time_0_test() { 151 | let #(now, _) = 152 | timestamp.system_time() |> timestamp.to_unix_seconds_and_nanoseconds 153 | 154 | // This test will start to fail once enough time has passed. 155 | // When that happens please update these values. 156 | let when_this_test_was_last_updated = 1_735_307_287 157 | let christmas_day_2025 = 1_766_620_800 158 | 159 | let assert True = now > when_this_test_was_last_updated 160 | let assert True = now < christmas_day_2025 161 | } 162 | 163 | pub fn to_rfc3339_0_test() { 164 | timestamp.from_unix_seconds(1_735_309_467) 165 | |> timestamp.to_rfc3339(calendar.utc_offset) 166 | |> should.equal("2024-12-27T14:24:27Z") 167 | } 168 | 169 | pub fn to_rfc3339_1_test() { 170 | timestamp.from_unix_seconds(1) 171 | |> timestamp.to_rfc3339(calendar.utc_offset) 172 | |> should.equal("1970-01-01T00:00:01Z") 173 | } 174 | 175 | pub fn to_rfc3339_2_test() { 176 | timestamp.from_unix_seconds(0) 177 | |> timestamp.to_rfc3339(calendar.utc_offset) 178 | |> should.equal("1970-01-01T00:00:00Z") 179 | } 180 | 181 | pub fn to_rfc3339_3_test() { 182 | timestamp.from_unix_seconds(123_456_789) 183 | |> timestamp.to_rfc3339(calendar.utc_offset) 184 | |> should.equal("1973-11-29T21:33:09Z") 185 | } 186 | 187 | pub fn to_rfc3339_4_test() { 188 | timestamp.from_unix_seconds(31_560_000) 189 | |> timestamp.to_rfc3339(calendar.utc_offset) 190 | |> should.equal("1971-01-01T06:40:00Z") 191 | } 192 | 193 | pub fn to_rfc3339_5_test() { 194 | timestamp.from_unix_seconds(-12_345_678) 195 | |> timestamp.to_rfc3339(calendar.utc_offset) 196 | |> should.equal("1969-08-11T02:38:42Z") 197 | } 198 | 199 | pub fn to_rfc3339_6_test() { 200 | timestamp.from_unix_seconds(-1) 201 | |> timestamp.to_rfc3339(calendar.utc_offset) 202 | |> should.equal("1969-12-31T23:59:59Z") 203 | } 204 | 205 | pub fn to_rfc3339_7_test() { 206 | timestamp.from_unix_seconds(-60 * 60 - 60 * 5) 207 | |> timestamp.to_rfc3339(duration.seconds(65 * 60)) 208 | |> should.equal("1970-01-01T00:00:00+01:05") 209 | } 210 | 211 | pub fn to_rfc3339_8_test() { 212 | timestamp.from_unix_seconds(0) 213 | |> timestamp.to_rfc3339(duration.seconds(120 * 60)) 214 | |> should.equal("1970-01-01T02:00:00+02:00") 215 | } 216 | 217 | pub fn to_rfc3339_9_test() { 218 | timestamp.from_unix_seconds(-62_167_219_200) 219 | |> timestamp.to_rfc3339(calendar.utc_offset) 220 | |> should.equal("0000-01-01T00:00:00Z") 221 | } 222 | 223 | pub fn to_rfc3339_10_test() { 224 | timestamp.from_unix_seconds(-62_135_596_800) 225 | |> timestamp.to_rfc3339(calendar.utc_offset) 226 | |> should.equal("0001-01-01T00:00:00Z") 227 | } 228 | 229 | pub fn to_rfc3339_11_test() { 230 | timestamp.from_unix_seconds(-61_851_600_000) 231 | |> timestamp.to_rfc3339(calendar.utc_offset) 232 | |> should.equal("0010-01-01T00:00:00Z") 233 | } 234 | 235 | pub fn to_rfc3339_12_test() { 236 | timestamp.from_unix_seconds(-59_011_459_200) 237 | |> timestamp.to_rfc3339(calendar.utc_offset) 238 | |> should.equal("0100-01-01T00:00:00Z") 239 | } 240 | 241 | pub fn to_rfc3339_13_test() { 242 | timestamp.from_unix_seconds_and_nanoseconds(0, 1) 243 | |> timestamp.to_rfc3339(calendar.utc_offset) 244 | |> should.equal("1970-01-01T00:00:00.000000001Z") 245 | } 246 | 247 | pub fn to_rfc3339_14_test() { 248 | timestamp.from_unix_seconds_and_nanoseconds(-1, 12) 249 | |> timestamp.to_rfc3339(calendar.utc_offset) 250 | |> should.equal("1969-12-31T23:59:59.000000012Z") 251 | } 252 | 253 | pub fn to_rfc3339_15_test() { 254 | timestamp.from_unix_seconds_and_nanoseconds(1, 123) 255 | |> timestamp.to_rfc3339(calendar.utc_offset) 256 | |> should.equal("1970-01-01T00:00:01.000000123Z") 257 | } 258 | 259 | pub fn to_rfc3339_16_test() { 260 | timestamp.from_unix_seconds_and_nanoseconds(0, 1230) 261 | |> timestamp.to_rfc3339(calendar.utc_offset) 262 | |> should.equal("1970-01-01T00:00:00.00000123Z") 263 | } 264 | 265 | pub fn to_rfc3339_17_test() { 266 | timestamp.from_unix_seconds_and_nanoseconds(0, 500_600_000) 267 | |> timestamp.to_rfc3339(calendar.utc_offset) 268 | |> should.equal("1970-01-01T00:00:00.5006Z") 269 | } 270 | 271 | pub fn to_rfc3339_18_test() { 272 | timestamp.from_unix_seconds_and_nanoseconds(0, 500_006) 273 | |> timestamp.to_rfc3339(calendar.utc_offset) 274 | |> should.equal("1970-01-01T00:00:00.000500006Z") 275 | } 276 | 277 | pub fn to_rfc3339_19_test() { 278 | timestamp.from_unix_seconds_and_nanoseconds(0, 999_999_999) 279 | |> timestamp.to_rfc3339(calendar.utc_offset) 280 | |> should.equal("1970-01-01T00:00:00.999999999Z") 281 | } 282 | 283 | pub fn to_rfc3339_20_test() { 284 | timestamp.from_unix_seconds_and_nanoseconds(0, 0) 285 | |> timestamp.to_rfc3339(calendar.utc_offset) 286 | |> should.equal("1970-01-01T00:00:00Z") 287 | } 288 | 289 | pub fn to_rfc3339_21_test() { 290 | timestamp.from_unix_seconds_and_nanoseconds(0, 1_000_000_001) 291 | |> timestamp.to_rfc3339(calendar.utc_offset) 292 | |> should.equal("1970-01-01T00:00:01.000000001Z") 293 | } 294 | 295 | // RFC 3339 Parsing 296 | 297 | pub fn parse_rfc3339_0_test() { 298 | let assert Ok(timestamp) = timestamp.parse_rfc3339("1970-01-01T00:00:00.6Z") 299 | 300 | timestamp 301 | |> timestamp.to_unix_seconds_and_nanoseconds 302 | |> should.equal(#(0, 600_000_000)) 303 | } 304 | 305 | pub fn parse_rfc3339_1_test() { 306 | let assert Ok(timestamp) = timestamp.parse_rfc3339("1969-12-31T23:59:59.6Z") 307 | 308 | timestamp 309 | |> timestamp.to_unix_seconds_and_nanoseconds 310 | |> should.equal(#(-1, 600_000_000)) 311 | } 312 | 313 | pub fn parse_rfc3339_2_test() { 314 | let assert Ok(timestamp) = 315 | timestamp.parse_rfc3339("1970-01-01t00:00:00.55+00:01") 316 | 317 | timestamp 318 | |> timestamp.to_unix_seconds_and_nanoseconds 319 | |> should.equal(#(-60, 550_000_000)) 320 | } 321 | 322 | pub fn parse_rfc3339_3_test() { 323 | let assert Ok(timestamp) = 324 | timestamp.parse_rfc3339("1970-01-01T00:00:00.55-00:01") 325 | 326 | timestamp 327 | |> timestamp.to_unix_seconds_and_nanoseconds 328 | |> should.equal(#(60, 550_000_000)) 329 | } 330 | 331 | pub fn timestamp_rfc3339_string_timestamp_roundtrip_property_test() { 332 | use timestamp <- qcheck.given(generators.timestamp()) 333 | 334 | let assert Ok(parsed_timestamp) = 335 | timestamp 336 | |> timestamp.to_rfc3339(calendar.utc_offset) 337 | |> timestamp.parse_rfc3339 338 | 339 | should.equal(timestamp, parsed_timestamp) 340 | } 341 | 342 | pub fn rfc3339_string_timestamp_rfc3339_string_roundtrip_property_test() { 343 | use date_time <- qcheck.given(generators.rfc3339( 344 | with_leap_second: True, 345 | second_fraction_spec: generators.Default, 346 | avoid_erlang_errors: False, 347 | )) 348 | 349 | let assert Ok(original_timestamp) = timestamp.parse_rfc3339(date_time) 350 | 351 | let assert Ok(roundtrip_timestamp) = 352 | original_timestamp 353 | |> timestamp.to_rfc3339(calendar.utc_offset) 354 | |> timestamp.parse_rfc3339 355 | 356 | should.equal(original_timestamp, roundtrip_timestamp) 357 | } 358 | 359 | // Eastern US Timezone round trip tests 360 | pub fn rfc3339_string_timestamp_rfc3339_string_roundtrip_to_est_test() { 361 | let assert Ok(date_time) = 362 | timestamp.parse_rfc3339("2025-02-04T13:00:00+00:00") 363 | 364 | date_time 365 | |> timestamp.to_rfc3339(duration.seconds(-18_000)) 366 | |> should.equal("2025-02-04T08:00:00-05:00") 367 | } 368 | 369 | pub fn rfc3339_string_timestamp_rfc3339_string_roundtrip_from_est_test() { 370 | let assert Ok(date_time) = 371 | timestamp.parse_rfc3339("2025-02-04T13:00:00-05:00") 372 | 373 | date_time 374 | |> timestamp.to_rfc3339(calendar.utc_offset) 375 | |> should.equal("2025-02-04T18:00:00Z") 376 | } 377 | 378 | // Check against OCaml Ptime reference implementation. 379 | // 380 | // These test cases include leap seconds. 381 | pub fn parse_rfc3339_examples_from_file_test() { 382 | let assert Ok(data) = simplifile.read("test/gleam/time/timestamps_parsed.tsv") 383 | data 384 | |> string.split(on: "\n") 385 | |> list.drop(1) 386 | |> list.each(fn(line) { 387 | case string.split(line, on: "\t") { 388 | [ts, seconds, nanos, _, _] -> { 389 | let assert Ok(expected_seconds) = int.parse(seconds) 390 | let assert Ok(expected_nanos) = int.parse(nanos) 391 | 392 | let assert Ok(parsed_ts) = timestamp.parse_rfc3339(ts) 393 | let #(parsed_seconds, parsed_nanos) = 394 | timestamp.to_unix_seconds_and_nanoseconds(parsed_ts) 395 | 396 | should.equal(expected_seconds, parsed_seconds) 397 | should.equal(expected_nanos, parsed_nanos) 398 | } 399 | // No more to parse. 400 | [""] -> Nil 401 | _ -> panic as "bad input line" 402 | } 403 | }) 404 | } 405 | 406 | @external(erlang, "gleam_time_test_ffi", "rfc3339_to_system_time_in_milliseconds") 407 | @external(javascript, "../../gleam_time_test_ffi.mjs", "rfc3339_to_system_time_in_milliseconds") 408 | fn rfc3339_to_system_time_in_milliseconds(input: String) -> Result(Int, Nil) 409 | 410 | // WARNING: This can give different values on Erlang and JS targets if you pass 411 | // in a timestamp that has more than 3 fractional digits. Erlang will give you 412 | // nanosecond precision, but will round to nearest nanosecond. JavaScript will 413 | // give you millisecond precision and trucate to the nearest millisecond. So, 414 | // the caller must ensure good values are passed in. 415 | fn parse_rfc3339_oracle(input: String) -> Result(timestamp.Timestamp, Nil) { 416 | use total_milliseconds <- result.map(rfc3339_to_system_time_in_milliseconds( 417 | input, 418 | )) 419 | // Break out the millisecond fraction first so that the conversion to 420 | // nanoseconds doesn't overflow for JS in the normalise function. 421 | let millisecond_fraction = total_milliseconds % 1000 422 | let whole_seconds = { total_milliseconds - millisecond_fraction } / 1000 423 | timestamp.from_unix_seconds_and_nanoseconds( 424 | seconds: whole_seconds, 425 | nanoseconds: millisecond_fraction * 1_000_000, 426 | ) 427 | } 428 | 429 | pub fn parse_rfc3339_matches_oracle_example_0_test() { 430 | let date_time = "9999-12-31T23:59:59.999Z" 431 | 432 | should.equal( 433 | timestamp.parse_rfc3339(date_time), 434 | parse_rfc3339_oracle(date_time), 435 | ) 436 | } 437 | 438 | pub fn parse_rfc3339_matches_oracle_example_1_test() { 439 | let date_time = "1970-01-01T00:00:00.111Z" 440 | 441 | should.equal( 442 | timestamp.parse_rfc3339(date_time), 443 | parse_rfc3339_oracle(date_time), 444 | ) 445 | } 446 | 447 | pub fn parse_rfc3339_matches_oracle_example_2_test() { 448 | let date_time = "1970-01-01T00:00:00.000Z" 449 | 450 | should.equal( 451 | timestamp.parse_rfc3339(date_time), 452 | parse_rfc3339_oracle(date_time), 453 | ) 454 | } 455 | 456 | pub fn parse_rfc3339_matches_oracle_example_3_test() { 457 | let date_time = "1969-12-31T23:59:59.444Z" 458 | 459 | should.equal( 460 | timestamp.parse_rfc3339(date_time), 461 | parse_rfc3339_oracle(date_time), 462 | ) 463 | } 464 | 465 | pub fn parse_rfc3339_matches_oracle_example_4_test() { 466 | let date_time = "1969-12-31T23:59:58.666Z" 467 | 468 | should.equal( 469 | timestamp.parse_rfc3339(date_time), 470 | parse_rfc3339_oracle(date_time), 471 | ) 472 | } 473 | 474 | pub fn parse_rfc3339_matches_oracle_example_5_test() { 475 | let date_time = "0000-01-01T00:00:00Z" 476 | 477 | should.equal( 478 | timestamp.parse_rfc3339(date_time), 479 | parse_rfc3339_oracle(date_time), 480 | ) 481 | } 482 | 483 | // The oracle gives badarg on Erlang as it is beyond the range of the 484 | // 0000-01-01T00:00:00Z limit. 485 | @target(javascript) 486 | pub fn parse_rfc3339_matches_oracle_example_6_test() { 487 | let date_time = "0000-01-01T00:00:00+00:01" 488 | 489 | should.equal( 490 | timestamp.parse_rfc3339(date_time), 491 | parse_rfc3339_oracle(date_time), 492 | ) 493 | } 494 | 495 | pub fn parse_rfc3339_matches_oracle_example_7_test() { 496 | let date_time = "0000-01-01T00:00:00-00:01" 497 | 498 | should.equal( 499 | timestamp.parse_rfc3339(date_time), 500 | parse_rfc3339_oracle(date_time), 501 | ) 502 | } 503 | 504 | pub fn parse_rfc3339_matches_oracle_example_8_test() { 505 | let date_time = "9999-12-31T23:59:59.999+00:01" 506 | 507 | should.equal( 508 | timestamp.parse_rfc3339(date_time), 509 | parse_rfc3339_oracle(date_time), 510 | ) 511 | } 512 | 513 | // This oracle gives badarg on Erlang as it is beyond the range of the 514 | // 9999-12-31T23:59:59.999Z limit. 515 | @target(javascript) 516 | pub fn parse_rfc3339_matches_oracle_example_9_test() { 517 | let date_time = "9999-12-31T23:59:59.999-00:01" 518 | 519 | should.equal( 520 | timestamp.parse_rfc3339(date_time), 521 | parse_rfc3339_oracle(date_time), 522 | ) 523 | } 524 | 525 | // JS returns NaN for any leap seconds, so skip this test in JS. 526 | @target(erlang) 527 | pub fn parse_rfc3339_matches_oracle_example_10_test() { 528 | let date_time = "1970-01-01T23:59:60Z" 529 | 530 | should.equal( 531 | timestamp.parse_rfc3339(date_time), 532 | parse_rfc3339_oracle(date_time), 533 | ) 534 | } 535 | 536 | pub fn parse_rfc3339_matches_oracle_property_test() { 537 | use date_time <- qcheck.given(generators.rfc3339( 538 | // JavaScript oracle cannot handle leap-seconds. 539 | with_leap_second: False, 540 | // JavaScript oracle has max precision of milliseconds. 541 | second_fraction_spec: generators.WithMaxLength(3), 542 | // Some valid timestamps cannot be parsed by the Erlang oracle. 543 | avoid_erlang_errors: True, 544 | )) 545 | 546 | should.equal( 547 | timestamp.parse_rfc3339(date_time), 548 | parse_rfc3339_oracle(date_time), 549 | ) 550 | } 551 | 552 | pub fn parse_rfc3339_succeeds_for_valid_inputs_property_test() { 553 | use date_time <- qcheck.given(generators.rfc3339( 554 | with_leap_second: True, 555 | second_fraction_spec: generators.Default, 556 | avoid_erlang_errors: False, 557 | )) 558 | 559 | let _ = timestamp.parse_rfc3339(date_time) |> should.be_ok 560 | Nil 561 | } 562 | 563 | pub fn parse_rfc3339_fails_for_invalid_inputs_test() { 564 | // The chance of randomly generating a valid RFC 3339 string is quite low.... 565 | use string <- qcheck.given(qcheck.string()) 566 | 567 | timestamp.parse_rfc3339(string) |> should.be_error 568 | } 569 | 570 | pub fn parse_rfc3339_bad_leapyear_test() { 571 | // 29 days in February in a non-leap year is an error. 572 | timestamp.parse_rfc3339("2023-02-29T00:00:00Z") 573 | |> should.equal(Error(Nil)) 574 | } 575 | 576 | pub fn parse_rfc3339_truncates_too_many_fractional_seconds_0_test() { 577 | let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:00.1234567899Z") 578 | 579 | let #(_, nanoseconds) = timestamp.to_unix_seconds_and_nanoseconds(ts) 580 | 581 | should.equal(nanoseconds, 123_456_789) 582 | } 583 | 584 | pub fn parse_rfc3339_truncates_too_many_fractional_seconds_1_test() { 585 | let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:00.1234567891Z") 586 | 587 | let #(_, nanoseconds) = timestamp.to_unix_seconds_and_nanoseconds(ts) 588 | 589 | should.equal(nanoseconds, 123_456_789) 590 | } 591 | 592 | // Examples from the docs 593 | 594 | pub fn parse_rfc3339_docs_example_0_test() { 595 | let assert Ok(ts) = 596 | timestamp.parse_rfc3339("1970-01-01T00:00:01.12345678999Z") 597 | timestamp.to_unix_seconds_and_nanoseconds(ts) 598 | |> should.equal(#(1, 123_456_789)) 599 | } 600 | 601 | pub fn parse_rfc3339_docs_example_1_test() { 602 | let assert Ok(ts) = timestamp.parse_rfc3339("2025-01-10t15:54:30-05:15") 603 | timestamp.to_unix_seconds_and_nanoseconds(ts) 604 | |> should.equal(#(1_736_543_370, 0)) 605 | } 606 | 607 | // Bad timestamps fail to parse 608 | 609 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_0_test() { 610 | timestamp.parse_rfc3339("12349-01-01T00:00:00Z") 611 | |> should.be_error 612 | } 613 | 614 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_1_test() { 615 | timestamp.parse_rfc3339("1234-019-01T00:00:00Z") 616 | |> should.be_error 617 | } 618 | 619 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_2_test() { 620 | timestamp.parse_rfc3339("1234-01-019T00:00:00Z") 621 | |> should.be_error 622 | } 623 | 624 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_3_test() { 625 | timestamp.parse_rfc3339("1234-01-01 00:00:00Z") 626 | |> should.be_error 627 | } 628 | 629 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_4_test() { 630 | timestamp.parse_rfc3339("1234-01-01T009:00:00Z") 631 | |> should.be_error 632 | } 633 | 634 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_5_test() { 635 | timestamp.parse_rfc3339("1234-01-01T00:009:00Z") 636 | |> should.be_error 637 | } 638 | 639 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_6_test() { 640 | timestamp.parse_rfc3339("1234-01-01T00:00:009Z") 641 | |> should.be_error 642 | } 643 | 644 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_7_test() { 645 | timestamp.parse_rfc3339("1234-01-01T00:00:00*00:00") 646 | |> should.be_error 647 | } 648 | 649 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_8_test() { 650 | timestamp.parse_rfc3339("1234-01-01T00:00:00+009:00") 651 | |> should.be_error 652 | } 653 | 654 | pub fn parse_rfc3339_returns_error_for_bad_timestamp_9_test() { 655 | timestamp.parse_rfc3339("1234-01-01T00:00:00+00:009") 656 | |> should.be_error 657 | } 658 | 659 | pub fn parse_rfc3339_docs_example_2_test() { 660 | let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31") 661 | } 662 | 663 | pub fn normalise_negative_millis_test() { 664 | timestamp.from_unix_seconds_and_nanoseconds(1, -1_000_000_000) 665 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(0, 0)) 666 | } 667 | 668 | pub fn normalise_negative_millis_1_test() { 669 | timestamp.from_unix_seconds_and_nanoseconds(1, -1_400_000_000) 670 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-1, 600_000_000)) 671 | } 672 | 673 | pub fn normalise_negative_millis_2_test() { 674 | timestamp.from_unix_seconds_and_nanoseconds(1, -2_600_000_000) 675 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-2, 400_000_000)) 676 | } 677 | 678 | pub fn normalise_negative_millis_3_test() { 679 | timestamp.from_unix_seconds_and_nanoseconds(0, -1_000_000_000) 680 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-1, 0)) 681 | } 682 | 683 | pub fn normalise_negative_millis_4_test() { 684 | timestamp.from_unix_seconds_and_nanoseconds(0, -1_400_000_000) 685 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-2, 600_000_000)) 686 | } 687 | 688 | pub fn normalise_negative_millis_5_test() { 689 | timestamp.from_unix_seconds_and_nanoseconds(0, -2_600_000_000) 690 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-3, 400_000_000)) 691 | } 692 | 693 | pub fn normalise_negative_millis_6_test() { 694 | timestamp.from_unix_seconds_and_nanoseconds(-1, -1_000_000_000) 695 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-2, 0)) 696 | } 697 | 698 | pub fn normalise_negative_millis_7_test() { 699 | timestamp.from_unix_seconds_and_nanoseconds(-1, -1_400_000_000) 700 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-3, 600_000_000)) 701 | } 702 | 703 | pub fn normalise_negative_millis_8_test() { 704 | timestamp.from_unix_seconds_and_nanoseconds(-1, -2_600_000_000) 705 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(-4, 400_000_000)) 706 | } 707 | 708 | pub fn normalise_positive_millis_test() { 709 | timestamp.from_unix_seconds_and_nanoseconds(1, 1_000_000_000) 710 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(2, 0)) 711 | } 712 | 713 | pub fn normalise_positive_millis_1_test() { 714 | timestamp.from_unix_seconds_and_nanoseconds(1, 1_400_000_000) 715 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(2, 400_000_000)) 716 | } 717 | 718 | pub fn normalise_positive_millis_2_test() { 719 | timestamp.from_unix_seconds_and_nanoseconds(1, 2_600_000_000) 720 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(3, 600_000_000)) 721 | } 722 | 723 | pub fn normalise_positive_millis_3_test() { 724 | timestamp.from_unix_seconds_and_nanoseconds(0, 1_000_000_000) 725 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(1, 0)) 726 | } 727 | 728 | pub fn normalise_positive_millis_4_test() { 729 | timestamp.from_unix_seconds_and_nanoseconds(0, 1_400_000_000) 730 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(1, 400_000_000)) 731 | } 732 | 733 | pub fn normalise_positive_millis_5_test() { 734 | timestamp.from_unix_seconds_and_nanoseconds(0, 2_600_000_000) 735 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(2, 600_000_000)) 736 | } 737 | 738 | pub fn normalise_positive_millis_6_test() { 739 | timestamp.from_unix_seconds_and_nanoseconds(-1, 1_000_000_000) 740 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(0, 0)) 741 | } 742 | 743 | pub fn normalise_positive_millis_7_test() { 744 | timestamp.from_unix_seconds_and_nanoseconds(-1, 1_400_000_000) 745 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(0, 400_000_000)) 746 | } 747 | 748 | pub fn normalise_positive_millis_8_test() { 749 | timestamp.from_unix_seconds_and_nanoseconds(-1, 2_600_000_000) 750 | |> should.equal(timestamp.from_unix_seconds_and_nanoseconds(1, 600_000_000)) 751 | } 752 | 753 | pub fn from_calendar_0_test() { 754 | timestamp.from_calendar( 755 | date: Date(year: 2024, month: December, day: 25), 756 | time: TimeOfDay(hours: 12, minutes: 30, seconds: 50, nanoseconds: 0), 757 | offset: duration.empty, 758 | ) 759 | |> timestamp.to_rfc3339(duration.empty) 760 | |> should.equal("2024-12-25T12:30:50Z") 761 | } 762 | 763 | pub fn from_calendar_1_test() { 764 | timestamp.from_calendar( 765 | date: Date(year: 50, month: August, day: 2), 766 | time: TimeOfDay(hours: 4, minutes: 10, seconds: 2, nanoseconds: 0), 767 | offset: duration.seconds(120), 768 | ) 769 | |> timestamp.to_rfc3339(duration.empty) 770 | |> should.equal("0050-08-02T04:08:02Z") 771 | } 772 | 773 | pub fn from_calendar_2_test() { 774 | timestamp.from_calendar( 775 | date: Date(year: 2024, month: December, day: 25), 776 | time: TimeOfDay(hours: 12, minutes: 30, seconds: 50, nanoseconds: 1_000_000), 777 | offset: duration.empty, 778 | ) 779 | |> timestamp.to_rfc3339(duration.empty) 780 | |> should.equal("2024-12-25T12:30:50.001Z") 781 | } 782 | 783 | pub fn to_calendar_0_test() { 784 | timestamp.from_unix_seconds(0) 785 | |> timestamp.to_calendar(calendar.utc_offset) 786 | |> should.equal(#( 787 | Date(year: 1970, month: January, day: 1), 788 | TimeOfDay(hours: 0, minutes: 0, seconds: 0, nanoseconds: 0), 789 | )) 790 | } 791 | 792 | pub fn calendar_roundtrip_test() { 793 | use timestamp1 <- qcheck.given(generators.timestamp()) 794 | let #(date, time_of_day) = 795 | timestamp.to_calendar(timestamp1, calendar.utc_offset) 796 | let timestamp2 = 797 | timestamp.from_calendar(date, time_of_day, calendar.utc_offset) 798 | should.equal(timestamp1, timestamp2) 799 | } 800 | -------------------------------------------------------------------------------- /test/gleam/time/timestamps_parsed.tsv: -------------------------------------------------------------------------------- 1 | ts g_seconds g_nanoseconds pt_days pt_picos 2 | 9865-03-18T23:39:23z 249148827563 0 2883666 85163000000000000 3 | 1366-06-30t16:32:21.22236390826z -19044746859 222363908 -220426 59541222363908260 4 | 9473-11-09t08:07:58.6787328663z 236798842078 678732866 2740727 29278678732866300 5 | 9933-09-15T18:44:09Z 251310278649 0 2908683 67449000000000000 6 | 0553-04-24t22:03:34.03033335326-14:47 -44706251366 30333353 -517434 46234030333353260 7 | 4405-05-04t07:25:39-04:08 76851891219 0 889489 41619000000000000 8 | 4980-12-12t04:22:03.02630Z 95016342123 26300000 1099726 15723026300000000 9 | 3599-04-16t08:21:54.114z 51415374114 114000000 595085 30114114000000000 10 | 7836-03-01T04:08:30.882Z 185118235710 882000000 2142572 14910882000000000 11 | 5858-09-10T07:52:40.023484911z 122715244360 23484911 1420315 28360023484911000 12 | 6390-04-17T21:13:34.833-21:03 139491051394 833000000 1614479 65794833000000000 13 | 9155-02-25t23:06:07Z 226741503967 0 2624322 83167000000000000 14 | 1200-11-12T04:20:35.000Z -24271558765 0 -280921 15635000000000000 15 | 7094-02-08t20:43:49.13274Z 161701217029 132740000 1871541 74629132740000000 16 | 6352-07-29t01:51:53.715320Z 138300659513 715320000 1600702 6713715320000000 17 | 7117-08-15T07:38:32.68Z 162443173112 680000000 1880129 27512680000000000 18 | 3276-07-07T04:13:46.2344604749-17:15 41229725326 234460474 477195 77326234460474900 19 | 9002-03-09t01:59:30-20:33 221914333950 0 2568452 81150000000000000 20 | 6498-06-24t13:29:30.6-20:34 142905117810 600000000 1653994 36210600000000000 21 | 9121-05-09T01:20:17.0076149+20:00 225674745617 7614900 2611976 19217007614900000 22 | 3928-12-04t16:17:26.07298716007-07:40 61817731046 72987160 715482 86246072987160070 23 | 3868-02-15T08:49:42.99628799208Z 59898991782 996287992 693275 31782996287992080 24 | 3945-05-10t04:42:24.026+04:13 62336132964 26000000 721483 1764026000000000 25 | 2437-11-25t00:18:00.38z 14765501880 380000000 170897 1080380000000000 26 | 7195-10-13t14:06:16.1601510Z 164909743576 160151000 1908677 50776160151000000 27 | 3423-12-20T12:16:21z 45882764181 0 531050 44181000000000000 28 | 7987-09-25t21:56:04.567370-13:26 189901365724 567370000 2197932 40924567370000000 29 | 8431-06-19T13:36:30Z 203904135390 0 2360001 48990000000000000 30 | 2143-10-26T17:13:57.73343368Z 5485166037 733433680 63485 62037733433680000 31 | 2474-01-18t07:22:08.7259428+00:48 15906263648 725942800 184100 23648725942800000 32 | 1327-08-24t01:04:02.843777017+12:44 -20270864398 843777017 -234617 44402843777017000 33 | 2200-10-25t04:30:22.000909829Z 7283795422 909829 84303 16222000909829000 34 | 1198-12-26t21:28:53.88958765676z -24330853867 889587656 -281608 77333889587656760 35 | 5167-09-14T14:44:41.140078359+00:21 100909722221 140078359 1167936 51821140078359000 36 | 8129-05-08t17:50:08Z 194370342608 0 2249656 64208000000000000 37 | 2018-01-01t04:11:54.81-23:47 1514865534 810000000 17533 14334810000000000 38 | 3988-11-29t08:27:12.3Z 63710699232 300000000 737392 30432300000000000 39 | 5900-10-08T04:52:37.22840967324Z 124043028757 228409673 1435683 17557228409673240 40 | 6475-08-05T06:00:11.0080+07:02 142182773891 8000000 1645633 82691008000000000 41 | 3240-09-13T00:56:12.6Z 40099452972 600000000 464114 3372600000000000 42 | 1226-05-13t06:04:32.3Z -23466909328 300000000 -271608 21872300000000000 43 | 6284-03-19t08:04:02.04z 136143446642 40000000 1575734 29042040000000000 44 | 4334-10-23t14:56:23Z 74626152983 0 863728 53783000000000000 45 | 4769-03-25t10:34:55.44459+10:55 88335099595 444590000 1022396 85195444590000000 46 | 4441-12-18T00:55:26.5582170210Z 78007625726 558217021 902866 3326558217021000 47 | 8397-04-21T13:22:43.8276-02:42 202826131483 827600000 2347524 57883827600000000 48 | 8693-04-25T16:52:60.72023146-11:12 212167397100 720231460 2455641 14700720231460000 49 | 2615-07-25t16:13:53.132z 20371968833 132000000 235786 58433132000000000 50 | 6334-04-11t11:56:41.967349z 137723198201 967349000 1594018 43001967349000000 51 | 1948-05-02T19:52:48.023286209+03:01 -683708892 23286209 -7914 60708023286209000 52 | 0206-01-09T16:19:12+23:20 -55665817248 0 -644281 61152000000000000 53 | 8645-07-09t17:04:36.1457505z 210659072676 145750500 2438183 61476145750500000 54 | 6850-12-12T23:02:13.72Z 154027868533 720000000 1782729 82933720000000000 55 | 0810-04-10T03:27:55-15:16 -36597417365 0 -423582 67435000000000000 56 | 6118-09-17T08:33:28.4308866Z 130920654808 430886600 1515285 30808430886600000 57 | 3887-09-28t11:40:07.207970-20:40 60518132407 207970000 700441 30007207970000000 58 | 8716-04-25T08:12:54.428863z 212893085574 428863000 2464040 29574428863000000 59 | 0738-10-17t12:36:18.268185Z -38853170622 268185000 -449690 45378268185000000 60 | 8279-10-29T15:30:46Z 199118878246 0 2304616 55846000000000000 61 | 4182-04-03T18:12:51.42z 69812043171 420000000 808009 65571420000000000 62 | 6500-09-18t22:56:31.5683+03:58 142975565911 568300000 1654809 68311568300000000 63 | 1476-09-26t10:28:42.68296+02:19 -15565881018 682960000 -180161 29382682960000000 64 | 3993-04-13T02:39:46.39623523-23:11 63848656246 396235230 738989 6646396235230000 65 | 9385-06-22t20:06:02.06+19:49 234009735422 60000000 2708446 1022060000000000 66 | 5834-12-14T23:11:54Z 121966125114 0 1411644 83514000000000000 67 | 2376-01-12T00:17:13.774664z 12813034633 774664000 148299 1033774664000000 68 | 9946-02-20t13:48:37.08756554-13:35 251702652217 87565540 2913225 12217087565540000 69 | 7111-09-24t13:48:27-04:18 162257277987 0 1877977 65187000000000000 70 | 4038-04-03T19:56:00.4616852906+11:12 65267801040 461685290 755414 31440461685290600 71 | 6044-11-28t22:53:23.93274677-18:05 128591859503 932746770 1488331 61103932746770000 72 | 9028-09-28T20:43:41-12:58 222752454101 0 2578153 34901000000000000 73 | 8808-12-09T11:11:31.21948-01:37 215816100511 219480000 2497871 46111219480000000 74 | 9566-01-11t10:31:42.8141605196+11:34 239707465062 814160519 2774391 82662814160519600 75 | 2911-02-11T10:06:22.86041z 29698653982 860410000 343734 36382860410000000 76 | 9506-02-01t12:20:15.40+05:31 237815851755 400000000 2752498 24555400000000000 77 | 3514-07-09T18:34:60.9486488+11:07 48740254080 948648800 564123 26880948648800000 78 | 0790-10-26t14:48:58.98-07:39 -37211362322 980000000 -430688 80878980000000000 79 | 9719-04-26t22:31:46.114z 244544826706 114000000 2830379 81106114000000000 80 | 0847-12-03t22:29:28.3z -35409317432 300000000 -409831 80968300000000000 81 | 5469-01-13T15:15:17.653-21:26 110418986477 653000000 1277997 45677653000000000 82 | 0762-12-03T07:15:45Z -38091746655 0 -440877 26145000000000000 83 | 7657-03-16t05:16:48.76+23:18 179470792728 760000000 2077208 21528760000000000 84 | 0536-11-01T21:51:60.3+07:54 -45226288920 300000000 -523453 50280300000000000 85 | 4927-12-23T00:20:20.90007+16:19 93344601680 900070000 1080377 28880900070000000 86 | 2943-07-04t17:59:50.9993+06:27 30720857570 999300000 355565 41570999300000000 87 | 0356-10-02T22:17:48-04:39 -50909115792 0 -589226 10608000000000000 88 | 3442-09-03t00:37:27.2783202606-11:59 46473050187 278320260 537882 45387278320260600 89 | 2073-08-08t00:16:36.7689z 3269376996 768900000 37840 996768900000000 90 | 9413-06-21T06:46:45+18:32 234893132085 0 2718670 44085000000000000 91 | 0392-10-27T21:17:34.5796257+02:01 -49770909806 579625700 -576053 69394579625700000 92 | 1592-08-02t05:20:01.3z -11910047999 300000000 -137848 19201300000000000 93 | 2842-02-10t15:08:27.6891z 27521219307 689100000 318532 54507689100000000 94 | 7668-06-28T03:48:44.58728976+16:38 179826952244 587289760 2081330 40244587289760000 95 | 0074-02-22T21:38:36.4874417z -59827342884 487441700 -692447 77916487441700000 96 | 0604-04-27t20:03:10Z -43096679810 0 -498805 72190000000000000 97 | 2451-08-26t19:38:04.2851119166z 15199472284 285111916 175919 70684285111916600 98 | 2990-07-15t03:56:09.06530839z 32205009369 65308390 372743 14169065308390000 99 | 0121-04-09T12:48:05.888215822+00:41 -58340260375 888215822 -675235 43625888215822000 100 | 5804-11-14T06:31:47Z 121016788307 0 1400657 23507000000000000 101 | 3108-05-13t20:29:26Z 35923292966 0 415778 73766000000000000 102 | 3566-07-30t17:26:35.56+02:17 50383091375 560000000 583137 54575560000000000 103 | 8518-07-29T04:23:28z 206653004608 0 2391817 15808000000000000 104 | 2746-06-26t00:18:04.78z 24503386684 780000000 283604 1084780000000000 105 | 6079-08-26t04:17:12.2+17:43 129687993252 200000000 1501018 38052200000000000 106 | 5836-11-30T05:16:54.761866971Z 122028009414 761866971 1412361 19014761866971000 107 | 2197-07-31T21:11:47.611676z 7181817107 611676000 83122 76307611676000000 108 | 5138-07-03t20:31:10.612788Z 99988288270 612788000 1157271 73870612788000000 109 | 4280-04-27T17:11:06.4174096+04:02 72906700146 417409600 843827 47346417409600000 110 | 3254-07-07t07:42:40.52174243-10:56 40535404720 521742430 469159 67120521742430000 111 | 5006-02-16t11:40:38.9077691Z 95810902838 907769100 1108922 42038907769100000 112 | 5528-09-16T12:02:02+03:13 112301974142 0 1299791 31742000000000000 113 | 0915-10-08t07:43:05Z -33268378615 0 -385051 27785000000000000 114 | 8166-02-02T18:21:35.6162635786+23:32 195529661375 616263578 2263074 67775616263578600 115 | 1706-08-04T16:57:05.77806516-03:49 -8312382835 778065160 -96209 74765778065160000 116 | 9890-10-21T18:07:58.774803306z 249956474878 774803306 2893014 65278774803306000 117 | 5135-10-20T09:44:40+19:24 99902902840 0 1156283 51640000000000000 118 | 4211-08-24t05:14:10+10:35 70739375950 0 818742 67150000000000000 119 | 2239-10-16T16:47:28-17:05 8513805148 0 98539 35548000000000000 120 | 5594-09-18t16:35:14.054740755Z 114384933314 54740755 1323899 59714054740755000 121 | 5797-01-28t01:36:46.26+09:08 120770843326 260000000 1397810 59326260000000000 122 | 6550-09-10t22:47:49.17470494z 144552725269 174704940 1673063 82069174704940000 123 | 5634-11-28T09:32:37.62724072684-08:38 115653377437 627240726 1338580 65437627240726840 124 | 1214-01-04t08:17:22.57z -23856738158 570000000 -276120 29842570000000000 125 | 1645-05-26T11:27:46.1Z -10243369934 100000000 -118558 41266100000000000 126 | 1795-01-23t18:25:55.25855657758-20:03 -5520389465 258556577 -63894 52135258556577580 127 | 5990-05-04T08:35:09.758006z 126869618109 758006000 1468398 30909758006000000 128 | 3571-11-12t21:44:43.0-21:49 50550032023 0 585069 70423000000000000 129 | 5305-12-23t22:31:07.41235Z 105273297067 412350000 1218440 81067412350000000 130 | 2699-08-20t01:17:06.075399903-17:25 23025062526 75399903 266493 67326075399903000 131 | 5941-04-10t21:39:26.49z 125321290766 490000000 1450477 77966490000000000 132 | 1859-10-09T17:20:24.9072z -3478487976 907200000 -40261 62424907200000000 133 | 6756-08-19t22:39:25.194Z 151051559965 194000000 1748281 81565194000000000 134 | 4964-03-13T16:12:11.22284+09:39 94487754791 222840000 1093608 23591222840000000 135 | 1377-05-01T20:32:32.4220706483-23:42 -18702675928 422070648 -216467 72872422070648300 136 | 5851-09-10t08:56:20.7+04:55 122494305680 700000000 1417758 14480700000000000 137 | 2512-05-26T13:20:04.605622-18:57 17116561024 605622000 198108 29824605622000000 138 | 9118-08-14t21:45:37.253381977Z 225588577537 253381977 2610978 78337253381977000 139 | 0587-07-22t14:01:58.6z -43625728682 600000000 -504928 50518600000000000 140 | 4164-11-23t15:28:60.9859422170Z 69264257340 985942217 801669 55740985942217000 141 | 5431-10-22T02:01:24.21621705009+06:11 109243972224 216217050 1264397 71424216217050090 142 | 1377-03-03T05:18:49.98947-08:26 -18707883311 989470000 -216527 49489989470000000 143 | 9147-12-27T00:24:42.64847-18:10 226515378882 648470000 2621705 66882648470000000 144 | 9587-04-20t19:24:38.049573Z 240378780278 49573000 2782161 69878049573000000 145 | 4138-12-14t19:57:00.2797850076+19:02 68445478500 279785007 792193 3300279785007600 146 | 1006-03-08T23:29:59.1978686-19:27 -30415064581 197868600 -352027 68219197868600000 147 | 7263-02-11t00:01:33.2129z 167034528093 212900000 1933270 93212900000000 148 | 5621-10-31T11:55:45.6510-19:36 115240779105 651000000 1333805 27105651000000000 149 | 4169-09-10t13:35:36Z 69415623336 0 803421 48936000000000000 150 | 2547-03-27T13:36:00.66347496489z 18215760960 663474964 210830 48960663474964890 151 | 0674-03-20T04:19:16.49844046560+09:05 -40891063544 498440465 -473277 69256498440465600 152 | 9428-04-20t17:43:52-11:25 235361308132 0 2724089 18532000000000000 153 | 2362-04-22T01:43:25.535223160+12:02 12379873285 535223160 143285 49285535223160000 154 | 8795-12-28t14:54:07.8Z 215407436047 800000000 2493141 53647800000000000 155 | 8828-08-11t15:24:47.2419814+08:03 216436864907 241981400 2505056 26507241981400000 156 | 6794-02-11T08:11:36Z 152234323896 0 1761971 29496000000000000 157 | 2214-04-16t14:48:46.9825-11:33 7709048506 982500000 89225 8506982500000000 158 | 4126-04-12t04:13:42.63036z 68045544822 630360000 787564 15222630360000000 159 | 5466-07-19t15:35:04.451962240+04:09 110340357964 451962240 1277087 41164451962240000 160 | 3032-05-29T22:06:51z 33526390011 0 388036 79611000000000000 161 | 7296-10-30T05:28:51.5834z 168098563731 583400000 1945585 19731583400000000 162 | 2317-08-04T22:52:18.62027116443z 10968907938 620271164 126954 82338620271164430 163 | 0470-08-18t13:33:55.439-01:23 -47315523785 439000000 -547634 53815439000000000 164 | 0432-02-20t04:30:38.40072756Z -48530258962 400727560 -561693 16238400727560000 165 | 2772-04-01T01:57:15.8792270z 25316503035 879227000 293015 7035879227000000 166 | 5974-09-13t19:09:12.593-06:19 126376162092 593000000 1462687 5292593000000000 167 | 4205-01-07T21:22:19.221650Z 70530384139 221650000 816323 76939221650000000 168 | 8337-12-17t00:20:45.308Z 200953354845 308000000 2325849 1245308000000000 169 | 3041-09-10T18:12:46.0z 33819358366 0 391427 65566000000000000 170 | 1334-10-07t19:07:00.47714768864z -20046027180 477147688 -232015 68820477147688640 171 | 1863-02-27t18:00:28.302135278z -3371608772 302135278 -39024 64828302135278000 172 | 4469-08-06T01:27:35.07z 78879662855 70000000 912959 5255070000000000 173 | 0659-04-07t04:11:39-10:58 -41362822221 0 -478737 54579000000000000 174 | 6340-08-05T22:27:06.07z 137922647226 70000000 1596326 80826070000000000 175 | 5454-03-28T19:12:06.5210404z 109951931526 521040400 1272591 69126521040400000 176 | 1892-06-06t20:00:26-16:37 -2447752954 0 -28331 45446000000000000 177 | 7395-04-18t07:19:16.39+01:03 171205769776 390000000 1981548 22576390000000000 178 | 9015-02-25T23:41:54.911-23:22 222323612634 911000000 2573189 83034911000000000 179 | 7994-03-31T01:29:10.3992486z 190106789350 399248600 2200310 5350399248600000 180 | 0486-04-01t13:29:07.993-13:57 -46822566833 993000000 -541928 12367993000000000 181 | 3072-03-03t09:00:29.405926+19:30 34781059829 405926000 402558 48629405926000000 182 | 4825-12-15t02:44:11z 90125232251 0 1043116 9851000000000000 183 | 6745-03-20t23:51:05z 150691276265 0 1744111 85865000000000000 184 | 8328-01-28T10:58:10.8802+04:15 200641387390 880200000 2322238 24190880200000000 185 | 5297-05-02t12:37:05.47237084881Z 105000583025 472370848 1215284 45425472370848810 186 | 3753-10-07t21:31:46.6151331-05:00 56290300306 615133100 651508 9106615133100000 187 | 3180-10-07T22:55:01.056z 38208149701 56000000 442223 82501056000000000 188 | 7941-11-17t21:26:54.76731469z 188454288414 767314690 2181183 77214767314690000 189 | 0751-04-09T09:38:50.0894158548z -38459456470 89415854 -445133 34730089415854800 190 | 5275-01-06t06:10:55.60+22:58 104296144375 600000000 1207131 25975600000000000 191 | 8549-04-26T13:40:49.80149354697-16:36 207623283409 801493546 2403047 22609801493546970 192 | 8038-09-01t00:56:16.360Z 191508627376 360000000 2216535 3376360000000000 193 | 6618-04-17T13:27:10.3790-02:38 146685917110 379000000 1697753 57910379000000000 194 | 0159-09-02T17:06:32-02:24 -57128473768 0 -661210 70232000000000000 195 | 5692-07-31T11:05:30.95Z 117473367930 950000000 1359645 39930950000000000 196 | 3811-09-11T05:39:57.09121000+02:36 58118180637 91210000 672664 11037091210000000 197 | 4396-06-08t15:29:08.19976172643z 76570932548 199761726 886237 55748199761726430 198 | 9108-11-22T09:35:16.65728413-16:49 225281701456 657284130 2607427 8656657284130000 199 | 9933-03-23t02:43:07.4389368887+16:55 251294953687 438936888 2908506 35287438936888700 200 | 6019-05-21t15:16:48.4613967392-10:44 127786298448 461396739 1479008 7248461396739200 201 | 6236-06-12T05:31:31.51-00:48 134636019571 510000000 1558287 22771510000000000 202 | 9742-10-07t23:48:14.581925777Z 245284847294 581925777 2838944 85694581925777000 203 | 6348-03-05T14:35:53.350+08:26 138161830193 350000000 1599095 22193350000000000 204 | 7820-07-27t17:57:51.777900-15:41 184626207531 777900000 2136877 34731777900000000 205 | 4241-03-17T01:12:58.2212182-17:39 71672410318 221218200 829541 67918221218200000 206 | 3947-07-10t15:02:07.9257763446-04:50 62404545127 925776344 722274 71527925776344600 207 | 6742-07-18T10:50:00.3082284078z 150606903000 308228407 1743135 39000308228407800 208 | 6710-06-24t19:37:58.17750Z 149595017878 177500000 1731423 70678177500000000 209 | 4971-01-22T23:22:06.629816-17:19 94704396066 629816000 1096115 60066629816000000 210 | 9840-02-23t03:15:31.45150-23:04 248357845171 451500000 2874512 8371451500000000 211 | 0219-01-25T09:01:05.017098416+09:13 -55254183115 17098416 -639517 85685017098416000 212 | 8899-12-16t05:18:39.78979579-08:04 218688384159 789795790 2531115 48159789795790000 213 | 3569-09-01t02:04:23.4427960z 50480589863 442796000 584266 7463442796000000 214 | 4729-12-09t23:10:54+07:33 87095230674 0 1008046 56274000000000000 215 | 9768-06-03t16:40:15.4085872649+22:44 246094394175 408587264 2848314 64575408587264900 216 | 6108-10-06T09:28:24-11:52 130606809624 0 1511652 76824000000000000 217 | 1145-03-13T17:45:43.9556548Z -26028281657 955654800 -301254 63943955654800000 218 | 2828-03-07t10:08:03.188186-11:44 27081640323 188186000 313444 78723188186000000 219 | 5583-12-24T07:47:08.675437210Z 114046127228 675437210 1319978 28028675437210000 220 | 9574-06-24t05:15:28.25354534392Z 239974118128 253545343 2777478 18928253545343920 221 | 2150-11-29t05:03:00.43750688680-00:22 5708985900 437506886 66076 19500437506886800 222 | 8375-04-09t19:21:11.211z 202130796071 211000000 2339476 69671211000000000 223 | 7990-09-01T08:39:40z 189993890380 0 2199003 31180000000000000 224 | 7821-10-02T23:08:27Z 184663494507 0 2137308 83307000000000000 225 | 2014-04-08T15:33:07.1310457733z 1396971187 131045773 16168 55987131045773300 226 | 2883-01-30t07:05:11z 28814079911 0 333496 25511000000000000 227 | 1524-07-17t08:46:52.6Z -14057334788 600000000 -162701 31612600000000000 228 | 4462-02-16t02:24:48.2-02:03 78643974468 200000000 910231 16068200000000000 229 | 3733-02-22t22:47:48.07253972738-18:45 55639589568 72539727 643976 63168072539727380 230 | 1399-09-04t06:56:18.464192327-13:23 -17997651642 464192327 -208307 73158464192327000 231 | 9840-10-04t12:36:11.8623057+11:15 248377108871 862305700 2874735 4871862305700000 232 | 0423-06-03T06:22:19.0226z -48805349861 22600000 -564877 22939022600000000 233 | 7301-10-20T16:42:46.12+07:01 168255394906 120000000 1947400 34906120000000000 234 | 6676-11-18T14:04:35.5z 148534869875 500000000 1719153 50675500000000000 235 | 7230-09-30t19:51:15.0195-00:47 166013181495 19500000 1921448 74295019500000000 236 | 7311-12-26t05:44:05.3433159z 168576702245 343315900 1951119 20645343315900000 237 | 8022-03-07t17:22:11.429+02:22 190988377211 429000000 2210513 54011429000000000 238 | 4642-04-14T11:51:12.7130293695+15:02 84329066952 713029369 976030 74952713029369500 239 | 9173-02-11T02:43:20.8-02:53 227308311380 800000000 2630883 20180800000000000 240 | 8270-12-14t05:21:12.21750331054+21:59 198838740132 217503310 2301374 26532217503310540 241 | 5540-02-10t10:21:04.518+20:04 112661677024 518000000 1303954 51424518000000000 242 | 0318-02-13T03:57:04z -52128388976 0 -603338 14224000000000000 243 | 7520-07-02t00:26:22.86-19:37 175156891402 860000000 2027278 72202860000000000 244 | 2012-05-14t23:32:34.6213z 1337038354 621300000 15474 84754621300000000 245 | 0788-11-02t02:22:37.72858019474+09:50 -37273937243 728580194 -431412 59557728580194740 246 | 4205-09-02t01:25:59.75z 70550875559 750000000 816561 5159750000000000 247 | 6828-02-23T21:15:12.4534Z 153308322912 453400000 1774401 76512453400000000 248 | 5775-07-19T21:19:13.79905692486Z 120091497553 799056924 1389947 76753799056924860 249 | 7454-01-27t07:57:14.57-03:31 173060623694 570000000 2003016 41294570000000000 250 | 0674-07-20t05:59:53.5979406427+06:47 -40880508427 597940642 -473155 83573597940642700 251 | 5042-09-05T13:20:00.9036Z 96964348800 903600000 1122272 48000903600000000 252 | 9844-09-21t06:01:26.516946z 248502232886 516946000 2876183 21686516946000000 253 | 7158-09-12T14:02:50.128682174z 163739455370 128682174 1895132 50570128682174000 254 | 7953-11-06T09:23:12.53123051+03:24 188831973552 531230510 2185555 21552531230510000 255 | 4438-03-24t14:11:00Z 77889737460 0 901501 51060000000000000 256 | 1977-06-15t04:48:02.4+11:06 235158122 400000000 2721 63722400000000000 257 | 9809-02-11t09:54:45.741991809z 247378528485 741991809 2863177 35685741991809000 258 | 5481-03-05t06:05:07.0565162z 110801973907 56516200 1282430 21907056516200000 259 | 6999-12-16T21:30:09.10929365+01:08 158730178929 109293650 1837154 73329109293650000 260 | 6607-10-28T11:28:57.6838734Z 146355506937 683873400 1693929 41337683873400000 261 | 5198-12-20t21:38:58.0853895Z 101896436338 85389500 1179356 77938085389500000 262 | 7802-07-01T14:21:48.1128287z 184055811708 112828700 2130275 51708112828700000 263 | 4130-12-06t19:45:00.65590309953+07:42 68192366580 655903099 789263 43380655903099530 264 | 6166-08-13t22:05:47.55696+05:36 132432424187 556960000 1532782 59387556960000000 265 | 3708-02-11t13:02:26.6z 54849531746 600000000 634832 46946600000000000 266 | 6023-09-01T00:05:12.35483605-08:04 127921363752 354836050 1480571 29352354836050000 267 | 2636-10-10T01:07:58.47726900354z 21041341678 477269003 243534 4078477269003540 268 | 1010-11-03t11:41:40.5151+11:17 -30268251320 515100000 -350327 1480515100000000 269 | 9281-04-01t06:51:14.618319+11:07 230720730254 618319000 2670378 71054618319000000 270 | 6992-11-24t09:04:34.2Z 158507399074 200000000 1834576 32674200000000000 271 | 7094-02-17T07:54:51.9Z 161701948491 900000000 1871550 28491900000000000 272 | 8076-02-12t16:06:01.3641z 192690461161 364100000 2230213 57961364100000000 273 | 1252-04-22T12:07:27.876627Z -22648161153 876627000 -262132 43647876627000000 274 | 3546-05-22t10:58:46.38056Z 49745962726 380560000 575763 39526380560000000 275 | 2266-10-07t10:28:48.184967343Z 9365020128 184967343 108391 37728184967343000 276 | 6806-06-29t12:12:38.8247+22:45 152624870858 824700000 1766491 48458824700000000 277 | 6588-11-13t03:04:03.84378826-22:20 145757496243 843788260 1687008 5043843788260000 278 | 7833-12-04t17:41:45.41Z 185047609305 410000000 2141754 63705410000000000 279 | 3139-06-18t07:10:12Z 36904576212 0 427136 25812000000000000 280 | 5258-09-22T02:26:06.7620821333Z 103782133566 762082133 1201182 8766762082133300 281 | 9017-08-22t01:08:47.983975+23:43 222401899547 983975000 2574096 5147983975000000 282 | 0003-09-24T18:52:03.9280323+04:09 -62049489417 928032300 -718166 52983928032300000 283 | 4964-08-31T12:49:19z 94502551759 0 1093779 46159000000000000 284 | 4339-04-30t10:04:47+21:55 74768616587 0 865377 43787000000000000 285 | 7521-11-05T11:39:14.89701846z 175199283554 897018460 2027769 41954897018460000 286 | 9024-02-19T07:45:44z 222606949544 0 2576469 27944000000000000 287 | 1902-05-27t13:17:42.6777206822-14:23 -2133202758 677720682 -24690 13242677720682200 288 | 8758-01-21t02:39:31.796188219-21:56 214210398931 796188219 2479287 2131796188219000 289 | 0993-02-24t08:05:39.55233712256Z -30826367661 552337122 -356787 29139552337122560 290 | 1583-04-11t09:02:02.0253390+08:56 -12203913238 25339000 -141249 362025339000000 291 | 3102-01-20t14:12:31-16:01 35724176011 0 413474 22411000000000000 292 | 6380-07-18t03:55:43.15510-12:35 139183374643 155100000 1610918 59443155100000000 293 | 2902-04-29t15:27:21.079127863-02:04 29421336681 79127863 340524 63081079127863000 294 | 9789-03-18t13:21:32.7139Z 246750499292 713900000 2855908 48092713900000000 295 | 9161-08-19t06:24:44.6z 226945952684 600000000 2626689 23084600000000000 296 | 0999-08-11t19:50:44.30339z -30622507756 303390000 -354428 71444303390000000 297 | 9364-04-29t00:55:55.050943-18:52 233342452075 50943000 2700722 71275050943000000 298 | 6848-01-20t13:56:60.167188-13:55 153936561120 167188000 1781673 13920167188000000 299 | 6864-12-24t14:06:25.86z 154470722785 860000000 1787855 50785860000000000 300 | 7111-07-24T14:02:17.728z 162251906537 728000000 1877915 50537728000000000 301 | 5513-11-12T00:37:12-15:32 111833539752 0 1294369 58152000000000000 302 | 2170-07-24T08:04:58.648400520-16:10 6329146498 648400520 73254 898648400520000 303 | 3937-01-18T12:17:19.87827063Z 62074037839 878270630 718449 44239878270630000 304 | 1980-05-11t16:16:05.866104317Z 326909765 866104317 3783 58565866104317000 305 | 9655-12-04T15:15:48.7538593030+21:40 242544303348 753859303 2807225 63348753859303000 306 | 2613-08-13T05:26:51.761+05:42 20310479091 761000000 235074 85491761000000000 307 | 4721-03-14t18:17:13.37+08:10 86819422033 370000000 1004854 36433370000000000 308 | 1078-02-18t19:14:16.24158952+21:03 -28144633724 241589520 -325749 79876241589520000 309 | 1107-01-14T06:51:01.9-11:41 -27232522079 900000000 -315192 66721900000000000 310 | 6006-08-28t21:21:07.31Z 127384608067 310000000 1474358 76867310000000000 311 | 2693-10-05t15:29:57.39953934-12:19 22839767337 399539340 264349 13737399539340000 312 | 9143-11-10t00:42:03.52233893365z 226385023323 522338933 2620197 2523522338933650 313 | 5434-03-27T09:59:46.6930465779+07:31 109320632926 693046577 1265285 8926693046577900 314 | 8795-03-30t08:59:40.8+09:25 215383793680 800000000 2492867 84880800000000000 315 | 1670-03-28T05:00:48.605Z -9459572352 605000000 -109486 18048605000000000 316 | 5378-09-07T17:54:26Z 107567718866 0 1244996 64466000000000000 317 | 4342-05-24t05:24:48.14752z 74865446688 147520000 866498 19488147520000000 318 | 7217-03-11T17:22:37.82727+21:08 165585327277 827270000 1916496 72877827270000000 319 | 3444-04-14T02:26:28.76+11:20 46523862388 760000000 538470 54388760000000000 320 | 3929-06-04t18:10:22.100197-02:14 61833443062 100197000 715664 73462100197000000 321 | 2238-09-29t00:07:55.07038483+13:56 8480628715 70384830 98155 36715070384830000 322 | 7691-04-11t06:31:36.816-08:07 180546071916 816000000 2089653 52716816000000000 323 | 9240-06-27t18:36:06.868305+05:19 229434470226 868305000 2655491 47826868305000000 324 | 2988-04-11t12:22:15.22238+18:54 32133691695 222380000 371917 62895222380000000 325 | 3808-12-11t08:05:19.05238934581-05:20 58031472319 52389345 671660 48319052389345810 326 | 9498-07-20T00:47:59.5704840+00:30 237578055479 570484000 2749746 1079570484000000 327 | 6767-01-21t10:00:04.0061+23:47 151380353584 6100000 1752087 36784006100000000 328 | 9296-06-12t19:28:28.2891651789-14:31 231200474368 289165178 2675931 35968289165178900 329 | 8242-01-11T06:07:09.370620870Z 197926092429 370620870 2290811 22029370620870000 330 | 5308-04-09T10:28:10.288081Z 105345656890 288081000 1219278 37690288081000000 331 | 8310-05-01t18:07:09.1787043z 200081470029 178704300 2315757 65229178704300000 332 | 6781-01-15T03:02:38.1238+10:31 151821707498 123800000 1757195 59498123800000000 333 | 1272-04-26T18:14:56.14082556z -22016641504 140825560 -254823 65696140825560000 334 | 5416-09-02T06:31:40.318-18:55 108766459600 318000000 1258871 5200318000000000 335 | 0669-03-20T18:26:46.260896438z -41048746394 260896438 -475102 66406260896438000 336 | 4040-04-13T21:27:19.7736Z 65331869239 773600000 756155 77239773600000000 337 | 1599-11-03T04:57:30.372Z -11681175750 372000000 -135199 17850372000000000 338 | 7645-09-24T16:28:54.886058397z 179108814534 886058397 2073018 59334886058397000 339 | 8883-10-23t19:36:08.66z 218178819368 660000000 2525217 70568660000000000 340 | 6279-06-05t16:11:53.11679346246Z 135992362313 116793462 1573985 58313116793462460 341 | 7385-04-05T09:26:23.36+01:04 170889121343 360000000 1977883 30143360000000000 342 | 1009-05-25T04:28:27.38269Z -30313769493 382690000 -350854 16107382690000000 343 | 4386-09-15t05:39:03.79956z 76263831543 799560000 882683 20343799560000000 344 | 8272-12-14t09:15:52.35592385909-01:51 198901998412 355923859 2302106 40012355923859090 345 | 6273-05-24T22:34:35.6-18:11 135802111535 600000000 1571783 60335600000000000 346 | 1766-05-24T14:26:44.76866871+06:46 -6425194756 768668710 -74366 27644768668710000 347 | 5305-12-26T14:19:03.49625579871z 105273526743 496255798 1218443 51543496255798710 348 | 0496-05-09t23:46:12.8Z -46503677628 800000000 -538238 85572800000000000 349 | 5842-02-08t11:14:26.7433Z 122191845266 743300000 1414257 40466743300000000 350 | 1343-09-15t11:55:45.355332z -19763957055 355332000 -228750 42945355332000000 351 | 6534-01-31t16:28:05.8849443Z 144028600085 884944300 1666997 59285884944300000 352 | 0696-01-27T18:04:16.340068161z -40201250144 340068161 -465293 65056340068161000 353 | 8400-03-24t11:13:60.6371-15:08 202918443720 637100000 2348593 8520637100000000 354 | 3570-01-12T23:54:51.64Z 50492159691 640000000 584399 86091640000000000 355 | 5618-10-23t20:47:50.272589-00:41 115145357330 272589000 1332700 77330272589000000 356 | 3005-02-08T15:56:33Z 32664786993 0 378064 57393000000000000 357 | 3956-09-22T04:22:27z 62694966147 0 725636 15747000000000000 358 | 2691-12-02t01:16:54.1677152363z 22781524614 167715236 263675 4614167715236300 359 | 7010-03-30T13:31:37.7132005557-14:56 159054726457 713200555 1840911 16057713200555700 360 | 3357-10-25T03:47:52.373677288Z 43795223272 373677288 506889 13672373677288000 361 | 0332-07-26T07:47:58.27382834-09:47 -51672407102 273828340 -598061 63298273828340000 362 | 7679-08-21T14:40:35.108495z 180178785635 108495000 2085402 52835108495000000 363 | 3539-03-25t23:31:48.513794970Z 49520071908 513794970 573148 84708513794970000 364 | 5297-07-08T20:55:19.5z 105006401719 500000000 1215351 75319500000000000 365 | 4565-10-31t03:58:32-00:13 81916546292 0 948108 15092000000000000 366 | 9058-01-20T23:37:26Z 223677416246 0 2588858 85046000000000000 367 | 2950-11-16T09:28:09.68900650+16:33 30953379309 689006500 358256 60909689006500000 368 | 8442-07-31T09:29:19.191824759+21:58 204254825479 191824759 2364060 41479191824759000 369 | 5901-06-01T05:07:31.17701-11:23 124063461031 177010000 1435919 59431177010000000 370 | 7865-08-17T09:41:04.7Z 186048006064 700000000 2153333 34864700000000000 371 | 9405-07-28t08:28:51.0z 234643940931 0 2715786 30531000000000000 372 | 0829-12-06t13:52:38.5299Z -35977082842 529900000 -416402 49958529900000000 373 | 7795-12-09t22:24:36.74675741304-13:56 183848962836 746757413 2127881 44436746757413040 374 | 3111-01-19T07:12:45.4272182z 36008003565 427218200 416759 25965427218200000 375 | 1574-01-19t03:02:29.4251427Z -12494984251 425142700 -144618 10949425142700000 376 | 9011-11-22t19:03:26.73-17:17 222220671626 730000000 2571998 44426730000000000 377 | 8232-06-07T03:38:05.985823z 197623251485 985823000 2287306 13085985823000000 378 | 4178-04-06t11:47:17.8799855398z 69686048837 879985539 806551 42437879985539800 379 | 6437-02-21T22:08:30.38Z 140969455710 380000000 1631590 79710380000000000 380 | 6821-09-21T23:40:28.6Z 153105637228 600000000 1772055 85228600000000000 381 | 1730-08-21T16:01:40.812+03:48 -7553562380 812000000 -87426 44020812000000000 382 | 5925-02-03t23:09:30.691353+19:44 124810601130 691353000 1444567 12330691353000000 383 | 6272-07-23t09:04:52.5Z 135775645492 500000000 1571477 32692500000000000 384 | 5569-06-23t04:57:17.2769972856Z 113588456237 276997285 1314681 17837276997285600 385 | 0777-09-09t22:03:36.136z -37625651784 136000000 -435483 79416136000000000 386 | 3884-04-23T21:38:46Z 60409834726 0 699187 77926000000000000 387 | 1910-01-05t02:16:30.6669Z -1893102210 666900000 -21911 8190666900000000 388 | 5508-03-14T05:29:26.499086-05:57 111654761186 499086000 1292300 41186499086000000 389 | 5977-06-11T23:10:41.7010-05:29 126462746381 701000000 1463689 16781701000000000 390 | 1687-04-26T04:34:45.94Z -8920610715 940000000 -103248 16485940000000000 391 | 7192-10-28T03:38:52+10:43 164816355352 0 1907596 60952000000000000 392 | 1036-04-10T02:05:48.65406598+15:40 -29465645652 654065980 -341038 37548654065980000 393 | 9676-01-29T14:05:11.2586663Z 243180367511 258666300 2814587 50711258666300000 394 | 5126-09-07T06:25:28.156Z 99615248728 156000000 1152954 23128156000000000 395 | 6713-03-14T19:51:59.71363027z 149680900319 713630270 1732417 71519713630270000 396 | 6220-11-16t23:54:01.84330Z 134144726041 843300000 1552600 86041843300000000 397 | 9056-04-01t11:02:52z 223620433372 0 2588199 39772000000000000 398 | 2460-10-14t08:44:50.34030987648z 15487749890 340309876 179256 31490340309876480 399 | 2337-06-02T16:56:15.464471166Z 11594595375 464471166 134196 60975464471166000 400 | 1592-05-13T05:30:55.209022094+20:05 -11917118045 209022094 -137930 33955209022094000 401 | 6396-11-13t08:04:56Z 139698461096 0 1616880 29096000000000000 402 | 5003-07-07t12:30:09.8+10:13 95728357029 800000000 1107967 8229800000000000 403 | 2274-11-26T13:07:22.1Z 9621810442 100000000 111363 47242100000000000 404 | 2372-10-15t05:10:49.80512793899z 12710754649 805127938 147115 18649805127938990 405 | 6812-05-22t21:23:60.53752293002-22:50 152811173640 537522930 1768647 72840537522930020 406 | 8895-02-12T21:25:44.9516531856-07:20 218535684344 951653185 2529348 17144951653185600 407 | 8552-06-23T17:24:16.6492232913-19:16 207723012016 649223291 2404201 45616649223291300 408 | 7797-12-02t19:08:54.585173285+19:50 183911383134 585173285 2128603 83934585173285000 409 | 5115-01-31T00:51:12.91z 99249151872 910000000 1148717 3072910000000000 410 | 9122-11-28t20:14:51.7461247-19:43 225724031871 746124700 2612546 57471746124700000 411 | 5798-01-12T09:48:36.6872840-05:23 120801078696 687284000 1398160 54696687284000000 412 | 8400-07-15t10:02:39.22230850392z 202928148159 222308503 2348705 36159222308503920 413 | 6521-07-21T10:25:17.45861z 143633125517 458610000 1662420 37517458610000000 414 | 9430-10-20T22:43:31.03280-01:19 235440172951 32800000 2725002 151032800000000 415 | 2584-11-30t15:25:18.15934895541Z 19404890718 159348955 224593 55518159348955410 416 | 8645-07-12T15:48:19.28565080z 210659327299 285650800 2438186 56899285650800000 417 | 1343-05-02T05:27:08.5772365Z -19775730772 577236500 -228886 19628577236500000 418 | 9985-04-02t08:32:50Z 252936894770 0 2927510 30770000000000000 419 | 2318-02-01T06:25:36.9568814800z 10984487136 956881480 127135 23136956881480000 420 | 5812-06-18t15:44:18.543729Z 121256408658 543729000 1403430 56658543729000000 421 | 7155-05-21t22:53:48.73z 163634943228 730000000 1893922 82428730000000000 422 | 6854-04-02T02:54:02.24884Z 154132080842 248840000 1783936 10442248840000000 423 | 1328-05-15T00:33:51.6360868239z -20247924369 636086823 -234351 2031636086823900 424 | 0983-04-22t17:10:60.60513714403-10:31 -31136991480 605137144 -360382 13320605137144030 425 | 4836-11-02T18:21:54.5+00:41 90468726054 500000000 1047091 63654500000000000 426 | 1593-11-21T02:48:29Z -11868930691 0 -137372 10109000000000000 427 | 5308-05-04t14:38:40z 105347831920 0 1219303 52720000000000000 428 | 4706-03-18T19:40:03.203+08:44 86346384963 203000000 999379 39363203000000000 429 | 9314-05-24t03:50:29.4093Z 231766631429 409300000 2682484 13829409300000000 430 | 6179-08-12T18:51:39.91069z 132842573499 910690000 1537529 67899910690000000 431 | 5259-04-17t20:15:01+06:53 103800057721 0 1201389 48121000000000000 432 | 0528-06-13t12:15:60.65238-19:12 -45490869120 652380000 -526515 26880652380000000 433 | 8515-09-25T05:12:19.32Z 206563324339 320000000 2390779 18739320000000000 434 | 5359-08-02T12:22:32.826Z 106964972552 826000000 1238020 44552826000000000 435 | 8640-07-21T04:10:30.006002749-03:57 210502310850 6002749 2436369 29250006002749000 436 | 5166-05-01T23:15:09.35z 100866467709 350000000 1167435 83709350000000000 437 | 9919-04-25t15:39:04.69-05:06 250856081104 690000000 2903426 74704690000000000 438 | 6923-09-30T08:08:33.08283-22:35 156325185813 82830000 1809319 24213082830000000 439 | 1902-12-25t05:25:40.25152Z -2114966060 251520000 -24479 19540251520000000 440 | 3969-06-13T13:52:28.32789322542+22:29 63096420208 327893225 730282 55408327893225420 441 | 2969-01-25t15:30:08.5+19:35 31527518108 500000000 364901 71708500000000000 442 | 1133-04-21t20:35:44.9581Z -26403593056 958100000 -305598 74144958100000000 443 | 9518-11-06t07:00:59.20Z 238218562859 200000000 2757159 25259200000000000 444 | 1853-10-13T00:49:12.290-00:20 -3667503048 290000000 -42448 4152290000000000 445 | 3588-02-04T14:21:10.510+20:19 51062032930 510000000 590995 64930510000000000 446 | 6480-12-11t10:57:49.87010772680Z 142351729069 870107726 1647589 39469870107726800 447 | 7417-02-21t21:07:38.2+01:20 171895204058 200000000 1989527 71258200000000000 448 | 0864-08-19t18:15:03Z -34881947097 0 -403727 65703000000000000 449 | 4308-01-01t04:56:18.8621885362Z 73780088178 862188536 853936 17778862188536200 450 | 4021-06-01T01:33:20.233611z 64736415200 233611000 749264 5600233611000000 451 | 0015-05-17T00:42:57.5-10:21 -61682043363 500000000 -713913 39837500000000000 452 | 5326-09-28T04:10:23.80+00:52 105928485503 800000000 1226024 11903800000000000 453 | 3131-09-21t03:28:26.889-22:05 36660389606 889000000 424310 5606889000000000 454 | 6908-07-19T02:03:17.2302z 155845476197 230200000 1803767 7397230200000000 455 | 9414-08-06T23:31:51.911841676-06:03 234928791291 911841676 2719083 20091911841676000 456 | 0498-12-18t12:48:35.1861+17:49 -46421442025 186100000 -537286 68375186100000000 457 | 7834-03-04t17:19:36.797742z 185055383976 797742000 2141844 62376797742000000 458 | 3464-05-09T14:27:12.77+01:48 47157251952 770000000 545801 45552770000000000 459 | 6560-07-03t03:25:07.35150601-18:31 144862379767 351506010 1676647 78967351506010000 460 | 7058-05-13t12:13:34Z 160573234414 0 1858486 44014000000000000 461 | 6272-08-31t20:24:50.74472Z 135779055890 744720000 1571516 73490744720000000 462 | 3125-01-01T21:01:43.0174520-16:08 36448405783 17452000 421856 47383017452000000 463 | 8100-05-18T02:58:36.484z 193456004316 484000000 2239074 10716484000000000 464 | 9118-02-22t22:24:03.046785z 225573632643 46785000 2610805 80643046785000000 465 | 9396-01-06t02:48:18.984160z 234342384498 984160000 2712296 10098984160000000 466 | 3479-05-24T12:39:42Z 47631847182 0 551294 45582000000000000 467 | 1315-06-26T13:24:45.749Z -20654562915 749000000 -239058 48285749000000000 468 | 4022-05-18t20:33:32.236520z 64766810012 236520000 749615 74012236520000000 469 | 0180-05-16t13:36:26.86750Z -56475138214 867500000 -653648 48986867500000000 470 | 1369-01-16t17:09:08.29179089+06:03 -18964328032 291790890 -219495 39968291790890000 471 | 6012-12-15t09:37:55z 127583372275 0 1476659 34675000000000000 472 | 6586-05-17T21:47:32.7752z 145678772852 775200000 1686096 78452775200000000 473 | 3062-04-30T17:17:51.771-15:15 34470606771 771000000 398965 30771771000000000 474 | 9680-10-14T11:19:45.8990034Z 243328965585 899003400 2816307 40785899003400000 475 | 3989-11-18T00:38:10.88229474z 63741256690 882294740 737746 2290882294740000 476 | 5146-05-24t07:30:26.80936Z 100237246226 809360000 1160153 27026809360000000 477 | 1079-02-02T15:10:24.00549Z -28114418976 5490000 -325399 54624005490000000 478 | 9866-05-27t20:53:32.3Z 249186401612 300000000 2884101 75212300000000000 479 | 8971-09-24t14:27:03.5Z 220953277623 500000000 2557329 52023500000000000 480 | 5445-07-19T00:53:47.934+04:15 109677616727 934000000 1269416 74327934000000000 481 | 1387-03-26t12:54:14.4487786Z -18390366346 448778600 -212852 46454448778600000 482 | 0850-01-30T21:35:17.25+15:10 -35341205683 250000000 -409042 23117250000000000 483 | 9423-12-21t06:49:07.792731z 235224542947 792731000 2722506 24547792731000000 484 | 4020-12-14t20:48:44.6845016954-15:15 64721937824 684501695 749096 43424684501695400 485 | 2338-09-02t14:53:41.23298018z 11634072821 232980180 134653 53621232980180000 486 | 1493-03-11T20:20:07.70603490218Z -15046573193 706034902 -174151 73207706034902180 487 | 1682-07-25t07:37:24.6793263176-08:04 -9070561116 679326317 -104984 56484679326317600 488 | 4474-01-01t02:46:34z 79018685194 0 914568 9994000000000000 489 | 8151-11-13T16:20:26.3-03:44 195080904266 300000000 2257880 72266300000000000 490 | 2267-04-16T20:49:25.698479560-07:39 9381587305 698479560 108583 16105698479560000 491 | 1845-05-01T03:24:56.0434522813-00:30 -3934209904 43452281 -45535 14096043452281300 492 | 0973-07-31t22:00:12.60-15:12 -31443850068 600000000 -363934 47532600000000000 493 | 2087-01-09t05:55:24.95717217020z 3692930124 957172170 42742 21324957172170200 494 | 1761-09-27t01:44:22.941118+18:11 -6572161598 941118000 -76067 27202941118000000 495 | 8311-07-23t14:07:51.57091035073+13:18 200120114991 570910350 2316205 2991570910350730 496 | 4294-10-28t14:15:40.55+18:34 73364298100 550000000 849123 70900550000000000 497 | 7050-06-11T04:06:27.16440506+13:58 160323199707 164405060 1855592 50907164405060000 498 | 1022-10-18T07:59:42.997Z -29890915218 997000000 -345960 28782997000000000 499 | 1643-01-23T05:01:59.7178383350+05:31 -10317198541 717838335 -119413 84659717838335000 500 | 2945-07-23t22:29:16.5z 30785696956 500000000 356315 80956500000000000 501 | 7433-08-01t16:52:03.422733799Z 172414025523 422733799 1995532 60723422733799000 502 | 3732-08-28t22:41:36.8123271+14:24 55624090656 812327100 643797 29856812327100000 503 | 1628-03-02t15:19:53.0933Z -10787157607 93300000 -124852 55193093300000000 504 | 4956-12-04T02:12:09.124269z 94258260729 124269000 1090952 7929124269000000 505 | 5161-10-30T19:29:29.9Z 100724412569 900000000 1165791 70169900000000000 506 | 9959-03-18T05:28:11.983296300+06:37 252115023071 983296300 2917997 82271983296300000 507 | 8689-10-15T09:51:54.3176938029+12:25 212056003614 317693802 2454351 77214317693802900 508 | 0411-11-14t10:26:41.8623123+01:01 -49169860459 862312300 -569096 33941862312300000 509 | 8793-12-31T22:26:14.56443524122-22:34 215344731614 564435241 2492415 75614564435241220 510 | 6077-11-08t22:20:13.704Z 129631443613 704000000 1500363 80413704000000000 511 | 4537-11-19t11:08:17.187972z 81034600097 187972000 937900 40097187972000000 512 | 0834-08-06t02:01:38Z -35829899902 0 -414698 7298000000000000 513 | 5038-08-16T01:41:32.8156815649+01:07 96836344472 815681564 1120791 2072815681564900 514 | 1984-02-23T11:12:17.6-18:59 446451077 600000000 5167 22277600000000000 515 | 9976-05-01T16:37:53-07:58 252655461353 0 2924253 2153000000000000 516 | 6358-02-27t03:54:19.3+11:57 138476793439 300000000 1602740 57439300000000000 517 | 1669-12-14T08:25:41Z -9468545659 0 -109590 30341000000000000 518 | 1502-03-06T15:04:57.4Z -14763113703 400000000 -170870 54297400000000000 519 | 6715-02-28T08:11:55.912Z 149742720715 912000000 1733133 29515912000000000 520 | 0604-03-27t12:07:18.8763487z -43099386762 876348700 -498836 43638876348700000 521 | 5855-04-05t21:39:06.499z 122606948346 499000000 1419061 77946499000000000 522 | 2313-03-31t05:51:30.5131360-00:00 10831729890 513136000 125367 21090513136000000 523 | 3434-03-17t16:15:54.050+17:41 46205850894 50000000 534789 81294050000000000 524 | 3973-11-13t19:52:54+10:10 63235935774 0 731897 34974000000000000 525 | 8597-07-26T11:50:18.40z 209145844218 400000000 2420669 42618400000000000 526 | 0013-01-14t23:10:50.50-10:19 -61755661810 500000000 -714765 34190500000000000 527 | 3215-03-23T20:28:34.882252z 39295484914 882252000 454808 73714882252000000 528 | 2486-12-01t13:00:34Z 16312366834 0 188800 46834000000000000 529 | 0958-05-01t05:46:38.869-16:44 -31925150962 869000000 -369505 81038869000000000 530 | 7860-04-07T09:06:05.2678+06:13 185878810385 267800000 2151375 10385267800000000 531 | 2741-09-19t19:35:30.85331287718+09:01 24353001270 853312877 281863 38070853312877180 532 | 5485-07-10T06:26:04.36-13:27 110939226784 360000000 1284018 71584360000000000 533 | 1224-06-28T04:03:24.33284z -23526014196 332840000 -272292 14604332840000000 534 | 3406-10-21T15:23:40.5396657678z 45341133820 539665767 524781 55420539665767800 535 | 0047-11-18t16:41:18-20:30 -60656122122 0 -702039 47478000000000000 536 | 7052-03-09T08:20:46.80-17:32 160378365166 800000000 1856231 6766800000000000 537 | 1681-06-22T21:57:56Z -9104925724 0 -105382 79076000000000000 538 | 1133-07-26t15:25:12.75373478-13:19 -26395269348 753734780 -305501 17052753734780000 539 | 2203-07-24T10:52:24.124z 7370391144 124000000 85305 39144124000000000 540 | 4006-06-21T00:33:30.1384690z 64264754010 138469000 743805 2010138469000000 541 | 1683-07-01t03:51:51Z -9041141289 0 -104643 13911000000000000 542 | 8808-07-08T02:10:18-08:04 215802785658 0 2497717 36858000000000000 543 | 2811-03-12T11:27:35.17781483-12:46 26545536815 177814830 307240 815177814830000 544 | 0800-08-22T18:32:50.3282039299z -36901373230 328203929 -427100 66770328203929900 545 | 2740-09-23t04:52:07.326-12:04 24321833767 326000000 281502 60967326000000000 546 | 3018-06-16t09:49:28.69918755759Z 33086051368 699187557 382940 35368699187557590 547 | 1397-08-30T08:52:55.92027Z -18061196825 920270000 -209042 31975920270000000 548 | 3508-07-18T18:42:36.322684753Z 48551769756 322684753 561941 67356322684753000 549 | 2246-09-02T22:09:19.69286213-10:58 8730925639 692862130 101052 32839692862130000 550 | 0617-01-30t16:30:45Z -42693982155 0 -494144 59445000000000000 551 | 3224-02-04T17:14:22.24344397Z 39575409262 243443970 458048 62062243443970000 552 | 2200-02-08t06:04:45.540301-12:31 7261468545 540301000 84044 66945540301000000 553 | 6013-12-02t09:59:38.7Z 127613786378 700000000 1477011 35978700000000000 554 | 5462-12-25T10:03:40.1386z 110227860220 138600000 1275785 36220138600000000 555 | 1873-01-26T07:15:42.95763459323Z -3058793058 957634593 -35403 26142957634593230 556 | 1639-08-25t00:22:20.020Z -10424936260 20000000 -120659 1340020000000000 557 | 4240-04-30t08:27:01.574505+10:40 71644600021 574505000 829219 78421574505000000 558 | 1004-10-21t12:15:47.0Z -30458634253 0 -352531 44147000000000000 559 | 2720-11-10T13:35:29.8550049112-13:49 23694866669 855004911 274246 12269855004911200 560 | 0041-04-10t16:30:12.0027179Z -60864679788 2717900 -704453 59412002717900000 561 | 6058-07-05t04:51:04.3105z 129020878264 310500000 1493297 17464310500000000 562 | 9121-03-11T10:29:11.2904189271-17:32 225669816071 290418927 2611919 14471290418927100 563 | 9355-03-17t10:19:56.59895288-18:49 233054687336 598952880 2697392 18536598952880000 564 | 9052-02-27t18:56:13.168921012+21:47 223491215353 168921012 2586703 76153168921012000 565 | 5261-04-05t21:29:24.19751947855-13:21 103862256624 197519478 1202109 39024197519478550 566 | 3849-02-14t05:22:07.627793+05:48 59299342447 627793000 686334 84847627793000000 567 | 4349-03-28t21:58:01.58539Z 75081506281 585390000 868998 79081585390000000 568 | 4609-07-31t17:47:23.479z 83297094443 479000000 964086 64043479000000000 569 | 2085-10-27T03:06:17.86+09:37 3654955757 860000000 42302 62957860000000000 570 | 4958-10-04t19:03:25.5315855Z 94316123005 531585500 1091621 68605531585500000 571 | 8302-06-08T17:13:21+15:18 199832234121 0 2312873 6921000000000000 572 | 7806-08-26T02:54:48+07:40 184186811688 0 2131791 69288000000000000 573 | 0813-09-12T17:00:38.3070321764-06:05 -36489315262 307032176 -422331 83138307032176400 574 | 8510-12-11t01:02:01.32590303Z 206412195721 325903030 2389030 3721325903030000 575 | 6801-01-16T02:00:00.2026Z 152452980000 202600000 1764502 7200202600000000 576 | 3412-10-21T09:11:57.517027+23:40 45530415117 517027000 526972 34317517027000000 577 | 1907-08-06T09:19:09.491z -1969368051 491000000 -22794 33549491000000000 578 | 8575-08-07T05:37:22.37725508612z 208452548242 377255086 2412645 20242377255086120 579 | 7763-09-21t15:23:20.735938Z 182832218600 735938000 2116113 55400735938000000 580 | 6762-03-21t09:17:00z 151227767820 0 1750321 33420000000000000 581 | 1584-06-03T23:49:15.7950492-15:32 -12167570325 795049200 -140829 55275795049200000 582 | 8375-08-31T02:00:28.10-22:02 202143254548 100000000 2339621 148100000000000 583 | 9993-04-28t06:02:28.5221z 253191592948 522100000 2930458 21748522100000000 584 | 4986-01-12T01:14:10.44z 95176775650 440000000 1101583 4450440000000000 585 | 3397-11-07T23:23:43.43767+05:00 45058703023 437670000 521512 66223437670000000 586 | 3061-11-01T17:55:32.559+23:03 34454919152 559000000 398783 67952559000000000 587 | 2364-09-11T23:19:41.9970400224z 12455421581 997040022 144159 83981997040022400 588 | 4282-09-19T13:10:17.60142685Z 72982300217 601426850 844702 47417601426850000 589 | 0903-10-07T22:57:35.17403957717+05:57 -33647122765 174039577 -389435 61235174039577170 590 | 2677-04-12T07:27:57.575073-21:48 22319644557 575073000 258329 18957575073000000 591 | 9100-12-08t17:41:51.1Z 225030591711 100000000 2604520 63711100000000000 592 | 7484-06-28t01:34:05.54z 174020492045 540000000 2014126 5645540000000000 593 | 3029-09-25t10:57:41.67z 33441937061 670000000 387059 39461670000000000 594 | 3439-09-10t05:16:21.26759294653Z 46378934181 267592946 536793 18981267592946530 595 | 6514-11-11t16:04:18-22:22 143422064778 0 1659977 51978000000000000 596 | 2219-01-01T15:09:19+04:06 7857687799 0 90945 39799000000000000 597 | 9193-05-26t01:22:11.923936690+03:16 227948421971 923936690 2638291 79571923936690000 598 | 7403-06-22t02:02:18.22073111z 171463744938 220731110 1984534 7338220731110000 599 | 0845-07-24t00:47:34.374750970+02:04 -35483879786 374750970 -410694 81814374750970000 600 | 7151-10-09t09:22:44.7+15:17 163520791544 700000000 1892601 65144700000000000 601 | 2522-02-20T00:00:57.3082255z 17423769657 308225500 201664 57308225500000 602 | 2541-11-27T05:32:15.327816611+07:37 18047570115 327816611 208883 78915327816611000 603 | 5019-07-24t14:04:01Z 96234789841 0 1113828 50641000000000000 604 | 2079-07-10t11:07:29.075590-00:09 3456213389 75590000 40002 40589075590000000 605 | 6012-05-06T21:25:07.95722z 127564147507 957220000 1476436 77107957220000000 606 | 9080-05-03t23:09:38.99510z 224380624178 995100000 2596997 83378995100000000 607 | 9477-07-26t15:27:14.875+05:03 236915922254 875000000 2742082 37454875000000000 608 | 0434-11-02T18:31:60.510240+11:51 -48445060740 510240000 -560707 24060510240000000 609 | 3027-09-29T05:12:29.62347680Z 33379103549 623476800 386332 18749623476800000 610 | 0453-02-28T07:47:30.1772379490z -47866781550 177237949 -554014 28050177237949000 611 | 3830-09-03t18:32:42.4281+13:40 58717111962 428100000 679596 17562428100000000 612 | 3253-09-23t18:14:16.7Z 40510606456 700000000 468872 65656700000000000 613 | 9477-06-28t04:32:39+07:37 236913454539 0 2742053 75339000000000000 614 | 7478-12-21t09:23:29.6373896+10:33 173846299829 637389600 2012109 82229637389600000 615 | 1603-07-29T10:14:16-03:47 -11563293524 0 -133835 50476000000000000 616 | 9545-09-15t21:06:02.065352494+05:45 239066176862 65352494 2766969 55262065352494000 617 | 3422-05-26T04:28:58+17:38 45833165458 0 530476 39058000000000000 618 | 6325-07-23T08:39:51.69445676521+05:37 137448068571 694456765 1590834 10971694456765210 619 | 9612-10-30T22:53:15.7-02:15 241184480895 700000000 2791487 4095700000000000 620 | 8911-02-24t04:25:38.55031755z 219041468738 550317550 2535202 15938550317550000 621 | 2636-05-03t00:21:60.6165604+10:12 21027478200 616560400 243373 51000616560400000 622 | 5973-02-26t06:28:23z 126327364103 0 1462122 23303000000000000 623 | 7285-04-07T14:56:00.662z 167733644160 662000000 1941361 53760662000000000 624 | 0787-03-20t11:55:47.253578579-22:08 -37325022973 253578579 -432003 36227253578579000 625 | 0010-01-15t20:19:22.8923021950Z -61850317238 892302195 -715861 73162892302195000 626 | 4341-04-23t09:00:01.70951924z 74831245201 709519240 866102 32401709519240000 627 | 2787-10-14T15:56:45.0636z 25806787005 63600000 298689 57405063600000000 628 | 2790-09-11T19:40:07.405-23:43 25898728987 405000000 299753 69787405000000000 629 | 6718-08-12T23:12:60.621683247-00:05 149851725480 621683247 1734394 83880621683247000 630 | 1835-06-09t02:46:19.868759851Z -4246463621 868759851 -49149 9979868759851000 631 | 9970-05-03t20:17:04.270706636+03:30 252466217224 270706636 2922062 60424270706636000 632 | 4445-04-28T16:22:32.6222-20:45 78113768852 622200000 904094 47252622200000000 633 | 8236-07-10T20:34:12-22:48 197752476132 0 2288801 69732000000000000 634 | 4473-04-21T07:33:15.733z 78996670395 733000000 914313 27195733000000000 635 | 9206-09-17T19:25:28.9518646Z 228368575528 951864600 2643154 69928951864600000 636 | 2701-08-31T14:38:24Z 23089070304 0 267234 52704000000000000 637 | 5733-06-29t13:35:22.9593358-23:50 118764451522 959335800 1374588 48322959335800000 638 | 3397-02-12t01:23:55.83Z 45035486635 830000000 521244 5035830000000000 639 | 2083-03-31T02:01:57.11243z 3573684117 112430000 41362 7317112430000000 640 | 6304-06-19T00:42:22.726247883z 136782434542 726247883 1583130 2542726247883000 641 | 9463-07-30T03:20:46-07:17 236474419066 0 2736972 38266000000000000 642 | 9672-07-04t20:09:13.2985Z 243067723753 298500000 2813283 72553298500000000 643 | 2816-03-27t22:17:37.8785z 26704678657 878500000 309081 80257878500000000 644 | 8542-05-13t21:55:55.55Z 207403797355 550000000 2400506 78955550000000000 645 | 6341-07-27t08:31:47.914914-13:31 137953404167 914914000 1596682 79367914914000000 646 | 8709-05-06t10:50:17.20659674609+02:17 212673112397 206596746 2461494 30797206596746090 647 | 0441-03-04T12:49:59.2986122Z -48245109001 298612200 -558393 46199298612200000 648 | 7858-08-30T01:36:45.36z 185828175405 360000000 2150789 5805360000000000 649 | 4737-09-16T19:58:26.8-06:13 87340471886 800000000 1010885 7886800000000000 650 | 7903-02-19t01:12:56.5291398z 187231569176 529139800 2167032 4376529139800000 651 | 8673-01-02T15:12:17.12-04:36 211526452097 120000000 2448222 71297120000000000 652 | 5852-02-05T05:32:08.3218-11:05 122507138228 321800000 1417906 59828321800000000 653 | 1698-02-20T21:47:01.975089788Z -8579009579 975089788 -99295 78421975089788000 654 | 2070-05-21t10:01:20z 3167892080 0 36665 36080000000000000 655 | 1504-01-11t19:12:18.3-00:47 -14704689642 300000000 -170194 71958300000000000 656 | 2085-01-11T13:27:53.27385857z 3630058073 273858570 42014 48473273858570000 657 | 8088-06-07t21:18:21z 193079193501 0 2234712 76701000000000000 658 | 3309-04-01T02:18:34.2z 42262568314 200000000 489150 8314200000000000 659 | 3404-01-31t15:59:31.59574467-10:33 45255292351 595744670 523788 9151595744670000 660 | 3350-08-17t23:33:03.1Z 43568407983 100000000 504263 84783100000000000 661 | 3786-02-28T10:44:28-23:02 57312611188 0 663340 35188000000000000 662 | 6555-11-30T05:12:06.1z 144717426726 100000000 1674970 18726100000000000 663 | 0636-10-01T12:49:25.9-05:58 -42073276355 900000000 -486960 67645900000000000 664 | 3541-12-08t20:12:43.02z 49605509563 20000000 574137 72763020000000000 665 | 6120-08-21T00:11:57+21:06 130981374357 0 1515988 11157000000000000 666 | 6275-08-05T02:26:53.060453392+14:29 135871300673 60453392 1572584 43073060453392000 667 | 1132-12-26t03:39:26.0Z -26413676434 0 -305714 13166000000000000 668 | 5605-03-05T09:34:56.747Z 114715042496 747000000 1327720 34496747000000000 669 | 8265-10-28t03:51:37.711355334Z 198676986697 711355334 2299502 13897711355334000 670 | 7728-09-05T04:33:01.032480+20:24 181726272541 32480000 2103313 29341032480000000 671 | 7356-11-10T01:31:12.715264z 169992869472 715264000 1967510 5472715264000000 672 | 7623-08-17t06:05:15.75613-13:34 178411232355 756130000 2064944 70755756130000000 673 | 0539-01-03t00:00:03Z -45157823997 0 -522660 3000000000000 674 | 8972-10-10t09:16:26.867729z 220986263786 867729000 2557711 33386867729000000 675 | 6312-12-29T13:30:40Z 137051616640 0 1586245 48640000000000000 676 | 6960-03-16T12:55:25.45060829204z 157475710525 450608292 1822635 46525450608292040 677 | 3593-03-06T00:02:29.4262389286Z 51222499349 426238928 592853 149426238928600 678 | 6631-08-28t19:10:04.90771z 147107646604 907710000 1702634 69004907710000000 679 | 9845-07-27T03:59:37.59965423961Z 248528923177 599654239 2876492 14377599654239610 680 | 6781-09-28T02:21:47Z 151843861307 0 1757452 8507000000000000 681 | 0603-01-04t23:33:46+04:50 -43138070174 0 -499284 67426000000000000 682 | 0773-08-06T05:26:47.565831070+00:45 -37754882293 565831070 -436978 16907565831070000 683 | 5151-07-21t19:37:10.77351849526-20:40 100400141830 773518495 1162038 58630773518495260 684 | 1326-07-14T20:49:36.390662872+10:29 -20305863564 390662872 -235022 37236390662872000 685 | 5394-08-18t04:30:54.9063474153+16:18 108070805574 906347415 1250819 43974906347415300 686 | 9677-06-01t19:36:43.4699730191z 243222637003 469973019 2815076 70603469973019100 687 | 1860-12-13T11:49:25.999405229z -3441269435 999405229 -39830 42565999405229000 688 | 0472-10-09t06:26:47.6603+14:20 -47247954793 660300000 -546852 58007660300000000 689 | 7265-03-03T07:30:13.503452z 167099441413 503452000 1934021 27013503452000000 690 | 5690-03-25T08:09:37.694-02:56 117399150337 694000000 1358786 39937694000000000 691 | 3622-11-06T02:09:39-03:59 52158838119 0 603690 22119000000000000 692 | 2013-09-02t05:23:48.831z 1378099428 831000000 15950 19428831000000000 693 | 0584-03-04T14:14:03.64484Z -43732431957 644840000 -506163 51243644840000000 694 | 7414-08-27T10:13:33.63664566153z 171816632013 636645661 1988618 36813636645661530 695 | 0422-01-17t17:59:59.9975z -48848680801 997500000 -565379 64799997500000000 696 | 6138-07-09t14:15:02.9569808921-15:21 131545834562 956980892 1522521 20162956980892100 697 | 5794-03-14t12:30:01.7043421+13:19 120680061061 704342100 1396759 83461704342100000 698 | 4614-12-17t01:40:18-20:33 83466886398 0 966051 79998000000000000 699 | 5801-02-15t14:51:30z 120898623090 0 1399289 53490000000000000 700 | 5404-01-26t19:35:18.1551488058z 108368739318 155148805 1254267 70518155148805800 701 | 0666-02-24t06:04:35.248363Z -41145558925 248363000 -476222 21875248363000000 702 | 1366-07-26T16:07:41.0728607Z -19042501939 72860700 -220400 58061072860700000 703 | 6043-03-26T06:25:45.95683477z 128538771945 956834770 1487717 23145956834770000 704 | 3631-07-06t14:44:15.5403102-11:00 52432278255 540310200 606855 6255540310200000 705 | 9952-05-05T06:39:59.79Z 251898359999 790000000 2915490 23999790000000000 706 | 3355-12-29t10:17:35.87Z 43737704255 870000000 506223 37055870000000000 707 | 9194-04-28t19:09:13.5977477+15:54 227977557313 597747700 2638629 11713597747700000 708 | 6938-03-02T09:30:02.609686z 156780178202 609686000 1814585 34202609686000000 709 | 6831-07-22T00:10:42.9048121+04:23 153415799262 904812100 1775645 71262904812100000 710 | 5462-12-28t15:15:21.4517250z 110228138121 451725000 1275788 54921451725000000 711 | 1404-12-29T13:03:54.93504942065+05:21 -17829908226 935049420 -206365 27774935049420650 712 | 4694-05-01T13:17:49.57164205Z 85971590269 571642050 995041 47869571642050000 713 | 1672-05-31T06:19:03.292626651-12:03 -9390836277 292626651 -108691 66123292626651000 714 | 4090-12-09t03:46:55.7637+07:18 66930352135 763700000 774656 73735763700000000 715 | 5316-09-13t19:49:22-20:31 105611790022 0 1222358 58822000000000000 716 | 0738-05-28t00:33:59.830386Z -38865482761 830386000 -449832 2039830386000000 717 | 2220-05-08t18:23:20.63+12:52 7900263080 630000000 91438 19880630000000000 718 | 4079-07-30t22:06:46.6z 66571884406 600000000 770507 79606600000000000 719 | 1025-08-21t19:25:00.7511z -29801190900 751100000 -344922 69900751100000000 720 | 0978-11-14t20:43:08.234+21:59 -31277063752 234000000 -362004 81848234000000000 721 | 0412-10-26T22:34:42.9482278-23:44 -49139746878 948227800 -568748 80322948227800000 722 | 9916-10-11T03:36:48.36946569087+16:31 250775953548 369465690 2902499 39948369465690870 723 | 6850-08-15t21:21:35.415709-16:15 154017639395 415709000 1782611 48995415709000000 724 | 0443-07-12T10:36:57.36443024Z -48170812983 364430240 -557533 38217364430240000 725 | 3762-04-22t21:05:25.95152314570z 56559762325 951523145 654626 75925951523145700 726 | 5611-07-07t10:08:38.480+23:43 114914975138 480000000 1330034 37538480000000000 727 | 6854-12-02T22:20:30-05:20 154153251630 0 1784181 13230000000000000 728 | 1323-05-15t04:38:17+03:53 -20405776483 0 -236178 2717000000000000 729 | 3790-08-03t08:11:58Z 57452227918 0 664956 29518000000000000 730 | 8639-09-17t18:26:32-10:36 210475774952 0 2436062 18152000000000000 731 | 5150-11-24T00:08:25.78280636884z 100379347705 782806368 1161798 505782806368840 732 | 6316-11-15t03:30:46+00:20 137174008246 0 1587662 11446000000000000 733 | 7292-02-03T19:36:40-05:08 167949074680 0 1943855 2680000000000000 734 | 2246-07-05T16:18:30.184+10:04 8725731270 184000000 100992 22470184000000000 735 | 3486-05-26t17:45:13.453760293Z 47852963113 453760293 553853 63913453760293000 736 | 1697-08-09T03:42:42.02Z -8595922638 20000000 -99490 13362020000000000 737 | 7520-10-05t18:58:30.682905806z 175165095510 682905806 2027373 68310682905806000 738 | 8393-10-20t01:21:35.2135+06:58 202715547815 213500000 2346244 66215213500000000 739 | 9603-08-27t15:54:44.24193-20:11 240894907544 241930000 2788135 43544241930000000 740 | 6532-06-28T00:56:51.8Z 143978259411 800000000 1666415 3411800000000000 741 | 1543-01-14t18:31:29.7-13:27 -13473619291 700000000 -155945 28709700000000000 742 | 5037-10-10t10:40:22.3003z 96809596822 300300000 1120481 38422300300000000 743 | 7056-12-04T05:49:08.1992676-07:01 160527876608 199267600 1857961 46208199267600000 744 | 3514-09-03t09:45:36.059942239+22:55 48745018236 59942239 564178 39036059942239000 745 | 9744-01-04T07:43:23.0396445134Z 245324015003 39644513 2839398 27803039644513400 746 | 2811-11-13T20:17:39.774216201-09:16 26566810419 774216201 307486 20019774216201000 747 | 7144-07-21t23:29:18.79Z 163293146958 790000000 1889966 84558790000000000 748 | 8509-10-08t05:38:14-08:07 206375175914 0 2388601 49514000000000000 749 | 9752-01-07t15:41:11.611Z 245576763671 611000000 2842323 56471611000000000 750 | 7894-09-10t23:09:39z 186965276979 0 2163949 83379000000000000 751 | 6104-11-17t01:46:31.46z 130484137591 460000000 1510233 6391460000000000 752 | 3028-09-29T19:45:58.759684Z 33410778358 759684000 386698 71158759684000000 753 | 7620-01-15T04:33:38.030-04:30 178298010218 30000000 2063634 32618030000000000 754 | 5879-01-25T19:12:55.1254Z 123358273975 125400000 1427757 69175125400000000 755 | 7641-10-29T12:05:60.756413+20:22 178985519040 756413000 2071591 56640756413000000 756 | 6932-07-09T20:05:35.1540Z 156602059535 154000000 1812523 72335154000000000 757 | 5041-12-03T18:20:08.3042z 96940520408 304200000 1121996 66008304200000000 758 | 0674-07-08T06:01:25.81464-14:55 -40881467015 814640000 -473166 75385814640000000 759 | 9136-07-15T21:24:06.0490604z 226154064246 49060400 2617523 77046049060400000 760 | 3356-02-23T15:08:58.28126318-18:28 43742626618 281263180 506280 34618281263180000 761 | 6336-01-08t14:18:42.582564295+03:21 137778231462 582564295 1594655 39462582564295000 762 | 9416-11-21t17:06:49.76796609315z 235001149609 767966093 2719920 61609767966093150 763 | 6372-02-01T01:25:57z 138916344357 0 1607828 5157000000000000 764 | 7616-10-13T23:17:19.87526558178z 178195331839 875265581 2062445 83839875265581780 765 | 3599-04-25T06:55:12Z 51416146512 0 595094 24912000000000000 766 | 5556-07-20t05:57:57.673762945Z 113180565477 673762945 1309960 21477673762945000 767 | 3693-12-04T21:21:07.21723366878-12:09 54401967007 217233668 629652 34207217233668780 768 | 1323-01-10T18:33:47.032638610+11:54 -20416555213 32638610 -236303 23987032638610000 769 | 0903-09-01T19:10:34.109630z -33650225366 109630000 -389471 69034109630000000 770 | 3189-05-14T04:28:18.080Z 38479465698 80000000 445364 16098080000000000 771 | 3411-02-18t12:24:39.940z 45477721479 940000000 526362 44679940000000000 772 | 6278-04-20t10:32:24.7-04:47 135956848764 700000000 1573574 55164700000000000 773 | 2792-10-09t22:21:34.7167216+20:45 25964156194 716721600 300511 5794716721600000 774 | 2082-11-27t10:46:31.864928z 3563001991 864928000 41238 38791864928000000 775 | 3419-09-20T23:28:39.083896z 45748711719 83896000 529498 84519083896000000 776 | 3598-08-18t19:00:48.20718936z 51394590048 207189360 594844 68448207189360000 777 | 9076-10-10t08:45:53.65216538z 224268165953 652165380 2595696 31553652165380000 778 | 6570-07-18T22:00:28.20460270z 145179208828 204602700 1680314 79228204602700000 779 | 2617-06-13T14:33:04.43227+10:42 20431453864 432270000 236475 13864432270000000 780 | 7987-09-26T22:48:06z 189901406886 0 2197932 82086000000000000 781 | 3595-09-29T04:00:03-07:33 51303497583 0 593790 41583000000000000 782 | 8870-04-06T20:41:30.71660955878z 217751316090 716609558 2520269 74490716609558780 783 | 3623-01-08t07:53:25.1313306512z 52164287605 131330651 603753 28405131330651200 784 | 7626-12-19T08:55:24.0586+12:58 178516555044 58600000 2066163 71844058600000000 785 | 8370-01-16t09:29:19.5-23:40 201965908159 500000000 2337568 32959500000000000 786 | 1297-02-09t04:18:10.9+23:06 -21234422870 900000000 -245769 18730900000000000 787 | 8891-05-23T18:34:29.1z 218418057269 100000000 2527986 66869100000000000 788 | 4687-08-15t04:56:09.86417+05:58 85759772289 864170000 992589 82689864170000000 789 | 7520-08-19t07:15:56.15+22:16 175160912396 150000000 2027325 32396150000000000 790 | 6807-12-16t14:44:16.14877180+09:09 152671152916 148771800 1767027 20116148771800000 791 | 0573-08-02T19:53:33.13+16:03 -44066578167 130000000 -510030 13833130000000000 792 | 1942-10-08T13:38:22.881225Z -859371698 881225000 -9947 49102881225000000 793 | 7429-04-28T10:54:50.24z 172279565690 240000000 1993976 39290240000000000 794 | 6591-03-27t01:14:44.04171878-17:29 145832121824 41718780 1687871 67424041718780000 795 | 1772-12-08T23:34:36.14382338Z -6218641524 143823380 -71976 84876143823380000 796 | 0451-02-12t15:21:13.2115117947z -47931295127 211511794 -554761 55273211511794700 797 | 2454-05-11T05:43:48+19:21 15284802168 0 176907 37368000000000000 798 | 5942-11-28t21:25:28.5Z 125372870728 500000000 1451074 77128500000000000 799 | 3482-05-30t04:51:17.7161Z 47727031877 716100000 552396 17477716100000000 800 | 9193-07-02T06:49:34.2964340980+16:48 227951589694 296434098 2638328 50494296434098000 801 | 3855-02-11T07:04:44z 59488412684 0 688523 25484000000000000 802 | 5351-01-23T17:29:57Z 106696027797 0 1234907 62997000000000000 803 | 6969-11-13t22:57:10.27619820+16:52 157780591510 276198200 1826164 21910276198200000 804 | 1285-10-20t22:03:01-12:15 -21591063719 0 -249897 37081000000000000 805 | 0851-09-19T18:12:33.72061083140Z -35289582447 720610831 -408445 65553720610831400 806 | 8320-06-28t22:23:47.9381097z 200402115827 938109700 2319468 80627938109700000 807 | 6320-01-12t00:36:49.166706039-18:50 137273686009 166706039 1588815 70009166706039000 808 | 0592-05-22t12:03:20.430924Z -43473153400 430924000 -503162 43400430924000000 809 | 7433-07-27T07:46:52.3698382Z 172413560812 369838200 1995527 28012369838200000 810 | 9652-09-29T10:14:50.14882+17:02 242443991570 148820000 2806064 61970148820000000 811 | 2465-08-25t23:34:59.247553118+16:28 15641190419 247553118 181032 25619247553118000 812 | 0962-10-09t04:31:02+16:44 -31785135178 0 -367884 42422000000000000 813 | 8168-10-09T01:14:03.64508571708Z 195614356443 645085717 2264055 4443645085717080 814 | 3918-08-01T02:50:02.108+06:41 61491211742 108000000 711703 72542108000000000 815 | 1365-08-11T01:06:48.3616z -19072709592 361600000 -220749 4008361600000000 816 | 2925-01-28T01:07:08.37z 30139261628 370000000 348834 4028370000000000 817 | 3525-03-23t01:34:13.94515-03:58 49078071133 945150000 568033 19933945150000000 818 | 8442-04-07t04:47:17.978Z 204244951637 978000000 2363946 17237978000000000 819 | 9688-05-29T04:37:60.87828889+14:34 243569426640 878288890 2819090 50640878288890000 820 | 9668-04-30T23:21:52.119+23:36 242935803952 119000000 2811756 85552119000000000 821 | 7533-09-11t15:06:25-22:55 175573317685 0 2032098 50485000000000000 822 | 4559-07-15t17:48:12.281592094Z 81717875292 281592094 945808 64092281592094000 823 | 1231-07-25t14:46:38z -23302804402 0 -269709 53198000000000000 824 | 4527-11-18t14:39:28-10:42 80718945688 0 934247 4888000000000000 825 | 1933-02-18t14:37:02.778733Z -1163409778 778733000 -13466 52622778733000000 826 | 8041-06-16T17:12:43.2292Z 191596727563 229200000 2217554 61963229200000000 827 | 1405-05-09T12:48:17Z -17818571503 0 -206234 46097000000000000 828 | 6443-05-20T13:42:00.929+11:52 141166288200 929000000 1633869 6600929000000000 829 | 6935-07-15t17:43:05.533554+08:43 156697146005 533554000 1813624 32405533554000000 830 | 1389-02-21T18:26:26z -18330039214 0 -212154 66386000000000000 831 | 9697-06-06T03:59:26.79387954090z 243854164766 793879540 2822386 14366793879540900 832 | 6119-04-04t09:57:48.57+23:24 130937769228 570000000 1515483 38028570000000000 833 | 3944-01-07t15:25:49.9535465-10:37 62293975369 953546500 720995 7369953546500000 834 | 2997-01-17t23:29:22-00:14 32410539802 0 375121 85402000000000000 835 | 1041-01-02t16:34:51.44Z -29316237909 440000000 -339309 59691440000000000 836 | 4306-06-05T13:52:00.3214694-20:56 73730515680 321469400 853362 38880321469400000 837 | 9648-09-02t10:07:55+08:52 242315457355 0 2804577 4555000000000000 838 | 2499-01-13t11:29:19.0937921-06:31 16694791219 93792100 193226 64819093792100000 839 | 3251-10-18T15:53:37.13581z 40449599617 135810000 468166 57217135810000000 840 | 5560-07-28t02:26:21-02:04 113307481821 0 1311429 16221000000000000 841 | 6455-03-29t23:22:38.8297547-06:50 141540588758 829754700 1638201 22358829754700000 842 | 1794-05-30T12:26:35.1640421+15:16 -5541101365 164042100 -64134 76235164042100000 843 | 1172-11-30T14:58:52.28862295034-00:06 -25153577708 288622950 -291130 54292288622950340 844 | 9460-07-07T18:47:59.576402+02:22 236377844759 576402000 2735854 59159576402000000 845 | 6054-12-15T18:38:48.18z 128908780728 180000000 1491999 67128180000000000 846 | 8277-07-30t01:11:24.2179708-05:06 199047910644 217970800 2303795 22644217970800000 847 | 4601-08-19t22:15:59.55291z 83046291359 552910000 961183 80159552910000000 848 | 1539-01-10T17:56:16.8+05:18 -13600264904 800000000 -157411 45496800000000000 849 | 5151-06-25T00:57:38z 100397753858 0 1162011 3458000000000000 850 | 4338-02-04T22:33:49.3324857948-09:27 74729894449 332485794 864929 28849332485794800 851 | 6031-07-06t20:54:17.04271663556Z 128168945657 42716635 1483436 75257042716635560 852 | 7964-03-11t00:35:27-05:55 189158394627 0 2189333 23427000000000000 853 | 4378-01-18t07:09:37z 75990640177 0 879521 25777000000000000 854 | 1799-03-24t01:40:55.20939-01:31 -5389102085 209390000 -62374 11515209390000000 855 | 3885-07-30T14:16:26.66405z 60449811386 664050000 699650 51386664050000000 856 | 6911-04-07t20:56:53z 155931253013 0 1804759 75413000000000000 857 | 7401-05-03T01:50:60.60-06:24 171396375300 600000000 1983754 29700600000000000 858 | 4229-08-09T06:31:43.3126z 71306202703 312600000 825303 23503312600000000 859 | 2457-02-10t10:25:50.058971999+11:56 15371764190 58971999 177913 80990058971999000 860 | 1973-12-17T11:29:07.0992337824+16:57 124914727 99233782 1445 66727099233782400 861 | 9692-09-19T22:38:57+17:17 243705475317 0 2820665 19317000000000000 862 | 3867-03-24t01:06:26.16797492-04:20 59870640386 167974920 692947 19586167974920000 863 | 0216-06-16t17:41:03.9131695-20:20 -55336384737 913169500 -640468 50463913169500000 864 | 5250-12-17t02:59:02.5985094825-20:27 103537178762 598509482 1198346 84362598509482500 865 | 6928-02-06t03:38:36.583781-17:30 156462527316 583781000 1810908 76116583781000000 866 | 5920-01-31T08:41:16.910-21:37 124652585896 910000000 1442738 22696910000000000 867 | 5781-03-31T23:54:58.145+09:29 120271357558 145000000 1392029 51958145000000000 868 | 3312-08-30T03:17:58z 42370312678 0 490397 11878000000000000 869 | 6821-04-07t04:47:52.174313640Z 153091140472 174313640 1771888 17272174313640000 870 | 5239-06-26t08:23:12.836391+06:13 103174913412 836391000 1194154 7812836391000000 871 | 9465-01-09T02:36:05.7z 236520095765 700000000 2737501 9365700000000000 872 | 6131-01-13T08:08:08Z 131309539688 0 1519786 29288000000000000 873 | 6839-05-03t01:14:14.3417-22:55 153661450154 341700000 1778489 554341700000000 874 | 9331-04-02T22:48:15.280015105Z 232298664495 280015105 2688641 82095280015105000 875 | 9157-08-19t21:00:53.709373+14:35 226819722353 709373000 2625228 23153709373000000 876 | 2470-12-17T11:52:38.4252-16:12 15808881878 425200000 182973 14678425200000000 877 | 7751-03-26t04:48:27.28611277345-04:16 182438039067 286112773 2111551 32667286112773450 878 | 0885-02-10T04:05:23-14:54 -34235672437 0 -396247 68363000000000000 879 | 3186-10-11T06:50:04.1064651896+03:11 38397728344 106465189 444418 13144106465189600 880 | 0584-11-10T12:19:46.898240z -43710752414 898240000 -505912 44386898240000000 881 | 5949-02-07T22:29:53.6948938531Z 125568397793 694893853 1453337 80993694893853100 882 | 6012-12-20t03:10:20.60532577+15:51 127583723960 605325770 1476663 40760605325770000 883 | 0005-04-09T21:53:19.8392Z -62000820401 839200000 -717603 78799839200000000 884 | 7152-10-07t09:07:40.3719298484z 163552295260 371929848 1892966 32860371929848400 885 | 6964-06-24T17:31:55.5Z 157610597515 500000000 1824196 63115500000000000 886 | 9190-04-12T17:47:19.2750+04:32 227849980519 275000000 2637152 47719275000000000 887 | 9620-04-15T18:41:43.21-17:31 241419874363 210000000 2794211 43963210000000000 888 | 5407-11-08T13:45:11.7+23:01 108488040251 700000000 1255648 53051700000000000 889 | 7295-08-28T04:18:59.7828596Z 168061493939 782859600 1945156 15539782859600000 890 | 3681-09-29t10:34:31.248918+09:23 54017457091 248918000 625202 4291248918000000 891 | 7590-05-11T21:05:20-00:15 177361392020 0 2052793 76820000000000000 892 | 0507-12-29T07:48:35.48147789-09:55 -46136499385 481477890 -533988 63815481477890000 893 | 5434-12-30t05:24:39z 109344662679 0 1265563 19479000000000000 894 | 2122-08-28t22:06:10.9609-14:12 4817449090 960900000 55757 44290960900000000 895 | 3888-03-16t06:24:20.9z 60532727060 900000000 700610 23060900000000000 896 | 2328-02-10T00:43:20.03608975Z 11300777000 36089750 130796 2600036089750000 897 | 8166-03-15T18:44:16.64066-02:21 195533298316 640660000 2263116 75916640660000000 898 | 2330-02-19t19:29:21.29770587z 11364780561 297705870 131536 70161297705870000 899 | 8333-04-15T02:48:43.2-03:31 200805891583 200000000 2324142 22783200000000000 900 | 9002-04-03t21:02:25.82312527-13:01 221916535405 823125270 2568478 36205823125270000 901 | 0096-09-22t02:14:18.74076+15:18 -59114840622 740760000 -684200 39378740760000000 902 | 5653-02-23T21:21:58.9172316874-16:53 116229046498 917231687 1345243 51298917231687400 903 | 4237-03-27t09:44:21.647178198+09:12 71546977941 647178198 828090 1941647178198000 904 | 3360-12-19T22:34:54.140-11:43 43894779474 140000000 508041 37074140000000000 905 | 3555-05-05t15:55:00.055754908z 50028508500 55754908 579033 57300055754908000 906 | 3009-05-15t06:54:44Z 32799279284 0 379621 24884000000000000 907 | 7593-07-19T06:28:30.78Z 177461994510 780000000 2053958 23310780000000000 908 | 5515-06-04t23:58:26.6817553994+05:17 111882710486 681755399 1294938 67286681755399400 909 | 0095-01-11T07:58:34.685-18:42 -59168265566 685000000 -684818 9634685000000000 910 | 0016-08-19t18:35:53.0581z -61642272247 58100000 -713453 66953058100000000 911 | 4130-07-13t22:57:32.7767+00:15 68179790552 776700000 789117 81752776700000000 912 | 9897-03-21T15:29:37.297Z 250158900577 297000000 2895357 55777297000000000 913 | 8290-01-09T15:38:59.7894028756z 199440718739 789402875 2308341 56339789402875600 914 | 7676-08-26T06:40:35.0088612Z 180084580835 8861200 2084312 24035008861200000 915 | 2115-02-08t23:13:18.7351551725Z 4579110798 735155172 52998 83598735155172500 916 | 7510-08-18T12:06:29-10:44 174845343029 0 2023672 82229000000000000 917 | 2553-06-28T04:07:02.9706317Z 18413150822 970631700 213115 14822970631700000 918 | 0818-01-20t12:33:13.83258639299Z -36351890807 832586392 -420740 45193832586392990 919 | 9280-09-18T16:00:37.80260870437Z 230703955237 802608704 2670184 57637802608704370 920 | 3292-08-22T14:22:48.92642573069Z 41738595768 926425730 483085 51768926425730690 921 | 6162-01-01T11:17:04.1796285Z 132286821424 179628500 1531097 40624179628500000 922 | 7710-08-18T07:46:01.4z 181156722361 400000000 2096721 27961400000000000 923 | 6584-01-11t10:00:29.80014063-06:55 145604710529 800140630 1685239 60929800140630000 924 | 2850-05-28T11:08:13.48340840-21:38 27782988373 483408400 321562 31573483408400000 925 | 3752-01-23t15:06:38.06192655150-12:54 56236478438 61926551 650885 14438061926551500 926 | 5965-12-30T02:03:48.83425-17:53 126101476608 834250000 1459507 71808834250000000 927 | 6115-12-29t15:10:60Z 130834883460 0 1514292 54660000000000000 928 | 0400-02-28T19:24:02.041403z -49539357358 41403000 -573373 69842041403000000 929 | 0252-01-10T04:57:01.270026198+12:48 -54214127459 270026198 -627479 58141270026198000 930 | 4670-07-10t15:10:10.00770+22:43 85220180830 7700000 986344 59230007700000000 931 | 4754-04-25t15:49:40.29754543Z 87864450580 297545430 1016949 56980297545430000 932 | 3221-07-11T17:15:51.95Z 39494366151 950000000 457110 62151950000000000 933 | 4059-02-03t12:00:12.106Z 65925403212 106000000 763025 43212106000000000 934 | 9506-01-08T15:38:45.28z 237813809925 280000000 2752474 56325280000000000 935 | 4517-05-18T05:25:56.4596704z 80387443556 459670400 930410 19556459670400000 936 | 8608-09-23t17:35:06.8511862914z 209498031306 851186291 2424745 63306851186291400 937 | 7094-09-24t04:20:26.60214234260z 161720857226 602142342 1871769 15626602142342600 938 | 0355-09-19T12:29:36.296250856Z -50941913424 296250856 -589606 44976296250856000 939 | 3606-07-16t07:46:51.2Z 51644159211 200000000 597733 28011200000000000 940 | 6510-12-19T09:25:35.35Z 143299013135 350000000 1658553 33935350000000000 941 | 8355-02-23t06:35:08.6z 201495710108 600000000 2332126 23708600000000000 942 | 5958-01-24t01:49:17.2971-15:18 125851165637 297100000 1456610 61637297100000000 943 | 1873-05-06t17:18:39.58373030-01:14 -3050112441 583730300 -35303 66759583730300000 944 | 6899-09-15t19:24:53.945590441Z 155566553093 945590441 1800538 69893945590441000 945 | 5169-02-01T12:02:53.92+00:52 100953429053 920000000 1168442 40253920000000000 946 | 7267-04-22T08:52:17.4280771-10:17 167166875357 428077100 1934801 68957428077100000 947 | 7200-03-07t12:01:14.4161Z 165048580874 416100000 1910284 43274416100000000 948 | 2947-12-07T06:11:01.61247+21:45 30860468761 612470000 357181 30361612470000000 949 | 7052-04-11t05:57:09.5-17:09 160381206369 500000000 1856263 83169500000000000 950 | 8063-06-28t12:27:46.872Z 192291971266 872000000 2225601 44866872000000000 951 | 4420-07-29t18:10:47.1005087175Z 77332731047 100508717 895054 65447100508717500 952 | 4816-08-18T15:29:03+01:20 89830994943 0 1039710 50943000000000000 953 | 1010-10-05T08:43:20.13-15:10 -30270672400 130000000 -350356 86000130000000000 954 | 2235-02-18t08:10:10.7+10:49 8366707270 700000000 96836 76870700000000000 955 | 0883-01-24t17:26:55.563132555z -34300305185 563132555 -396995 62815563132555000 956 | 2537-11-08T09:01:15-17:35 17919801375 0 207405 9375000000000000 957 | 5466-04-19T00:38:44+13:54 110332406684 0 1276995 38684000000000000 958 | 3473-07-23t03:43:25-13:05 47447743705 0 549163 60505000000000000 959 | 2333-09-01t13:08:07.385538Z 11476213687 385538000 132826 47287385538000000 960 | 7479-08-18t05:46:29.6+10:53 173867021609 600000000 2012349 68009600000000000 961 | 4115-12-29T15:17:33.1752534-14:14 67721031093 175253400 783808 19893175253400000 962 | 7756-11-16T07:18:55.0816685137Z 182616189535 81668513 2113613 26335081668513700 963 | 4528-01-14t09:16:54.86178-01:04 80723816454 861780000 934303 37254861780000000 964 | 1003-03-10T08:32:60.6264Z -30509710020 626400000 -353122 30780626400000000 965 | 2048-01-24t13:02:42.223388+07:38 2463456282 223388000 28512 19482223388000000 966 | 2595-09-25t21:17:14.47-07:56 19746306794 470000000 228545 18794470000000000 967 | 5880-05-10T19:25:05.0z 123398969105 0 1428228 69905000000000000 968 | 6551-09-29t19:50:22.6091168251z 144585892222 609116825 1673447 71422609116825100 969 | 9722-12-31t07:59:38.8931292334+23:31 244660897718 893129233 2831723 30518893129233400 970 | 6316-03-07T23:17:47z 137152221467 0 1587409 83867000000000000 971 | 5327-01-06T07:19:12.99927-12:08 105937183632 999270000 1226124 70032999270000000 972 | 0998-07-11t20:59:13Z -30656718047 0 -354824 75553000000000000 973 | 8223-05-16t21:44:48.94953z 197337332688 949530000 2283996 78288949530000000 974 | 3290-12-13T16:37:04.074603z 41685208624 74603000 482467 59824074603000000 975 | 1630-11-07T03:40:53.8018086187-18:13 -10702461967 801808618 -123872 78833801808618700 976 | 5071-10-05T12:13:09Z 97882085589 0 1132894 43989000000000000 977 | 9905-03-30t14:08:07.6867z 250412047687 686700000 2898287 50887686700000000 978 | 2275-06-17t13:19:27Z 9639350367 0 111566 47967000000000000 979 | 9096-12-09T13:24:49+05:34 224904498649 0 2603061 28249000000000000 980 | 5845-10-14t16:22:21.3285591035Z 122307985341 328559103 1415601 58941328559103500 981 | 2863-04-17t05:13:09.5335316z 28189573989 533531600 326268 18789533531600000 982 | 0752-09-27T00:24:26.24z -38413092934 240000000 -444596 1466240000000000 983 | 3583-01-03t18:27:33.5765-22:37 50901671073 576500000 589139 61473576500000000 984 | 1836-07-30t07:20:21.80576411Z -4210418379 805764110 -48732 26421805764110000 985 | 3298-04-04T14:53:52.23448730Z 41915804032 234487300 485136 53632234487300000 986 | 5437-06-28T06:38:33.3-07:28 109423404393 300000000 1266474 50793300000000000 987 | 7702-05-07T23:37:27+19:19 180895349907 0 2093696 15507000000000000 988 | 6954-05-09T04:15:49.2-15:58 157291013629 200000000 1820497 72829200000000000 989 | 6611-03-17t00:08:16.48+11:24 146462215456 480000000 1695164 45856480000000000 990 | 7847-12-07t05:47:29.177z 185489588849 177000000 2146870 20849177000000000 991 | 0497-07-18T07:47:57.471-15:40 -46466094723 471000000 -537803 84477471000000000 992 | 8811-09-27T21:50:52.2092+17:45 215904369952 209200000 2498893 14752209200000000 993 | 1664-03-12t22:50:20.60094Z -9650192980 600940000 -111693 82220600940000000 994 | 5011-06-14T04:45:06.7Z 95978839506 700000000 1110866 17106700000000000 995 | 4130-03-16t19:18:19.88370471844Z 68169496699 883704718 788998 69499883704718440 996 | 6576-01-20T00:53:20.73426Z 145352969600 734260000 1682326 3200734260000000 997 | 0259-01-22T03:22:17.62909Z -53992125463 629090000 -624909 12137629090000000 998 | 5719-12-17T06:36:03.06229161242+08:26 118337235003 62291612 1369643 79803062291612420 999 | 1664-03-28T11:52:45-19:48 -9648778755 0 -111676 27645000000000000 1000 | 0713-05-02T23:11:34Z -39656566106 0 -458989 83494000000000000 1001 | 0091-09-04t13:44:13.74151633+07:20 -59274178547 741516330 -686044 23053741516330000 1002 | -------------------------------------------------------------------------------- /test/gleam_time_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | 3 | pub fn main() { 4 | gleeunit.main() 5 | } 6 | -------------------------------------------------------------------------------- /test/gleam_time_test_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(gleam_time_test_ffi). 2 | -export([rfc3339_to_system_time_in_milliseconds/1]). 3 | 4 | % WARNING: This rounds, ours truncates. 5 | rfc3339_to_system_time_in_milliseconds(Timestamp) when is_binary(Timestamp) -> 6 | try 7 | Timestamp_list = binary:bin_to_list(Timestamp), 8 | Milliseconds = calendar:rfc3339_to_system_time(Timestamp_list, [{unit, millisecond}]), 9 | Adjusted_milliseconds = adjust_system_time(Milliseconds), 10 | {ok, Adjusted_milliseconds} 11 | catch 12 | error:_ -> 13 | {error, nil} 14 | end. 15 | 16 | % TODO: this time adjustment will need to be removed once the bug is fixed 17 | % upstream: https://github.com/erlang/otp/issues/9279. 18 | adjust_system_time(Milliseconds) when is_integer(Milliseconds) -> 19 | if 20 | Milliseconds >= 0 -> 21 | Milliseconds; 22 | Milliseconds < 0 -> 23 | % Fractional_seconds will be <= 0 here 24 | Fractional_seconds = Milliseconds rem 1_000, 25 | Milliseconds - 2 * Fractional_seconds 26 | end. 27 | -------------------------------------------------------------------------------- /test/gleam_time_test_ffi.mjs: -------------------------------------------------------------------------------- 1 | import { Error, Ok } from "./gleam.mjs"; 2 | 3 | export function rfc3339_to_system_time_in_milliseconds(timestamp) { 4 | try { 5 | const date = new Date(timestamp); 6 | 7 | if (isNaN(date)) { 8 | return new Error(undefined); 9 | } else { 10 | return new Ok(date.getTime()); 11 | } 12 | } catch { 13 | return new Error(undefined); 14 | } 15 | } 16 | --------------------------------------------------------------------------------