From ba025e8d00f9bdcf8aef32f2271ce897f433687f Mon Sep 17 00:00:00 2001 From: Dunbaratu Date: Wed, 13 Jan 2021 22:51:48 -0600 Subject: [PATCH 1/2] Fixes #2767 with two time types --- doc/source/commands/prediction.rst | 6 +- doc/source/structures/misc/time.rst | 765 ++++++++++++++++-- .../Compilation/CalculatorStructure.cs | 30 +- src/kOS.Safe/kOS.Safe.csproj | 1 + src/kOS/Function/Suffixed.cs | 98 ++- src/kOS/Suffixed/TimeBase.cs | 291 +++++++ src/kOS/Suffixed/TimeStamp.cs | 204 +++++ src/kOS/Suffixed/Timespan.cs | 162 ++-- src/kOS/kOS.csproj | 4 +- 9 files changed, 1403 insertions(+), 158 deletions(-) create mode 100644 src/kOS/Suffixed/TimeBase.cs create mode 100644 src/kOS/Suffixed/TimeStamp.cs diff --git a/doc/source/commands/prediction.rst b/doc/source/commands/prediction.rst index da9e7bef8..fbbe0db90 100644 --- a/doc/source/commands/prediction.rst +++ b/doc/source/commands/prediction.rst @@ -42,7 +42,7 @@ These return predicted information about the future position and velocity of an :type time: :struct:`TimeSpan` :return: A position :struct:`Vector` expressed as the coordinates in the :ref:`ship-center-raw-rotation ` frame - Returns a prediction of where the :struct:`Orbitable` will be at some :ref:`universal Timestamp `. If the :struct:`Orbitable` is a :struct:`Vessel`, and the :struct:`Vessel` has planned :ref:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. + Returns a prediction of where the :struct:`Orbitable` will be at some :ref:`universal Time `. If the :struct:`Orbitable` is a :struct:`Vessel`, and the :struct:`Vessel` has planned :ref:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. *Refrence Frame:* The reference frame that the future position gets returned in is the same reference frame as the current position @@ -62,7 +62,7 @@ These return predicted information about the future position and velocity of an :type time: :struct:`TimeSpan` :return: An :ref:`ObitalVelocity ` structure. - Returns a prediction of what the :ref:`Orbitable's ` velocity will be at some :ref:`universal Timestamp `. If the :struct:`Orbitable` is a :struct:`Vessel`, and the :struct:`Vessel` has planned :struct:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. + Returns a prediction of what the :ref:`Orbitable's ` velocity will be at some :ref:`universal Time `. If the :struct:`Orbitable` is a :struct:`Vessel`, and the :struct:`Vessel` has planned :struct:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. *Prerequisite:* If you are in a career mode game rather than a sandbox mode game, This function requires that you have your space @@ -101,7 +101,7 @@ These return predicted information about the future position and velocity of an :type time: :struct:`TimeSpan` :return: An :struct:`Orbit` structure. - Returns the :ref:`Orbit patch ` where the :struct:`Orbitable` object is predicted to be at some :ref:`universal Timestamp `. If the :struct:`Orbitable` is a :struct:`Vessel`, and the :struct:`Vessel` has planned :ref:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. + Returns the :ref:`Orbit patch ` where the :struct:`Orbitable` object is predicted to be at some :ref:`universal Time `. If the :struct:`Orbitable` is a :struct:`Vessel`, and the :struct:`Vessel` has planned :ref:`maneuver nodes `, the prediction assumes they will be executed exactly as planned. *Prerequisite:* If you are in a career mode game rather than a sandbox mode game, This function requires that you have your space diff --git a/doc/source/structures/misc/time.rst b/doc/source/structures/misc/time.rst index ac7f37bf6..01685624a 100644 --- a/doc/source/structures/misc/time.rst +++ b/doc/source/structures/misc/time.rst @@ -1,18 +1,84 @@ .. _time: -.. _timestamp: (For the documentation on controlling time **warp**, please see the :struct:`timewarp` page. This page is the documentation on the structure that holds an individual timestamp representing some universal moment in time.) -Time Span +Time +==== + +kOS provides two types you can use to work with game-time. One is +the :struct:`TimeStamp` and the other is the :struct:`TimeSpan`. +This page describes both, and the differences between them. + +.. _timestamp_timespan_diff: + +**Similarities between ``TimeStamp`` and ``TimeSpan``**: +Both :struct:`TimeStamp` and :struct:`TimeSpan` store an amount +of time, and can show you that time divided up into years, days, +hours, minutes, and seconds. + +**Differences between ``TimeStamp`` and ``TimeSpan``**: +The diference is that :struct:`TimeStamp` is for storing a single +point in time, while :struct:`TimeSpan` is for +storing an offset, or duration of time. Where they differ is in +what it means to call something "year 1" or "day 1". ``TimeStamp`` +is like a calendar moment. The first day of the year is called day +1, and the first year of the game is called year 1. ``TimeSpan``, +because it measures a duration of time, counts starting at zero +years and zero days. Had Kerbin had a more messy calendar like +Earth's Gregorian calendar with its dissimilar months some of which +are 30 days and some are 31 or occasionally 28 or 29, there would have +been even more differences between the two, but thankfully Kerbals don't +reckon things in months or weeks, so the only big difference is whether +you start counting at 1 or at 0 for years and days. + +Mixing TimeStamp with TimeSpan +------------------------------ + +There are rules for how you can mix and match :struct:`TimeStamp` and +:struct:`TimeSpan` in artithmetic and boolean comparisons. (For example +if you add a :struct:`TimeSpan` to a :struct:`TimeStamp` you get a new +:struct:`TimeStamp`.) The full rules for these operations are listed +in the :ref:`Time operators ` section further down this page. + +.. _universal_time: + +Timestamps as Scalar Universal time (seconds since Epoch) +--------------------------------------------------------- + +There are a number of places in kOS where a function or suffix expects +a time to be expressed as a simple scalar rather than as a +:struct:`TimeStamp`. When you come across these places what it means +is that you simply use the number of seconds since the game clock began. +(This is similar to the concept of "unix time" in real world computing, +if that's a term you've heard of.) For example, if you mean +"Exactly 1 hour in the gameworld after the campaign began" you'd use +a value of 3600 since there's 3600 seconds in one hour. If you mean +"Exactly 1 year and 20 seconds after the campaign began" you'd use +a value of 9201620, since there's 9201600 seconds in a Kerbal year +(9201600 = 60 * 60 * 6 * 426), plus 20 more seconds on top of that. + +You may easily convert to/from this type of time and the type +:struct:`TimeStamp` as follows: + + * :attr:`TimeStamp:SECONDS` converts a :struct:`TimeStamp` into + a :struct:`Scalar` universal time. + * :func:`TimeStamp(universal_time)` converts a :struct:`Scalar` + universal time into a :struct:`TimeStamp`. + +.. _timestamp: + +TimeStamp ========= -In several places the game uses a :struct:`TimeSpan` format. This is a structure that gives the time in various formats. It also allows you to perform arithmetic on the time. +In several places the game uses a :struct:`TimeStamp` format. This is a +structure that gives the time in various formats. In combination with +:struct:`TimeSpan` it also allows you to perform arithmetic on the time. -TimeSpan represents *SIMULATED* time ------------------------------------- +TimeStamp represents *SIMULATED* time +------------------------------------- When you are examining a :struct:`TimeSpan` you are looking at the "in character" **simulated** time, not the "out of character" real @@ -25,100 +91,135 @@ the following points illustrate: This allows you to use a :struct:`TimeSpan` such as is returned by the :global:`TIME` special variable to make correct physics calculations. -Built-in function TIME ----------------------- +Built-in function TIMESTAMP +--------------------------- -.. function:: TIME(universal_time) +.. function:: TIMESTAMP(universal_time) :parameter universal_time: (:struct:`Scalar`) - :return: A :struct`TimeSpan` of the time represented by the seconds timestamp passed in. - :rtype: :struct:`TimeSpan` + :return: A :struct`TimeStamp` of the time represented by the seconds passed in. + :rtype: :struct:`TimeStamp` - This creates a :struct:`TimeSpan` given a "universal time", + This creates a :struct:`TimeStamp` given a "universal time", which is a number of seconds since the current game began, IN GAMETIME. example: ``TIME(3600)`` will give you a :struct:`TimeSpan` representing the moment exactly 1 hour (3600 seconds) since the current game first began. The parameter is OPTIONAL. If you leave it off, - and just call ``TIME()``, then you end up getting + and just call ``TIMESTAMP()``, then you end up getting the current time, which is the same thing that :global:`TIME` gives you (without the parentheses). +.. function:: TIMESTAMP(year,day,hour,min,sec) + + :parameter year: (:struct:`Scalar`) + :parameter day: (:struct:`Scalar`) + :parameter hour: (:struct:`Scalar`) [optional] + :parameter min: (:struct:`Scalar`) [optional] + :parameter sec: (:struct:`Scalar`) [optional] + :return: A :struct`TimeStamp` of the time represented by the values passed in. + :rtype: :struct:`TimeStamp` + + This creates a :struct:`TimeStamp` given a year, day, hour-hand, + minute-hand, and second-hand. + + Because a :struct:`TimeStamp` is a calendar reckoning, the values + you use for the year and the day should start counting at 1, not + at 0. (The hour, minute, and second still start at zero). + + In other words:: + + // Notice these are equal because year and day start at 1 not 0: + set t1 to TIMESTAMP(0). + set t2 to TIMESTAMP(1,1,0,0,0). + print t1:full. + print t2:full. // Prints same as above. + + Note that the year and day are mandatory, but the remaining + parameters are optional and if you leave them off it assumes you + meant them to be zero (meaning it will give you a timestamp at + the very start of that date, right at midnight 0:00:00 O'clock). + +.. function:: TIME(universal_time) + + :parameter universal_time: (:struct:`Scalar`) + :return: A :struct`TimeStamp` + :rtype: :struct:`TimeStamp` + + This is an alias that means the same thing as + :func:`TIMESTAMP(universal_time)`. It exists to support older scripts + written before this was renamed to ``TIMESTAMP()``. + + Special variable TIME --------------------- .. global:: TIME :access: Get only - :type: :struct:`TimeSpan` + :type: :struct:`TimeStamp` The special variable :global:`TIME` is used to get the current time in the gameworld (not the real world where you're sitting in a chair playing Kerbal Space Program.) It is the same thing as calling :func:`TIME` with empty parentheses. -Using a TimeSpan ----------------- - - Any time you perform arithmetic on a :global:`TIMESPAN` you get a result back that is also a :struct:`TimeSpan`. In other words, :global:`TIME` is a :struct:`TimeSpan`, but ``TIME + 100`` is also a :struct:`TimeSpan`. - - Note that Kerbals do not have the concept of "months":: - - TIME // Gets the current universal time - TIME:CLOCK // Universal time in H:M:S format(1:50:26) - TIME:CALENDAR // Year 1, day 134 - TIME:YEAR // 1 - TIME:DAY // 134 : changes depending on KUNIVERSE:HOURSPERDAY - TIME:HOUR // 1 - TIME:MINUTE // 50 - TIME:SECOND // 26 - TIME:SECONDS // Total Seconds since campaign began +Kerbal Calendar Differs From Earth's +------------------------------------ Note that the notion of "how many hours in a day" and "how many days in a year" depends on the gameworld, not our real world. Kerbin has a shorter day, and a longer year in days as a result, than Earth. But there is an option in - KSP's main settings screen that can toggle this notion, and kOS will use - whatever option you set it to. + KSP's main settings screen that can toggle whether the game counts Kerbin + days (6 hours per day) or Earth days (24 hours per day) this notion, and + kOS will use whatever option you set it to alter the meaning of the Day + suffix of a :struct:`TIMESTAMP` and a :struct:`TIMESPAN`. Also note that the mods that alter the calendar for other solar systems, if they inject changes into KSP's main game, will cause these values to change too. +.. warning:: + + Please be aware that the kind of calendar :struct:`TimeStamp`'s use will depend on your KSP settings. The main KSP game supports both Kerbin time and Earth time and changing that setting will affect how :struct:`TimeStamp` works in kOS. + + The difference is whether 1 day = 6 hours or 1 day = 24 hours. + + You can access this setting from your script by using + :attr:`Kuniverse:HOURSPERDAY`. + .. highlight:: kerboscript Using TIME or TIME() to detect when the physics have been updated 'one tick' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The game will make an effort to maintain regular physics updates at a fixed rate (defaulting to 25 updates per second), sacrificing animation rate as necessary. When the game is unable to maintain regular updates at this rate, the clock time (in the upper left of the screen) will turn yellow or red instead of green. +The game will make an effort to maintain regular physics updates at a fixed rate (defaulting to 50 updates per second), sacrificing animation rate as necessary. When the game is unable to maintain regular updates at this rate, the clock time (in the upper left of the screen) will turn yellow or red instead of green. You can use the time reported by :global:`TIME` to detect whether or not a real physics 'tic' has occurred yet, which can be important for scripts that need to take measurements from the simulated universe. If no physics tic has occurred, then :global:`TIME` will still be exactly the same value. -.. warning:: - - Please be aware that the kind of calendar :struct:`TimeSpan`'s use will depend on your KSP settings. The main KSP game supports both Kerbin time and Earth time and changing that setting will affect how :struct:`TimeSpan` works in kOS. - - The difference is whether 1 day = 6 hours or 1 day = 24 hours. - - You can access this setting from your script by using - :attr:`Kuniverse:HOURSPERDAY`. +TimeStamp: Difference between SECOND and SECONDS +------------------------------------------------ .. warning:: - Beware the pitfall of confusing the :attr:`TimeSpan:SECOND` (singular) suffix with the :attr:`TimeSpan:SECONDS` (plural) suffix. + Beware the pitfall of confusing the :attr:`TimeStamp:SECOND` (singular) suffix with the :attr:`TimeStamp:SECONDS` (plural) suffix. - :attr:`TimeSpan:SECOND` + :attr:`TimeStamp:SECOND` This is the **whole** number of **remainder** seconds leftover after all whole-number minutes, hours, days, and years have been subtracted out, and it's never outside the range [0..60). It's essentially the 'seconds hand' on a clock. - :attr:`TimeSpan:SECONDS` + :attr:`TimeStamp:SECONDS` This is the number of seconds total if you want to represent time as just a simple flat number without all the components. It's the total count of the number of seconds since the beginning of time (Epoch). Because it's a floating point number, it can store times less than 1 second. Note this is a measure of how much simulated Kerbal time has passed since the game began. People experienced at programming will be familiar with this concept. It's the Kerbal's version of "unix time". The epoch (time zero) in the KSP game is the time at which you first started the new campaign. All campaign games begin with the planets in precisely the same position and the clock set to zero years, zero days, zero hours, and so on. -.. structure:: TimeSpan +.. structure:: TimeStamp + +TimeStamp Structure +------------------- .. list-table:: :header-rows: 1 @@ -129,27 +230,263 @@ You can use the time reported by :global:`TIME` to detect whether or not a real - Description + * - :attr:`FULL` + - :struct:`String` + - The full attr`:CALENDAR` and :attr`:CLOCK` as one string. * - :attr:`CLOCK` - :struct:`String` - "HH:MM:SS" * - :attr:`CALENDAR` - :struct:`String` - - "Year YYYY, day DDD" - * - :attr:`SECOND` - - :struct:`Scalar` (0-59) - - Second-hand number - * - :attr:`MINUTE` - - :struct:`Scalar` (0-59) - - Minute-hand number - * - :attr:`HOUR` - - :struct:`Scalar` (0-5) - - Hour-hand number + - "__y__d" format (i.e. "2y31d") + * - :attr:`YEAR` + - :struct:`Scalar` + - Year-hand number * - :attr:`DAY` - :struct:`Scalar` (1-426) - Day-hand number + * - :attr:`HOUR` + - :struct:`Scalar` (0-5) + - Hour-hand number + * - :attr:`MINUTE` + - :struct:`Scalar` (0-59) + - Minute-hand number + * - :attr:`SECOND` + - :struct:`Scalar` (0-59) + - Second-hand number + * - :attr:`SECONDS` + - :struct:`Scalar` (fractional) + - Total Seconds since Epoch (includes fractional partial seconds) + + +.. note:: + + This type is serializable. + + +.. attribute:: TimeStamp:FULL + + :access: Get only + :type: :struct:`String` + + The full string for the timestamp. (Down to the second anyway. Fractions of + seconds not shown), including year, day, hour, minute, and second. + The format is: + + ``Year XX Day XX HH:MM:SS`` + +.. attribute:: TimeStamp:CLOCK + + :access: Get only + :type: :struct:`String` + + Time in (HH:MM:SS) format. Does not show which year or day. + +.. attribute:: TimeStamp:CALENDAR + + :access: Get only + :type: :struct:`String` + + Date in ``Year XX Day XX`` format. + +.. attribute:: TimeStamp:YEAR + + :access: Get only + :type: :struct:`Scalar` + + Year-hand number. Note that the first year of the game, at "epoch" + time is actullay year 1, not year 0. + +.. attribute:: TimeStamp:DAY + + :access: Get only + :type: :struct:`Scalar` (1-426) or (1-356) + + Day-hand number. Kerbin has 426 days in its year if using Kerbin's + 6 hour day (less if :attr:`Kuniverse:HOURSPERDAY` is 24 meaning + the game is configured to show Earthlike days not Kerbin days. + + Note that the first day of the year is actually day 1, not day 0. + +.. attribute:: TimeStamp:HOUR + + :access: Get only + :type: :struct:`Scalar` (0-5) or (0-23) + + Hour-hand number. Note the setting :attr:`Kuniverse:HOURSPERDAY` affects + whether this will be a number from 0 to 5 (6 hour day) or a number + from 0 to 23 (24 hour day). + +.. attribute:: TimeStamp:MINUTE + + :access: Get only + :type: :struct:`Scalar` (0-59) + + Minute-hand number + +.. attribute:: TimeStamp:SECOND + + :access: Get only + :type: :struct:`Scalar` (0-59) + + Second-hand number. + +.. attribute:: TimeStamp:SECONDS + + :access: Get only + :type: :struct:`Scalar` (float) + + Total Seconds since Epoch. Epoch is defined as the moment your + current saved game's universe began (the point where you started + your campaign). Can be very precise to the current "physics tick" + and store values less than one second. (i.e. a typical value + might be something like 50402.103019 seconds). Please note + that if you print this in a loop again and again it will appear + to be "frozen" for a bit before moving in discrete jumps. This + reflects the fact that Kerbal Space Program is a computer simulated + world where time advances in discrete chunks, not smoothly. + +.. _timespan: + +TimeSpan +======== + +A :struct:`TimeSpan` is like a :struct:`TimeStamp` except that it counts +years starting at zero and days starting at zero, because it represents +an offset of time rather than a fixed single point of time on the +calendar/clock. It has fairly similar suffixes to :struct:`TimeStamp` +but their meaning can be subtly different as should be carefully +examined below in the suffix descriptions. + +Constructing +============ + +A :struct:`TimeSpan` can be created using built-in functions similar to those +for :struct:`TimeStamp`: + +.. function:: TIMESPAN(universal_time) + + :parameter universal_time: (:struct:`Scalar`) + :return: A :struct`TimeSpan` of the time represented by the seconds passed in. + :rtype: :struct:`TimeSpan` + + This creates a :struct:`TimeSpan` equal to the number of seconds + passed in. Fractional seconds are allowed for more precise spans. + + The parameter is OPTIONAL. If you leave it off, and just call + ``TIMESPAN()``, then you end up getting a timespan of zero duration. + +.. function:: TIMESPAN(year,day,hour,min,sec) + + :parameter year: (:struct:`Scalar`) + :parameter day: (:struct:`Scalar`) + :parameter hour: (:struct:`Scalar`) [optional] + :parameter min: (:struct:`Scalar`) [optional] + :parameter sec: (:struct:`Scalar`) [optional] + :return: A :struct`TimeSpan` of the time represented by the values passed in. + :rtype: :struct:`TimeSpan` + + This creates a :struct:`TimeSpan` that lasts this number of years + plus this number of days plus this number of hours plus this number + of minutes plus this number of seconds. + + Because a :struct:`TimeSpan` is NOT a calendar reckoning, but + an actual duration, the values you use for the year and the day + should start counting at 0, not at 1. + + In other words:: + + // Notice these are equal because year and day start at 0 not 1: + set span1 to TIMESPAN(0). + set span2 to TIMESPAN(0,0,0,0,0). + print span1:full. + print span2:full. // Prints same as above. + + Note that the year and day are mandatory in this function, but the + remaining parameters are optional and if you leave them off it + assumes you meant them to be zero (meaning it will give you a + timespan exactly equal to that many years and days, with no leftover + hours or minutes or seconds.) + +Offsetting TimeStamps with TimeSpans +------------------------------------ + +The main purpose of a :struct:`TimeSpan` is to be added or subtracted +from a :struct:`TimeStamp`. The exact rules for these operations +are elsewhere on this page in the :ref:`Time operators ` +section. + + +TimeSpan: Difference between SECOND and SECONDS +----------------------------------------------- + +.. warning:: + + Beware the pitfall of confusing the :attr:`TimeSpan:SECOND` (singular) + suffix with the :attr:`TimeSpan:SECONDS` (plural) suffix. + + :attr:`TimeSpan:SECOND` + + This is the **whole** number of **remainder** seconds leftover after all whole-number minutes, hours, days, and years have been subtracted out, and it's never outside the range [0..60). It's essentially the 'seconds hand' on a clock. + + :attr:`TimeSpan:SECONDS` + + This is the number of seconds total if you want to represent the + span of time as just a simple flat number without all the components. + It's the total count of the number of seconds within the time span, + and it can have a fractional component to represent times more precise + than one second. + +.. structure:: TimeSpan + +TimeSpan Structure +------------------ + + .. list-table:: + :header-rows: 1 + :widths: 1 1 4 + + * - Suffix + - Type + - Description + + + * - :attr:`FULL` + - :struct:`String` + - The full time duration split into fields for display. + * - :attr:`CLOCK` + - :struct:`String` + - THIS DOES NOT EXIST + * - :attr:`CALENDAR` + - :struct:`String` + - THIS DOES NOT EXIST * - :attr:`YEAR` - :struct:`Scalar` - - Year-hand number + - Whole number of years in the span. + * - :attr:`YEARS` + - :struct:`Scalar` + - TOTAL time in the span expressed in years. + * - :attr:`DAY` + - :struct:`Scalar` (1-426) + - Whole number of days after the last whole year in the span. + * - :attr:`DAYS` + - :struct:`Scalar` (1-426) + - TOTAL time in the span expressed in days. + * - :attr:`HOUR` + - :struct:`Scalar` (0-5) + - Whole number of hours after the last whole day in the span. + * - :attr:`HOURS` + - :struct:`Scalar` (0-5) + - TOTAL time in the span expressed in hours. + * - :attr:`MINUTE` + - :struct:`Scalar` (0-59) + - Whole number of minutes after the last whole hour in the span. + * - :attr:`MINUTES` + - :struct:`Scalar` (0-59) + - TOTAL time in the span expressed in minutes. + * - :attr:`SECOND` + - :struct:`Scalar` (0-59) + - Whole number of seconds after the last whole minute in the span. * - :attr:`SECONDS` - :struct:`Scalar` (fractional) - Total Seconds since Epoch (includes fractional partial seconds) @@ -160,60 +497,344 @@ You can use the time reported by :global:`TIME` to detect whether or not a real This type is serializable. +.. attribute:: TimeSpan:FULL + + :access: Get only + :type: :struct:`String` + + The full string for the TimeSpan. (Down to the second anyway. Fractions of + seconds not shown), including year, day, hour, minute, and second. + The format is: + + ``_y_d_h_m_s`` (where the underscores are numbers). + .. attribute:: TimeSpan:CLOCK :access: Get only :type: :struct:`String` - Time in (HH:MM:SS) format. + **DOES NOT EXIST** + ``TimeSpan`` has no such suffix as ``:CLOCK`` because it might miss the + important fact that the ``TimeSpan`` is bigger than a day by not showing + the year and day fields. Why document this then? To make it clear + that this is a difference compared to :struct:`TimeStamp` in case you + were looking for such a similar suffix for :struct:`TimeSpan` .. attribute:: TimeSpan:CALENDAR :access: Get only :type: :struct:`String` - Day in "Year YYYY, day DDD" format. (Kerbals don't have 'months'.) + **DOES NOT EXIST** + ``TimeSpan`` has no such suffix as ``:CALENDAR` because it might miss the + important fact that the ``TimeSpan`` has remainder time less than 1 day. + in the hour, minute, and second fields. Why document this then? To make + it clear that this is a difference compared to :struct:`TimeStamp` in + case you were looking for such a similar suffix for :struct:`TimeSpan` -.. attribute:: TimeSpan:SECOND +.. attribute:: TimeSpan:YEAR :access: Get only - :type: :struct:`Scalar` (0-59) + :type: :struct:`Scalar` - Second-hand number. + Whole number of Years in the span. Note that TimeSpan starts + counting years at 0 not at 1. This is a difference from how it + works for :struct:`TimeStamp` -.. attribute:: TimeSpan:MINUTE +.. attribute:: TimeSpan:YEARS :access: Get only - :type: :struct:`Scalar` (0-59) + :type: :struct:`Scalar` - Minute-hand number + TOTAL time in the span, expressed in units of years. This is not + the same as :attr:`TimeSpan:YEAR` because it includes a fractional + part and is the *entire* span, not just the whole number of years. + Example: If there are 426 days in a Year, and the Timespan is + 1 year and 213 days long, then this will return ``1.5`` rather than ``1``, + as the *entire* span is one and a half years. You can think of this + as being :attr:`TimeSpan:SECONDS` divided by seconds per year. + +.. attribute:: TimeSpan:DAY + + :access: Get only + :type: :struct:`Scalar` (1-426) or (1-356) + + Whole number of days remaining after the lst full year within the span. + Kerbin has 426 days in a year if using Kerbin's + 6 hour day (less if :attr:`Kuniverse:HOURSPERDAY` is 24 meaning + the game is configured to show Earthlike days not Kerbin days. + + Note that for spans the first day of the year is the zero-th + day, not the 1-th day. This is a difference from how it + works for :struct:`TimeStamp` + +.. attribute:: TimeSpan:DAYS + + :access: Get only + :type: :struct:`Scalar` + + TOTAL time in the span, expressed in units of days. This is not + the same as :attr:`TimeSpan:DAY` because it includes a fractional + part and is the *entire* span, not just the whole number of days leftover + in the last partial year. + Example: If there are 426 days in a Year, and the Timespan is + 1 year and 213 days and 12 hours long, then this will return ``639.5`` + rather than ``213``, as the *entire* span is 639 and a half days. .. attribute:: TimeSpan:HOUR :access: Get only :type: :struct:`Scalar` (0-5) or (0-23) - Hour-hand number. Kerbin has six hours in its day. + Whole number of hours remaining after the last full day in the span. + Note the setting :attr:`Kuniverse:HOURSPERDAY` affects + whether this will be a number from 0 to 5 (6 hour day) or a number + from 0 to 23 (24 hour day). -.. attribute:: TimeSpan:DAY +.. attribute:: TimeSpan:HOURS :access: Get only - :type: :struct:`Scalar` (1-426) or (1-356) + :type: :struct:`Scalar` - Day-hand number. Kerbin has 426 days in its year. + TOTAL time in the span, expressed in units of hours. This is not + the same as :attr:`TimeSpan:HOUR` because it includes a fractional + part and is the *entire* span, not just the whole number of hours + leftover in the last partial day. + Example: If the Timespan is 0 years, 2 days, 3 hours, and 20 minutes, + and days are 6 hours long, then this will return 15.3333333 since + the *entire* span is 2 days of 6 hours each, plus 3 more hours, plus + 20 minutes which is one third of an hour. -.. attribute:: TimeSpan:YEAR +.. attribute:: TimeSpan:MINUTE + + :access: Get only + :type: :struct:`Scalar` (0-59) + + Whole number of minutes remaining after the last full hour in the span. + +.. attribute:: TimeSpan:MINUTES :access: Get only :type: :struct:`Scalar` - Year-hand number + TOTAL time in the span, expressed in units of minutes. This is not + the same as :attr:`TimeSpan:MINUTE` because it includes a fractional + part and is the *entire* span, not just the whole number of minutes + leftover in the last partial hour. + Example: If the Timespan is 0 years, 0 days, 3 hours, 20 minutes, and + 30 seconds, then this will return ``200.5`` as that is the *entire* + span: 3 60-minute hours is 180, plus 20 more minutes is 200, plus 30 + seconds which is half a minute gives 200.5. + +.. attribute:: TimeSpan:SECOND + + :access: Get only + :type: :struct:`Scalar` (0-59) + + Whole number of seconds remaining after the last full minute in the span. + Please note the difference between this and :attr:`TimeSpan:SECONDS`. .. attribute:: TimeSpan:SECONDS :access: Get only :type: :struct:`Scalar` (float) - Total Seconds since Epoch. Epoch is defined as the moment your - current saved game's universe began (the point where you started - your campaign). Can be very precise. + Total Seconds in the TimeSpan, including fractonal part. Note + this is NOT the same as :attr:`TimeSpan:SECOND` (singular), + because this is the total span of time expressed in seconds, + and not just the leftover seconds in the last minute of the span. + +.. _time_operators: + +Time Operators +============== + +It is possible to mix and match :struct:`TimeStamp` with :struct:`TimeSpan` +operands and :struct:`Scalar` operands in math and comparison operations. + +Time Operators - arithmetic +--------------------------- + +You may subtract (*but NOT add*) two TimeStamps, which +gives a TimeSpan for the interval between the two: + + * a :struct:`TimeStamp` - a :struct:`TimeStamp` = a :struct:`TimeSpan` + * a :struct:`TimeStamp` + a :struct:`TimeStamp` = Illegal operation. + +Example:: + + // This sets A = year 1, day 3, hour 1: + set A to TIMESTAMP(1,3,1,0,0). + // This sets B = year 1, day 3, hour 2, minute 20: + set B to TIMESTAMP(1,3,2,20,0). + // This sets C to the difference between A and B, which is + // 0 years, 0 days, 1 hour, 20 minutes, 0 seconds + set C to B - A. + print C:full. // should print 0y0d1h20m0s + +You may add or subtract a TimeSpan and a TimeStamp, which gives a TimeStamp: + + * a :struct:`TimeStamp` + a :struct:`TimeSpan` = a :struct:`TimeStamp` + * a :struct:`TimeStamp` - a :struct:`TimeSpan` = a :struct:`TimeStamp` + +Example:: + + // This sets A = right now: + set A to TIMESTAMP(). + // This sets B = 1 hour and 30 minutes from right now: + set B to A + TIMESPAN(0,0,1,30,0). + // This sets C = 1 minute and 45 seconds before B: + set C to B - TIMESPAN(0,0,0,1,45). + +You may add or subtract a Scalar to either TimeStamps or TimeSpans. In either +case, *when adding or subtracting it assumes a Scalar is a number of seconds*: + + * a :struct:`TimeStamp` + a :struct:`Scalar` = a :struct:`TimeStamp` + * a :struct:`TimeStamp` - a :sturct:`Scalar` = a :struct:`TimeStamp` + * a :struct:`TimeSpan` + a :struct:`Scalar` = a :struct:`TimeSpan` + * a :struct:`TimeSpan` - a :struct:`Scalar` = a :struct:`TimeSpan` + +Example:: + + // This sets A = right now: + set A to TIMESTAMP(). + // This sets B = 3600 seconds from now: + set B to A + 3600. + // This sets C = half a second before B: + set C to B - 0.5. + + // This sets D = a span of 3 minutes: + set D to TIMESPAN(0,0,0,3,0). + // This sets D = 5 seconds longer than it was before: + set D to D + 5. + +You may add or subtract two TimeSpans to get a new longer or shorter +TimeSpan: + + * a :struct:`TimeSpan` + a :struct:`TimeSpan` = a :struct:`TimeSpan` + * a :struct:`TimeSpan` - a :struct:`TimeSpan` = a :struct:`TimeSpan` + +Example:: + + // This sets A = 30 minutes: + set A to TIMESPAN(0,0,0,30,0). + // This sets B = 10 minutes: + set B to TIMESPAN(0,0,0,10,0). + // This sets C = 40 minutes: + set C to A + B. + // This sets D = 20 minutes: + set D to A - B. + +You may divide or multiply a TimeSpan (*but NOT a TimeStamp*) by a scalar. +*When using scalars this way, they are interpreted as unit-less +coefficients and NOT as seconds like they are when adding or subtracting*. +This gives you a larger or smaller time interval. Note that if multiplying, +the order does not matter, but if dividing, then you *may not* put the +TimeStamp in the denominator. + + * a :struct:`TimeSpan` * a :struct:`Scalar` = a :struct:`TimeSpan` + * a :struct:`TimeSpan` / a :sturct:`Scalar` = a :struct:`TimeSpan` + * a :struct:`Scalar` * a :struct:`TimeSpan` = a :struct:`TimeSpan` + * a :struct:`Scalar` / a :struct:`TimeSpan` = Illegal Operation + * a :struct:`TimeSpan` / a :sturct:`Scalar` = a :struct:`TimeSpan` + * a :struct:`TimeStamp` * a :struct:`Scalar` = Illegal Operation + * a :struct:`TimeStamp` / a :struct:`Scalar` = Illegal Operation + * a :struct:`TimeStamp` * a :struct:`TimeSpan` = Illegal Operation + * a :struct:`TimeStamp` / a :struct:`TimeSpan` = Illegal Operation + * a :struct:`Scalar` * a :struct:`TimeStamp` = Illegal Operation + * a :struct:`Scalar` / a :struct:`TimeStamp` = Illegal Operation + * a :struct:`Scalar` * a :struct:`TimeStamp` = Illegal Operation + * a :struct:`Scalar` / a :struct:`TimeStamp` = Illegal Operation + +Example:: + + // This sets A = 45 minutes: + set A to TIMESPAN(0,0,0,45,0). + // This sets B to 1 hour 30 minutes (2 * 45 minutes = 90 minutes) + set B to 2 * A. + // This sets C to 22 minutes 30 seconds (half of 45 minutes): + set C to A / 2. + +Time Opertators - comparisons +----------------------------- + +You may check if two TimeStamps are equal, greater, or lesser. + + * (a :struct:`TimeStamp` = a :struct:`TimeStamp`) is true if they are the same time + * (a :struct:`TimeStamp` <> a :struct:`TimeStamp`) is true if they are not the same time + * (a :struct:`TimeStamp` < a :struct:`TimeStamp`) is true if the time on the left is sooner than the one on the right + * (a :struct:`TimeStamp` > a :struct:`TimeStamp`) is true if the time on the left is later than the one on the right + * (a :struct:`TimeStamp` <= a :struct:`TimeStamp`) works as expected, given the above. + * (a :struct:`TimeStamp` >= a :struct:`TimeStamp`) works as expected, given the above. + +Example:: + + // Run the loop until 3 seconds have passed: + local end_time is TIMESTAMP() + 3. // Now plus 3 seconds. + until TIMESTAMP() > end_time { // Note this is a TimeStamp > TimeStamp comparison + print "3 seconds aren't up yet...". + wait 0.2. + } + print "3 seconds have passed.". + +You may check if two TimeSpans are equal, greater, or lesser. + + * (a :struct:`TimeSpan` = a :struct:`TimeSpan`) is true if they are the same length + * (a :struct:`TimeSpan` <> a :struct:`TimeSpan`) is true if they are not the same length + * (a :struct:`TimeSpan` < a :struct:`TimeSpan`) is true if the span on the left is shorter than the one on the right + * (a :struct:`TimeSpan` > a :struct:`TimeSpan`) is true if the span on the left is longer than the one on the right + * (a :struct:`TimeSpan` <= a :struct:`TimeSpan`) works as expected, given the above. + * (a :struct:`TimeSpan` >= a :struct:`TimeSpan`) works as expected, given the above. + +Example:: + + local short_span is TIMESPAN(0,0,0,0,30). // 30 seconds + local long_span is TIMESPAN(0,0,0,5,0). // 5 minutes + if short_span < long_span { + print "I guess 30 seconds is shorter than 5 minutes.". + } + +You may compare TimeStamps with Scalars, or TimeSpans with Scalars. In all such +cases, the Scalar is interpreted as a number of seconds. In the case of comparing +a TimeStamp with a Scalar, the Scalar is taken as a Universal Time expressed +in seconds-since-epoch. In the case of comparing a TimeSpan to a Scalar, +the Scalar is just a duration of that many seconds. + + * (a :struct:`TimeStamp` = a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeStamp` <> a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeStamp` < a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeStamp` > a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeStamp` <= a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeStamp` >= a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeSpan` = a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeSpan` <> a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeSpan` < a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeSpan` > a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeSpan` <= a :struct:`Scalar`) Works as described above. + * (a :struct:`TimeSpan` >= a :struct:`Scalar`) Works as described above. + +Example:: + + local how_many_seconds_in_3_hours is 3 * 3600. + if TIMESTAMP() > how_many_seconds_in_3_hours { + print "This campaign universe has existed for at least 3 hours of game time.". + } + if TIMESPAN(1,0,0,0,0) > 1000000 { + print "One year is more than 1000000 seconds.". + } + +You *may NOT* compare TimeStamps with TimeSpans. All the following are illegal: + + * (a :struct:`TimeStamp` = a :struct:`TimeSpan`) is an Illegal Comparison + * (a :struct:`TimeStamp` <> a :struct:`TimeSpan`) is an Illegal Comparison + * (a :struct:`TimeStamp` < a :struct:`TimeSpan`) is an Illegal Comparison + * (a :struct:`TimeStamp` > a :struct:`TimeSpan`) is an Illegal Comparison + * (a :struct:`TimeStamp` <= a :struct:`TimeSpan`) is an Illegal Comparison + * (a :struct:`TimeStamp` >= a :struct:`TimeSpan`) is an Illegal Comparison + * (a :struct:`TimeSpan` = a :struct:`TimeStamp`) is an Illegal Comparison + * (a :struct:`TimeSpan` <> a :struct:`TimeStamp`) is an Illegal Comparison + * (a :struct:`TimeSpan` < a :struct:`TimeStamp`) is an Illegal Comparison + * (a :struct:`TimeSpan` > a :struct:`TimeStamp`) is an Illegal Comparison + * (a :struct:`TimeSpan` <= a :struct:`TimeStamp`) is an Illegal Comparison + * (a :struct:`TimeSpan` >= a :struct:`TimeStamp`) is an Illegal Comparison + diff --git a/src/kOS.Safe/Compilation/CalculatorStructure.cs b/src/kOS.Safe/Compilation/CalculatorStructure.cs index dc53a2e5f..fa05d6e1c 100644 --- a/src/kOS.Safe/Compilation/CalculatorStructure.cs +++ b/src/kOS.Safe/Compilation/CalculatorStructure.cs @@ -226,19 +226,41 @@ private static string GetMessage(string op, OperandPair pair) return string.Format("Cannot perform the operation: {0} On Structures {1} and {2}", op, t1, t2); } + /// + /// By default when you call MethodInfo.Invoke() it masks the exceptions + /// the invoked method throws so the kOS user wouldn't see the real message. + /// This fixes that for the operators we are trying to call here. + /// + private static object InvokeWithCorrectExceptions(MethodInfo meth, object obj, object [] parameters) + { + try + { + return meth.Invoke(obj, parameters); + } + catch (TargetInvocationException outerException) + { + // MethodInfo.Invoke() "helpfully" wraps the exceptions the method tries + // to throw inside a TargetInvocationException so you get THAT instead of + // the actual exception. In order to let the user see the real exception + // message, we have to unwrap this wrapper around it and re-throw it: + throw outerException.InnerException; + } + } + private bool TryInvokeExplicit(OperandPair pair, string methodName, out object result) { MethodInfo method1 = pair.LeftType.GetMethod(methodName, FLAGS, null, new[] { pair.LeftType, pair.RightType }, null); if (method1 != null) { - result = method1.Invoke(null, new[] {pair.Left, pair.Right}); + + result = InvokeWithCorrectExceptions(method1, null, new[] { pair.Left, pair.Right }); return true; } MethodInfo method2 = pair.RightType.GetMethod(methodName, FLAGS, null, new[] { pair.LeftType, pair.RightType }, null); if (method2 != null) { - result = method2.Invoke(null, new[] {pair.Left, pair.Right}); + result = InvokeWithCorrectExceptions(method2, null, new[] {pair.Left, pair.Right}); return true; } @@ -271,7 +293,7 @@ private bool TryCoerceImplicit(OperandPair pair, out OperandPair resultPair) if (convert2 != null) { couldCoerce = true; - newRight = convert2.Invoke(null, new[] { pair.Right }); + newRight = InvokeWithCorrectExceptions(convert2, null, new[] { pair.Right }); } else { @@ -282,7 +304,7 @@ private bool TryCoerceImplicit(OperandPair pair, out OperandPair resultPair) if (convert1 != null) { couldCoerce = true; - newLeft = convert1.Invoke(null, new[] { pair.Left }); + newLeft = InvokeWithCorrectExceptions(convert1, null, new[] { pair.Left }); } else { diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index 097e9c8a6..fdbf680a1 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -79,6 +79,7 @@ + diff --git a/src/kOS/Function/Suffixed.cs b/src/kOS/Function/Suffixed.cs index 3f65aba47..0af737b68 100644 --- a/src/kOS/Function/Suffixed.cs +++ b/src/kOS/Function/Suffixed.cs @@ -353,9 +353,8 @@ public override void Execute(SharedObjects shared) shared.SoundMaker.StopAllVoices(); } } - - [Function("time")] - public class Time : FunctionBase + [Function("timestamp", "time")] + public class FunctionTimeStamp : FunctionBase { // Note: "TIME" is both a bound variable AND a built-in function now. // If it gets called with parentheses(), the script calls this built-in function. @@ -373,12 +372,99 @@ public override void Execute(SharedObjects shared) // If zero args, then the default is to assume you want to // make a Timespan of "now": if (argCount == 0) - ut = Planetarium.GetUniversalTime(); - else + { + ReturnValue = new kOS.Suffixed.TimeStamp(Planetarium.GetUniversalTime()); + } + // If one arg, then assume its in UT timestamp seconds: + else if (argCount == 1) + { ut = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeStamp(ut); + } + // If more args, assume they are year, day, hour, minute, second, with optional + // args at the end (eg. if there's only 3 args, it's year, day, hour with no minutes or seconds). + else if (argCount == 2) + { + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeStamp(year, day, 0.0, 0.0, 0.0); + } + else if (argCount == 3) + { + double hour = GetDouble(PopValueAssert(shared)); + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeStamp(year, day, hour, 0.0, 0.0); + } + else if (argCount == 4) + { + double minute = GetDouble(PopValueAssert(shared)); + double hour = GetDouble(PopValueAssert(shared)); + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeStamp(year, day, hour, minute, 0.0); + } + else if (argCount == 5) + { + double second = GetDouble(PopValueAssert(shared)); + double minute = GetDouble(PopValueAssert(shared)); + double hour = GetDouble(PopValueAssert(shared)); + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeStamp(year, day, hour, minute, second); + } AssertArgBottomAndConsume(shared); + } + } - ReturnValue = new kOS.Suffixed.TimeSpan(ut); + [Function("timespan")] + public class FunctionTimeSpan : FunctionBase + { + public override void Execute(SharedObjects shared) + { + double ut; + // Accepts zero or one arg: + int argCount = CountRemainingArgs(shared); + + // If one arg, then assume its seconds: + if (argCount == 1) + { + ut = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeSpan(ut); + } + // If more args, assume they are year, day, hour, minute, second, with optional + // args at the end (eg. if there's only 3 args, it's year, day, hour with no minutes or seconds). + else if (argCount == 2) + { + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeSpan(year, day, 0.0, 0.0, 0.0); + } + else if (argCount == 3) + { + double hour = GetDouble(PopValueAssert(shared)); + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeSpan(year, day, hour, 0.0, 0.0); + } + else if (argCount == 4) + { + double minute = GetDouble(PopValueAssert(shared)); + double hour = GetDouble(PopValueAssert(shared)); + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeSpan(year, day, hour, minute, 0.0); + } + else if (argCount == 5) + { + double second = GetDouble(PopValueAssert(shared)); + double minute = GetDouble(PopValueAssert(shared)); + double hour = GetDouble(PopValueAssert(shared)); + double day = GetDouble(PopValueAssert(shared)); + double year = GetDouble(PopValueAssert(shared)); + ReturnValue = new kOS.Suffixed.TimeSpan(year, day, hour, minute, second); + } + AssertArgBottomAndConsume(shared); } } diff --git a/src/kOS/Suffixed/TimeBase.cs b/src/kOS/Suffixed/TimeBase.cs new file mode 100644 index 000000000..b0c52185f --- /dev/null +++ b/src/kOS/Suffixed/TimeBase.cs @@ -0,0 +1,291 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Serialization; +using System.Collections.Generic; +using kOS.Safe; + +namespace kOS.Suffixed +{ + /// + /// Both TimeStamp and TimeSpan will be derived from this common base class, + /// to avoid duplicate code between them. This is because really the only + /// difference between TimeStamp and TimeSpan is whether the nomenlcature + /// counts starting at 1 or starting at 0. The Kerbal calendar doesn't have + /// all the same ugly problems as the real world calender that necessitate a + /// separate timestamp type (a leap day in the middle of the year instead of + /// at the end offsetting all the dates that come after Feb 29th, for example, + /// or having this mythical concept of 'a month' which isn't even the same + /// number of days for all months, etc. The Kerbals never bothered with months + /// and instead just count days into the year, and don't put in leap days, instead + /// just having the same leftover fractional day at the end of every year. These + /// things make a seprate timespan and timestamp type a bit less necessary in the + /// Kerbal world, but one the remaining difference is whether you count starting at 1 + /// or starting at 0. + /// + [kOS.Safe.Utilities.KOSNomenclature("TimeBase")] + public abstract class TimeBase : SerializableStructure + { + /// + /// For serializaation, how will it be named in the JSON output. + /// + public abstract string DumpName { get; } + + /// + /// Override with either 0 or 1 for whether counting years and days starts counting at 0 or at 1. + /// + protected abstract double CountOffset { get; } + + protected double seconds; + + protected int SecondsPerDay { get { return KSPUtil.dateTimeFormatter.Day; } } + protected int SecondsPerHour { get { return KSPUtil.dateTimeFormatter.Hour; } } + protected int SecondsPerYear { get { return KSPUtil.dateTimeFormatter.Year; } } + protected int SecondsPerMinute { get { return KSPUtil.dateTimeFormatter.Minute; } } + + // Only used by CreateFromDump() and the other constructors. + // Don't make it public because it leaves fields + // unpopulated: + protected TimeBase() + { + InitializeSuffixes(); + } + + public TimeBase(double unixStyleTime) : this() + { + seconds = unixStyleTime; + } + + // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: + /* Derivative classes of TimeBase need to make something like this, replacing "TimeBase" with their own + * class name: + * + public static TimeBase CreateFromDump(SafeSharedObjects shared, Dump d) + { + var newObj = new TimeBase(); + newObj.LoadDump(d); + return newObj; + } + * + */ + + protected abstract void InitializeSuffixes(); + + /* need to give the two types different suffixes + * + private void InitializeSuffixes() + { + AddSuffix("YEAR", new Suffix(CalculateYear)); + AddSuffix("DAY", new Suffix(CalculateDay)); + AddSuffix("HOUR", new Suffix(CalculateHour)); + AddSuffix("MINUTE", new Suffix(CalculateMinute)); + AddSuffix("SECOND", new Suffix(CalculateSecond)); + AddSuffix("SECONDS", new Suffix(() => seconds)); + AddSuffix("CLOCK", new Suffix(() => string.Format("{0:00}:{1:00}:{2:00}", (int)CalculateHour(), (int)CalculateMinute(), (int)CalculateSecond()))); + AddSuffix("CALENDAR", new Suffix(() => "Year " + CalculateYear() + ", day " + CalculateDay())); + } + */ + + protected ScalarValue CalculateYear() + { + return (int)Math.Floor(seconds / SecondsPerYear) + CountOffset; + } + + /// + /// Change JUST the year part of the time, leaving the remainder (days, hours, etc) still intact. + /// + /// + protected void ChangeYear(ScalarValue newValue) + { + int zeroBasedNewValue = newValue - CountOffset; + seconds = (seconds % SecondsPerYear) + (zeroBasedNewValue * SecondsPerYear); + } + + protected ScalarValue CalculateDay() + { + return (int)Math.Floor(seconds % SecondsPerYear / SecondsPerDay) + CountOffset; + } + + /// + /// Change JUST the day part of the time, leaving the year, hour, minute, and second intact: + /// + /// + protected void ChangeDay(ScalarValue newValue) + { + int zeroBasedNewValue = newValue - CountOffset; + // Subtract old day value: + seconds -= Math.Floor(seconds % SecondsPerYear / SecondsPerDay); + // Add new day value: + seconds += zeroBasedNewValue * SecondsPerDay; + } + + protected ScalarValue CalculateHour() + { + return (int)Math.Floor(seconds % SecondsPerDay / SecondsPerHour); + } + + /// + /// Change JUST the Hour part of the time, leaving the year, day, minute, and second intact: + /// + /// + protected void ChangeHour(ScalarValue newValue) + { + int zeroBasedNewValue = newValue - CountOffset; + // Subtract old hour value: + seconds -= Math.Floor(seconds % SecondsPerDay / SecondsPerHour); + // Add new hour value: + seconds += zeroBasedNewValue * SecondsPerHour; + } + + protected ScalarValue CalculateMinute() + { + return (int)Math.Floor(seconds % SecondsPerHour / SecondsPerMinute); + } + + /// + /// Change JUST the Minute part of the time, leaving the year, day, hour, and second intact: + /// + /// + protected void ChangeMinute(ScalarValue newValue) + { + int zeroBasedNewValue = newValue - CountOffset; + // Subtract old minute value: + seconds -= Math.Floor(seconds % SecondsPerHour / SecondsPerMinute); + // Add new minute value: + seconds += zeroBasedNewValue * SecondsPerMinute; + } + + protected ScalarValue CalculateSecond() + { + return (int)Math.Floor(seconds % SecondsPerMinute); + } + + /// + /// Change JUST the Second part of the time, leaving the year, day, hour, and minute intact: + /// + /// + protected void ChangeSecond(ScalarValue newValue) + { + int zeroBasedNewValue = newValue - CountOffset; + // Subtract old minute value: + seconds -= Math.Floor(seconds % SecondsPerMinute); + // Add new minute value: + seconds += zeroBasedNewValue; + } + + public double ToUnixStyleTime() + { + return seconds; + } + + /* These conversions will have to be in the overriding classes: + * + * + public static TimeBase operator +(TimeBase a, TimeBase b) { return new TimeBase(a.ToUnixStyleTime() + b.ToUnixStyleTime()); } + public static TimeBase operator -(TimeBase a, TimeBase b) { return new TimeBase(a.ToUnixStyleTime() - b.ToUnixStyleTime()); } + public static TimeBase operator +(TimeBase a, double b) { return new TimeBase(a.ToUnixStyleTime() + b); } + public static TimeBase operator -(TimeBase a, double b) { return new TimeBase(a.ToUnixStyleTime() - b); } + public static TimeBase operator *(TimeBase a, double b) { return new TimeBase(a.ToUnixStyleTime() * b); } + public static TimeBase operator /(TimeBase a, double b) { return new TimeBase(a.ToUnixStyleTime() / b); } + public static TimeBase operator +(double b, TimeBase a) { return new TimeBase(b + a.ToUnixStyleTime()); } + public static TimeBase operator -(double b, TimeBase a) { return new TimeBase(b - a.ToUnixStyleTime()); } + public static TimeBase operator *(double b, TimeBase a) { return new TimeBase(b * a.ToUnixStyleTime()); } + public static TimeBase operator /(double b, TimeBase a) { return new TimeBase(b / a.ToUnixStyleTime()); } + public static TimeBase operator /(TimeBase b, TimeBase a) { return new TimeBase(b.ToUnixStyleTime() / a.ToUnixStyleTime()); } + public static bool operator >(TimeBase a, TimeBase b) { return a.ToUnixStyleTime() > b.ToUnixStyleTime(); } + public static bool operator <(TimeBase a, TimeBase b) { return a.ToUnixStyleTime() < b.ToUnixStyleTime(); } + public static bool operator >=(TimeBase a, TimeBase b) { return a.ToUnixStyleTime() >= b.ToUnixStyleTime(); } + public static bool operator <=(TimeBase a, TimeBase b) { return a.ToUnixStyleTime() <= b.ToUnixStyleTime(); } + public static bool operator >(TimeBase a, double b) { return a.ToUnixStyleTime() > b; } + public static bool operator <(TimeBase a, double b) { return a.ToUnixStyleTime() < b; } + public static bool operator >=(TimeBase a, double b) { return a.ToUnixStyleTime() >= b; } + public static bool operator <=(TimeBase a, double b) { return a.ToUnixStyleTime() <= b; } + public static bool operator >(double a, TimeBase b) { return a > b.ToUnixStyleTime(); } + public static bool operator <(double a, TimeBase b) { return a < b.ToUnixStyleTime(); } + public static bool operator >=(double a, TimeBase b) { return a >= b.ToUnixStyleTime(); } + public static bool operator <=(double a, TimeBase b) { return a <= b.ToUnixStyleTime(); } + + public static TimeBase operator +(TimeBase a, ScalarValue b) { return new TimeBase(a.ToUnixStyleTime() + b); } + public static TimeBase operator -(TimeBase a, ScalarValue b) { return new TimeBase(a.ToUnixStyleTime() - b); } + public static TimeBase operator *(TimeBase a, ScalarValue b) { return new TimeBase(a.ToUnixStyleTime() * b); } + public static TimeBase operator /(TimeBase a, ScalarValue b) { return new TimeBase(a.ToUnixStyleTime() / b); } + public static TimeBase operator +(ScalarValue b, TimeBase a) { return new TimeBase(b + a.ToUnixStyleTime()); } + public static TimeBase operator -(ScalarValue b, TimeBase a) { return new TimeBase(b - a.ToUnixStyleTime()); } + public static TimeBase operator *(ScalarValue b, TimeBase a) { return new TimeBase(b * a.ToUnixStyleTime()); } + public static TimeBase operator /(ScalarValue b, TimeBase a) { return new TimeBase(b / a.ToUnixStyleTime()); } + public static bool operator >(TimeBase a, ScalarValue b) { return a.ToUnixStyleTime() > b; } + public static bool operator <(TimeBase a, ScalarValue b) { return a.ToUnixStyleTime() < b; } + public static bool operator >=(TimeBase a, ScalarValue b) { return a.ToUnixStyleTime() >= b; } + public static bool operator <=(TimeBase a, ScalarValue b) { return a.ToUnixStyleTime() <= b; } + public static bool operator >(ScalarValue a, TimeBase b) { return a > b.ToUnixStyleTime(); } + public static bool operator <(ScalarValue a, TimeBase b) { return a < b.ToUnixStyleTime(); } + public static bool operator >=(ScalarValue a, TimeBase b) { return a >= b.ToUnixStyleTime(); } + public static bool operator <=(ScalarValue a, TimeBase b) { return a <= b.ToUnixStyleTime(); } + * + * + */ + + /* + public override bool Equals(object obj) + { + Type compareType = typeof(TimeSpan); + if (compareType.IsInstanceOfType(obj)) + { + TimeSpan t = obj as TimeSpan; + // Check the equality of the span value + return seconds == t.ToUnixStyleTime(); + } + return false; + } + */ + + public override int GetHashCode() + { + return seconds.GetHashCode(); + } + + /* + public static bool operator ==(TimeSpan a, TimeSpan b) + { + Type compareType = typeof(TimeSpan); + if (compareType.IsInstanceOfType(a)) + { + return a.Equals(b); // a is not null, we can use the built in equals function + } + return !compareType.IsInstanceOfType(b); // a is null, return true if b is null and false if not null + } + + public static bool operator !=(TimeSpan a, TimeSpan b) + { + return !(a == b); + } + */ + + public override string ToString() + { + return string.Format("TIME({0:0})", seconds); + } + + /* + public override Dump Dump() + { + var dump = new Dump + { + {DumpSpan, seconds} + }; + + return dump; + } + + public override void LoadDump(Dump dump) + { + seconds = Convert.ToDouble(dump[DumpSpan]); + } + + public int CompareTo(TimeSpan other) + { + return seconds.CompareTo(other.seconds); + } + */ + } +} diff --git a/src/kOS/Suffixed/TimeStamp.cs b/src/kOS/Suffixed/TimeStamp.cs new file mode 100644 index 000000000..a6f9f5629 --- /dev/null +++ b/src/kOS/Suffixed/TimeStamp.cs @@ -0,0 +1,204 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Exceptions; +using kOS.Safe.Compilation; +using kOS.Safe.Serialization; +using kOS.Safe; + +namespace kOS.Suffixed +{ + /// + /// The kind of time that refers to a fixed point in time and therefore + /// starts counting days and years at 1. + /// + [kOS.Safe.Utilities.KOSNomenclature("TimeStamp")] + public class TimeStamp : TimeBase, IComparable + { + public override string DumpName { get { return "timestamp"; } } + + /// + /// Override with either 0 or 1 for whether counting years and days starts counting at 0 or at 1. + /// + protected override double CountOffset { get { return 1.0; } } + + // Only used by CreateFromDump() and the other constructors. + // Don't make it public because it leaves fields + // unpopulated: + private TimeStamp() + { + InitializeSuffixes(); + } + + public TimeStamp(double unixStyleTime) : base(unixStyleTime) + { + } + + /// + /// Make a new TimeStamp giving it all the fields. Note this uses TimeStamp's convention of counting + /// years and days starting at one, not zero. In other words you create a time of zero seconds + /// since epoch by passing in (1, 1, 0, 0, 0). (contrast that with TimeSpan's simlar + /// consturctor where you'd pass in (0, 0, 0, 0, 0).) + /// + /// + /// + /// + /// + /// + public TimeStamp(double year, double day, double hour, double minute, double second) : this() + { + seconds = + (year - 1) * SecondsPerYear + + (day - 1) * SecondsPerDay + + hour * SecondsPerHour + + minute * SecondsPerMinute + + second; + } + + public static TimeStamp CreateFromDump(SafeSharedObjects shared, Dump d) + { + var newObj = new TimeStamp(); + newObj.LoadDump(d); + return newObj; + } + + protected override void InitializeSuffixes() + { + AddSuffix("YEAR", new SetSuffix(CalculateYear, value => ChangeYear(value))); + AddSuffix("DAY", new SetSuffix(CalculateDay, value => ChangeDay(value))); + AddSuffix("HOUR", new SetSuffix(CalculateHour, value => ChangeHour(value))); + AddSuffix("MINUTE", new SetSuffix(CalculateMinute, value => ChangeMinute(value))); + AddSuffix("SECOND", new SetSuffix(CalculateSecond, value => ChangeSecond(value))); + AddSuffix("SECONDS", new Suffix(() => seconds)); + AddSuffix("FULL", new Suffix(() => string.Format("Year {0} Day {1} {2,2:00}:{3,2:00}:{4,2:00}", (int)CalculateYear(), (int)CalculateDay(), (int)CalculateHour(), (int)CalculateMinute(), (int)CalculateSecond()))); + AddSuffix("CLOCK", new Suffix(() => string.Format("{0,2:00}:{1,2:00}:{2,2:00}", (int)CalculateHour(), (int)CalculateMinute(), (int)CalculateSecond()))); + AddSuffix("CALENDAR", new Suffix(() => string.Format("Year {0} Day {1}", CalculateYear(), CalculateDay()))); + } + + // + // Binary arithmetic operators in which both operands are TimeStamp: + // + public static TimeStamp operator +(TimeStamp a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "add", "to"); } + public static TimeSpan operator -(TimeStamp a, TimeStamp b) { return new TimeSpan(a.ToUnixStyleTime() - b.ToUnixStyleTime()); } + public static TimeStamp operator *(TimeStamp a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeStamp operator /(TimeStamp a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + // + // Binary arithmetic operators on TimeStamp and a (Double or Scalar): + // Assume when adding or subtracting that the scalar is a number of seconds. + // Assume when multiplying or dividing that the scalar is unit-less: + // + public static TimeStamp operator +(TimeStamp a, double b) { return new TimeStamp(a.ToUnixStyleTime() + b); } + public static TimeStamp operator -(TimeStamp a, double b) { return new TimeStamp(a.ToUnixStyleTime() - b); } + public static TimeStamp operator *(TimeStamp a, double b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeStamp operator /(TimeStamp a, double b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + public static TimeStamp operator +(double a, TimeStamp b) { return new TimeStamp(a + b.ToUnixStyleTime()); } + public static TimeStamp operator -(double a, TimeStamp b) { return new TimeStamp(a - b.ToUnixStyleTime()); } + public static TimeStamp operator *(double a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeStamp operator /(double a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + public static TimeStamp operator +(TimeStamp a, ScalarValue b) { return new TimeStamp(a.ToUnixStyleTime() + b); } + public static TimeStamp operator -(TimeStamp a, ScalarValue b) { return new TimeStamp(a.ToUnixStyleTime() - b); } + public static TimeStamp operator *(TimeStamp a, ScalarValue b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeStamp operator /(TimeStamp a, ScalarValue b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); ; } + public static TimeStamp operator +(ScalarValue a, TimeStamp b) { return new TimeStamp(a + b.ToUnixStyleTime()); } + public static TimeStamp operator -(ScalarValue a, TimeStamp b) { return new TimeStamp(a - b.ToUnixStyleTime()); } + public static TimeStamp operator *(ScalarValue a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + public static TimeStamp operator /(ScalarValue a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + // + // Binary comparison operators where both operands are TimeStamps: + // + public static bool operator >(TimeStamp a, TimeStamp b) { return a.ToUnixStyleTime() > b.ToUnixStyleTime(); } + public static bool operator <(TimeStamp a, TimeStamp b) { return a.ToUnixStyleTime() < b.ToUnixStyleTime(); } + public static bool operator >=(TimeStamp a, TimeStamp b) { return a.ToUnixStyleTime() >= b.ToUnixStyleTime(); } + public static bool operator <=(TimeStamp a, TimeStamp b) { return a.ToUnixStyleTime() <= b.ToUnixStyleTime(); } + // + // Binary comparison operators between a TimeStamp and a (Scalar or Double): + // Assume the scalar is a Unix-Style seconds since epoch when comparing: + // + public static bool operator >(TimeStamp a, double b) { return a.ToUnixStyleTime() > b; } + public static bool operator <(TimeStamp a, double b) { return a.ToUnixStyleTime() < b; } + public static bool operator >=(TimeStamp a, double b) { return a.ToUnixStyleTime() >= b; } + public static bool operator <=(TimeStamp a, double b) { return a.ToUnixStyleTime() <= b; } + public static bool operator >(double a, TimeStamp b) { return a > b.ToUnixStyleTime(); } + public static bool operator <(double a, TimeStamp b) { return a < b.ToUnixStyleTime(); } + public static bool operator >=(double a, TimeStamp b) { return a >= b.ToUnixStyleTime(); } + public static bool operator <=(double a, TimeStamp b) { return a <= b.ToUnixStyleTime(); } + public static bool operator >(TimeStamp a, ScalarValue b) { return a.ToUnixStyleTime() > b; } + public static bool operator <(TimeStamp a, ScalarValue b) { return a.ToUnixStyleTime() < b; } + public static bool operator >=(TimeStamp a, ScalarValue b) { return a.ToUnixStyleTime() >= b; } + public static bool operator <=(TimeStamp a, ScalarValue b) { return a.ToUnixStyleTime() <= b; } + public static bool operator >(ScalarValue a, TimeStamp b) { return a > b.ToUnixStyleTime(); } + public static bool operator <(ScalarValue a, TimeStamp b) { return a < b.ToUnixStyleTime(); } + public static bool operator >=(ScalarValue a, TimeStamp b) { return a >= b.ToUnixStyleTime(); } + public static bool operator <=(ScalarValue a, TimeStamp b) { return a <= b.ToUnixStyleTime(); } + // + // Binary arithmetic operators where one operand is a TimeStamp and the other is a TimeSpan: + // These could have been defined either here in TimeStamp or in TimeSpan, but to keep it in one + // place they've all been defined here: + // + public static TimeStamp operator +(TimeStamp a, TimeSpan b) { return new TimeStamp(a.ToUnixStyleTime() + b.ToUnixStyleTime()); } + public static TimeStamp operator -(TimeStamp a, TimeSpan b) { return new TimeStamp(a.ToUnixStyleTime() - b.ToUnixStyleTime()); } + public static TimeStamp operator *(TimeStamp a, TimeSpan b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeStamp operator /(TimeStamp a, TimeSpan b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + public static TimeStamp operator +(TimeSpan a, TimeStamp b) { return new TimeStamp(a.ToUnixStyleTime() + b.ToUnixStyleTime()); } + public static TimeStamp operator -(TimeSpan a, TimeStamp b) { return new TimeStamp(a.ToUnixStyleTime() - b.ToUnixStyleTime()); } + public static TimeStamp operator *(TimeSpan a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeStamp operator /(TimeSpan a, TimeStamp b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + + public override bool Equals(object obj) + { + Type compareType = typeof(TimeStamp); + if (compareType.IsInstanceOfType(obj)) + { + TimeStamp t = obj as TimeStamp; + // Check the equality of the span value + return seconds == t.ToUnixStyleTime(); + } + return false; + } + + public override int GetHashCode() + { + return seconds.GetHashCode(); + } + + public static bool operator ==(TimeStamp a, TimeStamp b) + { + Type compareType = typeof(TimeStamp); + if (compareType.IsInstanceOfType(a)) + { + return a.Equals(b); // a is not null, we can use the built in equals function + } + return !compareType.IsInstanceOfType(b); // a is null, return true if b is null and false if not null + } + + public static bool operator !=(TimeStamp a, TimeStamp b) + { + return !(a == b); + } + + public override string ToString() + { + return string.Format("TIMESTAMP({0})", seconds); + } + + public override Dump Dump() + { + var dump = new Dump + { + {DumpName, seconds} + }; + + return dump; + } + + public override void LoadDump(Dump dump) + { + seconds = Convert.ToDouble(dump[DumpName]); + } + + public int CompareTo(TimeStamp other) + { + return seconds.CompareTo(other.seconds); + } + } +} diff --git a/src/kOS/Suffixed/Timespan.cs b/src/kOS/Suffixed/Timespan.cs index 3f563e5fc..8b15e3dd5 100644 --- a/src/kOS/Suffixed/Timespan.cs +++ b/src/kOS/Suffixed/Timespan.cs @@ -1,23 +1,27 @@ using System; using kOS.Safe.Encapsulation; using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Exceptions; +using kOS.Safe.Compilation; using kOS.Safe.Serialization; using System.Collections.Generic; using kOS.Safe; namespace kOS.Suffixed { - [kOS.Safe.Utilities.KOSNomenclature("Timespan")] - public class TimeSpan : SerializableStructure, IComparable + /// + /// The kind of time that refers to differences between two times, + /// and therefore it counts starting at zero for years and days. + /// + [kOS.Safe.Utilities.KOSNomenclature("TimeSpan")] + public class TimeSpan : TimeBase, IComparable { - public const string DumpSpan = "span"; - - double span; - - private int SecondsPerDay { get { return KSPUtil.dateTimeFormatter.Day; } } - private int SecondsPerHour { get { return KSPUtil.dateTimeFormatter.Hour; } } - private int SecondsPerYear { get { return KSPUtil.dateTimeFormatter.Year; } } - private int SecondsPerMinute { get { return KSPUtil.dateTimeFormatter.Minute; } } + public override string DumpName { get { return "timespan"; } } + + /// + /// Override with either 0 or 1 for whether counting years and days starts counting at 0 or at 1. + /// + protected override double CountOffset { get { return 0.0; } } // Only used by CreateFromDump() and the other constructors. // Don't make it public because it leaves fields @@ -27,12 +31,31 @@ private TimeSpan() InitializeSuffixes(); } - public TimeSpan(double unixStyleTime) : this() + public TimeSpan(double unixStyleTime) : base(unixStyleTime) + { + } + + /// + /// Make a new TimeSpan giving it all the fields. Note this uses TimeSpan's convention of counting + /// years and days starting at zero not one. In other words you create a time of zero seconds + /// since epoch by passing in (0, 0, 0, 0, 0). (contrast that with TimeStamp's simlar + /// consturctor where you'd pass in (1, 1, 0, 0, 0).) + /// + /// + /// + /// + /// + /// + public TimeSpan(double year, double day, double hour, double minute, double second) : this() { - span = unixStyleTime; + seconds = + year * SecondsPerYear + + day * SecondsPerDay + + hour * SecondsPerHour + + minute * SecondsPerMinute + + second; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: public static TimeSpan CreateFromDump(SafeSharedObjects shared, Dump d) { var newObj = new TimeSpan(); @@ -40,63 +63,60 @@ public static TimeSpan CreateFromDump(SafeSharedObjects shared, Dump d) return newObj; } - private void InitializeSuffixes() - { - AddSuffix("YEAR", new Suffix(CalculateYear)); - AddSuffix("DAY", new Suffix(CalculateDay)); - AddSuffix("HOUR", new Suffix(CalculateHour)); - AddSuffix("MINUTE", new Suffix(CalculateMinute)); - AddSuffix("SECOND", new Suffix(CalculateSecond)); - AddSuffix("SECONDS", new Suffix(() => span)); - AddSuffix("CLOCK", new Suffix(() => string.Format("{0:00}:{1:00}:{2:00}", (int)CalculateHour(), (int)CalculateMinute(), (int)CalculateSecond()))); - AddSuffix("CALENDAR", new Suffix(() => "Year " + CalculateYear() + ", day " + CalculateDay())); - } - - private ScalarValue CalculateYear() - { - return (int)Math.Floor(span / SecondsPerYear) + 1; - } - - private ScalarValue CalculateDay() - { - return (int)Math.Floor(span % SecondsPerYear / SecondsPerDay) + 1; - } - - private ScalarValue CalculateHour() - { - return (int)Math.Floor(span % SecondsPerDay / SecondsPerHour); - } - - private ScalarValue CalculateMinute() - { - return (int)Math.Floor(span % SecondsPerHour / SecondsPerMinute); - } - - private ScalarValue CalculateSecond() + protected override void InitializeSuffixes() { - return (int)Math.Floor(span % SecondsPerMinute); - } - - public double ToUnixStyleTime() - { - return span; + AddSuffix("YEAR", new SetSuffix(CalculateYear, value => ChangeYear(value))); + AddSuffix("YEARS", new SetSuffix(() => seconds / SecondsPerYear, value => seconds = value * SecondsPerYear)); + AddSuffix("DAY", new SetSuffix(CalculateDay, value =>ChangeDay(value))); + AddSuffix("DAYS", new SetSuffix(() => seconds / SecondsPerDay, value => seconds = value * SecondsPerDay)); + AddSuffix("HOUR", new SetSuffix(CalculateHour, value => ChangeHour(value))); + AddSuffix("HOURS", new SetSuffix(() => seconds / SecondsPerHour, value => seconds = value * SecondsPerHour)); + AddSuffix("MINUTE", new SetSuffix(CalculateMinute, value => ChangeMinute(value))); + AddSuffix("MINUTES", new SetSuffix(() => seconds / SecondsPerMinute, value => seconds = value * SecondsPerMinute)); + AddSuffix("SECOND", new SetSuffix(CalculateSecond, value => ChangeSecond(value))); + AddSuffix("SECONDS", new SetSuffix(() => seconds, value => seconds = value)); + AddSuffix("FULL", new Suffix(() => string.Format("{0}y{1}d{2}h{3}m{4}s", (int)CalculateYear(), (int)CalculateDay(), (int)CalculateHour(), (int)CalculateMinute(), (int)CalculateSecond()))); } + // + // Binary arithmetic operators where both operands are TimeSpans: + // public static TimeSpan operator +(TimeSpan a, TimeSpan b) { return new TimeSpan(a.ToUnixStyleTime() + b.ToUnixStyleTime()); } public static TimeSpan operator -(TimeSpan a, TimeSpan b) { return new TimeSpan(a.ToUnixStyleTime() - b.ToUnixStyleTime()); } + public static TimeSpan operator *(TimeSpan a, TimeSpan b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "multiply", "by"); } + public static TimeSpan operator /(TimeSpan a, TimeSpan b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + // + // Binary arithmetic operators where one operand is TimeSpan and the other is (Double or Scalar): + // Assume the scalars are seconds when doing addition or subtraction operations: + // Assume the scalars are unit-less when doing multiplication or division operations: + // public static TimeSpan operator +(TimeSpan a, double b) { return new TimeSpan(a.ToUnixStyleTime() + b); } public static TimeSpan operator -(TimeSpan a, double b) { return new TimeSpan(a.ToUnixStyleTime() - b); } public static TimeSpan operator *(TimeSpan a, double b) { return new TimeSpan(a.ToUnixStyleTime() * b); } public static TimeSpan operator /(TimeSpan a, double b) { return new TimeSpan(a.ToUnixStyleTime() / b); } - public static TimeSpan operator +(double b, TimeSpan a) { return new TimeSpan(b + a.ToUnixStyleTime()); } - public static TimeSpan operator -(double b, TimeSpan a) { return new TimeSpan(b - a.ToUnixStyleTime()); } - public static TimeSpan operator *(double b, TimeSpan a) { return new TimeSpan(b * a.ToUnixStyleTime()); } - public static TimeSpan operator /(double b, TimeSpan a) { return new TimeSpan(b / a.ToUnixStyleTime()); } - public static TimeSpan operator /(TimeSpan b, TimeSpan a) { return new TimeSpan(b.ToUnixStyleTime() / a.ToUnixStyleTime()); } + public static TimeSpan operator +(double a, TimeSpan b) { return new TimeSpan(a + b.ToUnixStyleTime()); } + public static TimeSpan operator -(double a, TimeSpan b) { return new TimeSpan(a - b.ToUnixStyleTime()); } + public static TimeSpan operator *(double a, TimeSpan b) { return new TimeSpan(a * b.ToUnixStyleTime()); } + public static TimeSpan operator /(double a, TimeSpan b) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + public static TimeSpan operator +(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() + b); } + public static TimeSpan operator -(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() - b); } + public static TimeSpan operator *(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() * b); } + public static TimeSpan operator /(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() / b); } + public static TimeSpan operator +(ScalarValue b, TimeSpan a) { return new TimeSpan(b + a.ToUnixStyleTime()); } + public static TimeSpan operator -(ScalarValue b, TimeSpan a) { return new TimeSpan(b - a.ToUnixStyleTime()); } + public static TimeSpan operator *(ScalarValue b, TimeSpan a) { return new TimeSpan(b * a.ToUnixStyleTime()); } + public static TimeSpan operator /(ScalarValue b, TimeSpan a) { throw new KOSBinaryOperandTypeException(new OperandPair(a, b), "divide", "by"); } + // + // Binary comparison operators where both operands are TimeSpans: + // public static bool operator >(TimeSpan a, TimeSpan b) { return a.ToUnixStyleTime() > b.ToUnixStyleTime(); } public static bool operator <(TimeSpan a, TimeSpan b) { return a.ToUnixStyleTime() < b.ToUnixStyleTime(); } public static bool operator >=(TimeSpan a, TimeSpan b) { return a.ToUnixStyleTime() >= b.ToUnixStyleTime(); } public static bool operator <=(TimeSpan a, TimeSpan b) { return a.ToUnixStyleTime() <= b.ToUnixStyleTime(); } + // + // Binary comparison operators between a TimeSpan and a (Double or Scalar): + // Assume the scalar is a number of seconds in these cases: + // public static bool operator >(TimeSpan a, double b) { return a.ToUnixStyleTime() > b; } public static bool operator <(TimeSpan a, double b) { return a.ToUnixStyleTime() < b; } public static bool operator >=(TimeSpan a, double b) { return a.ToUnixStyleTime() >= b; } @@ -105,15 +125,6 @@ public double ToUnixStyleTime() public static bool operator <(double a, TimeSpan b) { return a < b.ToUnixStyleTime(); } public static bool operator >=(double a, TimeSpan b) { return a >= b.ToUnixStyleTime(); } public static bool operator <=(double a, TimeSpan b) { return a <= b.ToUnixStyleTime(); } - - public static TimeSpan operator +(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() + b); } - public static TimeSpan operator -(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() - b); } - public static TimeSpan operator *(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() * b); } - public static TimeSpan operator /(TimeSpan a, ScalarValue b) { return new TimeSpan(a.ToUnixStyleTime() / b); } - public static TimeSpan operator +(ScalarValue b, TimeSpan a) { return new TimeSpan(b + a.ToUnixStyleTime()); } - public static TimeSpan operator -(ScalarValue b, TimeSpan a) { return new TimeSpan(b - a.ToUnixStyleTime()); } - public static TimeSpan operator *(ScalarValue b, TimeSpan a) { return new TimeSpan(b * a.ToUnixStyleTime()); } - public static TimeSpan operator /(ScalarValue b, TimeSpan a) { return new TimeSpan(b / a.ToUnixStyleTime()); } public static bool operator >(TimeSpan a, ScalarValue b) { return a.ToUnixStyleTime() > b; } public static bool operator <(TimeSpan a, ScalarValue b) { return a.ToUnixStyleTime() < b; } public static bool operator >=(TimeSpan a, ScalarValue b) { return a.ToUnixStyleTime() >= b; } @@ -123,6 +134,13 @@ public double ToUnixStyleTime() public static bool operator >=(ScalarValue a, TimeSpan b) { return a >= b.ToUnixStyleTime(); } public static bool operator <=(ScalarValue a, TimeSpan b) { return a <= b.ToUnixStyleTime(); } + // + // Binary operators in which one operand is a TimeSpan and the other is a TimeStamp: + // + // --- These can either be defined here in TimeSpan or over in TimeStamp but not both. + // --- They were defined over in TimeStamp insteaad of here. + // + public override bool Equals(object obj) { Type compareType = typeof(TimeSpan); @@ -130,14 +148,14 @@ public override bool Equals(object obj) { TimeSpan t = obj as TimeSpan; // Check the equality of the span value - return span == t.ToUnixStyleTime(); + return seconds == t.ToUnixStyleTime(); } return false; } public override int GetHashCode() { - return span.GetHashCode(); + return seconds.GetHashCode(); } public static bool operator ==(TimeSpan a, TimeSpan b) @@ -157,14 +175,14 @@ public override int GetHashCode() public override string ToString() { - return string.Format("TIME({0:0})", span); + return string.Format("TIMESPAN({0})", seconds); } public override Dump Dump() { var dump = new Dump { - {DumpSpan, span} + {DumpName, seconds} }; return dump; @@ -172,12 +190,12 @@ public override Dump Dump() public override void LoadDump(Dump dump) { - span = Convert.ToDouble(dump[DumpSpan]); + seconds = Convert.ToDouble(dump[DumpName]); } public int CompareTo(TimeSpan other) { - return span.CompareTo(other.span); + return seconds.CompareTo(other.seconds); } } } diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj index 97575cb9f..59bea5c55 100644 --- a/src/kOS/kOS.csproj +++ b/src/kOS/kOS.csproj @@ -210,6 +210,8 @@ + + @@ -243,7 +245,7 @@ - + From be27b7b1dd1fb794810cfcef82bf2a6a28636604 Mon Sep 17 00:00:00 2001 From: Dunbaratu Date: Thu, 14 Jan 2021 13:56:31 -0600 Subject: [PATCH 2/2] Altered documentation with /u/nuggreat's suggestions. --- doc/source/structures/misc/time.rst | 84 +++++++++++++++-------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/doc/source/structures/misc/time.rst b/doc/source/structures/misc/time.rst index 01685624a..4b30c46c9 100644 --- a/doc/source/structures/misc/time.rst +++ b/doc/source/structures/misc/time.rst @@ -168,27 +168,21 @@ Special variable TIME Kerbal Calendar Differs From Earth's ------------------------------------ - Note that the notion of "how many hours in a day" and "how many days in a year" - depends on the gameworld, not our real world. Kerbin has a shorter day, and - a longer year in days as a result, than Earth. But there is an option in - KSP's main settings screen that can toggle whether the game counts Kerbin - days (6 hours per day) or Earth days (24 hours per day) this notion, and - kOS will use whatever option you set it to alter the meaning of the Day - suffix of a :struct:`TIMESTAMP` and a :struct:`TIMESPAN`. + Note that the notion of "how many hours in a day" and "how many days in + a year" depends on the gameworld, not our real world. Kerbin has a + shorter day (6 hours) than Earth, and 426 of these days make up a Kerbin + year. But there is an option in KSP's main settings screen + that can toggle whether the game counts with Kerbin days (6 hours per day) + or Earth days (24 hours per day). kOS will use whatever + option you set it to alter the meaning of the Day suffix of a + :struct:`TIMESTAMP` and a :struct:`TIMESPAN`. You can see what + the length of a day in the calendar is set to by reading + :attr:`Kuniverse:HOURSPERDAY`. Also note that the mods that alter the calendar for other solar systems, - if they inject changes into KSP's main game, will cause these values to + if they inject changes into KSP's main game, can cause these values to change too. -.. warning:: - - Please be aware that the kind of calendar :struct:`TimeStamp`'s use will depend on your KSP settings. The main KSP game supports both Kerbin time and Earth time and changing that setting will affect how :struct:`TimeStamp` works in kOS. - - The difference is whether 1 day = 6 hours or 1 day = 24 hours. - - You can access this setting from your script by using - :attr:`Kuniverse:HOURSPERDAY`. - .. highlight:: kerboscript Using TIME or TIME() to detect when the physics have been updated 'one tick' @@ -243,10 +237,10 @@ TimeStamp Structure - :struct:`Scalar` - Year-hand number * - :attr:`DAY` - - :struct:`Scalar` (1-426) + - :struct:`Scalar` (range varies by universe) - Day-hand number * - :attr:`HOUR` - - :struct:`Scalar` (0-5) + - :struct:`Scalar` (0-5) or (0-23) depending - Hour-hand number * - :attr:`MINUTE` - :struct:`Scalar` (0-59) @@ -300,11 +294,16 @@ TimeStamp Structure .. attribute:: TimeStamp:DAY :access: Get only - :type: :struct:`Scalar` (1-426) or (1-356) + :type: :struct:`Scalar` (range varies by universe) Day-hand number. Kerbin has 426 days in its year if using Kerbin's - 6 hour day (less if :attr:`Kuniverse:HOURSPERDAY` is 24 meaning - the game is configured to show Earthlike days not Kerbin days. + 6 hour day (one fourth of that if :attr:`Kuniverse:HOURSPERDAY` is + 24 meaning the game is configured to show Earthlike days not Kerbin + days.) + + Also note that with mods installed you might not be looking at + the stock universe, which could change the range this field could + be if it changes how long a year is in your solar system. Note that the first day of the year is actually day 1, not day 0. @@ -465,31 +464,31 @@ TimeSpan Structure - Whole number of years in the span. * - :attr:`YEARS` - :struct:`Scalar` - - TOTAL time in the span expressed in years. + - *TOTAL* time in the span expressed in years. * - :attr:`DAY` - - :struct:`Scalar` (1-426) + - :struct:`Scalar` (range vaires by universe) - Whole number of days after the last whole year in the span. * - :attr:`DAYS` - - :struct:`Scalar` (1-426) - - TOTAL time in the span expressed in days. + - :struct:`Scalar` + - *TOTAL* time in the span expressed in days. * - :attr:`HOUR` - - :struct:`Scalar` (0-5) + - :struct:`Scalar` (0-5) or (0-23) - Whole number of hours after the last whole day in the span. * - :attr:`HOURS` - - :struct:`Scalar` (0-5) - - TOTAL time in the span expressed in hours. + - :struct:`Scalar` + - *TOTAL* time in the span expressed in hours. * - :attr:`MINUTE` - :struct:`Scalar` (0-59) - Whole number of minutes after the last whole hour in the span. * - :attr:`MINUTES` - - :struct:`Scalar` (0-59) - - TOTAL time in the span expressed in minutes. + - :struct:`Scalar` + - *TOTAL* time in the span expressed in minutes. * - :attr:`SECOND` - :struct:`Scalar` (0-59) - Whole number of seconds after the last whole minute in the span. * - :attr:`SECONDS` - :struct:`Scalar` (fractional) - - Total Seconds since Epoch (includes fractional partial seconds) + - *TOTAL* Seconds since Epoch (includes fractional partial seconds) .. note:: @@ -546,7 +545,7 @@ TimeSpan Structure :access: Get only :type: :struct:`Scalar` - TOTAL time in the span, expressed in units of years. This is not + *TOTAL* time in the span, expressed in units of years. This is not the same as :attr:`TimeSpan:YEAR` because it includes a fractional part and is the *entire* span, not just the whole number of years. Example: If there are 426 days in a Year, and the Timespan is @@ -557,12 +556,17 @@ TimeSpan Structure .. attribute:: TimeSpan:DAY :access: Get only - :type: :struct:`Scalar` (1-426) or (1-356) + :type: :struct:`Scalar` (range varies by universe) Whole number of days remaining after the lst full year within the span. Kerbin has 426 days in a year if using Kerbin's - 6 hour day (less if :attr:`Kuniverse:HOURSPERDAY` is 24 meaning - the game is configured to show Earthlike days not Kerbin days. + 6 hour day (one fourth as much if if :attr:`Kuniverse:HOURSPERDAY` + is 24 meaning the game is configured to show Earthlike days not + Kerbin days. + + The range of possible values could be different if you have mods + installed that replace the stock solar system with a different + solar system and thus alter how long your homeworld's year is. Note that for spans the first day of the year is the zero-th day, not the 1-th day. This is a difference from how it @@ -573,7 +577,7 @@ TimeSpan Structure :access: Get only :type: :struct:`Scalar` - TOTAL time in the span, expressed in units of days. This is not + *TOTAL* time in the span, expressed in units of days. This is not the same as :attr:`TimeSpan:DAY` because it includes a fractional part and is the *entire* span, not just the whole number of days leftover in the last partial year. @@ -596,7 +600,7 @@ TimeSpan Structure :access: Get only :type: :struct:`Scalar` - TOTAL time in the span, expressed in units of hours. This is not + *TOTAL* time in the span, expressed in units of hours. This is not the same as :attr:`TimeSpan:HOUR` because it includes a fractional part and is the *entire* span, not just the whole number of hours leftover in the last partial day. @@ -617,7 +621,7 @@ TimeSpan Structure :access: Get only :type: :struct:`Scalar` - TOTAL time in the span, expressed in units of minutes. This is not + *TOTAL* time in the span, expressed in units of minutes. This is not the same as :attr:`TimeSpan:MINUTE` because it includes a fractional part and is the *entire* span, not just the whole number of minutes leftover in the last partial hour. @@ -639,7 +643,7 @@ TimeSpan Structure :access: Get only :type: :struct:`Scalar` (float) - Total Seconds in the TimeSpan, including fractonal part. Note + *TOTAL* Seconds in the TimeSpan, including fractonal part. Note this is NOT the same as :attr:`TimeSpan:SECOND` (singular), because this is the total span of time expressed in seconds, and not just the leftover seconds in the last minute of the span.