You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.
This document is in-progress; the design of the library is subject to change at any time.
Goal
This document outlines the design of the Time namespace in the HSL snip. The goal of this namespace is to provide an ergonomic and predictable API for operating on dates/times that helps developers fall into the pit of success.
Absolute Time
Abstractions
An absolute time is a point on an absolute timeline, represented by a single timestamp in nanoseconds. Intervals between timestamps are a single value in nanoseconds. There are two types of absolute times in the HSL:
Monotonic time: the number of nanoseconds elapsed since some OS-defined starting point. This is useful for doing local computations, such as timing a function call.
Unix time: the number of nanoseconds elapsed since 00:00:00 1 January 1970 (UTC). This is useful for doing non-local computations, e.g. using data provided by another service.
In the HSL, an absolute time is represented by the Time\Timestamp opaque type alias, which has a Time\Clock type parameter to distinguish between monotonic and Unix time. An interval is represented as the Time\Interval opaque type alias. Note that intervals and timestamps may be negative.
type Nanoseconds = int; // Purely for documentation purposes.
newtype Clock = mixed;
newtype Mono as Clock = Clock;
newtype Unix as Clock = Clock;
newtype Timestamp<Tc as Clock> = (Tc, Nanoseconds);
newtype Interval as Nanoseconds = Nanoseconds;
const Mono CLOCK_MONO = 1;
const Unix CLOCK_UNIX = 2;
These timestamps help the developer fall into the pit of success because:
Using nanoseconds allows for high-precision timestamps without potential rounding errors (e.g. if we were to use seconds and floating-point numbers).
They enforce that any arithmetic on absolute times always uses the same unit.
They enforce that it is impossible (or at least, fairly difficult) to mix up timestamps and intervals during arithmetic.
API
/**
* Returns a Timestamp representing the current
* monotonic time, i.e. the number of nanoseconds elapsed
* since some consistent internal point.
*/
function now(): Timestamp<Mono>;
/**
* Returns a Timestamp representing the current
* Unix time, i.e. the number of nanoseconds elapsed
* since 00:00:00 1 January 1970 (UTC).
*/
function now_unix(): Timestamp<Unix>;
/**
* Returns a Timestamp representing the given number of nanoseconds elapsed
* since 00:00:00 1 January 1970 (UTC).
*/
function timestamp_ns(
int $nanoseconds,
): Timestamp<Unix>;
/**
* Returns a Timestamp representing the given number of seconds elapsed
* since 00:00:00 1 January 1970 (UTC).
*/
function timestamp_s(
int $seconds,
): Timestamp<Unix>;
/**
* Returns an Interval representing the elapsed
* time given in nanoseconds.
*/
function interval_ns(
int $nanoseconds,
): Interval;
/**
* Returns an Interval representing the elapsed
* time given in seconds.
*/
function interval_s(
int $seconds,
): Interval;
/**
* Returns an Interval representing the time
* elapsed since the given Timestamp.
*
* - Throw Exception if time is in the future?
*/
function since<Tc as Clock>(
Timestamp<Tc> $timestamp,
): Interval;
/**
* Returns an Interval representing the time
* remaining until the given Timestamp.
*
* - Throw Exception if time is in the past?
*/
function until<Tc as Clock>(
Timestamp<Tc> $timestamp,
): Interval;
/**
* Returns an Interval representing the time
* elapsed between the given Timestamps.
*/
function between<Tc as Clock>(
Timestamp<Tc> $start_timestamp,
Timestamp<Tc> $end_timestamp,
): Interval;
/**
* Returns the result of adding the given
* Interval to the given Timestamp.
*/
function add<Tc as Clock>(
Timestamp<Tc> $timestamp,
Interval $interval,
): Timestamp<Tc>;
/**
* Returns the result of subtracting the given
* Interval from the given Timestamp.
*/
function subtract<Tc as Clock>(
Timestamp<Tc> $timestamp,
Interval $interval,
): Timestamp<Tc>;
/**
* Returns whether `$timestamp1` is after `$timestamp2`.
*/
function is_after<Tc as Clock>(
Timestamp<Tc> $timestamp1,
Timestamp<Tc> $timestamp2,
): bool;
/**
* Returns whether `$timestamp1` is before `$timestamp2`.
*/
function is_before<Tc as Clock>(
Timestamp<Tc> $timestamp1,
Timestamp<Tc> $timestamp2,
): bool;
/**
* Returns whether the given timestamp is between the given lower and upper
* upper bounds, exclusive.
*/
function is_between<Tc as Clock>(
Timestamp<Tc> $timestamp,
Timestamp<Tc> $lo_timestamp,
Timestamp<Tc> $hi_timestamp,
): bool;
Implementation
The majority of functions handling the monotonic time will be implemented in Hack, with the exception of Time\now(), which will call into the clock_gettime_ns() API provided by HHVM to obtain the current time in nanoseconds.
Local Time
Abstractions
Local times are different from absolute times in that they also contain time zone information, expressly for the purpose of representing times in a human-readable format. Local times are represented by an immutableTime\DateTime object, which contains a timestamp and a time zone.
Time zones in Hack are represented by the Time\TimeZone opaque type alias. To obtain a time zone, (TODO: Represent as object or as an opaque type alias to string?) There are three ways of identifying time zones:
Identifying. These take the form of a named region, e.g. America/New_York.
Offset. These take the form of an offset from UTC, e.g. +0400 or -05:00.
Aliased. PHP also supports time zone aliases like “EDT” and “PST”; but the Hack Standard Library will not support these aliases.
These abstractions help the developer fall into the pit of success because:
Making Time\DateTime immutable prevents classes of bugs where dates are inadvertently mutated away from the construction site.
Disallowing aliased time zones prevents the developer from accidentally using “PDT” when they are actually in “PST”, for example.
TODO: Similar to the re'...' string in Regex, could we have a dt'...' string to validate datetime formatting? Maybe this requires a generalization of the FormatString abstraction that we've hacked into Hack.
Construction API
TBD: This API is subject to change at any time! See design meeting notes for more information.
final class DateTime {
private function __construct(
public TimeZone $time_zone,
public int $year,
public int $month,
public int $day,
public int $hour = 0,
public int $minute = 0,
public int $seconds = 0,
public int $milliseconds = 0,
public int $microseconds = 0,
public int $nanoseconds = 0,
);
public function getTimestamp(): Timestamp<Unix>;
/**
* Returns a new DateTime constructed with the
* given Unix timestamp.
*/
public static function fromTimestamp(
TimeZone $time_zone,
Timestamp<Unix> $timestamp,
): DateTime;
public function fromLocalDateTime(
TimeZone $timezone,
LocalDateTime $datetime,
): (reason, DateTime);
public function fromLocalDateTimeX(
TimeZone $timezone,
LocalDateTime $datetime,
): DateTime;
/**
* Returns a new DateTime constructed with the
* given format string and date.
*
* - Can we restrict the types of formats the function accepts? Should we?
*/
public static function fromFormattedString(
TimeZone $time_zone,
DateTimeFormat $format,
string $formatted_date,
): DateTime;
/**
* Returns the DateTime as a string formatted per
* the given format string.
*/
public function format(
DateTimeFormat $format,
): string;
}
Transformation API
There are two ways to transform a DateTime object: setting (setting individual calendar/clock values) and shifting (i.e. adding or subtracting a time interval). Note that “transformation” in this context means returning a new immutable DateTime based on the one we're “mutating.”
Setting consists of setting individual values (e.g. year, day, hour, minute, etc.). When setting, the clock time of the resultant DateTime will be preserved to best of the library's ability. In certain situations, a resultant DateTime may not exist, such as when mutating across DST boundaries. In this case, the user must choose the proper API: one that returns the nearest valid DateTime along with a reason for its invalidity, or one that throws. The API is as follows:
public function withNanosecond(int $nanosecond): (reason, this);
public function withMicrosecond(int $microsecond): (reason, this);
public function withMillisecond(int $millisecond): (reason, this);
public function withSecond(int $second): (reason, this);
public function withMinute(int $minute): (reason, this);
public function withHour(int $hour): (reason, this);
public function withDay(int $day): (reason, this);
public function withMonth(int $month): (reason, this);
public function withYear(int $year): (reason, this);
public function withDate(int $year, int $month, int $day): (reason, this);
public function withTime(int $hour, int $minute, int $second, ...): (reason, this);
public function withNanosecondX(int $nanosecond): this;
public function withMicrosecondX(int $microsecond): this;
public function withMillisecondX(int $millisecond): this;
public function withSecondX(int $second): this;
public function withMinuteX(int $minute): this;
public function withHourX(int $hour): this;
public function withDayX(int $day): this;
public function withMonthX(int $month): this;
public function withYearX(int $year): this;
public function withDateX(int $year, int $month, int $day): this;
public function withTimeX(int $hour, int $minute, int $second, ...): (reason, this);
public function withTimezone(Timezone $timezone): this; // Updates clock timer
Shifting consists of adding or subtracting nanoseconds to/from the object's Unix timestamp. When shifting, mutations may change the clock time of the resultant DateTime. This means that each shifting API is guaranteed to return a valid DateTime, so we don't need different versions of them. Sometimes, this behavior may be undesirable, e.g. if a user shifts forward by one day on a DateTime representing the day before DST, the hour of the resultant DateTime would be different. In this case, users can use the setter API to preserve clock time. The API is as follows:
public function plusNanoseconds(int $nanoseconds): this;
public function plusMicroseconds(int $microseconds): this;
public function plusMilliseconds(int $milliseconds): this;
public function plusSeconds(int $seconds): this;
public function plusMinutes(int $minutes): this;
public function plusHours(int $hours): this;
public function plusDays(int $days): this;
public function plusWeeks(int $weeks): this;
// Omitted, because they vary in length.
// Users may choose to shift by however many days or use a set API.
// public function plusMonths(int $months): this;
// public function plusYears(int $years): this;
public function minusNanoseconds(int $nanoseconds): this;
public function minusMicroseconds(int $microseconds): this;
public function minusMilliseconds(int $milliseconds): this;
public function minusSeconds(int $seconds): this;
public function minusMinutes(int $minutes): this;
public function minusHours(int $hours): this;
public function minusDays(int $days): this;
public function minusWeeks(int $weeks): this;
// Omitted, because they vary in length. See above.
// public function minusMonths(int $months): this;
// public function minusYears(int $years): this;
// Enables "Next Tuesday at 2:30pm"-style mutations.
// Example: ->nextDay(Time\TUESDAY)->setTime(14, 30);
// TODO: DST can probably mess this up, make sure to test for it.
public function nextDay(Day $day, int $nth = 1): this;
public function nextMonth(Month $month, int $nth = 1): this;
public function prevDay(Day $day, int $nth = 1): this;
public function prevMonth(Month $month, int $nth = 1): this;
Implementation
TBD - See Design Meeting Notes for up-to-date information. We'd like to implement as much of the library in Hack as possible. Ideally, most of the Time\DateTime class could be written in Hack, and dispatch to the runtime only when formatting or transformation is needed (since we will need to use the timelib library for that).
Timezone-Agnostic Local Time
Oftentimes, developers want to work with the concept of a date/time free from the notion of a time zone. In that case, there is a LocalDateTime class, which has a similar API to that of DateTime, but does not take a time zone. It may be converted to a DateTime by adding a time zone via the toDateTime method. It will support the same format API as DateTime, minus the time zone portion.
API
final class LocalDateTime {
public function __construct(
public TimeZone $time_zone,
public int $year,
public int $month = 1,
public int $day = 1,
public int $hour = 0,
public int $minute = 0,
public int $seconds = 0,
public int $milliseconds = 0,
public int $microseconds = 0,
public int $nanoseconds = 0,
);
public function fromDateTime(
DateTime $datetime,
): this;
}
Transformation API
The LocalDateTime transformation API will be identical to that of DateTime, in that it will have shifting and setting, the only difference being the lack of uncertainty regarding time zones. However, because the user may still specify an invalid date (e.g. February 29th on a non-leap year), the setting APIs will still need two versions.
Dependencies
Because opaque type aliases are only transparent in the file in which they're defined, it may be impossible to follow the HSL convention of splitting the library up into files. We may want to consider implementing a feature that allows opaque type aliases to be transparent within an entire namespace, especially if the Time\DateTime class ends up being implemented in the runtime.
Use Cases
snip examples
Comparing Against Fields of the User's Local Time
The developer simply wants the current hour in the user's time zone. We could construct a DateTime object, and get the hour as a getter.
The developer wants to format a timestamp according to a particular timezone, and has to temporarily update global state to do so. Here, we can just construct a DateTime and call its format method.
Here, the developer wants to know the number of hours until midnight the following day. We can construct two DateTimes representing the two times, and get the period between them.
calls to \DateTime's setTimestamp or getTimestamp after a time zone has been set can result in the object's timestamp to be different than expected
changing a \DateTime's time zone can result in the object's timestamp changing; changing a time zone should NEVER change the timestamp, only the date or time fields
Internal representation of DateTime is UTC Timestamp + Timezone
Timezone is serialized as the name: “America/NY”
DateTime can be serialized as UTC timestamp (requires user to pass in a TimeZone to re-hydrate) or CalendarTime + Timezone
We must separate mono time + unix time
How to handle leap seconds? If I create an event for five years from now, will a leap second distort the time of the event?
In mono time, they don't exist
For Clock Time, it depends on the representation.
Keep using timelib for compatibility with the rest of FB
TODO:
How to be predictable when mutating across time zones or across irregular points like “spring forward” and “fall back”?
Return null?
Throw exception? If Exceptions, what Exceptions should DateTime throw?
Missing APIs like “next Tuesday at 2:00pm”, possibly generators for “every following Tuesday at 2:00pm”. What if such a DateTime doesn't exist or exists twice?
How to design an efficient “mutation” API given that the object is immutable?
Maybe a single batch function?
Maybe a builder?
If we want to be consistent with the absolute portion of the library, we should target nanosecond precision, but can timelib support/format that? Do we care about formatting ability at that level of precision?
Do we want to require that the user pass in a TimeZone every time? Or should it default to the system time zone? If the former, we should have a convenience function like Time\current_time_zone(). I'm currently leaning toward explicit.
Ben's Sketch
In this version Unix, Local, Mono and Zoned times are all represented via types not classes. This is nice for consistency — we don't want Mono and Unix to be class based. But it does make the APIs less ergonomic. Long term my hope is that Hack might implement C#-like value types to allow for class-like semantics. We should design this API to be amenable to being codemoded to a class representation
The perf of accessing day/month/year might not be ideal in this implementation since that would have to be recalculated on every access.
type Nanoseconds = int; // Purely for documentation purposes.
newtype Clock = mixed;
newtype CalendarBasedClock = Clock;
// POSIX monotonic time
newtype Mono as Clock = Clock;
// POSIX realtime Clock time
newtype Unix as CalendarBasedClock = CalendarBasedClock;
// Represents the concept of X milliseconds since the unix epoch
// in some undefined timezone
newtype Local as CalendarBasedClock = CalendarBasedClock;
// I removed Tc from the tuple, not sure why it's needed
newtype Timestamp<Tc as Clock> = Nanoseconds;
type MonoTime = Timestamp<Mono>;
type UnixTime = Timestamp<Unix>;
type LocalTime = Timestamp<Local>;
// Represents the instant at the given UnixTime in the given timezone
// As an invariant, the local time of that unix time given current
// timezone rules must equal the local time.
//
// Users will serialize only one of UnixTime and LocalTime
// depending on if they want to represent a fixed instant (eg the time of a solar
// eclipse) or a fixed local time (eg POTUS is inaugrated at noon on January 20th
// of a given year, eastern time regardless of changes in timezone rules)
newtype ZonedTime = (UnixTime, LocalTime, Timezone);
newtype Duration as Nanoseconds = Nanoseconds;
const Mono CLOCK_MONO = 1; // TODO: should use the same constants as clock_gettime
const Unix CLOCK_UNIX = 2;
const Local CLOCK_LOCAL = 2;
function unix_now(): UnixTime;
function mono_now(): MonoTime;
function localtime(
int $year,
int $month = 0,
int $day = 0,
int $hour = 0,
int $minute = 0,
int $second = 0,
int $nanos = 0): Local;
// Copied from ZonedDateTime.of in java 8
//
// In most cases, there is only one valid offset for a local date-time.
//
// In the case of an overlap, when clocks are set back, there are two valid offsets.
// This method uses the earlier offset typically corresponding to "summer".
//
// In the case of a gap, when clocks jump forward, there is no valid offset.
// Instead, the local date-time is adjusted to be later by the length of the gap.
// For a typical one hour daylight savings change, the local date-time will be
// moved one hour later into the offset typically corresponding to "summer".
function localToZoned(LocalTime $t, Timezone $z): ZonedTime;
// Returns 0, 1 or 2 possible times for this LocalTime in this timezone for the
// gap, normal and overlap cases respectively. The pair is sorted with nulls last
// Alternative representation:
// https://github.com/google/cctz/blob/master/include/cctz/time_zone.h#L123
// This is a google timezone library which represents these lookups using:
// (type, pre, trans, post)
// Where type = UNIQUE / SKIPPED REPEATED
// pre/post = a version of the time with the same minute/second before/after the
// transition
// trans = the transition point
function localToZonedStrict(LocalTime $t, Timezone $z): (?ZonedTime, ?ZonedTime);
// The instant $t represented in timezone $z
function unixToZoned(UnixTime $t, Timezone $z): ZonedTime;
// Convert the same instant in $t to a timezone $z
function zonedToZoned(ZonedTime $t, Timezone $z): ZonedTime;
function zonedToUnix(ZonedTime $t, Timezone $z): UnixTime;
function zonedToLocal(ZonedTime $t, Timezone $z): LocalTime;
// "add one day" "this date but on monday"
function addUnits<Tc as CalendarBasedClock>(Tc $tm, UnitType $unit, int $value): Tc;
function subUnits<Tc as CalendarBasedClock>(Tc $tm, UnitType $unit, int $value): Tc;
function setField<Tc as CalendarBasedClock>(Tc $tm, UnitType $unit, int $value): Tc;
function getField<Tc as CalendarBasedClock>(Tc $tm, UnitType $unit): int;
function adjustZonedByLocal(
ZonedTime $t,
(function (LocalTime): LocalTime) $adj): ZonedTime;
function adjustZonedByUnix(
ZonedTime $t,
(function (UnixTime): UnixTime) $adj): ZonedTime;
Before and after
// Get the current hour in user's timezone
$hour = (int) PHP\date('H', timetozone($time, $user)));
$hour = getField(UnitType::HOUR, zonedTimeForUser($time, $user))
16 Feb 2018
Last Week's Questions
How to be predictable when mutating across time zones or across irregular points like “spring forward” and “fall back”?
Return null?
Throw exception? If Exceptions, what Exceptions should DateTime throw?
Missing APIs like “next Tuesday at 2:00pm”, possibly generators for “every following Tuesday at 2:00pm”. What if such a DateTime doesn't exist or exists twice?
How to design an efficient “mutation” API given that the object is immutable?
Maybe a single batch function?
Maybe a builder?
If we want to be consistent with the absolute portion of the library, we should target nanosecond precision, but can timelib support/format that? Do we care about formatting ability at that level of precision?
Do we want to require that the user pass in a TimeZone every time? Or should it default to the system time zone? If the former, we should have a convenience function like Time\current_time_zone(). I'm currently leaning toward explicit.
Summary
How to be predictable when constructing/mutating DateTimes that don't exist.
Suggestion: two APIs.
One returns a single time, comes up with a reasonable time (“approximate”)
There's a danger if you use it in iteration, e.g. a week at a time. Messes up iteration across timezone shifts. Gets worse over years. Call it out in documentation.
Recurrence class, or a generator API.
Other: Multiple times, all possible interpretations (“strict”, “detailed”
Validator API: Input date/time timezone, returns a boolean?
OR: DateTime constructor private, constructor APIs return a tuple.
How to design a “next Tuesday at 2:00pm” API?
Setting values is different from “shifting” value, so probably should be different APIs
How about a lazy builder esque API, when has the same constructor functions as DateTime.
Helpful to have different ways of getting to a time. Add, subtract, etc. vs. Go to.
Difference is that the user defines the delta in one, the endpoint in the other.
add/subtract vs. withHour, withDay, etc.
Different builders for shifters and setters.
Should avoid ambiguous combinations of calls
Last one wins?
How to design an efficient mutation API?
Mutation APIs on the DateTime object returns a builder that needs to be materialized into the next DateTime
Do we want to require that the user pass in a TimeZone every time?
Yes
But only allow it in CLI mode? FB-only restrictions?
Questions for next time?
Is there a next time?
Lets work on diffs first
Will pre-emptively book the room 2 weeks in advance, we can free it if we want.
What's the interface between Hack and HHVM?
Determining the full range of timelib.
Let's make sure that the HHVM extension is only available to the HSL.
But HHVM: Is there any long term hope of value classes? It influences how we'd think about the API.
e.g. if constructing a bunch of objects is a perf issue, value classes would mitigate that.
01 Mar 2018
Formatting
Probably some format specifiers we shouldn't allow
How do we localize? Let's talk to someone from I18N to figure out if it's possible to format dates in different locales.
Should we support constructing from a formatted string?
Maybe only LocalDateTime, so you don't deal with timezones.
How to deal with malformed strings? Monday, (and it's not a Monday).
Probably worth another meeting.
Internal Representation
LocalDateTime: “Local timestamp”, essentially a serialized clock time
DateTime: UTC timestamp + timezone (innards)
Timezones
Leaning toward representing as immutable objects
Two types of timezones: Identified and Offset
Shouldn't really have user visible behavior, but allows other APIs to only accept one or the other.
Let's continue via diffs, at least the interfaces. And exploring what the userland+native boundary will be.
This will be weird with HSL since we'll need the objects to be in an HHVM extension, which will be separate from the HSL repository.
If we can only use native functions, we'll be okay.
Need an introspection API as well. Folks don't just create and mutate, they want to know differences between times. Joda Time represents this as a Period object.
06 April 2018
Formatting
snip
Can a general localization API be built?
See how C++ and Java does it.
Takes a few generic arguments in a skeleton
Existing PHP API that's supposed to do this, but it doesn't take skeletons, it gives limited options for common patterns
snip
For standard library, format is a method
Will use the same formatting at strftime
Localized formatting will be a separate method that takes a limited set of patterns that ICU understands
Open Question: Why should they be methods? It seems hacky to snip, could do plain functions instead.
The legacy formatting will be a separate function
Doesn't even use timelib, we literally iterate on every character
Create from format
Should support, but require either Timezone passed-in or as trailing data in the string
The text was updated successfully, but these errors were encountered:
I am looking forward to having a more typesafe version of \DateTime.
The properties of an immutable object should not be modifiable.
The properties in this API are public.
Does this rely on <<__Const>> to protect against accidental mutation?
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Dates & Times in the Hack Standard Library
Goal
This document outlines the design of the Time namespace in the HSL
snip. The goal of this namespace is to provide an ergonomic and predictable API for operating on dates/times that helps developers fall into the pit of success.Absolute Time
Abstractions
An absolute time is a point on an absolute timeline, represented by a single timestamp in nanoseconds. Intervals between timestamps are a single value in nanoseconds. There are two types of absolute times in the HSL:
In the HSL, an absolute time is represented by the
Time\Timestamp
opaque type alias, which has aTime\Clock
type parameter to distinguish between monotonic and Unix time. An interval is represented as theTime\Interval
opaque type alias. Note that intervals and timestamps may be negative.These timestamps help the developer fall into the pit of success because:
API
Implementation
The majority of functions handling the monotonic time will be implemented in Hack, with the exception of
Time\now()
, which will call into theclock_gettime_ns()
API provided by HHVM to obtain the current time in nanoseconds.Local Time
Abstractions
Local times are different from absolute times in that they also contain time zone information, expressly for the purpose of representing times in a human-readable format. Local times are represented by an immutable
Time\DateTime
object, which contains a timestamp and a time zone.Time zones in Hack are represented by the
Time\TimeZone
opaque type alias. To obtain a time zone, (TODO: Represent as object or as an opaque type alias to string?) There are three ways of identifying time zones:America/New_York.
+0400
or-05:00.
These abstractions help the developer fall into the pit of success because:
Time\DateTime
immutable prevents classes of bugs where dates are inadvertently mutated away from the construction site.TODO: Similar to the
re'...'
string in Regex, could we have adt'...'
string to validate datetime formatting? Maybe this requires a generalization of the FormatString abstraction that we've hacked into Hack.Construction API
TBD: This API is subject to change at any time! See design meeting notes for more information.
Transformation API
There are two ways to transform a
DateTime
object: setting (setting individual calendar/clock values) and shifting (i.e. adding or subtracting a time interval). Note that “transformation” in this context means returning a new immutableDateTime
based on the one we're “mutating.”Setting consists of setting individual values (e.g. year, day, hour, minute, etc.). When setting, the clock time of the resultant
DateTime
will be preserved to best of the library's ability. In certain situations, a resultantDateTime
may not exist, such as when mutating across DST boundaries. In this case, the user must choose the proper API: one that returns the nearest validDateTime
along with a reason for its invalidity, or one that throws. The API is as follows:Shifting consists of adding or subtracting nanoseconds to/from the object's Unix timestamp. When shifting, mutations may change the clock time of the resultant
DateTime
. This means that each shifting API is guaranteed to return a valid DateTime, so we don't need different versions of them. Sometimes, this behavior may be undesirable, e.g. if a user shifts forward by one day on aDateTime
representing the day before DST, the hour of the resultantDateTime
would be different. In this case, users can use the setter API to preserve clock time. The API is as follows:Implementation
TBD - See Design Meeting Notes for up-to-date information. We'd like to implement as much of the library in Hack as possible. Ideally, most of the
Time\DateTime
class could be written in Hack, and dispatch to the runtime only when formatting or transformation is needed (since we will need to use the timelib library for that).Timezone-Agnostic Local Time
Oftentimes, developers want to work with the concept of a date/time free from the notion of a time zone. In that case, there is a LocalDateTime class, which has a similar API to that of DateTime, but does not take a time zone. It may be converted to a DateTime by adding a time zone via the toDateTime method. It will support the same format API as DateTime, minus the time zone portion.
API
Transformation API
The LocalDateTime transformation API will be identical to that of DateTime, in that it will have shifting and setting, the only difference being the lack of uncertainty regarding time zones. However, because the user may still specify an invalid date (e.g. February 29th on a non-leap year), the setting APIs will still need two versions.
Dependencies
Because opaque type aliases are only transparent in the file in which they're defined, it may be impossible to follow the HSL convention of splitting the library up into files. We may want to consider implementing a feature that allows opaque type aliases to be transparent within an entire namespace, especially if the
Time\DateTime
class ends up being implemented in the runtime.Use Cases
snip examplesComparing Against Fields of the User's Local Time
The developer simply wants the current hour in the user's time zone. We could construct a DateTime object, and get the hour as a getter.
Literally... Using Timezones
The developer wants to format a timestamp according to a particular timezone, and has to temporarily update global state to do so. Here, we can just construct a DateTime and call its format method.
Getting the Period Between DateTimes
Here, the developer wants to know the number of hours until midnight the following day. We can construct two DateTimes representing the two times, and get the period between them.
References
Possible Inspirations
Known PHP DateTime bugs
Design Meeting Notes
09 Feb 2018
Object/Data Relationships
DateTime
timelib_time
timelib_tzinfo
[timelib_tzinfo](https://github.com/derickr/timelib/blob/master/timelib.h#L146)
Summary
TODO:
Time\current_time_zone()
. I'm currently leaning toward explicit.Ben's Sketch
Before and after
16 Feb 2018
Last Week's Questions
timelib
support/format that? Do we care about formatting ability at that level of precision?Time\current_time_zone()
. I'm currently leaning toward explicit.Summary
Questions for next time?
01 Mar 2018
06 April 2018
snipsnipsnip, could do plain functions instead.The text was updated successfully, but these errors were encountered: