├── .gitignore ├── LICENCE ├── README.md ├── RELEASE_NOTES.md ├── ascii_calendar.md ├── build.zig ├── calendar.zig ├── docs ├── README.md └── _config.yml ├── example.zig ├── formating_time_with_zig.md ├── src ├── time.zig └── time_test.zig └── z.mod /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | timezone-data -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Geofrey Ernest 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # time 2 | 3 | `time` for ziglang -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release notes for Time (v0.6.0) 2 | 3 | This marks another notable release from `v0.3.1`. For anyone of you who isn't 4 | familiar with what this is all about, [time](https://github.com/gernest/time) is a time package for the zig 5 | programming language. It offers a clean API to manipulate and display time, 6 | this package also supports time zones. 7 | 8 | ## Notable changes 9 | 10 | 11 | ### Print durations in a human format 12 | 13 | `Duration.string` returns a human readable string for the duration. 14 | For instance if you want to print duration of one hour ,four minutes and 10 seconds. 15 | 16 | ``` 17 | const hour = Duration.Hour.value; 18 | const minute = Duration.Minute.value; 19 | const second = Duration.Second.value; 20 | var d = Duration.init(hour + minute * 4 + second * 10); 21 | warn("duration is {} \n", d.string()); 22 | ``` 23 | ``` 24 | duration is 1h4m10s 25 | ``` 26 | 27 | 28 | ## date manipulations 29 | 30 | ### Time.addDate 31 | 32 | You can add date value to an existing Time instance to get a new date. 33 | 34 | For example 35 | 36 | ``` 37 | var local = time.Location.getLocal(); 38 | var ts = time.now(&local); 39 | var buf = try std.Buffer.init(std.debug.global_allocator, ""); 40 | defer buf.deinit(); 41 | try ts.string(&buf); 42 | warn("\ncurrent time is {}\n", buf.toSlice()); 43 | 44 | // let's add 1 year 45 | ts = ts.addDate(1, 0, 0); 46 | try ts.string(&buf); 47 | warn("this time next year is {}\n", buf.toSlice()); 48 | ``` 49 | 50 | ``` 51 | current time is 2018-12-08 10:32:30.000178063 +0300 EATm=+419006.837156719 52 | this time next year is 2019-12-08 10:32:30.000178063 +0300 EAT 53 | ``` 54 | 55 | Please play with the api, you can add year,month,day. If you want to go back in 56 | time then use negative numbers. From the example above if you want to back one year then 57 | use `ts.addDate(-1, 0, 0)` 58 | 59 | 60 | This is just highlight, there are 61 | 62 | - lots of bug fixes 63 | - cleanups to more idiomatic zig 64 | - lots of utility time goodies like `Time.beginningOfMonth` 65 | - improved documentation 66 | - lots of tests added 67 | - ... and so much more 68 | 69 | I encourage you to take a look at it so you can experiment for yourself. 70 | 71 | All kind of feedback is welcome. 72 | 73 | Enjoy 74 | 75 | -------------------------------------------------------------------------------- /ascii_calendar.md: -------------------------------------------------------------------------------- 1 | This is part of [time](https://github.com/gernest/time) package. I think it is cool to showcase how far the package has matured. 2 | 3 | `calendar.zig` 4 | 5 | ``` 6 | const Time = @import("./src/time.zig").Time; 7 | 8 | pub fn main() void { 9 | Time.calendar(); 10 | } 11 | ``` 12 | 13 | ``` 14 | $ zig run calendar.zig 15 | 16 | Sun |Mon |Tue |Wed |Thu |Fri |Sat | 17 | | | | | | | 1 | 18 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 9 | 10 | 11 | 12 |*13 | 14 | 15 | 20 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 22 | 30 | 31 | | | | | | 23 | ``` 24 | 25 | Today's date is prefixed with `*` eg `*13`. 26 | 27 | Enjoy. -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const Builder = @import("std").build.Builder; 2 | 3 | pub fn build(b: *Builder) void { 4 | const mode = b.standardReleaseOptions(); 5 | var main_tests = b.addTest("src/time_test.zig"); 6 | main_tests.setBuildMode(mode); 7 | const test_step = b.step("test", "Run library tests"); 8 | test_step.dependOn(&main_tests.step); 9 | b.default_step.dependOn(test_step); 10 | } 11 | -------------------------------------------------------------------------------- /calendar.zig: -------------------------------------------------------------------------------- 1 | // Calendar is a simple zig program that prints the calendar on the stdout. This 2 | // is to showcase maturity of the time library. 3 | 4 | const Time = @import("./src/time.zig").Time; 5 | 6 | pub fn main() void { 7 | Time.calendar(); 8 | } 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # time 2 | 3 | Time library for the zig programming language (ziglang). 4 | 5 | 6 | In my opinion, the go programming language offers a very elegant api for dealing with time. 7 | This library ports the [go standard library time package](). It is not 100% `1:1` mapping 8 | with the go counterpart but offers majority of what you might need for your time needs. 9 | 10 | 11 | # Locations and timezones 12 | 13 | A time library isn't complete with timezone. This library supports timezone. I comes with 14 | a parser implementation for reading timezone info from all supported platforms. -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-modernist -------------------------------------------------------------------------------- /example.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const warn = std.debug.warn; 3 | const time = @import("./src/time.zig"); 4 | const Duration = time.Duration; 5 | 6 | test "now" { 7 | var local = time.Location.getLocal(); 8 | var now = time.now(&local); 9 | 10 | warn("\n today's date {}", .{now.date()}); 11 | warn("\n today's time {}", .{now.clock()}); 12 | warn("\n local timezone detail {}\n", .{now.zone()}); 13 | 14 | // $ zig test example.zig 15 | // Test 1/1 now... 16 | // today's date DateDetail{ .year = 2018, .month = Month.November, .day = 25, .yday = 328 } 17 | // today's time Clock{ .hour = 11, .min = 17, .sec = 16 } 18 | // local timezone detail ZoneDetail{ .name = EAT, .offset = 10800 } 19 | // OK 20 | // All tests passed. 21 | } 22 | 23 | const formatTest = struct { 24 | name: []const u8, 25 | format: []const u8, 26 | fn init(name: []const u8, format: []const u8) formatTest { 27 | return formatTest{ .name = name, .format = format }; 28 | } 29 | }; 30 | 31 | const format_tests = [_]formatTest{ 32 | formatTest.init("ANSIC", time.ANSIC), 33 | formatTest.init("UnixDate", time.UnixDate), 34 | formatTest.init("RubyDate", time.RubyDate), 35 | formatTest.init("RFC822", time.RFC822), 36 | formatTest.init("RFC850", time.RFC850), 37 | formatTest.init("RFC1123", time.RFC1123), 38 | formatTest.init("RFC1123Z", time.RFC1123Z), 39 | formatTest.init("RFC3339", time.RFC3339), 40 | formatTest.init("RFC3339Nano", time.RFC3339Nano), 41 | formatTest.init("Kitchen", time.Kitchen), 42 | formatTest.init("am/pm", "3pm"), 43 | formatTest.init("AM/PM", "3PM"), 44 | formatTest.init("two-digit year", "06 01 02"), 45 | // Three-letter months and days must not be followed by lower-case letter. 46 | formatTest.init("Janet", "Hi Janet, the Month is January"), 47 | // Time stamps, Fractional seconds. 48 | formatTest.init("Stamp", time.Stamp), 49 | formatTest.init("StampMilli", time.StampMilli), 50 | formatTest.init("StampMicro", time.StampMicro), 51 | formatTest.init("StampNano", time.StampNano), 52 | }; 53 | 54 | test "time.format" { 55 | var local = time.Location.getLocal(); 56 | var ts = time.now(&local); 57 | 58 | var buf = std.ArrayList(u8).init(std.testing.allocator); 59 | defer buf.deinit(); 60 | warn("\n", .{}); 61 | for (format_tests) |value| { 62 | try buf.resize(0); 63 | try ts.formatBuffer(&buf, value.format); 64 | warn("{}: {}\n", .{ value.name, buf.items }); 65 | } 66 | 67 | // Test 2/2 time.format... 68 | // ANSIC: Thu Nov 29 05:46:03 2018 69 | // UnixDate: Thu Nov 29 05:46:03 EAT 2018 70 | // RubyDate: Thu Nov 29 05:46:03 +0300 2018 71 | // RFC822: 29 Nov 18 05:46 EAT 72 | // RFC850: Thursday, 29-Nov-18 05:46:03 EAT 73 | // RFC1123: Thu, 29 Nov 2018 05:46:03 EAT 74 | // RFC1123Z: Thu, 29 Nov 2018 05:46:03 +0300 75 | // RFC3339: 2018-11-29T05:46:03+03:00 76 | // RFC3339Nano: 2018-11-29T05:46:03.000024416+03:00 77 | // Kitchen: 5:46AM 78 | // am/pm: 5am 79 | // AM/PM: 5AM 80 | // two-digit year: 18 11 29 81 | // Janet: Hi Janet, the Month is November 82 | // Stamp: Nov 29 05:46:03 83 | // StampMilli: Nov 29 05:46:03.000 84 | // StampMicro: Nov 29 05:46:03.000024 85 | // StampNano: Nov 29 05:46:03.000024416 86 | // OK 87 | // All tests passed. 88 | } 89 | 90 | test "durations" { 91 | // print w0ne hour and 10 secods 92 | const hour = Duration.Hour.value; 93 | const minute = Duration.Minute.value; 94 | const second = Duration.Second.value; 95 | var d = Duration.init(hour + minute * 4 + second * 10); 96 | warn("duration is {} \n", .{d.string()}); 97 | } 98 | 99 | test "addDate" { 100 | var local = time.Location.getLocal(); 101 | var ts = time.now(&local); 102 | var buf = std.ArrayList(u8).init(std.testing.allocator); 103 | defer buf.deinit(); 104 | try ts.string(&buf); 105 | warn("\ncurrent time is {}\n", .{buf.items}); 106 | 107 | // let's add 1 year 108 | ts = ts.addDate(1, 0, 0); 109 | try ts.string(&buf); 110 | warn("this time next year is {}\n", .{buf.items}); 111 | } 112 | -------------------------------------------------------------------------------- /formating_time_with_zig.md: -------------------------------------------------------------------------------- 1 | ## formating timestamps with zig 2 | 3 | Most of you are not aware of the [time package](https://github.com/gernest/time). 4 | A new release has just landed with support for formating time into various timestamps. 5 | 6 | ### Layouts 7 | Time is formatted into different layouts. A layout is a string which defines 8 | how time is formatted, for example `Mon Jan 2 15:04:05 MST 2006`. 9 | 10 | You can read more about layouts 11 | here https://github.com/gernest/time/blob/a3d45b5f5b607b7bedd4d0d4ca12307f0d6ff52b/src/time.zig#L791-L850 12 | 13 | I will focus on showcasing using the standard timestamp layouts that are provided by the library. 14 | 15 | These are standard layouts provided by the library 16 | 17 | ``` 18 | pub const ANSIC = "Mon Jan _2 15:04:05 2006"; 19 | pub const UnixDate = "Mon Jan _2 15:04:05 MST 2006"; 20 | pub const RubyDate = "Mon Jan 02 15:04:05 -0700 2006"; 21 | pub const RFC822 = "02 Jan 06 15:04 MST"; 22 | pub const RFC822Z = "02 Jan 06 15:04 -0700"; // RFC822 with numeric zone 23 | pub const RFC850 = "Monday, 02-Jan-06 15:04:05 MST"; 24 | pub const RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"; 25 | pub const RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"; // RFC1123 with numeric zone 26 | pub const RFC3339 = "2006-01-02T15:04:05Z07:00"; 27 | pub const RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"; 28 | pub const Kitchen = "3:04PM"; 29 | // Handy time stamps. 30 | pub const Stamp = "Jan _2 15:04:05"; 31 | pub const StampMilli = "Jan _2 15:04:05.000"; 32 | pub const StampMicro = "Jan _2 15:04:05.000000"; 33 | pub const StampNano = "Jan _2 15:04:05.000000000"; 34 | ``` 35 | 36 | ### show me some code 37 | 38 | ``` 39 | const std = @import("std"); 40 | const warn = std.debug.warn; 41 | const time = @import("./src/time.zig"); 42 | 43 | 44 | const formatTest = struct { 45 | name: []const u8, 46 | format: []const u8, 47 | fn init(name: []const u8, format: []const u8) formatTest { 48 | return formatTest{ .name = name, .format = format }; 49 | } 50 | }; 51 | 52 | const format_tests = []formatTest{ 53 | formatTest.init("ANSIC", time.ANSIC), 54 | formatTest.init("UnixDate", time.UnixDate), 55 | formatTest.init("RubyDate", time.RubyDate), 56 | formatTest.init("RFC822", time.RFC822), 57 | formatTest.init("RFC850", time.RFC850), 58 | formatTest.init("RFC1123", time.RFC1123), 59 | formatTest.init("RFC1123Z", time.RFC1123Z), 60 | formatTest.init("RFC3339", time.RFC3339), 61 | formatTest.init("RFC3339Nano", time.RFC3339Nano), 62 | formatTest.init("Kitchen", time.Kitchen), 63 | formatTest.init("am/pm", "3pm"), 64 | formatTest.init("AM/PM", "3PM"), 65 | formatTest.init("two-digit year", "06 01 02"), 66 | // Three-letter months and days must not be followed by lower-case letter. 67 | formatTest.init("Janet", "Hi Janet, the Month is January"), 68 | // Time stamps, Fractional seconds. 69 | formatTest.init("Stamp", time.Stamp), 70 | formatTest.init("StampMilli", time.StampMilli), 71 | formatTest.init("StampMicro", time.StampMicro), 72 | formatTest.init("StampNano", time.StampNano), 73 | }; 74 | 75 | test "time.format" { 76 | var ts = time.now(); 77 | var buf = try std.Buffer.init(std.debug.global_allocator, ""); 78 | defer buf.deinit(); 79 | warn("\n"); 80 | for (format_tests) |value| { 81 | try ts.format(&buf, value.format); 82 | const got = buf.toSlice(); 83 | warn("{}: {}\n", value.name, got); 84 | } 85 | 86 | // Test 2/2 time.format... 87 | // ANSIC: Thu Nov 29 05:46:03 2018 88 | // UnixDate: Thu Nov 29 05:46:03 EAT 2018 89 | // RubyDate: Thu Nov 29 05:46:03 +0300 2018 90 | // RFC822: 29 Nov 18 05:46 EAT 91 | // RFC850: Thursday, 29-Nov-18 05:46:03 EAT 92 | // RFC1123: Thu, 29 Nov 2018 05:46:03 EAT 93 | // RFC1123Z: Thu, 29 Nov 2018 05:46:03 +0300 94 | // RFC3339: 2018-11-29T05:46:03+03:00 95 | // RFC3339Nano: 2018-11-29T05:46:03.000024416+03:00 96 | // Kitchen: 5:46AM 97 | // am/pm: 5am 98 | // AM/PM: 5AM 99 | // two-digit year: 18 11 29 100 | // Janet: Hi Janet, the Month is November 101 | // Stamp: Nov 29 05:46:03 102 | // StampMilli: Nov 29 05:46:03.000 103 | // StampMicro: Nov 29 05:46:03.000024 104 | // StampNano: Nov 29 05:46:03.000024416 105 | // OK 106 | // All tests passed. 107 | } 108 | ``` 109 | 110 | All kind of feedback is welcome. 111 | 112 | Enjoy. -------------------------------------------------------------------------------- /src/time.zig: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // Copyright 2018 Geofrey Ernest MIT LICENSE 6 | 7 | const builtin = @import("builtin"); 8 | const std = @import("std"); 9 | 10 | const Os = builtin.Os; 11 | const darwin = std.os.darwin; 12 | const linux = std.os.linux; 13 | const mem = std.mem; 14 | const warn = std.debug.warn; 15 | const assert = std.debug.assert; 16 | 17 | const windows = std.os.windows; 18 | 19 | pub const Location = struct { 20 | name: []const u8, 21 | zone: ?[]zone, 22 | tx: ?[]ZoneTrans, 23 | // Most lookups will be for the current time. 24 | // To avoid the binary search through tx, keep a 25 | // static one-element cache that gives the correct 26 | // zone for the time when the Location was created. 27 | // if cacheStart <= t < cacheEnd, 28 | // lookup can return cacheZone. 29 | // The units for cacheStart and cacheEnd are seconds 30 | // since January 1, 1970 UTC, to match the argument 31 | // to lookup. 32 | cache_start: ?i64, 33 | cache_end: ?i64, 34 | cached_zone: ?*zone, 35 | 36 | arena: std.heap.ArenaAllocator, 37 | const alpha: i64 = -1 << 63; 38 | const omega: i64 = 1 << 63 - 1; 39 | const max_file_size: usize = 10 << 20; 40 | const initLocation = switch (builtin.os.tag) { 41 | .linux => initLinux, 42 | .macos, .ios => initDarwin, 43 | else => @compileError("Unsupported OS"), 44 | }; 45 | pub var utc_local = Location.init(std.heap.page_allocator, "UTC"); 46 | var unix_sources = [_][]const u8{ 47 | "/usr/share/zoneinfo/", 48 | "/usr/share/lib/zoneinfo/", 49 | "/usr/lib/locale/TZ/", 50 | }; 51 | 52 | // readFile reads contents of a file with path and writes the read bytes to buf. 53 | 54 | const zone = struct { 55 | name: []const u8, 56 | offset: isize, 57 | is_dst: bool, 58 | }; 59 | 60 | const ZoneTrans = struct { 61 | when: i64, 62 | index: usize, 63 | is_std: bool, 64 | is_utc: bool, 65 | }; 66 | 67 | pub const zoneDetails = struct { 68 | name: []const u8, 69 | offset: isize, 70 | start: i64, 71 | end: i64, 72 | }; 73 | 74 | // alpha and omega are the beginning and end of time for zone 75 | // transitions. 76 | 77 | const dataIO = struct { 78 | p: []u8, 79 | n: usize, 80 | 81 | fn init(p: []u8) dataIO { 82 | return dataIO{ 83 | .p = p, 84 | .n = 0, 85 | }; 86 | } 87 | 88 | fn read(d: *dataIO, p: []u8) usize { 89 | if (d.n >= d.p.len) { 90 | // end of stream 91 | return 0; 92 | } 93 | const pos = d.n; 94 | const offset = pos + p.len; 95 | while ((d.n < offset) and (d.n < d.p.len)) : (d.n += 1) { 96 | p[d.n - pos] = d.p[d.n]; 97 | } 98 | return d.n - pos; 99 | } 100 | 101 | fn big4(d: *dataIO) !i32 { 102 | var p: [4]u8 = undefined; 103 | const size = d.read(p[0..]); 104 | if (size < 4) { 105 | return error.BadData; 106 | } 107 | const o = @intCast(i32, p[3]) | (@intCast(i32, p[2]) << 8) | (@intCast(i32, p[1]) << 16) | (@intCast(i32, p[0]) << 24); 108 | return o; 109 | } 110 | 111 | // advances the cursor by n. next read will start after skipping the n bytes. 112 | fn skip(d: *dataIO, n: usize) void { 113 | d.n += n; 114 | } 115 | 116 | fn byte(d: *dataIO) !u8 { 117 | if (d.n < d.p.len) { 118 | const u = d.p[d.n]; 119 | d.n += 1; 120 | return u; 121 | } 122 | return error.EOF; 123 | } 124 | }; 125 | 126 | fn init(a: *mem.Allocator, name: []const u8) Location { 127 | var arena = std.heap.ArenaAllocator.init(a); 128 | return Location{ 129 | .name = name, 130 | .zone = null, 131 | .tx = null, 132 | .arena = arena, 133 | .cache_start = null, 134 | .cache_end = null, 135 | .cached_zone = null, 136 | }; 137 | } 138 | 139 | pub fn deinit(self: *Location) void { 140 | self.arena.deinit(); 141 | } 142 | 143 | /// getLocal returns local timezone. 144 | pub fn getLocal() Location { 145 | return initLocation(); 146 | } 147 | 148 | /// firstZoneUsed returns whether the first zone is used by some 149 | /// transition. 150 | pub fn firstZoneUsed(self: *const Location) bool { 151 | if (self.tx) |tx| { 152 | for (tx) |value| { 153 | if (value.index == 0) { 154 | return true; 155 | } 156 | } 157 | } 158 | return false; 159 | } 160 | 161 | // lookupFirstZone returns the index of the time zone to use for times 162 | // before the first transition time, or when there are no transition 163 | // times. 164 | // 165 | // The reference implementation in localtime.c from 166 | // https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz 167 | // implements the following algorithm for these cases: 168 | // 1) If the first zone is unused by the transitions, use it. 169 | // 2) Otherwise, if there are transition times, and the first 170 | // transition is to a zone in daylight time, find the first 171 | // non-daylight-time zone before and closest to the first transition 172 | // zone. 173 | // 3) Otherwise, use the first zone that is not daylight time, if 174 | // there is one. 175 | // 4) Otherwise, use the first zone. 176 | pub fn lookupFirstZone(self: *const Location) usize { 177 | // Case 1. 178 | if (!self.firstZoneUsed()) { 179 | return 0; 180 | } 181 | 182 | // Case 2. 183 | if (self.tx) |tx| { 184 | if (tx.len > 0 and self.zone.?[tx[0].index].is_dst) { 185 | var zi = @intCast(isize, tx[0].index); 186 | while (zi >= 0) : (zi -= 1) { 187 | if (!self.zone.?[@intCast(usize, zi)].is_dst) { 188 | return @intCast(usize, zi); 189 | } 190 | } 191 | } 192 | } 193 | // Case 3. 194 | if (self.zone) |tzone| { 195 | for (tzone) |z, idx| { 196 | if (!z.is_dst) { 197 | return idx; 198 | } 199 | } 200 | } 201 | // Case 4. 202 | return 0; 203 | } 204 | 205 | /// lookup returns information about the time zone in use at an 206 | /// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. 207 | /// 208 | /// The returned information gives the name of the zone (such as "CET"), 209 | /// the start and end times bracketing sec when that zone is in effect, 210 | /// the offset in seconds east of UTC (such as -5*60*60), and whether 211 | /// the daylight savings is being observed at that time. 212 | pub fn lookup(self: *const Location, sec: i64) zoneDetails { 213 | if (self.zone == null) { 214 | return zoneDetails{ 215 | .name = "UTC", 216 | .offset = 0, 217 | .start = alpha, 218 | .end = omega, 219 | }; 220 | } 221 | if (self.tx) |tx| { 222 | if (tx.len == 0 or sec < tx[0].when) { 223 | const tzone = &self.zone.?[self.lookupFirstZone()]; 224 | var end: i64 = undefined; 225 | if (tx.len > 0) { 226 | end = tx[0].when; 227 | } else { 228 | end = omega; 229 | } 230 | return zoneDetails{ 231 | .name = tzone.name, 232 | .offset = tzone.offset, 233 | .start = alpha, 234 | .end = end, 235 | }; 236 | } 237 | } 238 | 239 | // Binary search for entry with largest time <= sec. 240 | // Not using sort.Search to avoid dependencies. 241 | var lo: usize = 0; 242 | var hi = self.tx.?.len; 243 | var end = omega; 244 | while ((hi - lo) > 1) { 245 | const m = lo + ((hi - lo) / 2); 246 | const lim = self.tx.?[m].when; 247 | if (sec < lim) { 248 | end = lim; 249 | hi = m; 250 | } else { 251 | lo = m; 252 | } 253 | } 254 | const tzone = &self.zone.?[self.tx.?[lo].index]; 255 | return zoneDetails{ 256 | .name = tzone.name, 257 | .offset = tzone.offset, 258 | .start = self.tx.?[lo].when, 259 | .end = end, 260 | }; 261 | } 262 | 263 | /// lookupName returns information about the time zone with 264 | /// the given name (such as "EST") at the given pseudo-Unix time 265 | /// (what the given time of day would be in UTC). 266 | pub fn lookupName(self: *Location, name: []const u8, unix: i64) !isize { 267 | // First try for a zone with the right name that was actually 268 | // in effect at the given time. (In Sydney, Australia, both standard 269 | // and daylight-savings time are abbreviated "EST". Using the 270 | // offset helps us pick the right one for the given time. 271 | // It's not perfect: during the backward transition we might pick 272 | // either one.) 273 | if (self.zone) |zone| { 274 | for (zone) |*z| { 275 | if (mem.eql(u8, z.name, name)) { 276 | const d = self.lookup(unix - @intCast(i64, z.offset)); 277 | if (mem.eql(d.name, z.name)) { 278 | return d.offset; 279 | } 280 | } 281 | } 282 | } 283 | 284 | // Otherwise fall back to an ordinary name match. 285 | if (self.zone) |zone| { 286 | for (zone) |*z| { 287 | if (mem.eql(u8, z.name, name)) { 288 | return z.offset; 289 | } 290 | } 291 | } 292 | return error.ZoneNotFound; 293 | } 294 | 295 | pub fn loadLocationFromTZData(a: *mem.Allocator, name: []const u8, data: []u8) !Location { 296 | var arena = std.heap.ArenaAllocator.init(a); 297 | var arena_allocator = &arena.allocator; 298 | defer arena.deinit(); 299 | errdefer arena.deinit(); 300 | var d = &dataIO.init(data); 301 | var magic: [4]u8 = undefined; 302 | var size = d.read(magic[0..]); 303 | if (size != 4) { 304 | return error.BadData; 305 | } 306 | if (!mem.eql(u8, &magic, "TZif")) { 307 | return error.BadData; 308 | } 309 | // 1-byte version, then 15 bytes of padding 310 | var p: [16]u8 = undefined; 311 | size = d.read(p[0..]); 312 | if (size != 16 or p[0] != 0 and p[0] != '2' and p[0] != '3') { 313 | return error.BadData; 314 | } 315 | // six big-endian 32-bit integers: 316 | // number of UTC/local indicators 317 | // number of standard/wall indicators 318 | // number of leap seconds 319 | // number of transition times 320 | // number of local time zones 321 | // number of characters of time zone abbrev strings 322 | const n_value = enum(usize) { 323 | UTCLocal, 324 | STDWall, 325 | Leap, 326 | Time, 327 | Zone, 328 | Char, 329 | }; 330 | 331 | var n: [6]usize = undefined; 332 | var i: usize = 0; 333 | while (i < 6) : (i += 1) { 334 | const nn = try d.big4(); 335 | n[i] = @intCast(usize, nn); 336 | } 337 | // Transition times. 338 | var tx_times = try arena_allocator.alloc(u8, n[@enumToInt(n_value.Time)] * 4); 339 | _ = d.read(tx_times); 340 | var tx_times_data = dataIO.init(tx_times); 341 | 342 | // Time zone indices for transition times. 343 | var tx_zone = try arena_allocator.alloc(u8, n[@enumToInt(n_value.Time)]); 344 | _ = d.read(tx_zone); 345 | var tx_zone_data = dataIO.init(tx_zone); 346 | 347 | // Zone info structures 348 | var zone_data_value = try arena_allocator.alloc(u8, n[@enumToInt(n_value.Zone)] * 6); 349 | _ = d.read(zone_data_value); 350 | var zone_data = dataIO.init(zone_data_value); 351 | 352 | // Time zone abbreviations. 353 | var abbrev = try arena_allocator.alloc(u8, n[@enumToInt(n_value.Char)]); 354 | _ = d.read(abbrev); 355 | 356 | // Leap-second time pairs 357 | d.skip(n[@enumToInt(n_value.Leap)] * 8); 358 | 359 | // Whether tx times associated with local time types 360 | // are specified as standard time or wall time. 361 | var isstd = try arena_allocator.alloc(u8, n[@enumToInt(n_value.STDWall)]); 362 | _ = d.read(isstd); 363 | 364 | var isutc = try arena_allocator.alloc(u8, n[@enumToInt(n_value.UTCLocal)]); 365 | size = d.read(isutc); 366 | if (size == 0) { 367 | return error.BadData; 368 | } 369 | 370 | // If version == 2 or 3, the entire file repeats, this time using 371 | // 8-byte ints for txtimes and leap seconds. 372 | // We won't need those until 2106. 373 | 374 | var loc = Location.init(a, name); 375 | errdefer loc.deinit(); 376 | var zalloc = &loc.arena.allocator; 377 | 378 | // Now we can build up a useful data structure. 379 | // First the zone information. 380 | //utcoff[4] isdst[1] nameindex[1] 381 | i = 0; 382 | var zones = try zalloc.alloc(zone, n[@enumToInt(n_value.Zone)]); 383 | while (i < n[@enumToInt(n_value.Zone)]) : (i += 1) { 384 | const zn = try zone_data.big4(); 385 | const b = try zone_data.byte(); 386 | var z: zone = undefined; 387 | z.offset = @intCast(isize, zn); 388 | z.is_dst = b != 0; 389 | const b2 = try zone_data.byte(); 390 | if (@intCast(usize, b2) >= abbrev.len) { 391 | return error.BadData; 392 | } 393 | const cn = byteString(abbrev[b2..]); 394 | // we copy the name and ensure it stay valid throughout location 395 | // lifetime. 396 | var znb = try zalloc.alloc(u8, cn.len); 397 | mem.copy(u8, znb, cn); 398 | z.name = znb; 399 | zones[i] = z; 400 | } 401 | loc.zone = zones; 402 | 403 | // Now the transition time info. 404 | i = 0; 405 | const tx_n = n[@enumToInt(n_value.Time)]; 406 | var tx_list = try zalloc.alloc(ZoneTrans, tx_n); 407 | if (tx_n != 0) { 408 | while (i < n[@enumToInt(n_value.Time)]) : (i += 1) { 409 | var tx: ZoneTrans = undefined; 410 | const w = try tx_times_data.big4(); 411 | tx.when = @intCast(i64, w); 412 | if (@intCast(usize, tx_zone[i]) >= zones.len) { 413 | return error.BadData; 414 | } 415 | tx.index = @intCast(usize, tx_zone[i]); 416 | if (i < isstd.len) { 417 | tx.is_std = isstd[i] != 0; 418 | } 419 | if (i < isutc.len) { 420 | tx.is_utc = isutc[i] != 0; 421 | } 422 | tx_list[i] = tx; 423 | } 424 | loc.tx = tx_list; 425 | } else { 426 | var ls = [_]ZoneTrans{ZoneTrans{ 427 | .when = alpha, 428 | .index = 0, 429 | .is_std = false, 430 | .is_utc = false, 431 | }}; 432 | loc.tx = ls[0..]; 433 | } 434 | return loc; 435 | } 436 | 437 | // darwin_sources directory to search for timezone files. 438 | fn readFile(path: []const u8) !void { 439 | var file = try std.fs.File.openRead(path); 440 | defer file.close(); 441 | var stream = &file.inStream().stream; 442 | try stream.readAllBuffer(buf, max_file_size); 443 | } 444 | 445 | fn loadLocationFile(a: *mem.Allocator, fileName: []const u8, sources: [][]const u8) ![]u8 { 446 | var buf = std.ArrayList(u8).init(a); 447 | defer buf.deinit(); 448 | for (sources) |source| { 449 | try buf.resize(0); 450 | try buf.appendSlice(source); 451 | if (buf.items[buf.items.len - 1] != '/') { 452 | try buf.append('/'); 453 | } 454 | try buf.appendSlice(fileName); 455 | if (std.fs.cwd().readFileAlloc(a, buf.items, 20 * 1024 * 1024)) |contents| { 456 | return contents; 457 | } else |err| { 458 | continue; 459 | } 460 | } 461 | return error.MissingZoneFile; 462 | } 463 | 464 | fn loadLocationFromTZFile(a: *mem.Allocator, name: []const u8, sources: [][]const u8) !Location { 465 | var buf: []u8 = undefined; 466 | var t = try loadLocationFile(a, name, sources); 467 | return loadLocationFromTZData(a, name, t); 468 | } 469 | 470 | pub fn load(name: []const u8) !Location { 471 | return loadLocationFromTZFile(std.heap.page_allocator, name, unix_sources[0..]); 472 | } 473 | 474 | fn initDarwin() Location { 475 | return initLinux(); 476 | } 477 | 478 | fn initLinux() Location { 479 | var tz: ?[]const u8 = null; 480 | if (std.process.getEnvMap(std.heap.page_allocator)) |value| { 481 | var env = value; 482 | defer env.deinit(); 483 | tz = env.get("TZ"); 484 | } else |err| {} 485 | if (tz) |name| { 486 | if (name.len != 0 and !mem.eql(u8, name, "UTC")) { 487 | if (loadLocationFromTZFile(std.heap.page_allocator, name, unix_sources[0..])) |tzone| { 488 | return tzone; 489 | } else |err| {} 490 | } 491 | } else { 492 | var etc = [_][]const u8{"/etc/"}; 493 | if (loadLocationFromTZFile(std.heap.page_allocator, "localtime", etc[0..])) |tzone| { 494 | var zz = tzone; 495 | zz.name = "local"; 496 | return zz; 497 | } else |err| {} 498 | } 499 | return utc_local; 500 | } 501 | 502 | fn byteString(x: []u8) []u8 { 503 | for (x) |v, idx| { 504 | if (v == 0) { 505 | return x[0..idx]; 506 | } 507 | } 508 | return x; 509 | } 510 | }; 511 | 512 | const seconds_per_minute = 60; 513 | const seconds_per_hour = 60 * seconds_per_minute; 514 | const seconds_per_day = 24 * seconds_per_hour; 515 | const seconds_per_week = 7 * seconds_per_day; 516 | const days_per_400_years = 365 * 400 + 97; 517 | const days_per_100_years = 365 * 100 + 24; 518 | const days_per_4_years = 365 * 4 + 1; 519 | // The unsigned zero year for internal calculations. 520 | // Must be 1 mod 400, and times before it will not compute correctly, 521 | // but otherwise can be changed at will. 522 | const absolute_zero_year: i64 = -292277022399; 523 | 524 | // The year of the zero Time. 525 | // Assumed by the unix_to_internal computation below. 526 | const internal_year: i64 = 1; 527 | 528 | // Offsets to convert between internal and absolute or Unix times. 529 | const absolute_to_internal: i64 = (absolute_zero_year - internal_year) * @floatToInt(i64, 365.2425 * @intToFloat(f64, seconds_per_day)); 530 | const internal_to_absolute = -absolute_to_internal; 531 | 532 | const unix_to_internal: i64 = (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400) * seconds_per_day; 533 | const internal_to_unix: i64 = -unix_to_internal; 534 | 535 | const wall_to_internal: i64 = (1884 * 365 + 1884 / 4 - 1884 / 100 + 1884 / 400) * seconds_per_day; 536 | const internal_to_wall: i64 = -wall_to_internal; 537 | 538 | const has_monotonic = 1 << 63; 539 | const max_wall = wall_to_internal + ((1 << 33) - 1); // year 2157 540 | const min_wall = wall_to_internal; // year 1885 541 | 542 | const nsec_mask: u64 = (1 << 30) - 1; 543 | const nsec_shift = 30; 544 | 545 | const context = @This(); 546 | 547 | pub const Time = struct { 548 | const Self = @This(); 549 | 550 | wall: u64, 551 | ext: i64, 552 | loc: *Location, 553 | 554 | const short_days = [_][]const u8{ 555 | "Sun", 556 | "Mon", 557 | "Tue", 558 | "Wed", 559 | "Thu", 560 | "Fri", 561 | "Sat", 562 | }; 563 | 564 | const divResult = struct { 565 | qmod: isize, 566 | r: Duration, 567 | }; 568 | 569 | // div divides self by d and returns the quotient parity and remainder. 570 | // We don't use the quotient parity anymore (round half up instead of round to even) 571 | // but it's still here in case we change our minds. 572 | 573 | fn nsec(self: Self) i32 { 574 | if (self.wall == 0) { 575 | return 0; 576 | } 577 | return @intCast(i32, self.wall & nsec_mask); 578 | } 579 | 580 | fn sec(self: Self) i64 { 581 | if ((self.wall & has_monotonic) != 0) { 582 | return wall_to_internal + @intCast(i64, self.wall << 1 >> (nsec_shift + 1)); 583 | } 584 | return self.ext; 585 | } 586 | 587 | // unixSec returns the time's seconds since Jan 1 1970 (Unix time). 588 | fn unixSec(self: Self) i64 { 589 | return self.sec() + internal_to_unix; 590 | } 591 | 592 | pub fn unix(self: Self) i64 { 593 | return self.unixSec(); 594 | } 595 | 596 | fn addSec(self: *Self, d: i64) void { 597 | if ((self.wall & has_monotonic) != 0) { 598 | const s = @intCast(i64, self.wall << 1 >> (nsec_shift + 1)); 599 | const dsec = s + d; 600 | if (0 <= dsec and dsec <= (1 << 33) - 1) { 601 | self.wall = self.wall & nsec_mask | @intCast(u64, dsec) << nsec_shift | has_monotonic; 602 | return; 603 | } 604 | // Wall second now out of range for packed field. 605 | // Move to ext 606 | self.stripMono(); 607 | } 608 | self.ext += d; 609 | } 610 | 611 | /// returns the time corresponding to adding the 612 | /// given number of years, months, and days to self. 613 | /// For example, addDate(-1, 2, 3) applied to January 1, 2011 614 | /// returns March 4, 2010. 615 | /// 616 | /// addDate normalizes its result in the same way that Date does, 617 | /// so, for example, adding one month to October 31 yields 618 | /// December 1, the normalized form for November 31. 619 | pub fn addDate(self: Self, years: isize, number_of_months: isize, number_of_days: isize) Self { 620 | const d = self.date(); 621 | const c = self.clock(); 622 | const m = @intCast(isize, @enumToInt(d.month)) + number_of_months; 623 | return context.date( 624 | d.year + years, 625 | m, 626 | d.day + number_of_days, 627 | c.hour, 628 | c.min, 629 | c.sec, 630 | @intCast(isize, self.nsec()), 631 | self.loc, 632 | ); 633 | } 634 | 635 | fn stripMono(self: *Self) void { 636 | if ((self.wall & has_monotonic) != 0) { 637 | self.ext = self.sec(); 638 | self.wall &= nsec_mask; 639 | } 640 | } 641 | 642 | pub fn setLoc(self: Self, l: *Location) void { 643 | self.stripMono(); 644 | self.loc = l; 645 | } 646 | 647 | fn setMono(self: Self, m: i64) void { 648 | if ((self.wall & has_monotonic) == 0) { 649 | const s = self.ext; 650 | if (s < min_wall or max_wall < s) { 651 | return; 652 | } 653 | self.wall |= has_monotonic | @intCast(u64, s - min_wall) << nsec_shift; 654 | } 655 | self.ext = m; 656 | } 657 | // mono returns t's monotonic clock reading. 658 | // It returns 0 for a missing reading. 659 | // This function is used only for testing, 660 | // so it's OK that technically 0 is a valid 661 | // monotonic clock reading as well. 662 | fn mono(self: Self) i64 { 663 | if ((self.wall & has_monotonic) == 0) { 664 | return 0; 665 | } 666 | return self.ext; 667 | } 668 | 669 | /// reports whether self represents the zero time instant, 670 | /// January 1, year 1, 00:00:00 UTC. 671 | pub fn isZero(self: Self) bool { 672 | return self.sec() == 0 and self.nsec() == 0; 673 | } 674 | 675 | /// returns true if time self is after time u. 676 | pub fn after(self: Self, u: Self) bool { 677 | const ts = self.sec(); 678 | const us = u.sec(); 679 | return ts > us or (ts == us and self.nsec() > u.nsec()); 680 | } 681 | 682 | /// returns true if time self is before u. 683 | pub fn before(self: Self, u: Self) bool { 684 | return (self.sec() < u.sec()) or (self.sec() == u.sec() and self.nsec() < u.nsec()); 685 | } 686 | 687 | /// reports whether self and u represent the same time instant. 688 | /// Two times can be equal even if they are in different locations. 689 | /// For example, 6:00 +0200 CEST and 4:00 UTC are Equal. 690 | /// See the documentation on the Time type for the pitfalls of using == with 691 | /// Time values; most code should use Equal instead. 692 | pub fn equal(self: Self, u: Self) bool { 693 | return self.sec() == u.sec() and self.nsec() == u.nsec(); 694 | } 695 | 696 | /// abs returns the time self as an absolute time, adjusted by the zone offset. 697 | /// It is called when computing a presentation property like Month or Hour. 698 | fn abs(self: Self) u64 { 699 | var usec = self.unixSec(); 700 | const d = self.loc.lookup(usec); 701 | usec += @intCast(i64, d.offset); 702 | var result: i64 = undefined; 703 | _ = @addWithOverflow(i64, usec, (unix_to_internal + internal_to_absolute), &result); 704 | return @bitCast(u64, result); 705 | } 706 | 707 | pub fn date(self: Self) DateDetail { 708 | return absDate(self.abs(), true); 709 | } 710 | 711 | pub fn year(self: Self) isize { 712 | const d = self.date(); 713 | return d.year; 714 | } 715 | 716 | pub fn month(self: Self) Month { 717 | const d = self.date(); 718 | return d.month; 719 | } 720 | 721 | pub fn day(self: Self) isize { 722 | const d = self.date(); 723 | return d.day; 724 | } 725 | 726 | /// returns the day of the week specified by self. 727 | pub fn weekday(self: Self) Weekday { 728 | return absWeekday(self.abs()); 729 | } 730 | 731 | /// isoWeek returns the ISO 8601 year and week number in which self occurs. 732 | /// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to 733 | /// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 734 | /// of year n+1. 735 | pub fn isoWeek(self: Self) ISOWeek { 736 | var d = self.date(); 737 | const wday = @mod(@intCast(isize, @enumToInt(self.weekday()) + 8), 7); 738 | const Mon: isize = 0; 739 | const Tue = Mon + 1; 740 | const Wed = Tue + 1; 741 | const Thu = Wed + 1; 742 | const Fri = Thu + 1; 743 | const Sat = Fri + 1; 744 | const Sun = Sat + 1; 745 | 746 | // Calculate week as number of Mondays in year up to 747 | // and including today, plus 1 because the first week is week 0. 748 | // Putting the + 1 inside the numerator as a + 7 keeps the 749 | // numerator from being negative, which would cause it to 750 | // round incorrectly. 751 | var week = @divTrunc(d.yday - wday + 7, 7); 752 | 753 | // The week number is now correct under the assumption 754 | // that the first Monday of the year is in week 1. 755 | // If Jan 1 is a Tuesday, Wednesday, or Thursday, the first Monday 756 | // is actually in week 2. 757 | const jan1wday = @mod((wday - d.yday + 7 * 53), 7); 758 | 759 | if (Tue <= jan1wday and jan1wday <= Thu) { 760 | week += 1; 761 | } 762 | if (week == 0) { 763 | d.year -= 1; 764 | week = 52; 765 | } 766 | 767 | // A year has 53 weeks when Jan 1 or Dec 31 is a Thursday, 768 | // meaning Jan 1 of the next year is a Friday 769 | // or it was a leap year and Jan 1 of the next year is a Saturday. 770 | if (jan1wday == Fri or (jan1wday == Sat) and isLeap(d.year)) { 771 | week += 1; 772 | } 773 | 774 | // December 29 to 31 are in week 1 of next year if 775 | // they are after the last Thursday of the year and 776 | // December 31 is a Monday, Tuesday, or Wednesday. 777 | if (@enumToInt(d.month) == @enumToInt(Month.December) and d.day >= 29 and wday < Thu) { 778 | const dec31wday = @mod((wday + 31 - d.day), 7); 779 | if (Mon <= dec31wday and dec31wday <= Wed) { 780 | d.year += 1; 781 | week = 1; 782 | } 783 | } 784 | return ISOWeek{ .year = d.year, .week = week }; 785 | } 786 | 787 | /// clock returns the hour, minute, and second within the day specified by t. 788 | pub fn clock(self: Self) Clock { 789 | return Clock.absClock(self.abs()); 790 | } 791 | 792 | /// returns the hour within the day specified by self, in the range [0, 23]. 793 | pub fn hour(self: Self) isize { 794 | return @divTrunc(@intCast(isize, self.abs() % seconds_per_day), seconds_per_hour); 795 | } 796 | 797 | /// returns the minute offset within the hour specified by self, in the 798 | /// range [0, 59]. 799 | pub fn minute(self: Self) isize { 800 | return @divTrunc(@intCast(isize, self.abs() % seconds_per_hour), seconds_per_minute); 801 | } 802 | 803 | /// returns the second offset within the minute specified by self, in the 804 | /// range [0, 59]. 805 | pub fn second(self: Self) isize { 806 | return @intCast(isize, self.abs() % seconds_per_minute); 807 | } 808 | 809 | /// returns the nanosecond offset within the second specified by self, 810 | /// in the range [0, 999999999]. 811 | pub fn nanosecond(self: Self) isize { 812 | return @intCast(isize, self.nsec()); 813 | } 814 | 815 | /// returns the day of the year specified by self, in the range [1,365] for non-leap years, 816 | /// and [1,366] in leap years. 817 | pub fn yearDay(self: Self) isize { 818 | const d = absDate(self.abs(), false); 819 | return d.yday + 1; 820 | } 821 | 822 | /// computes the time zone in effect at time t, returning the abbreviated 823 | /// name of the zone (such as "CET") and its offset in seconds east of UTC. 824 | pub fn zone(self: Self) ZoneDetail { 825 | const zn = self.loc.lookup(self.unixSec()); 826 | return ZoneDetail{ 827 | .name = zn.name, 828 | .offset = zn.offset, 829 | }; 830 | } 831 | 832 | /// utc returns time with the location set to UTC. 833 | fn utc(self: Self) Self { 834 | return Time{ 835 | .wall = self.wall, 836 | .ext = self.ext, 837 | .loc = &Location.utc_local, 838 | }; 839 | } 840 | 841 | fn string(self: Self, out: anytype) !void { 842 | try self.formatBuffer(out, DefaultFormat); 843 | // Format monotonic clock reading as m=±ddd.nnnnnnnnn. 844 | if ((self.wall & has_monotonic) != 0) { 845 | var m2 = @intCast(u64, self.ext); 846 | var sign: u8 = '+'; 847 | if (self.ext < 0) { 848 | sign = '-'; 849 | m2 = @intCast(u64, -self.ext); 850 | } 851 | var m1 = @divTrunc(m2, @as(u64, 1e9)); 852 | m2 = @mod(m2, @as(u64, 1e9)); 853 | var m0 = @divTrunc(m1, @as(u64, 1e9)); 854 | m1 = @mod(m1, @as(u64, 1e9)); 855 | try out.writeAll("m="); 856 | try out.writeByte(sign); 857 | var wid: usize = 0; 858 | if (m0 != 0) { 859 | try appendInt(out, @intCast(isize, m0), 0); 860 | wid = 9; 861 | } 862 | try appendInt(out, @intCast(isize, m1), wid); 863 | try out.writeByte('.'); 864 | try appendInt(out, @intCast(isize, m2), 9); 865 | } 866 | } 867 | 868 | pub fn format( 869 | self: Self, 870 | comptime fmt: []const u8, 871 | options: std.fmt.FormatOptions, 872 | out_stream: anytype, 873 | ) !void { 874 | try self.string(out_stream); 875 | } 876 | 877 | /// writes into out a textual representation of the time value formatted 878 | /// according to layout, which defines the format by showing how the reference 879 | /// time, defined to be 880 | /// Mon Jan 2 15:04:05 -0700 MST 2006 881 | /// would be displayed if it were the value; it serves as an example of the 882 | /// desired output. The same display rules will then be applied to the time 883 | /// value. 884 | /// 885 | /// A fractional second is represented by adding a period and zeros 886 | /// to the end of the seconds section of layout string, as in "15:04:05.000" 887 | /// to format a time stamp with millisecond precision. 888 | /// 889 | /// Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard 890 | /// and convenient representations of the reference time. For more information 891 | /// about the formats and the definition of the reference time, see the 892 | /// documentation for ANSIC and the other constants defined by this package. 893 | pub fn formatBuffer(self: Self, out: anytype, layout: []const u8) !void { 894 | return self.appendFormat(out, layout); 895 | } 896 | 897 | fn appendInt(stream: anytype, x: isize, width: usize) !void { 898 | var u = std.math.absCast(x); 899 | if (x < 0) { 900 | _ = try stream.write("-"); 901 | } 902 | var buf: [20]u8 = undefined; 903 | var i = buf.len; 904 | while (u >= 10) { 905 | i -= 1; 906 | const q = @divTrunc(u, 10); 907 | buf[i] = @intCast(u8, '0' + u - q * 10); 908 | u = q; 909 | } 910 | i -= 1; 911 | buf[i] = '0' + @intCast(u8, u); 912 | var w = buf.len - i; 913 | while (w < width) : (w += 1) { 914 | _ = try stream.write("0"); 915 | } 916 | const v = buf[i..]; 917 | _ = try stream.write(v); 918 | } 919 | 920 | fn formatNano(stream: anytype, nanosec: usize, n: usize, trim: bool) !void { 921 | var u = nanosec; 922 | var buf = [_]u8{0} ** 9; 923 | var start = buf.len; 924 | while (start > 0) { 925 | start -= 1; 926 | buf[start] = @intCast(u8, @mod(u, 10) + '0'); 927 | u /= 10; 928 | } 929 | var x = n; 930 | if (x > 9) { 931 | x = 9; 932 | } 933 | if (trim) { 934 | while (x > 0 and buf[x - 1] == '0') : (x -= 1) {} 935 | if (x == 0) { 936 | return; 937 | } 938 | } 939 | _ = try stream.write("."); 940 | _ = try stream.write(buf[0..x]); 941 | } 942 | 943 | /// appendFormat is like Format but appends the textual 944 | /// representation to b 945 | pub fn appendFormat(self: Self, stream: anytype, layout: []const u8) !void { 946 | const abs_value = self.abs(); 947 | const tz = self.zone(); 948 | const clock_value = self.clock(); 949 | const ddate = self.date(); 950 | var lay = layout; 951 | while (lay.len != 0) { 952 | const ctx = nextStdChunk(lay); 953 | if (ctx.prefix.len != 0) { 954 | try stream.writeAll(ctx.prefix); 955 | } 956 | lay = ctx.suffix; 957 | switch (ctx.chunk) { 958 | .none => return, 959 | .stdYear => { 960 | var y = ddate.year; 961 | if (y < 0) { 962 | y = -y; 963 | } 964 | try appendInt(stream, @mod(y, 100), 2); 965 | }, 966 | .stdLongYear => { 967 | try appendInt(stream, ddate.year, 4); 968 | }, 969 | .stdMonth => { 970 | _ = try stream.write(ddate.month.string()[0..3]); 971 | }, 972 | .stdLongMonth => { 973 | _ = try stream.write(ddate.month.string()); 974 | }, 975 | .stdNumMonth => { 976 | try appendInt(stream, @intCast(isize, @enumToInt(ddate.month)), 0); 977 | }, 978 | .stdZeroMonth => { 979 | try appendInt(stream, @intCast(isize, @enumToInt(ddate.month)), 2); 980 | }, 981 | .stdWeekDay => { 982 | const wk = self.weekday(); 983 | _ = try stream.write(wk.string()[0..3]); 984 | }, 985 | .stdLongWeekDay => { 986 | const wk = self.weekday(); 987 | _ = try stream.write(wk.string()); 988 | }, 989 | .stdDay => { 990 | try appendInt(stream, ddate.day, 0); 991 | }, 992 | .stdUnderDay => { 993 | if (ddate.day < 10) { 994 | _ = try stream.write(" "); 995 | } 996 | try appendInt(stream, ddate.day, 0); 997 | }, 998 | .stdZeroDay => { 999 | try appendInt(stream, ddate.day, 2); 1000 | }, 1001 | .stdHour => { 1002 | try appendInt(stream, clock_value.hour, 2); 1003 | }, 1004 | .stdHour12 => { 1005 | // Noon is 12PM, midnight is 12AM. 1006 | var hr = @mod(clock_value.hour, 12); 1007 | if (hr == 0) { 1008 | hr = 12; 1009 | } 1010 | try appendInt(stream, hr, 0); 1011 | }, 1012 | .stdZeroHour12 => { 1013 | // Noon is 12PM, midnight is 12AM. 1014 | var hr = @mod(clock_value.hour, 12); 1015 | if (hr == 0) { 1016 | hr = 12; 1017 | } 1018 | try appendInt(stream, hr, 2); 1019 | }, 1020 | .stdMinute => { 1021 | try appendInt(stream, clock_value.min, 0); 1022 | }, 1023 | .stdZeroMinute => { 1024 | try appendInt(stream, clock_value.min, 2); 1025 | }, 1026 | .stdSecond => { 1027 | try appendInt(stream, clock_value.sec, 0); 1028 | }, 1029 | .stdZeroSecond => { 1030 | try appendInt(stream, clock_value.sec, 2); 1031 | }, 1032 | .stdPM => { 1033 | if (clock_value.hour >= 12) { 1034 | _ = try stream.write("PM"); 1035 | } else { 1036 | _ = try stream.write("AM"); 1037 | } 1038 | }, 1039 | .stdpm => { 1040 | if (clock_value.hour >= 12) { 1041 | _ = try stream.write("pm"); 1042 | } else { 1043 | _ = try stream.write("am"); 1044 | } 1045 | }, 1046 | .stdISO8601TZ, .stdISO8601ColonTZ, .stdISO8601SecondsTZ, .stdISO8601ShortTZ, .stdISO8601ColonSecondsTZ, .stdNumTZ, .stdNumColonTZ, .stdNumSecondsTz, .stdNumShortTZ, .stdNumColonSecondsTZ => { 1047 | // Ugly special case. We cheat and take the "Z" variants 1048 | // to mean "the time zone as formatted for ISO 8601". 1049 | const cond = tz.offset == 0 and (ctx.chunk.eql(chunk.stdISO8601TZ) or 1050 | ctx.chunk.eql(chunk.stdISO8601ColonTZ) or 1051 | ctx.chunk.eql(chunk.stdISO8601SecondsTZ) or 1052 | ctx.chunk.eql(chunk.stdISO8601ShortTZ) or 1053 | ctx.chunk.eql(chunk.stdISO8601ColonSecondsTZ)); 1054 | if (cond) { 1055 | _ = try stream.write("Z"); 1056 | } 1057 | var z = @divTrunc(tz.offset, 60); 1058 | var abs_offset = tz.offset; 1059 | if (z < 0) { 1060 | _ = try stream.write("-"); 1061 | z = -z; 1062 | abs_offset = -abs_offset; 1063 | } else { 1064 | _ = try stream.write("+"); 1065 | } 1066 | try appendInt(stream, @divTrunc(z, 60), 2); 1067 | if (ctx.chunk.eql(chunk.stdISO8601ColonTZ) or 1068 | ctx.chunk.eql(chunk.stdNumColonTZ) or 1069 | ctx.chunk.eql(chunk.stdISO8601ColonSecondsTZ) or 1070 | ctx.chunk.eql(chunk.stdISO8601ColonSecondsTZ) or 1071 | ctx.chunk.eql(chunk.stdNumColonSecondsTZ)) 1072 | { 1073 | _ = try stream.write(":"); 1074 | } 1075 | if (!ctx.chunk.eql(chunk.stdNumShortTZ) and !ctx.chunk.eql(chunk.stdISO8601ShortTZ)) { 1076 | try appendInt(stream, @mod(z, 60), 2); 1077 | } 1078 | if (ctx.chunk.eql(chunk.stdISO8601SecondsTZ) or 1079 | ctx.chunk.eql(chunk.stdNumSecondsTz) or 1080 | ctx.chunk.eql(chunk.stdNumColonSecondsTZ) or 1081 | ctx.chunk.eql(chunk.stdISO8601ColonSecondsTZ)) 1082 | { 1083 | if (ctx.chunk.eql(chunk.stdNumColonSecondsTZ) or 1084 | ctx.chunk.eql(chunk.stdISO8601ColonSecondsTZ)) 1085 | { 1086 | _ = try stream.write(":"); 1087 | } 1088 | try appendInt(stream, @mod(abs_offset, 60), 2); 1089 | } 1090 | }, 1091 | .stdTZ => { 1092 | if (tz.name.len != 0) { 1093 | _ = try stream.write(tz.name); 1094 | continue; 1095 | } 1096 | var z = @divTrunc(tz.offset, 60); 1097 | if (z < 0) { 1098 | _ = try stream.write("-"); 1099 | z = -z; 1100 | } else { 1101 | _ = try stream.write("+"); 1102 | } 1103 | try appendInt(stream, @divTrunc(z, 60), 2); 1104 | try appendInt(stream, @mod(z, 60), 2); 1105 | }, 1106 | .stdFracSecond0, .stdFracSecond9 => { 1107 | try formatNano(stream, @intCast(usize, self.nanosecond()), ctx.args_shift.?, ctx.chunk.eql(chunk.stdFracSecond9)); 1108 | }, 1109 | else => unreachable, 1110 | } 1111 | } 1112 | } 1113 | 1114 | fn parseInternal(layout: []const u8, value: []const u8, default_location: *Location, local: *Location) !Self { 1115 | var alayout = layout; 1116 | var avalue = value; 1117 | var am_set = false; 1118 | var pm_set = false; 1119 | 1120 | var parsed_year: isize = 0; 1121 | var parsed_month: isize = -1; 1122 | var parsed_day: isize = -1; 1123 | var parsed_yday: isize = -1; 1124 | var parsed_hour: isize = 0; 1125 | var parsed_min: isize = 0; 1126 | var parsed_sec: isize = 0; 1127 | var parsed_nsec: isize = 0; 1128 | var z: ?*Location = null; 1129 | var zone_offset: isize = -1; 1130 | var zone_name = ""; 1131 | 1132 | var lay = layout; 1133 | var val = value; 1134 | while (true) { 1135 | const ctx = nextStdChunk(lay); 1136 | const std_str = lay[ctx.prefix.len..(lay.len - ctx.suffix.len)]; 1137 | val = try skip(val, ctx.prefix); 1138 | 1139 | if (ctx.chunk == .none) { 1140 | if (val.len != 0) { 1141 | return error.ExtraText; 1142 | } 1143 | } 1144 | lay = ctx.suffix; 1145 | switch (ctx.chunk) { 1146 | .stdYear => { 1147 | if (val.len < 2) { 1148 | return error.BadValue; 1149 | } 1150 | const p = val[0..2]; 1151 | val = val[2..]; 1152 | var has_err = false; 1153 | parsed_year = try std.fmt.parseInt(isize, p, 10); 1154 | if (parsed_year >= 69) { 1155 | // Unix time starts Dec 31 1969 in some time zones 1156 | parsed_year += 1900; 1157 | } else { 1158 | parsed_year += 2000; 1159 | } 1160 | }, 1161 | .stdLongYear => { 1162 | if (val.len < 4 or !isDigit(val, 0)) { 1163 | return error.BadValue; 1164 | } 1165 | const p = val[0..4]; 1166 | val = val[4..]; 1167 | parsed_year = try std.fmt.parseInt(isize, p, 10); 1168 | }, 1169 | .stdMonth => { 1170 | const idx = try lookup(short_month_names, val); 1171 | parsed_month = @intCast(isize, idx); 1172 | val = val[short_month_names[idx].len..]; 1173 | parsed_month += 1; 1174 | }, 1175 | .stdLongMonth => { 1176 | const idx = try lookup(long_month_names, val); 1177 | parsed_month = @intCast(isize, idx); 1178 | val = val[long_month_names[idx].len..]; 1179 | parsed_month += 1; 1180 | }, 1181 | .stdNumMonth, .stdZeroMonth => { 1182 | const n = try getNum(val, ctx.chunk == .stdZeroMonth); 1183 | parsed_month = n.value; 1184 | val = n.string; 1185 | }, 1186 | .stdWeekDay => { 1187 | const idx = try lookup(short_day_names, val); 1188 | val = val[short_day_names[idx].len..]; 1189 | }, 1190 | .stdLongWeekDay => { 1191 | const idx = try lookup(long_day_names, val); 1192 | val = val[long_day_names[idx].len..]; 1193 | }, 1194 | .stdDay, .stdUnderDay, .stdZeroDay => { 1195 | if (ctx.chunk == .stdUnderDay and 1196 | val.len > 0 and val[0] == ' ') 1197 | { 1198 | val = val[1..]; 1199 | } 1200 | const n = try getNum(val, ctx.chunk == .stdZeroDay); 1201 | parsed_day = n.value; 1202 | val = n.string; 1203 | // Note that we allow any one- or two-digit day here. 1204 | // The month, day, year combination is validated after we've completed parsing 1205 | }, 1206 | .stdUnderYearDay, .stdZeroYearDay => { 1207 | var i: usize = 0; 1208 | while (i < 2) : (i += 1) { 1209 | if (ctx.chunk == .stdUnderYearDay and 1210 | val.len > 0 and val[0] == ' ') 1211 | { 1212 | val = val[1..]; 1213 | } 1214 | } 1215 | const n = try getNum3(val, ctx.chunk == .stdZeroYearDay); 1216 | parsed_yday = n.value; 1217 | val = n.string; 1218 | // Note that we allow any one-, two-, or three-digit year-day here. 1219 | // The year-day, year combination is validated after we've completed parsing. 1220 | }, 1221 | .stdHour => { 1222 | const n = try getNum(val, false); 1223 | parsed_hour = n.value; 1224 | val = n.string; 1225 | if (parsed_hour < 0 or 24 <= parsed_hour) { 1226 | return error.BadHourRange; 1227 | } 1228 | }, 1229 | .stdHour12, .stdZeroHour12 => { 1230 | const n = try getNum(val, ctx.chunk == .stdZeroHour12); 1231 | parsed_hour = n.value; 1232 | val = n.string; 1233 | if (parsed_hour < 0 or 12 <= parsed_hour) { 1234 | return error.BadHourRange; 1235 | } 1236 | }, 1237 | .stdMinute, .stdZeroMinute => { 1238 | const n = try getNum(val, ctx.chunk == .stdZeroMinute); 1239 | parsed_min = n.value; 1240 | val = n.string; 1241 | if (parsed_min < 0 or 60 <= parsed_min) { 1242 | return error.BadMinuteRange; 1243 | } 1244 | }, 1245 | .stdSecond, .stdZeroSecond => { 1246 | const n = try getNum(val, ctx.chunk == .stdZeroSecond); 1247 | parsed_sec = n.value; 1248 | val = n.string; 1249 | if (parsed_sec < 0 or 60 <= parsed_sec) { 1250 | return error.BadSecondRange; 1251 | } 1252 | if (val.len > 2 and val[0] == '.' and isDigit(val, 1)) { 1253 | const ch = nextStdChunk(lay); 1254 | if (ch.chunk == .stdFracSecond0 or ch.chunk == .stdFracSecond9) { 1255 | // Fractional second in the layout; proceed normally 1256 | break; 1257 | } 1258 | var f: usize = 0; 1259 | while (f < val.len and isDigit(val, f)) : (f += 1) {} 1260 | parsed_nsec = try parseNanoseconds(val, f); 1261 | val = val[f..]; 1262 | } 1263 | }, 1264 | .stdPM => { 1265 | if (val.len < 2) { 1266 | return error.BadValue; 1267 | } 1268 | const p = val[0..2]; 1269 | val = val[2..]; 1270 | if (mem.eql(u8, p, "PM")) { 1271 | pm_set = true; 1272 | } else if (mem.eql(u8, p, "AM")) { 1273 | am_set = true; 1274 | } else { 1275 | return error.BadValue; 1276 | } 1277 | }, 1278 | .stdpm => { 1279 | if (val.len < 2) { 1280 | return error.BadValue; 1281 | } 1282 | const p = val[0..2]; 1283 | val = val[2..]; 1284 | if (mem.eql(u8, p, "pm")) { 1285 | pm_set = true; 1286 | } else if (mem.eql(u8, p, "am")) { 1287 | am_set = true; 1288 | } else { 1289 | return error.BadValue; 1290 | } 1291 | }, 1292 | else => {}, 1293 | } 1294 | } 1295 | return error.TODO; 1296 | } 1297 | 1298 | /// skip removes the given prefix from value, 1299 | /// treating runs of space characters as equivalent. 1300 | fn skip(value: []const u8, prefix: []const u8) ![]const u8 { 1301 | var v = value; 1302 | var p = prefix; 1303 | while (p.len > 0) { 1304 | if (p[0] == ' ') { 1305 | if (v.len > 0 and v[0] != ' ') { 1306 | return error.BadValue; 1307 | } 1308 | p = cutSpace(p); 1309 | v = cutSpace(v); 1310 | continue; 1311 | } 1312 | if (v.len == 0 or v[0] != p[0]) { 1313 | return error.BadValue; 1314 | } 1315 | p = p[1..]; 1316 | v = v[1..]; 1317 | } 1318 | return v; 1319 | } 1320 | 1321 | fn cutSpace(str: []const u8) []const u8 { 1322 | var s = str; 1323 | while (s.len > 0 and s[0] == ' ') : (s = s[1..]) {} 1324 | return s; 1325 | } 1326 | 1327 | /// add adds returns a new Time with duration added to self. 1328 | pub fn add(self: Self, d: Duration) Self { 1329 | var dsec = @divTrunc(d.value, @as(i64, 1e9)); 1330 | var nsec_value = self.nsec() + @intCast(i32, @mod(d.value, @as(i64, 1e9))); 1331 | if (nsec_value >= @as(i32, 1e9)) { 1332 | dsec += 1; 1333 | nsec_value -= @as(i32, 1e9); 1334 | } else if (nsec_value < 0) { 1335 | dsec -= 1; 1336 | nsec_value += @as(i32, 1e9); 1337 | } 1338 | var cp = self; 1339 | var t = &cp; 1340 | t.wall = (t.wall & ~nsec_mask) | @intCast(u64, nsec_value); // update nsec 1341 | t.addSec(dsec); 1342 | if (t.wall & has_monotonic != 0) { 1343 | const te = t.ext + @intCast(i64, d.value); 1344 | if (d.value < 0 and te > t.ext or d.value > 0 and te < t.ext) { 1345 | t.stripMono(); 1346 | } else { 1347 | t.ext = te; 1348 | } 1349 | } 1350 | return cp; 1351 | } 1352 | 1353 | /// sub returns the duration t-u. If the result exceeds the maximum (or minimum) 1354 | /// value that can be stored in a Duration, the maximum (or minimum) duration 1355 | /// will be returned. 1356 | /// To compute t-d for a duration d, use self.add(-d). 1357 | pub fn sub(self: Self, u: Self) Duration { 1358 | if ((self.wall & u.wall & has_monotonic) != 0) { 1359 | const te = self.ext; 1360 | const ue = u.ext; 1361 | var d = Duration.init(te - ue); 1362 | if (d.value < 0 and te > ue) { 1363 | return Duration.maxDuration; 1364 | } 1365 | if (d.value > 0 and te < ue) { 1366 | return Duration.minDuration; 1367 | } 1368 | return d; 1369 | } 1370 | const ts = self.sec(); 1371 | const us = u.sec(); 1372 | const ssub = ts - us; 1373 | if ((ssub < ts) != (us > 0)) { 1374 | if (self.before(u)) { 1375 | return Duration.minDuration; 1376 | } 1377 | return Duration.maxDuration; 1378 | } 1379 | if ((ssub < @divFloor(Duration.minDuration.value, Duration.Second.value)) or 1380 | (ssub > @divFloor(Duration.maxDuration.value, Duration.Second.value))) 1381 | { 1382 | if (self.before(u)) { 1383 | return Duration.minDuration; 1384 | } 1385 | return Duration.maxDuration; 1386 | } 1387 | var ssec: i64 = 0; 1388 | var nnsec: i64 = 0; 1389 | var d: i64 = 0; 1390 | ssec = ssub * Duration.Second.value; 1391 | nnsec = @intCast(i64, self.nsec() - u.nsec()); 1392 | if ((d > ssec) != (nnsec > 0)) { 1393 | if (self.before(u)) { 1394 | return Duration.minDuration; 1395 | } 1396 | return Duration.maxDuration; 1397 | } 1398 | return Duration.init(d); 1399 | } 1400 | 1401 | fn div(self: Self, d: Duration) divResult { 1402 | var neg = false; 1403 | var nsec_value = self.nsec(); 1404 | var sec_value = self.sec(); 1405 | if (sec_value < 0) { 1406 | // Operate on absolute value. 1407 | neg = true; 1408 | sec_value = -sec_value; 1409 | if (nsec_value < 0) { 1410 | nsec_value += @as(i32, 1e9); 1411 | sec_value -= 1; 1412 | } 1413 | } 1414 | var res = divResult{ .qmod = 0, .r = Duration.init(0) }; 1415 | if (d.value < @mod(Duration.Second.value, d.value * 2)) { 1416 | res.qmod = @intCast(isize, @divTrunc(nsec_value, @intCast(i32, d.value))) & 1; 1417 | res.r = Duration.init(@intCast(i64, @mod(nsec_value, @intCast(i32, d.value)))); 1418 | } else if (@mod(d.value, Duration.Second.value) == 0) { 1419 | const d1 = @divTrunc(d.value, Duration.Second.value); 1420 | res.qmod = @intCast(isize, @divTrunc(sec_value, d1)) & 1; 1421 | res.r = Duration.init(@mod(sec_value, d1) * Duration.Second.value + @intCast(i64, nsec_value)); 1422 | } else { 1423 | var s = @intCast(u64, sec_value); 1424 | var tmp = (s >> 32) * u64(1e9); 1425 | var u_1 = tmp >> 32; 1426 | var u_0 = tmp << 32; 1427 | tmp = (s & 0xFFFFFFFF) * u64(1e9); 1428 | var u_0x = u_0; 1429 | u_0 = u_0 + tmp; 1430 | if (u_0 < u_0x) { 1431 | u_1 += 1; 1432 | } 1433 | u_0x = u_0; 1434 | u_0 = u_0 + @intCast(u64, nsec_value); 1435 | if (u_0 < u_0x) { 1436 | u_1 += 1; 1437 | } 1438 | // Compute remainder by subtracting r<> 63) != 1) { 1442 | d1 <<= 1; 1443 | } 1444 | var d0 = u64(0); 1445 | while (true) { 1446 | res.qmod = 0; 1447 | if (u_1 > d1 or u_1 == d1 and u_0 >= d0) { 1448 | res.qmod = 1; 1449 | u_0x = u_0; 1450 | u_0 = u_0 - d0; 1451 | if (u_0 > u_0x) { 1452 | u_1 -= 1; 1453 | } 1454 | u_1 -= d1; 1455 | if (d1 == 0 and d0 == @intCast(u64, d.value)) { 1456 | break; 1457 | } 1458 | d0 >>= 1; 1459 | d0 |= (d1 & 1) << 63; 1460 | d1 >>= 1; 1461 | } 1462 | res.r = Duration.init(@intCast(i64, u_0)); 1463 | } 1464 | if (neg and res.r.value != 0) { 1465 | // If input was negative and not an exact multiple of d, we computed q, r such that 1466 | // q*d + r = -t 1467 | // But the right answers are given by -(q-1), d-r: 1468 | // q*d + r = -t 1469 | // -q*d - r = t 1470 | // -(q-1)*d + (d - r) = t 1471 | res.qmod ^= 1; 1472 | res.r = Duration.init(d.value - res.r.value); 1473 | } 1474 | return res; 1475 | } 1476 | return res; 1477 | } 1478 | 1479 | // these are utility functions that I ported from 1480 | // github.com/jinzhu/now 1481 | 1482 | pub fn beginningOfMinute(self: Self) Self { 1483 | //TODO: this needs truncate to be implemented. 1484 | return self; 1485 | } 1486 | 1487 | pub fn beginningOfHour(self: Self) Self { 1488 | const d = self.date(); 1489 | const c = self.clock(); 1490 | return context.date( 1491 | d.year, 1492 | @intCast(isize, @enumToInt(d.month)), 1493 | d.day, 1494 | c.hour, 1495 | 0, 1496 | 0, 1497 | 0, 1498 | self.loc, 1499 | ); 1500 | } 1501 | 1502 | pub fn beginningOfDay(self: Self) Self { 1503 | const d = self.date(); 1504 | return context.date( 1505 | d.year, 1506 | @intCast(isize, @enumToInt(d.month)), 1507 | d.day, 1508 | 0, 1509 | 0, 1510 | 0, 1511 | 0, 1512 | self.loc, 1513 | ); 1514 | } 1515 | 1516 | pub fn beginningOfWeek(self: Self) Self { 1517 | var t = self.beginningOfDay(); 1518 | const week_day = @intCast(isize, @enumToInt(self.weekday())); 1519 | return self.addDate(0, 0, -week_day); 1520 | } 1521 | 1522 | pub fn beginningOfMonth(self: Self) Self { 1523 | var d = self.date(); 1524 | return context.date( 1525 | d.year, 1526 | @intCast(isize, @enumToInt(d.month)), 1527 | 1, 1528 | 0, 1529 | 0, 1530 | 0, 1531 | 0, 1532 | self.loc, 1533 | ); 1534 | } 1535 | 1536 | pub fn endOfMonth(self: Self) Self { 1537 | return self.beginningOfMonth().addDate(0, 1, 0). 1538 | add(Duration.init(-Duration.Hour.value)); 1539 | } 1540 | 1541 | fn current_month() [4][7]usize { 1542 | return [4][7]usize{ 1543 | [_]usize{0} ** 7, 1544 | [_]usize{0} ** 7, 1545 | [_]usize{0} ** 7, 1546 | [_]usize{0} ** 7, 1547 | }; 1548 | } 1549 | 1550 | pub fn calendar() void { 1551 | var ma = [_][7]usize{ 1552 | [_]usize{0} ** 7, 1553 | [_]usize{0} ** 7, 1554 | [_]usize{0} ** 7, 1555 | [_]usize{0} ** 7, 1556 | [_]usize{0} ** 7, 1557 | [_]usize{0} ** 7, 1558 | }; 1559 | var m = ma[0..]; 1560 | var local = Location.getLocal(); 1561 | var current_time = now(&local); 1562 | const today = current_time.day(); 1563 | var begin = current_time.beginningOfMonth(); 1564 | var end = current_time.endOfMonth(); 1565 | const x = begin.date(); 1566 | const y = end.date(); 1567 | var i: usize = 1; 1568 | var at = @enumToInt(begin.weekday()); 1569 | var mx: usize = 0; 1570 | var d: usize = 1; 1571 | while (mx < m.len) : (mx += 1) { 1572 | var a = m[mx][0..]; 1573 | while (at < a.len and d <= @intCast(usize, y.day)) : (at += 1) { 1574 | a[at] = d; 1575 | d += 1; 1576 | } 1577 | at = 0; 1578 | } 1579 | warn("\n", .{}); 1580 | for (short_days) |ds| { 1581 | warn("{} |", .{ds}); 1582 | } 1583 | warn("\n", .{}); 1584 | for (m) |mv, idx| { 1585 | for (mv) |dv, vx| { 1586 | if (idx != 0 and vx == 0 and dv == 0) { 1587 | // The pre allocated month buffer is lage enough to span 7 1588 | // weeks. 1589 | // 1590 | // we know for a fact at the first week must have at least 1 1591 | // date,any other week that start with 0 date means we are 1592 | // past the end of the calendar so no need to keep printing. 1593 | return; 1594 | } 1595 | if (dv == 0) { 1596 | warn(" |", .{}); 1597 | continue; 1598 | } 1599 | if (dv == @intCast(usize, today)) { 1600 | if (dv < 10) { 1601 | warn(" *{} |", .{dv}); 1602 | } else { 1603 | warn("*{} |", .{dv}); 1604 | } 1605 | } else { 1606 | if (dv < 10) { 1607 | warn(" {} |", .{dv}); 1608 | } else { 1609 | warn(" {} |", .{dv}); 1610 | } 1611 | } 1612 | } 1613 | warn("\n", .{}); 1614 | } 1615 | warn("\n", .{}); 1616 | } 1617 | }; 1618 | 1619 | const ZoneDetail = struct { 1620 | name: []const u8, 1621 | offset: isize, 1622 | }; 1623 | 1624 | pub const Duration = struct { 1625 | value: i64, 1626 | 1627 | pub const Nanosecond = init(1); 1628 | pub const Microsecond = init(1000 * Nanosecond.value); 1629 | pub const Millisecond = init(1000 * Microsecond.value); 1630 | pub const Second = init(1000 * Millisecond.value); 1631 | pub const Minute = init(60 * Second.value); 1632 | pub const Hour = init(60 * Minute.value); 1633 | 1634 | const minDuration = init(-1 << 63); 1635 | const maxDuration = init((1 << 63) - 1); 1636 | 1637 | const fracRes = struct { 1638 | nw: usize, 1639 | nv: u64, 1640 | }; 1641 | 1642 | // fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the 1643 | // tail of buf, omitting trailing zeros. It omits the decimal 1644 | // point too when the fraction is 0. It returns the index where the 1645 | // output bytes begin and the value v/10**prec. 1646 | 1647 | pub fn init(v: i64) Duration { 1648 | return Duration{ .value = v }; 1649 | } 1650 | 1651 | fn fmtFrac(buf: []u8, value: u64, prec: usize) fracRes { 1652 | // Omit trailing zeros up to and including decimal point. 1653 | var w = buf.len; 1654 | var v = value; 1655 | var i: usize = 0; 1656 | var print: bool = false; 1657 | while (i < prec) : (i += 1) { 1658 | const digit = @mod(v, 10); 1659 | print = print or digit != 0; 1660 | if (print) { 1661 | w -= 1; 1662 | buf[w] = @intCast(u8, digit) + '0'; 1663 | } 1664 | v /= 10; 1665 | } 1666 | if (print) { 1667 | w -= 1; 1668 | buf[w] = '.'; 1669 | } 1670 | return fracRes{ .nw = w, .nv = v }; 1671 | } 1672 | 1673 | fn fmtInt(buf: []u8, value: u64) usize { 1674 | var w = buf.len; 1675 | var v = value; 1676 | if (v == 0) { 1677 | w -= 1; 1678 | buf[w] = '0'; 1679 | } else { 1680 | while (v > 0) { 1681 | w -= 1; 1682 | buf[w] = @intCast(u8, @mod(v, 10)) + '0'; 1683 | v /= 10; 1684 | } 1685 | } 1686 | return w; 1687 | } 1688 | 1689 | pub fn string(self: Duration) []const u8 { 1690 | var buf: [32]u8 = undefined; 1691 | var w = buf.len; 1692 | var u = @intCast(u64, self.value); 1693 | const neg = self.value < 0; 1694 | if (neg) { 1695 | u = @intCast(u64, -self.value); 1696 | } 1697 | if (u < @intCast(u64, Second.value)) { 1698 | // Special case: if duration is smaller than a second, 1699 | // use smaller units, like 1.2ms 1700 | var prec: usize = 0; 1701 | w -= 1; 1702 | buf[w] = 's'; 1703 | w -= 1; 1704 | if (u == 0) { 1705 | const s = "0s"; 1706 | return s[0..]; 1707 | } else if (u < @intCast(u64, Microsecond.value)) { 1708 | // print nanoseconds 1709 | prec = 0; 1710 | buf[w] = 'n'; 1711 | } else if (u < @intCast(u64, Millisecond.value)) { 1712 | // print microseconds 1713 | prec = 3; 1714 | // U+00B5 'µ' micro sign == 0xC2 0xB5 1715 | w -= 1; 1716 | mem.copy(u8, buf[w..], "µ"); 1717 | } else { 1718 | prec = 6; 1719 | buf[w] = 'm'; 1720 | } 1721 | const r = fmtFrac(buf[0..w], u, prec); 1722 | w = r.nw; 1723 | u = r.nv; 1724 | w = fmtInt(buf[0..w], u); 1725 | } else { 1726 | w -= 1; 1727 | buf[w] = 's'; 1728 | const r = fmtFrac(buf[0..w], u, 9); 1729 | w = r.nw; 1730 | u = r.nv; 1731 | w = fmtInt(buf[0..w], @mod(u, 60)); 1732 | u /= 60; 1733 | // u is now integer minutes 1734 | if (u > 0) { 1735 | w -= 1; 1736 | buf[w] = 'm'; 1737 | w = fmtInt(buf[0..w], @mod(u, 60)); 1738 | u /= 60; 1739 | // u is now integer hours 1740 | // Stop at hours because days can be different lengths. 1741 | if (u > 0) { 1742 | w -= 1; 1743 | buf[w] = 'h'; 1744 | w = fmtInt(buf[0..w], u); 1745 | } 1746 | } 1747 | } 1748 | if (neg) { 1749 | w -= 1; 1750 | buf[w] = '-'; 1751 | } 1752 | return buf[w..]; 1753 | } 1754 | 1755 | pub fn format( 1756 | self: Duration, 1757 | comptime fmt: []const u8, 1758 | options: std.fmt.FormatOptions, 1759 | out_stream: anytype, 1760 | ) !void { 1761 | try out_stream(context, self.string()); 1762 | } 1763 | 1764 | /// nanoseconds returns the duration as an integer nanosecond count. 1765 | pub fn nanoseconds(self: Duration) i64 { 1766 | return self.value; 1767 | } 1768 | 1769 | // These methods return float64 because the dominant 1770 | // use case is for printing a floating point number like 1.5s, and 1771 | // a truncation to integer would make them not useful in those cases. 1772 | // Splitting the integer and fraction ourselves guarantees that 1773 | // converting the returned float64 to an integer rounds the same 1774 | // way that a pure integer conversion would have, even in cases 1775 | // where, say, float64(d.Nanoseconds())/1e9 would have rounded 1776 | // differently. 1777 | 1778 | /// Seconds returns the duration as a floating point number of seconds. 1779 | pub fn seconds(self: Duration) f64 { 1780 | const sec = @divTrunc(self.value, Second.value); 1781 | const nsec = @mod(self.value, Second.value); 1782 | return @intToFloat(f64, sec) + @intToFloat(f64, nsec) / 1e9; 1783 | } 1784 | 1785 | /// Minutes returns the duration as a floating point number of minutes. 1786 | pub fn minutes(self: Duration) f64 { 1787 | const min = @divTrunc(self.value, Minute.value); 1788 | const nsec = @mod(self.value, Minute.value); 1789 | return @intToFloat(f64, min) + @intToFloat(f64, nsec) / (60 * 1e9); 1790 | } 1791 | 1792 | // Hours returns the duration as a floating point number of hours. 1793 | pub fn hours(self: Duration) f64 { 1794 | const hour = @divTrunc(self.value, Hour.value); 1795 | const nsec = @mod(self.value, Hour.value); 1796 | return @intToFloat(f64, hour) + @intToFloat(f64, nsec) / (60 * 60 * 1e9); 1797 | } 1798 | 1799 | /// Truncate returns the result of rounding d toward zero to a multiple of m. 1800 | /// If m <= 0, Truncate returns d unchanged. 1801 | pub fn truncate(self: Duration, m: Duration) Duration { 1802 | if (m.value <= 0) { 1803 | return self; 1804 | } 1805 | return init(self.value - @mod(d.value, m.value)); 1806 | } 1807 | 1808 | // lessThanHalf reports whether x+x < y but avoids overflow, 1809 | // assuming x and y are both positive (Duration is signed). 1810 | fn lessThanHalf(self: Duration, m: Duration) bool { 1811 | const x = @intCast(u64, self.value); 1812 | return x + x < @intCast(u64, m.value); 1813 | } 1814 | 1815 | // Round returns the result of rounding d to the nearest multiple of m. 1816 | // The rounding behavior for halfway values is to round away from zero. 1817 | // If the result exceeds the maximum (or minimum) 1818 | // value that can be stored in a Duration, 1819 | // Round returns the maximum (or minimum) duration. 1820 | // If m <= 0, Round returns d unchanged. 1821 | pub fn round(self: Duration, m: Duration) Duration { 1822 | if (v.value <= 0) { 1823 | return d; 1824 | } 1825 | var r = init(@mod(self.value, m.value)); 1826 | if (self.value < 0) { 1827 | r.value = -r.value; 1828 | if (r.lessThanHalf(m)) { 1829 | return init(self.value + r.value); 1830 | } 1831 | const d = self.value - m.value + r.value; 1832 | if (d < self.value) { 1833 | return init(d); 1834 | } 1835 | return init(minDuration); 1836 | } 1837 | 1838 | if (r.lessThanHalf(m)) { 1839 | return init(self.value - r.value); 1840 | } 1841 | const d = self.value + m.value - r.value; 1842 | if (d > self.value) { 1843 | return init(d); 1844 | } 1845 | return init(maxDuration); 1846 | } 1847 | }; 1848 | 1849 | const normRes = struct { 1850 | hi: isize, 1851 | lo: isize, 1852 | }; 1853 | 1854 | // norm returns nhi, nlo such that 1855 | // hi * base + lo == nhi * base + nlo 1856 | // 0 <= nlo < base 1857 | 1858 | fn norm(i: isize, o: isize, base: isize) normRes { 1859 | var hi = i; 1860 | var lo = o; 1861 | if (lo < 0) { 1862 | const n = @divTrunc(-lo - 1, base) + 1; 1863 | hi -= n; 1864 | lo += (n * base); 1865 | } 1866 | if (lo >= base) { 1867 | const n = @divTrunc(lo, base); 1868 | hi += n; 1869 | lo -= (n * base); 1870 | } 1871 | return normRes{ .hi = hi, .lo = lo }; 1872 | } 1873 | 1874 | /// date returns the Time corresponding to 1875 | /// yyyy-mm-dd hh:mm:ss + nsec nanoseconds 1876 | /// in the appropriate zone for that time in the given location. 1877 | /// 1878 | /// The month, day, hour, min, sec, and nsec values may be outside 1879 | /// their usual ranges and will be normalized during the conversion. 1880 | /// For example, October 32 converts to November 1. 1881 | /// 1882 | /// A daylight savings time transition skips or repeats times. 1883 | /// For example, in the United States, March 13, 2011 2:15am never occurred, 1884 | /// while November 6, 2011 1:15am occurred twice. In such cases, the 1885 | /// choice of time zone, and therefore the time, is not well-defined. 1886 | /// Date returns a time that is correct in one of the two zones involved 1887 | /// in the transition, but it does not guarantee which. 1888 | /// 1889 | /// Date panics if loc is nil. 1890 | pub fn date( 1891 | year: isize, 1892 | month: isize, 1893 | day: isize, 1894 | hour: isize, 1895 | min: isize, 1896 | sec: isize, 1897 | nsec: isize, 1898 | loc: *Location, 1899 | ) Time { 1900 | var v_year = year; 1901 | var v_day = day; 1902 | var v_hour = hour; 1903 | var v_min = min; 1904 | var v_sec = sec; 1905 | var v_nsec = nsec; 1906 | var v_loc = loc; 1907 | 1908 | // Normalize month, overflowing into year 1909 | var m = month - 1; 1910 | var r = norm(v_year, m, 12); 1911 | v_year = r.hi; 1912 | m = r.lo; 1913 | var v_month = @intToEnum(Month, @intCast(usize, m) + 1); 1914 | 1915 | // Normalize nsec, sec, min, hour, overflowing into day. 1916 | r = norm(sec, v_nsec, 1e9); 1917 | v_sec = r.hi; 1918 | v_nsec = r.lo; 1919 | r = norm(min, v_sec, 60); 1920 | v_min = r.hi; 1921 | v_sec = r.lo; 1922 | r = norm(v_hour, v_min, 60); 1923 | v_hour = r.hi; 1924 | v_min = r.lo; 1925 | r = norm(v_day, v_hour, 24); 1926 | v_day = r.hi; 1927 | v_hour = r.lo; 1928 | 1929 | var y = @intCast(u64, @intCast(i64, v_year) - absolute_zero_year); 1930 | 1931 | // Compute days since the absolute epoch. 1932 | 1933 | // Add in days from 400-year cycles. 1934 | var n = @divTrunc(y, 400); 1935 | y -= (400 * n); 1936 | var d = days_per_400_years * n; 1937 | 1938 | // Add in 100-year cycles. 1939 | n = @divTrunc(y, 100); 1940 | y -= 100 * n; 1941 | d += days_per_100_years * n; 1942 | 1943 | // Add in 4-year cycles. 1944 | n = @divTrunc(y, 4); 1945 | y -= 4 * n; 1946 | d += days_per_4_years * n; 1947 | 1948 | // Add in non-leap years. 1949 | n = y; 1950 | d += 365 * n; 1951 | 1952 | // Add in days before this month. 1953 | d += @intCast(u64, daysBefore[@enumToInt(v_month) - 1]); 1954 | if (isLeap(v_year) and @enumToInt(v_month) >= @enumToInt(Month.March)) { 1955 | d += 1; // February 29 1956 | } 1957 | 1958 | // Add in days before today. 1959 | d += @intCast(u64, v_day - 1); 1960 | 1961 | // Add in time elapsed today. 1962 | var abs = d * seconds_per_day; 1963 | 1964 | abs += @intCast(u64, hour * seconds_per_hour + min * seconds_per_minute + sec); 1965 | var unix_value: i64 = undefined; 1966 | _ = @addWithOverflow(i64, @bitCast(i64, abs), (absolute_to_internal + internal_to_unix), &unix_value); 1967 | 1968 | // Look for zone offset for t, so we can adjust to UTC. 1969 | // The lookup function expects UTC, so we pass t in the 1970 | // hope that it will not be too close to a zone transition, 1971 | // and then adjust if it is. 1972 | var zn = loc.lookup(unix_value); 1973 | if (zn.offset != 0) { 1974 | const utc_value = unix_value - @intCast(i64, zn.offset); 1975 | if (utc_value < zn.start) { 1976 | zn = loc.lookup(zn.start - 1); 1977 | } else if (utc_value >= zn.end) { 1978 | zn = loc.lookup(zn.end); 1979 | } 1980 | unix_value -= @intCast(i64, zn.offset); 1981 | } 1982 | return unixTimeWithLoc(unix_value, @intCast(i32, v_nsec), loc); 1983 | } 1984 | 1985 | /// ISO 8601 year and week number 1986 | pub const ISOWeek = struct { 1987 | year: isize, 1988 | week: isize, 1989 | }; 1990 | 1991 | pub const Clock = struct { 1992 | hour: isize, 1993 | min: isize, 1994 | sec: isize, 1995 | 1996 | fn absClock(abs: u64) Clock { 1997 | var sec = @intCast(isize, abs % seconds_per_day); 1998 | var hour = @divTrunc(sec, seconds_per_hour); 1999 | sec -= (hour * seconds_per_hour); 2000 | var min = @divTrunc(sec, seconds_per_minute); 2001 | sec -= (min * seconds_per_minute); 2002 | return Clock{ .hour = hour, .min = min, .sec = sec }; 2003 | } 2004 | }; 2005 | 2006 | fn absWeekday(abs: u64) Weekday { 2007 | const s = @mod(abs + @intCast(u64, @enumToInt(Weekday.Monday)) * seconds_per_day, seconds_per_week); 2008 | const w = s / seconds_per_day; 2009 | return @intToEnum(Weekday, @intCast(usize, w)); 2010 | } 2011 | 2012 | pub const Month = enum(usize) { 2013 | January = 1, 2014 | February = 2, 2015 | March = 3, 2016 | April = 4, 2017 | May = 5, 2018 | June = 6, 2019 | July = 7, 2020 | August = 8, 2021 | September = 9, 2022 | October = 10, 2023 | November = 11, 2024 | December = 12, 2025 | 2026 | pub fn string(self: Month) []const u8 { 2027 | const m = @enumToInt(self); 2028 | if (@enumToInt(Month.January) <= m and m <= @enumToInt(Month.December)) { 2029 | return months[m - 1]; 2030 | } 2031 | unreachable; 2032 | } 2033 | 2034 | pub fn format( 2035 | self: Month, 2036 | comptime fmt: []const u8, 2037 | options: std.fmt.FormatOptions, 2038 | out_stream: anytype, 2039 | ) !void { 2040 | try out_stream.writeAll(self.string()); 2041 | } 2042 | }; 2043 | 2044 | pub const DateDetail = struct { 2045 | year: isize, 2046 | month: Month, 2047 | day: isize, 2048 | yday: isize, 2049 | }; 2050 | 2051 | fn absDate(abs: u64, full: bool) DateDetail { 2052 | var details: DateDetail = undefined; 2053 | // Split into time and day. 2054 | var d = abs / seconds_per_day; 2055 | 2056 | // Account for 400 year cycles. 2057 | var n = d / days_per_400_years; 2058 | var y = 400 * n; 2059 | d -= days_per_400_years * n; 2060 | 2061 | // Cut off 100-year cycles. 2062 | // The last cycle has one extra leap year, so on the last day 2063 | // of that year, day / days_per_100_years will be 4 instead of 3. 2064 | // Cut it back down to 3 by subtracting n>>2. 2065 | n = d / days_per_100_years; 2066 | n -= n >> 2; 2067 | y += 100 * n; 2068 | d -= days_per_100_years * n; 2069 | 2070 | // Cut off 4-year cycles. 2071 | // The last cycle has a missing leap year, which does not 2072 | // affect the computation. 2073 | n = d / days_per_4_years; 2074 | y += 4 * n; 2075 | d -= days_per_4_years * n; 2076 | 2077 | // Cut off years within a 4-year cycle. 2078 | // The last year is a leap year, so on the last day of that year, 2079 | // day / 365 will be 4 instead of 3. Cut it back down to 3 2080 | // by subtracting n>>2. 2081 | n = d / 365; 2082 | n -= n >> 2; 2083 | y += n; 2084 | d -= 365 * n; 2085 | details.year = @intCast(isize, @intCast(i64, y) + absolute_zero_year); 2086 | details.yday = @intCast(isize, d); 2087 | if (!full) { 2088 | return details; 2089 | } 2090 | details.day = details.yday; 2091 | if (isLeap(details.year)) { 2092 | if (details.day > (31 + 29 - 1)) { 2093 | // After leap day; pretend it wasn't there. 2094 | details.day -= 1; 2095 | } else if (details.day == (31 + 29 - 1)) { 2096 | // Leap day. 2097 | details.month = Month.February; 2098 | details.day = 29; 2099 | return details; 2100 | } 2101 | } 2102 | 2103 | // Estimate month on assumption that every month has 31 days. 2104 | // The estimate may be too low by at most one month, so adjust. 2105 | var month = @intCast(usize, details.day) / @as(usize, 31); 2106 | const end = daysBefore[month + 1]; 2107 | var begin: isize = 0; 2108 | if (details.day >= end) { 2109 | month += 1; 2110 | begin = end; 2111 | } else { 2112 | begin = daysBefore[month]; 2113 | } 2114 | month += 1; 2115 | details.day = details.day - begin + 1; 2116 | details.month = @intToEnum(Month, month); 2117 | return details; 2118 | } 2119 | 2120 | // daysBefore[m] counts the number of days in a non-leap year 2121 | // before month m begins. There is an entry for m=12, counting 2122 | // the number of days before January of next year (365). 2123 | 2124 | const daysBefore = [_]isize{ 2125 | 0, 2126 | 31, 2127 | 31 + 28, 2128 | 31 + 28 + 31, 2129 | 31 + 28 + 31 + 30, 2130 | 31 + 28 + 31 + 30 + 31, 2131 | 31 + 28 + 31 + 30 + 31 + 30, 2132 | 31 + 28 + 31 + 30 + 31 + 30 + 31, 2133 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, 2134 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, 2135 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, 2136 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, 2137 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, 2138 | }; 2139 | 2140 | fn isLeap(year: isize) bool { 2141 | return @mod(year, 4) == 0 and (@mod(year, 100) != 0 or @mod(year, 100) == 0); 2142 | } 2143 | 2144 | const months = [_][]const u8{ 2145 | "January", 2146 | "February", 2147 | "March", 2148 | "April", 2149 | "May", 2150 | "June", 2151 | "July", 2152 | "August", 2153 | "September", 2154 | "October", 2155 | "November", 2156 | "December", 2157 | }; 2158 | 2159 | pub const Weekday = enum(usize) { 2160 | Sunday, 2161 | Monday, 2162 | Tuesday, 2163 | Wednesday, 2164 | Thursday, 2165 | Friday, 2166 | Saturday, 2167 | 2168 | pub fn string(self: Weekday) []const u8 { 2169 | const d = @enumToInt(self); 2170 | if (@enumToInt(Weekday.Sunday) <= d and d <= @enumToInt(Weekday.Saturday)) { 2171 | return days[d]; 2172 | } 2173 | unreachable; 2174 | } 2175 | 2176 | pub fn format( 2177 | self: Weekday, 2178 | comptime fmt: []const u8, 2179 | options: std.fmt.FormatOptions, 2180 | out_stream: anytype, 2181 | ) !void { 2182 | try out_stream(context, self.string()); 2183 | } 2184 | }; 2185 | 2186 | const days = [_][]const u8{ 2187 | "Sunday", 2188 | "Monday", 2189 | "Tuesday", 2190 | "Wednesday", 2191 | "Thursday", 2192 | "Friday", 2193 | "Saturday", 2194 | }; 2195 | 2196 | /// now returns the current local time and assigns the retuned time to use 2197 | /// local as location data. 2198 | pub fn now(local: *Location) Time { 2199 | const bt = timeNow(); 2200 | const sec = (bt.sec + unix_to_internal) - min_wall; 2201 | if ((@intCast(u64, sec) >> 33) != 0) { 2202 | return Time{ 2203 | .wall = @intCast(u64, bt.nsec), 2204 | .ext = sec + min_wall, 2205 | .loc = local, 2206 | }; 2207 | } 2208 | return Time{ 2209 | .wall = has_monotonic | (@intCast(u64, sec) << nsec_shift) | @intCast(u64, bt.nsec), 2210 | .ext = @intCast(i64, bt.mono), 2211 | .loc = local, 2212 | }; 2213 | } 2214 | 2215 | fn unixTime(sec: i64, nsec: i32) Time { 2216 | var local = getLocal(); 2217 | return unixTimeWithLoc(sec, nsec, local); 2218 | } 2219 | 2220 | fn unixTimeWithLoc(sec: i64, nsec: i32, loc: *Location) Time { 2221 | return Time{ 2222 | .wall = @intCast(u64, nsec), 2223 | .ext = sec + unix_to_internal, 2224 | .loc = loc, 2225 | }; 2226 | } 2227 | 2228 | pub fn unix(sec: i64, nsec: i64, local: *Location) Time { 2229 | var x = sec; 2230 | var y = nsec; 2231 | if (nsec < 0 or nsec >= @as(i64, 1e9)) { 2232 | const n = @divTrunc(nsec, @as(i64, 1e9)); 2233 | x += n; 2234 | y -= (n * @as(i64, 1e9)); 2235 | if (y < 0) { 2236 | y += @as(i64, 1e9); 2237 | x -= 1; 2238 | } 2239 | } 2240 | return unixTimeWithLoc(x, @intCast(i32, y), local); 2241 | } 2242 | 2243 | const bintime = struct { 2244 | sec: isize, 2245 | nsec: isize, 2246 | mono: u64, 2247 | }; 2248 | 2249 | fn timeNow() bintime { 2250 | switch (builtin.os.tag) { 2251 | .linux => { 2252 | var ts: std.os.timespec = undefined; 2253 | const err = std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts) catch unreachable; 2254 | return bintime{ .sec = ts.tv_sec, .nsec = ts.tv_nsec, .mono = clockNative() }; 2255 | }, 2256 | .macos, .ios => { 2257 | var tv: darwin.timeval = undefined; 2258 | var err = darwin.gettimeofday(&tv, null); 2259 | assert(err == 0); 2260 | return bintime{ .sec = tv.tv_sec, .nsec = tv.tv_usec, .mono = clockNative() }; 2261 | }, 2262 | else => @compileError("Unsupported OS"), 2263 | } 2264 | } 2265 | 2266 | const clockNative = switch (builtin.os.tag) { 2267 | .windows => clockWindows, 2268 | .linux => clockLinux, 2269 | .macos, .ios => clockDarwin, 2270 | else => @compileError("Unsupported OS"), 2271 | }; 2272 | 2273 | fn clockWindows() u64 { 2274 | var result: i64 = undefined; 2275 | var err = windows.QueryPerformanceCounter(&result); 2276 | assert(err != windows.FALSE); 2277 | return @intCast(u64, result); 2278 | } 2279 | 2280 | fn clockDarwin() u64 { 2281 | return darwin.mach_absolute_time(); 2282 | } 2283 | 2284 | fn clockLinux() u64 { 2285 | var ts: std.os.timespec = undefined; 2286 | var result = std.os.linux.clock_gettime(std.os.CLOCK_MONOTONIC, &ts); 2287 | assert(std.os.linux.getErrno(result) == 0); 2288 | return @intCast(u64, ts.tv_sec) * @as(u64, 1000000000) + @intCast(u64, ts.tv_nsec); 2289 | } 2290 | 2291 | // These are predefined layouts for use in Time.Format and time.Parse. 2292 | // The reference time used in the layouts is the specific time: 2293 | // Mon Jan 2 15:04:05 MST 2006 2294 | // which is Unix time 1136239445. Since MST is GMT-0700, 2295 | // the reference time can be thought of as 2296 | // 01/02 03:04:05PM '06 -0700 2297 | // To define your own format, write down what the reference time would look 2298 | // like formatted your way; see the values of constants like ANSIC, 2299 | // StampMicro or Kitchen for examples. The model is to demonstrate what the 2300 | // reference time looks like so that the Format and Parse methods can apply 2301 | // the same transformation to a general time value. 2302 | // 2303 | // Some valid layouts are invalid time values for time.Parse, due to formats 2304 | // such as _ for space padding and Z for zone information. 2305 | // 2306 | // Within the format string, an underscore _ represents a space that may be 2307 | // replaced by a digit if the following number (a day) has two digits; for 2308 | // compatibility with fixed-width Unix time formats. 2309 | // 2310 | // A decimal point followed by one or more zeros represents a fractional 2311 | // second, printed to the given number of decimal places. A decimal point 2312 | // followed by one or more nines represents a fractional second, printed to 2313 | // the given number of decimal places, with trailing zeros removed. 2314 | // When parsing (only), the input may contain a fractional second 2315 | // field immediately after the seconds field, even if the layout does not 2316 | // signify its presence. In that case a decimal point followed by a maximal 2317 | // series of digits is parsed as a fractional second. 2318 | // 2319 | // Numeric time zone offsets format as follows: 2320 | // -0700 ±hhmm 2321 | // -07:00 ±hh:mm 2322 | // -07 ±hh 2323 | // Replacing the sign in the format with a Z triggers 2324 | // the ISO 8601 behavior of printing Z instead of an 2325 | // offset for the UTC zone. Thus: 2326 | // Z0700 Z or ±hhmm 2327 | // Z07:00 Z or ±hh:mm 2328 | // Z07 Z or ±hh 2329 | // 2330 | // The recognized day of week formats are "Mon" and "Monday". 2331 | // The recognized month formats are "Jan" and "January". 2332 | // 2333 | // Text in the format string that is not recognized as part of the reference 2334 | // time is echoed verbatim during Format and expected to appear verbatim 2335 | // in the input to Parse. 2336 | // 2337 | // The executable example for Time.Format demonstrates the working 2338 | // of the layout string in detail and is a good reference. 2339 | // 2340 | // Note that the RFC822, RFC850, and RFC1123 formats should be applied 2341 | // only to local times. Applying them to UTC times will use "UTC" as the 2342 | // time zone abbreviation, while strictly speaking those RFCs require the 2343 | // use of "GMT" in that case. 2344 | // In general RFC1123Z should be used instead of RFC1123 for servers 2345 | // that insist on that format, and RFC3339 should be preferred for new protocols. 2346 | // RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; 2347 | // when used with time.Parse they do not accept all the time formats 2348 | // permitted by the RFCs. 2349 | // The RFC3339Nano format removes trailing zeros from the seconds field 2350 | // and thus may not sort correctly once formatted. 2351 | 2352 | pub const ANSIC = "Mon Jan _2 15:04:05 2006"; 2353 | pub const UnixDate = "Mon Jan _2 15:04:05 MST 2006"; 2354 | pub const RubyDate = "Mon Jan 02 15:04:05 -0700 2006"; 2355 | pub const RFC822 = "02 Jan 06 15:04 MST"; 2356 | pub const RFC822Z = "02 Jan 06 15:04 -0700"; // RFC822 with numeric zone 2357 | pub const RFC850 = "Monday, 02-Jan-06 15:04:05 MST"; 2358 | pub const RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"; 2359 | pub const RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"; // RFC1123 with numeric zone 2360 | pub const RFC3339 = "2006-01-02T15:04:05Z07:00"; 2361 | pub const RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"; 2362 | pub const Kitchen = "3:04PM"; 2363 | // Handy time stamps. 2364 | pub const Stamp = "Jan _2 15:04:05"; 2365 | pub const StampMilli = "Jan _2 15:04:05.000"; 2366 | pub const StampMicro = "Jan _2 15:04:05.000000"; 2367 | pub const StampNano = "Jan _2 15:04:05.000000000"; 2368 | 2369 | pub const DefaultFormat = "2006-01-02 15:04:05.999999999 -0700 MST"; 2370 | 2371 | pub const chunk = enum { 2372 | none, 2373 | stdLongMonth, // "January" 2374 | stdMonth, // "Jan" 2375 | stdNumMonth, // "1" 2376 | stdZeroMonth, // "01" 2377 | stdLongWeekDay, // "Monday" 2378 | stdWeekDay, // "Mon" 2379 | stdDay, // "2" 2380 | stdUnderDay, // "_2" 2381 | stdZeroDay, // "02" 2382 | stdUnderYearDay, // "__2" 2383 | stdZeroYearDay, // "002" 2384 | stdHour, // "15" 2385 | stdHour12, // "3" 2386 | stdZeroHour12, // "03" 2387 | stdMinute, // "4" 2388 | stdZeroMinute, // "04" 2389 | stdSecond, // "5" 2390 | stdZeroSecond, // "05" 2391 | stdLongYear, // "2006" 2392 | stdYear, // "06" 2393 | stdPM, // "PM" 2394 | stdpm, // "pm" 2395 | stdTZ, // "MST" 2396 | stdISO8601TZ, // "Z0700" // prints Z for UTC 2397 | stdISO8601SecondsTZ, // "Z070000" 2398 | stdISO8601ShortTZ, // "Z07" 2399 | stdISO8601ColonTZ, // "Z07:00" // prints Z for UTC 2400 | stdISO8601ColonSecondsTZ, // "Z07:00:00" 2401 | stdNumTZ, // "-0700" // always numeric 2402 | stdNumSecondsTz, // "-070000" 2403 | stdNumShortTZ, // "-07" // always numeric 2404 | stdNumColonTZ, // "-07:00" // always numeric 2405 | stdNumColonSecondsTZ, // "-07:00:00" 2406 | stdFracSecond0, // ".0", ".00", ... , trailing zeros included 2407 | stdFracSecond9, // ".9", ".99", ..., trailing zeros omitted 2408 | 2409 | stdNeedDate, // need month, day, year 2410 | stdNeedClock, // need hour, minute, second 2411 | stdArgShift, // extra argument in high bits, above low stdArgShift 2412 | 2413 | fn eql(self: chunk, other: chunk) bool { 2414 | return @enumToInt(self) == @enumToInt(other); 2415 | } 2416 | }; 2417 | 2418 | // startsWithLowerCase reports whether the string has a lower-case letter at the beginning. 2419 | // Its purpose is to prevent matching strings like "Month" when looking for "Mon". 2420 | 2421 | fn startsWithLowerCase(str: []const u8) bool { 2422 | if (str.len == 0) { 2423 | return false; 2424 | } 2425 | const c = str[0]; 2426 | return 'a' <= c and c <= 'z'; 2427 | } 2428 | 2429 | pub const chunkResult = struct { 2430 | prefix: []const u8, 2431 | suffix: []const u8, 2432 | chunk: chunk, 2433 | args_shift: ?usize, 2434 | }; 2435 | 2436 | const std0x = [_]chunk{ 2437 | chunk.stdZeroMonth, 2438 | chunk.stdZeroDay, 2439 | chunk.stdZeroHour12, 2440 | chunk.stdZeroMinute, 2441 | chunk.stdZeroSecond, 2442 | chunk.stdYear, 2443 | }; 2444 | 2445 | pub fn nextStdChunk(layout: []const u8) chunkResult { 2446 | var i: usize = 0; 2447 | while (i < layout.len) : (i += 1) { 2448 | switch (layout[i]) { 2449 | 'J' => { // January, Jan 2450 | if ((layout.len >= i + 3) and mem.eql(u8, layout[i .. i + 3], "Jan")) { 2451 | if ((layout.len >= i + 7) and mem.eql(u8, layout[i .. i + 7], "January")) { 2452 | return chunkResult{ 2453 | .prefix = layout[0..i], 2454 | .chunk = chunk.stdLongMonth, 2455 | .suffix = layout[i + 7 ..], 2456 | .args_shift = null, 2457 | }; 2458 | } 2459 | if (!startsWithLowerCase(layout[i + 3 ..])) { 2460 | return chunkResult{ 2461 | .prefix = layout[0..i], 2462 | .chunk = chunk.stdMonth, 2463 | .suffix = layout[i + 3 ..], 2464 | .args_shift = null, 2465 | }; 2466 | } 2467 | } 2468 | }, 2469 | 'M' => { // Monday, Mon, MST 2470 | if (layout.len >= 1 + 3) { 2471 | if (mem.eql(u8, layout[i .. i + 3], "Mon")) { 2472 | if ((layout.len >= i + 6) and mem.eql(u8, layout[i .. i + 6], "Monday")) { 2473 | return chunkResult{ 2474 | .prefix = layout[0..i], 2475 | .chunk = chunk.stdLongWeekDay, 2476 | .suffix = layout[i + 6 ..], 2477 | .args_shift = null, 2478 | }; 2479 | } 2480 | if (!startsWithLowerCase(layout[i + 3 ..])) { 2481 | return chunkResult{ 2482 | .prefix = layout[0..i], 2483 | .chunk = chunk.stdWeekDay, 2484 | .suffix = layout[i + 3 ..], 2485 | .args_shift = null, 2486 | }; 2487 | } 2488 | } 2489 | if (mem.eql(u8, layout[i .. i + 3], "MST")) { 2490 | return chunkResult{ 2491 | .prefix = layout[0..i], 2492 | .chunk = chunk.stdTZ, 2493 | .suffix = layout[i + 3 ..], 2494 | .args_shift = null, 2495 | }; 2496 | } 2497 | } 2498 | }, 2499 | '0' => { // 01, 02, 03, 04, 05, 06, 002 2500 | if (layout.len >= i + 2 and '1' <= layout[i + 1] and layout[i + 1] <= '6') { 2501 | const x = layout[i + 1] - '1'; 2502 | return chunkResult{ 2503 | .prefix = layout[0..i], 2504 | .chunk = std0x[x], 2505 | .suffix = layout[i + 2 ..], 2506 | .args_shift = null, 2507 | }; 2508 | } 2509 | if (layout.len >= i + 3 and layout[i + 1] == '0' and layout[i + 2] == '2') { 2510 | return chunkResult{ 2511 | .prefix = layout[0..i], 2512 | .chunk = .stdZeroYearDay, 2513 | .suffix = layout[i + 3 ..], 2514 | .args_shift = null, 2515 | }; 2516 | } 2517 | }, 2518 | '1' => { // 15, 1 2519 | if (layout.len >= i + 2 and layout[i + 1] == '5') { 2520 | return chunkResult{ 2521 | .prefix = layout[0..i], 2522 | .chunk = chunk.stdHour, 2523 | .suffix = layout[i + 2 ..], 2524 | .args_shift = null, 2525 | }; 2526 | } 2527 | return chunkResult{ 2528 | .prefix = layout[0..i], 2529 | .chunk = chunk.stdNumMonth, 2530 | .suffix = layout[i + 1 ..], 2531 | .args_shift = null, 2532 | }; 2533 | }, 2534 | '2' => { // 2006, 2 2535 | if (layout.len >= i + 4 and mem.eql(u8, layout[i .. i + 4], "2006")) { 2536 | return chunkResult{ 2537 | .prefix = layout[0..i], 2538 | .chunk = chunk.stdLongYear, 2539 | .suffix = layout[i + 4 ..], 2540 | .args_shift = null, 2541 | }; 2542 | } 2543 | return chunkResult{ 2544 | .prefix = layout[0..i], 2545 | .chunk = chunk.stdDay, 2546 | .suffix = layout[i + 1 ..], 2547 | .args_shift = null, 2548 | }; 2549 | }, 2550 | '_' => { // _2, _2006, __2 2551 | if (layout.len >= i + 4 and layout[i + 1] == '2') { 2552 | //_2006 is really a literal _, followed by stdLongYear 2553 | if (layout.len >= i + 5 and mem.eql(u8, layout[i + 1 .. i + 5], "2006")) { 2554 | return chunkResult{ 2555 | .prefix = layout[0..i], 2556 | .chunk = chunk.stdLongYear, 2557 | .suffix = layout[i + 5 ..], 2558 | .args_shift = null, 2559 | }; 2560 | } 2561 | return chunkResult{ 2562 | .prefix = layout[0..i], 2563 | .chunk = chunk.stdUnderDay, 2564 | .suffix = layout[i + 2 ..], 2565 | .args_shift = null, 2566 | }; 2567 | } 2568 | if (layout.len >= i + 3 and layout[i + 1] == '_' and layout[i + 2] == '2') { 2569 | return chunkResult{ 2570 | .prefix = layout[0..i], 2571 | .chunk = .stdUnderYearDay, 2572 | .suffix = layout[i + 3 ..], 2573 | .args_shift = null, 2574 | }; 2575 | } 2576 | }, 2577 | '3' => { 2578 | return chunkResult{ 2579 | .prefix = layout[0..i], 2580 | .chunk = chunk.stdHour12, 2581 | .suffix = layout[i + 1 ..], 2582 | .args_shift = null, 2583 | }; 2584 | }, 2585 | '4' => { 2586 | return chunkResult{ 2587 | .prefix = layout[0..i], 2588 | .chunk = chunk.stdMinute, 2589 | .suffix = layout[i + 1 ..], 2590 | .args_shift = null, 2591 | }; 2592 | }, 2593 | '5' => { 2594 | return chunkResult{ 2595 | .prefix = layout[0..i], 2596 | .chunk = chunk.stdSecond, 2597 | .suffix = layout[i + 1 ..], 2598 | .args_shift = null, 2599 | }; 2600 | }, 2601 | 'P' => { // PM 2602 | if (layout.len >= i + 2 and layout[i + 1] == 'M') { 2603 | return chunkResult{ 2604 | .prefix = layout[0..i], 2605 | .chunk = chunk.stdPM, 2606 | .suffix = layout[i + 2 ..], 2607 | .args_shift = null, 2608 | }; 2609 | } 2610 | }, 2611 | 'p' => { // pm 2612 | if (layout.len >= i + 2 and layout[i + 1] == 'm') { 2613 | return chunkResult{ 2614 | .prefix = layout[0..i], 2615 | .chunk = chunk.stdpm, 2616 | .suffix = layout[i + 2 ..], 2617 | .args_shift = null, 2618 | }; 2619 | } 2620 | }, 2621 | '-' => { 2622 | if (layout.len >= i + 7 and mem.eql(u8, layout[i .. i + 7], "-070000")) { 2623 | return chunkResult{ 2624 | .prefix = layout[0..i], 2625 | .chunk = chunk.stdNumSecondsTz, 2626 | .suffix = layout[i + 7 ..], 2627 | .args_shift = null, 2628 | }; 2629 | } 2630 | if (layout.len >= i + 9 and mem.eql(u8, layout[i .. i + 9], "-07:00:00")) { 2631 | return chunkResult{ 2632 | .prefix = layout[0..i], 2633 | .chunk = chunk.stdNumColonSecondsTZ, 2634 | .suffix = layout[i + 9 ..], 2635 | .args_shift = null, 2636 | }; 2637 | } 2638 | if (layout.len >= i + 5 and mem.eql(u8, layout[i .. i + 5], "-0700")) { 2639 | return chunkResult{ 2640 | .prefix = layout[0..i], 2641 | .chunk = chunk.stdNumTZ, 2642 | .suffix = layout[i + 5 ..], 2643 | .args_shift = null, 2644 | }; 2645 | } 2646 | if (layout.len >= i + 6 and mem.eql(u8, layout[i .. i + 6], "-07:00")) { 2647 | return chunkResult{ 2648 | .prefix = layout[0..i], 2649 | .chunk = chunk.stdNumColonTZ, 2650 | .suffix = layout[i + 6 ..], 2651 | .args_shift = null, 2652 | }; 2653 | } 2654 | if (layout.len >= i + 3 and mem.eql(u8, layout[i .. i + 3], "-07")) { 2655 | return chunkResult{ 2656 | .prefix = layout[0..i], 2657 | .chunk = chunk.stdNumShortTZ, 2658 | .suffix = layout[i + 3 ..], 2659 | .args_shift = null, 2660 | }; 2661 | } 2662 | }, 2663 | 'Z' => { // Z070000, Z07:00:00, Z0700, Z07:00, 2664 | if (layout.len >= i + 7 and mem.eql(u8, layout[i .. i + 7], "Z070000")) { 2665 | return chunkResult{ 2666 | .prefix = layout[0..i], 2667 | .chunk = chunk.stdISO8601SecondsTZ, 2668 | .suffix = layout[i + 7 ..], 2669 | .args_shift = null, 2670 | }; 2671 | } 2672 | if (layout.len >= i + 9 and mem.eql(u8, layout[i .. i + 9], "Z07:00:00")) { 2673 | return chunkResult{ 2674 | .prefix = layout[0..i], 2675 | .chunk = chunk.stdISO8601ColonSecondsTZ, 2676 | .suffix = layout[i + 9 ..], 2677 | .args_shift = null, 2678 | }; 2679 | } 2680 | if (layout.len >= i + 5 and mem.eql(u8, layout[i .. i + 5], "Z0700")) { 2681 | return chunkResult{ 2682 | .prefix = layout[0..i], 2683 | .chunk = chunk.stdISO8601TZ, 2684 | .suffix = layout[i + 5 ..], 2685 | .args_shift = null, 2686 | }; 2687 | } 2688 | if (layout.len >= i + 6 and mem.eql(u8, layout[i .. i + 6], "Z07:00")) { 2689 | return chunkResult{ 2690 | .prefix = layout[0..i], 2691 | .chunk = chunk.stdISO8601ColonTZ, 2692 | .suffix = layout[i + 6 ..], 2693 | .args_shift = null, 2694 | }; 2695 | } 2696 | if (layout.len >= i + 3 and mem.eql(u8, layout[i .. i + 3], "Z07")) { 2697 | return chunkResult{ 2698 | .prefix = layout[0..i], 2699 | .chunk = chunk.stdISO8601ShortTZ, 2700 | .suffix = layout[i + 6 ..], 2701 | .args_shift = null, 2702 | }; 2703 | } 2704 | }, 2705 | '.' => { // .000 or .999 - repeated digits for fractional seconds. 2706 | if (i + 1 < layout.len and (layout[i + 1] == '0' or layout[i + 1] == '9')) { 2707 | const ch = layout[i + 1]; 2708 | var j = i + 1; 2709 | while (j < layout.len and layout[j] == ch) : (j += 1) {} 2710 | if (!isDigit(layout, j)) { 2711 | var st = chunk.stdFracSecond0; 2712 | if (layout[i + 1] == '9') { 2713 | st = chunk.stdFracSecond9; 2714 | } 2715 | return chunkResult{ 2716 | .prefix = layout[0..i], 2717 | .chunk = st, 2718 | .suffix = layout[j..], 2719 | .args_shift = j - (i + 1), 2720 | }; 2721 | } 2722 | } 2723 | }, 2724 | else => {}, 2725 | } 2726 | } 2727 | 2728 | return chunkResult{ 2729 | .prefix = layout, 2730 | .chunk = chunk.none, 2731 | .suffix = "", 2732 | .args_shift = null, 2733 | }; 2734 | } 2735 | 2736 | fn isDigit(s: []const u8, i: usize) bool { 2737 | if (s.len <= i) { 2738 | return false; 2739 | } 2740 | const c = s[i]; 2741 | return '0' <= c and c <= '9'; 2742 | } 2743 | 2744 | const long_day_names = [_][]const u8{ 2745 | "Sunday", 2746 | "Monday", 2747 | "Tuesday", 2748 | "Wednesday", 2749 | "Thursday", 2750 | "Friday", 2751 | "Saturday", 2752 | }; 2753 | 2754 | const short_day_names = [_][]const u8{ 2755 | "Sun", 2756 | "Mon", 2757 | "Tue", 2758 | "Wed", 2759 | "Thu", 2760 | "Fri", 2761 | "Sat", 2762 | }; 2763 | 2764 | const short_month_names = [_][]const u8{ 2765 | "Jan", 2766 | "Feb", 2767 | "Mar", 2768 | "Apr", 2769 | "May", 2770 | "Jun", 2771 | "Jul", 2772 | "Aug", 2773 | "Sep", 2774 | "Oct", 2775 | "Nov", 2776 | "Dec", 2777 | }; 2778 | 2779 | const long_month_names = [_][]const u8{ 2780 | "January", 2781 | "February", 2782 | "March", 2783 | "April", 2784 | "May", 2785 | "June", 2786 | "July", 2787 | "August", 2788 | "September", 2789 | "October", 2790 | "November", 2791 | "December", 2792 | }; 2793 | 2794 | // match reports whether s1 and s2 match ignoring case. 2795 | // It is assumed s1 and s2 are the same length. 2796 | 2797 | fn match(s1: []const u8, s2: []const u8) bool { 2798 | if (s1.len != s2.len) { 2799 | return false; 2800 | } 2801 | var i: usize = 0; 2802 | while (i < s1.len) : (i += 1) { 2803 | var c1 = s1[i]; 2804 | var c2 = s2[i]; 2805 | if (c1 != c2) { 2806 | c1 |= ('a' - 'A'); 2807 | c2 |= ('a' - 'A'); 2808 | if (c1 != c2 or c1 < 'a' or c1 > 'z') { 2809 | return false; 2810 | } 2811 | } 2812 | } 2813 | return true; 2814 | } 2815 | 2816 | fn lookup(tab: []const []const u8, val: []const u8) !usize { 2817 | for (tab) |v, i| { 2818 | if (val.len >= v.len and match(val[0..v.len], v)) { 2819 | return i; 2820 | } 2821 | } 2822 | return error.BadValue; 2823 | } 2824 | 2825 | const Number = struct { 2826 | value: isize, 2827 | string: []const u8, 2828 | }; 2829 | 2830 | // getnum parses s[0:1] or s[0:2] (fixed forces s[0:2]) 2831 | // as a decimal integer and returns the integer and the 2832 | // remainder of the string. 2833 | fn getNum(s: []const u8, fixed: bool) !Number { 2834 | if (!isDigit(s, 0)) { 2835 | return error.BadData; 2836 | } 2837 | if (!isDigit(s, 1)) { 2838 | if (fixed) { 2839 | return error.BadData; 2840 | } 2841 | return Number{ 2842 | .value = @intCast(isize, s[0]) - '0', 2843 | .string = s[1..], 2844 | }; 2845 | } 2846 | const n = (@intCast(isize, s[0]) - '0') * 10 + (@intCast(isize, s[1]) - '0'); 2847 | return Number{ 2848 | .value = n, 2849 | .string = s[1..], 2850 | }; 2851 | } 2852 | 2853 | // getnum3 parses s[0:1], s[0:2], or s[0:3] (fixed forces s[0:3]) 2854 | // as a decimal integer and returns the integer and the remainder 2855 | // of the string. 2856 | fn getNum3(s: []const u8, fixed: bool) !Number { 2857 | var n: isize = 0; 2858 | var i: usize = 0; 2859 | while (i < 3 and isDigit(s, i)) : (i += 1) { 2860 | n = n * 10 + @intCast(isize, s[i] - '0'); 2861 | } 2862 | if (i == 0 or fixed and i != 3) { 2863 | return error.BadData; 2864 | } 2865 | return Number{ 2866 | .value = n, 2867 | .string = s[i..], 2868 | }; 2869 | } 2870 | 2871 | fn parseNanoseconds(value: []const u8, nbytes: usize) !isize { 2872 | if (value[0] != '.') { 2873 | return error.BadData; 2874 | } 2875 | var ns = try std.fmt.parseInt(isize, value[1..nbytes], 10); 2876 | const nf = @intToFloat(f64, ns); 2877 | if (nf < 0 or 1e9 <= nf) { 2878 | return error.BadFractionalRange; 2879 | } 2880 | const scale_digits = 10 - nbytes; 2881 | var i: usize = 0; 2882 | while (i < scale_digits) : (i += 1) { 2883 | ns *= 10; 2884 | } 2885 | return ns; 2886 | } 2887 | 2888 | const Fraction = struct { 2889 | x: 164, 2890 | scale: f64, 2891 | rem: []const u8, 2892 | }; 2893 | 2894 | fn leadingFraction(s: []const u8) !Fraction { 2895 | var i: usize = 0; 2896 | var scale: f64 = 1; 2897 | var overflow = false; 2898 | while (i < s.len) : (i += 1) { 2899 | //TODO(gernest): finish leadingFraction 2900 | } 2901 | } 2902 | -------------------------------------------------------------------------------- /src/time_test.zig: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // Copyright 2018 Geofrey Ernest MIT LICENSE 6 | 7 | const std = @import("std"); 8 | const time = @import("time.zig"); 9 | 10 | const April = time.Month.April; 11 | const December = time.Month.December; 12 | const January = time.Month.January; 13 | const Location = time.Location; 14 | const Monday = time.Weekday.Monday; 15 | const Saturday = time.Weekday.Saturday; 16 | const September = time.Month.September; 17 | const Sunday = time.Weekday.Sunday; 18 | const Thursday = time.Weekday.Thursday; 19 | const Wednesday = time.Weekday.Wednesday; 20 | const math = std.math; 21 | const mem = std.mem; 22 | const testing = std.testing; 23 | 24 | const failed_test = error.Failed; 25 | 26 | const parsedTime = struct { 27 | year: isize, 28 | month: time.Month, 29 | day: isize, 30 | hour: isize, 31 | minute: isize, 32 | second: isize, 33 | nanosecond: isize, 34 | weekday: time.Weekday, 35 | zone_offset: isize, 36 | zone: []const u8, 37 | 38 | fn init(year: isize, month: time.Month, day: isize, hour: isize, minute: isize, second: isize, nanosecond: isize, weekday: time.Weekday, zone_offset: isize, zone: []const u8) parsedTime { 39 | return parsedTime{ 40 | .year = year, 41 | .month = month, 42 | .day = day, 43 | .hour = hour, 44 | .minute = minute, 45 | .second = second, 46 | .nanosecond = nanosecond, 47 | .weekday = weekday, 48 | .zone_offset = zone_offset, 49 | .zone = zone, 50 | }; 51 | } 52 | }; 53 | 54 | const TimeTest = struct { 55 | seconds: i64, 56 | golden: parsedTime, 57 | }; 58 | 59 | const utc_tests = [_]TimeTest{ 60 | TimeTest{ .seconds = 0, .golden = parsedTime.init(1970, January, 1, 0, 0, 0, 0, Thursday, 0, "UTC") }, 61 | TimeTest{ .seconds = 1221681866, .golden = parsedTime.init(2008, September, 17, 20, 4, 26, 0, Wednesday, 0, "UTC") }, 62 | TimeTest{ .seconds = -1221681866, .golden = parsedTime.init(1931, April, 16, 3, 55, 34, 0, Thursday, 0, "UTC") }, 63 | TimeTest{ .seconds = -11644473600, .golden = parsedTime.init(1601, January, 1, 0, 0, 0, 0, Monday, 0, "UTC") }, 64 | TimeTest{ .seconds = 599529660, .golden = parsedTime.init(1988, December, 31, 0, 1, 0, 0, Saturday, 0, "UTC") }, 65 | TimeTest{ .seconds = 978220860, .golden = parsedTime.init(2000, December, 31, 0, 1, 0, 0, Sunday, 0, "UTC") }, 66 | }; 67 | 68 | const nano_tests = [_]TimeTest{ 69 | TimeTest{ .seconds = 0, .golden = parsedTime.init(1970, January, 1, 0, 0, 0, 1e8, Thursday, 0, "UTC") }, 70 | TimeTest{ .seconds = 1221681866, .golden = parsedTime.init(2008, September, 17, 20, 4, 26, 2e8, Wednesday, 0, "UTC") }, 71 | }; 72 | 73 | const local_tests = [_]TimeTest{ 74 | TimeTest{ .seconds = 0, .golden = parsedTime.init(1969, December, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST") }, 75 | TimeTest{ .seconds = 1221681866, .golden = parsedTime.init(2008, September, 17, 13, 4, 26, 0, Wednesday, -7 * 60 * 60, "PDT") }, 76 | }; 77 | 78 | const nano_local_tests = [_]TimeTest{ 79 | TimeTest{ .seconds = 0, .golden = parsedTime.init(1969, December, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST") }, 80 | TimeTest{ .seconds = 1221681866, .golden = parsedTime.init(2008, September, 17, 13, 4, 26, 3e8, Wednesday, -7 * 60 * 60, "PDT") }, 81 | }; 82 | 83 | fn same(t: time.Time, u: *parsedTime) bool { 84 | const date = t.date(); 85 | const clock = t.clock(); 86 | const zone = t.zone(); 87 | const check = date.year != u.year or @enumToInt(date.month) != @enumToInt(u.month) or 88 | date.day != u.day or clock.hour != u.hour or clock.min != u.minute or clock.sec != u.second or 89 | !mem.eql(u8, zone.name, u.zone) or zone.offset != u.zone_offset; 90 | if (check) { 91 | return false; 92 | } 93 | return t.year() == u.year and 94 | @enumToInt(t.month()) == @enumToInt(u.month) and 95 | t.day() == u.day and 96 | t.hour() == u.hour and 97 | t.minute() == u.minute and 98 | t.second() == u.second and 99 | t.nanosecond() == u.nanosecond and 100 | @enumToInt(t.weekday()) == @enumToInt(u.weekday); 101 | } 102 | 103 | test "TestSecondsToUTC" { 104 | // try skip(); 105 | for (utc_tests) |ts| { 106 | var tm = time.unix(ts.seconds, 0, &Location.utc_local); 107 | const ns = tm.unix(); 108 | testing.expectEqual(ns, ts.seconds); 109 | var golden = ts.golden; 110 | testing.expect(same(tm, &golden)); 111 | } 112 | } 113 | 114 | test "TestNanosecondsToUTC" { 115 | // try skip(); 116 | for (nano_tests) |tv| { 117 | var golden = tv.golden; 118 | const nsec = tv.seconds * @as(i64, 1e9) + @intCast(i64, golden.nanosecond); 119 | var tm = time.unix(0, nsec, &Location.utc_local); 120 | const new_nsec = tm.unix() * @as(i64, 1e9) + @intCast(i64, tm.nanosecond()); 121 | testing.expectEqual(new_nsec, nsec); 122 | testing.expect(same(tm, &golden)); 123 | } 124 | } 125 | 126 | test "TestSecondsToLocalTime" { 127 | // try skip(); 128 | var loc = try Location.load("US/Pacific"); 129 | defer loc.deinit(); 130 | for (local_tests) |tv| { 131 | var golden = tv.golden; 132 | const sec = tv.seconds; 133 | var tm = time.unix(sec, 0, &loc); 134 | const new_sec = tm.unix(); 135 | testing.expectEqual(new_sec, sec); 136 | testing.expect(same(tm, &golden)); 137 | } 138 | } 139 | 140 | test "TestNanosecondsToUTC" { 141 | // try skip(); 142 | var loc = try Location.load("US/Pacific"); 143 | defer loc.deinit(); 144 | for (nano_local_tests) |tv| { 145 | var golden = tv.golden; 146 | const nsec = tv.seconds * @as(i64, 1e9) + @intCast(i64, golden.nanosecond); 147 | var tm = time.unix(0, nsec, &loc); 148 | const new_nsec = tm.unix() * @as(i64, 1e9) + @intCast(i64, tm.nanosecond()); 149 | testing.expectEqual(new_nsec, nsec); 150 | testing.expect(same(tm, &golden)); 151 | } 152 | } 153 | 154 | const formatTest = struct { 155 | name: []const u8, 156 | format: []const u8, 157 | result: []const u8, 158 | 159 | fn init(name: []const u8, format: []const u8, result: []const u8) formatTest { 160 | return formatTest{ .name = name, .format = format, .result = result }; 161 | } 162 | }; 163 | 164 | const format_tests = [_]formatTest{ 165 | formatTest.init("ANSIC", time.ANSIC, "Wed Feb 4 21:00:57 2009"), 166 | formatTest.init("UnixDate", time.UnixDate, "Wed Feb 4 21:00:57 PST 2009"), 167 | formatTest.init("RubyDate", time.RubyDate, "Wed Feb 04 21:00:57 -0800 2009"), 168 | formatTest.init("RFC822", time.RFC822, "04 Feb 09 21:00 PST"), 169 | formatTest.init("RFC850", time.RFC850, "Wednesday, 04-Feb-09 21:00:57 PST"), 170 | formatTest.init("RFC1123", time.RFC1123, "Wed, 04 Feb 2009 21:00:57 PST"), 171 | formatTest.init("RFC1123Z", time.RFC1123Z, "Wed, 04 Feb 2009 21:00:57 -0800"), 172 | formatTest.init("RFC3339", time.RFC3339, "2009-02-04T21:00:57-08:00"), 173 | formatTest.init("RFC3339Nano", time.RFC3339Nano, "2009-02-04T21:00:57.0123456-08:00"), 174 | formatTest.init("Kitchen", time.Kitchen, "9:00PM"), 175 | formatTest.init("am/pm", "3pm", "9pm"), 176 | formatTest.init("AM/PM", "3PM", "9PM"), 177 | formatTest.init("two-digit year", "06 01 02", "09 02 04"), 178 | // Three-letter months and days must not be followed by lower-case letter. 179 | formatTest.init("Janet", "Hi Janet, the Month is January", "Hi Janet, the Month is February"), 180 | // Time stamps, Fractional seconds. 181 | formatTest.init("Stamp", time.Stamp, "Feb 4 21:00:57"), 182 | formatTest.init("StampMilli", time.StampMilli, "Feb 4 21:00:57.012"), 183 | formatTest.init("StampMicro", time.StampMicro, "Feb 4 21:00:57.012345"), 184 | formatTest.init("StampNano", time.StampNano, "Feb 4 21:00:57.012345600"), 185 | }; 186 | 187 | test "TestFormat" { 188 | // try skip(); 189 | var tz = try Location.load("US/Pacific"); 190 | defer tz.deinit(); 191 | var ts = time.unix(0, 1233810057012345600, &tz); 192 | var buf = std.ArrayList(u8).init(std.testing.allocator); 193 | defer buf.deinit(); 194 | for (format_tests) |value| { 195 | try buf.resize(0); 196 | try ts.formatBuffer(buf.writer(), value.format); 197 | testing.expect(std.mem.eql(u8, buf.items, value.result)); 198 | } 199 | } 200 | 201 | test "calendar" { 202 | // try skip(); 203 | time.Time.calendar(); 204 | } 205 | 206 | fn skip() !void { 207 | return error.SkipZigTest; 208 | } 209 | 210 | test "TestFormatSingleDigits" { 211 | // try skip(); 212 | var buf = std.ArrayList(u8).init(std.testing.allocator); 213 | defer buf.deinit(); 214 | 215 | var tt = time.date(2001, 2, 3, 4, 5, 6, 700000000, &Location.utc_local); 216 | const ts = formatTest.init("single digit format", "3:4:5", "4:5:6"); 217 | 218 | try tt.formatBuffer(buf.writer(), ts.format); 219 | testing.expect(std.mem.eql(u8, buf.items, ts.result)); 220 | 221 | const want = "2001-02-03 04:05:06.7 +0000 UTC"; 222 | try buf.resize(0); 223 | try buf.writer().print("{}", .{tt}); 224 | testing.expect(std.mem.eql(u8, buf.items, want)); 225 | } 226 | 227 | test "TestFormatShortYear" { 228 | // try skip(); 229 | var buf = std.ArrayList(u8).init(std.testing.allocator); 230 | defer buf.deinit(); 231 | 232 | var want = std.ArrayList(u8).init(std.testing.allocator); 233 | defer want.deinit(); 234 | 235 | const years = [_]isize{ 236 | -100001, -100000, -99999, 237 | -10001, -10000, -9999, 238 | -1001, -1000, -999, 239 | -101, -100, -99, 240 | -11, -10, -9, 241 | -1, 0, 1, 242 | 9, 10, 11, 243 | 99, 100, 101, 244 | 999, 1000, 1001, 245 | 9999, 10000, 10001, 246 | 99999, 100000, 100001, 247 | }; 248 | for (years) |y| { 249 | const m = @enumToInt(January); 250 | const x = @intCast(isize, m); 251 | var tt = time.date(y, x, 1, 0, 0, 0, 0, &Location.utc_local); 252 | try buf.resize(0); 253 | try tt.formatBuffer(buf.writer(), "2006.01.02"); 254 | try want.resize(0); 255 | const day: usize = 1; 256 | const month: usize = 1; 257 | if (y < 0) { 258 | try want.writer().print("-{d:0>4}.{d:0>2}.{d:0>2}", .{ math.absCast(y), month, day }); 259 | } else { 260 | try want.writer().print("{d:0>4}.{d:0>2}.{d:0>2}", .{ math.absCast(y), month, day }); 261 | } 262 | if (!std.mem.eql(u8, buf.items, want.items)) { 263 | std.debug.warn("case: '{}' expected '{}' got '{}'\n", .{ y, want.items, buf.items }); 264 | } 265 | } 266 | } 267 | 268 | test "TestNextStdChunk" { 269 | const next_std_chunk_tests = [_][]const u8{ 270 | "(2006)-(01)-(02)T(15):(04):(05)(Z07:00)", 271 | "(2006)-(01)-(02) (002) (15):(04):(05)", 272 | "(2006)-(01) (002) (15):(04):(05)", 273 | "(2006)-(002) (15):(04):(05)", 274 | "(2006)(002)(01) (15):(04):(05)", 275 | "(2006)(002)(04) (15):(04):(05)", 276 | }; 277 | var buf = std.ArrayList([]const u8).init(std.testing.allocator); 278 | defer buf.deinit(); 279 | for (next_std_chunk_tests) |marked, i| { 280 | try markChunk(&buf, marked); 281 | // FIXME: fix check, buf.items doesn't work 282 | // testing.expect(std.mem.eql([]const u8, buf.items, bufM.items)); 283 | } 284 | } 285 | 286 | var tmp: [39]u8 = undefined; 287 | 288 | fn removeParen(format: []const u8) []const u8 { 289 | var s = tmp[0..format.len]; 290 | var i: usize = 0; 291 | var n = i; 292 | while (i < format.len) : (i += 1) { 293 | if (format[i] == '(' or format[i] == ')') { 294 | continue; 295 | } 296 | s[n] = format[i]; 297 | n += 1; 298 | } 299 | return s[0..n]; 300 | } 301 | 302 | fn markChunk(buf: *std.ArrayList([]const u8), format: []const u8) !void { 303 | try buf.resize(0); 304 | var s = removeParen(format); 305 | 306 | while (s.len > 0) { 307 | var ch = time.nextStdChunk(s); 308 | try buf.append(ch.prefix); 309 | if (ch.chunk != .none) { 310 | try buf.append("("); 311 | try buf.append(chunName(ch.chunk)); 312 | try buf.append(")"); 313 | } 314 | s = ch.suffix; 315 | } 316 | } 317 | 318 | fn chunName(ch: time.chunk) []const u8 { 319 | return switch (ch) { 320 | .none => "", 321 | .stdLongMonth => "January", 322 | .stdMonth => "Jan", 323 | .stdNumMonth => "1", 324 | .stdZeroMonth => "01", 325 | .stdLongWeekDay => "Monday", 326 | .stdWeekDay => "Mon", 327 | .stdDay => "2", 328 | .stdUnderDay => "_2", 329 | .stdZeroDay => "02", 330 | .stdUnderYearDay => "__2", 331 | .stdZeroYearDay => "002", 332 | .stdHour => "15", 333 | .stdHour12 => "3", 334 | .stdZeroHour12 => "03", 335 | .stdMinute => "4", 336 | .stdZeroMinute => "04", 337 | .stdSecond => "5", 338 | .stdZeroSecond => "05", 339 | .stdLongYear => "2006", 340 | .stdYear => "06", 341 | .stdPM => "PM", 342 | .stdpm => "pm", 343 | .stdTZ => "MST", 344 | .stdISO8601TZ => "Z0700", 345 | .stdISO8601SecondsTZ => "Z070000", 346 | .stdISO8601ShortTZ => "Z07", 347 | .stdISO8601ColonTZ => "Z07:00", 348 | .stdISO8601ColonSecondsTZ => "Z07:00:00", 349 | .stdNumTZ => "-0700", 350 | .stdNumSecondsTz => "-070000", 351 | .stdNumShortTZ => "-07", 352 | .stdNumColonTZ => "-07:00", 353 | .stdNumColonSecondsTZ => "-07:00:00", 354 | else => "unknown", 355 | }; 356 | } 357 | -------------------------------------------------------------------------------- /z.mod: -------------------------------------------------------------------------------- 1 | module github.com/gernest/time 2 | 3 | exports time 4 | --------------------------------------------------------------------------------