672 lines
16 KiB
Plaintext
672 lines
16 KiB
Plaintext
note
|
|
description: "[
|
|
An exact point of time as on a gregorian callendar.
|
|
Has a `Year', `Month', and `Day' (i.e. a date).
|
|
]"
|
|
names: "date, time"
|
|
date: "1 Jan 99"
|
|
author: "Jimmy J. Johnson"
|
|
copyright: "Copyright 2009, Jimmy J. Johnson"
|
|
license: "Eiffel Forum License v2 (see forum.txt)"
|
|
URL: "$URL:$"
|
|
date: "$Date: 2009-06-25 21:37:23 -0400 (Thu, 25 Jun 2009) $"
|
|
revision: "$Revision: 7 $"
|
|
|
|
class
|
|
YMD_TIME
|
|
|
|
inherit
|
|
|
|
ABSTRACT_TIME
|
|
rename
|
|
as_integer as as_days,
|
|
from_integer as from_days
|
|
redefine
|
|
default_create,
|
|
is_valid,
|
|
duration_anchor,
|
|
interval_anchor
|
|
end
|
|
|
|
create
|
|
default_create,
|
|
set_now,
|
|
set_now_utc,
|
|
set,
|
|
from_days,
|
|
from_string
|
|
|
|
feature {NONE} -- Initialization
|
|
|
|
default_create
|
|
-- Create an instance based on todays date.
|
|
do
|
|
set_now
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
year: INTEGER
|
|
-- Year part of the date.
|
|
|
|
month: INTEGER
|
|
-- Month part of the date.
|
|
|
|
day: INTEGER
|
|
-- Day part of the date.
|
|
do
|
|
Result := internal_day
|
|
if Result > last_day_of_month then
|
|
Result := last_day_of_month
|
|
end
|
|
end
|
|
|
|
week_number: INTEGER
|
|
-- Week of the year containing this date.
|
|
local
|
|
d: YMD_TIME
|
|
first_d: INTEGER -- Jan 1st is on what day?
|
|
do
|
|
create d
|
|
d.set (year, 1, 1)
|
|
first_d := d.weekday
|
|
Result := (((julian + first_d - 1 - 1) // 7) + 1)
|
|
ensure
|
|
result_large_enough: Result >= 1
|
|
result_small_enough: Result <= 54 -- 53 ? 54 if leapyear falls just right.
|
|
end
|
|
|
|
last_day_of_month: INTEGER
|
|
-- Date of last day for current month
|
|
do
|
|
inspect
|
|
month
|
|
when 2 then
|
|
if is_leapyear then
|
|
Result := 29
|
|
else
|
|
Result := 28
|
|
end
|
|
when 4, 6, 9, 11 then
|
|
Result := 30
|
|
else
|
|
Result := 31
|
|
end
|
|
ensure
|
|
day_in_range: Result >= 28 and Result <= 31
|
|
good_not_leap: Result = 28 implies (month = 2 and not is_leapyear)
|
|
good_in_leap: Result = 29 implies (month = 2 and is_leapyear)
|
|
good_30s: Result = 30 implies (month = 4 or month = 6 or month = 9 or month = 11)
|
|
good_31s: Result = 31 implies (month=1 or month=3 or month=5 or month=7 or month=8 or month=10 or month=12)
|
|
end
|
|
|
|
days_remaining_this_month: INTEGER
|
|
-- Number of days from current until end of month.
|
|
-- Used in some calculations.
|
|
do
|
|
Result := last_day_of_month - day
|
|
ensure
|
|
valid_result: Result >= 0 and Result < last_day_of_month
|
|
end
|
|
|
|
julian: INTEGER
|
|
-- Day of the year between 1 and 366
|
|
local
|
|
n,i : INTEGER
|
|
do
|
|
from
|
|
i := 1
|
|
until
|
|
i >= month
|
|
loop
|
|
inspect i
|
|
when 2 then
|
|
if is_leapyear then
|
|
n := n + 29
|
|
else
|
|
n := n + 28
|
|
end
|
|
when 4,6,9,11 then
|
|
n := n + 30
|
|
else
|
|
n := n + 31
|
|
end
|
|
i := i + 1
|
|
end
|
|
result := n + day
|
|
ensure
|
|
valid_leapyear_result: is_leapyear implies (1 <= Result and Result <= 366)
|
|
valid_result: not is_leapyear implies (1 <= Result and Result <= 365)
|
|
end
|
|
|
|
|
|
weekday: INTEGER
|
|
-- 1 for Sunday, 2 for Monday, etc
|
|
-- Only works as far back as ~2 Mar 0001. ???
|
|
local
|
|
x : INTEGER
|
|
do
|
|
x := internal\\7 + 1 + 1
|
|
if x > 7 then -- it can only be 8
|
|
x := 1
|
|
end
|
|
result := x
|
|
ensure
|
|
valid_weekday: 1 <= Result and Result <= 7
|
|
end
|
|
|
|
as_string: STRING
|
|
-- The date represented as a string with no spaces.
|
|
-- 18 Jan 2005 would be "20050118".
|
|
do
|
|
create Result.make (10)
|
|
if is_bc then
|
|
Result.append ("BC")
|
|
end
|
|
if not (year.abs >= 1000) then
|
|
Result.append ("0")
|
|
end
|
|
if not (year.abs >= 100) then
|
|
Result.append ("0")
|
|
end
|
|
if not (year.abs >= 10) then
|
|
Result.append ("0")
|
|
end
|
|
Result.append (year.abs.out)
|
|
if not (month >= 10) then
|
|
Result.append ("0")
|
|
end
|
|
Result.append (month.out)
|
|
if not (day >= 10) then
|
|
Result.append ("0")
|
|
end
|
|
Result.append (day.out)
|
|
end
|
|
|
|
as_days: INTEGER
|
|
-- The number of days from midnight (00:00:00)
|
|
-- on 1 Jan 1970 to the beginning Current's `day'.
|
|
local
|
|
t: YMD_TIME
|
|
do
|
|
create t
|
|
t.set (1970, 1, 1)
|
|
Result := days_between (t)
|
|
end
|
|
|
|
feature -- Element Change
|
|
|
|
from_days (a_days: INTEGER)
|
|
-- Change Current to the time represented by `a_days'.
|
|
-- `A_days' is assumed to be the number of days since 1 Jan 1970.
|
|
-- `A_days' must represent a date that is not BC
|
|
do
|
|
set (1970, 1, 1)
|
|
add_days (a_days)
|
|
end
|
|
|
|
from_string (a_string: STRING)
|
|
-- Change Current to the time represented by `a_string'.
|
|
-- It must be in the format as provided by `as_string'.
|
|
local
|
|
d, m, y: INTEGER
|
|
do
|
|
y := a_string.substring (1, 4).to_integer
|
|
m := a_string.substring (5, 6).to_integer
|
|
d := a_string.substring (7, 8).to_integer
|
|
set (y, m, d)
|
|
end
|
|
|
|
set_now
|
|
-- Set the current object to today's date.
|
|
-- This was copied from ISE's DATE class with the one minor change.
|
|
do
|
|
C_date.update
|
|
set (C_date.year_now, C_date.month_now, C_date.day_now)
|
|
end
|
|
|
|
set_now_utc
|
|
-- Set the current object to today's date in utc format.
|
|
-- This was copied from ISE's DATE class with the one minor change.
|
|
do
|
|
C_date.update
|
|
set (C_date.year_now, C_date.month_now, C_date.day_now)
|
|
end
|
|
|
|
set_now_utc_fine
|
|
-- Set the current object to today's date in utc format.
|
|
-- This was copied from ISE's TIME class with minor changes.
|
|
do
|
|
C_date.update
|
|
set (C_date.year_now, C_date.month_now, C_date.day_now)
|
|
end
|
|
|
|
set (a_year, a_month, a_day: INTEGER)
|
|
-- Give date new year, month, and day.
|
|
-- If day > num days in month then day will return last day in the month.
|
|
require
|
|
realistic_year: a_year /= 0
|
|
realistic_month: a_month >= 1 and a_month <= 12
|
|
realistic_day: a_day >= 1 and a_day <= 31
|
|
do
|
|
year := a_year
|
|
month := a_month
|
|
internal_day := a_day
|
|
ensure
|
|
year_assigned: year = a_year
|
|
month_assigned: month = a_month
|
|
day_assigned: day = a_day
|
|
end
|
|
|
|
set_year (a_year: INTEGER)
|
|
-- Change the year.
|
|
require
|
|
realistic_year: a_year /= 0
|
|
do
|
|
year := a_year
|
|
ensure
|
|
year_assigned: year = a_year
|
|
end
|
|
|
|
set_month (a_month: INTEGER)
|
|
-- Change the month.
|
|
require
|
|
realistic_month: a_month >= 1 and a_month <= 12
|
|
do
|
|
month := a_month
|
|
ensure
|
|
month_assigned: month = a_month
|
|
end
|
|
|
|
set_day (a_day: INTEGER)
|
|
-- Change the day.
|
|
-- If a_day > number of days in the month then
|
|
-- 'day' will be the last day of month.
|
|
require
|
|
realistic_day: a_day >= 1 and a_day <= 31
|
|
do
|
|
internal_day := a_day
|
|
ensure
|
|
day_assigned: day = a_day
|
|
end
|
|
|
|
truncate_to_years
|
|
-- Set the day to first day of month 1.
|
|
-- Use when all but the `year' is to be ignored.
|
|
do
|
|
set_day (1)
|
|
set_month (1)
|
|
ensure
|
|
year_unchanged: year = old year
|
|
month_one: month = 1
|
|
day_one: day = 1
|
|
end
|
|
|
|
truncate_to_months
|
|
-- Set day to first day of current month.
|
|
-- Use when the `day' portion of date is to be ignored.
|
|
do
|
|
set_day (1)
|
|
ensure
|
|
year_unchanged: year = old year
|
|
month_unchanged: month = old month
|
|
day_one: day = 1
|
|
end
|
|
|
|
feature -- Status report
|
|
|
|
is_leapyear: BOOLEAN
|
|
-- Is this a leapyear?
|
|
do
|
|
if is_bc then
|
|
Result := (year + 1) \\ 4 = 0 and not ((year + 1) \\ 400 = 0)
|
|
else
|
|
Result := year \\ 4 = 0 and (not (year \\ 100 = 0) or else year \\ 400 = 0)
|
|
end
|
|
end
|
|
|
|
is_bc: BOOLEAN
|
|
-- Does the date represent a date B.C. (ie year < 1)
|
|
do
|
|
Result := year <= -1
|
|
ensure
|
|
definition: Result implies year <= -1
|
|
end
|
|
|
|
is_representable_as_integer: BOOLEAN
|
|
-- Can Current be represented as an integer?
|
|
do
|
|
Result := not is_bc and then
|
|
(Current >= Minimum_representable_date and Current <= Maximum_representable_date)
|
|
end
|
|
|
|
feature -- Querry
|
|
|
|
days_between (other: like Current): INTEGER
|
|
-- Days between this date and 'other'.
|
|
-- Only works back to ~2 Mar 0001.
|
|
require
|
|
other_exists : other /= Void
|
|
do
|
|
Result := (other.internal - internal).abs
|
|
ensure
|
|
definition: Result = (other.internal - internal).abs
|
|
end
|
|
|
|
time_between (other: like Current): like Duration_anchor
|
|
-- The difference between two dates as a duration
|
|
local
|
|
larger, smaller: like Current
|
|
y, m, d: INTEGER
|
|
do
|
|
larger := max (other)
|
|
smaller := min (other)
|
|
y := larger.year - smaller.year
|
|
m := larger.month - smaller.month
|
|
d := larger.day - smaller.day
|
|
if d < 0 then
|
|
d := d + smaller.last_day_of_month
|
|
m := m - 1
|
|
end
|
|
if m < 0 then
|
|
m := m + 12
|
|
y := y - 1
|
|
end
|
|
create Result
|
|
Result.set (y, m, d)
|
|
if Current < other then
|
|
Result.negate
|
|
end
|
|
end
|
|
|
|
is_valid_integer_representation (a_integer: INTEGER): BOOLEAN
|
|
-- Is `a_integer' in range to be converted to a time?
|
|
-- Dependent on the `internal' representation of dates.
|
|
do
|
|
-- These values were found by trial and error. This will give a
|
|
-- date from 1 Jan 0001 to 18 Oct 1,469,902, which, I believe, is
|
|
-- far enough into the future.
|
|
Result := a_integer >= 1721426 and a_integer <= 538592032
|
|
ensure then
|
|
definition: Result implies (a_integer >= 1721426) and then
|
|
(a_integer <= 538592032) -- dependent on `internal'
|
|
end
|
|
|
|
is_valid_string_representation (a_string: STRING): BOOLEAN
|
|
-- Is `a_string' in a format that can be used to initialize Current?
|
|
local
|
|
bcs: detachable STRING
|
|
ys, ms, ds: STRING
|
|
y, m, d: INTEGER
|
|
pad: INTEGER -- add 2 if is "BC"
|
|
do
|
|
if a_string /= Void and then (a_string.count = 8 or a_string.count = 10) then
|
|
if a_string.count = 10 then
|
|
pad := 2
|
|
bcs := a_string.substring (1, 2)
|
|
end
|
|
ys := a_string.substring (1 + pad, 4 + pad)
|
|
ms := a_string.substring (5 + pad, 6 + pad)
|
|
ds := a_string.substring (7 + pad, 8 + pad)
|
|
if ys.is_integer and then ms.is_integer and then ds.is_integer then
|
|
y := ys.to_integer
|
|
m := ms.to_integer
|
|
d := ds.to_integer
|
|
if (y /= 0) and then (m >= 0 and m < 12)and then (d >= 0 and d <= 31) then
|
|
Result := True
|
|
if bcs /= Void and then not equal (bcs, "BC") then
|
|
Result := False
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
feature -- Basic operations
|
|
|
|
add_years (a_num: INTEGER)
|
|
-- Add 'a_num' number of years to the date. Works for negative numbers also.
|
|
local
|
|
y: INTEGER
|
|
do
|
|
y := year
|
|
year := year + a_num
|
|
if year = 0 then -- Must preserve invarient: year can not be 0.
|
|
if y < 0 then -- year was less than 0 and increased to 0.
|
|
year := 1
|
|
else -- year was greater than 0 and decreased to 0.
|
|
year := -1
|
|
end
|
|
end
|
|
ensure
|
|
valid_date: is_valid
|
|
end
|
|
|
|
add_months (a_num: INTEGER)
|
|
-- Add 'a_num' number of months to the date. Works for negative numbers also.
|
|
local
|
|
m: INTEGER -- store month prior making month valid to call 'add_years'.
|
|
do
|
|
month := month + a_num
|
|
m := month
|
|
month := month \\ 12 -- preserve invarient
|
|
if month < 1 then
|
|
month := month + 12 -- preserve invarient
|
|
add_years (-1)
|
|
end
|
|
add_years (m // 12) -- add a year for every multiple of 12.
|
|
ensure
|
|
valid_date: is_valid
|
|
end
|
|
|
|
add_days (a_num: INTEGER)
|
|
-- Add 'a_num' number of days to the date. Works for negative numbers also.
|
|
local
|
|
i: INTEGER
|
|
do
|
|
if a_num > 0 then
|
|
from i := a_num
|
|
until i <= days_remaining_this_month
|
|
loop
|
|
i := i - (days_remaining_this_month + 1)
|
|
set_day (1)
|
|
add_months (1)
|
|
end
|
|
set_day (day + i)
|
|
elseif a_num < 0 then
|
|
from
|
|
i := a_num.abs
|
|
until
|
|
i < day
|
|
loop
|
|
i := (day - i).abs
|
|
add_months (-1)
|
|
set_day (last_day_of_month)
|
|
end
|
|
set_day (day - i)
|
|
else
|
|
-- do nothing if a_num = 0
|
|
end
|
|
ensure
|
|
valid_date: is_valid
|
|
end
|
|
|
|
add_duration (a_duration: like Duration_anchor)
|
|
-- Add a length of time (in years, months, and days) to the date.
|
|
do
|
|
add_days (a_duration.days)
|
|
add_months (a_duration.months)
|
|
add_years (a_duration.years)
|
|
end
|
|
|
|
feature -- Comparison
|
|
|
|
is_less alias "<" (other: like Current): BOOLEAN
|
|
-- Does this date come before 'other'?
|
|
require else
|
|
other_not_void: other /= Void
|
|
do
|
|
Result := year < other.year or else
|
|
(year = other.year) and (month < other.month) or else
|
|
(year = other.year) and (month = other.month) and (day < other.day)
|
|
ensure then
|
|
-- definition: year < other.year or else
|
|
-- (year = other.year) and (month < other.month) or else
|
|
-- (year = other.year) and (month = other.month) and (day < other.day)
|
|
end
|
|
|
|
feature {YMD_TIME} -- Implementation
|
|
|
|
frozen internal: INTEGER
|
|
-- Internal representation of YMD_TIME
|
|
-- Used internally by some features.
|
|
-- Does not work for BC dates; only works back to 1 January 0001,
|
|
-- at which time the result is 1,721,426.
|
|
-- Will work up to a date of 18 Oct 1,469,902 (found by trial).
|
|
require
|
|
not_bc: not is_bc
|
|
local
|
|
c, ya : INTEGER;
|
|
d,m,y : INTEGER;
|
|
do
|
|
d := day;
|
|
m := month;
|
|
y := year;
|
|
if m > 2 then
|
|
m := m - 3;
|
|
else
|
|
m := m + 9;
|
|
y := y - 1;
|
|
end
|
|
c := y // 100;
|
|
ya := y - 100 * c;
|
|
result := (146097 * c) // 4 + (1461 * ya) // 4 + (153 * m + 2) // 5 + d + 1721119;
|
|
ensure
|
|
result_large_enough: Result >= 1721426
|
|
result_small_enough: Result <= 538592032
|
|
end
|
|
|
|
frozen from_internal (num: INTEGER)
|
|
-- Create a YMD_TIME from an internal representation.
|
|
local
|
|
y,m,d,j : INTEGER
|
|
do
|
|
j := num;
|
|
j := j - 1721119
|
|
y := (4 * j - 1) // 146097; j := 4 * j - 1 - 146097 * y;
|
|
d := j // 4;
|
|
j := (4 * d + 3) // 1461; d := 4 * d + 3 - 1461 * j;
|
|
d := (d + 4) // 4;
|
|
m := (5 * d - 3) // 153; d := 5 * d - 3 - 153 * m;
|
|
d := (d + 5) // 5;
|
|
y := 100 * y + j;
|
|
if m < 10 then
|
|
m := m + 3;
|
|
else
|
|
m := m - 9;
|
|
y := y + 1;
|
|
end;
|
|
internal_day := d;
|
|
month := m;
|
|
year := y;
|
|
end
|
|
|
|
feature {NONE} -- Implementation
|
|
|
|
internal_day: INTEGER
|
|
-- Used to save last day of month if day is greater than 28, 30, or 31.
|
|
-- Actual day is calculated from this value.
|
|
|
|
is_valid: BOOLEAN
|
|
-- Is the date logical?
|
|
do
|
|
Result := is_valid_year and is_valid_month and is_valid_day
|
|
end
|
|
|
|
is_valid_year: BOOLEAN
|
|
-- Is the year logical?
|
|
-- Only invalid year is year "0".
|
|
do
|
|
Result := year /= 0
|
|
ensure
|
|
definition: year /= 0
|
|
end
|
|
|
|
is_valid_month: BOOLEAN
|
|
-- Is the month logical?
|
|
do
|
|
Result := 1 <= month and month <= 12
|
|
ensure
|
|
definition: 1 <= month and month <= 12
|
|
end
|
|
|
|
is_valid_day: BOOLEAN
|
|
-- Is the day logical based on month and year?
|
|
do
|
|
Result := day >= 1 and then
|
|
( (day <= 28) or else
|
|
((month=4 or month=6 or month=9 or month=11) and then day <= 30) or else
|
|
((month=1 or month=3 or month=5 or month=7 or month=8 or month=10 or month=12) and then day <= 31) or else
|
|
(month=2 and is_leapyear and day <= 29) )
|
|
end
|
|
|
|
feature {NONE} -- Implementation
|
|
|
|
Minimum_representable_date: like Current
|
|
-- The earliest date that can be represented as an integer.
|
|
-- This value is dependent on the implementation of `internal' and
|
|
-- was found by trial and error to be 1 Jan 0001.
|
|
do
|
|
create Result
|
|
Result.set_year (1)
|
|
Result.set_month (1)
|
|
Result.set_day (1)
|
|
end
|
|
|
|
Maximum_representable_date: like Current
|
|
-- The latest date that can be represented as an integer.
|
|
-- This value is dependent on the implementation of `internal' and
|
|
-- was found by trial and error to be 18 Oct 1,469,902.
|
|
do
|
|
create Result
|
|
Result.set_year (1_469_902)
|
|
Result.set_month (10)
|
|
Result.set_day (18)
|
|
end
|
|
|
|
feature {NONE} -- Anchors (for covariant redefinitions)
|
|
|
|
duration_anchor: YMD_DURATION
|
|
-- Anchor for features using durations.
|
|
-- Not to be called; just used to anchor types.
|
|
-- Declared as a feature to avoid adding an attribute.
|
|
require else
|
|
not_callable: False
|
|
do
|
|
check
|
|
do_not_call: False then
|
|
-- Because give no info; simply used as anchor.
|
|
end
|
|
end
|
|
|
|
interval_anchor: YMD_INTERVAL
|
|
-- Anchor for features using intervals.
|
|
-- Not to be called; just used to anchor types.
|
|
-- Declared as a feature to avoid adding an attribute.
|
|
require else
|
|
not_callable: False
|
|
do
|
|
check
|
|
do_not_call: False then
|
|
-- Because give no info; simply used as anchor.
|
|
end
|
|
end
|
|
|
|
invariant
|
|
|
|
is_valid: is_valid
|
|
|
|
end -- class YMD_TIME
|
|
|
|
|