aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChloe Kudryavtsev <code@toast.bunkerlabs.net>2023-06-18 10:19:48 +0200
committerChloe Kudryavtsev <code@toast.bunkerlabs.net>2023-06-18 10:19:48 +0200
commit2efbd2af7826b972c7a230efbeb0fc543b81ca1d (patch)
treee931e0687191dc986a66feba4543792a38460ff5
parentallow constructing your own date/tm values (diff)
prepare for 1.0.0 release and pkgs.janet inclusion
-rw-r--r--CONTRIBUTING.md10
-rw-r--r--README.md110
-rw-r--r--TECH.md56
3 files changed, 144 insertions, 32 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..3d2a05e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,10 @@
+# Guidelines for Contributing to Date
+Thanks for taking the time to contribute!
+
+This repository is relatively feature-complete.
+If you can figure out a non-dirty way to add NetBSD style `_z`-terminated
+functions, that would be an accepted feature.
+Besides that, no further features will be accepted.
+
+On the other hand, additional tests (especially if they point out errors in the
+codebase), example files, and bug reports are highly welcome.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1456efc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,110 @@
+# Date
+Complete ISO C99 date and time support for Janet.
+
+It contains the following components:
+* `date/native`: a low level C interface to ISO C99 facilities (plus `timegm`).
+* `date`: a safer janet wrapper around `date/native`.
+Feel free to use either of these.
+
+## Installation
+`jpm install https://github.com/cosmictoast/janet-date.git`
+
+You can add it to your `project.janet` dependency list like so:
+```janet
+(declare-project
+ :name "my-project"
+ :dependencies
+ [{:repo "https://github.com/cosmictoast/janet-date.git"
+ :tag "v1.0.0"}]
+ # ...
+```
+
+## Caveats
+On most platforms (excluding windows), the native time.h impementation is
+replaced by the [IANA's tzcode](https://data.iana.org/time-zones/tz-link.html).
+It is shipped with this repository as a submodule.
+However, if your code has different behavior than native libc code,
+this is likely why.
+
+This is often desirable, since some platforms (such as macos) have incorrect
+datetime handling in the libc.
+
+In addition, this library ships the non-standard `timegm` function.
+It is provided natively in windows and in IANA's tzcode, but not on many other
+platforms.
+
+## Examples
+Note that these examples exclusively show off the janet bindings,
+for native API, please see the test suite and `src/` directory.
+
+```janet
+# there are two types of objects, date/time and date/tm
+# date/time represents a specific moment in time, free of context
+# you can create one that represents the current moment using date/time
+(def now (date/time))
+
+# the other object type is date/tm
+# date/tm represents a specific calendar time
+# this includes timezone information, year, day of year, and so on
+# you can create a date/tm object yourself, or from a date/time object
+(def calendar-now-utc (:gmtime now))
+(def calendar-now-local (:localtime now))
+# other fields are initialized to their default values, usually zero
+(def calendar-unix-epoch (date/utc {:year 1970}))
+# you can also use date/local to construct a date/time object
+# from a local calendar time
+
+# you can get a date/time object from a date/tm object
+# however, since date/time does not have the concept of timezones,
+# you have to specify whether the date/tm object is in UTC or localtime
+(def now-from-utc (:timegm calendar-now-utc)) # timegm is for GMT (UTC)
+(def now-from-local (:mktime calendar-now-local)) # mktime is for localtime
+
+# you can compare date/time objects
+(assert (= now-from-utc now-from-local))
+(assert (= now-from-utc now))
+
+# you can compare date/tm objects of the same type (local or UTC)
+# however, such comparisons are not very robust
+
+# you can print either date/time or date/tm objects in various formats
+(print (date/format now)) # default format
+(print (date/format now "%c")) # equivalent
+(print (date/format now :html)) # standard WG formatting
+(print (date/format now :html true)) # print as UTC instead of localtime
+
+(print (:strftime calendar-now-utc :html)) # takes same format argument
+# the :strftime message is lower level, you *must* specify a format
+# (:strftime calendar-now-utc) # error: arity mismatch
+
+# you may mutate date/tm objects
+# when you call mktime or gmtime, you are normalizing the object
+# you can also normalize it in-place
+(put calendar-now-utc :sec 120) # out of range
+(:timegm! calendar-now-utc)
+# now in range
+(assert > (:timegm calendar-now-utc) now)
+
+# however, it is recommended to work with date/time objects
+# you can mutate those using the update-time function suite
+(def in-a-bit
+ (update-time now
+ # you can set values directly
+ :hour 30
+ # or you can pass an x -> x function like to `update`
+ :sec inc))
+(assert (> in-a-bit now))
+
+# update-time is normalized to strip unsafe keys and takes keywords
+# you may also use the less safe update-time* to pass a raw dict
+(def in-a-bit* (update-time* now {:hour 30 :sec inc}))
+(assert (= in-a-bit in-a-bit*))
+
+# finally, there's a for-internal-use update-time!
+# it takes a function that will apply transformations
+# to the intermediate date/tm object
+# it's not recommended for end-users
+
+# finally, you can pass nil to the update functions, in which case
+# (date/time) will be used, letting you skip a step
+```
diff --git a/TECH.md b/TECH.md
index 6919db2..7c063d2 100644
--- a/TECH.md
+++ b/TECH.md
@@ -1,37 +1,29 @@
-# Time is a mess
-ISO C99 is fairly restrictive, while time in the wild is *wild*.
+# Reading Guide
+A general discussion on time, and why there isn't enough of it to discuss it.
-> The mktime function converts the broken-down time, expressed as local time, in the
-> structure pointed to by timeptr into a calendar time value with the same encoding as
-> that of the values returned by the time function. The original values of the tm_wday
-> and tm_yday components of the structure are ignored, and the original values of the
-> other components are not restricted to the ranges indicated above. On successful
-> completion, the values of the tm_wday and tm_yday components of the structure are
-> set appropriately, and the other components are set to represent the specified calendar
-> time, but with their values forced to the ranges indicated above; the final value of
-> tm_mday is not set until tm_mon and tm_year are determined.
+ISO C99 is fairly restrictive, intending to only really work with localtime.
+This assumption goes fairly deep, to the point of the macos libc (for example)
+presuming that all strftime calls are against localtime.
-This means that `mktime` must operate on `localtime` output.
-The implication (which is accurate) implies that `gmtime(t) == gmtime(mktime(localtime(t)))`.
+Thankfully, IANA tzcode is more reasonable, as it handles things correctly
+and provides timegm (which means you can operate in UTC until final display).
-As such, the source of truth is `time_t`, which is UTC-only.
-When we want to modify a `time_t`, we want to use localtime to perform the modification.
+On the overall, the package wraps around all ISO C99 functions that are not
+marked for future deprecation, plus timegm.
+The native wrapping is optimized for working primarily with time_t, only really
+using struct tm for final output and intermediate representations.
+This is how it's recommended to use the library as well.
-`strftime` actually can handle a `gmtime`, but various platforms are buggy.
-For example, macOS `%z %Z` will correctly report UTC but will report the localtime offset.
-On Linux, the same format will correctly report an offset of exactly `+0000`, but claim to be in `GMT`.
-As such, `%z` and `%Z` cannot be counted on.
+The primary thing missing is the ability to represent an arbitrary-timezone.
+For example, representing EST while localtime is CET.
+Unfortunately, ISO C99 simply does not provide a way of doing this.
+IANA tzcode does have ways to do this, namely via the NetBSD-inspired
+`_z`-terminated functions.
+Unfortunately, making those usable without completely messing up the codebase
+is non-trivial.
-Ok, `%z` and `%Z` don't work correctly because `struct tm` is often extended.
-When I convert to and from a dictionary, I lose that information.
-
-Ok I've confirmed that MacOS has bugs in the libc and have now reported them.
-
-I've pulled in eggert tz. This helps a lot, but some details are still unfortunate (due to the spec).
-The spec specifies quite explicitly that mktime operates on localtime output, which means I'll most
-likely have to wrap all time_t manipulations in a localtime.
-Let's start with the native api, and then the janet wrapper will purely speak time_t, I think?
-I technically also have automatic "free" access to non-ISOC99 things, since they're simply
-there to be used. However, I'd need to find a way to expose some libtz internals (as they can
-redefine time_t, define timezone_t, etc) to get that working.
-As such, I don't think I'll be doing that.
+Anyway, if nothing else, something really fun that came out of this project
+is that I got to report a libc bug to apple, so that's cool.
+It's been almost a month and they don't seem to have acknowledged it, so if you
+have to work with time, I would recommend just using IANA tzcode.
+It's literally standard.